`numberize` - rewrite only positive leaves, add map value numberization by lukaszsamson · Pull Request #15801 · elixir-lang/elixir · GitHub
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions lib/elixir/lib/module/types/descr.ex
34 changes: 34 additions & 0 deletions lib/elixir/test/elixir/module/types/descr_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2737,6 +2737,40 @@ defmodule Module.Types.DescrTest do
assert dynamic(list(binary(), float())) |> numberize() ==
dynamic(list(binary(), number()))
end

test "with domain keys" do
# `==` compares map keys exactly but coerces values, so only the types
# the domain keys point to are widened.
assert closed_map([{domain_key(:integer), integer()}]) |> numberize() ==
closed_map([{domain_key(:integer), number()}])

assert open_map([{domain_key(:tuple), tuple([float()])}, {:a, {integer(), false}}])
|> numberize() ==
open_map([{domain_key(:tuple), tuple([number()])}, {:a, {number(), false}}])
end

test "with negations" do
# Negations must not be widened: `{1}` belongs to `term() and not {float()}`
# and `{1} == {1.0}`, so both must survive numberize.
negated = opt_difference(term(), tuple([float()]))
assert subtype?(tuple([integer()]), numberize(negated))
assert subtype?(tuple([float()]), numberize(negated))

negated = opt_difference(non_empty_list(integer(), atom()), non_empty_list(float(), atom()))
assert subtype?(non_empty_list(integer(), atom()), numberize(negated))
assert subtype?(non_empty_list(float(), atom()), numberize(negated))

negated = opt_difference(open_map(a: {integer(), false}), open_map(a: {float(), false}))
assert subtype?(open_map(a: {integer(), false}), numberize(negated))
assert subtype?(open_map(a: {float(), false}), numberize(negated))

# Negations untouched by numberize are kept as is
type = opt_difference(tuple(), tuple([binary()]))
assert numberize(type) == type

type = opt_difference(list(integer(), atom()), list(binary(), atom()))
assert numberize(type) == opt_difference(list(number(), atom()), list(binary(), atom()))
end
end

describe "map_get" do
Expand Down
43 changes: 43 additions & 0 deletions lib/elixir/test/elixir/module/types/expr_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2094,6 +2094,49 @@ defmodule Module.Types.ExprTest do
atom([:non_empty_map, :maybe_empty_map])
end

test "does not discard equal values when narrowing against negated types" do
# `other` carries a negation (`... and not {float()}`) from the clause
# subtraction. Widening integers/floats inside that negation would wrongly
# remove `{1}`, even though `{1} == {1.0}` at runtime.
assert typecheck!(
[v, q],
(
other =
case v do
{a} when is_float(a) -> {:float, a}
other -> other
end

w = if q, do: {1}, else: {1.5}

case w do
x when x == other -> {:matched, x}
_ -> :nomatch
end
)
)
|> to_quoted_string() ==
"dynamic({:matched, {float() or integer()}}) or :nomatch"
end

test "does not discard equal values when narrowing against domain keys" do
# `==` coerces map values, so narrowing `x` from `x == y` must widen the
# `integer() => integer()` domain value to `integer() => number()`.
assert typecheck!(
(
y = %{1 => 1}
w = %{1 => 1.0}

case w do
x when x == y and map_size(x) == 1 -> {:eq, x}
_ -> :ne
end
)
)
|> to_quoted_string() ==
":ne or {:eq, %{integer() => float()} and not empty_map()}"
end

Comment on lines +2097 to +2139

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unit tests on numberize are enough here!

Suggested change

test "consider external variables as not precise" do
assert typecheck!(
[x],
Expand Down
Loading