From 3be1399587553842e593e7109590f85ca93d401e Mon Sep 17 00:00:00 2001 From: Aaron Tinio Date: Thu, 12 Mar 2026 13:12:21 +0800 Subject: [PATCH] Skip (is_atom(name) or :fail) in is_struct/2 and is_exception/2 guard expansion when name is a compile-time atom --- lib/elixir/lib/exception.ex | 9 ++++ lib/elixir/lib/kernel.ex | 42 +++++++++++++------ .../test/elixir/module/types/pattern_test.exs | 4 ++ 3 files changed, 43 insertions(+), 12 deletions(-) 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()))