Skip to content

Commit 7d14cad

Browse files
Refactor reporting code (#4929)
### Description Make reporting of related locations more consistent, so related locations are always shown on a separate line. Example: ``` -git-issue-864y.dfy(6,9): Error: duplicate name of top-level declaration: A [Related location] git-issue-864y.dfy(7,8) +git-issue-864y.dfy(6,9): Error: duplicate name of top-level declaration: A +git-issue-864y.dfy(7,8): Related location ``` ### How has this been tested? Expect files have been updated to match the new related location format. <small>By submitting this pull request, I confirm that my contribution is made under the terms of the [MIT license](https://github.com/dafny-lang/dafny/blob/master/LICENSE.txt).</small>
1 parent 2ef450b commit 7d14cad

26 files changed

+643
-564
lines changed

Source/DafnyCore/AST/Types/MapType.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System.Collections.Generic;
2+
using System.Diagnostics.Contracts;
3+
4+
namespace Microsoft.Dafny;
5+
6+
public class MapType : CollectionType {
7+
public bool Finite {
8+
get { return finite; }
9+
set { finite = value; }
10+
}
11+
private bool finite;
12+
public Type Range {
13+
get { return range; }
14+
}
15+
private Type range;
16+
public override void SetTypeArgs(Type domain, Type range) {
17+
base.SetTypeArgs(domain, range);
18+
Contract.Assume(this.range == null); // Can only set once. This is really a precondition.
19+
this.range = range;
20+
}
21+
public MapType(bool finite, Type domain, Type range) : base(domain, range) {
22+
Contract.Requires((domain == null && range == null) || (domain != null && range != null));
23+
this.finite = finite;
24+
this.range = range;
25+
}
26+
public Type Domain {
27+
get { return Arg; }
28+
}
29+
public override string CollectionTypeName { get { return finite ? "map" : "imap"; } }
30+
[System.Diagnostics.Contracts.Pure]
31+
public override string TypeName(DafnyOptions options, ModuleDefinition context, bool parseAble) {
32+
Contract.Ensures(Contract.Result<string>() != null);
33+
var targs = HasTypeArg() ? this.TypeArgsToString(options, context, parseAble) : "";
34+
return CollectionTypeName + targs;
35+
}
36+
public override bool Equals(Type that, bool keepConstraints = false) {
37+
var t = that.NormalizeExpand(keepConstraints) as MapType;
38+
return t != null && Finite == t.Finite && Arg.Equals(t.Arg, keepConstraints) && Range.Equals(t.Range, keepConstraints);
39+
}
40+
41+
public override Type Subst(IDictionary<TypeParameter, Type> subst) {
42+
var dom = Domain.Subst(subst);
43+
if (dom is InferredTypeProxy) {
44+
((InferredTypeProxy)dom).KeepConstraints = true;
45+
}
46+
var ran = Range.Subst(subst);
47+
if (ran is InferredTypeProxy) {
48+
((InferredTypeProxy)ran).KeepConstraints = true;
49+
}
50+
if (dom == Domain && ran == Range) {
51+
return this;
52+
} else {
53+
return new MapType(Finite, dom, ran);
54+
}
55+
}
56+
57+
public override Type ReplaceTypeArguments(List<Type> arguments) {
58+
return new MapType(Finite, arguments[0], arguments[1]);
59+
}
60+
61+
public override bool SupportsEquality {
62+
get {
63+
// A map type supports equality if both its Keys type and Values type does. It is checked
64+
// that the Keys type always supports equality, so we only need to check the Values type here.
65+
return range.SupportsEquality;
66+
}
67+
}
68+
public override bool ComputeMayInvolveReferences(ISet<DatatypeDecl> visitedDatatypes) {
69+
return Domain.ComputeMayInvolveReferences(visitedDatatypes) || Range.ComputeMayInvolveReferences(visitedDatatypes);
70+
}
71+
72+
public override BinaryExpr.ResolvedOpcode ResolvedOpcodeForIn => BinaryExpr.ResolvedOpcode.InMap;
73+
public override ComprehensionExpr.CollectionBoundedPool GetBoundedPool(Expression source) {
74+
return new ComprehensionExpr.MapBoundedPool(source, Domain, Domain, Finite);
75+
}
76+
}

Source/DafnyCore/AST/Types/Types.cs

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2124,77 +2124,6 @@ public override ComprehensionExpr.CollectionBoundedPool GetBoundedPool(Expressio
21242124
return new ComprehensionExpr.SeqBoundedPool(source, Arg, Arg);
21252125
}
21262126
}
2127-
public class MapType : CollectionType {
2128-
public bool Finite {
2129-
get { return finite; }
2130-
set { finite = value; }
2131-
}
2132-
private bool finite;
2133-
public Type Range {
2134-
get { return range; }
2135-
}
2136-
private Type range;
2137-
public override void SetTypeArgs(Type domain, Type range) {
2138-
base.SetTypeArgs(domain, range);
2139-
Contract.Assume(this.range == null); // Can only set once. This is really a precondition.
2140-
this.range = range;
2141-
}
2142-
public MapType(bool finite, Type domain, Type range) : base(domain, range) {
2143-
Contract.Requires((domain == null && range == null) || (domain != null && range != null));
2144-
this.finite = finite;
2145-
this.range = range;
2146-
}
2147-
public Type Domain {
2148-
get { return Arg; }
2149-
}
2150-
public override string CollectionTypeName { get { return finite ? "map" : "imap"; } }
2151-
[System.Diagnostics.Contracts.Pure]
2152-
public override string TypeName(DafnyOptions options, ModuleDefinition context, bool parseAble) {
2153-
Contract.Ensures(Contract.Result<string>() != null);
2154-
var targs = HasTypeArg() ? this.TypeArgsToString(options, context, parseAble) : "";
2155-
return CollectionTypeName + targs;
2156-
}
2157-
public override bool Equals(Type that, bool keepConstraints = false) {
2158-
var t = that.NormalizeExpand(keepConstraints) as MapType;
2159-
return t != null && Finite == t.Finite && Arg.Equals(t.Arg, keepConstraints) && Range.Equals(t.Range, keepConstraints);
2160-
}
2161-
2162-
public override Type Subst(IDictionary<TypeParameter, Type> subst) {
2163-
var dom = Domain.Subst(subst);
2164-
if (dom is InferredTypeProxy) {
2165-
((InferredTypeProxy)dom).KeepConstraints = true;
2166-
}
2167-
var ran = Range.Subst(subst);
2168-
if (ran is InferredTypeProxy) {
2169-
((InferredTypeProxy)ran).KeepConstraints = true;
2170-
}
2171-
if (dom == Domain && ran == Range) {
2172-
return this;
2173-
} else {
2174-
return new MapType(Finite, dom, ran);
2175-
}
2176-
}
2177-
2178-
public override Type ReplaceTypeArguments(List<Type> arguments) {
2179-
return new MapType(Finite, arguments[0], arguments[1]);
2180-
}
2181-
2182-
public override bool SupportsEquality {
2183-
get {
2184-
// A map type supports equality if both its Keys type and Values type does. It is checked
2185-
// that the Keys type always supports equality, so we only need to check the Values type here.
2186-
return range.SupportsEquality;
2187-
}
2188-
}
2189-
public override bool ComputeMayInvolveReferences(ISet<DatatypeDecl> visitedDatatypes) {
2190-
return Domain.ComputeMayInvolveReferences(visitedDatatypes) || Range.ComputeMayInvolveReferences(visitedDatatypes);
2191-
}
2192-
2193-
public override BinaryExpr.ResolvedOpcode ResolvedOpcodeForIn => BinaryExpr.ResolvedOpcode.InMap;
2194-
public override ComprehensionExpr.CollectionBoundedPool GetBoundedPool(Expression source) {
2195-
return new ComprehensionExpr.MapBoundedPool(source, Domain, Domain, Finite);
2196-
}
2197-
}
21982127

