From 79ae21611861656b58dbb0b75c82408c4db47a57 Mon Sep 17 00:00:00 2001 From: Divyansh Sharma Date: Tue, 3 Mar 2026 22:24:21 +0530 Subject: [PATCH] Fix issue 22175 - require ref for void AA update callbacks --- compiler/test/fail_compilation/fail22175.d | 18 ++++++++++++++++++ druntime/src/object.d | 22 ++++++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 compiler/test/fail_compilation/fail22175.d diff --git a/compiler/test/fail_compilation/fail22175.d b/compiler/test/fail_compilation/fail22175.d new file mode 100644 index 000000000000..5c393edaddf7 --- /dev/null +++ b/compiler/test/fail_compilation/fail22175.d @@ -0,0 +1,18 @@ +// https://github.com/dlang/dmd/issues/22175 +// void-returning update callback for AA must take value by ref +/* +TEST_OUTPUT: +--- +fail_compilation/fail22175.d(14): Error: static assert: "void-returning update callback must take ref parameter" +--- +*/ + +void main() +{ + int[int] aa; + // Regression guard: this callback shape must never compile. + static assert( + is(typeof(aa.update(1, () => 10, delegate void(int x) { x += 1; }))), + "void-returning update callback must take ref parameter" + ); +} diff --git a/druntime/src/object.d b/druntime/src/object.d index bb0c3d3c2738..47bd85b8c67a 100644 --- a/druntime/src/object.d +++ b/druntime/src/object.d @@ -3544,10 +3544,12 @@ private enum bool isSafeCopyable(T) = is(typeof(() @safe { union U { T x; } T *x * create = The callable to create a value for `key`. * Must return V. * update = The callable to call if `key` exists. - * Takes a V argument, returns a V or void. + * Takes a ref V or V argument. Returns a V or void. + * If it returns void, the parameter must be ref. */ void update(K, V, C, U)(ref V[K] aa, K key, scope C create, scope U update) -if (is(typeof(create()) : V) && (is(typeof(update(aa[K.init])) : V) || is(typeof(update(aa[K.init])) == void))) +if (is(typeof(create()) : V) && (is(typeof(update(aa[K.init])) : V) + || (is(typeof(update(aa[K.init])) == void) && !is(typeof(update(V.init)) == void)))) { bool found; auto p = _aaGetX(aa, key, found, create()); @@ -3568,7 +3570,7 @@ if (is(typeof(create()) : V) && (is(typeof(update(aa[K.init])) : V) || is(typeof // create aa.update("key", () => 1, - (int) {} // not executed + (ref int) {} // not executed ); assert(aa["key"] == 1); @@ -3590,7 +3592,7 @@ if (is(typeof(create()) : V) && (is(typeof(update(aa[K.init])) : V) || is(typeof // 'update' without changing value aa.update("key", () => 0, // not executed - (int) { + (ref int) { // do something else }); assert(aa["key"] == 4); @@ -3626,6 +3628,18 @@ if (is(typeof(create()) : V) && (is(typeof(update(aa[K.init])) : V) || is(typeof static assert(!is(typeof(() @safe { aais.update(S(1234), { return 1234; }, (ref int x) { x++; return x; }); }))); } +// void-returning update callback must take parameter by ref (issue 22175) +@safe unittest +{ + int[string] aa; + // void-returning by-value must be rejected + static assert(!is(typeof(() { aa.update("key", () => 0, (int v) { }); }))); + // void-returning by-ref must be accepted + static assert(is(typeof(() { aa.update("key", () => 0, (ref int v) { }); }))); + // non-void returning by-value is fine (value used to update) + static assert(is(typeof(() { aa.update("key", () => 0, (int v) => v * 2); }))); +} + @safe unittest { struct S0