Skip to content

Support recursive type descriptors#15530

Open
gldubc wants to merge 2 commits into
elixir-lang:mainfrom
gldubc:recursive-types-pr
Open

Support recursive type descriptors#15530
gldubc wants to merge 2 commits into
elixir-lang:mainfrom
gldubc:recursive-types-pr

Conversation

@gldubc

@gldubc gldubc commented Jun 26, 2026

Copy link
Copy Markdown
Member

Summary

  • add recursive descriptor nodes and unfolding support in descriptor operations
  • add recursive descriptor tests
  • keep recursive map-domain if_set lazy to avoid infinite unfolding during construction

Validation

  • make compile
  • make test_stdlib TEST_FILES="module/types/recursive_test.exs"
  • make test_stdlib TEST_FILES="module/types/descr_test.exs"

Some extra information

This PR adds a new main data-structure: a type node, which essentially encodes a way to unfold a recursive type and lives next to the descr structure. For instance, int_list = {integer(), int_list} or nil will be encoded using a node that can produce the Descr representing {integer(), int_list} or nil.

Emptiness detection is done using a simple algorith, with no memoization, that keeps track of which bdds have been encountered before. A memoizing algorith is possible, and could be implemented too if performances becomes an issue, notably when instantiating polymorphic types.

Limitation (edit: solved by f889551)

Currently, recursive types don't support opt_*. An easy way to loop: build

X = nil | {integer(), X}

and try to compute opt_intersection(<node X>, <node X>).

<node X> unfolds to the descr

 nil | {integer(), <node X>}

So when opt_intersection/2 intersects tuple fields individually, it eventually asks:

opt_intersection(<node X>, <node X>)

which unfolds to the same thing again.

Assisted-by: Codex:GPT-5 (writing tests)

defp map_put_domain(domain, [], _initial, _value), do: domain

# Map domains store if_set(value). For recursive nodes, keep that application lazy
# so constructing the descriptor does not unfold the same map domain forever.

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.

Why do we need this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

To avoid the recursive type X = %{atom => if_set(X)} looping due to if_set(X) being like a union X or not_set()

@gldubc

gldubc commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@josevalim f889551 implements in opt operations the tracking that a set operations(intersection, union, difference) has already been tried on two bdds. This is threaded in seen. This solves the limitation of the PR.

Assisted-by: Fable (to test all the possible loop paths in the opt operations)

@gldubc gldubc marked this pull request as ready for review July 14, 2026 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants