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

Stop generating more methods than necessary #293

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

paracycle
Copy link
Member

@paracycle paracycle commented Apr 23, 2021

Motivation

We used to treat methods that didn't have a source location the same as methods that explicitly were not defined in the current gem. That resulted in Tapioca creating more method definitions than necessary.

We would only skip method generation for a method if the constant it was on was an ignored type (i.e. a built-in type), so that we wouldn't keep redefining methods for built-in types. However, for all other types, especially types that come from other gems, we would just keep on generating all the methods regardless of if they were defined by this gem or not.

Moreover, the source location check was happening at the wrong location, before unwrapping the method signature. Thus, many methods with signatures would not be generated when the previous problem was fixed, since our code would see them as being defined in Sorbet runtime.

Implementation

The fix is to return a more fine-grained result from method_in_gem? which signals yes/no/don't-have-source-location. Based on that we can skip generating don't-have-source-location cases if they are for built-in types and totally ignore the methods that have a source location and are definitely not defined in the current gem.

Additionally, if we try to unwrap the method signature and we get an exception, that means the signature block raised an error. If we continue with the method as is, the source location checks would think the method definition does not belong in the gem (since the method is still wrapped), and we would thus skip the method generation. To avoid that, the signature_for method is now raising a custom exception to signal that exceptional case, so that we can at least continue generating "a" method definition.

Tests

Updated existing tests.

We used to treat methods that didn't have a source location the same as
methods that explicitly were not defined in the current gem. That
resulted in Tapioca creating more method definitions than necessary.

We would only skip method generation for a method if the constant it was
on was an ignored type (i.e. a built-in type), so that we wouldn't keep
redefining methods for built-in types. However, for all other types,
especially types that come from other gems, we would just keep on
generating all the methods regardless of if they were defined by this
gem or not.

Moreover, the source location check was happening at the wrong location,
before unwrapping the method signature. Thus, many methods with
signatures would not be generated when the previous problem was fixed,
since our code would see them as being defined in Sorbet runtime.

The fix is to return a more fine-grained result from `method_in_gem?`
which signals yes/no/don't-have-source-location. Based on that we can
skip generating don't-have-source-location cases if they are for
built-in types and totally ignore the methods that have a source
location and are definitely not defined in the current gem.

Additionally, if we try to unwrap the method signature and we get an
exception, that means the signature block raised an error. If we
continue with the method as is, the source location checks would think
the method definition does not belong in the gem (since the method is
still wrapped), and we would thus skip the method generation. To avoid
that, the `signature_for` method is not raising a custom exception to
signal that exceptional case, so that we can at least continue
generating "a" method definition.
@paracycle paracycle requested a review from a team April 23, 2021 19:26

gem.contains_path?(source_location)
source_location == "(eval)" || gem.contains_path?(source_location)
Copy link
Member

Choose a reason for hiding this comment

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

When is the source_location equal to (eval)?

Copy link
Member Author

@paracycle paracycle Apr 23, 2021

Choose a reason for hiding this comment

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

Ah, forgot to mention that in the PR description, good catch. That happens when:

class Foo
  module_eval("def foo; end")
end

Foo.instance_method(:foo).source_location
# => ["(eval)", 1]

Copy link
Member Author

Choose a reason for hiding this comment

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

Most usages of eval and friends do:

class Foo
  module_eval("def bar; end", __FILE__, __LINE__)
end

puts Foo.instance_method(:bar).source_location
# => ["foo.rb", 2]

though, so I would expect this to be an edge-case situation.

Still, I think erring on the side of caution for eval defined methods is the right call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants