From 09ee4b9757a0f83844231a4805755e18b0d01a30 Mon Sep 17 00:00:00 2001 From: gulugulubing <413153391@qq.com> Date: Mon, 27 Jul 2026 10:24:03 -0600 Subject: [PATCH 1/3] fix the indexes problem --- compiler/src/dmd/ctfeexpr.d | 46 ++++++++++++++++++++++++++++------- compiler/src/dmd/dinterpret.d | 21 +++++++++++++--- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/compiler/src/dmd/ctfeexpr.d b/compiler/src/dmd/ctfeexpr.d index cdd7022a2c41..ebe0872b36bc 100644 --- a/compiler/src/dmd/ctfeexpr.d +++ b/compiler/src/dmd/ctfeexpr.d @@ -193,7 +193,10 @@ bool needToCopyLiteral(const Expression expr) nothrow } } -private Expressions* copyLiteralArray(Expressions* oldelems, Expression basis = null) +// oldelems can have null elements when the ArrayLiteralExp uses 'basis'. +// This preserves the sparse encoding; callers that need dense arrays +// should use copyLiteralArrayExpand. +private Expressions* copyLiteralArray(Expressions* oldelems) { if (!oldelems) return oldelems; @@ -201,7 +204,22 @@ private Expressions* copyLiteralArray(Expressions* oldelems, Expression basis = auto newelems = new Expressions(oldelems.length); foreach (i, el; *oldelems) { - (*newelems)[i] = copyLiteral(el ? el : basis).copy(); + (*newelems)[i] = el ? copyLiteral(el).copy() : null; + } + return newelems; +} + +// Expand null elements using the given basis, for callers that need +// a dense array (e.g. concatenation, AA operations). +private Expressions* copyLiteralArrayExpand(Expressions* oldelems, Expression basis) +{ + auto newelems = copyLiteralArray(oldelems); + if (basis && newelems) + { + foreach (ref e; *newelems) + { + if (!e) e = copyLiteral(basis).copy(); + } } return newelems; } @@ -226,9 +244,10 @@ UnionExp copyLiteral(Expression e) } if (auto ale = e.isArrayLiteralExp()) { - auto elements = copyLiteralArray(ale.elements, ale.basis); + auto elements = copyLiteralArray(ale.elements); + auto basis = ale.basis ? copyLiteral(ale.basis).copy() : null; - emplaceExp!(ArrayLiteralExp)(&ue, e.loc, e.type, elements); + emplaceExp!(ArrayLiteralExp)(&ue, e.loc, e.type, basis, elements); ArrayLiteralExp r = ue.exp().isArrayLiteralExp(); r.ownedByCtfe = OwnedBy.ctfe; @@ -1094,7 +1113,9 @@ private int ctfeCmpArrays(Loc loc, Expression e1, Expression e2, uinteger_t len) foreach (size_t i; 0 .. cast(size_t)len) { Expression ee1 = (*ae1.elements)[cast(size_t)(lo1 + i)]; + if (!ee1) ee1 = ae1.basis; Expression ee2 = (*ae2.elements)[cast(size_t)(lo2 + i)]; + if (!ee2) ee2 = ae2.basis; if (needCmp) { const sinteger_t c = ee1.toInteger() - ee2.toInteger(); @@ -1386,7 +1407,8 @@ UnionExp ctfeCat(Loc loc, Type type, Expression e1, Expression e2) foreach (size_t i; 0 .. es2.elements.length) { Expression es2e = (*es2.elements)[i]; - if (es2e.op != EXP.int64) + if (!es2e) es2e = es2.basis; + if (!es2e || es2e.op != EXP.int64) { emplaceExp!(CTFEExp)(&ue, EXP.cantExpression); return ue; @@ -1416,7 +1438,8 @@ UnionExp ctfeCat(Loc loc, Type type, Expression e1, Expression e2) foreach (size_t i; 0 .. es2.elements.length) { Expression es2e = (*es2.elements)[i]; - if (es2e.op != EXP.int64) + if (!es2e) es2e = es2.basis; + if (!es2e || es2e.op != EXP.int64) { emplaceExp!(CTFEExp)(&ue, EXP.cantExpression); return ue; @@ -1438,9 +1461,9 @@ UnionExp ctfeCat(Loc loc, Type type, Expression e1, Expression e2) // [ e1 ] ~ [ e2 ] ---> [ e1, e2 ] ArrayLiteralExp es1 = e1.isArrayLiteralExp(); ArrayLiteralExp es2 = e2.isArrayLiteralExp(); - emplaceExp!(ArrayLiteralExp)(&ue, es1.loc, type, copyLiteralArray(es1.elements)); + emplaceExp!(ArrayLiteralExp)(&ue, es1.loc, type, copyLiteralArrayExpand(es1.elements, es1.basis)); es1 = ue.exp().isArrayLiteralExp(); - es1.elements.insert(es1.elements.length, copyLiteralArray(es2.elements)); + es1.elements.insert(es1.elements.length, copyLiteralArrayExpand(es2.elements, es2.basis)); return ue; } if (e1.op == EXP.arrayLiteral && e2.op == EXP.null_ && t1.nextOf().equals(t2.nextOf())) @@ -1508,6 +1531,7 @@ Expression ctfeIndex(UnionExp* pue, Loc loc, Type type, Expression e1, uinteger_ return CTFEExp.cantexp; } Expression e = (*ale.elements)[cast(size_t)indx]; + if (!e) e = ale.basis; return paintTypeOntoLiteral(pue, type, e); } @@ -1744,7 +1768,11 @@ Expression changeArrayLiteralLength(UnionExp* pue, Loc loc, TypeArray arrayType, assert(oldval.op == EXP.arrayLiteral); ArrayLiteralExp ae = oldval.isArrayLiteralExp(); foreach (size_t i; 0 .. copylen) - (*elements)[i] = (*ae.elements)[indxlo + i]; + { + Expression e = (*ae.elements)[indxlo + i]; + if (!e) e = ae.basis; + (*elements)[i] = e; + } } if (elemType.ty == Tstruct || elemType.ty == Tsarray) { diff --git a/compiler/src/dmd/dinterpret.d b/compiler/src/dmd/dinterpret.d index f044fdc25028..2eb8fb78467b 100644 --- a/compiler/src/dmd/dinterpret.d +++ b/compiler/src/dmd/dinterpret.d @@ -734,6 +734,7 @@ private Expression interpretFunction(UnionExp* pue, FuncDeclaration fd, InterSta assert(ne); auto ale = thisarg.isAddrExp().e1.isArrayLiteralExp(); e = (*ale.elements)[cast(size_t)ne.getInteger()]; + if (!e) e = ale.basis; if (auto ae = e.isAddrExp()) { e = ae.e1; @@ -1771,7 +1772,9 @@ public: assert(result.op == EXP.address); result = result.isAddrExp().e1; assert(result.op == EXP.arrayLiteral); - result = (*result.isArrayLiteralExp().elements)[0]; + auto rale = result.isArrayLiteralExp(); + result = (*rale.elements)[0]; + if (!result) result = rale.basis; if (e.type.ty == Tstruct) { result = result.isAddrExp().e1; @@ -5541,6 +5544,7 @@ public: // https://issues.dlang.org/show_bug.cgi?id=14686 foreach (elem; *ale.elements) { + if (!elem) continue; Expression ex = evaluatePostblit(istate, elem); if (exceptionOrCant(ex)) return; @@ -5709,7 +5713,9 @@ public: const indx = cast(size_t)ie.e2.toInteger(); if (indx < ale.elements.length) { - if (Expression xx = (*ale.elements)[indx]) + Expression xx = (*ale.elements)[indx]; + if (!xx) xx = ale.basis; + if (xx) { if (auto iex = xx.isIndexExp()) origType = iex.e1.type.nextOf(); @@ -5998,6 +6004,7 @@ public: if (ae.elements.length == 1) { result = (*ae.elements)[0]; + if (!result) result = ae.basis; return; } } @@ -7161,7 +7168,11 @@ StringExp arrayLiteralToString(ArrayLiteralExp ale) { T[] result = new T[len]; foreach (i; 0 .. len) - result[i] = cast(T) (*ale.elements)[i].isIntegerExp().getInteger(); + { + auto el = (*ale.elements)[i]; + if (!el) el = ale.basis; + result[i] = cast(T) el.isIntegerExp().getInteger(); + } return new StringExp(ale.loc, result[], len, cast(ubyte) size); } @@ -7499,6 +7510,7 @@ private Expression evaluatePostblit(InterState* istate, Expression e) { foreach (elem; *ale.elements) { + if (!elem) continue; if (auto ex = evaluatePostblit(istate, elem)) return ex; } @@ -7531,7 +7543,10 @@ private Expression evaluateDtor(InterState* istate, Expression e) if (auto ale = e.isArrayLiteralExp()) { foreach_reverse (elem; *ale.elements) + { + if (!elem) continue; e = evaluateDtor(istate, elem); + } } else if (e.op == EXP.structLiteral) { From ba55db79465b3647f74ea11f443253e0a8988f68 Mon Sep 17 00:00:00 2001 From: gulugulubing <413153391@qq.com> Date: Mon, 27 Jul 2026 11:21:59 -0600 Subject: [PATCH 2/3] add tests --- compiler/test/compilable/test_issue23367.d | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 compiler/test/compilable/test_issue23367.d diff --git a/compiler/test/compilable/test_issue23367.d b/compiler/test/compilable/test_issue23367.d new file mode 100644 index 000000000000..452d5764522e --- /dev/null +++ b/compiler/test/compilable/test_issue23367.d @@ -0,0 +1,50 @@ +// https://issues.dlang.org/show_bug.cgi?id=23367 +// Tests for sparse array literal optimization: copyLiteral now preserves +// CONSTANT encoding (null elements + basis) instead of expanding to dense. +// Most sparse-array code paths are already covered by existing CTFE tests; +// this test covers the novel paths specific to the implementation. + +// ============================================================ +// Dynamic array resize filling with array-typed default values. +// The resize copies old elements (which may be null in sparse arrays) +// and fills new slots with the default element. +// ============================================================ +alias f = { + int[1][] pieces = []; + pieces.length = 2; + return pieces; +}; +static assert(f() == [[0], [0]]); + +alias g = { + int[1][] pieces = []; + pieces.length = 3; + pieces[1] = [42]; + return pieces; +}; +static assert(g() == [[0], [42], [0]]); + +// Larger resize: grow from empty to many elements +static int testLargeGrow() +{ + int[2][] pieces = []; + pieces.length = 1000; + pieces[0] = [1, 2]; + pieces[999] = [3, 4]; + return pieces[0][0] + pieces[0][1] + + pieces[999][0] + pieces[999][1] + + pieces[500][0]; +} +static assert(testLargeGrow() == 1 + 2 + 3 + 4 + 0); + +// ============================================================ +// Concatenation of two sparse arrays that each have a basis. +// ============================================================ +static int testCatSparse() +{ + int[500] a = 1; + int[500] b = 2; + auto c = a ~ b; + return c[0] + c[250] + c[499] + c[500] + c[750] + c[999]; +} +static assert(testCatSparse() == 1 + 1 + 1 + 2 + 2 + 2); From 67346f81e91d928a885adb0551db6eeb8ca64de8 Mon Sep 17 00:00:00 2001 From: gulugulubing <413153391@qq.com> Date: Mon, 27 Jul 2026 17:55:40 -0600 Subject: [PATCH 3/3] correct the issue url in test file --- compiler/test/compilable/test_issue23367.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test/compilable/test_issue23367.d b/compiler/test/compilable/test_issue23367.d index 452d5764522e..bf07fa246a52 100644 --- a/compiler/test/compilable/test_issue23367.d +++ b/compiler/test/compilable/test_issue23367.d @@ -1,4 +1,4 @@ -// https://issues.dlang.org/show_bug.cgi?id=23367 +// https://github.com/dlang/dmd/issues/23367 // Tests for sparse array literal optimization: copyLiteral now preserves // CONSTANT encoding (null elements + basis) instead of expanding to dense. // Most sparse-array code paths are already covered by existing CTFE tests;