diff --git a/lib/elixir/lib/exception.ex b/lib/elixir/lib/exception.ex index 3d2fdc60b40..51ba620fa4b 100644 --- a/lib/elixir/lib/exception.ex +++ b/lib/elixir/lib/exception.ex @@ -313,6 +313,15 @@ defmodule Exception do ), do: map_node?(node_1) and map_key_node?(node_2) and struct_validation_node?(node_3) + defp struct_macro?( + {:and, _, + [ + {:and, _, [%{node: node_1 = {_, _, [arg]}}, %{node: node_2 = {_, _, [arg, _]}}]}, + %{node: node_3 = {_, _, [{_, _, [_, arg]}, _]}} + ]} + ), + do: map_node?(node_1) and map_key_node?(node_2) and struct_validation_node?(node_3) + defp struct_macro?( {:and, _, [ diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index 4ae542edc87..4530dcc6df7 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -2681,11 +2681,19 @@ defmodule Kernel do invalid_match!(:is_struct) :guard -> - quote do - is_map(unquote(term)) and - (is_atom(unquote(name)) or :fail) and - :erlang.is_map_key(:__struct__, unquote(term)) and - :erlang.map_get(:__struct__, unquote(term)) == unquote(name) + if is_atom(name) or is_atom(Macro.expand(name, __CALLER__)) do + quote do + is_map(unquote(term)) and + :erlang.is_map_key(:__struct__, unquote(term)) and + :erlang.map_get(:__struct__, unquote(term)) == unquote(name) + end + else + quote do + is_map(unquote(term)) and + (is_atom(unquote(name)) or :fail) and + :erlang.is_map_key(:__struct__, unquote(term)) and + :erlang.map_get(:__struct__, unquote(term)) == unquote(name) + end end end end @@ -2805,13 +2813,23 @@ defmodule Kernel do invalid_match!(:is_exception) :guard -> - quote do - is_map(unquote(term)) and - (is_atom(unquote(name)) or :fail) and - :erlang.is_map_key(:__struct__, unquote(term)) and - :erlang.map_get(:__struct__, unquote(term)) == unquote(name) and - :erlang.is_map_key(:__exception__, unquote(term)) and - :erlang.map_get(:__exception__, unquote(term)) == true + if is_atom(name) or is_atom(Macro.expand(name, __CALLER__)) do + quote do + is_map(unquote(term)) and + :erlang.is_map_key(:__struct__, unquote(term)) and + :erlang.map_get(:__struct__, unquote(term)) == unquote(name) and + :erlang.is_map_key(:__exception__, unquote(term)) and + :erlang.map_get(:__exception__, unquote(term)) == true + end + else + quote do + is_map(unquote(term)) and + (is_atom(unquote(name)) or :fail) and + :erlang.is_map_key(:__struct__, unquote(term)) and + :erlang.map_get(:__struct__, unquote(term)) == unquote(name) and + :erlang.is_map_key(:__exception__, unquote(term)) and + :erlang.map_get(:__exception__, unquote(term)) == true + end end end end diff --git a/lib/elixir/test/elixir/module/types/pattern_test.exs b/lib/elixir/test/elixir/module/types/pattern_test.exs index e5b39f41b98..90944ad85c4 100644 --- a/lib/elixir/test/elixir/module/types/pattern_test.exs +++ b/lib/elixir/test/elixir/module/types/pattern_test.exs @@ -647,6 +647,10 @@ defmodule Module.Types.PatternTest do assert typecheck!([x], is_struct(x, URI), x) == dynamic(open_map(__struct__: atom([URI]))) end + test "not is_struct/2" do + assert typecheck!([x], not is_struct(x, URI), x) + end + test "is_binary/1" do assert typecheck!([x], is_binary(x), x) == dynamic(binary()) assert typecheck!([x], not is_binary(x), x) == dynamic(negation(binary()))