Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve template constraint error messages #9715

Merged
merged 8 commits into from
Jul 11, 2019
173 changes: 170 additions & 3 deletions src/dmd/dtemplate.d
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,8 @@ struct TemplatePrevious
*/
extern (C++) final class TemplateDeclaration : ScopeDsymbol
{
import dmd.root.array : Array;

TemplateParameters* parameters; // array of TemplateParameter's
TemplateParameters* origParameters; // originals for Ddoc

Expand All @@ -538,6 +540,10 @@ extern (C++) final class TemplateDeclaration : ScopeDsymbol
// threaded list of previous instantiation attempts on stack
TemplatePrevious* previous;

private Expression lastConstraint; /// the constraint after the last failed evaluation
private Array!Expression lastConstraintNegs; /// its negative parts
private Objects* lastConstraintTiargs; /// template instance arguments for `lastConstraint`

extern (D) this(const ref Loc loc, Identifier ident, TemplateParameters* parameters, Expression constraint, Dsymbols* decldefs, bool ismixin = false, bool literal = false)
{
super(loc, ident);
Expand Down Expand Up @@ -680,6 +686,39 @@ extern (C++) final class TemplateDeclaration : ScopeDsymbol
return buf.extractChars();
}

/****************************
* Similar to `toChars`, but does not print the template constraints
*/
const(char)* toCharsNoConstraints()
{
if (literal)
return Dsymbol.toChars();

OutBuffer buf;
HdrGenState hgs;

buf.writestring(ident.toChars());
buf.writeByte('(');
foreach (i, tp; *parameters)
{
if (i > 0)
buf.writestring(", ");
.toCBuffer(tp, &buf, &hgs);
}
buf.writeByte(')');

if (onemember)
{
FuncDeclaration fd = onemember.isFuncDeclaration();
if (fd && fd.type)
{
TypeFunction tf = fd.type.isTypeFunction();
buf.writestring(parametersTypeToChars(tf.parameterList));
}
}
return buf.extractChars();
}

override Prot prot() pure nothrow @nogc @safe
{
return protection;
Expand Down Expand Up @@ -782,15 +821,23 @@ extern (C++) final class TemplateDeclaration : ScopeDsymbol
fd.selectorParameter = hiddenParams.selectorParameter;
}

Expression e = constraint.syntaxCopy();
lastConstraint = constraint.syntaxCopy();
lastConstraintTiargs = ti.tiargs;
lastConstraintNegs.setDim(0);

import dmd.staticcond;

assert(ti.inst is null);
ti.inst = ti; // temporary instantiation to enable genIdent()
scx.flags |= SCOPE.constraint;
bool errors;
bool result = evalStaticCondition(scx, constraint, e, errors);
const bool result = evalStaticCondition(scx, constraint, lastConstraint, errors, &lastConstraintNegs);
if (result || errors)
{
lastConstraint = null;
lastConstraintTiargs = null;
lastConstraintNegs.setDim(0);
}
ti.inst = null;
ti.symtab = null;
scx = scx.pop();
Expand All @@ -800,6 +847,115 @@ extern (C++) final class TemplateDeclaration : ScopeDsymbol
return result;
}

/****************************
* Destructively get the error message from the last constraint evaluation
* Params:
* tip = tip to show after printing all overloads
*/
const(char)* getConstraintEvalError(ref const(char)* tip)
{
import dmd.staticcond;

// there will be a full tree view in verbose mode, and more compact list in the usual
const full = global.params.verbose;
uint count;
const msg = visualizeStaticCondition(constraint, lastConstraint, lastConstraintNegs[], full, count);
scope (exit)
{
lastConstraint = null;
lastConstraintTiargs = null;
lastConstraintNegs.setDim(0);
}
if (msg)
{
OutBuffer buf;

assert(parameters && lastConstraintTiargs);
if (parameters.length > 0)
{
formatParamsWithTiargs(*lastConstraintTiargs, buf);
buf.writenl();
}
if (!full)
{
// choosing singular/plural
const s = (count == 1) ?
" must satisfy the following constraint:" :
" must satisfy one of the following constraints:";
buf.writestring(s);
buf.writenl();
// the constraints
buf.writeByte('`');
buf.writestring(msg);
buf.writeByte('`');
}
else
{
buf.writestring(" whose parameters have the following constraints:");
buf.writenl();
const sep = " `~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`";
buf.writestring(sep);
buf.writenl();
// the constraints
buf.writeByte('`');
buf.writestring(msg);
buf.writeByte('`');
buf.writestring(sep);
tip = "not satisfied constraints are marked with `>`";
}
return buf.extractChars();
}
else
return null;
}

