Allow setting the name of a class or module. (#7483)

Introduce `Module#set_temporary_name` for setting identifiers for otherwise
anonymous modules/classes.
This commit is contained in:
Samuel Williams 2023-06-21 16:49:51 +09:00 коммит произвёл GitHub
Родитель e25403d0d9
Коммит a87bce86bb
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 144 добавлений и 0 удалений

Просмотреть файл

@ -45,6 +45,10 @@ Note: We're only listing outstanding class updates.
The class use equality semantic to lookup keys like a regular hash, The class use equality semantic to lookup keys like a regular hash,
but it doesn't hold strong references on the keys. [[Feature #18498]] but it doesn't hold strong references on the keys. [[Feature #18498]]
* Module
* `Module#set_temporary_name` added for setting a temporary name for a module. [[Feature #19521]]
## Stdlib updates ## Stdlib updates
The following default gems are updated. The following default gems are updated.
@ -134,3 +138,4 @@ changelog for details of the default gems or bundled gems.
[Feature #19347]: https://bugs.ruby-lang.org/issues/19347 [Feature #19347]: https://bugs.ruby-lang.org/issues/19347
[Feature #19538]: https://bugs.ruby-lang.org/issues/19538 [Feature #19538]: https://bugs.ruby-lang.org/issues/19538
[Feature #19591]: https://bugs.ruby-lang.org/issues/19591 [Feature #19591]: https://bugs.ruby-lang.org/issues/19591
[Feature #19521]: https://bugs.ruby-lang.org/issues/19521

Просмотреть файл

@ -36,6 +36,22 @@ static inline bool ROBJ_TRANSIENT_P(VALUE obj);
static inline void ROBJ_TRANSIENT_SET(VALUE obj); static inline void ROBJ_TRANSIENT_SET(VALUE obj);
static inline void ROBJ_TRANSIENT_UNSET(VALUE obj); static inline void ROBJ_TRANSIENT_UNSET(VALUE obj);
/**
* Sets the name of a module.
*
* Non-permanently named classes can have a temporary name assigned (or
* cleared). In that case the name will be used for `#inspect` and `#to_s`, and
* nested classes/modules will be named with the temporary name as a prefix.
*
* After the module is assigned to a constant, the temporary name will be
* discarded, and the name will be computed based on the nesting.
*
* @param[in] mod An instance of ::rb_cModule.
* @param[in] name An instance of ::rb_cString.
* @retval mod
*/
VALUE rb_mod_set_temporary_name(VALUE, VALUE);
struct gen_ivtbl; struct gen_ivtbl;
int rb_gen_ivtbl_get(VALUE obj, ID id, struct gen_ivtbl **ivtbl); int rb_gen_ivtbl_get(VALUE obj, ID id, struct gen_ivtbl **ivtbl);
int rb_obj_evacuate_ivs_to_hash_table(ID key, VALUE val, st_data_t arg); int rb_obj_evacuate_ivs_to_hash_table(ID key, VALUE val, st_data_t arg);

Просмотреть файл

@ -4508,6 +4508,7 @@ InitVM_Object(void)
rb_define_method(rb_cModule, "included_modules", rb_mod_included_modules, 0); /* in class.c */ rb_define_method(rb_cModule, "included_modules", rb_mod_included_modules, 0); /* in class.c */
rb_define_method(rb_cModule, "include?", rb_mod_include_p, 1); /* in class.c */ rb_define_method(rb_cModule, "include?", rb_mod_include_p, 1); /* in class.c */
rb_define_method(rb_cModule, "name", rb_mod_name, 0); /* in variable.c */ rb_define_method(rb_cModule, "name", rb_mod_name, 0); /* in variable.c */
rb_define_method(rb_cModule, "set_temporary_name", rb_mod_set_temporary_name, 1); /* in variable.c */
rb_define_method(rb_cModule, "ancestors", rb_mod_ancestors, 0); /* in class.c */ rb_define_method(rb_cModule, "ancestors", rb_mod_ancestors, 0); /* in class.c */
rb_define_method(rb_cModule, "attr", rb_mod_attr, -1); rb_define_method(rb_cModule, "attr", rb_mod_attr, -1);

Просмотреть файл

@ -6,6 +6,55 @@ describe "Module#name" do
Module.new.name.should be_nil Module.new.name.should be_nil
end end
ruby_version_is "3.3" do
it "can assign a temporary name" do
m = Module.new
m.name.should be_nil
m.set_temporary_name("fake_name")
m.name.should == "fake_name"
m.set_temporary_name(nil)
m.name.should be_nil
end
it "can't assign empty string as name" do
m = Module.new
-> { m.set_temporary_name("") }.should raise_error(ArgumentError, "empty class/module name")
end
it "can't assign a constant name as a temporary name" do
m = Module.new
-> { m.set_temporary_name("Object") }.should raise_error(ArgumentError, "name must not be valid constant name")
end
it "can't assign name to permanent module" do
-> { Object.set_temporary_name("fake_name") }.should raise_error(RuntimeError, "can't change permanent name")
end
it "can assign a temporary name to a nested module" do
m = Module.new
module m::N; end
m::N.name.should =~ /\A#<Module:0x\h+>::N\z/
m::N.set_temporary_name("fake_name")
m::N.name.should == "fake_name"
m::N.set_temporary_name(nil)
m::N.name.should be_nil
end
it "can update the name when assigned to a constant" do
m = Module.new
m::N = Module.new
m::N.name.should =~ /\A#<Module:0x\h+>::N\z/
m::N.set_temporary_name(nil)
m::M = m::N
m::M.name.should =~ /\A#<Module:0x\h+>::M\z/m
end
end
ruby_version_is ""..."3.0" do ruby_version_is ""..."3.0" do
it "is nil when assigned to a constant in an anonymous module" do it "is nil when assigned to a constant in an anonymous module" do
m = Module.new m = Module.new

Просмотреть файл

@ -134,6 +134,79 @@ rb_mod_name(VALUE mod)
return classname(mod, &permanent); return classname(mod, &permanent);
} }
/*
* call-seq:
* mod.set_temporary_name(string) -> self
* mod.set_temporary_name(nil) -> self
*
* Sets the temporary name of the module +mod+. This name is used as a prefix
* for the names of constants declared in +mod+. If the module is assigned a
* permanent name, the temporary name is discarded.
*
* After a permanent name is assigned, a temporary name can no longer be set,
* and this method raises a RuntimeError.
*
* If the name given is not a string or is a zero length string, this method
* raises an ArgumentError.
*
* The temporary name must not be a valid constant name, to avoid confusion
* with actual constants. If you attempt to set a temporary name that is a
* a valid constant name, this method raises an ArgumentError.
*
* If the given name is +nil+, the module becomes anonymous.
*
* Example:
*
* m = Module.new # => #<Module:0x0000000102c68f38>
* m.name #=> nil
*
* m.set_temporary_name("fake_name") # => fake_name
* m.name #=> "fake_name"
*
* m.set_temporary_name(nil) # => #<Module:0x0000000102c68f38>
* m.name #=> nil
*
* n = Module.new
* n.set_temporary_name("fake_name")
*
* n::M = m
* n::M.name #=> "fake_name::M"
* N = n
*
* N.name #=> "nested_fake_name"
* N::M.name #=> "N::M"
*/
VALUE
rb_mod_set_temporary_name(VALUE mod, VALUE name)
{
// We don't allow setting the name if the classpath is already permanent:
if (RCLASS_EXT(mod)->permanent_classpath) {
rb_raise(rb_eRuntimeError, "can't change permanent name");
}
if (NIL_P(name)) {
// Set the temporary classpath to NULL (anonymous):
RCLASS_SET_CLASSPATH(mod, 0, FALSE);
} else {
// Ensure the name is a string:
StringValue(name);
if (RSTRING_LEN(name) == 0) {
rb_raise(rb_eArgError, "empty class/module name");
}
if (rb_is_const_name(name)) {
rb_raise(rb_eArgError, "name must not be valid constant name");
}
// Set the temporary classpath to the given name:
RCLASS_SET_CLASSPATH(mod, name, FALSE);
}
return mod;
}
static VALUE static VALUE
make_temporary_path(VALUE obj, VALUE klass) make_temporary_path(VALUE obj, VALUE klass)
{ {