2 * Lazily evaluate static conditions for `static if`, `static assert` and template constraints.
4 * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
5 * Authors: $(LINK2 http://www.digitalmars.com, Walter Bright)
6 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/staticcond.d, _staticcond.d)
8 * Documentation: https://dlang.org/phobos/dmd_staticcond.html
9 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/staticcond.d
12 module dmd.staticcond;
14 import dmd.arraytypes;
19 import dmd.expression;
20 import dmd.expressionsem;
22 import dmd.identifier;
24 import dmd.root.array;
25 import dmd.common.outbuffer;
30 /********************************************
31 * Semantically analyze and then evaluate a static condition at compile time.
32 * This is special because short circuit operators &&, || and ?: at the top
33 * level are not semantically analyzed if the result of the expression is not
36 * sc = instantiating scope
37 * original = original expression, for error messages
38 * e = resulting expression
39 * errors = set to `true` if errors occurred
40 * negatives = array to store negative clauses
42 * true if evaluates to true
44 bool evalStaticCondition(Scope* sc, Expression original, Expression e, out bool errors, Expressions* negatives = null)
49 bool impl(Expression e)
53 NotExp ne = cast(NotExp)e;
57 if (e.op == EXP.andAnd || e.op == EXP.orOr)
59 LogicalExp aae = cast(LogicalExp)e;
60 bool result = impl(aae.e1);
63 if (e.op == EXP.andAnd)
73 result = impl(aae.e2);
74 return !errors && result;
77 if (e.op == EXP.question)
79 CondExp ce = cast(CondExp)e;
80 bool result = impl(ce.econd);
83 Expression leg = result ? ce.e1 : ce.e2;
85 return !errors && result;
88 Expression before = e;
89 const uint nerrors = global.errors;
92 sc.flags |= SCOPE.condition;
94 e = e.expressionSemantic(sc);
95 e = resolveProperties(sc, e);
99 e = e.optimize(WANTvalue);
101 if (nerrors != global.errors ||
103 e.type.toBasetype() == Type.terror)
109 e = e.ctfeInterpret();
111 const opt = e.toBool();
112 if (opt.hasValue(true))
114 else if (opt.hasValue(false))
117 negatives.push(before);
121 e.error("expression `%s` is not constant", e.toChars());
128 /********************************************
129 * Format a static condition as a tree-like structure, marking failed and
130 * bypassed expressions.
132 * original = original expression
133 * instantiated = instantiated expression
134 * negatives = array with negative clauses from `instantiated` expression
135 * full = controls whether it shows the full output or only failed parts
136 * itemCount = returns the number of written clauses
138 * formatted string or `null` if the expressions were `null`, or if the
139 * instantiated expression is not based on the original one
141 const(char)* visualizeStaticCondition(Expression original, Expression instantiated,
142 const Expression[] negatives, bool full, ref uint itemCount)
144 if (!original || !instantiated || original.loc !is instantiated.loc)
150 itemCount = visualizeFull(original, instantiated, negatives, buf);
152 itemCount = visualizeShort(original, instantiated, negatives, buf);
154 return buf.extractChars();
157 private uint visualizeFull(Expression original, Expression instantiated,
158 const Expression[] negatives, ref OutBuffer buf)
160 // tree-like structure; traverse and format simultaneously
164 static void printOr(uint indent, ref OutBuffer buf)
166 buf.reserve(indent * 4 + 8);
167 foreach (i; 0 .. indent)
168 buf.writestring(" ");
169 buf.writestring(" or:\n");
172 // returns true if satisfied
173 bool impl(Expression orig, Expression e, bool inverted, bool orOperand, bool unreached)
177 // lower all 'not' to the bottom
178 // !(A && B) -> !A || !B
179 // !(A || B) -> !A && !B
182 if (op == EXP.andAnd)
184 else if (op == EXP.orOr)
190 NotExp no = cast(NotExp)orig;
191 NotExp ne = cast(NotExp)e;
193 return impl(no.e1, ne.e1, !inverted, orOperand, unreached);
195 else if (op == EXP.andAnd)
197 BinExp bo = cast(BinExp)orig;
198 BinExp be = cast(BinExp)e;
200 const r1 = impl(bo.e1, be.e1, inverted, false, unreached);
201 const r2 = impl(bo.e2, be.e2, inverted, false, unreached || !r1);
204 else if (op == EXP.orOr)
206 if (!orOperand) // do not indent A || B || C twice
208 BinExp bo = cast(BinExp)orig;
209 BinExp be = cast(BinExp)e;
211 const r1 = impl(bo.e1, be.e1, inverted, true, unreached);
212 printOr(indent, buf);
213 const r2 = impl(bo.e2, be.e2, inverted, true, unreached);
218 else if (op == EXP.question)
220 CondExp co = cast(CondExp)orig;
221 CondExp ce = cast(CondExp)e;
225 // rewrite (A ? B : C) as (A && B || !A && C)
228 const r1 = impl(co.econd, ce.econd, inverted, false, unreached);
229 const r2 = impl(co.e1, ce.e1, inverted, false, unreached || !r1);
230 printOr(indent, buf);
231 const r3 = impl(co.econd, ce.econd, !inverted, false, unreached);
232 const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r3);
235 return r1 && r2 || r3 && r4;
239 // rewrite !(A ? B : C) as (!A || !B) && (A || !C)
242 const r1 = impl(co.econd, ce.econd, inverted, false, unreached);
243 printOr(indent, buf);
244 const r2 = impl(co.e1, ce.e1, inverted, false, unreached);
245 const r12 = r1 || r2;
246 const r3 = impl(co.econd, ce.econd, !inverted, false, unreached || !r12);
247 printOr(indent, buf);
248 const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r12);
251 return (r1 || r2) && (r3 || r4);
254 else // 'primitive' expression
256 buf.reserve(indent * 4 + 4);
257 foreach (i; 0 .. indent)
258 buf.writestring(" ");
260 // find its value; it may be not computed, if there was a short circuit,
261 // but we handle this case with `unreached` flag
265 foreach (fe; negatives)
274 // write the marks first
275 const satisfied = inverted ? !value : value;
276 if (!satisfied && !unreached)
277 buf.writestring(" > ");
279 buf.writestring(" - ");
281 buf.writestring(" ");
282 // then the expression itself
285 buf.writestring(orig.toChars);
292 impl(original, instantiated, false, true, false);
296 private uint visualizeShort(Expression original, Expression instantiated,
297 const Expression[] negatives, ref OutBuffer buf)
299 // simple list; somewhat similar to long version, so no comments
300 // one difference is that it needs to hold items to display in a stack
310 bool impl(Expression orig, Expression e, bool inverted)
316 if (op == EXP.andAnd)
318 else if (op == EXP.orOr)
324 NotExp no = cast(NotExp)orig;
325 NotExp ne = cast(NotExp)e;
327 return impl(no.e1, ne.e1, !inverted);
329 else if (op == EXP.andAnd)
331 BinExp bo = cast(BinExp)orig;
332 BinExp be = cast(BinExp)e;
334 bool r = impl(bo.e1, be.e1, inverted);
335 r = r && impl(bo.e2, be.e2, inverted);
338 else if (op == EXP.orOr)
340 BinExp bo = cast(BinExp)orig;
341 BinExp be = cast(BinExp)e;
343 const lbefore = stack.length;
344 bool r = impl(bo.e1, be.e1, inverted);
345 r = r || impl(bo.e2, be.e2, inverted);
347 stack.setDim(lbefore); // purge added positive items
350 else if (op == EXP.question)
352 CondExp co = cast(CondExp)orig;
353 CondExp ce = cast(CondExp)e;
357 const lbefore = stack.length;
358 bool a = impl(co.econd, ce.econd, inverted);
359 a = a && impl(co.e1, ce.e1, inverted);
363 b = impl(co.econd, ce.econd, !inverted);
364 b = b && impl(co.e2, ce.e2, inverted);
368 stack.setDim(lbefore);
375 const lbefore = stack.length;
376 a = impl(co.econd, ce.econd, inverted);
377 a = a || impl(co.e1, ce.e1, inverted);
379 stack.setDim(lbefore);
384 const lbefore = stack.length;
385 b = impl(co.econd, ce.econd, !inverted);
386 b = b || impl(co.e2, ce.e2, inverted);
388 stack.setDim(lbefore);
393 else // 'primitive' expression
396 foreach (fe; negatives)
404 const satisfied = inverted ? !value : value;
406 stack.push(Item(orig, inverted));
411 impl(original, instantiated, false);
413 foreach (i; 0 .. stack.length)
415 // write the expression only
416 buf.writestring(" ");
417 if (stack[i].inverted)
419 buf.writestring(stack[i].orig.toChars);
420 // here with no trailing newline
421 if (i + 1 < stack.length)
424 return cast(uint)stack.length;