private void formatParamsWithTiargs(ref Objects tiargs, ref OutBuffer buf)
{
buf.writestring(" with `");

// write usual arguments line-by-line
// skips trailing default ones - they are not present in `tiargs`
const bool variadic = isVariadic() !is null;
const end = cast(int)parameters.length - (variadic ? 1 : 0);
uint i;
for (; i < tiargs.length && i < end; i++)
{
if (i > 0)
{
buf.writeByte(',');
buf.writenl();
buf.writestring(" ");
}
buf.write((*parameters)[i]);
buf.writestring(" = ");
buf.write(tiargs[i]);
}
// write remaining variadic arguments on the last line
if (variadic)
{
if (i > 0)
{
buf.writeByte(',');
buf.writenl();
buf.writestring(" ");
}
buf.write((*parameters)[end]);
buf.writestring(" = ");
buf.writeByte('(');
if (cast(int)tiargs.length - end > 0)
{
buf.write(tiargs[end]);
foreach (j; parameters.length .. tiargs.length)
{
buf.writestring(", ");
buf.write(tiargs[j]);
}
}
buf.writeByte(')');
}
buf.writeByte('`');
}

/******************************
* Create a scope for the parameters of the TemplateInstance
* `ti` in the parent scope sc from the ScopeDsymbol paramsym.
Expand Down Expand Up @@ -7073,7 +7229,18 @@ extern (C++) class TemplateInstance : ScopeDsymbol
else if (tdecl && !tdecl.overnext)
{
// Only one template, so we can give better error message
error("does not match template declaration `%s`", tdecl.toChars());
const(char)* msg = "does not match template declaration";
const(char)* tip;
const tmsg = tdecl.toCharsNoConstraints();
const cmsg = tdecl.getConstraintEvalError(tip);
if (cmsg)
{
error("%s `%s`\n%s", msg, tmsg, cmsg);
if (tip)
.tip(tip);
}
else
error("%s `%s`", msg, tmsg);
}
else
.error(loc, "%s `%s.%s` does not match any template declaration", tempdecl.kind(), tempdecl.parent.toPrettyChars(), tempdecl.ident.toChars());
Expand Down
30 changes: 30 additions & 0 deletions src/dmd/errors.d
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ enum Classification
gagged = Color.brightBlue, /// for gagged errors
warning = Color.brightYellow, /// for warnings
deprecation = Color.brightCyan, /// for deprecations
tip = Color.brightGreen, /// for tip messages
}

/**
Expand Down Expand Up @@ -400,6 +401,20 @@ extern (C++) void message(const(char)* format, ...)
va_end(ap);
}

/**
* Print a tip message with the prefix and highlighting.
* Params:
* format = printf-style format specification
* ... = printf-style variadic arguments
*/
extern (C++) void tip(const(char)* format, ...)
{
va_list ap;
va_start(ap, format);
vtip(format, ap);
va_end(ap);
}

/**
* Just print to stderr, doesn't care about gagging.
* (format,ap) text within backticks gets syntax highlighted.
Expand Down Expand Up @@ -610,6 +625,21 @@ extern (C++) void vmessage(const ref Loc loc, const(char)* format, va_list ap)
fflush(stdout); // ensure it gets written out in case of compiler aborts
}

/**
* Same as $(D tip), but takes a va_list parameter.
* Params:
* format = printf-style format specification
* ap = printf-style variadic arguments
*/
extern (C++) void vtip(const(char)* format, va_list ap)
{
if (!global.gag)
{
Loc loc = Loc.init;
verrorPrint(loc, Classification.tip, " Tip: ", format, ap);
}
}

/**
* Same as $(D deprecationSupplemental), but takes a va_list parameter.
* Params:
Expand Down
13 changes: 12 additions & 1 deletion src/dmd/func.d
Original file line number Diff line number Diff line change
Expand Up @@ -2939,6 +2939,7 @@ if (is(Decl == TemplateDeclaration) || is(Decl == FuncDeclaration))
{
// max num of overloads to print (-v overrides this).
int numToDisplay = 5;
const(char)* constraintsTip;

overloadApply(declaration, (Dsymbol s)
{
Expand All @@ -2956,7 +2957,14 @@ if (is(Decl == TemplateDeclaration) || is(Decl == FuncDeclaration))
}
else if (auto td = s.isTemplateDeclaration())
{
.errorSupplemental(td.loc, "`%s`", td.toPrettyChars());
import dmd.staticcond;

const tmsg = td.toCharsNoConstraints();
const cmsg = td.getConstraintEvalError(constraintsTip);
if (cmsg)
.errorSupplemental(td.loc, "`%s`\n%s", tmsg, cmsg);
else
.errorSupplemental(td.loc, "`%s`", tmsg);
nextOverload = td.overnext;
}

Expand All @@ -2972,6 +2980,9 @@ if (is(Decl == TemplateDeclaration) || is(Decl == FuncDeclaration))
.errorSupplemental(loc, "... (%d more, -v to show) ...", num);
return 1; // stop iterating
});
// should be only in verbose mode
if (constraintsTip)
.tip(constraintsTip);
}

/**************************************
Expand Down
Loading