Skip to content

Commit 5be6c0d

Browse files
committed
Generate expressive diagnostic messages
1 parent ba2e27f commit 5be6c0d

File tree

12 files changed

+239
-4
lines changed

12 files changed

+239
-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
@@ -586,6 +586,9 @@ dmd -cov -unittest myprog.d
586586
Option("verrors=spec",
587587
"show errors from speculative compiles such as __traits(compiles,...)"
588588
),
589+
Option("verrors=context",
590+
"show error messages with the context of the erroring source line"
591+
),
589592
Option("-version",
590593
"print compiler version and exit"
591594
),

src/dmd/errors.d

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

src/dmd/filecache.d

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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, filecache.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+
/**
22+
A line-by-line representation of a $(REF File, dmd,root,file).
23+
*/
24+
class FileAndLines
25+
{
26+
File* file;
27+
const(char[])[] lines;
28+
29+
/**
30+
File to read and split into its lines.
31+
*/
32+
this(const(char)[] filename)
33+
{
34+
file = new File(filename);
35+
readAndSplit();
36+
}
37+
38+
// Read a file and split the file buffer linewise
39+
private void readAndSplit()
40+
{
41+
file.read();
42+
auto buf = file.buffer;
43+
// slice into lines
44+
while (*buf)
45+
{
46+
auto prevBuf = buf;
47+
for (; *buf != '\n' && *buf != '\r'; buf++)
48+
{
49+
if (!*buf)
50+
break;
51+
}
52+
// handle Windows line endings
53+
if (*buf == '\r' && *(buf + 1) == '\n')
54+
buf++;
55+
lines ~= cast(const(char)[]) prevBuf[0 .. buf - prevBuf];
56+
buf++;
57+
}
58+
}
59+
60+
void destroy()
61+
{
62+
if (file)
63+
{
64+
file.destroy();
65+
file = null;
66+
lines.destroy();
67+
lines = null;
68+
}
69+
}
70+
71+
~this()
72+
{
73+
destroy();
74+
}
75+
}
76+
77+
/**
78+
A simple file cache that can be used to avoid reading the same file multiple times.
79+
It stores its cached files as $(LREF FileAndLines)
80+
*/
81+
struct FileCache
82+
{
83+
private StringTable files;
84+
85+
/**
86+
Add or get a file from the file cache.
87+
If the file isn't part of the cache, it will be read from the filesystem.
88+
If the file has been read before, the cached file object will be returned
89+
90+
Params:
91+
file = file to load in (or get from) the cache
92+
93+
Returns: a $(LREF FileAndLines) object containing a line-by-line representation of the requested file
94+
*/
95+
FileAndLines addOrGetFile(const(char)[] file)
96+
{
97+
if (auto payload = files.lookup(file))
98+
{
99+
if (payload !is null)
100+
return cast(typeof(return)) payload.ptrvalue;
101+
}
102+
103+
auto lines = new FileAndLines(file);
104+
files.insert(file, cast(void*) lines);
105+
return lines;
106+
}
107+
108+
__gshared fileCache = FileCache();
109+
110+
// Initializes the global FileCache singleton
111+
static __gshared void _init()
112+
{
113+
fileCache.initialize();
114+
}
115+
116+
void initialize()
117+
{
118+
files._init();
119+
}
120+
121+
void deinitialize()
122+
{
123+
foreach (sv; files)
124+
sv.destroy();
125+
files.reset();
126+
}
127+
}

src/dmd/frontend.d

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ void initDMD()
5353
import dmd.builtin : builtin_init;
5454
import dmd.dmodule : Module;
5555
import dmd.expression : Expression;
56+
import dmd.filecache : FileCache;
5657
import dmd.globals : global;
5758
import dmd.id : Id;
5859
import dmd.mars : setTarget, addDefaultVersionIdentifiers;
@@ -71,6 +72,7 @@ void initDMD()
7172
Expression._init();
7273
Objc._init();
7374
builtin_init();
75+
FileCache._init();
7476
}
7577

7678
/**

src/dmd/globals.d

+1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ struct Param
161161
bool vmarkdown; // list instances of Markdown replacements in Ddoc
162162

163163
bool showGaggedErrors; // print gagged errors anyway
164+
bool printErrorContext; // print errors with the error context (the error line in the source file)
164165
bool manual; // open browser on compiler manual
165166
bool usage; // print usage and exit
166167
bool mcpuUsage; // print help on -mcpu switch

src/dmd/mars.d

+6
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,8 @@ private int tryMain(size_t argc, const(char)** argv)
466466
Expression._init();
467467
Objc._init();
468468
builtin_init();
469+
import dmd.filecache : FileCache;
470+
FileCache._init();
469471

470472
version(CRuntime_Microsoft)
471473
{
@@ -1723,6 +1725,10 @@ bool parseCommandLine(const ref Strings arguments, const size_t argc, ref Param
17231725
{
17241726
params.showGaggedErrors = true;
17251727
}
1728+
else if (startsWith(p + 9, "context"))
1729+
{
1730+
params.printErrorContext = true;
1731+
}
17261732
else
17271733
goto Lerror;
17281734
}

src/dmd/root/stringtable.d

+14
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,20 @@ public:
217217
return 0;
218218
}
219219

220+
extern(D) int opApply(scope int delegate(const(StringValue)*) dg)
221+
{
222+
foreach (const se; table[0 .. tabledim])
223+
{
224+
if (!se.vptr)
225+
continue;
226+
const sv = getValue(se.vptr);
227+
int result = dg(sv);
228+
if (result)
229+
return result;
230+
}
231+
return 0;
232+
}
233+
220234
private:
221235
nothrow:
222236
uint allocValue(const(char)[] str, void* ptrvalue)

src/posix.mak

+3-3
Original file line numberDiff line numberDiff line change
@@ -319,10 +319,10 @@ FRONT_SRCS=$(addsuffix .d, $(addprefix $D/,access aggregate aliasthis apply argt
319319
typinf utils scanelf scanmach statement_rewrite_walker statementsem staticcond safe blockexit printast \
320320
semantic2 semantic3))
321321

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

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

327327
ROOT_SRCS = $(addsuffix .d,$(addprefix $(ROOT)/,aav array ctfloat file \
328328
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=context
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)