21992128
public abstract class TypeProxy : Type {
22002129
public override IEnumerable<INode> Children => Enumerable.Empty<Node>();

Source/DafnyCore/DafnyConsolePrinter.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,15 @@ private string GetFileLine(Uri uri, int lineIndex) {
7979
if (0 <= lineIndex && lineIndex < lines.Count) {
8080
return lines[lineIndex];
8181
}
82-
return "<nonexistent line>";
82+
return null;
8383
}
8484

8585
public void WriteSourceCodeSnippet(IToken tok, TextWriter tw) {
8686
string line = GetFileLine(tok.Uri, tok.line - 1);
87+
if (line == null) {
88+
return;
89+
}
90+
8791
string lineNumber = tok.line.ToString();
8892
string lineNumberSpaces = new string(' ', lineNumber.Length);
8993
string columnSpaces = new string(' ', tok.col - 1);
@@ -149,4 +153,4 @@ public override void ReportEndVerifyImplementation(Implementation implementation
149153
var impl = new ImplementationLogEntry(implementation.VerboseName, implementation.tok);
150154
VerificationResults.Add(new ConsoleLogEntry(impl, DistillVerificationResult(result)));
151155
}
152-
}
156+
}

Source/DafnyCore/Generic/ConsoleErrorReporter.cs

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,51 +18,63 @@ private ConsoleColor ColorForLevel(ErrorLevel level) {
1818
}
1919

