module Tapioca::Runtime::GenericTypeRegistry
def create_generic_type(constant, name)
def create_generic_type(constant, name) generic_type = case constant when Class # For classes, we want to create a subclass, so that an instance of # the generic class `Foo[Bar]` is still a `Foo`. That is: # `Foo[Bar].new.is_a?(Foo)` should be true, which isn't the case # if we just clone the class. But subclassing works just fine. create_safe_subclass(constant) else # This can only be a module and it is fine to just clone modules # since they can't have instances and will not have `is_a?` relationships. # Moreover, we never `include`/`extend` any generic modules into the # ancestor tree, so this doesn't become a problem with checking the # instance of a class being `is_a?` of a module type. constant.clone end # Let's set the `name` and `to_s` methods to return the proper generic name name_proc = -> { name } generic_type.define_singleton_method(:name, name_proc) generic_type.define_singleton_method(:to_s, name_proc) override_type = GenericType.new(generic_type, constant) override_type_proc = -> { override_type } generic_type.define_singleton_method(:__tapioca_override_type, override_type_proc) # We need to define a `<=` method on the cloned constant, so that Sorbet # can do covariance/contravariance checks on the type variables. # # Normally, we would be doing proper covariance/contravariance checks here, but # that is not necessary, since we are not implementing a runtime type checker # here. It is just enough for the checks to pass, so that we can serialize the # signatures, assuming the sigs were well-formed. # # So we act like all subtype checks pass. generic_type.define_singleton_method(:<=) { |_| true } # Return the generic type we created generic_type end
def create_safe_subclass(constant)
def create_safe_subclass(constant) # Lookup the "inherited" class method inherited_method = constant.method(:inherited) # and the module that defines it owner = inherited_method.owner # If no one has overridden the inherited method yet, just subclass return Class.new(constant) if Class == owner begin # Otherwise, some inherited method could be preventing us # from creating subclasses, so let's override it and rescue owner.send(:define_method, :inherited) do |s| inherited_method.call(s) rescue # Ignoring errors end # return a subclass Class.new(constant) ensure # Reinstate the original inherited method back. owner.send(:define_method, :inherited, inherited_method) end end
def generic_type_instance?(instance)
def generic_type_instance?(instance) @generic_instances.values.any? { |generic_type| generic_type === instance } end
def lookup_or_initialize_type_variables(constant)
def lookup_or_initialize_type_variables(constant) @type_variables[constant] ||= [] end
def lookup_type_variables(constant)
def lookup_type_variables(constant) @type_variables[constant] end
def register_type(constant, types)
def register_type(constant, types) # Build the name of the instantiated generic type, # something like `"Foo[X, Y, Z]"` type_list = types.map { |type| T::Utils.coerce(type).name }.join(", ") name = "#{Reflection.name_of(constant)}[#{type_list}]" # Create a generic type with an overridden `name` # method that returns the name we constructed above. # # Also, we try to memoize the generic type based on the name, so that # we don't have to keep recreating them all the time. @generic_instances[name] ||= create_generic_type(constant, name) end
def register_type_variable(constant, type_variable)
def register_type_variable(constant, type_variable) type_variables = lookup_or_initialize_type_variables(constant) type_variables << type_variable end