From c8667f2c73402f7dd60fd55e3074ab7c96e99183 Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Tue, 1 Jul 2025 15:50:17 +0300 Subject: [PATCH 1/7] add ddoc for some func --- compiler/src/dmd/semantic3.d | 135 +++++++++++++++++++++++++++++++++-- 1 file changed, 130 insertions(+), 5 deletions(-) diff --git a/compiler/src/dmd/semantic3.d b/compiler/src/dmd/semantic3.d index 303b32874c31..b8497c11727f 100644 --- a/compiler/src/dmd/semantic3.d +++ b/compiler/src/dmd/semantic3.d @@ -1630,6 +1630,40 @@ private extern(C++) final class Semantic3Visitor : Visitor } } +/** + * Helper struct used exclusively inside the *semantic-3* stage + * for functions. + * + * `FuncDeclSem3` is an ultra-light wrapper that groups the data + * needed to perform additional checks on a single + * $(D FuncDeclaration) after its body has been analysed by + * $(REF Semantic3Visitor, dmd.semantic3). + * + * The motivation for factoring this logic out of + * `Semantic3Visitor.visit(FuncDeclaration)` is to keep that + * (already very large) method readable while still making it + * easy to add further *semantic-3* helpers in the future. + * + * Members: + * funcdecl ― the function currently being processed; + * never `null`. + * + * sc ― the scope in which the analysis must be + * performed (includes flags such as + * `inContract`, `linkage`, etc.). + * + * Methods: + * checkInContractOverrides ― verifies that if the current + * function has an *in* contract, every function it + * overrides also declares an *in* contract. + * Emits an error and stops at the first offender. + * + * Usage: + * --- + * auto helper = FuncDeclSem3(fd, sc); + * helper.checkInContractOverrides(); + * --- + */ private struct FuncDeclSem3 { // The FuncDeclaration subject to Semantic analysis @@ -1663,6 +1697,57 @@ private struct FuncDeclSem3 } } +/** + * Perform *semantic-3* analysis on all special members of a + * `struct` that are required before the backend can emit that + * struct’s `TypeInfo`. + * + * The routine is invoked when: + * $(OL + * $(LI the struct itself is being processed in the main + * `Semantic3Visitor`, or) + * $(LI the compiler needs `TypeInfo` for a struct whose semantic + * pass has not finished yet (e.g. during CTFE).) + * ) + * + * For each potential special member it checks whether + * `semanticRun < PASS.semantic3done` **and** a saved `_scope` + * still exists; if so it runs `member.semantic3(scope)` *under + * gagging* (`global.startGagging`) so that any errors are suppressed, + * then: + * + * $(UL + * $(LI on failure, replaces the symbol with its error stub + * (`xerreq`, `xerrcmp`, …) so later stages never see a + * half-analysed definition.) + * $(LI on success, leaves the analysed function in place.) + * ) + * + * Members handled: + * $(UL + * $(LI equality operator `sd.xeq`) + * $(LI three-way compare `sd.xcmp`) + * $(LI `toString` (looked up via `search_toString`)) + * $(LI `toHash` `sd.xhash`) + * $(LI postblit `sd.postblit`) + * $(LI destructor `sd.dtor`) + * ) + * + * Params: + * sd = Struct whose special members may require a late + * semantic-3 pass. + * + * Returns: Nothing. All work is done for side-effects on `sd`. + * + * Notes: + * $(UL + * $(LI The function is purposely tolerant: failure of one member + * does not prevent others from being analysed.) + * $(LI Because analysis is gagged, no diagnostics reach the user + * here; if the struct’s semantic pass is replayed later + * outside the gag context the real errors will surface.) + * ) + */ void semanticTypeInfoMembers(StructDeclaration sd) { if (sd.xeq && @@ -1715,13 +1800,53 @@ void semanticTypeInfoMembers(StructDeclaration sd) } } -/*********************************************** - * Check that the function contains any closure. - * If it's @nogc, report suitable errors. - * This is mostly consistent with FuncDeclaration::needsClosure(). +/** + * Determine whether the given function will need to allocate a _closure_ and + * verify that such an allocation is allowed under the current compilation + * settings. + * + * The procedure is three-step: + * + * $(OL + * $(LI **Early exit:** if `fd.needsClosure` is `false`, no closure is needed + * and the routine returns `false`.) + * + * $(LI **Attempt to record GC usage:** `fd.setGC` is called to mark the function + * as performing a GC allocation. Emitting a closure is *illegal* and + * triggers an error in either of these cases: + * + * $(UL + * $(LI the function itself is annotated `@nogc`, or) + * $(LI the compilation is running with the GC disabled + * (e.g. `-betterC`, so `global.params.useGC == false`).) + * ) + * When such an error is detected, the routine ultimately returns `true`.) + * + * $(LI **Otherwise** the allocation is permitted. The helper records the + * fact via `fd.printGCUsage` for later backend stages and returns + * `false`.) + * ) + * + * Whenever an error is emitted, every nested function that actually closes + * over a variable is listed in a supplemental diagnostic, together with the + * location of the captured variable’s declaration. (This extra walk is + * skipped when the compiler is gagged.) + * + * Params: + * fd = function to analyse. * * Returns: - * true if any errors occur. + * `true` if at least one diagnostic was issued (i.e. closure allocation + * is disallowed under `@nogc` or `-betterC`); + * `false` in all other cases (either no closure needed, or allocation + * is allowed). + * + * See_Also: + * $(UL + * $(LI `FuncDeclaration.needsClosure`) + * $(LI `FuncDeclaration.setGC`) + * $(LI `FuncDeclaration.printGCUsage`) + * ) */ extern (D) bool checkClosure(FuncDeclaration fd) { From c051b27b5ad9c4e8da5879b17505d66141dad114 Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Tue, 1 Jul 2025 16:17:53 +0300 Subject: [PATCH 2/7] rewrite --- compiler/src/dmd/semantic3.d | 63 ++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/compiler/src/dmd/semantic3.d b/compiler/src/dmd/semantic3.d index b8497c11727f..2a5756dce711 100644 --- a/compiler/src/dmd/semantic3.d +++ b/compiler/src/dmd/semantic3.d @@ -1698,55 +1698,48 @@ private struct FuncDeclSem3 } /** - * Perform *semantic-3* analysis on all special members of a - * `struct` that are required before the backend can emit that + * Runs a *semantic-3* pass on any **special members** of a `struct` + * that must be fully analysed before the backend can emit the * struct’s `TypeInfo`. * - * The routine is invoked when: + * The routine is invoked in two situations: * $(OL - * $(LI the struct itself is being processed in the main - * `Semantic3Visitor`, or) - * $(LI the compiler needs `TypeInfo` for a struct whose semantic - * pass has not finished yet (e.g. during CTFE).) + * $(LI during the normal `Semantic3Visitor` walk over the struct, or) + * $(LI on-demand when CTFE needs a `TypeInfo` for a struct whose own + * semantic pass is not finished yet.) * ) * - * For each potential special member it checks whether - * `semanticRun < PASS.semantic3done` **and** a saved `_scope` - * still exists; if so it runs `member.semantic3(scope)` *under - * gagging* (`global.startGagging`) so that any errors are suppressed, - * then: + * What it does, member by member: * * $(UL - * $(LI on failure, replaces the symbol with its error stub - * (`xerreq`, `xerrcmp`, …) so later stages never see a - * half-analysed definition.) - * $(LI on success, leaves the analysed function in place.) + * $(LI **`opEquals` / `opCmp`** (`sd.xeq`, `sd.xcmp`) – + * if the member is still pending (`semanticRun < PASS.semantic3done`) + * and has a saved `_scope`, it is analysed **under + * gagging** (`global.startGagging`). + * If an error occurs, the member is replaced with the matching + * _error stub_ (`xerreq` or `xerrcmp`) so that later stages + * never see a half-analysed symbol.) + * + * $(LI **`toString`**, **`toHash`**, **postblit**, **destructor** – + * also re-entered when still pending, but analysed *without* + * gagging. Any error simply propagates; the original symbol + * remains in place (no stub substitution).) * ) * - * Members handled: + * Notes: * $(UL - * $(LI equality operator `sd.xeq`) - * $(LI three-way compare `sd.xcmp`) - * $(LI `toString` (looked up via `search_toString`)) - * $(LI `toHash` `sd.xhash`) - * $(LI postblit `sd.postblit`) - * $(LI destructor `sd.dtor`) + * $(LI Each member is handled independently — a failure in one does + * not prevent the others from being processed.) + * $(LI Because gagging is used *only* for `opEquals`/`opCmp`, errors + * in the other members are reported immediately, which helps + * surface problems earlier during CTFE.) * ) * * Params: - * sd = Struct whose special members may require a late - * semantic-3 pass. - * - * Returns: Nothing. All work is done for side-effects on `sd`. + * sd = struct whose pending special members (if any) will get + * their late *semantic-3* analysis. * - * Notes: - * $(UL - * $(LI The function is purposely tolerant: failure of one member - * does not prevent others from being analysed.) - * $(LI Because analysis is gagged, no diagnostics reach the user - * here; if the struct’s semantic pass is replayed later - * outside the gag context the real errors will surface.) - * ) + * Returns: None; all work is performed for its side-effects on `sd`. */ void semanticTypeInfoMembers(StructDeclaration sd) { From 47e504a66b9cf128fd4b68350737493d555ced30 Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Wed, 2 Jul 2025 14:58:17 +0300 Subject: [PATCH 3/7] fix after review --- compiler/src/dmd/semantic3.d | 85 ++++-------------------------------- 1 file changed, 9 insertions(+), 76 deletions(-) diff --git a/compiler/src/dmd/semantic3.d b/compiler/src/dmd/semantic3.d index 2a5756dce711..65416a114603 100644 --- a/compiler/src/dmd/semantic3.d +++ b/compiler/src/dmd/semantic3.d @@ -1,5 +1,7 @@ /** - * Performs the semantic3 stage, which deals with function bodies. + * Performs the semantic3 stage of semantic analysis, which finalizes + * function bodies and late semantic checks for templates, mixins, + * aggregates, and special members. * * Copyright: Copyright (C) 1999-2025 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) @@ -1630,40 +1632,8 @@ private extern(C++) final class Semantic3Visitor : Visitor } } -/** - * Helper struct used exclusively inside the *semantic-3* stage - * for functions. - * - * `FuncDeclSem3` is an ultra-light wrapper that groups the data - * needed to perform additional checks on a single - * $(D FuncDeclaration) after its body has been analysed by - * $(REF Semantic3Visitor, dmd.semantic3). - * - * The motivation for factoring this logic out of - * `Semantic3Visitor.visit(FuncDeclaration)` is to keep that - * (already very large) method readable while still making it - * easy to add further *semantic-3* helpers in the future. - * - * Members: - * funcdecl ― the function currently being processed; - * never `null`. - * - * sc ― the scope in which the analysis must be - * performed (includes flags such as - * `inContract`, `linkage`, etc.). - * - * Methods: - * checkInContractOverrides ― verifies that if the current - * function has an *in* contract, every function it - * overrides also declares an *in* contract. - * Emits an error and stops at the first offender. - * - * Usage: - * --- - * auto helper = FuncDeclSem3(fd, sc); - * helper.checkInContractOverrides(); - * --- - */ +/// Helper for semantic3 analysis of functions. +/// This struct is part of a WIP refactoring to simplify large `visit(FuncDeclaration)` logic. private struct FuncDeclSem3 { // The FuncDeclaration subject to Semantic analysis @@ -1698,48 +1668,11 @@ private struct FuncDeclSem3 } /** - * Runs a *semantic-3* pass on any **special members** of a `struct` - * that must be fully analysed before the backend can emit the - * struct’s `TypeInfo`. - * - * The routine is invoked in two situations: - * $(OL - * $(LI during the normal `Semantic3Visitor` walk over the struct, or) - * $(LI on-demand when CTFE needs a `TypeInfo` for a struct whose own - * semantic pass is not finished yet.) - * ) - * - * What it does, member by member: - * - * $(UL - * $(LI **`opEquals` / `opCmp`** (`sd.xeq`, `sd.xcmp`) – - * if the member is still pending (`semanticRun < PASS.semantic3done`) - * and has a saved `_scope`, it is analysed **under - * gagging** (`global.startGagging`). - * If an error occurs, the member is replaced with the matching - * _error stub_ (`xerreq` or `xerrcmp`) so that later stages - * never see a half-analysed symbol.) - * - * $(LI **`toString`**, **`toHash`**, **postblit**, **destructor** – - * also re-entered when still pending, but analysed *without* - * gagging. Any error simply propagates; the original symbol - * remains in place (no stub substitution).) - * ) - * - * Notes: - * $(UL - * $(LI Each member is handled independently — a failure in one does - * not prevent the others from being processed.) - * $(LI Because gagging is used *only* for `opEquals`/`opCmp`, errors - * in the other members are reported immediately, which helps - * surface problems earlier during CTFE.) - * ) - * - * Params: - * sd = struct whose pending special members (if any) will get - * their late *semantic-3* analysis. + * Ensures special members of a struct are fully analysed + * before the backend emits TypeInfo. * - * Returns: None; all work is performed for its side-effects on `sd`. + * Handles late semantic analysis for members like `opEquals`, `opCmp`, + * `toString`, `toHash`, postblit, and destructor. */ void semanticTypeInfoMembers(StructDeclaration sd) { From 9857a523b00879dc6d20a85c37721cadc0d91f7f Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Wed, 2 Jul 2025 15:13:20 +0300 Subject: [PATCH 4/7] fix after review --- compiler/src/dmd/semantic3.d | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/compiler/src/dmd/semantic3.d b/compiler/src/dmd/semantic3.d index 65416a114603..2c60a4205a3c 100644 --- a/compiler/src/dmd/semantic3.d +++ b/compiler/src/dmd/semantic3.d @@ -1731,28 +1731,6 @@ void semanticTypeInfoMembers(StructDeclaration sd) * verify that such an allocation is allowed under the current compilation * settings. * - * The procedure is three-step: - * - * $(OL - * $(LI **Early exit:** if `fd.needsClosure` is `false`, no closure is needed - * and the routine returns `false`.) - * - * $(LI **Attempt to record GC usage:** `fd.setGC` is called to mark the function - * as performing a GC allocation. Emitting a closure is *illegal* and - * triggers an error in either of these cases: - * - * $(UL - * $(LI the function itself is annotated `@nogc`, or) - * $(LI the compilation is running with the GC disabled - * (e.g. `-betterC`, so `global.params.useGC == false`).) - * ) - * When such an error is detected, the routine ultimately returns `true`.) - * - * $(LI **Otherwise** the allocation is permitted. The helper records the - * fact via `fd.printGCUsage` for later backend stages and returns - * `false`.) - * ) - * * Whenever an error is emitted, every nested function that actually closes * over a variable is listed in a supplemental diagnostic, together with the * location of the captured variable’s declaration. (This extra walk is From 3ae0fa0ae938c6a5233ce5b49f3c83b9f4b178e2 Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Wed, 2 Jul 2025 17:32:45 +0300 Subject: [PATCH 5/7] fix after review --- compiler/src/dmd/semantic3.d | 9 --------- 1 file changed, 9 deletions(-) diff --git a/compiler/src/dmd/semantic3.d b/compiler/src/dmd/semantic3.d index 2c60a4205a3c..b04696baf3e9 100644 --- a/compiler/src/dmd/semantic3.d +++ b/compiler/src/dmd/semantic3.d @@ -1736,15 +1736,6 @@ void semanticTypeInfoMembers(StructDeclaration sd) * location of the captured variable’s declaration. (This extra walk is * skipped when the compiler is gagged.) * - * Params: - * fd = function to analyse. - * - * Returns: - * `true` if at least one diagnostic was issued (i.e. closure allocation - * is disallowed under `@nogc` or `-betterC`); - * `false` in all other cases (either no closure needed, or allocation - * is allowed). - * * See_Also: * $(UL * $(LI `FuncDeclaration.needsClosure`) From 95a9d02ebbce349c9cc6894ddf2a6361b950d8bc Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Tue, 8 Jul 2025 18:51:07 +0300 Subject: [PATCH 6/7] closureless-splitpath --- compiler/src/dmd/main.d | 16 ++++++++++---- compiler/src/dmd/root/filename.d | 36 +++++++++++++++++++------------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/compiler/src/dmd/main.d b/compiler/src/dmd/main.d index 84497773df0e..7abb06c80eea 100644 --- a/compiler/src/dmd/main.d +++ b/compiler/src/dmd/main.d @@ -414,15 +414,23 @@ private int tryMain(size_t argc, const(char)** argv, out Param params) foreach (entry; imppath) { - int sink(const(char)* p) nothrow + struct SinkContext { - ImportPathInfo temp = entry; + ImportPathInfo* entry; + Array!ImportPathInfo* array; + } + + static int sink(const(char)* p, void* ctx) nothrow + { + auto c = cast(SinkContext*)ctx; + ImportPathInfo temp = *c.entry; temp.path = p; - array.push(temp); + c.array.push(temp); return 0; } - FileName.splitPath(&sink, entry.path); + SinkContext context = SinkContext(&entry, &array); + FileName.splitPath(&sink, entry.path, &context); FileName.appendSplitPath(entry.path, pathsOnlyArray); } diff --git a/compiler/src/dmd/root/filename.d b/compiler/src/dmd/root/filename.d index 5ad07750b3b0..c17c59641a89 100644 --- a/compiler/src/dmd/root/filename.d +++ b/compiler/src/dmd/root/filename.d @@ -471,14 +471,16 @@ nothrow: // Split a path and append the results to `array` extern (C++) static void appendSplitPath(const(char)* path, ref Strings array) { - int sink(const(char)* p) nothrow + static int sink(const(char)* p, void* ctx) nothrow { - array.push(p); + auto arr = cast(Strings*)ctx; + arr.push(p); return 0; } - splitPath(&sink, path); + splitPath(&sink, path, &array); } + /**** * Split path (such as that returned by `getenv("PATH")`) into pieces, each piece is mem.xmalloc'd * Handle double quotes and ~. @@ -487,7 +489,7 @@ nothrow: * sink = send the path pieces here, end when sink() returns !=0 * path = the path to split up. */ - static void splitPath(int delegate(const(char)*) nothrow sink, const(char)* path) + static void splitPath(int function(const(char)*, void*) nothrow sink, const(char)* path, void* ctx) { if (!path) return; @@ -561,8 +563,8 @@ nothrow: } if (buf.length) // if path is not empty { - if (sink(buf.extractChars())) - break; + if (sink(buf.extractChars(), ctx)) + break; } } while (c); } @@ -712,31 +714,35 @@ nothrow: extern (D) static const(char)[] searchPath(const char* path, const char[] name, bool cwd) { if (absolute(name)) - { return exists(name) ? name : null; - } if (cwd) - { if (exists(name)) return name; - } if (path && *path) { const(char)[] result; - int sink(const(char)* p) nothrow + struct SinkContext + { + const(char)[] name; + const(char)[]* resultSlot; + } + SinkContext ctx = { name, &result }; + + static int sink(const(char)* p, void* vctx) nothrow { - auto n = combine(p.toDString, name); + auto ctx = cast(SinkContext*)vctx; + auto n = combine(p.toDString, ctx.name); mem.xfree(cast(void*)p); if (exists(n)) { - result = n; - return 1; // done with splitPath() call + *ctx.resultSlot = n; + return 1; } return 0; } - splitPath(&sink, path); + splitPath(&sink, path, &ctx); return result; } return null; From 3d52846eb5982eed9281d1a008f8dc8444609e4f Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Wed, 9 Jul 2025 11:10:29 +0300 Subject: [PATCH 7/7] fix ddoc after review --- compiler/src/dmd/root/filename.d | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dmd/root/filename.d b/compiler/src/dmd/root/filename.d index c17c59641a89..6da2cd48612f 100644 --- a/compiler/src/dmd/root/filename.d +++ b/compiler/src/dmd/root/filename.d @@ -488,6 +488,7 @@ nothrow: * Params: * sink = send the path pieces here, end when sink() returns !=0 * path = the path to split up. + * ctx = pointer to context passed to sink */ static void splitPath(int function(const(char)*, void*) nothrow sink, const(char)* path, void* ctx) {