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?
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.