rubysorbet

Any way to instantiate a generic type in Sorbet? (calling new on type_member)


I'd like to instantiate an instance of the generic type I pass in. For example,

module MyModule
  extend T::Sig
  extend T::Generic

  class << self
    extend T::Sig
    extend T::Generic

    Elem = type_member {{ upper: T::Struct }}
    
    sig { returns(Elem) }
    def my_new_struct
      Elem.new
    end
  end
end

If I then call MyModule[SomeStruct].my_new_struct, this passes Sorbet, but when the code is actually run it crashes with

NoMethodError: undefined method `new' for #<T::Types::TypeMember:0x00007f784b99d4a8 @variance=:invariant>

Is it possible to do this?


Solution

  • I don't believe this is possible.

    MyModule[SomeStruct] resolves to the same Module object as just MyModule. I.e. the ::[] operator just returns self. So at runtime, there's no way to distinguish MyModule[SomeStructA] from MyModule[SomeStructB]

    The [SomeStruct] syntax only exists to "communicate" with the static Sorbet type-checker, which type-checks the generics. Unfortunately, there's no runtime representation of the concrete types of these type members.

    It's possible to kinda hack this together, by implementing a [] operator which returns a dynamically-instantiated subclass (this would only work for classes) of MyModule which stores the arguments (the concrete values of the type members) as instance variables on the new type.