2020
protected override bool MessageCore(MessageSource source, ErrorLevel level, string errorId, IToken tok, string msg) {
21-
if (base.MessageCore(source, level, errorId, tok, msg) && (Options is { PrintTooltips: true } || level != ErrorLevel.Info)) {
22-
// Extra indent added to make it easier to distinguish multiline error messages for clients that rely on the CLI
23-
msg = msg.Replace("\n", "\n ");
21+
var printMessage = base.MessageCore(source, level, errorId, tok, msg) && (Options is { PrintTooltips: true } || level != ErrorLevel.Info);
22+
if (!printMessage) {
23+
return false;
24+
}
25+
26+
// Extra indent added to make it easier to distinguish multiline error messages for clients that rely on the CLI
27+
msg = msg.Replace("\n", "\n ");
28+
29+
ConsoleColor previousColor = Console.ForegroundColor;
30+
if (Options.OutputWriter == Console.Out) {
31+
Console.ForegroundColor = ColorForLevel(level);
32+
}
33+
var errorLine = ErrorToString(level, tok, msg);
2434

25-
ConsoleColor previousColor = Console.ForegroundColor;
26-
if (Options.OutputWriter == Console.Out) {
27-
Console.ForegroundColor = ColorForLevel(level);
35+
if (Options.Verbose && !String.IsNullOrEmpty(errorId) && errorId != "none") {
36+
errorLine += " (ID: " + errorId + ")\n";
37+
var info = ErrorRegistry.GetDetail(errorId);
38+
if (info != null) {
39+
errorLine += info; // already ends with eol character
2840
}
29-
var errorLine = ErrorToString(level, tok, msg);
30-
while (tok is NestedToken nestedToken) {
31-
tok = nestedToken.Inner;
32-
if (tok.Filepath == nestedToken.Filepath &&
33-
tok.line == nestedToken.line &&
34-
tok.col == nestedToken.col) {
35-
continue;
36-
}
37-
msg = nestedToken.Message ?? "[Related location]";
38-
errorLine += $" {msg} {tok.TokenToString(Options)}";
41+
} else {
42+
errorLine += "\n";
43+
}
44+
45+
var innerToken = tok;
46+
while (innerToken is NestedToken nestedToken) {
47+
innerToken = nestedToken.Inner;
48+
if (innerToken.Filepath == nestedToken.Filepath &&
49+
innerToken.line == nestedToken.line &&
50+
innerToken.col == nestedToken.col) {
51+
continue;
3952
}
4053

41-
if (Options.Verbose && !String.IsNullOrEmpty(errorId) && errorId != "none") {
42-
errorLine += " (ID: " + errorId + ")\n";
43-
var info = ErrorRegistry.GetDetail(errorId);
44-
if (info != null) {
45-
errorLine += info; // already ends with eol character
46-
}
54+
var innerMessage = nestedToken.Message;
55+
if (innerMessage == null) {
56+
innerMessage = "Related location";
4757
} else {
48-
errorLine += "\n";
58+
innerMessage = "Related location: " + innerMessage;
4959
}
5060

51-
Options.OutputWriter.Write(errorLine);
61+
errorLine += $"{innerToken.TokenToString(Options)}: {innerMessage}\n";
62+
}
5263

53-
if (Options.Get(DafnyConsolePrinter.ShowSnippets) && tok.Uri != null) {
54-
TextWriter tw = new StringWriter();
55-
new DafnyConsolePrinter(Options).WriteSourceCodeSnippet(tok.ToRange(), tw);
56-
Options.OutputWriter.Write(tw.ToString());
57-
}
64+
Options.OutputWriter.Write(errorLine);
5865

59-
if (Options.OutputWriter == Console.Out) {
60-
Console.ForegroundColor = previousColor;
61-
}
62-
return true;
66+
67+
if (Options.Get(DafnyConsolePrinter.ShowSnippets) && tok.Uri != null) {
68+
TextWriter tw = new StringWriter();
69+
new DafnyConsolePrinter(Options).WriteSourceCodeSnippet(tok.ToRange(), tw);
70+
Options.OutputWriter.Write(tw.ToString());
71+
}
72+
73+
if (Options.OutputWriter == Console.Out) {
74+
Console.ForegroundColor = previousColor;
6375
}
6476

65-
return false;
77+
return true;
6678
}
6779

6880
public ConsoleErrorReporter(DafnyOptions options) : base(options) {

Source/DafnyCore/Generic/ErrorReporter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ protected ErrorReporter(DafnyOptions options) {
1717
public bool HasErrorsUntilResolver => ErrorCountUntilResolver > 0;
1818
public int ErrorCountUntilResolver => CountExceptVerifierAndCompiler(ErrorLevel.Error);
1919

20-
public virtual bool Message(MessageSource source, ErrorLevel level, string errorId, IToken tok, string msg) {
20+
public bool Message(MessageSource source, ErrorLevel level, string errorId, IToken tok, string msg) {
2121
if (Options.WarningsAsErrors && level == ErrorLevel.Warning) {
2222
level = ErrorLevel.Error;
2323
}

Source/DafnyCore/JsonDiagnostics.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,10 @@ private static JsonObject SerializeRelated(Boogie.IToken tok, string? category,
6161
}
6262

6363
private static IEnumerable<JsonNode> SerializeInnerTokens(Boogie.IToken tok) {
64-
while (tok is NestedToken ntok) {
65-
tok = ntok.Inner;
66-
yield return SerializeRelated(tok, null, "Related location");
64+
while (tok is NestedToken nestedToken) {
65+
tok = nestedToken.Inner;
66+
var message = nestedToken.Message != null ? "Related location: " + nestedToken.Message : "Related location";
67+
yield return SerializeRelated(tok, null, message);
6768
}
6869
}
6970

@@ -112,7 +113,7 @@ public DafnyJsonConsolePrinter(DafnyOptions options) : base(options) {
112113
public class JsonConsoleErrorReporter : BatchErrorReporter {
113114
protected override bool MessageCore(MessageSource source, ErrorLevel level, string errorID, Dafny.IToken tok, string msg) {
114115
if (base.MessageCore(source, level, errorID, tok, msg) && (Options is { PrintTooltips: true } || level != ErrorLevel.Info)) {
115-
new DiagnosticMessageData(source, level, tok, null, msg, null).WriteJsonTo(Options.OutputWriter);
116+
new DiagnosticMessageData(source, level, tok, level == ErrorLevel.Error ? "Error" : null, msg, null).WriteJsonTo(Options.OutputWriter);
116117
return true;
117118
}
118119

@@ -121,4 +122,4 @@ protected override bool MessageCore(MessageSource source, ErrorLevel level, stri
121122

122123
public JsonConsoleErrorReporter(DafnyOptions options) : base(options) {
123124
}
124-
}
125+
}

Source/DafnyCore/Resolver/ProgramResolver.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@ protected void InstantiateReplaceableModules(Program dafnyProgram) {
219219
if (compiledModule.Implements is { Kind: ImplementationKind.Replacement }) {
220220
var target = compiledModule.Implements.Target.Def;
221221
if (target.Replacement != null) {
222-
Reporter!.Error(MessageSource.Compiler, new NestedToken(compiledModule.Tok, target.Replacement.Tok, "Other replacing module:"),
222+
Reporter!.Error(MessageSource.Compiler, new NestedToken(compiledModule.Tok, target.Replacement.Tok,
223+
$"other replacing module"),
223224
"a replaceable module may only be replaced once");
224225
} else {
225226
target.Replacement = compiledModule.Replacement ?? compiledModule;

Source/DafnyLanguageServer/Handlers/DafnyWorkspaceSymbolHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ protected override WorkspaceSymbolRegistrationOptions CreateRegistrationOptions(
5858
Name = name,
5959
ContainerName = def.Kind.ToString(),
6060
Kind = FromDafnySymbolKind(def.Kind),
61-
Location = Workspace.Util.CreateLocation(def.NameToken)
61+
Location = Workspace.DiagnosticUtil.CreateLocation(def.NameToken)
6262
}, matchDistance);
6363
}
6464
}

0 commit comments

Comments
 (0)