Skip to content

Commit 3eef738

Browse files
authored
Related locations available in Resolver and prints to Console and LSP Diagnostics (#1695)
* Support for messages in nested tokens. Console printing now displays information in nested tokens coming from the Resolution phase The Language Server now displays in formation in nested tokens from the resolution phase No more duplicate messages.
1 parent ef07877 commit 3eef738

File tree

59 files changed

+348
-284
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+348
-284
lines changed

Source/Dafny/AST/DafnyAst.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8900,14 +8900,16 @@ public virtual string val {
89008900
}
89018901

89028902
public class NestedToken : TokenWrapper {
8903-
public NestedToken(IToken outer, IToken inner)
8903+
public NestedToken(IToken outer, IToken inner, string message = null)
89048904
: base(outer) {
89058905
Contract.Requires(outer != null);
89068906
Contract.Requires(inner != null);
89078907
Inner = inner;
8908+
this.Message = message;
89088909
}
89098910
public IToken Outer { get { return WrappedToken; } }
89108911
public readonly IToken Inner;
8912+
public readonly string Message;
89118913
}
89128914

89138915
/// <summary>

Source/Dafny/Reporting.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,19 @@ public override bool Message(MessageSource source, ErrorLevel level, IToken tok,
199199

200200
ConsoleColor previousColor = Console.ForegroundColor;
201201
Console.ForegroundColor = ColorForLevel(level);
202-
Console.WriteLine(ErrorToString(level, tok, msg));
202+
var errorLine = ErrorToString(level, tok, msg);
203+
while (tok is NestedToken nestedToken) {
204+
tok = nestedToken.Inner;
205+
if (tok.filename == nestedToken.filename &&
206+
tok.line == nestedToken.line &&
207+
tok.col == nestedToken.col) {
208+
continue;
209+
}
210+
msg = nestedToken.Message ?? "[Related location]";
211+
errorLine += $" {msg} {TokenToString(tok)}";
212+
}
213+
Console.WriteLine(errorLine);
214+
203215
Console.ForegroundColor = previousColor;
204216
return true;
205217
} else {

Source/Dafny/Resolver.cs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18547,6 +18547,25 @@ public bool ContainsDecl(Thing t) {
1854718547
// Looks for every non-ghost comprehensions, and if they are using a subset type,
1854818548
// check that the subset constraint is compilable. If it is not compilable, raises an error.
1854918549
public class SubsetConstraintGhostChecker : ProgramTraverser {
18550+
public class FirstErrorCollector : ErrorReporter {
18551+
public string FirstCollectedMessage = "";
18552+
public IToken FirstCollectedToken = Token.NoToken;
18553+
public bool Collected = false;
18554+
18555+
public override bool Message(MessageSource source, ErrorLevel level, IToken tok, string msg) {
18556+
if (!Collected && level == ErrorLevel.Error) {
18557+
FirstCollectedMessage = msg;
18558+
FirstCollectedToken = tok;
18559+
Collected = true;
18560+
}
18561+
return true;
18562+
}
18563+
18564+
public override int Count(ErrorLevel level) {
18565+
return level == ErrorLevel.Error && Collected ? 1 : 0;
18566+
}
18567+
}
18568+
1855018569
public Resolver resolver;
1855118570

1855218571
public SubsetConstraintGhostChecker(Resolver resolver) {
@@ -18610,15 +18629,19 @@ public override bool Traverse(Expression expr, [CanBeNull] string field, [CanBeN
1861018629
}
1861118630

1861218631
if (!constraintIsCompilable) {
18613-
// Explicitly report the error
18614-
var showProvenance = constraint.tok.line != 0;
18615-
this.resolver.Reporter.Error(MessageSource.Resolver, boundVar.tok,
18616-
$"{boundVar.Type} is a subset type and its constraint is not compilable, hence it cannot yet be used as the type of a bound variable in {what}." +
18617-
(showProvenance ? " The next error will explain why the constraint is not compilable." : ""));
18618-
if (showProvenance) {
18619-
ExpressionTester.CheckIsCompilable(this.resolver, constraint,
18632+
IToken finalToken = boundVar.tok;
18633+
if (constraint.tok.line != 0) {
18634+
var errorCollector = new FirstErrorCollector();
18635+
ExpressionTester.CheckIsCompilable(this.resolver, errorCollector, constraint,
1862018636
new CodeContextWrapper(subsetTypeDecl, true));
18637+
if (errorCollector.Collected) {
18638+
finalToken = new NestedToken(finalToken, errorCollector.FirstCollectedToken,
18639+
"The constraint is not compilable because " + errorCollector.FirstCollectedMessage
18640+
);
18641+
}
1862118642
}
18643+
this.resolver.Reporter.Error(MessageSource.Resolver, finalToken,
18644+
$"{boundVar.Type} is a subset type and its constraint is not compilable, hence it cannot yet be used as the type of a bound variable in {what}.");
1862218645
}
1862318646
}
1862418647
}

Source/Dafny/Triggers/QuantifiersCollection.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,18 @@ private void CommitOne(QuantifierWithTriggers q, bool addHeader) {
285285
var msg = new StringBuilder();
286286
var indent = addHeader ? " " : "";
287287
bool suppressWarnings = Attributes.Contains(q.quantifier.Attributes, "nowarn");
288+
var reportingToken = q.quantifier.tok;
289+
290+
void SetRelatedLocationMessage(string msg) {
291+
if (reportingToken is NestedToken nestedToken) {
292+
reportingToken = new NestedToken(nestedToken.Outer, nestedToken.Inner, msg);
293+
}
294+
}
288295

289296
if (!TriggerUtils.NeedsAutoTriggers(q.quantifier)) { // NOTE: split and autotriggers attributes are passed down to Boogie
290297
var extraMsg = TriggerUtils.WantsAutoTriggers(q.quantifier) ? "" : " Note that {:autotriggers false} can cause instabilities. Consider using {:nowarn}, {:matchingloop} (not great either), or a manual trigger instead.";
291298
msg.AppendFormat("Not generating triggers for \"{0}\".{1}", Printer.ExprToString(q.quantifier.Term), extraMsg).AppendLine();
299+
SetRelatedLocationMessage("Trigger position:");
292300
} else {
293301
if (addHeader) {
294302
msg.AppendFormat("For expression \"{0}\":", Printer.ExprToString(q.quantifier.Term)).AppendLine();
@@ -299,32 +307,42 @@ private void CommitOne(QuantifierWithTriggers q, bool addHeader) {
299307
}
300308

301309
AddTriggersToMessage("Selected triggers:", q.Candidates, msg, indent);
310+
if (q.Candidates.Count > 0) {
311+
SetRelatedLocationMessage("Trigger position:");
312+
}
302313
AddTriggersToMessage("Rejected triggers:", q.RejectedCandidates, msg, indent, true);
314+
if (q.RejectedCandidates.Count > 0 && q.Candidates.Count == 0) {
315+
SetRelatedLocationMessage("Rejected trigger position:");
316+
}
303317

304318
#if QUANTIFIER_WARNINGS
305-
var WARN_TAG = DafnyOptions.O.UnicodeOutput ? "⚠ " : "/!\\ ";
319+
var WARN_TAG = DafnyOptions.O.UnicodeOutput ? "⚠ " : @"/!\ ";
306320
var WARN_TAG_OVERRIDE = suppressWarnings ? "(Suppressed warning) " : WARN_TAG;
307321
var WARN_LEVEL = suppressWarnings ? ErrorLevel.Info : ErrorLevel.Warning;
308322
var WARN = indent + WARN_TAG_OVERRIDE;
309323
if (!q.CandidateTerms.Any()) {
310324
errorLevel = WARN_LEVEL;
311325
msg.Append(WARN).AppendLine("No terms found to trigger on.");
326+
SetRelatedLocationMessage("Potential trigger not suitable:");
312327
} else if (!q.Candidates.Any()) {
313328
errorLevel = WARN_LEVEL;
314329
msg.Append(WARN).AppendLine("No trigger covering all quantified variables found.");
330+
SetRelatedLocationMessage("Potential trigger not suitable:");
315331
} else if (!q.CouldSuppressLoops && !q.AllowsLoops) {
316332
errorLevel = WARN_LEVEL;
317333
msg.Append(WARN).AppendLine("Suppressing loops would leave this expression without triggers.");
334+
SetRelatedLocationMessage("Trigger position:");
318335
} else if (suppressWarnings) {
319336
errorLevel = ErrorLevel.Warning;
320337
msg.Append(indent).Append(WARN_TAG).AppendLine("There is no warning here to suppress.");
338+
SetRelatedLocationMessage("Trigger position:");
321339
}
322340
#endif
323341
}
324342

325343
if (msg.Length > 0) {
326344
var msgStr = msg.ToString().TrimEnd("\r\n ".ToCharArray());
327-
reporter.Message(MessageSource.Rewriter, errorLevel, q.quantifier.tok, msgStr);
345+
reporter.Message(MessageSource.Rewriter, errorLevel, reportingToken, msgStr);
328346
}
329347
}
330348

Source/Dafny/Util.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -757,20 +757,25 @@ public class ExpressionTester {
757757
private ErrorReporter reporter = null;
758758
private Resolver resolver = null;
759759

760-
public ExpressionTester(Resolver resolver = null, bool reportErrors = false) {
761-
Contract.Requires(reportErrors == false || resolver != null);
762-
if (resolver != null) {
763-
this.reporter = resolver.Reporter;
764-
this.resolver = resolver;
765-
}
760+
public ExpressionTester(Resolver resolver = null, bool reportErrors = false) :
761+
this(resolver, resolver?.Reporter, reportErrors) {
762+
}
766763

764+
public ExpressionTester(Resolver resolver, ErrorReporter reporter, bool reportErrors = false) {
765+
Contract.Requires(reportErrors == false || resolver != null);
766+
this.resolver = resolver;
767+
this.reporter = reporter;
767768
this.reportErrors = reportErrors;
768769
}
769770

770771
// Static call to CheckIsCompilable
771772
public static bool CheckIsCompilable(Resolver resolver, Expression expr, ICodeContext codeContext) {
772773
return new ExpressionTester(resolver, resolver != null).CheckIsCompilable(expr, codeContext);
773774
}
775+
// Static call to CheckIsCompilable
776+
public static bool CheckIsCompilable(Resolver resolver, ErrorReporter reporter, Expression expr, ICodeContext codeContext) {
777+
return new ExpressionTester(resolver, reporter, resolver != null).CheckIsCompilable(expr, codeContext);
778+
}
774779

775780
/// <summary>
776781
/// Try to make "expr" compilable (in particular, mark LHSs of a let-expression as ghosts if

Source/DafnyLanguageServer/Language/DiagnosticErrorReporter.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,19 @@ public override bool Message(MessageSource source, ErrorLevel level, IToken tok,
9393
if (ErrorsOnly && level != ErrorLevel.Error) {
9494
return false;
9595
}
96-
96+
var relatedInformation = new List<DiagnosticRelatedInformation>();
97+
if (tok is NestedToken nestedToken) {
98+
relatedInformation.AddRange(
99+
CreateDiagnosticRelatedInformationFor(
100+
nestedToken.Inner, nestedToken.Message ?? "Related location")
101+
);
102+
}
97103
var item = new Diagnostic {
98104
Severity = ToSeverity(level),
99105
Message = msg,
100106
Range = tok.GetLspRange(),
101-
Source = source.ToString()
107+
Source = source.ToString(),
108+
RelatedInformation = relatedInformation,
102109
};
103110
AddDiagnosticForFile(item, GetDocumentUriOrDefault(tok));
104111
return true;

Test/allocated1/dafny0/DiscoverBounds.dfy.expect

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
DiscoverBounds.dfy(32,7): Warning: /!\ No terms found to trigger on.
2-
DiscoverBounds.dfy(33,7): Warning: /!\ No terms found to trigger on.
3-
DiscoverBounds.dfy(34,7): Warning: /!\ No terms found to trigger on.
4-
DiscoverBounds.dfy(35,7): Warning: /!\ No terms found to trigger on.
5-
DiscoverBounds.dfy(36,7): Warning: /!\ No terms found to trigger on.
1+
DiscoverBounds.dfy(32,7): Warning: /!\ No terms found to trigger on. Potential trigger not suitable: DiscoverBounds.dfy(32,33)
2+
DiscoverBounds.dfy(33,7): Warning: /!\ No terms found to trigger on. Potential trigger not suitable: DiscoverBounds.dfy(33,33)
3+
DiscoverBounds.dfy(34,7): Warning: /!\ No terms found to trigger on. Potential trigger not suitable: DiscoverBounds.dfy(34,36)
4+
DiscoverBounds.dfy(35,7): Warning: /!\ No terms found to trigger on. Potential trigger not suitable: DiscoverBounds.dfy(35,39)
5+
DiscoverBounds.dfy(36,7): Warning: /!\ No terms found to trigger on. Potential trigger not suitable: DiscoverBounds.dfy(36,42)
66

77
Dafny program verifier finished with 7 verified, 0 errors
88
0

Test/allocated1/dafny0/InSetComprehension.dfy.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ InSetComprehension.dfy(39,16): Info: Not generating triggers for "Id(u)". Note t
1111
InSetComprehension.dfy(76,13): Info: Selected triggers: {x in si}
1212
InSetComprehension.dfy(78,13): Info: Selected triggers: {y in ti}
1313
InSetComprehension.dfy(81,13): Info: Selected triggers: {n in si}
14-
InSetComprehension.dfy(82,5): Info: Selected triggers: {si[i]}
14+
InSetComprehension.dfy(82,5): Info: Selected triggers: {si[i]} Trigger position: InSetComprehension.dfy(82,37)
1515
InSetComprehension.dfy(53,24): Info: Selected triggers: {x in elems}
1616
InSetComprehension.dfy(51,25): Info: Selected triggers: {x in elems}
1717
InSetComprehension.dfy(67,24): Info: Selected triggers: {x in elems}

Test/allocated1/dafny0/LetExpr.dfy.expect

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
LetExpr.dfy(45,2): Warning: /!\ No terms found to trigger on.
2-
LetExpr.dfy(206,4): Warning: /!\ No terms found to trigger on.
1+
LetExpr.dfy(45,2): Warning: /!\ No terms found to trigger on. Potential trigger not suitable: LetExpr.dfy(45,39)
2+
LetExpr.dfy(206,4): Warning: /!\ No terms found to trigger on. Potential trigger not suitable: LetExpr.dfy(206,30)
33
LetExpr.dfy(340,18): Error: value does not satisfy the subset constraints of 'nat'
44
LetExpr.dfy(344,13): Error: value does not satisfy the subset constraints of 'nat'
55
LetExpr.dfy(390,33): Error: assertion violation
@@ -15,7 +15,7 @@ LetExpr.dfy(323,11): Error: to be compilable, the value of a let-such-that expre
1515
LetExpr.dfy(109,22): Error: assertion violation
1616

1717
Dafny program verifier finished with 37 verified, 13 errors
18-
LetExpr.dfy.tmp.dprint.dfy(281,4): Warning: /!\ No terms found to trigger on.
19-
LetExpr.dfy.tmp.dprint.dfy(46,2): Warning: /!\ No terms found to trigger on.
18+
LetExpr.dfy.tmp.dprint.dfy(281,4): Warning: /!\ No terms found to trigger on. Potential trigger not suitable: LetExpr.dfy.tmp.dprint.dfy(283,8)
19+
LetExpr.dfy.tmp.dprint.dfy(46,2): Warning: /!\ No terms found to trigger on. Potential trigger not suitable: LetExpr.dfy.tmp.dprint.dfy(47,12)
2020

2121
Dafny program verifier did not attempt verification

Test/allocated1/dafny0/Matrix-OOB.dfy.expect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Matrix-OOB.dfy(11,10): Info: Selected triggers: {m[i, j]}
1+
Matrix-OOB.dfy(11,10): Info: Selected triggers: {m[i, j]} Trigger position: Matrix-OOB.dfy(11,33)
22
Matrix-OOB.dfy(11,26): Error: index 0 out of range
33
Matrix-OOB.dfy(11,26): Error: index 1 out of range
44
Matrix-OOB.dfy(12,0): Error: A postcondition might not hold on this return path.

0 commit comments

Comments
 (0)