| /** |
| * 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; |
| } |