Project

General

Profile

Actions

Bug #19067

closed

Module methods not usable at toplevel under wrapped script

Added by shioyama (Chris Salzberg) over 1 year ago. Updated over 1 year ago.

Status:
Rejected
Assignee:
-
Target version:
-
[ruby-core:110378]

Description

#foo.rb 
module Foo
end
private_constant :Foo
module MyModule; end
load "./foo.rb", MyModule
# undefined method `private_constant' for main:Object (NoMethodError)
#                                            
# private_constant :Foo                      
# ^^^^^^^^^^^^^^^^                           
# Did you mean?  private_methods

However, this works:

module MyModule
  module Foo
  end
  private_constant :Foo
end

load loads the code under the wrap module, so this seems like a bug to me.

This applies to all methods on Module, which are usable inside a normal module definition but not usable when the module is the wrap argument to load.

I think these should all be usable in this context. If they cannot, then clarity is needed about what "being executed under" means in the documentation for load.

See: https://bugs.ruby-lang.org/issues/19024#note-23

Actions #1

Updated by shioyama (Chris Salzberg) over 1 year ago

  • Subject changed from `private_constant` does not work at toplevel in wrapped module context to private_constant does not work at toplevel in wrapped module context
Actions #2

Updated by shioyama (Chris Salzberg) over 1 year ago

  • Description updated (diff)
Actions #3

Updated by shioyama (Chris Salzberg) over 1 year ago

  • Subject changed from private_constant does not work at toplevel in wrapped module context to private_constant raises NoMethodError when called at toplevel under wrapped script
Actions #4

Updated by shioyama (Chris Salzberg) over 1 year ago

  • Description updated (diff)
Actions #5

Updated by shioyama (Chris Salzberg) over 1 year ago

  • Subject changed from private_constant raises NoMethodError when called at toplevel under wrapped script to private_constant and other Module methods not usable at toplevel under wrapped script
Actions #6

Updated by shioyama (Chris Salzberg) over 1 year ago

  • Subject changed from private_constant and other Module methods not usable at toplevel under wrapped script to Module class methods not usable at toplevel under wrapped script
Actions #7

Updated by shioyama (Chris Salzberg) over 1 year ago

  • Description updated (diff)
Actions #8

Updated by shioyama (Chris Salzberg) over 1 year ago

  • Description updated (diff)
Actions #9

Updated by shioyama (Chris Salzberg) over 1 year ago

  • Subject changed from Module class methods not usable at toplevel under wrapped script to Module methods not usable at toplevel under wrapped script

Updated by fxn (Xavier Noria) over 1 year ago

I have always been skeptical about the ability to pass a module to Kernel#load.

As an author of the target file I no longer control the nesting, which is fundamental to know the constant references I am writing are the ones I actually mean. Now, the nesting is always in the hands of the caller, and that to me is a red flag.

In this case, private_constant does not work at the top-level, why should I write that code in the first place? You can't either define methods for that matter. This feature is not like writing module WrappingModule at the top of the file.

The documentation is unclear about what does "being executed under" mean. Without that definition, we cannot know if this is a bug.

Updated by shioyama (Chris Salzberg) over 1 year ago

The documentation is unclear about what does "being executed under" mean. Without that definition, we cannot know if this is a bug.

This is true. I am labelling it as a "bug" but it should be understood as "inconsistency".

Updated by shioyama (Chris Salzberg) over 1 year ago

As an author of the target file I no longer control the nesting, which is fundamental to know the constant references I am writing are the ones I actually mean.

You cannot be sure the constant you are referencing is the one you mean, ever. I can define "my" Foo, then load your file which does something with "your" Foo, and your assumptions about Foo may be incorrect because of what my Foo has in it.

If you want to absolutely be sure what a constant is referencing, you would need to disallow load altogether.

Updated by mame (Yusuke Endoh) over 1 year ago

I think we can say the following by the same reasoning:

# foo.rb
p self
module MyModule; end
load "./foo.rb", MyModule #=> current: self, expected?: MyModule

because:

module MyModule
  p self #=> MyModule
end

This is a bit incompatible, but I wonder if it is better than dealing with Module#using, Module#private_method, etc. individually.

Updated by shioyama (Chris Salzberg) over 1 year ago

This is a bit incompatible, but I wonder if it is better than dealing with Module#using, Module#private_method, etc. individually.

Yes that's a fair point, and to be honest, I didn't realize how much failed to work as "expected" until after I had created this issue.

Still, I think this is worth thinking about. Previously, it was not possible to control the module to load, so this was not really an issue. But now it is.

And I do think it's slightly ambiguous what is supposed to happen here.

e.g.

# foo.rb
require "my_dependency"

# ... code that depends on my_dependency
module MyModule
  def require(filename)
    puts "required #{filename}!"
  end
end

load "./foo.rb", MyModule
#=> required my_dependency!

In this case, because top_self extends the top_wrapper module, require is (re-)defined and foo.rb does not actually require its dependency.

This is interesting, but I don't think it was the original intention of extending top_self with top_wrapper.

Actions #15

Updated by shioyama (Chris Salzberg) over 1 year ago

  • Description updated (diff)
Actions #16

Updated by shioyama (Chris Salzberg) over 1 year ago

  • Description updated (diff)

Updated by shioyama (Chris Salzberg) over 1 year ago

This is actually even confusing prior to the recent change:

If the optional wrap parameter is true, the loaded script will be executed under an anonymous module, protecting the calling program's global namespace.

# foo.rb
def foo
end

foo
load "foo.rb", true

This "works", but it does not do this, which is what I think would be expected:

Module.new do
  def foo
  end

  foo
end
# undefined local variable or method `foo' for #<Module:0x00007f0823a7efb8> (NameError)

I think what load actually does is powerful and I've stated so elsewhere, but it is not simply "executing it under an anonymous namespace".

Updated by matz (Yukihiro Matsumoto) over 1 year ago

load "foo.rb" should work (similar enough) with or without wrapping module. In that sense, replacing self is unacceptable.
Probably you want to get the "target class/module" which is the target of constant/method definitions. I have no idea of notation right now.

Matz.

Actions #19

Updated by matz (Yukihiro Matsumoto) over 1 year ago

  • Status changed from Open to Rejected
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0