Skip to content

Commit 3c7d9ca

Browse files
committed
Generate expressive diagnostic messages
1 parent 9aaeadc commit 3c7d9ca

File tree

11 files changed

+248
-4
lines changed

11 files changed

+248
-4
lines changed

changelog/error-context.dd

+20
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

+1
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

+3
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

+25
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

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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+
import core.memory : GC;
75+
file.__dtor();
76+
GC.free(file);
77+
file = null;
78+
lines.__dtor();
79+
GC.free(lines);
80+
lines = null;
81+
}
82+
}
83+
~this()
84+
{
85+
destroy();
86+
}
87+
}
88+
89+
/**
90+
A simple file cache that can be used to avoid reading the same file multiple times.
91+
It stores its cached files as $(LREF FileAndLines)
92+
*/
93+
struct FileCache
94+
{
95+
private StringTable files;
96+
97+
/**
98+
Add or get a file from the file cache.
99+
If the file isn't part of the cache, it will be read from the filesystem.
100+
If the file has been read before, the cached file object will be returned
101+
102+
Params:
103+
file = file to load in (or get from) the cache
104+
105+
Returns: a $(LREF FileAndLines) object containing a line-by-line representation of the requested file
106+
*/
107+
FileAndLines addOrGetFile(const(char)[] file)
108+
{
109+
if (auto payload = files.lookup(file.ptr, file.length))
110+
if (payload !is null)
111+
return cast(typeof(return)) payload.ptrvalue;
112+
113+
auto lines = new FileAndLines(file.ptr);
114+
auto p = files.insert(file.ptr, file.length, cast(void*) lines);
115+
return cast(typeof(return)) p.ptrvalue;
116+
}
117+
118+
void initialize()
119+
{
120+
files._init();
121+
}
122+
123+
void deinitialize()
124+
{
125+
foreach (sv; files)
126+
sv.destroy();
127+
files.reset();
128+
}
129+
}
130+
131+
static fileCache = FileCache();
132+
133+
shared static this()
134+
{
135+
fileCache.initialize();
136+
}
137+
shared static ~this()
138+
{
139+
fileCache.deinitialize();
140+
}

src/dmd/globals.d

+1
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

+4
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

+14
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

+3-3
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

+1-1
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 \
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
REQUIRED_ARGS: -verrors=pretty
3+
TEST_OUTPUT:
4+
---
5+
fail_compilation/fail_pretty_errors.d(20): Error: undefined identifier `a`
6+
a = 1;
7+
^
8+
fail_compilation/fail_pretty_errors.d-mixin-25(25): Error: undefined identifier `b`
9+
fail_compilation/fail_pretty_errors.d(30): Error: cannot implicitly convert expression `5` of type `int` to `string`
10+
string x = 5;
11+
^
12+
fail_compilation/fail_pretty_errors.d(35): Error: mixin `fail_pretty_errors.testMixin2.mixinTemplate!()` error instantiating
13+
mixin mixinTemplate;
14+
^
15+
---
16+
*/
17+
18+
void foo()
19+
{
20+
a = 1;
21+
}
22+
23+
void testMixin1()
24+
{
25+
mixin("b = 1;");
26+
}
27+
28+
mixin template mixinTemplate()
29+
{
30+
string x = 5;
31+
}
32+
33+
void testMixin2()
34+
{
35+
mixin mixinTemplate;
36+
}

0 commit comments

Comments
 (0)