From 2e98958454753adbde4fc7578a647cc6b7392364 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Thu, 9 Jul 2026 14:07:34 +0200 Subject: [PATCH] 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. Co-Authored-By: Claude Fable 5 --- lib/elixir/lib/code/typespec.ex | 6 ++++-- lib/elixir/test/elixir/typespec_test.exs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/elixir/lib/code/typespec.ex b/lib/elixir/lib/code/typespec.ex index 5288cd536f7..7b1236c2bb5 100644 --- a/lib/elixir/lib/code/typespec.ex +++ b/lib/elixir/lib/code/typespec.ex @@ -80,7 +80,8 @@ defmodule Code.Typespec do Returns all types available from the module's BEAM code. The result is returned as a list of tuples where the first - element is the type (`:typep`, `:type` and `:opaque`). + element is the type (`:typep`, `:type`, `:opaque` and, on Erlang/OTP 28+, + `:nominal`). The module must have a corresponding BEAM file which can be located by the runtime system. The types will be in the Erlang @@ -95,9 +96,10 @@ defmodule Code.Typespec do types = for {:attribute, _, kind, {name, _, args} = type} <- abstract_code, - kind in [:opaque, :type] do + kind in [:opaque, :type, :nominal] do cond do kind == :opaque -> {:opaque, type} + kind == :nominal -> {:nominal, type} {name, length(args)} in exported_types -> {:type, type} true -> {:typep, type} end diff --git a/lib/elixir/test/elixir/typespec_test.exs b/lib/elixir/test/elixir/typespec_test.exs index dd8705bb57b..b226eceda48 100644 --- a/lib/elixir/test/elixir/typespec_test.exs +++ b/lib/elixir/test/elixir/typespec_test.exs @@ -1438,6 +1438,23 @@ defmodule TypespecTest do assert Code.Typespec.fetch_specs(Unknown) == :error end + if System.otp_release() >= "28" do + test "retrieval of Erlang nominal types" do + forms = [ + {:attribute, 1, :module, :typespec_nominal_sample}, + {:attribute, 2, :export_type, [meters: 0]}, + {:attribute, 3, :nominal, {:meters, {:type, 3, :integer, []}, []}}, + {:attribute, 4, :type, {:plain, {:type, 4, :atom, []}, []}} + ] + + {:ok, :typespec_nominal_sample, beam} = :compile.forms(forms, [:debug_info]) + + assert {:ok, types} = Code.Typespec.fetch_types(beam) + assert {:nominal, {:meters, {:type, 3, :integer, []}, []}} in types + assert {:typep, {:plain, {:type, 4, :atom, []}, []}} in types + end + end + # This is a test that implements all types specified in lib/elixir/pages/typespecs.md test "documented types and their AST" do defmodule SomeStruct do