blob: aa6f37ca67b478a9257d5a1e19814edca1e80104 [file] [log] [blame]
/**
* Lazily evaluate static conditions for `static if`, `static assert` and template constraints.
*
* Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/staticcond.d, _staticcond.d)
* Documentation: https://dlang.org/phobos/dmd_staticcond.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/staticcond.d
*/
module dmd.staticcond;
import dmd.arraytypes;
import dmd.dmodule;
import dmd.dscope;
import dmd.dsymbol;
import dmd.errors;
import dmd.expression;
import dmd.expressionsem;
import dmd.globals;
import dmd.identifier;
import dmd.mtype;
import dmd.root.array;
import dmd.common.outbuffer;
import dmd.tokens;
/********************************************
* Semantically analyze and then evaluate a static condition at compile time.
* This is special because short circuit operators &&, || and ?: at the top
* level are not semantically analyzed if the result of the expression is not
* necessary.
* Params:
* sc = instantiating scope
* original = original expression, for error messages
* e = resulting expression
* errors = set to `true` if errors occurred
* negatives = array to store negative clauses
* Returns:
* true if evaluates to true
*/
bool evalStaticCondition(Scope* sc, Expression original, Expression e, out bool errors, Expressions* negatives = null)
{
if (negatives)
negatives.setDim(0);
bool impl(Expression e)
{
if (e.isNotExp())
{
NotExp ne = cast(NotExp)e;
return !impl(ne.e1);
}
if (e.op == EXP.andAnd || e.op == EXP.orOr)
{
LogicalExp aae = cast(LogicalExp)e;
bool result = impl(aae.e1);
if (errors)
return false;
if (e.op == EXP.andAnd)
{
if (!result)
return false;
}
else
{
if (result)
return true;
}
result = impl(aae.e2);
return !errors && result;
}
if (e.op == EXP.question)
{
CondExp ce = cast(CondExp)e;
bool result = impl(ce.econd);
if (errors)
return false;
Expression leg = result ? ce.e1 : ce.e2;
result = impl(leg);
return !errors && result;
}
Expression before = e;
const uint nerrors = global.errors;
sc = sc.startCTFE();
sc.flags |= SCOPE.condition;
e = e.expressionSemantic(sc);
e = resolveProperties(sc, e);
e = e.toBoolean(sc);
sc = sc.endCTFE();
e = e.optimize(WANTvalue);
if (nerrors != global.errors ||
e.isErrorExp() ||
e.type.toBasetype() == Type.terror)
{
errors = true;
return false;
}
e = e.ctfeInterpret();
const opt = e.toBool();
if (opt.isEmpty())
{
e.error("expression `%s` is not constant", e.toChars());
errors = true;
return false;
}
if (negatives && !opt.get())
negatives.push(before);
return opt.get();
}
return impl(e);
}
/********************************************
* Format a static condition as a tree-like structure, marking failed and
* bypassed expressions.
* Params:
* original = original expression
* instantiated = instantiated expression
* negatives = array with negative clauses from `instantiated` expression
* full = controls whether it shows the full output or only failed parts
* itemCount = returns the number of written clauses
* Returns:
* formatted string or `null` if the expressions were `null`, or if the
* instantiated expression is not based on the original one
*/
const(char)* visualizeStaticCondition(Expression original, Expression instantiated,
const Expression[] negatives, bool full, ref uint itemCount)
{
if (!original || !instantiated || original.loc !is instantiated.loc)
return null;
OutBuffer buf;
if (full)
itemCount = visualizeFull(original, instantiated, negatives, buf);
else
itemCount = visualizeShort(original, instantiated, negatives, buf);
return buf.extractChars();
}
private uint visualizeFull(Expression original, Expression instantiated,
const Expression[] negatives, ref OutBuffer buf)
{
// tree-like structure; traverse and format simultaneously
uint count;
uint indent;
static void printOr(uint indent, ref OutBuffer buf)
{
buf.reserve(indent * 4 + 8);
foreach (i; 0 .. indent)
buf.writestring(" ");
buf.writestring(" or:\n");
}
// returns true if satisfied
bool impl(Expression orig, Expression e, bool inverted, bool orOperand, bool unreached)
{
EXP op = orig.op;
// lower all 'not' to the bottom
// !(A && B) -> !A || !B
// !(A || B) -> !A && !B
if (inverted)
{
if (op == EXP.andAnd)
op = EXP.orOr;
else if (op == EXP.orOr)
op = EXP.andAnd;
}
if (op == EXP.not)
{
NotExp no = cast(NotExp)orig;
NotExp ne = cast(NotExp)e;
assert(ne);
return impl(no.e1, ne.e1, !inverted, orOperand, unreached);
}
else if (op == EXP.andAnd)
{
BinExp bo = cast(BinExp)orig;
BinExp be = cast(BinExp)e;
assert(be);
const r1 = impl(bo.e1, be.e1, inverted, false, unreached);
const r2 = impl(bo.e2, be.e2, inverted, false, unreached || !r1);
return r1 && r2;
}
else if (op == EXP.orOr)
{
if (!orOperand) // do not indent A || B || C twice
indent++;
BinExp bo = cast(BinExp)orig;
BinExp be = cast(BinExp)e;
assert(be);
const r1 = impl(bo.e1, be.e1, inverted, true, unreached);
printOr(indent, buf);
const r2 = impl(bo.e2, be.e2, inverted, true, unreached);
if (!orOperand)
indent--;
return r1 || r2;
}
else if (op == EXP.question)
{
CondExp co = cast(CondExp)orig;
CondExp ce = cast(CondExp)e;
assert(ce);
if (!inverted)
{
// rewrite (A ? B : C) as (A && B || !A && C)
if (!orOperand)
indent++;
const r1 = impl(co.econd, ce.econd, inverted, false, unreached);
const r2 = impl(co.e1, ce.e1, inverted, false, unreached || !r1);
printOr(indent, buf);
const r3 = impl(co.econd, ce.econd, !inverted, false, unreached);
const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r3);
if (!orOperand)
indent--;
return r1 && r2 || r3 && r4;
}
else
{
// rewrite !(A ? B : C) as (!A || !B) && (A || !C)
if (!orOperand)
indent++;
const r1 = impl(co.econd, ce.econd, inverted, false, unreached);
printOr(indent, buf);
const r2 = impl(co.e1, ce.e1, inverted, false, unreached);
const r12 = r1 || r2;
const r3 = impl(co.econd, ce.econd, !inverted, false, unreached || !r12);
printOr(indent, buf);
const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r12);
if (!orOperand)
indent--;
return (r1 || r2) && (r3 || r4);
}
}
else // 'primitive' expression
{
buf.reserve(indent * 4 + 4);
foreach (i; 0 .. indent)
buf.writestring(" ");
// find its value; it may be not computed, if there was a short circuit,
// but we handle this case with `unreached` flag
bool value = true;
if (!unreached)
{
foreach (fe; negatives)
{
if (fe is e)
{
value = false;
break;
}
}
}
// write the marks first
const satisfied = inverted ? !value : value;
if (!satisfied && !unreached)
buf.writestring(" > ");
else if (unreached)
buf.writestring(" - ");
else
buf.writestring(" ");
// then the expression itself
if (inverted)
buf.writeByte('!');
buf.writestring(orig.toChars);
buf.writenl();
count++;
return satisfied;
}
}
impl(original, instantiated, false, true, false);
return count;
}
private uint visualizeShort(Expression original, Expression instantiated,
const Expression[] negatives, ref OutBuffer buf)
{
// simple list; somewhat similar to long version, so no comments
// one difference is that it needs to hold items to display in a stack
static struct Item
{
Expression orig;
bool inverted;
}
Array!Item stack;
bool impl(Expression orig, Expression e, bool inverted)
{
EXP op = orig.op;
if (inverted)
{
if (op == EXP.andAnd)
op = EXP.orOr;
else if (op == EXP.orOr)
op = EXP.andAnd;
}
if (op == EXP.not)
{
NotExp no = cast(NotExp)orig;
NotExp ne = cast(NotExp)e;
assert(ne);
return impl(no.e1, ne.e1, !inverted);
}
else if (op == EXP.andAnd)
{
BinExp bo = cast(BinExp)orig;
BinExp be = cast(BinExp)e;
assert(be);
bool r = impl(bo.e1, be.e1, inverted);
r = r && impl(bo.e2, be.e2, inverted);
return r;
}
else if (op == EXP.orOr)
{
BinExp bo = cast(BinExp)orig;
BinExp be = cast(BinExp)e;
assert(be);
const lbefore = stack.length;
bool r = impl(bo.e1, be.e1, inverted);
r = r || impl(bo.e2, be.e2, inverted);
if (r)
stack.setDim(lbefore); // purge added positive items
return r;
}
else if (op == EXP.question)
{
CondExp co = cast(CondExp)orig;
CondExp ce = cast(CondExp)e;
assert(ce);
if (!inverted)
{
const lbefore = stack.length;
bool a = impl(co.econd, ce.econd, inverted);
a = a && impl(co.e1, ce.e1, inverted);
bool b;
if (!a)
{
b = impl(co.econd, ce.econd, !inverted);
b = b && impl(co.e2, ce.e2, inverted);
}
const r = a || b;
if (r)
stack.setDim(lbefore);
return r;
}
else
{
bool a;
{
const lbefore = stack.length;
a = impl(co.econd, ce.econd, inverted);
a = a || impl(co.e1, ce.e1, inverted);
if (a)
stack.setDim(lbefore);
}
bool b;
if (a)
{
const lbefore = stack.length;
b = impl(co.econd, ce.econd, !inverted);
b = b || impl(co.e2, ce.e2, inverted);
if (b)
stack.setDim(lbefore);
}
return a && b;
}
}
else // 'primitive' expression
{
bool value = true;
foreach (fe; negatives)
{
if (fe is e)
{
value = false;
break;
}
}
const satisfied = inverted ? !value : value;
if (!satisfied)
stack.push(Item(orig, inverted));
return satisfied;
}
}
impl(original, instantiated, false);
foreach (i; 0 .. stack.length)
{
// write the expression only
buf.writestring(" ");
if (stack[i].inverted)
buf.writeByte('!');
buf.writestring(stack[i].orig.toChars);
// here with no trailing newline
if (i + 1 < stack.length)
buf.writenl();
}
return cast(uint)stack.length;
}