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

Serialize generic types with unspecified type variables #329

Merged
merged 1 commit into from
May 25, 2021

Conversation

paracycle
Copy link
Member

Motivation

The generic serialization code is serializing generic types with unspecified type variables incorrectly. Sorbet currently allows one to type Foo.new even if Foo is a generic type that expects type variables, and treats Foo.new as if it was Foo[T.untyped].new or similar.

However, Sorbet complains about T.let(T.unsafe(nil), Foo) usage, with the error message saying that Foo is a generic class without type arguments.

Thus, if the user code has a constant definition like FOO = Foo.new (where Foo is a generic class), then the generated RBI code for it, FOO = T.let(T.unsafe(nil), Foo) would end up raising a type checking error.

Implementation

The fix is to treat generic types differently when grabbing their name. If their name does not already have the type variables, then we add T.untyped for each of the type variables it would expect and generate the type name like that.

Tests

Added an extra test to check for this specific case.

@paracycle paracycle requested review from dougedey-shopify and a team May 25, 2021 14:23
The generic serialization code is serializing generic types with
unspecified type variables incorrectly. Sorbet currently allows one to
type `Foo.new` even if `Foo` is a generic type that expects type
variables, and treats `Foo.new` as if it was `Foo[T.untyped].new` or
similar.

However, Sorbet complains about `T.let(T.unsafe(nil), Foo)` usage, with
the error message saying that `Foo` is a generic class without type
arguments.

Thus, if the user code has a constant definition like `FOO = Foo.new`
(where `Foo` is a generic class), then the generated RBI code for it,
`FOO = T.let(T.unsafe(nil), Foo)` would end up raising a type checking
error.

The fix is to treat generic types differently when grabbing their name.
If their name does not already have the type variables, then we add
`T.untyped` for each of the type variables it would expect and generate
the type name like that.

klass_name = if klass == ObjectSpace::WeakMap
# WeakMap is an implicit generic with one type variable
"ObjectSpace::WeakMap[T.untyped]"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why can't we treat this one like the other classes extending T::Generic? https://github.com/sorbet/sorbet/blob/master/rbi/stdlib/objspace.rbi#L170-L171

Copy link
Member Author

Choose a reason for hiding this comment

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

As you point out, the fact that this is generic is encoded in the RBI file, but no artifact of that exists at runtime.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder if such a list should exist in sorbet-runtime so it can "tag" known generic types?

Copy link
Member Author

Choose a reason for hiding this comment

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

TBH, there aren't that many of them, and, IMO, ObjectSpace::WeakMap shouldn't be marked as a generic type anyway. I'll see what upstream thinks about making it non-generic.

Copy link
Member Author

Choose a reason for hiding this comment

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

Looking at it again, I think it was meant to be:

class ObjectSpace::WeakMap < Object
  include Enumerable

  extend T::Generic
  Elem = type_member(:out, fixed: T.untyped)
# ...

like how other types that have to include Enumerable do it: https://github.com/Shopify/sorbet/blob/master/rbi/core/struct.rbi/#L31-L35

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think sorbet-runtime could leave some info in the instance about this so we can pick it up from tapioca

@paracycle paracycle merged commit aff7450 into master May 25, 2021
@paracycle paracycle deleted the uk-fix-generic-const-type branch May 25, 2021 18:12
@shopify-shipit shopify-shipit bot temporarily deployed to production May 25, 2021 22:53 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants