summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Williams <samuel.williams@oriontransfer.co.nz>2023-06-21 16:49:51 +0900
committerGitHub <noreply@github.com>2023-06-21 16:49:51 +0900
commita87bce86bb2a7581943355b41abaf41a6ad18218 (patch)
tree34686e94b2f5fb9f10d6e17a06385d98e97b0014
parente25403d0d97b737901d137c54635df0413155ad4 (diff)
Allow setting the name of a class or module. (#7483)
Introduce `Module#set_temporary_name` for setting identifiers for otherwise anonymous modules/classes.
Notes
Notes: Merged-By: ioquatix <samuel@codeotaku.com>
-rw-r--r--NEWS.md5
-rw-r--r--internal/variable.h16
-rw-r--r--object.c1
-rw-r--r--spec/ruby/core/module/name_spec.rb49
-rw-r--r--variable.c73
5 files changed, 144 insertions, 0 deletions
diff --git a/NEWS.md b/NEWS.md
index 570f16a705..b8aabd0f31 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -45,6 +45,10 @@ Note: We're only listing outstanding class updates.
The class use equality semantic to lookup keys like a regular hash,
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
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 #19538]: https://bugs.ruby-lang.org/issues/19538
[Feature #19591]: https://bugs.ruby-lang.org/issues/19591
+[Feature #19521]: https://bugs.ruby-lang.org/issues/19521
diff --git a/internal/variable.h b/internal/variable.h
index 4436cd789c..4fb7b23cf5 100644
--- a/internal/variable.h
+++ b/internal/variable.h
@@ -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_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;
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);
diff --git a/object.c b/object.c
index 94e85ed316..823b8df2cf 100644
--- a/object.c
+++ b/object.c
@@ -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, "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, "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, "attr", rb_mod_attr, -1);
diff --git a/spec/ruby/core/module/name_spec.rb b/spec/ruby/core/module/name_spec.rb
index b78bbfcc80..bbc1841239 100644
--- a/spec/ruby/core/module/name_spec.rb
+++ b/spec/ruby/core/module/name_spec.rb
@@ -6,6 +6,55 @@ describe "Module#name" do
Module.new.name.should be_nil
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
it "is nil when assigned to a constant in an anonymous module" do
m = Module.new
diff --git a/variable.c b/variable.c
index ce6feda885..1e6367e3c5 100644
--- a/variable.c
+++ b/variable.c
@@ -134,6 +134,79 @@ rb_mod_name(VALUE mod)
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
make_temporary_path(VALUE obj, VALUE klass)
{