Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions compiler/test/fail_compilation/fail22175.d
Original file line number Diff line number Diff line change
@@ -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; }))),
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static assert here is written as a positive assertion — it passes only when the expression is true, i.e., when the offending code does compile. Because the goal of this test is to verify that this callback shape is rejected, the condition should be negated: !is(typeof(...)). As written, the test will fail (triggering the error message) precisely when the fix is working correctly, which is the opposite of the intended behaviour. The expected TEST_OUTPUT in the comment on line 6 appears to rely on this assertion failing, which confirms the inversion. A correct regression guard would use static assert(!is(typeof(...)), ...) so the test only fails when the fix is accidentally reverted.

Suggested change
is(typeof(aa.update(1, () => 10, delegate void(int x) { x += 1; }))),
!is(typeof(aa.update(1, () => 10, delegate void(int x) { x += 1; }))),

Copilot uses AI. Check for mistakes.
"void-returning update callback must take ref parameter"
);
}
22 changes: 18 additions & 4 deletions druntime/src/object.d
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down
Loading