summaryrefslogtreecommitdiff
path: root/struct.c
diff options
context:
space:
mode:
authorUfuk Kayserilioglu <ufuk@paralaus.com>2022-12-22 02:27:38 +0200
committerGitHub <noreply@github.com>2022-12-21 16:27:38 -0800
commit99cee85775bc5656b942bf4e1b0568a8f40addcd (patch)
tree2411e0b27cfc55a559a50ac28eb1e0acec202e6f /struct.c
parent398aaed2f0e56a81c5c15cc1126b6bbba79ffec0 (diff)
downloadruby-99cee85775bc5656b942bf4e1b0568a8f40addcd.tar.gz
Add copy with changes functionality for Data objects (#6766)
Implements [Feature #19000] This commit adds copy with changes functionality for `Data` objects using a new method `Data#with`. Since Data objects are immutable, the only way to change them is by creating a copy. This PR adds a `with` method for `Data` class instances that optionally takes keyword arguments. If the `with` method is called with no arguments, the behaviour is the same as the `Kernel#dup` method, i.e. a new shallow copy is created with no field values changed. However, if keyword arguments are supplied to the `with` method, then the copy is created with the specified field values changed. For example: ```ruby Point = Data.define(:x, :y) point = Point.new(x: 1, y: 2) point.with(x: 3) # => #<data Point x: 3, y: 2> ``` Passing positional arguments to `with` or passing keyword arguments to it that do not correspond to any of the members of the Data class will raise an `ArgumentError`. Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
Diffstat (limited to 'struct.c')
-rw-r--r--struct.c59
1 files changed, 59 insertions, 0 deletions
diff --git a/struct.c b/struct.c
index 3733669eab..825987917d 100644
--- a/struct.c
+++ b/struct.c
@@ -1834,6 +1834,63 @@ rb_data_init_copy(VALUE copy, VALUE s)
/*
* call-seq:
+ * with(**kwargs) -> instance
+ *
+ * Returns a shallow copy of +self+ --- the instance variables of
+ * +self+ are copied, but not the objects they reference.
+ *
+ * If the method is supplied any keyword arguments, the copy will
+ * be created with the respective field values updated to use the
+ * supplied keyword argument values. Note that it is an error to
+ * supply a keyword that the Data class does not have as a member.
+ *
+ * Point = Data.define(:x, :y)
+ *
+ * origin = Point.new(x: 0, y: 0)
+ *
+ * up = origin.with(x: 1)
+ * right = origin.with(y: 1)
+ * up_and_right = up.with(y: 1)
+ *
+ * p origin # #<data Point x=0, y=0>
+ * p up # #<data Point x=1, y=0>
+ * p right # #<data Point x=0, y=1>
+ * p up_and_right # #<data Point x=1, y=1>
+ *
+ * out = origin.with(z: 1) # ArgumentError: unknown keyword: :z
+ * some_point = origin.with(1, 2) # ArgumentError: expected keyword arguments, got positional arguments
+ *
+ */
+
+static VALUE
+rb_data_with(int argc, const VALUE *argv, VALUE self)
+{
+ VALUE kwargs;
+ rb_scan_args(argc, argv, "0:", &kwargs);
+ if (NIL_P(kwargs)) {
+ return self;
+ }
+
+ VALUE copy = rb_obj_alloc(rb_obj_class(self));
+ rb_struct_init_copy(copy, self);
+
+ struct struct_hash_set_arg arg;
+ arg.self = copy;
+ arg.unknown_keywords = Qnil;
+ rb_hash_foreach(kwargs, struct_hash_set_i, (VALUE)&arg);
+ // Freeze early before potentially raising, so that we don't leave an
+ // unfrozen copy on the heap, which could get exposed via ObjectSpace.
+ RB_OBJ_FREEZE_RAW(copy);
+
+ if (arg.unknown_keywords != Qnil) {
+ rb_exc_raise(rb_keyword_error_new("unknown", arg.unknown_keywords));
+ }
+
+ return copy;
+}
+
+/*
+ * call-seq:
* inspect -> string
* to_s -> string
*
@@ -2205,6 +2262,8 @@ InitVM_Struct(void)
rb_define_method(rb_cData, "deconstruct", rb_data_deconstruct, 0);
rb_define_method(rb_cData, "deconstruct_keys", rb_data_deconstruct_keys, 1);
+
+ rb_define_method(rb_cData, "with", rb_data_with, -1);
}
#undef rb_intern