Tapioca DSL crashes when using `T::Struct` containing generic members
Summary
When running tapioca dsl in a Rails app that
- Overrides
T::Configuration.call_validation_error_handlerduring application load with a handler that raises errors - Contains a
T::Structwith a member whose type is a generic interface - Instantiates that struct during app startup with a concrete implementation of the generic interface
Tapioca will crash while trying to create the struct because of a type mismatch error between the interface and the implementation.
I created a repository containing what seems to be the absolute minimum configuration necessary to activate this bug. You can find it here: https://github.com/amanda-mitchell/tapioca-bug-repro
The short version of my analysis is that while most type checks are suppressed by Tapioca setting the default check level to :never, members declared on T::Struct don't respect this setting, so they still produce type errors.
Workarounds
Although I'd love to see this get a long term fix, I'd also like to document some potential workarounds for folks who might run across this in the meantime.
Use setter_validate
T::Struct contains an undocumented option for member declarations called setter_validate, and this is expected to a proc that performs validation side effects. Providing an empty (no-op) lambda will force the implementation of T::Struct onto an alternate code path that does not activate the bug.
Wrap the type in an intersection with Kernel
Simply switching the type from MyInterface[TypeArgument] to T.all(MyInterface[TypeArgument], Kernel) is also enough to nudge T::Struct onto the code path listed above.
Disable runtime checks while running Tapioca
If you have some code that can detect when Tapioca is running and set your implementation of T::Configuration.call_validation_error_handler to something that no-ops, you won't encounter the crash.
Thanks for the very detailed issue and the workarounds, very useful for others who might experience this in the future. I think it'd be best if sorbet-runtime was modified to respect default_checked_level.
We don't use T::Struct internally so I don't imagine us getting around to this issue but contributions are welcome to both here and to the sorbet repo 🙂