Search code examples
rubysorbet

How to dynamically define `prop`s on a `T::Struct` in sorbet at runtime?


I have a struct that is defined as:

# typed: true
require 'sorbet-runtime'

class MyStruct < T::Struct
  MyPropType = T.type_alias { T::Hash[Symbol, Class] }
  
  class << self
    extend T::Sig

    sig { params(props: MyPropType).void }
    def register_props(props)
      props.each do |prop_name, prop_type|
        prop(prop_name, prop_type)
      end
    end
  end
end

Notice how props are defined at runtime.

Then somewhere in my codebase, at startup, I do MyStruct.register_props({ foo: T.untyped, bar: T.nilable(T.untyped) }).

Initializing MyStruct gives error when passing the codebase through typecheck. MyStruct.new(foo: 'foo', bar: Bar.new).

$ ./bin/srb typecheck

/path/to/file.rb:66: Too many arguments provided for method MyStruct#initialize. Expected: 0, got: 1 https://srb.help/7004

How do I define props on T::Struct at runtime without the above typecheck error?


Solution

  • AFAIK T::Structs cannot be defined dynamically (I mean they can but...), since the typechecker needs to statically know which props it's going to have. For this case I think you should use T::InexactStruct. See https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/struct.rb

    EDIT: Add snippet for future references

    # typed: strict
    
    class SomeStruct < T::InexactStruct
      extend T::Sig
    
      sig { params(props: T::Array[T.untyped]).void }
      def self.register(props)
        props.each do |name, type|
          prop name, type
        end
      end
    end
    
    SomeStruct.register [[:one, String], [:two, String]]
    
    SomeStruct.new() # This would raise an error on runtime because of missing arguments, but not on static check
    SomeStruct.new(one: '', two: '') # works on runtime, no static error
    SomeStruct.new(one: '', two: 1)  # fails on runtime because of type mismatch, no static error
    SomeStruct.new(one: '', two: '', three: '') # fails on runtime because of extra argument, no static error