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

Require type var splats in included/inherited generic to match splats in definition #10240

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

HertzDevil
Copy link
Contributor

Resolves #3649.

Given a generic definition and a generic inheritance (or include), there are 4 distinct cases to consider:

class Foo(T, U); end
class Bar(A, B, C) < Foo(A, B); end    # okay
class Bar(A, B, C) < Foo(A, B, C); end # error

Neither the superclass expression nor the definition includes a splat. The number of type arguments must match exactly. This is already covered.

class Foo(T, U, *V); end
class Bar(A, B) < Foo(A, B); end # okay
class Bar(A, B) < Foo(A); end    # error

The definition contains a splat, but the superclass expression doesn't. The number of type arguments must not be less than the number of non-splat type vars in the definition. This is also covered. (Note that the definition can only use at most one splat, but the superclass can use any number of splats, including splats of things other than type parameters.)

class Foo(T, U); end
class Bar(*A) < Foo(*A); end       # error
class Bar(*A) < Foo(*A, *A); end   # error
class Bar(*A, B) < Foo(*A, B); end # error

If the definition doesn't use a splat, any type var splat in the superclass expression now results in a compile-time error.

class Foo(T, *U, V); end
class Bar(*A) < Foo(Int32, *A, Int32); end        # okay, *A splats into *U
class Bar(*A) < Foo(Int32, *A, Int32, Int32); end # okay, *A splats into *U
class Bar(*A) < Foo(Int32, *A, *A, Int32); end    # okay, *A and *A splat into *U
class Bar(*A) < Foo(*A, Int32, Int32); end        # error, *A splats into T
class Bar(*A) < Foo(Int32, *A); end               # error, *A splats into V

The definition has a splat, and at least one type var splat is also given in the superclass expression. If there are m type vars before the splat in Foo's definition and n type vars after the splat, then the first m and the last n type arguments of any included/inherited Foo must not contain any type var splats. The remaining type arguments will be collected by the splat parameter.

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

@straight-shoota straight-shoota left a comment

Choose a reason for hiding this comment

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

❤️


case {type_vars.any?(TypeSplat), splat_index}
in {true, Int32}
# node is `(A, B, C, *D, E)`, definition is `(T, *U, V, W)`; *D would splat into V
Copy link
Member

Choose a reason for hiding this comment

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

Could you explain this a bit more? Aren't all types on the left hand side need to be know to know how to splat them? I don't understand why D would splat only into V, which is not a splat.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The arguments before and after the splat in the instantiation are always matched first, so A, B, and C would match T and *U, followed by E with W, and finally *D with *U and V, which would be disallowed regardless of what types the TypeParameters and TypeSplats have. Thus the definition must not have more than 3 non-splat parameters before *U or more than 1 non-splat parameter after *U.

Copy link
Member

Choose a reason for hiding this comment

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

In the PR description it says:

class Foo(T, *U, V); end
class Bar(*A) < Foo(*A, Int32, Int32); end        # error, *A splats into T

But if I do:

Bar(Int32, Int32, Int32).new

then we get:

Foo(Int32, Int32, Int32, Int32, Int32)

and for:

Foo(T, *U, V)

we get:

  • T: Int32
  • U: Int32, Int32, Int32
  • V: Int32

So that shouldn't be an error?

That said, I'm sure I'm misunderstanding something.

Copy link
Member

Choose a reason for hiding this comment

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

I guess what I'm saying is... don't we need to expand/collect all the types on node and then match that against the definition?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In light of #3649 (comment), maybe this restriction isn't really necessary...

@HertzDevil HertzDevil marked this pull request as draft June 18, 2021 22: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.

Splat type vars in inheritance
3 participants