From c8667f2c73402f7dd60fd55e3074ab7c96e99183 Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Tue, 1 Jul 2025 15:50:17 +0300 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 668f726450eee7eba0d86455f406cf9693d6aa4c Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Mon, 14 Jul 2025 14:10:48 +0300 Subject: [PATCH 06/11] refactor/irstate-attrs --- compiler/src/dmd/toir.d | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/compiler/src/dmd/toir.d b/compiler/src/dmd/toir.d index 2e3aa5de4e5b..73404d9d435b 100644 --- a/compiler/src/dmd/toir.d +++ b/compiler/src/dmd/toir.d @@ -106,7 +106,9 @@ struct IRState this.Cfile = m.filetype == FileType.c; } - FuncDeclaration getFunc() @safe + @property + @safe pure nothrow @nogc + FuncDeclaration getFunc() { return symbol; } @@ -115,6 +117,9 @@ struct IRState * Returns: * true if do array bounds checking for the current function */ + @property + @safe pure nothrow @nogc + pragma(inline, true) bool arrayBoundsCheck() { if (m.filetype == FileType.c) @@ -127,12 +132,9 @@ struct IRState return true; case CHECKENABLE.safeonly: { - if (FuncDeclaration fd = getFunc()) - { - Type t = fd.type; - if (t.ty == Tfunction && (cast(TypeFunction)t).trust == TRUST.safe) - return true; - } + if (auto fd = getFunc()) + if (auto tf = fd.type.isTypeFunction()) + return tf.trust == TRUST.safe; return false; } case CHECKENABLE._default: From dccd3a6771a1219d54afef0cce97e2515d421289 Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Mon, 14 Jul 2025 15:05:06 +0300 Subject: [PATCH 07/11] refactoring --- compiler/src/dmd/toir.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dmd/toir.d b/compiler/src/dmd/toir.d index 73404d9d435b..fb20d77b121c 100644 --- a/compiler/src/dmd/toir.d +++ b/compiler/src/dmd/toir.d @@ -119,7 +119,7 @@ struct IRState */ @property @safe pure nothrow @nogc - pragma(inline, true) + pragma(inline) bool arrayBoundsCheck() { if (m.filetype == FileType.c) From 3a10958e6ce889ef723038240e8eab6d5eab2e29 Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Mon, 14 Jul 2025 18:42:56 +0300 Subject: [PATCH 08/11] add pragma(inline, true) --- compiler/src/dmd/toir.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dmd/toir.d b/compiler/src/dmd/toir.d index fb20d77b121c..73404d9d435b 100644 --- a/compiler/src/dmd/toir.d +++ b/compiler/src/dmd/toir.d @@ -119,7 +119,7 @@ struct IRState */ @property @safe pure nothrow @nogc - pragma(inline) + pragma(inline, true) bool arrayBoundsCheck() { if (m.filetype == FileType.c) From 3118f2f1b6b0ba23e775436fd27d2934c177a5fd Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Mon, 14 Jul 2025 18:48:58 +0300 Subject: [PATCH 09/11] change pragma(inline, true) -> pragma(inline, false) --- compiler/src/dmd/toir.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dmd/toir.d b/compiler/src/dmd/toir.d index 73404d9d435b..1017d1b136ca 100644 --- a/compiler/src/dmd/toir.d +++ b/compiler/src/dmd/toir.d @@ -119,7 +119,7 @@ struct IRState */ @property @safe pure nothrow @nogc - pragma(inline, true) + pragma(inline, false) bool arrayBoundsCheck() { if (m.filetype == FileType.c) From 3f72b6451b3734cdca2545e87f2654cdce3a54ac Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Tue, 15 Jul 2025 11:13:55 +0300 Subject: [PATCH 10/11] revent --- compiler/src/dmd/toir.d | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/compiler/src/dmd/toir.d b/compiler/src/dmd/toir.d index 1017d1b136ca..fff84ab6454b 100644 --- a/compiler/src/dmd/toir.d +++ b/compiler/src/dmd/toir.d @@ -106,9 +106,7 @@ struct IRState this.Cfile = m.filetype == FileType.c; } - @property - @safe pure nothrow @nogc - FuncDeclaration getFunc() + FuncDeclaration getFunc() @safe { return symbol; } @@ -117,9 +115,6 @@ struct IRState * Returns: * true if do array bounds checking for the current function */ - @property - @safe pure nothrow @nogc - pragma(inline, false) bool arrayBoundsCheck() { if (m.filetype == FileType.c) @@ -132,9 +127,12 @@ struct IRState return true; case CHECKENABLE.safeonly: { - if (auto fd = getFunc()) - if (auto tf = fd.type.isTypeFunction()) - return tf.trust == TRUST.safe; + if (FuncDeclaration fd = getFunc()) + { + Type t = fd.type; + if (t.ty == Tfunction && (cast(TypeFunction)t).trust == TRUST.safe) + return true; + } return false; } case CHECKENABLE._default: @@ -1209,4 +1207,4 @@ RET retStyle(TypeFunction tf, bool needsThis) { //printf("TypeFunction.retStyle() %s\n", toChars()); return target.isReturnOnStack(tf, needsThis) ? RET.stack : RET.regs; -} +} \ No newline at end of file From 1d319702c2843b0904d160b987eb2141672b05a0 Mon Sep 17 00:00:00 2001 From: Sergii Kuzko Date: Thu, 10 Jul 2025 18:49:28 +0300 Subject: [PATCH 11/11] Fix && refactoring closure issue in appendSplitPath by marking array as scope ref --- compiler/src/dmd/semantic3.d | 59 +++++++++++++++---- compiler/src/dmd/toir.d | 2 +- .../test/compilable/closure_class_no_dtor.d | 15 +++++ .../compilable/closure_local_struct_no_dtor.d | 15 +++++ .../compilable/closure_nested_func_no_dtor.d | 20 +++++++ compiler/test/compilable/closure_no_dtor.d | 15 +++++ .../test/compilable/closure_this_no_dtor.d | 19 ++++++ .../fail_compilation/closure_class_dtor.d | 22 +++++++ .../closure_local_struct_dtor.d | 23 ++++++++ .../closure_nested_func_dtor.d | 27 +++++++++ .../test/fail_compilation/closure_this_dtor.d | 26 ++++++++ .../test/fail_compilation/closure_with_dtor.d | 22 +++++++ 12 files changed, 253 insertions(+), 12 deletions(-) create mode 100644 compiler/test/compilable/closure_class_no_dtor.d create mode 100644 compiler/test/compilable/closure_local_struct_no_dtor.d create mode 100644 compiler/test/compilable/closure_nested_func_no_dtor.d create mode 100644 compiler/test/compilable/closure_no_dtor.d create mode 100644 compiler/test/compilable/closure_this_no_dtor.d create mode 100644 compiler/test/fail_compilation/closure_class_dtor.d create mode 100644 compiler/test/fail_compilation/closure_local_struct_dtor.d create mode 100644 compiler/test/fail_compilation/closure_nested_func_dtor.d create mode 100644 compiler/test/fail_compilation/closure_this_dtor.d create mode 100644 compiler/test/fail_compilation/closure_with_dtor.d diff --git a/compiler/src/dmd/semantic3.d b/compiler/src/dmd/semantic3.d index b1f4f338d389..d4b901977548 100644 --- a/compiler/src/dmd/semantic3.d +++ b/compiler/src/dmd/semantic3.d @@ -1729,10 +1729,12 @@ void semanticTypeInfoMembers(StructDeclaration sd) * verify that such an allocation is allowed under the current compilation * settings. * - * 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.) + * Emits errors if: + * - A captured variable has a destructor, which is prohibited. + * - Closure allocation violates `@nogc` or `-betterC`. + * + * Additionally, provides supplemental diagnostics listing nested functions + * that close over variables, unless the compiler is gagged. * * See_Also: * $(UL @@ -1747,6 +1749,33 @@ extern (D) bool checkClosure(FuncDeclaration fd) if (!fd.needsClosure()) return false; + // Preventing closure construction due to variables with destructor + foreach (v; fd.closureVars) + { + if (!v.type) + continue; + + if (auto ts = v.type.isTypeStruct()) + { + if (ts.sym && ts.sym.dtor) + { + .error(v.loc, "variable `%s` has scoped destruction, cannot build closure", v.toPrettyChars()); + fd.errors = true; + return true; + } + } + else if (auto tc = v.type.isTypeClass()) + { + if (tc.sym && tc.sym.dtor && (v.storage_class & STC.scope_)) + { + .error(v.loc, "scoped class variable `%s` has destructor, cannot build closure", v.toPrettyChars()); + fd.errors = true; + return true; + } + } + } + + // Checking compilation restrictions if (fd.setGC(fd.loc, "allocating a closure for `%s()`", fd)) { .error(fd.loc, "%s `%s` is `@nogc` yet allocates closure for `%s()` with the GC", fd.kind, fd.toPrettyChars(), fd.toChars()); @@ -1765,7 +1794,8 @@ extern (D) bool checkClosure(FuncDeclaration fd) return false; } - FuncDeclarations a; + // Additional diagnostics: who captures variables + FuncDeclarations reported; foreach (v; fd.closureVars) { foreach (f; v.nestedrefs) @@ -1778,18 +1808,25 @@ extern (D) bool checkClosure(FuncDeclaration fd) auto fx = s.isFuncDeclaration(); if (!fx) continue; + if (fx.isThis() || fx.tookAddressOf || checkEscapingSiblings(fx, fd)) { - foreach (f2; a) + bool alreadyReported = false; + foreach (r; reported) { - if (f2 == f) - break LcheckAncestorsOfANestedRef; + if (r == f) + { + alreadyReported = true; + break; + } } - a.push(f); - .errorSupplemental(f.loc, "%s `%s` closes over variable `%s`", - f.kind, f.toErrMsg(), v.toChars()); + if (alreadyReported) + break LcheckAncestorsOfANestedRef; + + reported.push(f); + .errorSupplemental(f.loc, "%s `%s` closes over variable `%s`", f.kind, f.toErrMsg(), v.toChars()); if (v.ident != Id.This) .errorSupplemental(v.loc, "`%s` declared here", v.toChars()); diff --git a/compiler/src/dmd/toir.d b/compiler/src/dmd/toir.d index fff84ab6454b..2e3aa5de4e5b 100644 --- a/compiler/src/dmd/toir.d +++ b/compiler/src/dmd/toir.d @@ -1207,4 +1207,4 @@ RET retStyle(TypeFunction tf, bool needsThis) { //printf("TypeFunction.retStyle() %s\n", toChars()); return target.isReturnOnStack(tf, needsThis) ? RET.stack : RET.regs; -} \ No newline at end of file +} diff --git a/compiler/test/compilable/closure_class_no_dtor.d b/compiler/test/compilable/closure_class_no_dtor.d new file mode 100644 index 000000000000..3e0cf7a4e824 --- /dev/null +++ b/compiler/test/compilable/closure_class_no_dtor.d @@ -0,0 +1,15 @@ +/** + * Test that capturing a class without destructor in a closure is allowed. + */ + +class C +{ + int x; +} + +void main() +{ + auto obj = new C(); + + auto dg = () { return obj; }; // OK: no destructor +} diff --git a/compiler/test/compilable/closure_local_struct_no_dtor.d b/compiler/test/compilable/closure_local_struct_no_dtor.d new file mode 100644 index 000000000000..415fd1809858 --- /dev/null +++ b/compiler/test/compilable/closure_local_struct_no_dtor.d @@ -0,0 +1,15 @@ +/** + * Test that capturing a local struct without destructor in a closure is allowed. + */ + +void main() +{ + struct Local + { + int x; + } + + Local s; + + auto dg = () { return s; }; // OK: no destructor +} diff --git a/compiler/test/compilable/closure_nested_func_no_dtor.d b/compiler/test/compilable/closure_nested_func_no_dtor.d new file mode 100644 index 000000000000..094628763517 --- /dev/null +++ b/compiler/test/compilable/closure_nested_func_no_dtor.d @@ -0,0 +1,20 @@ +/** + * Test that capturing a variable without destructor in a closure inside a nested function is allowed. + */ + +struct S +{ + int x; +} + +void main() +{ + S s; + + void nested() + { + auto dg = () { return s; }; // OK: no destructor + } + + nested(); +} diff --git a/compiler/test/compilable/closure_no_dtor.d b/compiler/test/compilable/closure_no_dtor.d new file mode 100644 index 000000000000..9629b6932f73 --- /dev/null +++ b/compiler/test/compilable/closure_no_dtor.d @@ -0,0 +1,15 @@ +/** + * Test that capturing a struct without destructor in a closure is allowed. + */ + +struct NoDtor +{ + int x; +} + +void main() +{ + NoDtor s; + + auto dg = () { return s; }; // OK: no destructor +} diff --git a/compiler/test/compilable/closure_this_no_dtor.d b/compiler/test/compilable/closure_this_no_dtor.d new file mode 100644 index 000000000000..92e7ff1e8c54 --- /dev/null +++ b/compiler/test/compilable/closure_this_no_dtor.d @@ -0,0 +1,19 @@ +/** + * Test that capturing `this` of a struct without destructor in a closure is allowed. + */ + +struct S +{ + int x; + + void foo() + { + auto dg = () { return this; }; // OK: no destructor + } +} + +void main() +{ + S s; + s.foo(); +} diff --git a/compiler/test/fail_compilation/closure_class_dtor.d b/compiler/test/fail_compilation/closure_class_dtor.d new file mode 100644 index 000000000000..1ba97be0eb70 --- /dev/null +++ b/compiler/test/fail_compilation/closure_class_dtor.d @@ -0,0 +1,22 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/closure_class_dtor.d(19): Error: scoped class variable `closure_class_dtor.main.obj` has destructor, cannot build closure +--- +*/ + +/** + * Test that capturing a class with destructor in a closure is forbidden. + */ + +class C +{ + ~this() {} +} + +void main() +{ + scope obj = new C(); + + auto dg = () { return obj; }; // should be banned +} diff --git a/compiler/test/fail_compilation/closure_local_struct_dtor.d b/compiler/test/fail_compilation/closure_local_struct_dtor.d new file mode 100644 index 000000000000..63f5f6742a09 --- /dev/null +++ b/compiler/test/fail_compilation/closure_local_struct_dtor.d @@ -0,0 +1,23 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/closure_local_struct_dtor.d(20): Error: variable `closure_local_struct_dtor.main.s` has scoped destruction, cannot build closure +--- +*/ + +/** + * Test that capturing a local struct with destructor in a closure is forbidden. + */ + +void main() +{ + struct Local + { + ~this() {} + int x; + } + + Local s; + + auto dg = () { return s; }; // should be banned +} diff --git a/compiler/test/fail_compilation/closure_nested_func_dtor.d b/compiler/test/fail_compilation/closure_nested_func_dtor.d new file mode 100644 index 000000000000..b862c4f7e689 --- /dev/null +++ b/compiler/test/fail_compilation/closure_nested_func_dtor.d @@ -0,0 +1,27 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/closure_nested_func_dtor.d(19): Error: variable `closure_nested_func_dtor.main.s` has scoped destruction, cannot build closure +--- +*/ + +/** + * Test that capturing a variable with destructor in a closure inside a nested function is forbidden. + */ + +struct S +{ + ~this() {} +} + +void main() +{ + S s; + + void nested() + { + auto dg = () { return s; }; // should be banned + } + + nested(); +} diff --git a/compiler/test/fail_compilation/closure_this_dtor.d b/compiler/test/fail_compilation/closure_this_dtor.d new file mode 100644 index 000000000000..1e08d760a9a5 --- /dev/null +++ b/compiler/test/fail_compilation/closure_this_dtor.d @@ -0,0 +1,26 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/closure_this_dtor.d(16): Error: variable `closure_this_dtor.S.foo.this` has scoped destruction, cannot build closure +--- +*/ + +/** + * Test that capturing `this` of a struct with destructor in a closure is forbidden. + */ + +struct S +{ + ~this() {} + + void foo() + { + auto dg = () { return this; }; // should be banned + } +} + +void main() +{ + S s; + s.foo(); +} diff --git a/compiler/test/fail_compilation/closure_with_dtor.d b/compiler/test/fail_compilation/closure_with_dtor.d new file mode 100644 index 000000000000..674793fc5dde --- /dev/null +++ b/compiler/test/fail_compilation/closure_with_dtor.d @@ -0,0 +1,22 @@ +/* +TEST_OUTPUT: +--- +fail_compilation/closure_with_dtor.d(19): Error: variable `closure_with_dtor.main.s` has scoped destruction, cannot build closure +--- +*/ + +/** + * Test that capturing a struct with destructor in a closure is forbidden. + */ + +struct S +{ + ~this() {} +} + +void main() +{ + S s; + + auto dg = () { return s; }; // should be banned +}