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

Support type var splats inside Tuple during generic parameter substitution #10232

Merged

Conversation

HertzDevil
Copy link
Contributor

@HertzDevil HertzDevil commented Jan 10, 2021

Fixes #8520.

Consider the example from there:

class Foo(R, *A)
  def initialize(@action : Proc(*A, R))
  end
end
 
Foo(String, Int32, Bool).new ->(a : Int32, b : Bool) { "foo" }

When Foo(String, Int32, Bool) is instantiated, the compiler requests the type of the @action instance variable; this must be a type, not an AST node. (Generic type splats inside AST nodes are already supported, so the type restriction of #initialize works as intended.) The compiler knows the following type parameter substitutions:

  • R -> String
  • A -> Tuple(Int32, Bool)

It then proceeds to perform type parameter substitution on Proc(*A, R). The compiler also "instiantiates" an unbound Crystal::GenericInstanceType from this expression, even if the type arguments are not known in advance. This "type" has the following type vars:

  • T (from Proc's definition) -> Tuple(*A) (this A is from the outer Foo)
  • R (from Proc's definition) -> R (from the outer Foo)

The compiler doesn't know how to substitute A -> Tuple(Int32, Bool) into Tuple(*A), as it previously assumes there are no type var splats inside an unbound Tuple(...) expression; this is what this PR implements. As Crystal::TupleInstanceType backs all generic type var splats, including ones in included/inherited types, this PR also makes the following possible:

class C(*T)
  def foo
    T
  end
end

module M(*T)
  def bar
    T
  end
end

class Baz(*T) < C(*T, Int32)
  include M(String, *T)
end

x = Baz(Bool, Char).new
x.foo # Before: Tuple(*T, Int32)
      # After:  Tuple(Bool, Char, Int32)
x.bar # Before: Tuple(String, *T)
      # After:  Tuple(String, Bool, Char)

{% Baz(Bool, Char).ancestors %} # Before: [M(String, *T), C(*T, Int32), Reference, Object]
                                # After:  [M(String, Bool, Char), C(Bool, Char, Int32), Reference, Object]

The "more args" specs are written in such a way that splat expansions in unbound generic expressions always correspond to splat parameters in the generic definitions; see #3649 (comment) for the rationale behind this.

@straight-shoota straight-shoota added kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler:semantic labels Jan 10, 2021
@asterite
Copy link
Member

Just wow.

How are you finding the compiler's code so far? Please be honest, I personally think it's not the best thing in the world :-)

@HertzDevil
Copy link
Contributor Author

My impression so far is that Crystal's compiler being written in bleeding-edge Crystal itself makes it far better than a lot of programming languages by default (for example you wouldn't see a C++20 compiler written in bleeding-edge C++20); the Crystal language is overdue for a formal specification, but the compiler's code itself is good enough. Perhaps all LLVM-backed languages are like that.

@asterite asterite added this to the 1.1.0 milestone Jun 4, 2021
@asterite asterite merged commit 5f9c863 into crystal-lang:master Jun 4, 2021
@HertzDevil HertzDevil deleted the bug/generic-splat-in-tuple branch June 5, 2021 02:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler:semantic
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Can't use splat generics in Proc type declaration for instance variable
3 participants