Skip to content

Commit

Permalink
Merge pull request #2649 from takaram/method_missing
Browse files Browse the repository at this point in the history
`Object#method` supports methods invoked through `method_missing`
  • Loading branch information
elia committed Apr 11, 2024
2 parents 35ae25c + 890bec1 commit 132f778
Show file tree
Hide file tree
Showing 4 changed files with 19 additions and 15 deletions.
21 changes: 18 additions & 3 deletions opal/corelib/kernel.rb
@@ -1,4 +1,4 @@
# helpers: truthy, coerce_to, respond_to, Opal, deny_frozen_access, freeze, freeze_props, jsid, each_ivar
# helpers: truthy, coerce_to, respond_to, Opal, deny_frozen_access, freeze, freeze_props, jsid, each_ivar, slice
# use_strict: true
# backtick_javascript: true

Expand Down Expand Up @@ -34,11 +34,26 @@ def method(name)
%x{
var meth = self[$jsid(name)];
if (!meth || meth.$$stub) {
if (meth && !meth.$$stub) {
return #{::Method.new(self, `meth.$$owner || #{self.class}`, `meth`, name)};
}
var respond_to_missing = self['$respond_to_missing?'];
if (respond_to_missing.$$pristine || !respond_to_missing.call(self, name, true)) {
#{::Kernel.raise ::NameError.new("undefined method `#{name}' for class `#{self.class}'", name)};
}
return #{::Method.new(self, `meth.$$owner || #{self.class}`, `meth`, name)};
meth = function wrapper() {
var method_missing = self.$method_missing;
if (method_missing == null) {
#{::Kernel.raise ::NameError.new("undefined method `#{name}' for class `#{self.class}'", name)};
}
method_missing.$$p = wrapper.$$p;
return method_missing.apply(self, [name].concat($slice(arguments)));
};
meth.$$parameters = [['rest']]
meth.$$arity = -1;
return #{::Method.new(self, self.class, `meth`, name)};
}
end

Expand Down
3 changes: 0 additions & 3 deletions spec/filters/bugs/delegate.rb
Expand Up @@ -2,7 +2,4 @@
opal_filter "Delegate" do
fails "Delegator#!= is delegated in general" # Exception: Maximum call stack size exceeded
fails "Delegator#== is delegated in general" # Exception: Maximum call stack size exceeded
fails "Delegator#method raises a NameError if method is no longer valid because object has changed" # Expected NameError but no exception was raised ("foo" was returned)
fails "Delegator#method returns a method object for public methods of the delegate object" # NameError: undefined method `pub' for class `DelegateSpecs::Delegator'
fails "Delegator#method returns a method that respond_to_missing?" # NameError: undefined method `pub_too' for class `DelegateSpecs::Simple'
end
4 changes: 0 additions & 4 deletions spec/filters/bugs/kernel.rb
Expand Up @@ -78,13 +78,9 @@
fails "Kernel#is_a? does not take into account `class` method overriding" # TypeError: can't define singleton
fails "Kernel#kind_of? does not take into account `class` method overriding" # TypeError: can't define singleton
fails "Kernel#local_variables is accessible from bindings" # Expected [] to include "a"
fails "Kernel#method can be called even if we only respond_to_missing? method, true" # NameError: undefined method `handled_privately' for class `KernelSpecs::RespondViaMissing'
fails "Kernel#method can call a #method_missing accepting zero or one arguments" # NameError: undefined method `foo' for class `#<Class:0x4bc80>'
fails "Kernel#method converts the given name to a String using #to_str calls #to_str to convert the given name to a String" # Mock 'method-name' expected to receive to_str("any_args") exactly 1 times but received it 0 times
fails "Kernel#method converts the given name to a String using #to_str raises a NoMethodError if the given argument raises a NoMethodError during type coercion to a String" # Expected NoMethodError but got: NameError (undefined method `#<MockObject:0x9e5a6>' for class `Class')
fails "Kernel#method converts the given name to a String using #to_str raises a TypeError if the given name can't be converted to a String" # Expected TypeError but got: NameError (undefined method `' for class `Class')
fails "Kernel#method returns a method object if respond_to_missing?(method) is true" # NameError: undefined method `handled_publicly' for class `KernelSpecs::RespondViaMissing'
fails "Kernel#method the returned method object if respond_to_missing?(method) calls #method_missing with a Symbol name" # NameError: undefined method `handled_publicly' for class `KernelSpecs::RespondViaMissing'
fails "Kernel#method will see an alias of the original method as == when in a derived class" # Expected #<Method: KernelSpecs::B#aliased_pub_method (defined in KernelSpecs::B in ruby/core/kernel/fixtures/classes.rb:164)> == #<Method: KernelSpecs::B#pub_method (defined in KernelSpecs::A in ruby/core/kernel/fixtures/classes.rb:164)> to be truthy but was false
fails "Kernel#methods does not return private singleton methods defined in 'class << self'" # Expected ["ichi", "san", "shi", "roku", "shichi", "hachi", "juu", "juu_ichi", "juu_ni"] not to include "shichi"
fails "Kernel#object_id returns a different value for two Bignum literals" # Expected 4e+100 == 4e+100 to be falsy but was true
Expand Down
6 changes: 1 addition & 5 deletions spec/filters/unsupported/privacy.rb
Expand Up @@ -22,14 +22,10 @@
fails "DelegateClass.public_instance_methods does not include the protected methods of the delegated class" # Expected ["extra", "extra_private", "extra_protected", "__getobj__", "__setobj__", "pub", "priv", "prot", "to_json", "guard", "guard_not", "with_feature", "without_feature", "new_fd", "new_io", "should", "should_not", "version_is", "ruby_version_is", "kernel_version_is", "suppress_warning", "suppress_keyword_warning", "should_receive", "should_not_receive", "stub!", "mock", "mock_int", "mock_numeric", "evaluate", "before", "after", "describe", "it", "it_should_behave_like", "context", "specify", "it_behaves_like", "ruby_bug", "conflicts_with", "big_endian", "little_endian", "platform_is", "platform_is_not", "quarantine!", "not_supported_on", "as_superuser", "as_real_superuser", "as_user", "argf", "argv", "new_datetime", "with_timezone", "fixture", "flunk", "cp", "mkdir_p", "rm_r", "touch", "mock_to_path", "nan_value", "infinity_value", "bignum_value", "max_long", "min_long", "fixnum_max", "fixnum_min", "ruby_exe_options", "resolve_ruby_exe", "ruby_exe", "ruby_cmd", "opal_filter", "opal_unsupported_filter", "frozen_error_class", "pack_format", "unpack_format", "DelegateClass", "expect", "eq", "pretty_print", "pretty_print_cycle", "pretty_print_instance_variables", "pretty_print_inspect", "<=>", "method", "Array", "at_exit", "caller", "caller_locations", "class", "copy_instance_variables", "copy_singleton_methods", "clone", "define_singleton_method", "dup", "enum_for", "exit", "extend", "gets", "hash", "initialize_copy", "instance_of?", "instance_variable_defined?", "instance_variable_get", "instance_variable_set", "remove_instance_variable", "instance_variables", "Integer", "Float", "Hash", "is_a?", "itself", "lambda", "load", "loop", "nil?", "printf", "proc", "puts", "p", "print", "readline", "warn", "raise", "rand", "respond_to?", "require", "require_relative", "require_tree", "singleton_class", "sleep", "srand", "String", "tap", "to_proc", "catch", "throw", "open", "yield_self", "fail", "kind_of?", "object_id", "public_send", "send", "then", "to_enum", "format", "sprintf", "Complex", "Rational", "taint", "untaint", "tainted?", "private_methods", "private_instance_methods", "protected_instance_methods", "eval", "binding", "Pathname", "require_remote", "pretty_inspect", "pp", "opal_parse", "eval_js", "BigDecimal", "initialize", "method_missing", "respond_to_missing?", "target_respond_to?", "methods", "public_methods", "protected_methods", "==", "!=", "eql?", "!", "marshal_dump", "marshal_load", "initialize_clone", "initialize_dup", "freeze", "frozen?", "__raise__", "__send__", "__id__", "equal?", "instance_eval", "instance_exec", "singleton_method_added", "singleton_method_removed", "singleton_method_undefined", "__marshal__", "module_specs_public_method_on_object", "module_specs_private_method_on_object", "module_specs_protected_method_on_object", "module_specs_private_method_on_object_for_kernel_public", "module_specs_public_method_on_object_for_kernel_protected", "module_specs_public_method_on_object_for_kernel_private", "lang_send_rest_len", "example_instance_method_of_object", "defined_specs_method", "defined_specs_receiver", "main_public_method", "main_public_method2", "main_private_method", "main_private_method2", "toplevel_define_other_method", "some_toplevel_method", "public_toplevel_method", "be_close_to_matrix", "check_autoload", "shellsplit", "shellwords", "shellescape", "shelljoin", "=~", "!~", "===", "inspect", "to_s", "module_specs_public_method_on_kernel", "module_specs_alias_on_kernel"] not to include "prot"
fails "Delegator#method raises a NameError for a private methods of the delegate object" # Expected warning to match: /delegator does not forward private method #priv/ but got: ""
fails "Delegator#method raises a NameError for protected methods of the delegate object" # Expected warning to match: /delegator does not forward private method #prot/ but got: ""
fails "Delegator#method returns a method object for private methods of the Delegator class" # NameError: undefined method `extra_private' for class `DelegateSpecs::Simple'
fails "Delegator#method returns a method object for protected methods of the Delegator class" # NameError: undefined method `extra_protected' for class `DelegateSpecs::Simple'
fails "Delegator#method returns a method object for public methods of the Delegator class" # NameError: undefined method `extra' for class `DelegateSpecs::Simple'
fails "Delegator#methods does not include private methods" # Expected ["singleton_method", "pub", "respond_to_missing?", "method_missing", "priv", "prot", "to_json", "guard", "guard_not", "with_feature", "without_feature", "new_fd", "new_io", "should", "should_not", "version_is", "ruby_version_is", "kernel_version_is", "suppress_warning", "suppress_keyword_warning", "should_receive", "should_not_receive", "stub!", "mock", "mock_int", "mock_numeric", "evaluate", "before", "after", "describe", "it", "it_should_behave_like", "context", "specify", "it_behaves_like", "ruby_bug", "conflicts_with", "big_endian", "little_endian", "platform_is", "platform_is_not", "quarantine!", "not_supported_on", "as_superuser", "as_real_superuser", "as_user", "argf", "argv", "new_datetime", "with_timezone", "fixture", "flunk", "cp", "mkdir_p", "rm_r", "touch", "mock_to_path", "nan_value", "infinity_value", "bignum_value", "max_long", "min_long", "fixnum_max", "fixnum_min", "ruby_exe_options", "resolve_ruby_exe", "ruby_exe", "ruby_cmd", "opal_filter", "opal_unsupported_filter", "frozen_error_class", "pack_format", "unpack_format", "DelegateClass", "module_specs_public_method_on_object", "module_specs_private_method_on_object", "module_specs_protected_method_on_object", "module_specs_private_method_on_object_for_kernel_public", "module_specs_public_method_on_object_for_kernel_protected", "module_specs_public_method_on_object_for_kernel_private", "lang_send_rest_len", "example_instance_method_of_object", "defined_specs_method", "defined_specs_receiver", "main_public_method", "main_public_method2", "main_private_method", "main_private_method2", "toplevel_define_other_method", "some_toplevel_method", "public_toplevel_method", "be_close_to_matrix", "shellsplit", "shellwords", "shellescape", "shelljoin", "expect", "eq", "pretty_print", "pretty_print_cycle", "pretty_print_instance_variables", "pretty_print_inspect", "=~", "!~", "===", "<=>", "method", "methods", "public_methods", "Array", "at_exit", "caller", "caller_locations", "class", "copy_instance_variables", "copy_singleton_methods", "clone", "initialize_clone", "define_singleton_method", "dup", "initialize_dup", "enum_for", "equal?", "exit", "extend", "freeze", "frozen?", "gets", "hash", "initialize_copy", "inspect", "instance_of?", "instance_variable_defined?", "instance_variable_get", "instance_variable_set", "remove_instance_variable", "instance_variables", "Integer", "Float", "Hash", "is_a?", "itself", "lambda", "load", "loop", "nil?", "printf", "proc", "puts", "p", "print", "readline", "warn", "raise", "rand", "respond_to?", "require", "require_relative", "require_tree", "singleton_class", "sleep", "srand", "String", "tap", "to_proc", "to_s", "catch", "throw", "open", "yield_self", "fail", "kind_of?", "object_id", "public_send", "send", "then", "to_enum", "format", "sprintf", "Complex", "Rational", "taint", "untaint", "tainted?", "private_methods", "protected_methods", "private_instance_methods", "protected_instance_methods", "eval", "binding", "Pathname", "require_remote", "pretty_inspect", "pp", "opal_parse", "eval_js", "BigDecimal", "module_specs_public_method_on_kernel", "module_specs_alias_on_kernel", "__send__", "__id__", "==", "!", "initialize", "eql?", "!=", "instance_eval", "instance_exec", "singleton_method_added", "singleton_method_removed", "singleton_method_undefined", "__marshal__"] not to include "priv"
fails "Delegator#private_methods includes all private instance methods of the Delegate class" # Expected [] to include "extra_private"
fails "Delegator#protected_methods includes protected instance methods of the Delegator class" # Expected [] to include "extra_protected"
fails "Delegator#protected_methods includes protected methods of the delegate object" # Expected [] to include "prot"
fails "Delegator#public_methods includes public instance methods of the Delegator class" # Expected ["pub", "respond_to_missing?", "method_missing", "priv", "prot", "to_json", "guard", "guard_not", "with_feature", "without_feature", "new_fd", "new_io", "should", "should_not", "version_is", "ruby_version_is", "kernel_version_is", "suppress_warning", "suppress_keyword_warning", "should_receive", "should_not_receive", "stub!", "mock", "mock_int", "mock_numeric", "evaluate", "before", "after", "describe", "it", "it_should_behave_like", "context", "specify", "it_behaves_like", "ruby_bug", "conflicts_with", "big_endian", "little_endian", "platform_is", "platform_is_not", "quarantine!", "not_supported_on", "as_superuser", "as_real_superuser", "as_user", "argf", "argv", "new_datetime", "with_timezone", "fixture", "flunk", "cp", "mkdir_p", "rm_r", "touch", "mock_to_path", "nan_value", "infinity_value", "bignum_value", "max_long", "min_long", "fixnum_max", "fixnum_min", "ruby_exe_options", "resolve_ruby_exe", "ruby_exe", "ruby_cmd", "opal_filter", "opal_unsupported_filter", "frozen_error_class", "pack_format", "unpack_format", "DelegateClass", "module_specs_public_method_on_object", "module_specs_private_method_on_object", "module_specs_protected_method_on_object", "module_specs_private_method_on_object_for_kernel_public", "module_specs_public_method_on_object_for_kernel_protected", "module_specs_public_method_on_object_for_kernel_private", "lang_send_rest_len", "expect", "eq", "pretty_print", "pretty_print_cycle", "pretty_print_instance_variables", "pretty_print_inspect", "=~", "!~", "===", "<=>", "method", "methods", "public_methods", "Array", "at_exit", "caller", "caller_locations", "class", "copy_instance_variables", "copy_singleton_methods", "clone", "initialize_clone", "define_singleton_method", "dup", "initialize_dup", "enum_for", "equal?", "exit", "extend", "freeze", "frozen?", "gets", "hash", "initialize_copy", "inspect", "instance_of?", "instance_variable_defined?", "instance_variable_get", "instance_variable_set", "remove_instance_variable", "instance_variables", "Integer", "Float", "Hash", "is_a?", "itself", "lambda", "load", "loop", "nil?", "printf", "proc", "puts", "p", "print", "readline", "warn", "raise", "rand", "respond_to?", "require", "require_relative", "require_tree", "singleton_class", "sleep", "srand", "String", "tap", "to_proc", "to_s", "catch", "throw", "open", "yield_self", "fail", "kind_of?", "object_id", "public_send", "send", "then", "to_enum", "format", "sprintf", "Complex", "Rational", "taint", "untaint", "tainted?", "private_methods", "protected_methods", "private_instance_methods", "protected_instance_methods", "eval", "binding", "Pathname", "require_remote", "pretty_inspect", "pp", "opal_parse", "eval_js", "BigDecimal", "module_specs_public_method_on_kernel", "module_specs_alias_on_kernel", "__send__", "__id__", "==", "!", "initialize", "eql?", "!=", "instance_eval", "instance_exec", "singleton_method_added", "singleton_method_removed", "singleton_method_undefined"] to include "extra"
fails "Enumerator#initialize is a private method" # Expected Enumerator to have private instance method 'initialize' but it does not
fails "Enumerator::Chain#initialize is a private method" # Expected Enumerator::Chain to have private instance method 'initialize' but it does not
fails "Enumerator::Generator#initialize is a private method" # Expected Enumerator::Generator to have private instance method 'initialize' but it does not
Expand Down Expand Up @@ -251,5 +247,5 @@
fails "Regexp#initialize is a private method" # Expected Regexp to have private method 'initialize' but it does not
fails "Set#flatten_merge is protected" # Expected Set to have protected instance method 'flatten_merge' but it does not
fails "String#+@ returns mutable copy despite freeze-magic-comment in file" # NoMethodError: undefined method `tmp' for #<MSpecEnv:0x2022e>
fails "StringScanner#initialize is a private method" # Expected StringScanner to have private instance method 'initialize' but it does not
fails "StringScanner#initialize is a private method" # Expected StringScanner to have private instance method 'initialize' but it does not
end

0 comments on commit 132f778

Please sign in to comment.