Skip to content

Filter out mixins that weren't performed in current gem - #1012

Merged
Morriar merged 3 commits into
mainfrom
emily/mixin-filtering
Jun 29, 2022
Merged

Filter out mixins that weren't performed in current gem#1012
Morriar merged 3 commits into
mainfrom
emily/mixin-filtering

Conversation

@egiurleo

@egiurleo egiurleo commented Jun 27, 2022

Copy link
Copy Markdown
Contributor

Closes #890

Motivation

This is the culmination of all the dynamic mixin work!

Currently, when RBIs are generated for mixins, tapioca doesn't take into account whether a mixin was performed in the current gem or in another gem. This results in strange scenarios where one gem will re-open a class to perform a mixin, and then that mixin is reflected in another gem's RBI. We should only generate RBI for mixins that are performed in the current gem.

Implementation

This PR can be read commit-by-commit.

  1. The first commit refactors the mixin tracker to remove the mixin_locations_for method, which is no longer used, and modify the constants_with_mixin method to keep track of mixin locations.
  2. The second commit moves the constant_name_from_singleton_class method into Runtime::Reflection so it can be shared with the Module extension coming from the mixin tracker.
  3. The second commit modifies the mixins pipeline listener to use the mixin_locations_for method to skip any mixins that weren't performed in the current gem.

Tests

Modified existing tests to remove mixins that should be filtered out. Un-commented two lines in a cli test that now pass because of these changes!

Tophatting 🎩

Can generate gem RBIs on Shopify core with these changes.

@egiurleo
egiurleo marked this pull request as ready for review June 27, 2022 15:27
@egiurleo
egiurleo force-pushed the emily/mixin-filtering branch from b9aca82 to 50ada09 Compare June 27, 2022 16:07
# constants that we discover to the pipeline to be processed.
Runtime::Trackers::Mixin.constants_with_mixin(mixin).each do |constant, location|
next unless mixed_in_by_gem?(location)
Runtime::Trackers::Mixin.constants_with_mixin(mixin).each do |_, location_info|

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The changes in this file are a result of the refactor to constants_with_mixin. Functionality is not changed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this be:

Suggested change
Runtime::Trackers::Mixin.constants_with_mixin(mixin).each do |_, location_info|
Runtime::Trackers::Mixin.constants_with_mixin(mixin).each_value do |location_info|

instead?

Comment thread lib/tapioca/gem/listeners/mixins.rb Outdated
# Some extend mixins are actually includes or prepend on the singleton class.
# If an extend mixin is not found in the tracker, these lines search for a prepend
# or include on the singleton class and use the resulting file as the mixin location.
if mixin_location.nil? && mixin_type == Runtime::Trackers::Mixin::Type::Extend

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sometimes, extension mixins are actually tracked in the mixin tracker as prepends or includes to the singleton class. If the mixin is not found under the Runtime::Trackers::Mixin::Type::Extend type, these lines search the mixin tracker for a prepend or include on the singleton class.

This feels a little bit hacky to me...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is something we can rectify at mixin registration time, I guess. If a mixin is being prepended/included into a singleton class, then we can record that as an extend on the attached class at that time.

actionpack_rbi = @project.read("sorbet/rbi/gems/[email protected]")
# actionpack RBI should have nothing in it about `TypedParameters`
# refute_includes(actionpack_rbi, "TypedParameters")
refute_includes(actionpack_rbi, "TypedParameters")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This assertion passes now 🎉

Comment thread spec/tapioca/gem/pipeline_spec.rb
@egiurleo
egiurleo requested a review from a team June 27, 2022 16:09
Comment thread lib/tapioca/gem/listeners/mixins.rb Outdated
.map do |mod|
mixin_location = mixin_location(constant, mod, mixin_type)

# Some extend mixins are actually includes or prepend on the singleton class.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
# Some extend mixins are actually includes or prepend on the singleton class.
# Some extend mixins are actually includes or prepends on the singleton class.

Comment thread lib/tapioca/gem/listeners/mixins.rb Outdated
location: String,
).returns(T::Boolean)
end
def mixed_in_by_gem?(location)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The method is mixed_in_by_gem? but works with a location only, which doesn't seem strictly related to mixins, should we rename it location_in_gem??

Or maybe mixed_in_by_gem? should take a constant?

Comment thread lib/tapioca/gem/listeners/mixins.rb Outdated
Comment on lines +77 to +81
sig do
params(
location: String,
).returns(T::Boolean)
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's inline signatures < 120 chars please 🙏

