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

Remove T.unsafe from extension method call #1047

Merged
merged 1 commit into from
Jul 8, 2022

Conversation

sambostock
Copy link
Contributor

Motivation

Sorbet doesn't know that ::FrozenRecord::Base will have ::Tapioca::Dsl::Compilers::Extensions::FrozenRecord prepended to its singleton_class. Therefore, it does not allow us to call the method provided by that module.

T.unsafe is used to allow the call, which can be improved.

Implementation

If we tell Sorbet that ConstantType will be the union of both types, it allows us to call the method.

I came up with this approach in #1046, which also makes use of an extension. I did not want to use T.usafe if possible, so I came up with this approach.

Tests

bundle exec srb tc is the test 😅

Sorbet doesn't know that `::FrozenRecord::Base` will have
`::Tapioca::Dsl::Compilers::Extensions::FrozenRecord` prepended to its
`singleton_class`. Therefore, it does not allow us to call the methods provided
by that module.

If we tell it that `ConstantType` will be the union of both, it allows us to do so.
@@ -65,7 +65,7 @@ module Compilers
class FrozenRecord < Compiler
extend T::Sig

ConstantType = type_member { { fixed: T.class_of(::FrozenRecord::Base) } }
ConstantType = type_member { { fixed: T.all(T.class_of(::FrozenRecord::Base), Extensions::FrozenRecord) } }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there an alternative way to explicitly tell Sorbet that T.class_of(::FrozenRecord::Base).is_a?(Extensions::FrozenRecord)?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think so.

Copy link
Contributor Author

@sambostock sambostock left a comment

Choose a reason for hiding this comment

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

My intuition was that it should also be possible to say

sig { override.returns(T::Enumerable[ConstantType]) }
def self.gather_constants
  # ...

but this doesn't seem to be the case due to the differences between type_member and type_template, which I'm not super familiar with.

Is there a good way to specify both, or is the idea here that it's not worth it since generics are erased at runtime anyway, so Module is sufficient?

@@ -65,7 +65,7 @@ module Compilers
class FrozenRecord < Compiler
extend T::Sig

ConstantType = type_member { { fixed: T.class_of(::FrozenRecord::Base) } }
ConstantType = type_member { { fixed: T.all(T.class_of(::FrozenRecord::Base), Extensions::FrozenRecord) } }
Copy link
Member

Choose a reason for hiding this comment

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

I don't think so.

@vinistock
Copy link
Member

Concerning the return type of gather_constants, I think it would work to use ConstantType, but I'm not sure we'd gain a lot in terms of type checking.

@paracycle
Copy link
Member

Is there a good way to specify both, or is the idea here that it's not worth it since generics are erased at runtime anyway, so Module is sufficient?

So a few things here:

  1. We dump all of the constants returned from all the gather_constants calls into a set of Modules anyway, so having a more precise type here does not help much for that stage.
  2. More importantly, this quickly gets into co/contra-variance since we would be expecting the return type of the gather_constants to be substitutable for the return type of the same method on the base class, so the type variable needs to be covariant (i.e. :out in Sorbet), but you can only declare co/contra-variant type parameters in modules and not classes, so that ends up making the whole thing more complicated to pull off. See here for reference: https://sorbet.org/docs/generics#in-out-and-variance

@paracycle paracycle merged commit 1ab1b65 into main Jul 8, 2022
@paracycle paracycle deleted the remove-unsafe-in-frozen-record branch July 8, 2022 21:07
@shopify-shipit shopify-shipit bot temporarily deployed to production July 14, 2022 18:56 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.

3 participants