| /** | |
| * Lazily evaluate static conditions for `static if`, `static assert` and template constraints. | |
| * | |
| * Copyright: Copyright (C) 1999-2022 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; | |
| } |