# constants that we discover to the pipeline to be processed.
Runtime::Trackers::Mixin.constants_with_mixin(mixin).each do |constant, location|
next unless mixed_in_by_gem?(location)
Runtime::Trackers::Mixin.constants_with_mixin(mixin).each do |_, location_info|

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this be:

Suggested change
Runtime::Trackers::Mixin.constants_with_mixin(mixin).each do |_, location_info|
Runtime::Trackers::Mixin.constants_with_mixin(mixin).each_value do |location_info|

instead?

Comment thread lib/tapioca/gem/listeners/mixins.rb Outdated
).returns(T.nilable(String))
end
def mixin_location(constant, mixin, mixin_type)
T.must(Runtime::Trackers::Mixin.constants_with_mixin(mixin)[mixin_type])[constant]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we use dig instead?

Suggested change
T.must(Runtime::Trackers::Mixin.constants_with_mixin(mixin)[mixin_type])[constant]
Runtime::Trackers::Mixin.constants_with_mixin(mixin).dig(mixin_type, constant)

Comment thread lib/tapioca/gem/listeners/mixins.rb Outdated
Comment on lines +107 to +108
T.must(mixin_locations[Runtime::Trackers::Mixin::Type::Include])[singleton_class] ||
T.must(mixin_locations[Runtime::Trackers::Mixin::Type::Prepend])[singleton_class]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Again, dig would be much nicer:

Suggested change
T.must(mixin_locations[Runtime::Trackers::Mixin::Type::Include])[singleton_class] ||
T.must(mixin_locations[Runtime::Trackers::Mixin::Type::Prepend])[singleton_class]
mixin_locations.dig(Runtime::Trackers::Mixin::Type::Include, singleton_class) ||
mixin_locations.dig(Runtime::Trackers::Mixin::Type::Prepend, singleton_class)

Comment thread spec/tapioca/gem/pipeline_spec.rb
Comment thread lib/tapioca/gem/listeners/mixins.rb Outdated
# Some extend mixins are actually includes or prepend on the singleton class.
# If an extend mixin is not found in the tracker, these lines search for a prepend
# or include on the singleton class and use the resulting file as the mixin location.
if mixin_location.nil? && mixin_type == Runtime::Trackers::Mixin::Type::Extend

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is something we can rectify at mixin registration time, I guess. If a mixin is being prepended/included into a singleton class, then we can record that as an extend on the attached class at that time.

egiurleo added 3 commits June 28, 2022 12:50
Refactor the Trackers::Mixin#constants_with_mixin method to keep track
of the type of each mixin. This will be used to filter mixins that were
not performed in the current gem.
Uses `Trackers::Mixin` to find the location of each mixin and filter out
any that did not occur in the current gem to avoid generating
unnecessary RBI.
@egiurleo
egiurleo force-pushed the emily/mixin-filtering branch from 50ada09 to 445d9ef Compare June 28, 2022 20:37
end

sig { params(constant: Module).returns(T.nilable(String)) }
def constant_name_from_singleton_class(constant)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I moved these two methods into reflection because I had to use them in Trackers::Mixin to find the attached class based on a singleton class.

Comment on lines -56 to -59
sig { params(constant: Module).returns(T.nilable(String)) }
def constant_name_from_singleton_class(constant)
constant.to_s.match("#<Class:(.+)>")&.captures&.first
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This method was moved into Runtime::Reflection

Comment thread lib/tapioca/gem/listeners/mixins.rb
# attached class. Registering the mixin as an extend on the attached class ensures that
# this mixin can be found whether searching for an include/prepend on the singleton class
# or an extend on the attached class.
def register_extend_on_attached_class(constant)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This functionality was moved from Listeners::Mixins so that an include/prepend on a singleton class can also be found as an extend on the attached class.

Comment on lines -323 to -325
.gsub(/^\s+include ::Minitest::Expectations\s/, "")
.gsub(/^\s+include ::JSON::Ext::Generator::GeneratorMethods::Object\s/, "")
.gsub(/^\s+include ::PP::ObjectMixin\s/, "")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@paracycle Is this gsub you were talking about?

@egiurleo
egiurleo requested review from Morriar and paracycle June 28, 2022 20:42
@Morriar
Morriar merged commit b61b074 into main Jun 29, 2022
@Morriar
Morriar deleted the emily/mixin-filtering branch June 29, 2022 14:40
@shopify-shipit
shopify-shipit Bot temporarily deployed to production July 7, 2022 17:53 Inactive
@shopify-shipit
shopify-shipit Bot temporarily deployed to 0-9-stable August 19, 2022 20:37 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Better mixin generation for gem RBIs

3 participants