Project

General

Profile

Actions

Bug #22276

open

alias in a module falls back to Object even in classes not inheriting from Object

Bug #22276: alias in a module falls back to Object even in classes not inheriting from Object

Added by shugo (Shugo Maeda) 3 days ago. Updated 3 days ago.

Status:
Open
Assignee:
-
Target version:
-
ruby -v:
ruby 4.1.0dev (2026-08-28T07:43:16Z master 69b49ac7ae) +PRISM [x86_64-linux]
[ruby-core:126537]

Description

When alias (or alias_method) is used in a module and the method is not found in the module, alias searches the method from Object. So a module can alias a method of Kernel or Object, and the alias works even if the module is later included in a class that does not have Object and Kernel as ancestors:

module M
  alias foo puts
  public :foo
end

class X < BasicObject
  include ::M
end

X.new.foo("hello")  #=> hello

X does not include Kernel, but X.new.foo calls Kernel#puts.

This fallback comes from Ruby 1.8, where Object was the root class, so every class that includes a module always had Object and Kernel as ancestors. Since Ruby 1.9 introduced BasicObject, this is no longer true, but the fallback is unchanged. As discussed in #22273, a module should be able to alias only its own methods and its ancestors' methods, and Object is not an ancestor of a module.

This behavior is intentional in the current implementation. There are tests for it (test_alias_in_module in test/ruby/test_alias.rb for #9663, and "accesses a method defined on Object from Kernel" in spec/ruby/core/module/alias_method_spec.rb), and the documentation of Module#alias_method has an example module Mod; alias_method :orig_exit, :exit; end. So code like module M; alias orig_to_s to_s; end exists, and simply raising NameError will break it.

Possible fixes:

  1. Remove the fallback and raise NameError. This breaks existing code.
  2. Do not resolve the method at alias time. Instead, resolve it at call time from the ancestors of the receiver's class, like ZSUPER methods. Existing code that includes the module in a subclass of Object keeps working, and X.new.foo above raises NoMethodError.
  3. Keep the current behavior and document it.

I think 2 is the best choice for compatibility.

Updated by shugo (Shugo Maeda) 3 days ago Actions #1

I implemented option 2: https://github.com/ruby/ruby/pull/18553

When alias in a module does not find the method in the module, it now creates a ZSUPER method entry whose original name is the aliased method. Object is searched only to check that the method exists and to take its visibility, so alias foo bar in a module still raises NameError when bar is not defined anywhere.

The method is resolved at call time, starting from the ancestors after the module in the receiver's class, in the same way as super. So:

module M
  alias foo puts
  public :foo
end

class X < BasicObject
  include ::M
end
X.new.foo("hello")  #=> NoMethodError (undefined method 'foo' for an instance of X)

class Y
  include M
end
Y.new.foo("hello")  #=> hello

Existing code that includes the module in a subclass of Object keeps working, including the alias_method_chain pattern:

module M
  alias orig_puts puts
  def puts(*args) = orig_puts("wrapped:", *args)
end

Because the alias is resolved from the ancestors after the module, an alias in Kernel of a method defined in Object can no longer be called (the alias itself is still created). This is the "descendant method" case discussed in #22273. The spec "accesses a method defined on Object from Kernel" only checks the method lists, so it still passes.

Other visible changes:

  • M.instance_method(:foo) on the module itself raises NameError, because the method cannot be resolved without a receiver class. Y.instance_method(:foo) and Y.new.method(:foo) work, and Method#name returns :foo.
  • private :class; alias_method :xyz, :class in a module used to raise NameError by accident (the lookup continued from the module's superclass, which is nil). Now it creates a private alias. The test test_undef_method_error_message_with_zsuper_method is changed to use a class for the same error message check.

Internally, ZSUPER dispatch now looks up def->original_id instead of the called name. They were always the same before, so existing ZSUPER methods are not affected.

Actions

Also available in: PDF Atom