Skip to content

Commit 34a6b55

Browse files
authored
Merge pull request #22285 from CyberShadow/dip1000-scope-errors
Improve scope violation error messages to show inference chain
2 parents 325f1fe + 221eca5 commit 34a6b55

File tree

6 files changed

+94
-8
lines changed

6 files changed

+94
-8
lines changed

compiler/src/dmd/escape.d

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ bool checkAssocArrayLiteralEscape(ref Scope sc, AssocArrayLiteralExp ae, bool ga
293293
* recursionLimit = recursion limit for printing the reason
294294
* positive = true for telling why v is scope, false for telling why v is not scope
295295
*/
296-
private
296+
public
297297
void printScopeReason(E)(E printFunc, VarDeclaration v, int recursionLimit, bool positive)
298298
{
299299
recursionLimit--;
@@ -375,8 +375,8 @@ bool checkParamArgumentEscape(ref Scope sc, FuncDeclaration fdc, Identifier parI
375375
(desc ~ " `%s` assigned to non-scope anonymous parameter");
376376

377377
if (isThis ?
378-
sc.setUnsafeDIP1000(gag, arg.loc, msg, arg, fdc.toParent2(), fdc) :
379-
sc.setUnsafeDIP1000(gag, arg.loc, msg, v, parId ? parId : fdc, fdc))
378+
sc.setUnsafeDIP1000(gag, arg.loc, vPar, msg, arg, fdc.toParent2(), fdc) :
379+
sc.setUnsafeDIP1000(gag, arg.loc, vPar, msg, v, parId ? parId : fdc, fdc))
380380
{
381381
result = true;
382382
printScopeReason(previewSupplementalFunc(sc.isDeprecated(), sc.useDIP1000), vPar, 10, false);
@@ -2214,6 +2214,14 @@ bool setUnsafeDIP1000(ref Scope sc, bool gag, Loc loc, const(char)* msg,
22142214
return setUnsafePreview(&sc, sc.useDIP1000, gag, loc, msg, args);
22152215
}
22162216

2217+
/// Overload for scope violations that also stores the variable whose scope status caused the issue
2218+
private
2219+
bool setUnsafeDIP1000(ref Scope sc, bool gag, Loc loc, VarDeclaration scopeVar,
2220+
const(char)* msg, RootObject[] args...)
2221+
{
2222+
return setUnsafePreview(&sc, sc.useDIP1000, gag, loc, scopeVar, msg, args);
2223+
}
2224+
22172225
/***************************************
22182226
* Check that taking the address of `v` is `@safe`
22192227
*

compiler/src/dmd/expressionsem.d

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3164,6 +3164,12 @@ public void errorSupplementalInferredAttr(FuncDeclaration fd, int maxDepth, bool
31643164
if (s.action.length > 0)
31653165
{
31663166
errorFunc(s.loc, "and %.*s makes it fail to infer `%.*s`", s.action.fTuple.expand, attr.fTuple.expand);
3167+
// For scope violations, also print why the target parameter is not scope
3168+
if (s.scopeVar)
3169+
{
3170+
import dmd.escape : printScopeReason;
3171+
printScopeReason(errorFunc, s.scopeVar, 10, false);
3172+
}
31673173
}
31683174
else if (s.fd)
31693175
{

compiler/src/dmd/frontend.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3707,10 +3707,12 @@ struct AttributeViolation final
37073707
Loc loc;
37083708
FuncDeclaration* fd;
37093709
_d_dynamicArray< const char > action;
3710+
VarDeclaration* scopeVar;
37103711
AttributeViolation() :
37113712
loc(),
37123713
fd(),
3713-
action()
3714+
action(),
3715+
scopeVar()
37143716
{
37153717
}
37163718
};

compiler/src/dmd/func.d

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,6 +1430,8 @@ struct AttributeViolation
14301430

14311431
string action; /// Action that made the attribute fail to get inferred
14321432

1433+
VarDeclaration scopeVar; /// For scope violations: the parameter whose scope status caused the issue
1434+
14331435
this(Loc loc, FuncDeclaration fd) { this.loc = loc; this.fd = fd; }
14341436

14351437
this(Loc loc, const(char)* fmt, RootObject[] args)
@@ -1445,4 +1447,10 @@ struct AttributeViolation
14451447
);
14461448
this.action = buf.extractSlice();
14471449
}
1450+
1451+
this(Loc loc, const(char)* fmt, VarDeclaration scopeVar, RootObject[] args)
1452+
{
1453+
this(loc, fmt, args);
1454+
this.scopeVar = scopeVar;
1455+
}
14481456
}

compiler/src/dmd/safe.d

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -370,12 +370,22 @@ bool isTrusted(FuncDeclaration fd)
370370
*/
371371
extern (D) void reportSafeError(FuncDeclaration fd, bool gag, Loc loc,
372372
const(char)* format, RootObject[] args...)
373+
{
374+
reportSafeError(fd, gag, loc, null, format, args);
375+
}
376+
377+
/// Overload that also stores the variable whose scope status caused the violation
378+
extern (D) void reportSafeError(FuncDeclaration fd, bool gag, Loc loc,
379+
VarDeclaration scopeVar, const(char)* format, RootObject[] args...)
373380
{
374381
if (fd.type.toTypeFunction().trust == TRUST.system) // function was just inferred to be @system
375382
{
376383
if (format)
377384
{
378-
fd.safetyViolation = new AttributeViolation(loc, format, args);
385+
if (scopeVar)
386+
fd.safetyViolation = new AttributeViolation(loc, format, scopeVar, args);
387+
else
388+
fd.safetyViolation = new AttributeViolation(loc, format, args);
379389
}
380390
else if (args.length > 0)
381391
{
@@ -460,6 +470,13 @@ extern (D) bool setUnsafeCall(FuncDeclaration fd, FuncDeclaration f)
460470
* Returns: whether there is a safe error
461471
*/
462472
bool setUnsafe(Scope* sc, bool gag, Loc loc, const(char)* format, RootObject[] args...)
473+
{
474+
return sc.setUnsafe(gag, loc, null, format, args);
475+
}
476+
477+
/// Overload that also stores the variable whose scope status caused the violation
478+
bool setUnsafe(Scope* sc, bool gag, Loc loc, VarDeclaration scopeVar,
479+
const(char)* format, RootObject[] args...)
463480
{
464481
if (sc.intypeof)
465482
return false; // typeof(cast(int*)0) is safe
@@ -504,7 +521,7 @@ bool setUnsafe(Scope* sc, bool gag, Loc loc, const(char)* format, RootObject[] a
504521
{
505522
if (format || args.length > 0)
506523
{
507-
reportSafeError(sc.func, gag, loc, format, args);
524+
reportSafeError(sc.func, gag, loc, scopeVar, format, args);
508525
}
509526
return sc.func.isSafe(); // it is only an error if in an @safe function
510527
}
@@ -531,6 +548,13 @@ bool setUnsafe(Scope* sc, bool gag, Loc loc, const(char)* format, RootObject[] a
531548
* Returns: whether an actual safe error (not deprecation) occured
532549
*/
533550
bool setUnsafePreview(Scope* sc, FeatureState fs, bool gag, Loc loc, const(char)* format, RootObject[] args...)
551+
{
552+
return setUnsafePreview(sc, fs, gag, loc, null, format, args);
553+
}
554+
555+
/// Overload for scope violations that also stores the variable whose scope status caused the issue
556+
bool setUnsafePreview(Scope* sc, FeatureState fs, bool gag, Loc loc, VarDeclaration scopeVar,
557+
const(char)* format, RootObject[] args...)
534558
{
535559
//printf("setUnsafePreview() fs:%d %s\n", fs, fmt);
536560
assert(format);
@@ -540,7 +564,7 @@ bool setUnsafePreview(Scope* sc, FeatureState fs, bool gag, Loc loc, const(char)
540564
return false;
541565

542566
case enabled:
543-
return sc.setUnsafe(gag, loc, format, args);
567+
return sc.setUnsafe(gag, loc, scopeVar, format, args);
544568

545569
case default_:
546570
if (!sc.func)
@@ -556,7 +580,10 @@ bool setUnsafePreview(Scope* sc, FeatureState fs, bool gag, Loc loc, const(char)
556580
else if (!sc.func.safetyViolation)
557581
{
558582
import dmd.func : AttributeViolation;
559-
sc.func.safetyViolation = new AttributeViolation(loc, format, args);
583+
if (scopeVar)
584+
sc.func.safetyViolation = new AttributeViolation(loc, format, scopeVar, args);
585+
else
586+
sc.func.safetyViolation = new AttributeViolation(loc, format, args);
560587
}
561588
return false;
562589
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
REQUIRED_ARGS: -preview=dip1000
3+
TEST_OUTPUT:
4+
---
5+
fail_compilation/scope_infer_diagnostic.d(34): Error: `@safe` function `scope_infer_diagnostic.outer` cannot call `@system` function `scope_infer_diagnostic.inner!(void delegate(const(char)[]) pure nothrow @nogc @safe).inner`
6+
fail_compilation/scope_infer_diagnostic.d(28): and assigning scope variable `w` to non-scope parameter `w` calling `callee` makes it fail to infer `@safe`
7+
fail_compilation/scope_infer_diagnostic.d(21): `w` is not `scope` because of `globalPtr = & w`
8+
fail_compilation/scope_infer_diagnostic.d(25): `scope_infer_diagnostic.inner!(void delegate(const(char)[]) pure nothrow @nogc @safe).inner` is declared here
9+
---
10+
*/
11+
12+
// Test that scope violation error messages show WHY the callee's parameter
13+
// is not scope when going through the @safe inference chain.
14+
// This is similar to how @nogc violations show the full inference chain.
15+
16+
void* globalPtr;
17+
18+
void callee(Writer)(Writer w) @trusted
19+
{
20+
// This escapes w, preventing it from being inferred as scope
21+
globalPtr = cast(void*)&w;
22+
w("x");
23+
}
24+
25+
void inner(Writer)(scope Writer w)
26+
{
27+
// Scope violation: passing scope w to non-scope parameter
28+
callee(w);
29+
}
30+
31+
void outer() @safe
32+
{
33+
int x;
34+
inner((const(char)[] s) { x++; });
35+
}

0 commit comments

Comments
 (0)