From c90cb26c7ffb15e6bc97fc3b614dc53e9217acbb Mon Sep 17 00:00:00 2001 From: Rainer Schuetze Date: Tue, 13 Jan 2026 00:37:26 +0100 Subject: [PATCH 01/29] fix #22386 - Indexed initialization of multi dimensional const array does not compile. (#22391) pass the element type to initializerToExpression() for the element if the dimensions for type and initializer match --- compiler/src/dmd/initsem.d | 18 +++++++++++++++++- compiler/test/runnable/b10562.d | 8 ++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/compiler/src/dmd/initsem.d b/compiler/src/dmd/initsem.d index c02ee34b5a90..9e23f3719c26 100644 --- a/compiler/src/dmd/initsem.d +++ b/compiler/src/dmd/initsem.d @@ -1414,6 +1414,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; @@ -1424,7 +1440,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/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 ] ]); + } } From 1fe509bff91c08e817926d594187915e54199947 Mon Sep 17 00:00:00 2001 From: Rainer Schuetze Date: Sat, 17 Jan 2026 03:53:21 +0100 Subject: [PATCH 02/29] fix #22406 - [REG 2.112] Error: function '_d_aaLen' is not callable using argument types '(shared(int[int]))' (#22407) add shared overloads for _d_aaLen and _d_aaIn --- compiler/test/runnable/testaa3.d | 35 ++++++++++++++++++++++++++++++ druntime/src/core/internal/newaa.d | 24 ++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/compiler/test/runnable/testaa3.d b/compiler/test/runnable/testaa3.d index 734c0c4cba0f..53a0e8bb86cc 100644 --- a/compiler/test/runnable/testaa3.d +++ b/compiler/test/runnable/testaa3.d @@ -393,6 +393,40 @@ 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); +} + /***************************************************/ void main() @@ -421,4 +455,5 @@ void main() test12403(); test21066(); test22354(); + testShared(); } diff --git a/druntime/src/core/internal/newaa.d b/druntime/src/core/internal/newaa.d index 966c2a5daeef..9424756c3419 100644 --- a/druntime/src/core/internal/newaa.d +++ b/druntime/src/core/internal/newaa.d @@ -458,6 +458,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. @@ -626,6 +638,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; From c75c2d23c9c78a05d8eef4bed085221b9e1b62a5 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Mon, 19 Jan 2026 23:58:22 +0100 Subject: [PATCH 03/29] Fix #22422 - Missing _d_cast lowering for CommaExp (#22424) --- compiler/src/dmd/expressionsem.d | 4 ++-- compiler/test/runnable/test22422.d | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 compiler/test/runnable/test22422.d diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index feee84989c93..4516d220171a 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -7628,7 +7628,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); } @@ -10044,7 +10044,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } } - if (auto cex = ex.isCastExp()) + if (auto cex = lastComma(ex).isCastExp()) { lowerCastExp(cex, sc); } 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); +} From 737a9bf436ba914a699bc27c2ab3926976f6f8f3 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Fri, 30 Jan 2026 15:32:09 +0100 Subject: [PATCH 04/29] etc.linux.memoryerror: Adapt to dynamic SIGSTKSZ since glibc v2.34 (#22473) This fixes segfault-handler tests for LDC Linux CI, apparently caused by an insufficient alternate-stack size. Claude was able to diagnose the problem: https://github.com/ldc-developers/ldc/pull/5041#issuecomment-3689617009 --- druntime/src/core/sys/posix/unistd.d | 16 +++++++++++++++- druntime/src/etc/linux/memoryerror.d | 26 +++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/druntime/src/core/sys/posix/unistd.d b/druntime/src/core/sys/posix/unistd.d index 860630b42255..83536a304017 100644 --- a/druntime/src/core/sys/posix/unistd.d +++ b/druntime/src/core/sys/posix/unistd.d @@ -671,7 +671,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/etc/linux/memoryerror.d b/druntime/src/etc/linux/memoryerror.d index b44a7f6d68f9..b97bb7948f08 100644 --- a/druntime/src/etc/linux/memoryerror.d +++ b/druntime/src/etc/linux/memoryerror.d @@ -50,6 +50,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; } @@ -423,7 +424,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; From e6902e38c7de018a38d39eaab769a67061ac1b61 Mon Sep 17 00:00:00 2001 From: "Richard (Rikki) Andrew Cattermole" Date: Tue, 21 Oct 2025 21:12:50 +1300 Subject: [PATCH 05/29] Bump OSX runner image to 14 (#21985) --- .github/workflows/main.yml | 23 ++++++++++------------- .github/workflows/runnable_cxx.yml | 4 ++-- ci/run.sh | 3 ++- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0ba35a7c53bf..c599285da151 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,20 +54,17 @@ jobs: container_image: alpine:3.21 host_dmd: ldmd2 # macOS - - job_name: macOS 13 x64, DMD (latest) - os: macos-13 + - job_name: macOS 14 x64, DMD (latest) + os: macos-14 host_dmd: dmd # Disabled because of failure https://issues.dlang.org/show_bug.cgi?id=24518 - # - job_name: macOS 13 x64, DMD (coverage) - # os: macos-13 - # host_dmd: dmd - # coverage: true - - job_name: macOS 13 x64, DMD (bootstrap) - os: macos-13 - xcode: '14.3.1' # work around 'ld: multiple errors: symbol count from symbol table and dynamic symbol table differ' with old bootstrap compiler - # de-facto bootstrap version on OSX - # See: https://github.com/dlang/dmd/pull/13890 - host_dmd: dmd-2.099.1 + #- job_name: macOS 14 x64, DMD (coverage) + # os: macos-14 + # host_dmd: dmd + # coverage: true + - job_name: macOS 14 x64, DMD (bootstrap) + os: macos-14 + host_dmd: dmd-2.107.1 # Windows - job_name: Windows x64, LDC os: windows-2022 @@ -79,7 +76,7 @@ jobs: name: ${{ matrix.job_name }} runs-on: ${{ matrix.os }} container: ${{ matrix.container_image }} - timeout-minutes: 40 + timeout-minutes: 60 # OSX tests can still be running runnable at 40 env: # for ci/run.sh: OS_NAME: ${{ startsWith(matrix.os, 'ubuntu') && 'linux' || (startsWith(matrix.os, 'macos') && 'osx' || (startsWith(matrix.os, 'windows') && 'windows' || '')) }} diff --git a/.github/workflows/runnable_cxx.yml b/.github/workflows/runnable_cxx.yml index 391d17a75074..2cdc591f1ab3 100644 --- a/.github/workflows/runnable_cxx.yml +++ b/.github/workflows/runnable_cxx.yml @@ -70,9 +70,9 @@ jobs: - { os: ubuntu-22.04, compiler: g++-9, model: 64 } - { os: ubuntu-22.04, compiler: g++-9, model: 32 } # macOS, Apple clang from Xcode: + # Disabled due to issues with x87: https://github.com/dlang/dmd/issues/21987 + #- { os: macos-15, xcode: '16.4', model: 64 } - { os: macos-14, xcode: '16.2', model: 64 } - - { os: macos-13, xcode: '15.2', model: 64 } - - { os: macos-13, xcode: '14.3.1', model: 64 } # Windows, cl.exe from Visual Studio: # NOTE: as of April 2025, image windows-2025 only has VS 2022, so no point in testing that image too - { os: windows-2022, model: 64 } diff --git a/ci/run.sh b/ci/run.sh index 58a4fd4f9ea4..292345b139c0 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -35,7 +35,8 @@ else NM=nm if [ "$OS_NAME" == "osx" ]; then - export PATH="/usr/local/opt/llvm/bin:$PATH" + # As of macos-14 gmake won't be used purely based upon the symlink, must force override via PATH. + export PATH="/opt/homebrew/opt/make/libexec/gnubin:/usr/local/opt/llvm/bin:$PATH" fi fi From cfa18060b40d3925cae51f4629850379937cec51 Mon Sep 17 00:00:00 2001 From: "Richard (Rikki) Andrew Cattermole" Date: Sun, 26 Oct 2025 01:03:14 +1300 Subject: [PATCH 06/29] Bump Windows 2019 to 2022 for Azure pipelines (#22000) --- azure-pipelines.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 50756e8ed06a..0b11b4bc11ee 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,15 +1,15 @@ # Learn more: https://aka.ms/yaml variables: - VSINSTALLDIR: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\ + VSINSTALLDIR: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\ jobs: - job: Windows_DMD_bootstrap timeoutInMinutes: 60 pool: - vmImage: 'windows-2019' + vmImage: 'windows-2022' variables: D_COMPILER: dmd - HOST_DMD_VERSION: 2.079.0 + HOST_DMD_VERSION: 2.097.1 strategy: matrix: x64: @@ -21,7 +21,7 @@ jobs: - job: Windows_DMD_latest timeoutInMinutes: 60 pool: - vmImage: 'windows-2019' + vmImage: 'windows-2022' variables: D_COMPILER: dmd HOST_DMD_VERSION: LATEST @@ -40,7 +40,7 @@ jobs: - job: Windows_Coverage timeoutInMinutes: 60 pool: - vmImage: 'windows-2019' + vmImage: 'windows-2022' variables: D_COMPILER: dmd HOST_DMD_VERSION: LATEST @@ -57,10 +57,10 @@ jobs: - job: Windows_VisualD_LDC timeoutInMinutes: 60 pool: - vmImage: 'windows-2019' + vmImage: 'windows-2022' variables: D_COMPILER: ldc - VISUALD_VER: v0.49.0 + VISUALD_VER: v1.4.0 LDC_VERSION: 1.23.0 strategy: matrix: From 477cb991da1fde8bac985bba0204e65af8b324a1 Mon Sep 17 00:00:00 2001 From: Rainer Schuetze Date: Fri, 24 Oct 2025 12:44:24 +0200 Subject: [PATCH 07/29] redirect output of gc/printf test to file to avoid pollution of CI logs (#22021) --- druntime/test/gc/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/druntime/test/gc/Makefile b/druntime/test/gc/Makefile index 3d5acdc56469..cb96de7f61b6 100644 --- a/druntime/test/gc/Makefile +++ b/druntime/test/gc/Makefile @@ -50,6 +50,7 @@ $(ROOT)/printf$(DOTEXE): $(src_gc) $(ROOT)/printf$(DOTEXE): private extra_sources = $(src_gc) $(ROOT)/printf$(DOTEXE): extra_dflags += \ -debug=PRINTF -debug=PRINTF_TO_FILE -debug=COLLECT_PRINTF -version=OnlyLowMemUnittests $(core_ut) -main +$(ROOT)/printf.done: run_args += >$(ROOT)/printf.log $(ROOT)/memstomp$(DOTEXE): $(src_lifetime) $(ROOT)/memstomp$(DOTEXE): private extra_sources = $(src_lifetime) From 215c9679a50425d271ccaec2d098a6a307903dd2 Mon Sep 17 00:00:00 2001 From: Rainer Schuetze Date: Tue, 4 Nov 2025 12:09:39 +0100 Subject: [PATCH 08/29] allow dmd test run arguments to be grouped with quotes so they don't generate as many combinations (#22059) apply to the slower CI builds by combining -O and -release --- ci/run.sh | 2 +- compiler/test/tools/d_do_test.d | 50 ++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/ci/run.sh b/ci/run.sh index 292345b139c0..947e314d7973 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -121,7 +121,7 @@ test_dmd() { if [ "$FULL_BUILD" == "true" ] && [ "$OS_NAME" == "linux" ]; then local args=() # use all default ARGS else - local args=(ARGS="-O -inline -release") + local args=(ARGS="-inline '-O -release'") fi if type -P apk &>/dev/null; then diff --git a/compiler/test/tools/d_do_test.d b/compiler/test/tools/d_do_test.d index 3f7e490febb4..7d7cb879bd17 100755 --- a/compiler/test/tools/d_do_test.d +++ b/compiler/test/tools/d_do_test.d @@ -880,11 +880,59 @@ REQUIRED_ARGS(linux32): -fPIC assert(args.requiredArgs == "-os=windows", args.requiredArgs); } +string[] splitQuoted(string text) +{ + import std.utf, std.uni; + enum quote = '\''; + + string[] args; + size_t pos = 0; + while (pos < text.length) + { + size_t startpos = pos; + dchar ch = decode(text, pos); + if (isWhite(ch)) + continue; + + size_t quoted = 0; + size_t endpos = pos; + while (pos < text.length) + { + if (ch == quote) + { + while (pos < text.length) + { + dchar ch2 = decode(text, pos); + if (ch2 == quote) // no escaping + { + quoted++; + break; + } + } + ch = 0; + } + else + { + ch = decode(text, pos); + } + if (isWhite(ch)) + break; + endpos = pos; + } + if (quoted == 1 && text[startpos] == quote && text[endpos-1] == quote) + startpos++, endpos--; + auto arg = text[startpos .. endpos].strip; + if (!arg.empty) + args ~= arg; + } + return args; +} + /// Generates all permutations of the space-separated word contained in `argstr` string[] combinations(string argstr) { string[] results; - string[] args = split(argstr); + string[] args = splitQuoted(argstr); long combinations = 1 << args.length; for (size_t i = 0; i < combinations; i++) { From f6e760424b99fe7b425458c0e0581395d669cdf0 Mon Sep 17 00:00:00 2001 From: Rainer Schuetze Date: Mon, 24 Nov 2025 10:01:23 +0100 Subject: [PATCH 09/29] AA: don't require TypeInfo if compiling without it (#22137) --- druntime/src/core/internal/newaa.d | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/druntime/src/core/internal/newaa.d b/druntime/src/core/internal/newaa.d index 9424756c3419..88f8d044ebf4 100644 --- a/druntime/src/core/internal/newaa.d +++ b/druntime/src/core/internal/newaa.d @@ -222,7 +222,8 @@ private: firstUsed = cast(uint) buckets.length; // only for binary compatibility - entryTI = typeid(Entry!(K, V)); + version(D_TypeInfo) + entryTI = typeid(Entry!(K, V)); hashFn = delegate size_t (scope ref const K key) nothrow pure @nogc @safe { return pure_hashOf!K(key); }; From 903a7aff0193aaabaf12c98d333824bcc37bc8e3 Mon Sep 17 00:00:00 2001 From: Rainer Date: Sun, 1 Feb 2026 10:21:11 +0100 Subject: [PATCH 10/29] fix #22480 - Regression in DMD v2.112, betterC can not work with CTFE associative array any more don't abort if arrays are used with betterC, just skip code generation of the function as with other allocations don't require TypeInfo in array lowerings if compiling without it --- compiler/src/dmd/expressionsem.d | 7 +++++-- compiler/test/compilable/imports/test22480b.d | 13 +++++++++++++ compiler/test/compilable/test22480.d | 9 +++++++++ compiler/test/compilable/test24295.d | 11 +++++++++++ compiler/test/fail_compilation/test24295.d | 13 ------------- druntime/src/core/internal/array/capacity.d | 5 ++++- druntime/src/core/internal/array/construction.d | 5 ++++- druntime/src/core/internal/array/utils.d | 5 ++++- druntime/src/core/lifetime.d | 10 ++++++++-- 9 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 compiler/test/compilable/imports/test22480b.d create mode 100644 compiler/test/compilable/test22480.d create mode 100644 compiler/test/compilable/test24295.d delete mode 100644 compiler/test/fail_compilation/test24295.d diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 4516d220171a..4700a46ee7c8 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -6109,8 +6109,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()) 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/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/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/druntime/src/core/internal/array/capacity.d b/druntime/src/core/internal/array/capacity.d index 833918664d95..8ab8958e777a 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 9eae8f092b2d..25147a222cfb 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/lifetime.d b/druntime/src/core/lifetime.d index deb81c3ee2bd..85d9bfbcd671 100644 --- a/druntime/src/core/lifetime.d +++ b/druntime/src/core/lifetime.d @@ -2760,7 +2760,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); } @@ -2832,7 +2835,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)); From e9edba92c794d628877cf0ec69bad0af7606139f Mon Sep 17 00:00:00 2001 From: Rainer Date: Thu, 5 Feb 2026 09:37:20 +0100 Subject: [PATCH 11/29] fix #22510 - dup of inout aa broke with 2.112 handle inout in aa.dup, create mutable AA as much as possible --- compiler/test/fail_compilation/fail19898a.d | 2 +- compiler/test/fail_compilation/fail19898b.d | 4 +-- compiler/test/fail_compilation/test17977.d | 2 +- compiler/test/fail_compilation/test9150.d | 2 +- druntime/src/object.d | 18 +++++++++-- druntime/test/aa/src/test_aa.d | 35 +++++++++++++++++++++ 6 files changed, 55 insertions(+), 8 deletions(-) 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/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/druntime/src/object.d b/druntime/src/object.d index f1f54bf283c5..3097e6b658f8 100644 --- a/druntime/src/object.d +++ b/druntime/src/object.d @@ -3065,11 +3065,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 b870cdb5686a..518d63e6508a 100644 --- a/druntime/test/aa/src/test_aa.d +++ b/druntime/test/aa/src/test_aa.d @@ -1021,3 +1021,38 @@ void testAliasThis() s.remove(1); assert(S.numCopies == 0); } + +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])); +} From e5ca2f16810be4f5aaf7503f0d46ee33bc3794bb Mon Sep 17 00:00:00 2001 From: Rainer Date: Fri, 6 Feb 2026 12:00:29 +0100 Subject: [PATCH 12/29] fix #21976: [REG 2.112.0] Array comparison performance regression restore going through __equals lowering for all arrays but strings --- compiler/src/dmd/expressionsem.d | 10 ++++++++++ compiler/test/fail_compilation/verifyhookexist.d | 11 ++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 4700a46ee7c8..13cef20818ff 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -14050,6 +14050,16 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor Type t1n = t1.nextOf().toBasetype(); Type t2n = t2.nextOf().toBasetype(); + if (t1.ty == Tarray && t2.ty == Tarray) + // the dmd backend generates 'repe cmpsb' instead of a call to memcmp, + // which has (very) bad performance on a lot of processors. So restore the + // dmd 2.111 condition of always lowering to __equals for arrays. + // Though not affected, LDC and GDC are likely to inline and optimize + // whatever version is selected here, so do this unconditionally. + // See https://github.com/dlang/dmd/issues/21976 + if (!t1n.ty.isSomeChar) // likely to be short + return false; + if (t1n.size() != t2n.size()) return false; diff --git a/compiler/test/fail_compilation/verifyhookexist.d b/compiler/test/fail_compilation/verifyhookexist.d index a05641ebd51c..1ddd3e5b53e0 100644 --- a/compiler/test/fail_compilation/verifyhookexist.d +++ b/compiler/test/fail_compilation/verifyhookexist.d @@ -8,11 +8,12 @@ EXTRA_SOURCES: extra-files/minimal/object.d /* TEST_OUTPUT: --- -fail_compilation/verifyhookexist.d(21): Error: `object.__ArrayCast` not found. The current runtime does not support casting array of structs, or the runtime is corrupt. -fail_compilation/verifyhookexist.d(28): Error: `object.__cmp` not found. The current runtime does not support comparing arrays, or the runtime is corrupt. -fail_compilation/verifyhookexist.d(32): Error: `object._d_assert_fail` not found. The current runtime does not support generating assert messages, or the runtime is corrupt. -fail_compilation/verifyhookexist.d(35): Error: `object.__switch` not found. The current runtime does not support switch cases on strings, or the runtime is corrupt. -fail_compilation/verifyhookexist.d(40): Error: `object.__switch_error` not found. The current runtime does not support generating assert messages, or the runtime is corrupt. +fail_compilation/verifyhookexist.d(22): Error: `object.__ArrayCast` not found. The current runtime does not support casting array of structs, or the runtime is corrupt. +fail_compilation\verifyhookexist.d(28): Error: `object.__equals` not found. The current runtime does not support equal checks on arrays, or the runtime is corrupt. +fail_compilation/verifyhookexist.d(29): Error: `object.__cmp` not found. The current runtime does not support comparing arrays, or the runtime is corrupt. +fail_compilation/verifyhookexist.d(33): Error: `object._d_assert_fail` not found. The current runtime does not support generating assert messages, or the runtime is corrupt. +fail_compilation/verifyhookexist.d(36): Error: `object.__switch` not found. The current runtime does not support switch cases on strings, or the runtime is corrupt. +fail_compilation/verifyhookexist.d(41): Error: `object.__switch_error` not found. The current runtime does not support generating assert messages, or the runtime is corrupt. --- */ From 0226084fa1f2f693e1e27d32f7859669529445ef Mon Sep 17 00:00:00 2001 From: Rainer Schuetze Date: Sat, 7 Feb 2026 01:24:43 +0100 Subject: [PATCH 13/29] Revert "fix #21976: [REG 2.112.0] Array comparison performance regression" (#22529) This reverts commit e5ca2f16810be4f5aaf7503f0d46ee33bc3794bb. --- compiler/src/dmd/expressionsem.d | 10 ---------- compiler/test/fail_compilation/verifyhookexist.d | 11 +++++------ 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 13cef20818ff..4700a46ee7c8 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -14050,16 +14050,6 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor Type t1n = t1.nextOf().toBasetype(); Type t2n = t2.nextOf().toBasetype(); - if (t1.ty == Tarray && t2.ty == Tarray) - // the dmd backend generates 'repe cmpsb' instead of a call to memcmp, - // which has (very) bad performance on a lot of processors. So restore the - // dmd 2.111 condition of always lowering to __equals for arrays. - // Though not affected, LDC and GDC are likely to inline and optimize - // whatever version is selected here, so do this unconditionally. - // See https://github.com/dlang/dmd/issues/21976 - if (!t1n.ty.isSomeChar) // likely to be short - return false; - if (t1n.size() != t2n.size()) return false; diff --git a/compiler/test/fail_compilation/verifyhookexist.d b/compiler/test/fail_compilation/verifyhookexist.d index 1ddd3e5b53e0..a05641ebd51c 100644 --- a/compiler/test/fail_compilation/verifyhookexist.d +++ b/compiler/test/fail_compilation/verifyhookexist.d @@ -8,12 +8,11 @@ EXTRA_SOURCES: extra-files/minimal/object.d /* TEST_OUTPUT: --- -fail_compilation/verifyhookexist.d(22): Error: `object.__ArrayCast` not found. The current runtime does not support casting array of structs, or the runtime is corrupt. -fail_compilation\verifyhookexist.d(28): Error: `object.__equals` not found. The current runtime does not support equal checks on arrays, or the runtime is corrupt. -fail_compilation/verifyhookexist.d(29): Error: `object.__cmp` not found. The current runtime does not support comparing arrays, or the runtime is corrupt. -fail_compilation/verifyhookexist.d(33): Error: `object._d_assert_fail` not found. The current runtime does not support generating assert messages, or the runtime is corrupt. -fail_compilation/verifyhookexist.d(36): Error: `object.__switch` not found. The current runtime does not support switch cases on strings, or the runtime is corrupt. -fail_compilation/verifyhookexist.d(41): Error: `object.__switch_error` not found. The current runtime does not support generating assert messages, or the runtime is corrupt. +fail_compilation/verifyhookexist.d(21): Error: `object.__ArrayCast` not found. The current runtime does not support casting array of structs, or the runtime is corrupt. +fail_compilation/verifyhookexist.d(28): Error: `object.__cmp` not found. The current runtime does not support comparing arrays, or the runtime is corrupt. +fail_compilation/verifyhookexist.d(32): Error: `object._d_assert_fail` not found. The current runtime does not support generating assert messages, or the runtime is corrupt. +fail_compilation/verifyhookexist.d(35): Error: `object.__switch` not found. The current runtime does not support switch cases on strings, or the runtime is corrupt. +fail_compilation/verifyhookexist.d(40): Error: `object.__switch_error` not found. The current runtime does not support generating assert messages, or the runtime is corrupt. --- */ From b01f9a638ce9b672c632bbe21aceda0fce6e46be Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Mon, 9 Feb 2026 23:54:35 +0100 Subject: [PATCH 14/29] Fix #22544 - Cannot index AA in `with` block (#22547) --- compiler/src/dmd/expressionsem.d | 4 ++-- compiler/test/compilable/test22544.d | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 compiler/test/compilable/test22544.d diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 4700a46ee7c8..105d43a4125d 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -18970,7 +18970,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) { @@ -19027,7 +19027,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/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(); +} From 883c2f63614fe5dfe1ea4a6777956a903e3c18ca Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Wed, 11 Feb 2026 03:50:51 +0100 Subject: [PATCH 15/29] buildAAIndexRValueX(): Avoid IdentifierExp, prefer direct VarExp (#22555) --- compiler/src/dmd/expressionsem.d | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 105d43a4125d..84e7819d1d15 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -18836,8 +18836,8 @@ private Expression buildAAIndexRValueX(Type t, Expression eaa, Expression ekey, { // __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")); @@ -18847,9 +18847,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; } From 76c59d8f87e41e777c4b07e4d2c902dfb4e88903 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Fri, 13 Feb 2026 01:01:26 +0100 Subject: [PATCH 16/29] [stable] Fix #22543 - Cannot index AA literal in global-variable initializer (#22552) --- compiler/src/dmd/expressionsem.d | 2 +- compiler/test/compilable/test22543.d | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 compiler/test/compilable/test22543.d diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 84e7819d1d15..5181fe7644ce 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -18832,7 +18832,7 @@ 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); 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"]; +} From 3666dcf2016cb36951100f1ceb10a8e663c1ca94 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Sat, 14 Feb 2026 22:33:00 +0100 Subject: [PATCH 17/29] Fix #22501 - GC allocation for CTFE-available static array (#22571) Optimize the operands for trivially-memcmp-able array comparisons too (as done for non-memcmp-able arrays), so that e.g. slice-expressions of array literals are promoted to array literals. --- compiler/src/dmd/expressionsem.d | 10 ++++++---- compiler/test/compilable/test22501.d | 12 ++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 compiler/test/compilable/test22501.d diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 5181fe7644ce..5fea4e408b8b 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -14203,12 +14203,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; 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]; + } +} From f7bf401b59eb2bc0611748d5286c68d8162803dd Mon Sep 17 00:00:00 2001 From: Rainer Schuetze Date: Sat, 1 Nov 2025 12:34:30 +0100 Subject: [PATCH 18/29] avoid spurious Windows CI test failure in druntime/test/shared (#22052) tests dllgc and dynamiccast compile both executable and shared library from the same source file, but the DLL with -version=DLL. Both targets have the same intermediate object file which can cause both build and runtime errors when building concurrently. Solution: generate object files for DLLs to a different directory --- druntime/test/shared/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/druntime/test/shared/Makefile b/druntime/test/shared/Makefile index 4c359b09a0aa..f56de4a2f934 100644 --- a/druntime/test/shared/Makefile +++ b/druntime/test/shared/Makefile @@ -33,7 +33,7 @@ PATH := $(dir $(DRUNTIMESO));$(PATH) endif $(ROOT)/dllgc.done: $(ROOT)/dllgc$(DOTDLL) -$(ROOT)/dllgc$(DOTDLL): extra_dflags += -version=DLL +$(ROOT)/dllgc$(DOTDLL): extra_dflags += -version=DLL -od=$(ROOT)/dll endif # Windows $(ROOT)/dynamiccast.done: $(ROOT)/%.done: $(ROOT)/%$(DOTEXE) $(ROOT)/%$(DOTDLL) @@ -44,7 +44,7 @@ $(ROOT)/dynamiccast.done: $(ROOT)/%.done: $(ROOT)/%$(DOTEXE) $(ROOT)/%$(DOTDLL) test -f $(ROOT)/dynamiccast_endmain @touch $@ $(ROOT)/dynamiccast$(DOTEXE): private extra_ldlibs.d += $(LINKDL) -$(ROOT)/dynamiccast$(DOTDLL): private extra_dflags += -version=DLL +$(ROOT)/dynamiccast$(DOTDLL): private extra_dflags += -version=DLL -od=$(ROOT)/dll # Avoid a race condition that I sometimes hit with make -j8. # Maybe temporary file collisions when invoking the linker? From 69a04d33abd700aa5c8f2eabf3ec784a9a766022 Mon Sep 17 00:00:00 2001 From: limepoutine Date: Tue, 2 Dec 2025 18:35:49 +0800 Subject: [PATCH 19/29] Serialize compilation of dllgc test (#22162) --- druntime/test/common.mak | 2 +- druntime/test/shared/Makefile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/druntime/test/common.mak b/druntime/test/common.mak index 406b2fd5668a..085593c457be 100644 --- a/druntime/test/common.mak +++ b/druntime/test/common.mak @@ -157,7 +157,7 @@ TARGET_ARCH.d = $(model_flag) ########## Other common code ########## -.PHONY: all cleam +.PHONY: all clean all: $(TESTS:%=$(OBJDIR)/%.done) $(OBJDIR)/%.done: $(OBJDIR)/%$(DOTEXE) diff --git a/druntime/test/shared/Makefile b/druntime/test/shared/Makefile index f56de4a2f934..8553f12c4ba3 100644 --- a/druntime/test/shared/Makefile +++ b/druntime/test/shared/Makefile @@ -32,8 +32,8 @@ extra_dflags += -version=SharedRuntime PATH := $(dir $(DRUNTIMESO));$(PATH) endif -$(ROOT)/dllgc.done: $(ROOT)/dllgc$(DOTDLL) -$(ROOT)/dllgc$(DOTDLL): extra_dflags += -version=DLL -od=$(ROOT)/dll +$(ROOT)/dllgc$(DOTEXE): $(ROOT)/dllgc$(DOTDLL) +$(ROOT)/dllgc$(DOTDLL): private extra_dflags += -version=DLL -od=$(ROOT)/dll endif # Windows $(ROOT)/dynamiccast.done: $(ROOT)/%.done: $(ROOT)/%$(DOTEXE) $(ROOT)/%$(DOTDLL) From f2c0a8c795e5d445d1d794e8394ba3e378a7caa9 Mon Sep 17 00:00:00 2001 From: "Richard (Rikki) Andrew Cattermole" Date: Fri, 20 Feb 2026 13:48:00 +1300 Subject: [PATCH 20/29] Fix issue #22535 - [REG 2.112] Unicode symbols inside q (#22539) --- compiler/src/dmd/common/charactertables.d | 7 +++++++ compiler/src/dmd/frontend.h | 2 ++ compiler/src/dmd/lexer.d | 24 +++++++++++++---------- compiler/test/runnable/lexer.d | 12 ++++++++++++ 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/compiler/src/dmd/common/charactertables.d b/compiler/src/dmd/common/charactertables.d index 7df5234904c3..78b740e857e7 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/frontend.h b/compiler/src/dmd/frontend.h index b697cdacf65e..9a893eeb55c5 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -8193,6 +8193,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/lexer.d b/compiler/src/dmd/lexer.d index 36a4c83b7ec5..c83a13213261 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); } @@ -2398,7 +2402,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` @@ -2471,7 +2475,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/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); } /*********************************************************/ From 26895c7cae186c4fe3826e7f6a92e646c7207505 Mon Sep 17 00:00:00 2001 From: Martin Kinkelin Date: Fri, 20 Feb 2026 07:23:57 +0100 Subject: [PATCH 21/29] Fix #22594 - TypeInfo_Class.m_flags wrong wrt. noPointers flag when the only pointers come from tuple members (#22595) --- compiler/src/dmd/glue/toobj.d | 14 +++++--------- compiler/test/runnable/test22594.d | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 compiler/test/runnable/test22594.d diff --git a/compiler/src/dmd/glue/toobj.d b/compiler/src/dmd/glue/toobj.d index 2e31c4f89ac6..275e7dc45d2e 100644 --- a/compiler/src/dmd/glue/toobj.d +++ b/compiler/src/dmd/glue/toobj.d @@ -1219,17 +1219,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/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)); +} From 75e04f49fd78ac220f03ed6af40e7233bf5259da Mon Sep 17 00:00:00 2001 From: Dennis Date: Fri, 20 Feb 2026 18:52:40 +0100 Subject: [PATCH 22/29] Fix LONG instead of LRESULT return in winuser.d (#22583) Co-authored-by: Dennis Korpel --- druntime/src/core/sys/windows/winuser.d | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/druntime/src/core/sys/windows/winuser.d b/druntime/src/core/sys/windows/winuser.d index a7917b774f4c..4a92be8ef862 100644 --- a/druntime/src/core/sys/windows/winuser.d +++ b/druntime/src/core/sys/windows/winuser.d @@ -3720,8 +3720,8 @@ nothrow @nogc { alias GetWindow GetNextWindow; 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); From df6465e43d1dcbcf980864793f4689052b515f1d Mon Sep 17 00:00:00 2001 From: Dennis Date: Tue, 24 Feb 2026 14:17:21 +0100 Subject: [PATCH 23/29] Fix #22625 - undefined identifier 'addr' in core.sys.posix.netinet.in_.IN6* (#22627) Co-authored-by: Dennis Korpel --- druntime/src/core/sys/posix/netinet/in_.d | 48 +++++++++++------------ 1 file changed, 24 insertions(+), 24 deletions(-) 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; From 546af4a04264d146e8b0d15649c78dac5f3c405d Mon Sep 17 00:00:00 2001 From: Divyansh Sharma <140371139+divyansharma001@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:38:54 +0530 Subject: [PATCH 24/29] Fix Issue #22567 - alias this and nested AAs cause compiler SIGILL (#22622) When an alias this redirects a nested AA write through a struct field, the inner AA IndexExp was left with modifiable=true and never lowered to an rvalue read, causing a SIGILL in the IR generator. Add revertModifiableAAIndexReads() to handle this case: it recurses through DotVarExp wrappers produced by alias this and lowers any modifiable AA IndexExp to a proper rvalue read before the side-effect extraction in rewriteAAIndexAssign(). --- compiler/src/dmd/expressionsem.d | 21 +++++++++++++++++++++ compiler/test/runnable/testaa3.d | 18 ++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 5fea4e408b8b..15c88e98ebe0 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -18884,6 +18884,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) { @@ -18934,6 +18954,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; diff --git a/compiler/test/runnable/testaa3.d b/compiler/test/runnable/testaa3.d index 53a0e8bb86cc..e94273b7da7a 100644 --- a/compiler/test/runnable/testaa3.d +++ b/compiler/test/runnable/testaa3.d @@ -429,6 +429,23 @@ void testShared() /***************************************************/ +// 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() { assert(testLiteral()); @@ -456,4 +473,5 @@ void main() test21066(); test22354(); testShared(); + test22567(); } From 40555fce3801009331a2092f41888e79e24c1d73 Mon Sep 17 00:00:00 2001 From: Rainer Date: Thu, 26 Feb 2026 11:34:10 +0100 Subject: [PATCH 25/29] fix issue #22556 - shared AAs with RefCounted values don't work even after removing shared allow AA.clear with shared values for backward compatibility --- compiler/test/runnable/testaa3.d | 17 +++++++++++++++++ druntime/src/object.d | 14 ++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/compiler/test/runnable/testaa3.d b/compiler/test/runnable/testaa3.d index e94273b7da7a..1ba165b55e6c 100644 --- a/compiler/test/runnable/testaa3.d +++ b/compiler/test/runnable/testaa3.d @@ -427,6 +427,22 @@ void testShared() 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 @@ -474,4 +490,5 @@ void main() test22354(); testShared(); test22567(); + test22556(); } diff --git a/druntime/src/object.d b/druntime/src/object.d index 3097e6b658f8..bf450fc706aa 100644 --- a/druntime/src/object.d +++ b/druntime/src/object.d @@ -2985,16 +2985,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 { From 6da2d9e64ded77d220f92c9ff02a022bd88b98b8 Mon Sep 17 00:00:00 2001 From: Divyansh Sharma <140371139+divyansharma001@users.noreply.github.com> Date: Mon, 2 Mar 2026 11:50:52 +0530 Subject: [PATCH 26/29] =?UTF-8?q?Fix=20Issue=2022614=20-=20[Windows]=20tmp?= =?UTF-8?q?nam()=20in=20runPreprocessor=20generates=20roo=E2=80=A6=20(#226?= =?UTF-8?q?15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix Issue 22614 - [Windows] tmpnam() in runPreprocessor generates root-drive paths, fails with permission denied for non-admin users --- compiler/src/dmd/link.d | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/compiler/src/dmd/link.d b/compiler/src/dmd/link.d index b3ad7cc3dabd..1429efd1d1b8 100644 --- a/compiler/src/dmd/link.d +++ b/compiler/src/dmd/link.d @@ -958,9 +958,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; From 6bb9fd7d3b32539ffe1c6d4add6988a4c72460c6 Mon Sep 17 00:00:00 2001 From: Iain Buclaw Date: Tue, 3 Mar 2026 06:16:56 +0000 Subject: [PATCH 27/29] bump VERSION to v2.112.1-rc.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index bb14ba5b599c..13947446d4b6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.112.0 +v2.112.1-rc.1 From 623d9c4ac33ef3798fe5ea884702ecaa4c216d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Wed, 4 Mar 2026 00:15:26 +0100 Subject: [PATCH 28/29] Fix the fstatat declaration on macOS/x64. (#22689) The x64 version of fstatat() needs to be mangled with the "INODE64" suffix in order to match the stat_t layout defined by Druntime. See https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/bsd/sys/stat.h#L573 --- druntime/src/core/sys/posix/sys/stat.d | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/druntime/src/core/sys/posix/sys/stat.d b/druntime/src/core/sys/posix/sys/stat.d index 762d6fcc76ed..9c1d873c7d6e 100644 --- a/druntime/src/core/sys/posix/sys/stat.d +++ b/druntime/src/core/sys/posix/sys/stat.d @@ -2008,12 +2008,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*); } @@ -2021,11 +2023,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); From 7af5ba36cb9b03e5c54e784226a69b2eccf4f394 Mon Sep 17 00:00:00 2001 From: Iain Buclaw Date: Fri, 20 Mar 2026 09:17:08 +0000 Subject: [PATCH 29/29] bump VERSION to v2.112.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 13947446d4b6..767e5d2d3b30 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.112.1-rc.1 +v2.112.1