Skip to content

Commit 835a240

Browse files
committed
Generate expressive diagnostic messages
1 parent 9aaeadc commit 835a240

File tree

11 files changed

+245
-4
lines changed

11 files changed

+245
-4
lines changed

changelog/error-context.dd

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
dmd now supports expressive diagnostic error messages with `-verrors=context`
2+
3+
With the new CLI option `-verrors=context` dmd will now show the offending line directly in its error messages.
4+
Consider this faulty program `test.d`:
5+
6+
---
7+
void foo()
8+
{
9+
a = 1;
10+
}
11+
---
12+
13+
Now run it with `-verrors=context`:
14+
15+
$(CONSOLE
16+
> dmd -verrors=context test.d
17+
test.d(4): $(RED Error): undefined identifier a
18+
a = 1;
19+
^
20+
)

dub.sdl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ subPackage {
2222
"src/dmd/console.d" \
2323
"src/dmd/entity.d" \
2424
"src/dmd/errors.d" \
25+
"src/dmd/filecache.d" \
2526
"src/dmd/globals.d" \
2627
"src/dmd/id.d" \
2728
"src/dmd/identifier.d" \

src/dmd/cli.d

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,9 @@ dmd -cov -unittest myprog.d
553553
Option("verrors=spec",
554554
"show errors from speculative compiles such as __traits(compiles,...)"
555555
),
556+
Option("verrors=context",
557+
"show error messages with the context of the erroring source line"
558+
),
556559
Option("-version",
557560
"print compiler version and exit"
558561
),

src/dmd/errors.d

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,31 @@ private void verrorPrint(const ref Loc loc, Color headerColor, const(char)* head
236236
else
237237
fputs(tmp.peekString(), stderr);
238238
fputc('\n', stderr);
239+
240+
if (global.params.contextPrintErrors &&
241+
// ignore invalid files
242+
loc != Loc.initial &&
243+
// ignore mixins for now
244+
!loc.filename.strstr(".d-mixin-"))
245+
{
246+
import dmd.filecache : fileCache;
247+
auto fllines = fileCache.addOrGetFile(loc.filename[0 .. strlen(loc.filename)]);
248+
249+
if (loc.linnum - 1 < fllines.lines.dim)
250+
{
251+
auto line = (*fllines.lines)[loc.linnum - 1];
252+
if (loc.charnum < line.length)
253+
{
254+
fprintf(stderr, "%.*s\n", line.length, line.ptr);
255+
foreach (_; 1 .. loc.charnum)
256+
fputc(' ', stderr);
257+
258+
fputc('^', stderr);
259+
fputc('\n', stderr);
260+
}
261+
}
262+
}
263+
end:
239264
fflush(stderr); // ensure it gets written out in case of compiler aborts
240265
}
241266

src/dmd/filecache.d

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/**
2+
* Compiler implementation of the
3+
* $(LINK2 http://www.dlang.org, D programming language).
4+
*
5+
* Copyright: Copyright (C) 1999-2018 by The D Language Foundation, All Rights Reserved
6+
* Authors: $(LINK2 http://www.digitalmars.com, Walter Bright)
7+
* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
8+
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/filecache.d, _errors.d)
9+
* Documentation: https://dlang.org/phobos/dmd_filecache.html
10+
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/filecache.d
11+
*/
12+
13+
module dmd.filecache;
14+
15+
import dmd.root.stringtable;
16+
import dmd.root.array;
17+
import dmd.root.file;
18+
19+
import core.stdc.stdio;
20+
21+
// A simple wrapper around a string in D for extern(C++) compatibility
22+
extern(C++) struct Dstring
23+
{
24+
char* ptr;
25+
size_t length;
26+
extern(D) string toString()
27+
{
28+
return cast(string) ptr[0 .. length];
29+
}
30+
}
31+
32+
/**
33+
A line-by-line representation of a $(REF File, dmd, root, file).
34+
*/
35+
class FileAndLines
36+
{
37+
File* file;
38+
Array!Dstring* lines;
39+
40+
/**
41+
File to read and split into its lines.
42+
*/
43+
this(const(char)* filename)
44+
{
45+
file = File.create(filename);
46+
lines = new Array!Dstring;
47+
readAndSplit();
48+
}
49+
50+
// Read a file and split the file buffer linewise
51+
private void readAndSplit()
52+
{
53+
file.read();
54+
auto buf = file.buffer;
55+
// slice into lines
56+
while (*buf)
57+
{
58+
auto prevBuf = buf;
59+
for (; *buf != '\n'; buf++)
60+
{
61+
if (!*buf)
62+
break;
63+
}
64+
if (buf != prevBuf)
65+
lines.push(Dstring(cast(char*) prevBuf, buf - prevBuf));
66+
buf++;
67+
}
68+
}
69+
70+
void destroy()
71+
{
72+
if (file)
73+
{
74+
file.destroy();
75+
file = null;
76+
lines.destroy();
77+
lines = null;
78+
}
79+
}
80+
~this()
81+
{
82+
destroy();
83+
}
84+
}
85+
86+
/**
87+
A simple file cache that can be used to avoid reading the same file multiple times.
88+
It stores its cached files as $(LREF FileAndLines)
89+
*/
90+
struct FileCache
91+
{
92+
private StringTable files;
93+
94+
/**
95+
Add or get a file from the file cache.
96+
If the file isn't part of the cache, it will be read from the filesystem.
97+
If the file has been read before, the cached file object will be returned
98+
99+
Params:
100+
file = file to load in (or get from) the cache
101+
102+
Returns: a $(LREF FileAndLines) object containing a line-by-line representation of the requested file
103+
*/
104+
FileAndLines addOrGetFile(const(char)[] file)
105+
{
106+
if (auto payload = files.lookup(file.ptr, file.length))
107+
if (payload !is null)
108+
return cast(typeof(return)) payload.ptrvalue;
109+
110+
auto lines = new FileAndLines(file.ptr);
111+
auto p = files.insert(file.ptr, file.length, cast(void*) lines);
112+
return cast(typeof(return)) p.ptrvalue;
113+
}
114+
115+
void initialize()
116+
{
117+
files._init();
118+
}
119+
120+
void deinitialize()
121+
{
122+
foreach (sv; files)
123+
sv.destroy();
124+
files.reset();
125+
}
126+
}
127+
128+
static fileCache = FileCache();
129+
130+
shared static this()
131+
{
132+
fileCache.initialize();
133+
}
134+
shared static ~this()
135+
{
136+
fileCache.deinitialize();
137+
}

src/dmd/globals.d

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ struct Param
158158
*/
159159

160160
bool showGaggedErrors; // print gagged errors anyway
161+
bool contextPrintErrors; // print errors with the error context (the error line in the source file)
161162
bool manual; // open browser on compiler manual
162163
bool usage; // print usage and exit
163164
bool mcpuUsage; // print help on -mcpu switch

src/dmd/mars.d

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,6 +1732,10 @@ private bool parseCommandLine(const ref Strings arguments, const size_t argc, re
17321732
{
17331733
params.showGaggedErrors = true;
17341734
}
1735+
else if (startsWith(p + 9, "context"))
1736+
{
1737+
params.contextPrintErrors = true;
1738+
}
17351739
else
17361740
goto Lerror;
17371741
}

src/dmd/root/stringtable.d

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,20 @@ public:
168168
return 0;
169169
}
170170

