Skip to content

Commit 36df332

Browse files
committed
[GR-24384] Refinements and super (#2039).
PullRequest: truffleruby/1730
2 parents 5cd679c + b02b27e commit 36df332

File tree

13 files changed

+376
-56
lines changed

13 files changed

+376
-56
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,11 @@ Compatibility:
5858
* Implemented `rb_enc_isalnum` and `rb_enc_isspace`.
5959
* `RUBY_REVISION` is now the full commit hash used to build TruffleRuby, similar to MRI 2.7+.
6060
* Implemented `rb_enc_mbc_to_codepoint`.
61-
* Change the lookup methods to achieve Refinements specification (#2033, @ssnickolay)
61+
* Changed the lookup methods to achieve Refinements specification (#2033, @ssnickolay)
6262
* Implemented `Digest::Instance#new` (#2040).
6363
* Implemented `ONIGENC_MBC_CASE_FOLD`.
6464
* Fixed `Thread#raise` to call the exception class' constructor with no arguments when given no message (#2045).
65+
* Fixed `refine + super` compatibility (#2039, @ssnickolay)
6566

6667
Performance:
6768

doc/contributor/refinements.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,15 @@ The lookup order:
8888
R1 -> A -> B -> R2 -> D -> E -> C -> ...
8989
```
9090

91-
The `super` lookup works in much the same way, except `C` ancestors have a higher priority than other active refinements.
91+
## Super Dispatch
9292

93-
The lookup order for `super`:
94-
```ruby
95-
R1 -> A -> B -> C -> ... -> R2 -> D -> E -> C -> ...
96-
```
93+
94+
The `super` lookup [works in two modes](https://bugs.ruby-lang.org/issues/16977):
95+
96+
1. If `super` is called from a method is directly in R, then we should search in `C` ancestors and ignore other active refinements.
97+
2. If `super` is called from a method placed in a module which included to R, then we should search over all active refinements (as we do for a regular lookup).
98+
99+
Additionally, `super` has access to the caller active refinements, so we use `InternalMethod#activeRefinements` to keep and re-use necessary refinements.
97100

98101
## References
99102

spec/ruby/core/module/fixtures/refine.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ class ClassWithFoo
33
def foo; "foo" end
44
end
55

6+
class ClassWithSuperFoo
7+
def foo; [:C] end
8+
end
9+
610
module PrependedModule
711
def foo; "foo from prepended module"; end
812
end
@@ -11,7 +15,11 @@ module IncludedModule
1115
def foo; "foo from included module"; end
1216
end
1317

14-
def self.build_refined_class
15-
Class.new(ClassWithFoo)
18+
def self.build_refined_class(for_super: false)
19+
if for_super
20+
Class.new(ClassWithSuperFoo)
21+
else
22+
Class.new(ClassWithFoo)
23+
end
1624
end
1725
end

spec/ruby/core/module/refine_spec.rb

Lines changed: 230 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -731,24 +731,49 @@ def foo
731731
result.should == "foo"
732732
end
733733

734+
it "looks in the refined class from included module" do
735+
refined_class = ModuleSpecs.build_refined_class(for_super: true)
736+
737+
a = Module.new do
738+
def foo
739+
[:A] + super
740+
end
741+
end
742+
743+
refinement = Module.new do
744+
refine refined_class do
745+
include a
746+
end
747+
end
748+
749+
result = nil
750+
Module.new do
751+
using refinement
752+
753+
result = refined_class.new.foo
754+
end
755+
756+
result.should == [:A, :C]
757+
end
758+
734759
# super in a method of a refinement invokes the method in the refined
735760
# class even if there is another refinement which has been activated
736761
# in the same context.
737-
it "looks in the refined class even if there is another active refinement" do
738-
refined_class = ModuleSpecs.build_refined_class
762+
it "looks in the refined class first if called from refined method" do
763+
refined_class = ModuleSpecs.build_refined_class(for_super: true)
739764

740765
refinement = Module.new do
741766
refine refined_class do
742767
def foo
743-
"foo from refinement"
768+
[:R1]
744769
end
745770
end
746771
end
747772

748773
refinement_with_super = Module.new do
749774
refine refined_class do
750775
def foo
751-
super
776+
[:R2] + super
752777
end
753778
end
754779
end
@@ -760,7 +785,207 @@ def foo
760785
result = refined_class.new.foo
761786
end
762787

763-
result.should == "foo"
788+
result.should == [:R2, :C]
789+
end
790+
791+
it "looks only in the refined class even if there is another active refinement" do
792+
refined_class = ModuleSpecs.build_refined_class(for_super: true)
793+
794+
refinement = Module.new do
795+
refine refined_class do
796+
def bar
797+
"you cannot see me from super because I belong to another active R"
798+
end
799+
end
800+
end
801+
802+
refinement_with_super = Module.new do
803+
refine refined_class do
804+
def bar
805+
super
806+
end
807+
end
808+
end
809+
810+
811+
Module.new do
812+
using refinement
813+
using refinement_with_super
814+
-> {
815+
refined_class.new.bar
816+
}.should raise_error(NoMethodError)
817+
end
818+
end
819+
820+
it "does't have access to active refinements for C from included module" do
821+
refined_class = ModuleSpecs.build_refined_class
822+
823+
a = Module.new do
824+
def foo
825+
super + bar
826+
end
827+
end
828+
829+
refinement = Module.new do
830+
refine refined_class do
831+
include a
832+
833+
def bar
834+
"bar is not seen from A methods"
835+
end
836+
end
837+
end
838+
839+
Module.new do
840+
using refinement
841+
-> {
842+
refined_class.new.foo
843+
}.should raise_error(NameError) { |e| e.name.should == :bar }
844+
end
845+
end
846+
847+
it "does't have access to other active refinements from included module" do
848+
refined_class = ModuleSpecs.build_refined_class
849+
850+
refinement_integer = Module.new do
851+
refine Integer do
852+
def bar
853+
"bar is not seen from A methods"
854+
end
855+
end
856+
end
857+
858+
a = Module.new do
859+
def foo
860+
super + 1.bar
861+
end
862+
end
863+
864+
refinement = Module.new do
865+
refine refined_class do
866+
include a
867+
end
868+
end
869+
870+
Module.new do
871+
using refinement
872+
using refinement_integer
873+
-> {
874+
refined_class.new.foo
875+
}.should raise_error(NameError) { |e| e.name.should == :bar }
876+
end
877+
end
878+
879+
# https://bugs.ruby-lang.org/issues/16977
880+
it "looks in the another active refinement if super called from included modules" do
881+
refined_class = ModuleSpecs.build_refined_class(for_super: true)
882+
883+
a = Module.new do
884+
def foo
885+
[:A] + super
886+
end
887+
end
888+
889+
b = Module.new do
890+
def foo
891+
[:B] + super
892+
end
893+
end
894+
895+
refinement_a = Module.new do
896+
refine refined_class do
897+
include a
898+
end
899+
end
900+
901+
refinement_b = Module.new do
902+
refine refined_class do
903+
include b
904+
end
905+
end
906+
907+
result = nil
908+
Module.new do
909+
using refinement_a
910+
using refinement_b
911+
result = refined_class.new.foo
912+
end
913+
914+
result.should == [:B, :A, :C]
915+
end
916+
917+
it "looks in the current active refinement from included modules" do
918+
refined_class = ModuleSpecs.build_refined_class(for_super: true)
919+
920+
a = Module.new do
921+
def foo
922+
[:A] + super
923+
end
924+
end
925+
926+
b = Module.new do
927+
def foo
928+
[:B] + super
929+
end
930+
end
931+
932+
refinement = Module.new do
933+
refine refined_class do
934+
def foo
935+
[:LAST] + super
936+
end
937+
end
938+
end
939+
940+
refinement_a_b = Module.new do
941+
refine refined_class do
942+
include a
943+
include b
944+
end
945+
end
946+
947+
result = nil
948+
Module.new do
949+
using refinement
950+
using refinement_a_b
951+
result = refined_class.new.foo
952+
end
953+
954+
result.should == [:B, :A, :LAST, :C]
955+
end
956+
957+
it "looks in the lexical scope refinements before other active refinements" do
958+
refined_class = ModuleSpecs.build_refined_class(for_super: true)
959+
960+
refinement_local = Module.new do
961+
refine refined_class do
962+
def foo
963+
[:LOCAL] + super
964+
end
965+
end
966+
end
967+
968+
a = Module.new do
969+
using refinement_local
970+
971+
def foo
972+
[:A] + super
973+
end
974+
end
975+
976+
refinement = Module.new do
977+
refine refined_class do
978+
include a
979+
end
980+
end
981+
982+
result = nil
983+
Module.new do
984+
using refinement
985+
result = refined_class.new.foo
986+
end
987+
988+
result.should == [:A, :LOCAL, :C]
764989
end
765990
end
766991

src/main/java/org/truffleruby/cext/CExtNodes.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -877,8 +877,9 @@ protected Object callSuper(VirtualFrame frame, Object[] args) {
877877
final InternalMethod callingMethod = RubyArguments.getMethod(callingMethodFrame);
878878
final Object callingSelf = RubyArguments.getSelf(callingMethodFrame);
879879
final DynamicObject callingMetaclass = metaClassNode.executeMetaClass(callingSelf);
880+
final DeclarationContext declarationContext = RubyArguments.getDeclarationContext(frame);
880881
final MethodLookupResult superMethodLookup = ModuleOperations
881-
.lookupSuperMethod(callingMethod, callingMetaclass);
882+
.lookupSuperMethod(callingMethod, callingMetaclass, declarationContext);
882883
final InternalMethod superMethod = superMethodLookup.getMethod();
883884
return callSuperMethodNode.executeCallSuperMethod(frame, callingSelf, superMethod, args, null);
884885
}

src/main/java/org/truffleruby/core/method/MethodNodes.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ protected Object superMethod(DynamicObject method) {
206206
Object receiver = Layouts.METHOD.getReceiver(method);
207207
InternalMethod internalMethod = Layouts.METHOD.getMethod(method);
208208
DynamicObject selfMetaClass = metaClassNode.executeMetaClass(receiver);
209-
MethodLookupResult superMethod = ModuleOperations.lookupSuperMethod(internalMethod, selfMetaClass);
209+
MethodLookupResult superMethod = ModuleOperations.lookupSuperMethod(internalMethod, selfMetaClass, null);
210210
if (!superMethod.isDefined()) {
211211
return nil;
212212
} else {

src/main/java/org/truffleruby/core/method/UnboundMethodNodes.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ public abstract static class SuperMethodNode extends CoreMethodArrayArgumentsNod
203203
protected Object superMethod(DynamicObject unboundMethod) {
204204
InternalMethod internalMethod = Layouts.UNBOUND_METHOD.getMethod(unboundMethod);
205205
DynamicObject origin = Layouts.UNBOUND_METHOD.getOrigin(unboundMethod);
206-
MethodLookupResult superMethod = ModuleOperations.lookupSuperMethod(internalMethod, origin);
206+
MethodLookupResult superMethod = ModuleOperations.lookupSuperMethod(internalMethod, origin, null);
207207
if (!superMethod.isDefined()) {
208208
return nil;
209209
} else {

0 commit comments

Comments
 (0)