Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Foreign constant reflection breaks with non-standard singleton class inspect #1097

Closed
michaelherold opened this issue Aug 4, 2022 · 3 comments · Fixed by #1098
Closed

Foreign constant reflection breaks with non-standard singleton class inspect #1097

michaelherold opened this issue Aug 4, 2022 · 3 comments · Fixed by #1098
Labels
bug Something isn't working

Comments

@michaelherold
Copy link
Contributor

For classes that redefine their singleton class #inspect method (e.g. ActiveRecord::Base, ActiveRecord::SchemaMigration, and ActiveRecord::InternalMetadata all override with connection information), Tapioca cannot constantize the string into a module, leading to a TypeError down the chain when attempting to handle mixins for those constants.

This pops up in cases where gems monkey-patch those classes. When you run tapioca gem <affected_gem>, you end up with the following style of stacktrace.

Stacktrace of error
/Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/runtime/reflection.rb:61:in `bind_call': bind argument must be an instance of Module (TypeError)
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/runtime/reflection.rb:61:in `ancestors_of'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/gem/listeners/mixins.rb:115:in `interesting_ancestors_of'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/gem/listeners/mixins.rb:19:in `on_scope'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/gem/listeners/base.rb:26:in `dispatch'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/gem/pipeline.rb:188:in `block in on_node'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/gem/pipeline.rb:188:in `each'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/gem/pipeline.rb:188:in `on_node'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/gem/pipeline.rb:153:in `dispatch'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/gem/pipeline.rb:49:in `compile'
        from /Users/herold/.gem/ruby/3.1.2/gems/sorbet-runtime-0.5.10263/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /Users/herold/.gem/ruby/3.1.2/gems/sorbet-runtime-0.5.10263/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/commands/gem.rb:188:in `compile_gem_rbi'
        from /Users/herold/.gem/ruby/3.1.2/gems/sorbet-runtime-0.5.10263/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /Users/herold/.gem/ruby/3.1.2/gems/sorbet-runtime-0.5.10263/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/commands/gem.rb:79:in `block (2 levels) in execute'
        from /Users/herold/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/shell/basic.rb:44:in `indent'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/commands/gem.rb:78:in `block in execute'
        from /Users/herold/.gem/ruby/3.1.2/gems/parallel-1.22.1/lib/parallel.rb:587:in `call_with_index'
        from /Users/herold/.gem/ruby/3.1.2/gems/parallel-1.22.1/lib/parallel.rb:354:in `block in work_direct'
        from /Users/herold/.gem/ruby/3.1.2/gems/parallel-1.22.1/lib/parallel.rb:597:in `with_instrumentation'
        from /Users/herold/.gem/ruby/3.1.2/gems/parallel-1.22.1/lib/parallel.rb:353:in `work_direct'
        from /Users/herold/.gem/ruby/3.1.2/gems/parallel-1.22.1/lib/parallel.rb:288:in `map'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/executor.rb:31:in `run_in_parallel'
        from /Users/herold/.gem/ruby/3.1.2/gems/sorbet-runtime-0.5.10263/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /Users/herold/.gem/ruby/3.1.2/gems/sorbet-runtime-0.5.10263/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/commands/gem.rb:77:in `execute'
        from /Users/herold/.gem/ruby/3.1.2/gems/sorbet-runtime-0.5.10263/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /Users/herold/.gem/ruby/3.1.2/gems/sorbet-runtime-0.5.10263/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/cli.rb:265:in `block in gem'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca.rb:18:in `block in silence_warnings'
        from /opt/rubies/3.1.2/lib/ruby/3.1.0/rubygems/user_interaction.rb:46:in `use_ui'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca.rb:17:in `silence_warnings'
        from /Users/herold/.gem/ruby/3.1.2/gems/sorbet-runtime-0.5.10263/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /Users/herold/.gem/ruby/3.1.2/gems/sorbet-runtime-0.5.10263/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/lib/tapioca/cli.rb:225:in `gem'
        from /Users/herold/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
        from /Users/herold/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
        from /Users/herold/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
        from /Users/herold/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/base.rb:485:in `start'
        from /Users/herold/.gem/ruby/3.1.2/gems/tapioca-0.9.2/exe/tapioca:23:in `<top (required)>'
        from bin/tapioca:29:in `load'
        from bin/tapioca:29:in `<main>'

In this example, Tapioca::Runtime::Trackers::Mixin ends up with a constants_with_mixin hash with ActiveRecord::SchemaMigration (call 'ActiveRecord::SchemaMigration.connection' to establish a connection) as the key. This, in turn, gets fed through constantize and can't be converted into the correct class, thus ending up with a nil as the constant in the Tapioca::Gem::Event::ForeignConstantFound event.

I attempted a naive patch to remove /\s+.*/ from the name, but that ended up creating syntax errors in the generated RBI file since the original name gets output to the file. Maybe if we sanitize it higher in the stack?

I have a minimal reproduction project available that uses an internal Shopify gem to reproduce the bug if you'd like me to upload that.

cc @vahe

@michaelherold michaelherold added the bug Something isn't working label Aug 4, 2022
@egiurleo
Copy link
Contributor

egiurleo commented Aug 4, 2022

Hi @michaelherold! Thanks so much for reporting this.

I attempted to write a test reproducing the issue. The stacktrace I get is slightly different but I think it stems from the same problem. Does this test accurately capture your issue?

@michaelherold
Copy link
Contributor Author

It might stem from the same problem, but the error is presenting differently so I'm not sure! Since this doesn't crash, it feels like it might be different.

I pushed this repo as a minimal reproduction for you. I couldn't figure out how to write a test that showed the behavior so I did this to play around!

@egiurleo
Copy link
Contributor

egiurleo commented Aug 5, 2022

@michaelherold Thanks so much for the repro! I've written an updated test and I'm working on fixing the issue now :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants