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

Track class new in ConstantDefinition #1044

Merged
merged 6 commits into from
Jul 12, 2022
Merged

Track class new in ConstantDefinition #1044

merged 6 commits into from
Jul 12, 2022

Conversation

vinistock
Copy link
Member

Motivation

Based on @paracycle's work in this commit.

Start handling c_return to capture constants defined by Constant = Class.new. Currently, there's a bug in main where we can't generate RBIs including locations because these occurrences aren't tracked.

After this PR, generating all RBIs in Tapioca with locations works.

Implementation

  1. Started comparing classes by identity, because classes defined by Something = Class.new don't have names by the time we capture them
  2. Changed all relevant code to pass only class instances to ConstantDefinition
  3. Started handling c_return in ConstantDefinition to capture these occurrences
  4. After 3, we start having a StackLevelTooDeep error that is caused by the NamePatch. As per @paracycle's suggestion, I inlined the implementation of qualified_name_of, which fixes the recursion issue
  5. Finally, started passing event.constant instead of event.symbol in the source location listener and stopped trying to add comments if location is nil (which can still happen for constant aliases)

Tests

Added a test that does not pass without handling c_return.

@vinistock vinistock requested a review from a team July 7, 2022 21:19
@vinistock vinistock self-assigned this Jul 7, 2022
@vinistock
Copy link
Member Author

Applied the suggested refactors and shortener the ConstantDefinition a bit. However, there are two tests that are now failing because certain modules are no longer generated for them. I'm not 100% sure what's going on.

Also, it should be supplied the `caller_locations` so that we can
process the caller locations without the calls to this method in between.
This allows dynamically created constants to be attributed to the
file that triggered their creation instead of the actual location that
created the module.

For example, given a file `foo.rb`:
```ruby
module Foo
  class_methods do
    # ...
  end
end
```
we expect a `Foo::ClassMethods` module to be created when this file is
processed. That module will be created inside Active Support using
something like `const_set(:ClassMethods, Module.new)`, but that module
should be attributed to `foo.rb` where the `class_methods` call is
being made.
The `ActionController::Base::HelperMethods` module should never have
been generated inside the `some_engine` gem RBI file in the first place.
@ghost ghost added the cla-needed label Jul 8, 2022
Copy link
Contributor

@egiurleo egiurleo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I like your extension of resolve_loc to make it useful in more cases.

key = name_of(tp.self)
file = tp.path
lineno = tp.lineno
TracePoint.trace(:class, :c_return) do |tp|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is entirely a suggestion, feel free to disregard - I personally find the single TracePoint with the case statement hard to parse. Some of the lines are pretty deeply nested, and the two cases don't really share any common functionality, aside from setting the key-value-pairs in @class_files.

What would you think about calling TracePoint twice, separating out the behavior for class and c_return, and then extracting @class_files[key] ||= Set.new ... into a shared method? I personally would find that much easier to read, but I don't think it's a huge deal either way.

(I'm also curious if there's any performance benefit to calling TTracePoint once?)

Suggested change
TracePoint.trace(:class, :c_return) do |tp|
TracePoint.trace(:class) do |tp|
next if tp.self.singleton_class?
if tp.path == "(eval)"
caller_location = T.must(caller_locations)
.drop_while { |loc| loc.path == "(eval)" }
.first
next unless caller_location
loc = ConstantLocation.new(
path: caller_location.absolute_path || "",
lineno: caller_location.lineno
)
else
loc = build_constant_location(tp, caller_locations)
end
add_to_class_files(tp.self, loc)
end
TracePoint.trace(:c_return) do |tp|
next unless tp.method_id == :new
next unless Module === tp.return_value
key = tp.return_value
loc = build_constant_location(tp, caller_locations)
add_to_class_files(key, loc)
end

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that option as well. The latest commit changed it.

@ghost ghost removed the cla-needed label Jul 8, 2022
@vinistock vinistock merged commit b178398 into main Jul 12, 2022
@vinistock vinistock deleted the vs/track_class_new branch July 12, 2022 19:05
@shopify-shipit shopify-shipit bot temporarily deployed to production July 14, 2022 18:56 Inactive
@shopify-shipit shopify-shipit bot temporarily deployed to 0-9-stable August 22, 2022 21:36 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants