Filter out mixins that weren't performed in current gem - #1012
Conversation
b9aca82 to
50ada09
Compare
| # 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| |
There was a problem hiding this comment.
The changes in this file are a result of the refactor to constants_with_mixin. Functionality is not changed.
There was a problem hiding this comment.
Should this be:
| Runtime::Trackers::Mixin.constants_with_mixin(mixin).each do |_, location_info| | |
| Runtime::Trackers::Mixin.constants_with_mixin(mixin).each_value do |location_info| |
instead?
| # 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 |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
This assertion passes now 🎉
| .map do |mod| | ||
| mixin_location = mixin_location(constant, mod, mixin_type) | ||
|
|
||
| # Some extend mixins are actually includes or prepend on the singleton class. |
There was a problem hiding this comment.
| # Some extend mixins are actually includes or prepend on the singleton class. | |
| # Some extend mixins are actually includes or prepends on the singleton class. |
| location: String, | ||
| ).returns(T::Boolean) | ||
| end | ||
| def mixed_in_by_gem?(location) |
There was a problem hiding this comment.
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?
| sig do | ||
| params( | ||
| location: String, | ||
| ).returns(T::Boolean) | ||
| end |
There was a problem hiding this comment.
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| |
There was a problem hiding this comment.
Should this be:
| Runtime::Trackers::Mixin.constants_with_mixin(mixin).each do |_, location_info| | |
| Runtime::Trackers::Mixin.constants_with_mixin(mixin).each_value do |location_info| |
instead?
| ).returns(T.nilable(String)) | ||
| end | ||
| def mixin_location(constant, mixin, mixin_type) | ||
| T.must(Runtime::Trackers::Mixin.constants_with_mixin(mixin)[mixin_type])[constant] |
There was a problem hiding this comment.
Can we use dig instead?
| T.must(Runtime::Trackers::Mixin.constants_with_mixin(mixin)[mixin_type])[constant] | |
| Runtime::Trackers::Mixin.constants_with_mixin(mixin).dig(mixin_type, constant) |
| T.must(mixin_locations[Runtime::Trackers::Mixin::Type::Include])[singleton_class] || | ||
| T.must(mixin_locations[Runtime::Trackers::Mixin::Type::Prepend])[singleton_class] |
There was a problem hiding this comment.
Again, dig would be much nicer:
| 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) |
| # 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 |
There was a problem hiding this comment.
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.
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.
50ada09 to
445d9ef
Compare
| end | ||
|
|
||
| sig { params(constant: Module).returns(T.nilable(String)) } | ||
| def constant_name_from_singleton_class(constant) |
There was a problem hiding this comment.
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.
| sig { params(constant: Module).returns(T.nilable(String)) } | ||
| def constant_name_from_singleton_class(constant) | ||
| constant.to_s.match("#<Class:(.+)>")&.captures&.first | ||
| end |
There was a problem hiding this comment.
This method was moved into Runtime::Reflection
| # 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) |
There was a problem hiding this comment.
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.
| .gsub(/^\s+include ::Minitest::Expectations\s/, "") | ||
| .gsub(/^\s+include ::JSON::Ext::Generator::GeneratorMethods::Object\s/, "") | ||
| .gsub(/^\s+include ::PP::ObjectMixin\s/, "") |
There was a problem hiding this comment.
@paracycle Is this gsub you were talking about?
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.
mixin_locations_formethod, which is no longer used, and modify theconstants_with_mixinmethod to keep track of mixin locations.constant_name_from_singleton_classmethod intoRuntime::Reflectionso it can be shared with the Module extension coming from the mixin tracker.mixin_locations_formethod 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.