diff --git a/VERSION b/VERSION index bb14ba5b599c..767e5d2d3b30 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.112.0 +v2.112.1 diff --git a/compiler/src/dmd/common/charactertables.d b/compiler/src/dmd/common/charactertables.d index baa126203288..620c04cfffca 100644 --- a/compiler/src/dmd/common/charactertables.d +++ b/compiler/src/dmd/common/charactertables.d @@ -185,6 +185,13 @@ bool c_isalnum(const int c) ( c >= 'A' && c <= 'Z')); } +/// +bool isAlphaASCII(const dchar c) +{ + return (( c >= 'a' && c <= 'z') || + ( c >= 'A' && c <= 'Z')); +} + extern(D) private: // originally from dmd.root.utf diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 883408459059..027651a56c2a 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -6992,8 +6992,11 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor if (!global.params.useGC && sc.needsCodegen()) { - error(exp.loc, "expression `%s` allocates with the GC and cannot be used with switch `-%s`", exp.toErrMsg(), SwitchVariadic.ptr); - return setError(); + if (sc.func) + { + sc.func.skipCodegen = true; // same net result as calling checkGC + goto LskipNewArrayLowering; // not checked in sc.needsCodegen() !? + } } if (!sc.needsCodegen()) @@ -8516,7 +8519,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor { exp.type = tf.next; auto casted_exp = exp.castTo(sc, t); - if (auto cex = casted_exp.isCastExp()) + if (auto cex = lastComma(casted_exp).isCastExp()) { lowerCastExp(cex, sc); } @@ -10933,7 +10936,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } } - if (auto cex = ex.isCastExp()) + if (auto cex = lastComma(ex).isCastExp()) { lowerCastExp(cex, sc); } @@ -15079,12 +15082,14 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor return; } - // When array comparison is not lowered to `__equals`, `memcmp` is used, but - // GC checks occur before the expression is lowered to `memcmp` in e2ir.d. - // Thus, we will consider the literal arrays as on-stack arrays to avoid issues - // during GC checks. + // Remaining array comparisons are trivially memcmp-able. + // Allocate array-literal operands on the stack, since they don't + // escape during the comparison; enabling @nogc for these. if (isArrayComparison) { + exp.e1 = exp.e1.optimize(WANTvalue); + exp.e2 = exp.e2.optimize(WANTvalue); + if (auto ale1 = exp.e1.isArrayLiteralExp()) { ale1.onstack = true; @@ -19745,12 +19750,12 @@ private Expression buildAAIndexRValueX(Type t, Expression eaa, Expression ekey, auto call = new CallExp(loc, func, arguments); e0 = Expression.combine(e0, call); - if (arrayBoundsCheck(sc.func)) + if (sc.func && arrayBoundsCheck(sc.func)) { // __aaget = _d_aaGetRvalueX(aa, key), __aaget ? __aaget : onRangeError(__FILE__, __LINE__) auto ei = new ExpInitializer(loc, e0); - auto vartmp = Identifier.generateId("__aaget"); - auto vardecl = new VarDeclaration(loc, null, vartmp, ei, STC.exptemp); + auto id = Identifier.generateId("__aaget"); + auto vardecl = new VarDeclaration(loc, null, id, ei, STC.exptemp); auto declexp = new DeclarationExp(loc, vardecl); //Expression idrange = new IdentifierExp(loc, Identifier.idPool("_d_arraybounds")); @@ -19760,9 +19765,9 @@ private Expression buildAAIndexRValueX(Type t, Expression eaa, Expression ekey, auto locargs = new Expressions(new FileInitExp(loc, EXP.file), new LineInitExp(loc)); auto ex = new CallExp(loc, idrange, locargs); - auto idvar1 = new IdentifierExp(loc, vartmp); - auto idvar2 = new IdentifierExp(loc, vartmp); - auto cond = new CondExp(loc, idvar1, idvar2, ex); + auto ve1 = new VarExp(loc, vardecl); + auto ve2 = new VarExp(loc, vardecl); + auto cond = new CondExp(loc, ve1, ve2, ex); auto comma = new CommaExp(loc, declexp, cond); return comma; } @@ -19795,6 +19800,26 @@ Expression revertIndexAssignToRvalues(IndexExp ie, Scope* sc) return lowerAAIndexRead(ie, sc); } +// Ditto, but traverses DotVarExp from `alias this` rewrites. +private Expression revertModifiableAAIndexReads(Expression e, Scope* sc) +{ + // Recurse through dot-accesses (alias this produces DotVarExp on an inner IndexExp) + if (auto dve = e.isDotVarExp()) + { + dve.e1 = revertModifiableAAIndexReads(dve.e1, sc); + return e; + } + if (auto ie = e.isIndexExp()) + { + // Recurse first to handle deeper nesting + ie.e1 = revertModifiableAAIndexReads(ie.e1, sc); + // Lower a modifiable AA IndexExp to an rvalue read + if (ie.modifiable && ie.e1.type.isTypeAArray()) + return lowerAAIndexRead(ie, sc); + } + return e; +} + // helper for rewriteAAIndexAssign private Expression implicitConvertToStruct(Expression ev, StructDeclaration sd, Scope* sc) { @@ -19845,6 +19870,7 @@ private Expression rewriteAAIndexAssign(BinExp exp, Scope* sc, ref Type[2] alias // find the AA of multi dimensional access for (auto ieaa = ie.e1.isIndexExp(); ieaa && ieaa.e1.type.isTypeAArray(); ieaa = ieaa.e1.isIndexExp()) eaa = ieaa.e1; + eaa = revertModifiableAAIndexReads(eaa, sc); eaa = extractSideEffect(sc, "__aatmp", e0, eaa); // collect all keys of multi dimensional access Expressions ekeys; @@ -19883,7 +19909,7 @@ private Expression rewriteAAIndexAssign(BinExp exp, Scope* sc, ref Type[2] alias auto tiargs = new Objects(taa.index, taa.next); func = new DotTemplateInstanceExp(loc, func, hook, tiargs); - auto arguments = new Expressions(eaa, ekeys[i-1], new IdentifierExp(loc, idfound)); + auto arguments = new Expressions(eaa, ekeys[i-1], new VarExp(loc, varfound)); eaa = new CallExp(loc, func, arguments); if (i > 1) { @@ -19940,7 +19966,7 @@ private Expression rewriteAAIndexAssign(BinExp exp, Scope* sc, ref Type[2] alias ex = new CastExp(ex.loc, ex, Type.tvoid); ey = new CastExp(ey.loc, ey, Type.tvoid); } - Expression condfound = new IdentifierExp(loc, idfound); + Expression condfound = new VarExp(loc, varfound); ex = new CondExp(loc, condfound, ex, ey); ex = Expression.combine(e0, ex); ex.isCommaExp().originalExp = exp; diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index 91261e8a5a52..8e8e2fe76f8e 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -7979,6 +7979,8 @@ extern bool c_isxdigit(const int32_t c); extern bool c_isalnum(const int32_t c); +extern bool isAlphaASCII(const char32_t c); + extern void error(Loc loc, const char* format, ...); extern void error(const char* filename, uint32_t linnum, uint32_t charnum, const char* format, ...); diff --git a/compiler/src/dmd/glue/toobj.d b/compiler/src/dmd/glue/toobj.d index d35129647150..4d8a9182b833 100644 --- a/compiler/src/dmd/glue/toobj.d +++ b/compiler/src/dmd/glue/toobj.d @@ -1348,17 +1348,13 @@ private void ClassInfoToDt(ref DtBuilder dtb, ClassDeclaration cd, Symbol* sinit Louter: for (ClassDeclaration pc = cd; pc; pc = pc.baseClass) { - if (pc.members) + foreach (vd; pc.fields) { - for (size_t i = 0; i < pc.members.length; i++) + //printf("vd = %s %s\n", vd.kind(), vd.toChars()); + if (vd.hasPointers()) { - Dsymbol sm = (*pc.members)[i]; - //printf("sm = %s %s\n", sm.kind(), sm.toChars()); - if (sm.hasPointers()) - { - flags &= ~ClassFlags.noPointers; // not no-how, not no-way - break Louter; - } + flags &= ~ClassFlags.noPointers; // not no-how, not no-way + break Louter; } } } diff --git a/compiler/src/dmd/initsem.d b/compiler/src/dmd/initsem.d index 21ad68fb6ced..644577f72afa 100644 --- a/compiler/src/dmd/initsem.d +++ b/compiler/src/dmd/initsem.d @@ -1468,6 +1468,22 @@ Expression initializerToExpression(Initializer init, Type itype = null, const bo } } + // enforce the element type only if the dimensions match, otherwise the value + // might be used as an initializer for the whole array at another dimension + Type isTypeArray(Type tn) + { + auto ty = tn ? tn.ty : Tnone; + return ty == Tarray || ty == Tsarray || ty == Taarray || ty == Tvector ? tn : null; + } + Type tnext = isTypeArray(itype); + auto initn = init; + while (tnext && initn) + { + tnext = isTypeArray(tnext.nextOf()); + initn = initn.value.length ? initn.value[0].isArrayInitializer() : null; + } + Type telem = itype && !tnext && !initn ? itype.nextOf() : null; + auto elements = new Expressions(edim); elements.zero(); size_t j = 0; @@ -1478,7 +1494,7 @@ Expression initializerToExpression(Initializer init, Type itype = null, const bo assert(j < edim); if (Initializer iz = init.value[i]) { - if (Expression ex = iz.initializerToExpression(null, isCfile)) + if (Expression ex = iz.initializerToExpression(telem, isCfile)) { (*elements)[j] = ex; ++j; diff --git a/compiler/src/dmd/lexer.d b/compiler/src/dmd/lexer.d index b5b82a91df61..c51fdf6123e9 100644 --- a/compiler/src/dmd/lexer.d +++ b/compiler/src/dmd/lexer.d @@ -62,7 +62,11 @@ struct CompileEnv */ class Lexer { - private __gshared OutBuffer stringbuffer; + private __gshared + { + OutBuffer stringbuffer; + OutBuffer stringbuffersecondary; // functions that use stringbuffer can call scan that needs this. + } BaseLoc* baseLoc; // Used to generate `scanloc`, which is just an index into this data structure Loc scanloc; // for error messages @@ -1431,7 +1435,7 @@ class Lexer p++; break; default: - if (isalpha(*p) || (p != idstart && isdigit(*p))) + if (isAlphaASCII(*p) || (p != idstart && isdigit(*p))) continue; error(loc, "unterminated named entity &%.*s;", cast(int)(p - idstart + 1), idstart); c = '?'; @@ -1766,7 +1770,7 @@ class Lexer uint blankrol = 0; uint startline = 0; p++; - stringbuffer.setsize(0); + stringbuffersecondary.setsize(0); while (1) { const s = p; @@ -1785,7 +1789,7 @@ class Lexer } if (hereid) { - stringbuffer.writeUTF8(c); + stringbuffersecondary.writeUTF8(c); continue; } break; @@ -1825,7 +1829,7 @@ class Lexer delimright = ']'; else if (c == '<') delimright = '>'; - else if (isalpha(c) || c == '_' || (c >= 0x80 && charLookup.isStart(c))) + else if (isAlphaASCII(c) || c == '_' || (c >= 0x80 && charLookup.isStart(c))) { // Start of identifier; must be a heredoc Token tok; @@ -1875,7 +1879,7 @@ class Lexer goto Ldone; // we're looking for a new identifier token - if (startline && (isalpha(c) || c == '_' || (c >= 0x80 && charLookup.isStart(c))) && hereid) + if (startline && (isAlphaASCII(c) || c == '_' || (c >= 0x80 && charLookup.isStart(c))) && hereid) { Token tok; auto psave = p; @@ -1890,7 +1894,7 @@ class Lexer } p = psave; } - stringbuffer.writeUTF8(c); + stringbuffersecondary.writeUTF8(c); startline = 0; } } @@ -1903,7 +1907,7 @@ class Lexer error("delimited string must end in `\"`"); else error(token.loc, "delimited string must end in `%c\"`", delimright); - result.setString(stringbuffer); + result.setString(stringbuffersecondary); stringPostfix(result); } @@ -2404,7 +2408,7 @@ class Lexer case '.': if (p[1] == '.') goto Ldone; // if ".." - if (isalpha(p[1]) || p[1] == '_' || p[1] & 0x80) + if (isAlphaASCII(p[1]) || p[1] == '_' || p[1] & 0x80) { if (Ccompile && (p[1] == 'f' || p[1] == 'F' || p[1] == 'l' || p[1] == 'L')) goto Lreal; // if `0.f` or `0.L` @@ -2477,7 +2481,7 @@ class Lexer case '.': if (p[1] == '.') goto Ldone; // if ".." - if (base <= 10 && n > 0 && (isalpha(p[1]) || p[1] == '_' || p[1] & 0x80)) + if (base <= 10 && n > 0 && (isAlphaASCII(p[1]) || p[1] == '_' || p[1] & 0x80)) { if (Ccompile && base == 10 && (p[1] == 'e' || p[1] == 'E' || p[1] == 'f' || p[1] == 'F' || p[1] == 'l' || p[1] == 'L')) diff --git a/compiler/src/dmd/link.d b/compiler/src/dmd/link.d index 0381cc46c5f1..f327c08f6290 100644 --- a/compiler/src/dmd/link.d +++ b/compiler/src/dmd/link.d @@ -962,9 +962,13 @@ public int runPreprocessor(Loc loc, const(char)[] cpp, const(char)[] filename, c version (Windows) { // generate unique temporary file name for preprocessed output - const(char)* tmpname = tmpnam(null); - assert(tmpname); - const(char)[] ifilename = tmpname[0 .. strlen(tmpname) + 1]; + char[MAX_PATH] tempDir = void; + char[MAX_PATH] tempFile = void; + if (GetTempPathA(MAX_PATH, tempDir.ptr) == 0) + return STATUS_FAILED; + if (GetTempFileNameA(tempDir.ptr, "dmd", 0, tempFile.ptr) == 0) + return STATUS_FAILED; + const(char)[] ifilename = tempFile[0 .. strlen(tempFile.ptr) + 1]; ifilename = xarraydup(ifilename); const(char)[] output = ifilename; diff --git a/compiler/test/compilable/imports/test22480b.d b/compiler/test/compilable/imports/test22480b.d new file mode 100644 index 000000000000..93f9202f0916 --- /dev/null +++ b/compiler/test/compilable/imports/test22480b.d @@ -0,0 +1,13 @@ +module immports.test22480b; + +auto parseAA() +{ + bool[string] aa; + aa["key"] = true; + assert("key" in aa); + assert(aa["key"]); + assert(aa.length == 1); + assert(aa == aa); + aa.rehash(); + return true; +} diff --git a/compiler/test/compilable/test22480.d b/compiler/test/compilable/test22480.d new file mode 100644 index 000000000000..54e925cea79a --- /dev/null +++ b/compiler/test/compilable/test22480.d @@ -0,0 +1,9 @@ +import imports.test22480b; + +@nogc nothrow: +enum a = parseAA(); + +extern(C) int main() +{ + return 0; +} diff --git a/compiler/test/compilable/test22501.d b/compiler/test/compilable/test22501.d new file mode 100644 index 000000000000..dc6fb2dbd683 --- /dev/null +++ b/compiler/test/compilable/test22501.d @@ -0,0 +1,12 @@ +// https://github.com/dlang/dmd/issues/22501 + +struct A { + ubyte[16] bytes; + + enum something = A(cast(ubyte[16])[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); + + @nogc nothrow pure + bool isB() const { + return bytes[0..12] == something.bytes[0..12]; + } +} diff --git a/compiler/test/compilable/test22543.d b/compiler/test/compilable/test22543.d new file mode 100644 index 000000000000..7a6a36f2490d --- /dev/null +++ b/compiler/test/compilable/test22543.d @@ -0,0 +1,11 @@ +// https://github.com/dlang/dmd/issues/22543 + +enum int[string] aa = [ + "a": 42, +]; + +int i = aa["a"]; + +void foo() { + static int j = aa["a"]; +} diff --git a/compiler/test/compilable/test22544.d b/compiler/test/compilable/test22544.d new file mode 100644 index 000000000000..b9d18f18b03a --- /dev/null +++ b/compiler/test/compilable/test22544.d @@ -0,0 +1,11 @@ +// https://github.com/dlang/dmd/issues/22544 + +struct S { + int bar() { return 1; } +} + +void foo(string key) { + int[string] aa; + with (S()) + aa[key] = bar(); +} diff --git a/compiler/test/compilable/test24295.d b/compiler/test/compilable/test24295.d new file mode 100644 index 000000000000..b80c18e306ed --- /dev/null +++ b/compiler/test/compilable/test24295.d @@ -0,0 +1,11 @@ +// REQUIRED_ARGS: -betterC + +int f() +{ + int[] overlaps = new int[1]; + overlaps[0] = 3; + return overlaps[0]; +} + +enum res_f = f(); +static assert(res_f == 3); diff --git a/compiler/test/fail_compilation/fail19898a.d b/compiler/test/fail_compilation/fail19898a.d index e23ed04305f0..1ff06ca20f62 100644 --- a/compiler/test/fail_compilation/fail19898a.d +++ b/compiler/test/fail_compilation/fail19898a.d @@ -2,7 +2,7 @@ REQUIRED_ARGS: -m64 TEST_OUTPUT: --- -fail_compilation/fail19898a.d(10): Error: expression `__key2 < __limit3` of type `__vector(int[4])` does not have a boolean value +fail_compilation/fail19898a.d(10): Error: expression `__key$n$ < __limit$n$` of type `__vector(int[4])` does not have a boolean value --- */ void f (__vector(int[4]) n) diff --git a/compiler/test/fail_compilation/fail19898b.d b/compiler/test/fail_compilation/fail19898b.d index 5101da5617b9..a59af4a688f0 100644 --- a/compiler/test/fail_compilation/fail19898b.d +++ b/compiler/test/fail_compilation/fail19898b.d @@ -3,8 +3,8 @@ REQUIRED_ARGS: -m64 TEST_OUTPUT: --- fail_compilation/fail19898b.d(17): Error: cannot implicitly convert expression `m` of type `S` to `__vector(int[4])` -fail_compilation/fail19898b.d(17): Error: expression `__key2 != __limit3` of type `__vector(int[4])` does not have a boolean value -fail_compilation/fail19898b.d(17): Error: cannot cast expression `__key2` of type `__vector(int[4])` to `S` +fail_compilation/fail19898b.d(17): Error: expression `__key$n$ != __limit$n$` of type `__vector(int[4])` does not have a boolean value +fail_compilation/fail19898b.d(17): Error: cannot cast expression `__key$n$` of type `__vector(int[4])` to `S` --- */ struct S diff --git a/compiler/test/fail_compilation/test17977.d b/compiler/test/fail_compilation/test17977.d index b79d36c71fcc..30475ae59c84 100644 --- a/compiler/test/fail_compilation/test17977.d +++ b/compiler/test/fail_compilation/test17977.d @@ -3,7 +3,7 @@ https://issues.dlang.org/show_bug.cgi?id=15399 REQUIRED_ARGS: -preview=dip1000 TEST_OUTPUT: --- -fail_compilation/test17977.d(19): Error: assigning address of variable `__slList3` to `elem` with longer lifetime is not allowed in a `@safe` function +fail_compilation/test17977.d(19): Error: assigning address of variable `__slList$n$` to `elem` with longer lifetime is not allowed in a `@safe` function --- */ diff --git a/compiler/test/fail_compilation/test24295.d b/compiler/test/fail_compilation/test24295.d deleted file mode 100644 index 58b6e92df8fc..000000000000 --- a/compiler/test/fail_compilation/test24295.d +++ /dev/null @@ -1,13 +0,0 @@ -// REQUIRED_ARGS: -betterC - -/* -TEST_OUTPUT: ---- -fail_compilation/test24295.d(12): Error: expression `new int[](1$?:32=u|64=LU$)` allocates with the GC and cannot be used with switch `-betterC` ---- -*/ - -void f() -{ - int[] overlaps = new int[1]; -} diff --git a/compiler/test/fail_compilation/test9150.d b/compiler/test/fail_compilation/test9150.d index 5f66b360fec1..bad84438db88 100644 --- a/compiler/test/fail_compilation/test9150.d +++ b/compiler/test/fail_compilation/test9150.d @@ -3,7 +3,7 @@ /* TEST_OUTPUT: --- -fail_compilation/test9150.d(14): Error: mismatched array lengths 5 and 3 for assignment `row[] = __r2[__key3]` +fail_compilation/test9150.d(14): Error: mismatched array lengths 5 and 3 for assignment `row[] = __r$n$[__key$n$]` --- */ diff --git a/compiler/test/runnable/b10562.d b/compiler/test/runnable/b10562.d index cf79d1644613..01ac9b8dea33 100644 --- a/compiler/test/runnable/b10562.d +++ b/compiler/test/runnable/b10562.d @@ -90,4 +90,12 @@ void main() // arr = slice; // assert(arr == [[ slice, slice, slice ], [ slice, slice, slice ]]); } + { + // https://github.com/dlang/dmd/issues/22386 + const int[3][2] a = [[1:2, 3],[0: 0, 1:2, 2:3]]; + assert(a == [ [ 0, 2, 3 ], [ 0, 2, 3 ] ]); + + const int[3][2] b = [[1:2, 2:3],[0: 0, 1:2, 2:3]]; + assert(b == [ [ 0, 2, 3 ], [ 0, 2, 3 ] ]); + } } diff --git a/compiler/test/runnable/lexer.d b/compiler/test/runnable/lexer.d index 0e1068a42899..daae3fa98761 100644 --- a/compiler/test/runnable/lexer.d +++ b/compiler/test/runnable/lexer.d @@ -42,6 +42,18 @@ foo s = q{{foo}"}"}; assert(s == "{foo}\"}\""); + + // https://github.com/dlang/dmd/issues/22535 + s = q"EOS +*是是是* +的的的. +*000* +EOS"; + assert(s == "*是是是* +的的的. +*000* +"); + assert(s.length == 29); } /*********************************************************/ diff --git a/compiler/test/runnable/test22422.d b/compiler/test/runnable/test22422.d new file mode 100644 index 000000000000..d51b93799aad --- /dev/null +++ b/compiler/test/runnable/test22422.d @@ -0,0 +1,21 @@ +// https://github.com/dlang/dmd/issues/22422 + +class C {} +class D {} + +struct S { + ~this() {} + D opIndex(size_t) { return new D; } +} + +S makeS() { + return S(); +} + +C foo() { + return cast(C) makeS()[0]; +} + +void main() { + assert(foo() is null); +} diff --git a/compiler/test/runnable/test22594.d b/compiler/test/runnable/test22594.d new file mode 100644 index 000000000000..0c16ba6e17ac --- /dev/null +++ b/compiler/test/runnable/test22594.d @@ -0,0 +1,23 @@ +// https://github.com/dlang/dmd/issues/22594 +// PERMUTE_ARGS: + +import core.memory : GC; + +class C(Ts...) { + Ts tuple; +} + +void main() { + alias NoPointers = C!int; + alias WithPointers = C!(void*); + + assert(typeid(NoPointers).m_flags & TypeInfo_Class.ClassFlags.noPointers); + assert(!(typeid(WithPointers).m_flags & TypeInfo_Class.ClassFlags.noPointers)); + + auto noPointers = new NoPointers; + // FIXME: regressed with v2.103 + //assert(GC.getAttr(cast(void*) noPointers) & GC.BlkAttr.NO_SCAN); + + auto withPointers = new WithPointers; + assert(!(GC.getAttr(cast(void*) withPointers) & GC.BlkAttr.NO_SCAN)); +} diff --git a/compiler/test/runnable/testaa3.d b/compiler/test/runnable/testaa3.d index 734c0c4cba0f..1ba165b55e6c 100644 --- a/compiler/test/runnable/testaa3.d +++ b/compiler/test/runnable/testaa3.d @@ -393,6 +393,73 @@ void test22354() assert(aa["x"].length == 1); // FAILS: length is still 0 } +// https://github.com/dlang/dmd/issues/22406 +void testShared() +{ + shared int[int] processes = [1: 1, 2:4, 3:9]; + + cast(void)processes.sizeof; + cast(void)processes.length; + cast(void)processes.dup; + cast(void)processes.rehash; + //cast(void)processes.clear; + //cast(void)processes.keys; + //cast(void)processes.values; + //cast(void)processes.byKey; + //cast(void)processes.byValue; + //cast(void)processes.byKeyValue; + processes.remove(3); + assert(2 in processes); + + immutable int[int] iprocesses = [1: 1, 2:4, 3:9]; + + cast(void)iprocesses.sizeof; + cast(void)iprocesses.length; + cast(void)iprocesses.dup; + //cast(void)iprocesses.rehash; + //cast(void)iprocesses.clear; + cast(void)iprocesses.keys; + cast(void)iprocesses.values; + cast(void)iprocesses.byKey; + cast(void)iprocesses.byValue; + cast(void)iprocesses.byKeyValue; + //iprocesses.remove(3); + assert(2 in iprocesses); +} + +// https://github.com/dlang/dmd/issues/22556 +void test22556() +{ + static struct RefCounted(T) + { + this(this) {} + } + struct S {} + alias R = RefCounted!S; + shared R[string] foo; + + (cast (R[string]) foo).clear; // WORKS + (cast() foo).clear; // FAILS with 2.112.0, WORKS with 2.111.0 + static assert(!__traits(compiles, foo.clear)); +} + +/***************************************************/ + +// https://github.com/dlang/dmd/issues/22567 +void test22567() +{ + struct S + { + string[string] data; + alias this = data; + } + + S[string] foo; + foo["bar"] = S(); + foo["bar"]["baz"] = "boom"; + assert(foo["bar"]["baz"] == "boom"); +} + /***************************************************/ void main() @@ -421,4 +488,7 @@ void main() test12403(); test21066(); test22354(); + testShared(); + test22567(); + test22556(); } diff --git a/druntime/src/core/internal/array/capacity.d b/druntime/src/core/internal/array/capacity.d index 2992d30b17c2..cbcc3d3b6d29 100644 --- a/druntime/src/core/internal/array/capacity.d +++ b/druntime/src/core/internal/array/capacity.d @@ -142,7 +142,10 @@ do auto attrs = __typeAttrs!T((*p).ptr) | BlkAttr.APPENDABLE; // use this static enum to avoid recomputing TypeInfo for every call. - static enum ti = typeid(T); + version (D_TypeInfo) + static enum ti = typeid(T); + else + static enum ti = null; auto ptr = GC.malloc(reqsize, attrs, ti); if (ptr is null) { diff --git a/druntime/src/core/internal/array/construction.d b/druntime/src/core/internal/array/construction.d index 4797244993fe..4b47e20b0a4e 100644 --- a/druntime/src/core/internal/array/construction.d +++ b/druntime/src/core/internal/array/construction.d @@ -672,7 +672,10 @@ void* _d_arrayliteralTX(T)(size_t length) @trusted pure nothrow static if (is(T == struct) && hasElaborateDestructor!T) attrs |= BlkAttr.FINALIZE; - return GC.malloc(allocsize, attrs, typeid(T)); + version (D_TypeInfo) + return GC.malloc(allocsize, attrs, typeid(T)); + else + return GC.malloc(allocsize, attrs, null); } } diff --git a/druntime/src/core/internal/array/utils.d b/druntime/src/core/internal/array/utils.d index 25adb1e628ac..40caa39c89e8 100644 --- a/druntime/src/core/internal/array/utils.d +++ b/druntime/src/core/internal/array/utils.d @@ -151,7 +151,10 @@ void[] __arrayAlloc(T)(size_t arrSize) @trusted static if (!hasIndirections!T) attr |= BlkAttr.NO_SCAN; - auto ptr = GC.malloc(arrSize, attr, typeid(T)); + version(D_TypeInfo) + auto ptr = GC.malloc(arrSize, attr, typeid(T)); + else + auto ptr = GC.malloc(arrSize, attr, null); if (ptr) return ptr[0 .. arrSize]; return null; diff --git a/druntime/src/core/internal/newaa.d b/druntime/src/core/internal/newaa.d index 55bc16e5e8b7..bd95fb1ea6b5 100644 --- a/druntime/src/core/internal/newaa.d +++ b/druntime/src/core/internal/newaa.d @@ -463,6 +463,18 @@ size_t _d_aaLen(K, V)(inout V[K] a) auto aa = _toAA!(K, V)(a); return aa ? aa.length : 0; } +/// ditto +size_t _d_aaLen(K, V)(shared V[K] a) +{ + // accept shared for backward compatibility, should be deprecated + return _d_aaLen(cast(V[K]) a); +} +/// ditto +size_t _d_aaLen(K, V)(const shared V[K] a) +{ + // accept shared for backward compatibility, should be deprecated + return _d_aaLen(cast(V[K]) a); +} /****************************** * Lookup key in aa. @@ -631,6 +643,18 @@ auto _d_aaIn(T : V[K], K, V, K2)(inout T a, auto ref scope K2 key) return &p.entry.value; return null; } +/// ditto +auto _d_aaIn(T : V[K], K, V, K2)(shared T a, auto ref scope K2 key) +{ + // accept shared for backward compatibility, should be deprecated + return _d_aaIn(cast(V[K]) a, key); +} +/// ditto +auto _d_aaIn(T : V[K], K, V, K2)(const shared T a, auto ref scope K2 key) +{ + // accept shared for backward compatibility, should be deprecated + return _d_aaIn(cast(V[K]) a, key); +} // fake purity for backward compatibility with runtime hooks private extern(C) bool gc_inFinalizer() pure nothrow @safe; diff --git a/druntime/src/core/lifetime.d b/druntime/src/core/lifetime.d index d19c0bbc0e0b..cd38648bc22e 100644 --- a/druntime/src/core/lifetime.d +++ b/druntime/src/core/lifetime.d @@ -2781,7 +2781,10 @@ if (is(T == class)) static if (!hasIndirections!T) attr |= BlkAttr.NO_SCAN; - p = GC.malloc(init.length, attr, typeid(T)); + version(D_TypeInfo) + p = GC.malloc(init.length, attr, typeid(T)); + else + p = GC.malloc(init.length, attr, null); debug(PRINTF) printf(" p = %p\n", p); } @@ -2853,7 +2856,10 @@ T* _d_newitemT(T)() @trusted if (TypeInfoSize!T) flags |= GC.BlkAttr.FINALIZE; - auto p = GC.malloc(itemSize, flags, typeid(T)); + version(D_TypeInfo) + auto p = GC.malloc(itemSize, flags, typeid(T)); + else + auto p = GC.malloc(itemSize, flags, null); emplaceInitializer(*(cast(T*) p)); diff --git a/druntime/src/core/sys/posix/netinet/in_.d b/druntime/src/core/sys/posix/netinet/in_.d index 09a4a5e07d10..75359ea213a7 100644 --- a/druntime/src/core/sys/posix/netinet/in_.d +++ b/druntime/src/core/sys/posix/netinet/in_.d @@ -547,7 +547,7 @@ version (CRuntime_Glibc) } // macros - extern (D) int IN6_IS_ADDR_UNSPECIFIED()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_UNSPECIFIED()(const scope in6_addr* addr) pure { return (cast(uint32_t*) addr)[0] == 0 && (cast(uint32_t*) addr)[1] == 0 && @@ -555,7 +555,7 @@ version (CRuntime_Glibc) (cast(uint32_t*) addr)[3] == 0; } - extern (D) int IN6_IS_ADDR_LOOPBACK()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_LOOPBACK()(const scope in6_addr* addr) pure { return (cast(uint32_t*) addr)[0] == 0 && (cast(uint32_t*) addr)[1] == 0 && @@ -563,29 +563,29 @@ version (CRuntime_Glibc) (cast(uint32_t*) addr)[3] == htonl( 1 ); } - extern (D) int IN6_IS_ADDR_MULTICAST()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_MULTICAST()(const scope in6_addr* addr) pure { return (cast(uint8_t*) addr)[0] == 0xff; } - extern (D) int IN6_IS_ADDR_LINKLOCAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_LINKLOCAL()(const scope in6_addr* addr) pure { return ((cast(uint32_t*) addr)[0] & htonl( 0xffc00000 )) == htonl( 0xfe800000 ); } - extern (D) int IN6_IS_ADDR_SITELOCAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_SITELOCAL()(const scope in6_addr* addr) pure { return ((cast(uint32_t*) addr)[0] & htonl( 0xffc00000 )) == htonl( 0xfec00000 ); } - extern (D) int IN6_IS_ADDR_V4MAPPED()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_V4MAPPED()(const scope in6_addr* addr) pure { return (cast(uint32_t*) addr)[0] == 0 && (cast(uint32_t*) addr)[1] == 0 && (cast(uint32_t*) addr)[2] == htonl( 0xffff ); } - extern (D) int IN6_IS_ADDR_V4COMPAT()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_V4COMPAT()(const scope in6_addr* addr) pure { return (cast(uint32_t*) addr)[0] == 0 && (cast(uint32_t*) addr)[1] == 0 && @@ -593,31 +593,31 @@ version (CRuntime_Glibc) ntohl( (cast(uint32_t*) addr)[3] ) > 1; } - extern (D) int IN6_IS_ADDR_MC_NODELOCAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_MC_NODELOCAL()(const scope in6_addr* addr) pure { return IN6_IS_ADDR_MULTICAST( addr ) && ((cast(uint8_t*) addr)[1] & 0xf) == 0x1; } - extern (D) int IN6_IS_ADDR_MC_LINKLOCAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_MC_LINKLOCAL()(const scope in6_addr* addr) pure { return IN6_IS_ADDR_MULTICAST( addr ) && ((cast(uint8_t*) addr)[1] & 0xf) == 0x2; } - extern (D) int IN6_IS_ADDR_MC_SITELOCAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_MC_SITELOCAL()(const scope in6_addr* addr) pure { return IN6_IS_ADDR_MULTICAST(addr) && ((cast(uint8_t*) addr)[1] & 0xf) == 0x5; } - extern (D) int IN6_IS_ADDR_MC_ORGLOCAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_MC_ORGLOCAL()(const scope in6_addr* addr) pure { return IN6_IS_ADDR_MULTICAST( addr) && ((cast(uint8_t*) addr)[1] & 0xf) == 0x8; } - extern (D) int IN6_IS_ADDR_MC_GLOBAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_MC_GLOBAL()(const scope in6_addr* addr) pure { return IN6_IS_ADDR_MULTICAST( addr ) && ((cast(uint8_t*) addr)[1] & 0xf) == 0xe; @@ -670,7 +670,7 @@ else version (Darwin) } // macros - extern (D) int IN6_IS_ADDR_UNSPECIFIED()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_UNSPECIFIED()(const scope in6_addr* addr) pure { return (cast(uint32_t*) addr)[0] == 0 && (cast(uint32_t*) addr)[1] == 0 && @@ -678,7 +678,7 @@ else version (Darwin) (cast(uint32_t*) addr)[3] == 0; } - extern (D) int IN6_IS_ADDR_LOOPBACK()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_LOOPBACK()(const scope in6_addr* addr) pure { return (cast(uint32_t*) addr)[0] == 0 && (cast(uint32_t*) addr)[1] == 0 && @@ -686,29 +686,29 @@ else version (Darwin) (cast(uint32_t*) addr)[3] == ntohl( 1 ); } - extern (D) int IN6_IS_ADDR_MULTICAST()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_MULTICAST()(const scope in6_addr* addr) pure { return addr.s6_addr[0] == 0xff; } - extern (D) int IN6_IS_ADDR_LINKLOCAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_LINKLOCAL()(const scope in6_addr* addr) pure { return addr.s6_addr[0] == 0xfe && (addr.s6_addr[1] & 0xc0) == 0x80; } - extern (D) int IN6_IS_ADDR_SITELOCAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_SITELOCAL()(const scope in6_addr* addr) pure { return addr.s6_addr[0] == 0xfe && (addr.s6_addr[1] & 0xc0) == 0xc0; } - extern (D) int IN6_IS_ADDR_V4MAPPED()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_V4MAPPED()(const scope in6_addr* addr) pure { return (cast(uint32_t*) addr)[0] == 0 && (cast(uint32_t*) addr)[1] == 0 && (cast(uint32_t*) addr)[2] == ntohl( 0x0000ffff ); } - extern (D) int IN6_IS_ADDR_V4COMPAT()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_V4COMPAT()(const scope in6_addr* addr) pure { return (cast(uint32_t*) addr)[0] == 0 && (cast(uint32_t*) addr)[1] == 0 && @@ -717,31 +717,31 @@ else version (Darwin) (cast(uint32_t*) addr)[3] != ntohl( 1 ); } - extern (D) int IN6_IS_ADDR_MC_NODELOCAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_MC_NODELOCAL()(const scope in6_addr* addr) pure { return IN6_IS_ADDR_MULTICAST( addr ) && ((cast(uint8_t*) addr)[1] & 0xf) == 0x1; } - extern (D) int IN6_IS_ADDR_MC_LINKLOCAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_MC_LINKLOCAL()(const scope in6_addr* addr) pure { return IN6_IS_ADDR_MULTICAST( addr ) && ((cast(uint8_t*) addr)[1] & 0xf) == 0x2; } - extern (D) int IN6_IS_ADDR_MC_SITELOCAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_MC_SITELOCAL()(const scope in6_addr* addr) pure { return IN6_IS_ADDR_MULTICAST(addr) && ((cast(uint8_t*) addr)[1] & 0xf) == 0x5; } - extern (D) int IN6_IS_ADDR_MC_ORGLOCAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_MC_ORGLOCAL()(const scope in6_addr* addr) pure { return IN6_IS_ADDR_MULTICAST( addr) && ((cast(uint8_t*) addr)[1] & 0xf) == 0x8; } - extern (D) int IN6_IS_ADDR_MC_GLOBAL()(const scope in6_addr* add) pure + extern (D) int IN6_IS_ADDR_MC_GLOBAL()(const scope in6_addr* addr) pure { return IN6_IS_ADDR_MULTICAST( addr ) && ((cast(uint8_t*) addr)[1] & 0xf) == 0xe; diff --git a/druntime/src/core/sys/posix/sys/stat.d b/druntime/src/core/sys/posix/sys/stat.d index 27de01fe979a..a14fc149d707 100644 --- a/druntime/src/core/sys/posix/sys/stat.d +++ b/druntime/src/core/sys/posix/sys/stat.d @@ -2010,12 +2010,14 @@ else version (Darwin) version (AArch64) { int fstat(int, stat_t*); + int fstatat(int, const scope char*, stat_t*, int); int lstat(const scope char*, stat_t*); int stat(const scope char*, stat_t*); } else { pragma(mangle, "fstat$INODE64") int fstat(int, stat_t*); + pragma(mangle, "fstatat$INODE64") int fstatat(int, const scope char*, stat_t*, int); pragma(mangle, "lstat$INODE64") int lstat(const scope char*, stat_t*); pragma(mangle, "stat$INODE64") int stat(const scope char*, stat_t*); } @@ -2023,11 +2025,11 @@ else version (Darwin) else { int fstat(int, stat_t*); + int fstatat(int, const scope char*, stat_t*, int); int lstat(const scope char*, stat_t*); int stat(const scope char*, stat_t*); } int fchmodat(int, const scope char*, mode_t, int); - int fstatat(int, const scope char*, stat_t*, int); int futimens(int, ref const(timespec)[2]); int mkdirat(int, const scope char*, mode_t); int mkfifoat(int, const scope char*, mode_t); diff --git a/druntime/src/core/sys/posix/unistd.d b/druntime/src/core/sys/posix/unistd.d index 5c3182fc7a21..ea6d866999ca 100644 --- a/druntime/src/core/sys/posix/unistd.d +++ b/druntime/src/core/sys/posix/unistd.d @@ -673,7 +673,21 @@ version (CRuntime_Glibc) _SC_LEVEL4_CACHE_LINESIZE, _SC_IPV6 = _SC_LEVEL1_ICACHE_SIZE + 50, - _SC_RAW_SOCKETS + _SC_RAW_SOCKETS, + _SC_V7_ILP32_OFF32, + _SC_V7_ILP32_OFFBIG, + _SC_V7_LP64_OFF64, + _SC_V7_LPBIG_OFFBIG, + _SC_SS_REPL_MAX, + _SC_TRACE_EVENT_NAME_MAX, + _SC_TRACE_NAME_MAX, + _SC_TRACE_SYS_MAX, + _SC_TRACE_USER_EVENT_MAX, + _SC_XOPEN_STREAMS, + _SC_THREAD_ROBUST_PRIO_INHERIT, + _SC_THREAD_ROBUST_PRIO_PROTECT, + _SC_MINSIGSTKSZ, + _SC_SIGSTKSZ, } } else version (Darwin) diff --git a/druntime/src/core/sys/windows/winuser.d b/druntime/src/core/sys/windows/winuser.d index dddaeb0535ab..9d8c26440edb 100644 --- a/druntime/src/core/sys/windows/winuser.d +++ b/druntime/src/core/sys/windows/winuser.d @@ -3720,8 +3720,8 @@ nothrow @nogc { alias GetNextWindow = GetWindow; extern (Windows) nothrow @nogc: -LONG DispatchMessageA(const(MSG)*); -LONG DispatchMessageW(const(MSG)*); +LRESULT DispatchMessageA(const(MSG)*); +LRESULT DispatchMessageW(const(MSG)*); int DlgDirListA(HWND, LPSTR, int, int, UINT); int DlgDirListW(HWND, LPWSTR, int, int, UINT); int DlgDirListComboBoxA(HWND, LPSTR, int, int, UINT); @@ -4034,8 +4034,8 @@ BOOL ScreenToClient(HWND, LPPOINT); BOOL ScrollDC(HDC, int, int, LPCRECT, LPCRECT, HRGN, LPRECT); BOOL ScrollWindow(HWND, int, int, LPCRECT, LPCRECT); int ScrollWindowEx(HWND, int, int, LPCRECT, LPCRECT, HRGN, LPRECT, UINT); -LONG SendDlgItemMessageA(HWND, int, UINT, WPARAM, LPARAM); -LONG SendDlgItemMessageW(HWND, int, UINT, WPARAM, LPARAM); +LRESULT SendDlgItemMessageA(HWND, int, UINT, WPARAM, LPARAM); +LRESULT SendDlgItemMessageW(HWND, int, UINT, WPARAM, LPARAM); LRESULT SendMessageA(HWND, UINT, WPARAM, LPARAM); BOOL SendMessageCallbackA(HWND, UINT, WPARAM, LPARAM, SENDASYNCPROC, ULONG_PTR); BOOL SendMessageCallbackW(HWND, UINT, WPARAM, LPARAM, SENDASYNCPROC, ULONG_PTR); diff --git a/druntime/src/etc/linux/memoryerror.d b/druntime/src/etc/linux/memoryerror.d index db6a0eab863f..5b372e7f477e 100644 --- a/druntime/src/etc/linux/memoryerror.d +++ b/druntime/src/etc/linux/memoryerror.d @@ -51,6 +51,7 @@ import ucontext = core.sys.posix.ucontext; version (MemoryAssertSupported) { + import core.stdc.stdlib : malloc; import core.sys.posix.signal : SA_ONSTACK, sigaltstack, SIGSTKSZ, stack_t; } @@ -357,7 +358,30 @@ version (MemoryAssertSupported) // Set up alternate stack, because segfaults can be caused by stack overflow, // in which case the stack is already exhausted - __gshared ubyte[SIGSTKSZ] altStack; + + __gshared void[] altStack; // lazily allocated once only via malloc; never free'd + if (altStack.ptr is null) + { + version (CRuntime_Glibc) + { + // glibc v2.34 switched to a dynamic SIGSTKSZ + import core.sys.posix.unistd : sysconf, _SC_SIGSTKSZ; + + auto size = sysconf(_SC_SIGSTKSZ); + if (size <= 0) + size = SIGSTKSZ; + } + else + { + const size = SIGSTKSZ; + } + + if (auto p = malloc(size)) + altStack = p[0 .. size]; + else + return false; + } + stack_t ss; ss.ss_sp = altStack.ptr; ss.ss_size = altStack.length; diff --git a/druntime/src/object.d b/druntime/src/object.d index 015666cfdb82..e6c6db6e5ba9 100644 --- a/druntime/src/object.d +++ b/druntime/src/object.d @@ -2992,16 +2992,30 @@ alias AssociativeArray(Key, Value) = Value[Key]; * aa = The associative array. */ void clear(Value, Key)(Value[Key] aa) @trusted +if (!is(Value == shared)) { _aaClear(aa); } /** ditto */ void clear(Value, Key)(Value[Key]* aa) @trusted +if (!is(Value == shared)) { (*aa).clear(); } +/** ditto */ +void clear(Value, Key)(shared(Value)[Key] aa) +{ + return (cast(Value[Key])aa).clear(); +} + +/** ditto */ +void clear(Value, Key)(shared(Value)[Key]* aa) +{ + (cast(Value[Key])*aa).clear(); +} + /// @safe unittest { @@ -3072,11 +3086,23 @@ Value[Key] rehash(T : shared Value[Key], Value, Key)(T* aa) */ auto dup(T : V[K], K, V)(T aa) { + import core.internal.traits : substInout, Unconst; + // Bug10720 - check whether V is copyable - static assert(is(typeof({ V v = aa[K.init]; })), - "cannot call " ~ T.stringof ~ ".dup because " ~ V.stringof ~ " is not copyable"); + static if (is(typeof({ Unconst!V v = aa[K.init]; }))) + alias Vret = Unconst!V; + else static if (is(typeof({ V v = aa[K.init]; }))) + alias Vret = V; + else + static assert(false, "cannot call " ~ T.stringof ~ ".dup because " ~ V.stringof ~ " is not copyable"); + alias Kret = typeof([K.init][0]); // strip const if possible by copy + + alias K1 = substInout!K; + alias V1 = substInout!Vret; - return _aaDup(aa); + auto naa = _aaDup((() @trusted => cast(V1[K1])aa)()); + auto maa = ((inout T) @trusted => cast(Vret[Kret])naa)(aa); + return maa; } /** ditto */ diff --git a/druntime/test/aa/src/test_aa.d b/druntime/test/aa/src/test_aa.d index 214bc9a12b2e..aef99e8ab5a9 100644 --- a/druntime/test/aa/src/test_aa.d +++ b/druntime/test/aa/src/test_aa.d @@ -1048,3 +1048,38 @@ void testAliasThis2() assert(B(5) in aa); assert(A(false, 5) in aa); } + +void test22510() +{ + static struct S(AA) + { + AA aa_; + auto aa() inout => this.aa_.dup; + } + auto testDup(AA)() + { + S!AA s; + return s.aa(); + } + auto aa_ii = testDup!(int[int])(); + static assert(is(typeof(aa_ii) == int[int])); + + auto aa_cii = testDup!(const(int[int]))(); + static assert(is(typeof(aa_cii) == int[int])); + + static struct T + { + char[] s; // non const indirection disallows conversion of const(T) -> T when copying + } + auto aa_it = testDup!(int[T])(); + static assert(is(typeof(aa_it) == int[T])); + + auto aa_cit = testDup!(const(int[T]))(); + static assert(is(typeof(aa_cit) == int[T])); + + auto aa_ti = testDup!(T[int])(); + static assert(is(typeof(aa_ti) == T[int])); + + auto aa_cti = testDup!(const(T[int]))(); + static assert(is(typeof(aa_cti) == const(T)[int])); +}