From eb8ea336d33af7e1dec4c17964c671c33cf75ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Bu=C5=82at?= Date: Fri, 18 Dec 2020 19:17:57 +0100 Subject: Feature 17314: allow to pass array to public, protected and private methods --- NEWS.md | 3 +++ spec/ruby/core/main/fixtures/classes.rb | 8 ++++++ spec/ruby/core/main/private_spec.rb | 31 +++++++++++++++++++---- spec/ruby/core/main/public_spec.rb | 27 +++++++++++++++++--- spec/ruby/core/module/shared/set_visibility.rb | 34 ++++++++++++++++++++++++++ vm_method.c | 31 ++++++++++++++++++----- 6 files changed, 120 insertions(+), 14 deletions(-) diff --git a/NEWS.md b/NEWS.md index f72e7f028c..3e42459fc0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -249,6 +249,9 @@ Outstanding ones only. p C.ancestors #=> [C, M1, M2, Object, Kernel, BasicObject] ``` + * Module#public, Module#protected and Module#private methods now accept single + array argument with a list of method names. [[Feature #17314]] + * Module#attr_accessor, Module#attr_reader, Module#attr_writer and Module#attr methods now return array of defined methods names as symbols. [[Feature #17314]] diff --git a/spec/ruby/core/main/fixtures/classes.rb b/spec/ruby/core/main/fixtures/classes.rb index 6aba948ce0..757cee4e4a 100644 --- a/spec/ruby/core/main/fixtures/classes.rb +++ b/spec/ruby/core/main/fixtures/classes.rb @@ -13,6 +13,14 @@ def main_public_method end public :main_public_method +def main_public_method2 +end +public :main_public_method2 + def main_private_method end private :main_private_method + +def main_private_method2 +end +private :main_private_method2 diff --git a/spec/ruby/core/main/private_spec.rb b/spec/ruby/core/main/private_spec.rb index e34e0c7b7b..78c5d287d4 100644 --- a/spec/ruby/core/main/private_spec.rb +++ b/spec/ruby/core/main/private_spec.rb @@ -4,20 +4,41 @@ require_relative 'fixtures/classes' describe "main#private" do after :each do Object.send(:public, :main_public_method) + Object.send(:public, :main_public_method2) end - it "sets the visibility of the given method to private" do - eval "private :main_public_method", TOPLEVEL_BINDING - Object.should have_private_method(:main_public_method) + context "when single argument is passed and it is not an array" do + it "sets the visibility of the given methods to private" do + eval "private :main_public_method", TOPLEVEL_BINDING + Object.should have_private_method(:main_public_method) + end + end + + context "when multiple arguments are passed" do + it "sets the visibility of the given methods to private" do + eval "private :main_public_method, :main_public_method2", TOPLEVEL_BINDING + Object.should have_private_method(:main_public_method) + Object.should have_private_method(:main_public_method2) + end + end + + ruby_version_is "3.0" do + context "when single argument is passed and is an array" do + it "sets the visibility of the given methods to private" do + eval "private [:main_public_method, :main_public_method2]", TOPLEVEL_BINDING + Object.should have_private_method(:main_public_method) + Object.should have_private_method(:main_public_method2) + end + end end it "returns Object" do eval("private :main_public_method", TOPLEVEL_BINDING).should equal(Object) end - it "raises a NameError when given an undefined name" do + it "raises a NameError when at least one of given method names is undefined" do -> do - eval "private :main_undefined_method", TOPLEVEL_BINDING + eval "private :main_public_method, :main_undefined_method", TOPLEVEL_BINDING end.should raise_error(NameError) end end diff --git a/spec/ruby/core/main/public_spec.rb b/spec/ruby/core/main/public_spec.rb index afe25c705a..bfc27a9e80 100644 --- a/spec/ruby/core/main/public_spec.rb +++ b/spec/ruby/core/main/public_spec.rb @@ -4,11 +4,32 @@ require_relative 'fixtures/classes' describe "main#public" do after :each do Object.send(:private, :main_private_method) + Object.send(:private, :main_private_method2) end - it "sets the visibility of the given method to public" do - eval "public :main_private_method", TOPLEVEL_BINDING - Object.should_not have_private_method(:main_private_method) + context "when single argument is passed and it is not an array" do + it "sets the visibility of the given methods to public" do + eval "public :main_private_method", TOPLEVEL_BINDING + Object.should_not have_private_method(:main_private_method) + end + end + + context "when multiple arguments are passed" do + it "sets the visibility of the given methods to public" do + eval "public :main_private_method, :main_private_method2", TOPLEVEL_BINDING + Object.should_not have_private_method(:main_private_method) + Object.should_not have_private_method(:main_private_method2) + end + end + + ruby_version_is "3.0" do + context "when single argument is passed and is an array" do + it "sets the visibility of the given methods to public" do + eval "public [:main_private_method, :main_private_method2]", TOPLEVEL_BINDING + Object.should_not have_private_method(:main_private_method) + Object.should_not have_private_method(:main_private_method2) + end + end end it "returns Object" do diff --git a/spec/ruby/core/module/shared/set_visibility.rb b/spec/ruby/core/module/shared/set_visibility.rb index a04b1a54a0..9f31e230ca 100644 --- a/spec/ruby/core/module/shared/set_visibility.rb +++ b/spec/ruby/core/module/shared/set_visibility.rb @@ -6,6 +6,40 @@ describe :set_visibility, shared: true do end describe "with argument" do + describe "one or more arguments" do + it "sets visibility of given method names" do + visibility = @method + old_visibility = [:protected, :private].find {|vis| vis != visibility } + + mod = Module.new { + send old_visibility + def test1() end + def test2() end + send visibility, :test1, :test2 + } + mod.should send(:"have_#{visibility}_instance_method", :test1, false) + mod.should send(:"have_#{visibility}_instance_method", :test2, false) + end + end + + ruby_version_is "3.0" do + describe "array as a single argument" do + it "sets visibility of given method names" do + visibility = @method + old_visibility = [:protected, :private].find {|vis| vis != visibility } + + mod = Module.new { + send old_visibility + def test1() end + def test2() end + send visibility, [:test1, :test2] + } + mod.should send(:"have_#{visibility}_instance_method", :test1, false) + mod.should send(:"have_#{visibility}_instance_method", :test2, false) + end + end + end + it "does not clone method from the ancestor when setting to the same visibility in a child" do visibility = @method parent = Module.new { diff --git a/vm_method.c b/vm_method.c index 1546722b14..1fbb481b50 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1973,6 +1973,16 @@ rb_mod_alias_method(VALUE mod, VALUE newname, VALUE oldname) return ID2SYM(id); } +static void +check_and_export_method(VALUE self, VALUE name, rb_method_visibility_t visi) +{ + ID id = rb_check_id(&name); + if (!id) { + rb_print_undef_str(self, name); + } + rb_export_method(self, id, visi); +} + static void set_method_visibility(VALUE self, int argc, const VALUE *argv, rb_method_visibility_t visi) { @@ -1985,13 +1995,19 @@ set_method_visibility(VALUE self, int argc, const VALUE *argv, rb_method_visibil return; } - for (i = 0; i < argc; i++) { - VALUE v = argv[i]; - ID id = rb_check_id(&v); - if (!id) { - rb_print_undef_str(self, v); + + VALUE v; + + if (argc == 1 && (v = rb_check_array_type(argv[0])) != Qnil) { + long j; + + for (j = 0; j < RARRAY_LEN(v); j++) { + check_and_export_method(self, RARRAY_AREF(v, j), visi); } - rb_export_method(self, id, visi); + } else { + for (i = 0; i < argc; i++) { + check_and_export_method(self, argv[i], visi); + } } } @@ -2013,6 +2029,7 @@ set_visibility(int argc, const VALUE *argv, VALUE module, rb_method_visibility_t * public -> self * public(symbol, ...) -> self * public(string, ...) -> self + * public(array) -> self * * With no arguments, sets the default visibility for subsequently * defined methods to public. With arguments, sets the named methods to @@ -2031,6 +2048,7 @@ rb_mod_public(int argc, VALUE *argv, VALUE module) * protected -> self * protected(symbol, ...) -> self * protected(string, ...) -> self + * protected(array) -> self * * With no arguments, sets the default visibility for subsequently * defined methods to protected. With arguments, sets the named methods @@ -2058,6 +2076,7 @@ rb_mod_protected(int argc, VALUE *argv, VALUE module) * private -> self * private(symbol, ...) -> self * private(string, ...) -> self + * private(array) -> self * * With no arguments, sets the default visibility for subsequently * defined methods to private. With arguments, sets the named methods -- cgit v1.2.3