Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazy definition of aliases, and mutually recursive aliases #11959

Open
HertzDevil opened this issue Apr 1, 2022 · 0 comments
Open

Lazy definition of aliases, and mutually recursive aliases #11959

HertzDevil opened this issue Apr 1, 2022 · 0 comments

Comments

@HertzDevil
Copy link
Contributor

HertzDevil commented Apr 1, 2022

An alias definition stores the raw type expression, but does not immediately resolve that type expression until the alias is used somewhere. In the main semantic phase this should be fine, but in the top-level phase not all types are defined yet, which means syntactically identical aliases can mean different things depending on require order, macros, or overload ordering (#11678 shows how Number::Primitive is prone to this):

class Foo
  alias Bar1 = Foo
  alias Bar2 = Foo
  alias Bar3 = Foo
  alias Bar4 = Foo

  # uses `Bar2`'s definition
  {% Bar2 %}

  def foo3(x : Bar3); end

  # overload ordering uses `Bar4`'s definition
  def foo4(x : Bar4); end
  def foo4(x : Int32); end

  class Foo; end
end

Foo::Bar1 # => Foo::Foo
Foo::Bar2 # => Foo
Foo::Bar3 # => Foo::Foo
Foo::Bar4 # => Foo

This behavior is confusing in both user code and the compiler's own source code. I propose that we disallow those alias definitions in the future, so every alias definition must be resolved immediately:

class Foo
  alias Bar = Foo
  # `Foo::Bar` refers to `::Foo`, nothing written here will change that

  alias Baz = Fooo        # Undefined constant Fooo
  alias Baz = Array(Fooo) # Undefined constant Fooo
end

A direct consequence is that mutually recursive aliases (#7704) are no longer possible, since the first alias must reference some yet undefined alias:

# aliases referencing each other
alias A1 = Int32 | Hash(A1, B1) # Error: undefined constant B1
alias B1 = String | Hash(B1, A1)

# these mutually recursive aliases reference each other only indirectly,
# even though each type expression alone would suggest otherwise
alias A2 = Int32 | Hash(B2, C2) # Error: undefined constant B2
alias B2 = String | Hash(C2, A2)
alias C2 = Char | Hash(A2, B2)

Now consider a self-recursive alias:

module Foo
  alias Foo = Int32 | Array(Foo)
end

The Foo on the right-hand side must refer to ::Foo::Foo in case of a self-recursive alias, or the module ::Foo if recursive aliases are completely disallowed (#5155). Self-recursive types are fixable, and a great variety of FP languages have them after all, so I think they could stay for the moment. Having at most one recursive element means this recursive alias should behave like what one would call μX . Int32 | Array(X) in academic research.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant