Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 48 additions & 11 deletions compiler/src/dmd/semantic3.d
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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());
Expand All @@ -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)
Expand All @@ -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());

Expand Down
15 changes: 15 additions & 0 deletions compiler/test/compilable/closure_class_no_dtor.d
Original file line number Diff line number Diff line change
@@ -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
}
15 changes: 15 additions & 0 deletions compiler/test/compilable/closure_local_struct_no_dtor.d
Original file line number Diff line number Diff line change
@@ -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
}
20 changes: 20 additions & 0 deletions compiler/test/compilable/closure_nested_func_no_dtor.d
Original file line number Diff line number Diff line change
@@ -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();
}
15 changes: 15 additions & 0 deletions compiler/test/compilable/closure_no_dtor.d
Original file line number Diff line number Diff line change
@@ -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
}
19 changes: 19 additions & 0 deletions compiler/test/compilable/closure_this_no_dtor.d
Original file line number Diff line number Diff line change
@@ -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();
}
22 changes: 22 additions & 0 deletions compiler/test/fail_compilation/closure_class_dtor.d
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 23 additions & 0 deletions compiler/test/fail_compilation/closure_local_struct_dtor.d
Original file line number Diff line number Diff line change
@@ -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
}
27 changes: 27 additions & 0 deletions compiler/test/fail_compilation/closure_nested_func_dtor.d
Original file line number Diff line number Diff line change
@@ -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();
}
26 changes: 26 additions & 0 deletions compiler/test/fail_compilation/closure_this_dtor.d
Original file line number Diff line number Diff line change
@@ -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();
}
22 changes: 22 additions & 0 deletions compiler/test/fail_compilation/closure_with_dtor.d
Original file line number Diff line number Diff line change
@@ -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
}
Comment on lines +17 to +22
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FAOD, in all these examples, none of the closures escape, so calling dtor on s at the end of scope is correct behaviour and should remain valid.

Loading