Skip to content

Commit 668c0d9

Browse files
lukaszsamsonclaude
andcommitted
Handle Erlang/OTP 28 nominal types in Code.Typespec.fetch_types/1
-nominal type declarations (EEP 69, OTP 28) were silently dropped from the result. Return them as {:nominal, type} entries, mirroring how :opaque is reported, and tighten the spec to enumerate the kind atoms. The spec comparison script now also expands remote nominal types. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 4317ae8 commit 668c0d9

3 files changed

Lines changed: 24 additions & 4 deletions

File tree

lib/elixir/lib/code/typespec.ex

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,15 @@ defmodule Code.Typespec do
8080
Returns all types available from the module's BEAM code.
8181
8282
The result is returned as a list of tuples where the first
83-
element is the type (`:typep`, `:type` and `:opaque`).
83+
element is the type (`:typep`, `:type`, `:opaque` and, on Erlang/OTP 28+,
84+
`:nominal`).
8485
8586
The module must have a corresponding BEAM file which can be
8687
located by the runtime system. The types will be in the Erlang
8788
Abstract Format.
8889
"""
89-
@spec fetch_types(module | binary) :: {:ok, [tuple]} | :error
90+
@spec fetch_types(module | binary) ::
91+
{:ok, [{:nominal | :opaque | :type | :typep, tuple}]} | :error
9092
def fetch_types(module) when is_atom(module) or is_binary(module) do
9193
case typespecs_abstract_code(module) do
9294
{:ok, abstract_code} ->
@@ -95,9 +97,10 @@ defmodule Code.Typespec do
9597

9698
types =
9799
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code,
98-
kind in [:opaque, :type] do
100+
kind in [:opaque, :type, :nominal] do
99101
cond do
100102
kind == :opaque -> {:opaque, type}
103+
kind == :nominal -> {:nominal, type}
101104
{name, length(args)} in exported_types -> {:type, type}
102105
true -> {:typep, type}
103106
end

lib/elixir/scripts/compare_specs_and_signatures.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ defmodule SpecToDescr do
399399
with {:ok, types} <- Code.Typespec.fetch_types(mod),
400400
{_kind, {^name, body, params}} <-
401401
Enum.find(types, fn {kind, {n, _b, p}} ->
402-
kind in [:type, :typep, :opaque] and n == name and length(p) == arity
402+
kind in [:type, :typep, :opaque, :nominal] and n == name and length(p) == arity
403403
end) do
404404
{:ok, {params, body}}
405405
else

lib/elixir/test/elixir/typespec_test.exs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,6 +1438,23 @@ defmodule TypespecTest do
14381438
assert Code.Typespec.fetch_specs(Unknown) == :error
14391439
end
14401440

1441+
if System.otp_release() >= "28" do
1442+
test "retrieval of Erlang nominal types" do
1443+
forms = [
1444+
{:attribute, 1, :module, :typespec_nominal_sample},
1445+
{:attribute, 2, :export_type, [meters: 0]},
1446+
{:attribute, 3, :nominal, {:meters, {:type, 3, :integer, []}, []}},
1447+
{:attribute, 4, :type, {:plain, {:type, 4, :atom, []}, []}}
1448+
]
1449+
1450+
{:ok, :typespec_nominal_sample, beam} = :compile.forms(forms, [:debug_info])
1451+
1452+
assert {:ok, types} = Code.Typespec.fetch_types(beam)
1453+
assert {:nominal, {:meters, {:type, 3, :integer, []}, []}} in types
1454+
assert {:typep, {:plain, {:type, 4, :atom, []}, []}} in types
1455+
end
1456+
end
1457+
14411458
# This is a test that implements all types specified in lib/elixir/pages/typespecs.md
14421459
test "documented types and their AST" do
14431460
defmodule SomeStruct do

0 commit comments

Comments
 (0)