From 856349f4757857bb36a4cd9d48184b949274774d Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Sat, 6 Jul 2024 22:09:07 -0500 Subject: [PATCH 1/3] Add Kernel#protected_methods --- include/natalie/kernel_module.hpp | 1 + lib/natalie/compiler/binding_gen.rb | 1 + spec/core/kernel/protected_methods_spec.rb | 69 ++++++++++++++++++++++ src/kernel_module.cpp | 7 +++ 4 files changed, 78 insertions(+) create mode 100644 spec/core/kernel/protected_methods_spec.rb diff --git a/include/natalie/kernel_module.hpp b/include/natalie/kernel_module.hpp index ca452b67a..aa32d764f 100644 --- a/include/natalie/kernel_module.hpp +++ b/include/natalie/kernel_module.hpp @@ -89,6 +89,7 @@ class KernelModule : public Object { Value methods(Env *env, Value regular_val); bool neqtilde(Env *, Value); Value private_methods(Env *, Value = nullptr); + Value protected_methods(Env *, Value = nullptr); Value remove_instance_variable(Env *env, Value name_val); Value tap(Env *env, Block *block); bool is_a(Env *env, Value module); diff --git a/lib/natalie/compiler/binding_gen.rb b/lib/natalie/compiler/binding_gen.rb index 1b47e625b..c85d0145e 100644 --- a/lib/natalie/compiler/binding_gen.rb +++ b/lib/natalie/compiler/binding_gen.rb @@ -996,6 +996,7 @@ def generate_name gen.binding('Kernel', 'method', 'KernelModule', 'method', argc: 1, pass_env: true, pass_block: false, return_type: :Object) gen.binding('Kernel', 'methods', 'KernelModule', 'methods', argc: 0..1, pass_env: true, pass_block: false, return_type: :Object) gen.binding('Kernel', 'private_methods', 'KernelModule', 'private_methods', argc: 0..1, pass_env: true, pass_block: false, return_type: :Object) +gen.binding('Kernel', 'protected_methods', 'KernelModule', 'protected_methods', argc: 0..1, pass_env: true, pass_block: false, return_type: :Object) gen.binding('Kernel', 'public_methods', 'KernelModule', 'methods', argc: 0..1, pass_env: true, pass_block: false, return_type: :Object) gen.binding('Kernel', 'public_send', 'Object', 'public_send', argc: 1.., pass_env: true, pass_block: true, return_type: :Object) gen.binding('Kernel', 'object_id', 'Object', 'object_id', argc: 0, pass_env: false, pass_block: false, return_type: :int) diff --git a/spec/core/kernel/protected_methods_spec.rb b/spec/core/kernel/protected_methods_spec.rb new file mode 100644 index 000000000..d3334e886 --- /dev/null +++ b/spec/core/kernel/protected_methods_spec.rb @@ -0,0 +1,69 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' + +# TODO: rewrite + +# The reason why having include() is to show the specification explicitly. +# You should use have_protected_method() with the exception of this spec. +describe "Kernel#protected_methods" do + it "returns a list of the names of protected methods accessible in the object" do + KernelSpecs::Methods.protected_methods(false).sort.should include(:juu_ichi) + KernelSpecs::Methods.new.protected_methods(false).should include(:ku) + end + + it "returns a list of the names of protected methods accessible in the object and from its ancestors and mixed-in modules" do + l1 = KernelSpecs::Methods.protected_methods(false) + l2 = KernelSpecs::Methods.protected_methods + (l1 & l2).should include(:juu_ichi) + KernelSpecs::Methods.new.protected_methods.should include(:ku) + end + + it "returns methods mixed in to the metaclass" do + m = KernelSpecs::Methods.new + m.extend(KernelSpecs::Methods::MetaclassMethods) + m.protected_methods.should include(:nopeeking) + end +end + +describe :kernel_protected_methods_supers, shared: true do + it "returns a unique list for an object extended by a module" do + m = ReflectSpecs.oed.protected_methods(*@object) + m.select { |x| x == :pro }.sort.should == [:pro] + end + + it "returns a unique list for a class including a module" do + m = ReflectSpecs::D.new.protected_methods(*@object) + m.select { |x| x == :pro }.sort.should == [:pro] + end + + it "returns a unique list for a subclass of a class that includes a module" do + m = ReflectSpecs::E.new.protected_methods(*@object) + m.select { |x| x == :pro }.sort.should == [:pro] + end +end + +describe :kernel_protected_methods_with_falsy, shared: true do + it "returns a list of protected methods in without its ancestors" do + ReflectSpecs::F.protected_methods(@object).select{|m|/_pro\z/ =~ m}.sort.should == [:ds_pro, :fs_pro] + ReflectSpecs::F.new.protected_methods(@object).should == [:f_pro] + end +end + +describe "Kernel#protected_methods" do + describe "when not passed an argument" do + it_behaves_like :kernel_protected_methods_supers, nil, [] + end + + describe "when passed true" do + it_behaves_like :kernel_protected_methods_supers, nil, true + end + + describe "when passed false" do + it_behaves_like :kernel_protected_methods_with_falsy, nil, false + end + + describe "when passed nil" do + it_behaves_like :kernel_protected_methods_with_falsy, nil, nil + end +end diff --git a/src/kernel_module.cpp b/src/kernel_module.cpp index 6cc1e0559..1cae0621b 100644 --- a/src/kernel_module.cpp +++ b/src/kernel_module.cpp @@ -542,6 +542,13 @@ Value KernelModule::private_methods(Env *env, Value regular_val) { } } +Value KernelModule::protected_methods(Env *env, Value recur) { + if (singleton_class()) + return singleton_class()->protected_instance_methods(env, recur); + else + return klass()->protected_instance_methods(env, FalseObject::the()); +} + Value KernelModule::proc(Env *env, Block *block) { if (block) { return new ProcObject { block }; From ed4accbd9394dea00746a99eb62b94416d211457 Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Sat, 6 Jul 2024 22:12:44 -0500 Subject: [PATCH 2/3] Make Kernel#private_methods spec-compliant --- spec/core/kernel/private_methods_spec.rb | 10 +++------- src/kernel_module.cpp | 19 +++++-------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/spec/core/kernel/private_methods_spec.rb b/spec/core/kernel/private_methods_spec.rb index 866fcd938..041634d1e 100644 --- a/spec/core/kernel/private_methods_spec.rb +++ b/spec/core/kernel/private_methods_spec.rb @@ -8,9 +8,7 @@ m = KernelSpecs::Methods.private_methods(false) m.should include(:shichi) m = KernelSpecs::Methods.new.private_methods(false) - NATFIXME 'returns a list of the names of privately accessible methods in the object', exception: SpecFailedException do - m.should include(:juu_shi) - end + m.should include(:juu_shi) end it "returns a list of the names of privately accessible methods in the object and its ancestors and mixed-in modules" do @@ -47,10 +45,8 @@ describe :kernel_private_methods_with_falsy, shared: true do it "returns a list of private methods in without its ancestors" do - NATFIXME 'returns a list of private methods in without its ancestors', exception: SpecFailedException do - ReflectSpecs::F.private_methods(@object).select{|m|/_pri\z/ =~ m}.sort.should == [:ds_pri, :fs_pri] - ReflectSpecs::F.new.private_methods(@object).should == [:f_pri] - end + ReflectSpecs::F.private_methods(@object).select{|m|/_pri\z/ =~ m}.sort.should == [:ds_pri, :fs_pri] + ReflectSpecs::F.new.private_methods(@object).should == [:f_pri] end end diff --git a/src/kernel_module.cpp b/src/kernel_module.cpp index 1cae0621b..6cae3bf14 100644 --- a/src/kernel_module.cpp +++ b/src/kernel_module.cpp @@ -526,20 +526,11 @@ Value KernelModule::print(Env *env, Args args) { return _stdout->send(env, "write"_s, args); } -Value KernelModule::private_methods(Env *env, Value regular_val) { - bool regular = regular_val ? regular_val->is_truthy() : true; - if (regular) { - if (singleton_class()) { - return singleton_class()->private_instance_methods(env, TrueObject::the()); - } else { - return klass()->private_instance_methods(env, TrueObject::the()); - } - } - if (singleton_class()) { - return singleton_class()->private_instance_methods(env, FalseObject::the()); - } else { - return new ArrayObject {}; - } +Value KernelModule::private_methods(Env *env, Value recur) { + if (singleton_class()) + return singleton_class()->private_instance_methods(env, recur); + else + return klass()->private_instance_methods(env, FalseObject::the()); } Value KernelModule::protected_methods(Env *env, Value recur) { From f6b7aa409870ba41f80991d9125fa8fe8881cd0c Mon Sep 17 00:00:00 2001 From: Tim Morgan Date: Sat, 6 Jul 2024 22:14:22 -0500 Subject: [PATCH 3/3] Make Kernel#public_methods spec-compliant --- include/natalie/kernel_module.hpp | 1 + lib/natalie/compiler/binding_gen.rb | 2 +- spec/core/kernel/public_methods_spec.rb | 76 +++++++++++++++++++++++++ src/kernel_module.cpp | 7 +++ 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 spec/core/kernel/public_methods_spec.rb diff --git a/include/natalie/kernel_module.hpp b/include/natalie/kernel_module.hpp index aa32d764f..b1f3bdd6d 100644 --- a/include/natalie/kernel_module.hpp +++ b/include/natalie/kernel_module.hpp @@ -90,6 +90,7 @@ class KernelModule : public Object { bool neqtilde(Env *, Value); Value private_methods(Env *, Value = nullptr); Value protected_methods(Env *, Value = nullptr); + Value public_methods(Env *, Value = nullptr); Value remove_instance_variable(Env *env, Value name_val); Value tap(Env *env, Block *block); bool is_a(Env *env, Value module); diff --git a/lib/natalie/compiler/binding_gen.rb b/lib/natalie/compiler/binding_gen.rb index c85d0145e..a2f79abc2 100644 --- a/lib/natalie/compiler/binding_gen.rb +++ b/lib/natalie/compiler/binding_gen.rb @@ -997,7 +997,7 @@ def generate_name gen.binding('Kernel', 'methods', 'KernelModule', 'methods', argc: 0..1, pass_env: true, pass_block: false, return_type: :Object) gen.binding('Kernel', 'private_methods', 'KernelModule', 'private_methods', argc: 0..1, pass_env: true, pass_block: false, return_type: :Object) gen.binding('Kernel', 'protected_methods', 'KernelModule', 'protected_methods', argc: 0..1, pass_env: true, pass_block: false, return_type: :Object) -gen.binding('Kernel', 'public_methods', 'KernelModule', 'methods', argc: 0..1, pass_env: true, pass_block: false, return_type: :Object) +gen.binding('Kernel', 'public_methods', 'KernelModule', 'public_methods', argc: 0..1, pass_env: true, pass_block: false, return_type: :Object) gen.binding('Kernel', 'public_send', 'Object', 'public_send', argc: 1.., pass_env: true, pass_block: true, return_type: :Object) gen.binding('Kernel', 'object_id', 'Object', 'object_id', argc: 0, pass_env: false, pass_block: false, return_type: :int) gen.binding('Kernel', 'remove_instance_variable', 'KernelModule', 'remove_instance_variable', argc: 1, pass_env: true, pass_block: false, return_type: :Object) diff --git a/spec/core/kernel/public_methods_spec.rb b/spec/core/kernel/public_methods_spec.rb new file mode 100644 index 000000000..a5512784f --- /dev/null +++ b/spec/core/kernel/public_methods_spec.rb @@ -0,0 +1,76 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' + +# TODO: rewrite +describe "Kernel#public_methods" do + it "returns a list of the names of publicly accessible methods in the object" do + KernelSpecs::Methods.public_methods(false).sort.should include(:hachi, + :ichi, :juu, :juu_ni, :roku, :san, :shi) + KernelSpecs::Methods.new.public_methods(false).sort.should include(:juu_san, :ni) + end + + it "returns a list of names without protected accessible methods in the object" do + KernelSpecs::Methods.public_methods(false).sort.should_not include(:juu_ichi) + KernelSpecs::Methods.new.public_methods(false).sort.should_not include(:ku) + end + + it "returns a list of the names of publicly accessible methods in the object and its ancestors and mixed-in modules" do + (KernelSpecs::Methods.public_methods(false) & KernelSpecs::Methods.public_methods).sort.should include( + :hachi, :ichi, :juu, :juu_ni, :roku, :san, :shi) + m = KernelSpecs::Methods.new.public_methods + m.should include(:ni, :juu_san) + end + + it "returns methods mixed in to the metaclass" do + m = KernelSpecs::Methods.new + m.extend(KernelSpecs::Methods::MetaclassMethods) + m.public_methods.should include(:peekaboo) + end + + it "returns public methods for immediates" do + 10.public_methods.should include(:divmod) + end +end + +describe :kernel_public_methods_supers, shared: true do + it "returns a unique list for an object extended by a module" do + m = ReflectSpecs.oed.public_methods(*@object) + m.select { |x| x == :pub }.sort.should == [:pub] + end + + it "returns a unique list for a class including a module" do + m = ReflectSpecs::D.new.public_methods(*@object) + m.select { |x| x == :pub }.sort.should == [:pub] + end + + it "returns a unique list for a subclass of a class that includes a module" do + m = ReflectSpecs::E.new.public_methods(*@object) + m.select { |x| x == :pub }.sort.should == [:pub] + end +end + +describe :kernel_public_methods_with_falsy, shared: true do + it "returns a list of public methods in without its ancestors" do + ReflectSpecs::F.public_methods(@object).select{|m|/_pub\z/ =~ m}.sort.should == [:ds_pub, :fs_pub] + ReflectSpecs::F.new.public_methods(@object).should == [:f_pub] + end +end + +describe "Kernel#public_methods" do + describe "when not passed an argument" do + it_behaves_like :kernel_public_methods_supers, nil, [] + end + + describe "when passed true" do + it_behaves_like :kernel_public_methods_supers, nil, true + end + + describe "when passed false" do + it_behaves_like :kernel_public_methods_with_falsy, nil, false + end + + describe "when passed nil" do + it_behaves_like :kernel_public_methods_with_falsy, nil, nil + end +end diff --git a/src/kernel_module.cpp b/src/kernel_module.cpp index 6cae3bf14..ed7af638c 100644 --- a/src/kernel_module.cpp +++ b/src/kernel_module.cpp @@ -540,6 +540,13 @@ Value KernelModule::protected_methods(Env *env, Value recur) { return klass()->protected_instance_methods(env, FalseObject::the()); } +Value KernelModule::public_methods(Env *env, Value recur) { + if (singleton_class()) + return singleton_class()->public_instance_methods(env, recur); + else + return klass()->public_instance_methods(env, FalseObject::the()); +} + Value KernelModule::proc(Env *env, Block *block) { if (block) { return new ProcObject { block };