Issue #19520 has been updated by ioquatix (Samuel Williams).
I had a call with @ioquatix (Samuel Williams), trying
to explain the importance of the name that Ruby shows us, for example:
undefined method 'zzz' for #<Foo::Bar:0x00007efc38711fc0> (NoMethodError)
people will of course expect that Foo::Bar refers to the class of the object.
As demonstrated, if users want to do this, it's already possible.
```ruby
Foo = Module.new
#=> Foo
Foo.class_eval("class Bar;end")
bar = Foo::Bar.new
#=> #<Foo::Bar:0x000055c0ae3c1e68>
Object.send(:remove_const, Foo.name)
#=> Foo
bar.zzz
#=> undefined method `zzz' for #<Foo::Bar:0x000055c0ae3c1e68>
(NoMethodError)
Foo::Bar
#=> uninitialized constant Foo (NameError)
```
(Actually C code can define lowercase constants, so
just not uppercase first letter is not enough, e.g. IO::generic_writable seen from
StringIO.ancestors).
It's not possible to access such constants using the normal constant lookup:
```ruby
StringIO.ancestors
#=> [StringIO, IO::generic_writable, IO::generic_readable, Enumerable, Data, Object,
PP::ObjectMixin, Kernel, BasicObject]
IO::generic_writable
#=> undefined method `generic_writable' for IO:Class (NoMethodError)
```
But I also don't think that matters much for this proposal anyway.
"One you assign a permanent name, it replaces any
fake/temporary name." -> That wasn't clear to me and the description
doesn't seem to mention it. So that's some extra complexity both for the user and
for implementations.
This is how Ruby already works internally, this is not part of my PR. This is how
anonymous modules already work.
https://github.com/ruby/ruby/blob/868f03cce1a2d7a4df9b03b8338e3af4c69041d0/…
is the implementation that already exists. This is a way to cache the class names. It is
used to inform child modules that the class name won't change in the future.
```ruby
m = Module.new # internally, the name is not permanent.
M = m # now it becomes permanent and any child constants in m should update their names to
be permanent too.
```
This is part of the reason why one could consider `remove_const` to be buggy.
And the same for Zeitwerk, while I guess it's
possible to break the constant path<->module mapping there with reloading e.g. maybe
by storing an old > Class in a global variable and doing that only once per process
(e.g. only if the global is unset), it's just extremely uncommon.
I don't think it's uncommon to cache instances of a class in some global mapping.
Does Zeitwerk reload the entire namespace or just ones that changed? I don't know
enough about it. @fxn any thoughts on how this is handled? I'm assuming Zeitwerk
reloading can create orphaned constants (i.e. it's calling `remove_const`).
> One thing that could help is for this name to be visually different than a regular
constant path, so e.g. it cannot start with an uppercase letter > (as said in
https://bugs.ruby-lang.org/issues/19450#note-17), and probably start with some symbol to
make it even more obvious.
(Actually C code can define lowercase constants, so
just not uppercase first letter is not enough, e.g. IO::generic_writable seen from
StringIO.ancestors).
This is the current convention for anonymous modules, to a certain extent. If one
overrides `Class#name`, that's no longer true.
What I didn't have time to discuss on the call is
what's the use-case for this besides tests which want to label anonymous
modules/classes to make it easier to debug them.
Well, I think this is already explained multiple times, i.e. the examples I gave +
Ruby's own CI.
Another thought: maybe a much simpler way to solve
most of these use-cases is adding Module#source_location, which is the [file, line] at
which the Module was created.
That could also work for anonymous modules, they could capture at which file, line
Module.new was called.
Do I think this is potentially a good idea? Yes.
Does that work for all the example use cases I gave? No.
I think what might make more sense is:
- Introducing `anonymous?` as a predicate for whether a given class/module is itself
rooted in the global namespace or not.
- (Consider) changing `remove_const` to correctly convert class/module back to anonymous.
----------------------------------------
Feature #19520: Support for `Module.new(name)` and `Class.new(superclass, name)`.
https://bugs.ruby-lang.org/issues/19520#change-102399
* Author: ioquatix (Samuel Williams)
* Status: Open
* Priority: Normal
----------------------------------------
See <https://bugs.ruby-lang.org/issues/19450> for previous discussion and
motivation.
[This
proposal](https://github.com/ruby/ruby/pull/7376) introduces the `name` parameter to
`Class.new` and `Module.new`:
```ruby
Class.new(superclass, name)
Module.new(name)
```
As a slight change, we could use keyword arguments instead.
## Example usage
The current Ruby test suite has code which shows the usefulness of this new method:
```ruby
def labeled_module(name, &block)
Module.new do
singleton_class.class_eval {
define_method(:to_s) {name}
alias inspect to_s
alias name to_s
}
class_eval(&block) if block
end
end
module_function :labeled_module
def labeled_class(name, superclass = Object, &block)
Class.new(superclass) do
singleton_class.class_eval {
define_method(:to_s) {name}
alias inspect to_s
alias name to_s
}
class_eval(&block) if block
end
end
module_function :labeled_class
```
The updated code would look like this:
```ruby
def labeled_module(name, &block)
Module.new(name, &block)
end
def labeled_class(name, superclass = Object, &block)
Class.new(superclass, name, &block)
end
module_function :labeled_class
```
--
https://bugs.ruby-lang.org/