ruby-changes:26241
From: shugo <ko1@a...>
Date: Tue, 11 Dec 2012 01:05:56 +0900 (JST)
Subject: [ruby-changes:26241] shugo:r38298 (trunk): * fix the behavior when a module is included into a refinement.
shugo 2012-12-11 01:05:45 +0900 (Tue, 11 Dec 2012) New Revision: 38298 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=38298 Log: * fix the behavior when a module is included into a refinement. This change is a little tricky, so it might be better to prohibit module inclusion to refinements. * include/ruby/ruby.h (RMODULE_INCLUDED_INTO_REFINEMENT): new flag to represent that a module (iclass) is included into a refinement. * class.c (include_modules_at): set RMODULE_INCLUDED_INTO_REFINEMENT if klass is a refinement. * eval.c (rb_mod_refine): set the superclass of a refinement to the refined class for super. * eval.c (rb_using_refinement): skip the above superclass (the refined class) when creating iclasses for refinements. Otherwise, `using Refinement1; using Refinement2' creates iclasses: <Refinement2> -> <RefinedClass> -> <Refinement1> -> RefinedClass, where <Module> is an iclass for Module, so RefinedClass is searched before Refinement1. The correct iclasses should be <Refinement2> -> <Refinement1> -> RefinedClass. * vm_insnhelper.c (vm_search_normal_superclass): if klass is an iclass for a refinement, use the refinement's superclass instead of the iclass's superclass. Otherwise, multiple refinements are searched by super. For example, if a refinement Refinement2 includes a module M (i.e., Refinement2 -> <M> -> RefinedClass, and if refinements iclasses are <Refinement2> -> <M>' -> <Refinement1> -> RefinedClass, then super in <Refinement2> should use Refinement2's superclass <M> instead of <Refinement2>'s superclass <M>'. * vm_insnhelper.c (vm_search_super_method): do not raise a NotImplementError if current_defind_class is a module included into a refinement. Because of the change of vm_search_normal_superclass(), the receiver might not be an instance of the module('s iclass). * test/ruby/test_refinement.rb: related test. Modified files: trunk/ChangeLog trunk/class.c trunk/eval.c trunk/include/ruby/ruby.h trunk/test/ruby/test_refinement.rb trunk/vm_insnhelper.c Index: include/ruby/ruby.h =================================================================== --- include/ruby/ruby.h (revision 38297) +++ include/ruby/ruby.h (revision 38298) @@ -737,6 +737,7 @@ #define RMODULE_SUPER(m) RCLASS_SUPER(m) #define RMODULE_IS_OVERLAID FL_USER2 #define RMODULE_IS_REFINEMENT FL_USER3 +#define RMODULE_INCLUDED_INTO_REFINEMENT FL_USER4 struct RFloat { struct RBasic basic; Index: ChangeLog =================================================================== --- ChangeLog (revision 38297) +++ ChangeLog (revision 38298) @@ -1,3 +1,44 @@ +Tue Dec 11 00:26:58 2012 Shugo Maeda <shugo@r...> + + * fix the behavior when a module is included into a refinement. + This change is a little tricky, so it might be better to prohibit + module inclusion to refinements. + + * include/ruby/ruby.h (RMODULE_INCLUDED_INTO_REFINEMENT): new flag + to represent that a module (iclass) is included into a refinement. + + * class.c (include_modules_at): set RMODULE_INCLUDED_INTO_REFINEMENT + if klass is a refinement. + + * eval.c (rb_mod_refine): set the superclass of a refinement to the + refined class for super. + + * eval.c (rb_using_refinement): skip the above superclass (the + refined class) when creating iclasses for refinements. Otherwise, + `using Refinement1; using Refinement2' creates iclasses: + <Refinement2> -> <RefinedClass> -> <Refinement1> -> RefinedClass, + where <Module> is an iclass for Module, so RefinedClass is + searched before Refinement1. The correct iclasses should be + <Refinement2> -> <Refinement1> -> RefinedClass. + + * vm_insnhelper.c (vm_search_normal_superclass): if klass is an + iclass for a refinement, use the refinement's superclass instead + of the iclass's superclass. Otherwise, multiple refinements are + searched by super. For example, if a refinement Refinement2 + includes a module M (i.e., Refinement2 -> <M> -> RefinedClass, + and if refinements iclasses are <Refinement2> -> <M>' -> + <Refinement1> -> RefinedClass, then super in <Refinement2> should + use Refinement2's superclass <M> instead of <Refinement2>'s + superclass <M>'. + + * vm_insnhelper.c (vm_search_super_method): do not raise a + NotImplementError if current_defind_class is a module included + into a refinement. Because of the change of + vm_search_normal_superclass(), the receiver might not be an + instance of the module('s iclass). + + * test/ruby/test_refinement.rb: related test. + Mon Dec 10 18:35:25 2012 Shugo Maeda <shugo@r...> * vm_method.c (rb_method_entry_without_refinements): use Index: eval.c =================================================================== --- eval.c (revision 38297) +++ eval.c (revision 38298) @@ -1079,7 +1079,7 @@ c = iclass = rb_include_class_new(module, superclass); RCLASS_REFINED_CLASS(c) = klass; module = RCLASS_SUPER(module); - while (module) { + while (module && module != klass) { FL_SET(module, RMODULE_IS_OVERLAID); c = RCLASS_SUPER(c) = rb_include_class_new(module, RCLASS_SUPER(c)); RCLASS_REFINED_CLASS(c) = klass; @@ -1193,6 +1193,7 @@ refinement = rb_hash_lookup(refinements, klass); if (NIL_P(refinement)) { refinement = rb_module_new(); + RCLASS_SUPER(refinement) = klass; FL_SET(refinement, RMODULE_IS_REFINEMENT); CONST_ID(id_refined_class, "__refined_class__"); rb_ivar_set(refinement, id_refined_class, klass); Index: class.c =================================================================== --- class.c (revision 38297) +++ class.c (revision 38298) @@ -720,6 +720,7 @@ st_foreach(RMODULE_M_TBL(module), add_refined_method_entry_i, (st_data_t) refined_class); + FL_SET(c, RMODULE_INCLUDED_INTO_REFINEMENT); } if (RMODULE_M_TBL(module) && RMODULE_M_TBL(module)->num_entries) changed = 1; Index: vm_insnhelper.c =================================================================== --- vm_insnhelper.c (revision 38297) +++ vm_insnhelper.c (revision 38298) @@ -1866,12 +1866,10 @@ { if (BUILTIN_TYPE(klass) == T_ICLASS && FL_TEST(RBASIC(klass)->klass, RMODULE_IS_REFINEMENT)) { - return rb_refinement_module_get_refined_class(RBASIC(klass)->klass); + klass = RBASIC(klass)->klass; } - else { - klass = RCLASS_ORIGIN(klass); - return RCLASS_SUPER(klass); - } + klass = RCLASS_ORIGIN(klass); + return RCLASS_SUPER(klass); } static void @@ -1945,7 +1943,8 @@ current_defind_class = RCLASS_REFINED_CLASS(current_defind_class); } - if (!rb_obj_is_kind_of(ci->recv, current_defind_class)) { + if (!FL_TEST(current_defind_class, RMODULE_INCLUDED_INTO_REFINEMENT) && + !rb_obj_is_kind_of(ci->recv, current_defind_class)) { rb_raise(rb_eNotImpError, "super from singleton method that is defined to multiple classes is not supported; this will be fixed in 2.0.0 or later"); } Index: test/ruby/test_refinement.rb =================================================================== --- test/ruby/test_refinement.rb (revision 38297) +++ test/ruby/test_refinement.rb (revision 38298) @@ -13,6 +13,10 @@ return "Foo#y" end + def a + return "Foo#a" + end + def call_x return x end @@ -31,6 +35,10 @@ def z return "FooExt#z" end + + def a + return "FooExt#a" + end end end @@ -98,6 +106,10 @@ def self.invoke_y_on(foo) return foo.y end + + def self.invoke_a_on(foo) + return foo.a + end end EOF @@ -122,6 +134,13 @@ assert_equal("Foo#y", foo.y) end + def test_using_same_class_refinements + foo = Foo.new + assert_equal("Foo#a", foo.a) + assert_equal("FooExt#a", FooExtClient2.invoke_a_on(foo)) + assert_equal("Foo#a", foo.a) + end + def test_new_method foo = Foo.new assert_raise(NoMethodError) { foo.z } @@ -610,6 +629,69 @@ end end + module IncludeIntoRefinement + class C + def bar + return "C#bar" + end + + def baz + return "C#baz" + end + end + + module Mixin + def foo + return "Mixin#foo" + end + + def bar + return super << " Mixin#bar" + end + + def baz + return super << " Mixin#baz" + end + end + + module M + refine C do + include Mixin + + def baz + return super << " M#baz" + end + end + end + end + + eval <<-EOF, TOPLEVEL_BINDING + using TestRefinement::IncludeIntoRefinement::M + + module TestRefinement::IncludeIntoRefinement::User + def self.invoke_foo_on(x) + x.foo + end + + def self.invoke_bar_on(x) + x.bar + end + + def self.invoke_baz_on(x) + x.baz + end + end + EOF + + def test_include_into_refinement + x = IncludeIntoRefinement::C.new + assert_equal("Mixin#foo", IncludeIntoRefinement::User.invoke_foo_on(x)) + assert_equal("C#bar Mixin#bar", + IncludeIntoRefinement::User.invoke_bar_on(x)) + assert_equal("C#baz Mixin#baz M#baz", + IncludeIntoRefinement::User.invoke_baz_on(x)) + end + private def eval_using(mod, s) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/