171+
extern(D) int opApply(scope int delegate(const(StringValue)*) dg)
172+
{
173+
foreach (const se; table[0 .. tabledim])
174+
{
175+
if (!se.vptr)
176+
continue;
177+
const sv = getValue(se.vptr);
178+
int result = dg(sv);
179+
if (result)
180+
return result;
181+
}
182+
return 0;
183+
}
184+
171185
StringValue* update(const(char)[] name) nothrow
172186
{
173187
return update(name.ptr, name.length);

src/posix.mak

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -307,10 +307,10 @@ FRONT_SRCS=$(addsuffix .d, $(addprefix $D/,access aggregate aliasthis apply argt
307307
typinf utils scanelf scanmach statement_rewrite_walker statementsem staticcond safe blockexit printast \
308308
semantic2 semantic3))
309309

310-
LEXER_SRCS=$(addsuffix .d, $(addprefix $D/, console entity errors globals id identifier lexer tokens utf))
310+
LEXER_SRCS=$(addsuffix .d, $(addprefix $D/, console entity errors filecache globals id identifier lexer tokens utf root/stringtable root/file))
311311

312-
LEXER_ROOT=$(addsuffix .d, $(addprefix $(ROOT)/, array ctfloat file filename outbuffer port rmem \
313-
rootobject stringtable hash))
312+
LEXER_ROOT=$(addsuffix .d, $(addprefix $(ROOT)/, array ctfloat filename outbuffer port rmem \
313+
rootobject hash))
314314

315315
ROOT_SRCS = $(addsuffix .d,$(addprefix $(ROOT)/,aav array ctfloat file \
316316
filename man outbuffer port response rmem rootobject speller \

src/win32.mak

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ FRONT_SRCS=$D/access.d $D/aggregate.d $D/aliasthis.d $D/apply.d $D/argtypes.d $D
165165
$D/libmscoff.d $D/scanmscoff.d $D/statement_rewrite_walker.d $D/statementsem.d $D/staticcond.d \
166166
$D/semantic2.d $D/semantic3.d
167167

168-
LEXER_SRCS=$D/console.d $D/entity.d $D/errors.d $D/globals.d $D/id.d $D/identifier.d \
168+
LEXER_SRCS=$D/console.d $D/entity.d $D/errors.d $D/filecache.d $D/globals.d $D/id.d $D/identifier.d \
169169
$D/lexer.d $D/tokens.d $D/utf.d
170170

171171
LEXER_ROOT=$(ROOT)/array.d $(ROOT)/ctfloat.d $(ROOT)/file.d $(ROOT)/filename.d \

0 commit comments

Comments
 (0)