Home | History | Annotate | Line # | Download | only in dmd
      1  1.1  mrg /**
      2  1.1  mrg  * Lazily evaluate static conditions for `static if`, `static assert` and template constraints.
      3  1.1  mrg  *
      4  1.1  mrg  * Copyright:   Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
      5  1.1  mrg  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
      6  1.1  mrg  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
      7  1.1  mrg  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/staticcond.d, _staticcond.d)
      8  1.1  mrg  * Documentation:  https://dlang.org/phobos/dmd_staticcond.html
      9  1.1  mrg  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/staticcond.d
     10  1.1  mrg  */
     11  1.1  mrg 
     12  1.1  mrg module dmd.staticcond;
     13  1.1  mrg 
     14  1.1  mrg import dmd.arraytypes;
     15  1.1  mrg import dmd.dmodule;
     16  1.1  mrg import dmd.dscope;
     17  1.1  mrg import dmd.dsymbol;
     18  1.1  mrg import dmd.errors;
     19  1.1  mrg import dmd.expression;
     20  1.1  mrg import dmd.expressionsem;
     21  1.1  mrg import dmd.globals;
     22  1.1  mrg import dmd.identifier;
     23  1.1  mrg import dmd.mtype;
     24  1.1  mrg import dmd.root.array;
     25  1.1  mrg import dmd.common.outbuffer;
     26  1.1  mrg import dmd.tokens;
     27  1.1  mrg 
     28  1.1  mrg 
     29  1.1  mrg 
     30  1.1  mrg /********************************************
     31  1.1  mrg  * Semantically analyze and then evaluate a static condition at compile time.
     32  1.1  mrg  * This is special because short circuit operators &&, || and ?: at the top
     33  1.1  mrg  * level are not semantically analyzed if the result of the expression is not
     34  1.1  mrg  * necessary.
     35  1.1  mrg  * Params:
     36  1.1  mrg  *      sc  = instantiating scope
     37  1.1  mrg  *      original = original expression, for error messages
     38  1.1  mrg  *      e =  resulting expression
     39  1.1  mrg  *      errors = set to `true` if errors occurred
     40  1.1  mrg  *      negatives = array to store negative clauses
     41  1.1  mrg  * Returns:
     42  1.1  mrg  *      true if evaluates to true
     43  1.1  mrg  */
     44  1.1  mrg bool evalStaticCondition(Scope* sc, Expression original, Expression e, out bool errors, Expressions* negatives = null)
     45  1.1  mrg {
     46  1.1  mrg     if (negatives)
     47  1.1  mrg         negatives.setDim(0);
     48  1.1  mrg 
     49  1.1  mrg     bool impl(Expression e)
     50  1.1  mrg     {
     51  1.1  mrg         if (e.isNotExp())
     52  1.1  mrg         {
     53  1.1  mrg             NotExp ne = cast(NotExp)e;
     54  1.1  mrg             return !impl(ne.e1);
     55  1.1  mrg         }
     56  1.1  mrg 
     57  1.1  mrg         if (e.op == EXP.andAnd || e.op == EXP.orOr)
     58  1.1  mrg         {
     59  1.1  mrg             LogicalExp aae = cast(LogicalExp)e;
     60  1.1  mrg             bool result = impl(aae.e1);
     61  1.1  mrg             if (errors)
     62  1.1  mrg                 return false;
     63  1.1  mrg             if (e.op == EXP.andAnd)
     64  1.1  mrg             {
     65  1.1  mrg                 if (!result)
     66  1.1  mrg                     return false;
     67  1.1  mrg             }
     68  1.1  mrg             else
     69  1.1  mrg             {
     70  1.1  mrg                 if (result)
     71  1.1  mrg                     return true;
     72  1.1  mrg             }
     73  1.1  mrg             result = impl(aae.e2);
     74  1.1  mrg             return !errors && result;
     75  1.1  mrg         }
     76  1.1  mrg 
     77  1.1  mrg         if (e.op == EXP.question)
     78  1.1  mrg         {
     79  1.1  mrg             CondExp ce = cast(CondExp)e;
     80  1.1  mrg             bool result = impl(ce.econd);
     81  1.1  mrg             if (errors)
     82  1.1  mrg                 return false;
     83  1.1  mrg             Expression leg = result ? ce.e1 : ce.e2;
     84  1.1  mrg             result = impl(leg);
     85  1.1  mrg             return !errors && result;
     86  1.1  mrg         }
     87  1.1  mrg 
     88  1.1  mrg         Expression before = e;
     89  1.1  mrg         const uint nerrors = global.errors;
     90  1.1  mrg 
     91  1.1  mrg         sc = sc.startCTFE();
     92  1.1  mrg         sc.flags |= SCOPE.condition;
     93  1.1  mrg 
     94  1.1  mrg         e = e.expressionSemantic(sc);
     95  1.1  mrg         e = resolveProperties(sc, e);
     96  1.1  mrg         e = e.toBoolean(sc);
     97  1.1  mrg 
     98  1.1  mrg         sc = sc.endCTFE();
     99  1.1  mrg         e = e.optimize(WANTvalue);
    100  1.1  mrg 
    101  1.1  mrg         if (nerrors != global.errors ||
    102  1.1  mrg             e.isErrorExp() ||
    103  1.1  mrg             e.type.toBasetype() == Type.terror)
    104  1.1  mrg         {
    105  1.1  mrg             errors = true;
    106  1.1  mrg             return false;
    107  1.1  mrg         }
    108  1.1  mrg 
    109  1.1  mrg         e = e.ctfeInterpret();
    110  1.1  mrg 
    111  1.1  mrg         const opt = e.toBool();
    112  1.1  mrg         if (opt.isEmpty())
    113  1.1  mrg         {
    114  1.1  mrg             e.error("expression `%s` is not constant", e.toChars());
    115  1.1  mrg             errors = true;
    116  1.1  mrg             return false;
    117  1.1  mrg         }
    118  1.1  mrg 
    119  1.1  mrg         if (negatives && !opt.get())
    120  1.1  mrg             negatives.push(before);
    121  1.1  mrg         return opt.get();
    122  1.1  mrg     }
    123  1.1  mrg     return impl(e);
    124  1.1  mrg }
    125  1.1  mrg 
    126  1.1  mrg /********************************************
    127  1.1  mrg  * Format a static condition as a tree-like structure, marking failed and
    128  1.1  mrg  * bypassed expressions.
    129  1.1  mrg  * Params:
    130  1.1  mrg  *      original = original expression
    131  1.1  mrg  *      instantiated = instantiated expression
    132  1.1  mrg  *      negatives = array with negative clauses from `instantiated` expression
    133  1.1  mrg  *      full = controls whether it shows the full output or only failed parts
    134  1.1  mrg  *      itemCount = returns the number of written clauses
    135  1.1  mrg  * Returns:
    136  1.1  mrg  *      formatted string or `null` if the expressions were `null`, or if the
    137  1.1  mrg  *      instantiated expression is not based on the original one
    138  1.1  mrg  */
    139  1.1  mrg const(char)* visualizeStaticCondition(Expression original, Expression instantiated,
    140  1.1  mrg     const Expression[] negatives, bool full, ref uint itemCount)
    141  1.1  mrg {
    142  1.1  mrg     if (!original || !instantiated || original.loc !is instantiated.loc)
    143  1.1  mrg         return null;
    144  1.1  mrg 
    145  1.1  mrg     OutBuffer buf;
    146  1.1  mrg 
    147  1.1  mrg     if (full)
    148  1.1  mrg         itemCount = visualizeFull(original, instantiated, negatives, buf);
    149  1.1  mrg     else
    150  1.1  mrg         itemCount = visualizeShort(original, instantiated, negatives, buf);
    151  1.1  mrg 
    152  1.1  mrg     return buf.extractChars();
    153  1.1  mrg }
    154  1.1  mrg 
    155  1.1  mrg private uint visualizeFull(Expression original, Expression instantiated,
    156  1.1  mrg     const Expression[] negatives, ref OutBuffer buf)
    157  1.1  mrg {
    158  1.1  mrg     // tree-like structure; traverse and format simultaneously
    159  1.1  mrg     uint count;
    160  1.1  mrg     uint indent;
    161  1.1  mrg 
    162  1.1  mrg     static void printOr(uint indent, ref OutBuffer buf)
    163  1.1  mrg     {
    164  1.1  mrg         buf.reserve(indent * 4 + 8);
    165  1.1  mrg         foreach (i; 0 .. indent)
    166  1.1  mrg             buf.writestring("    ");
    167  1.1  mrg         buf.writestring("    or:\n");
    168  1.1  mrg     }
    169  1.1  mrg 
    170  1.1  mrg     // returns true if satisfied
    171  1.1  mrg     bool impl(Expression orig, Expression e, bool inverted, bool orOperand, bool unreached)
    172  1.1  mrg     {
    173  1.1  mrg         EXP op = orig.op;
    174  1.1  mrg 
    175  1.1  mrg         // lower all 'not' to the bottom
    176  1.1  mrg         // !(A && B) -> !A || !B
    177  1.1  mrg         // !(A || B) -> !A && !B
    178  1.1  mrg         if (inverted)
    179  1.1  mrg         {
    180  1.1  mrg             if (op == EXP.andAnd)
    181  1.1  mrg                 op = EXP.orOr;
    182  1.1  mrg             else if (op == EXP.orOr)
    183  1.1  mrg                 op = EXP.andAnd;
    184  1.1  mrg         }
    185  1.1  mrg 
    186  1.1  mrg         if (op == EXP.not)
    187  1.1  mrg         {
    188  1.1  mrg             NotExp no = cast(NotExp)orig;
    189  1.1  mrg             NotExp ne = cast(NotExp)e;
    190  1.1  mrg             assert(ne);
    191  1.1  mrg             return impl(no.e1, ne.e1, !inverted, orOperand, unreached);
    192  1.1  mrg         }
    193  1.1  mrg         else if (op == EXP.andAnd)
    194  1.1  mrg         {
    195  1.1  mrg             BinExp bo = cast(BinExp)orig;
    196  1.1  mrg             BinExp be = cast(BinExp)e;
    197  1.1  mrg             assert(be);
    198  1.1  mrg             const r1 = impl(bo.e1, be.e1, inverted, false, unreached);
    199  1.1  mrg             const r2 = impl(bo.e2, be.e2, inverted, false, unreached || !r1);
    200  1.1  mrg             return r1 && r2;
    201  1.1  mrg         }
    202  1.1  mrg         else if (op == EXP.orOr)
    203  1.1  mrg         {
    204  1.1  mrg             if (!orOperand) // do not indent A || B || C twice
    205  1.1  mrg                 indent++;
    206  1.1  mrg             BinExp bo = cast(BinExp)orig;
    207  1.1  mrg             BinExp be = cast(BinExp)e;
    208  1.1  mrg             assert(be);
    209  1.1  mrg             const r1 = impl(bo.e1, be.e1, inverted, true, unreached);
    210  1.1  mrg             printOr(indent, buf);
    211  1.1  mrg             const r2 = impl(bo.e2, be.e2, inverted, true, unreached);
    212  1.1  mrg             if (!orOperand)
    213  1.1  mrg                 indent--;
    214  1.1  mrg             return r1 || r2;
    215  1.1  mrg         }
    216  1.1  mrg         else if (op == EXP.question)
    217  1.1  mrg         {
    218  1.1  mrg             CondExp co = cast(CondExp)orig;
    219  1.1  mrg             CondExp ce = cast(CondExp)e;
    220  1.1  mrg             assert(ce);
    221  1.1  mrg             if (!inverted)
    222  1.1  mrg             {
    223  1.1  mrg                 // rewrite (A ? B : C) as (A && B || !A && C)
    224  1.1  mrg                 if (!orOperand)
    225  1.1  mrg                     indent++;
    226  1.1  mrg                 const r1 = impl(co.econd, ce.econd, inverted, false, unreached);
    227  1.1  mrg                 const r2 = impl(co.e1, ce.e1, inverted, false, unreached || !r1);
    228  1.1  mrg                 printOr(indent, buf);
    229  1.1  mrg                 const r3 = impl(co.econd, ce.econd, !inverted, false, unreached);
    230  1.1  mrg                 const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r3);
    231  1.1  mrg                 if (!orOperand)
    232  1.1  mrg                     indent--;
    233  1.1  mrg                 return r1 && r2 || r3 && r4;
    234  1.1  mrg             }
    235  1.1  mrg             else
    236  1.1  mrg             {
    237  1.1  mrg                 // rewrite !(A ? B : C) as (!A || !B) && (A || !C)
    238  1.1  mrg                 if (!orOperand)
    239  1.1  mrg                     indent++;
    240  1.1  mrg                 const r1 = impl(co.econd, ce.econd, inverted, false, unreached);
    241  1.1  mrg                 printOr(indent, buf);
    242  1.1  mrg                 const r2 = impl(co.e1, ce.e1, inverted, false, unreached);
    243  1.1  mrg                 const r12 = r1 || r2;
    244  1.1  mrg                 const r3 = impl(co.econd, ce.econd, !inverted, false, unreached || !r12);
    245  1.1  mrg                 printOr(indent, buf);
    246  1.1  mrg                 const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r12);
    247  1.1  mrg                 if (!orOperand)
    248  1.1  mrg                     indent--;
    249  1.1  mrg                 return (r1 || r2) && (r3 || r4);
    250  1.1  mrg             }
    251  1.1  mrg         }
    252  1.1  mrg         else // 'primitive' expression
    253  1.1  mrg         {
    254  1.1  mrg             buf.reserve(indent * 4 + 4);
    255  1.1  mrg             foreach (i; 0 .. indent)
    256  1.1  mrg                 buf.writestring("    ");
    257  1.1  mrg 
    258  1.1  mrg             // find its value; it may be not computed, if there was a short circuit,
    259  1.1  mrg             // but we handle this case with `unreached` flag
    260  1.1  mrg             bool value = true;
    261  1.1  mrg             if (!unreached)
    262  1.1  mrg             {
    263  1.1  mrg                 foreach (fe; negatives)
    264  1.1  mrg                 {
    265  1.1  mrg                     if (fe is e)
    266  1.1  mrg                     {
    267  1.1  mrg                         value = false;
    268  1.1  mrg                         break;
    269  1.1  mrg                     }
    270  1.1  mrg                 }
    271  1.1  mrg             }
    272  1.1  mrg             // write the marks first
    273  1.1  mrg             const satisfied = inverted ? !value : value;
    274  1.1  mrg             if (!satisfied && !unreached)
    275  1.1  mrg                 buf.writestring("  > ");
    276  1.1  mrg             else if (unreached)
    277  1.1  mrg                 buf.writestring("  - ");
    278  1.1  mrg             else
    279  1.1  mrg                 buf.writestring("    ");
    280  1.1  mrg             // then the expression itself
    281  1.1  mrg             if (inverted)
    282  1.1  mrg                 buf.writeByte('!');
    283  1.1  mrg             buf.writestring(orig.toChars);
    284  1.1  mrg             buf.writenl();
    285  1.1  mrg             count++;
    286  1.1  mrg             return satisfied;
    287  1.1  mrg         }
    288  1.1  mrg     }
    289  1.1  mrg 
    290  1.1  mrg     impl(original, instantiated, false, true, false);
    291  1.1  mrg     return count;
    292  1.1  mrg }
    293  1.1  mrg 
    294  1.1  mrg private uint visualizeShort(Expression original, Expression instantiated,
    295  1.1  mrg     const Expression[] negatives, ref OutBuffer buf)
    296  1.1  mrg {
    297  1.1  mrg     // simple list; somewhat similar to long version, so no comments
    298  1.1  mrg     // one difference is that it needs to hold items to display in a stack
    299  1.1  mrg 
    300  1.1  mrg     static struct Item
    301  1.1  mrg     {
    302  1.1  mrg         Expression orig;
    303  1.1  mrg         bool inverted;
    304  1.1  mrg     }
    305  1.1  mrg 
    306  1.1  mrg     Array!Item stack;
    307  1.1  mrg 
    308  1.1  mrg     bool impl(Expression orig, Expression e, bool inverted)
    309  1.1  mrg     {
    310  1.1  mrg         EXP op = orig.op;
    311  1.1  mrg 
    312  1.1  mrg         if (inverted)
    313  1.1  mrg         {
    314  1.1  mrg             if (op == EXP.andAnd)
    315  1.1  mrg                 op = EXP.orOr;
    316  1.1  mrg             else if (op == EXP.orOr)
    317  1.1  mrg                 op = EXP.andAnd;
    318  1.1  mrg         }
    319  1.1  mrg 
    320  1.1  mrg         if (op == EXP.not)
    321  1.1  mrg         {
    322  1.1  mrg             NotExp no = cast(NotExp)orig;
    323  1.1  mrg             NotExp ne = cast(NotExp)e;
    324  1.1  mrg             assert(ne);
    325  1.1  mrg             return impl(no.e1, ne.e1, !inverted);
    326  1.1  mrg         }
    327  1.1  mrg         else if (op == EXP.andAnd)
    328  1.1  mrg         {
    329  1.1  mrg             BinExp bo = cast(BinExp)orig;
    330  1.1  mrg             BinExp be = cast(BinExp)e;
    331  1.1  mrg             assert(be);
    332  1.1  mrg             bool r = impl(bo.e1, be.e1, inverted);
    333  1.1  mrg             r = r && impl(bo.e2, be.e2, inverted);
    334  1.1  mrg             return r;
    335  1.1  mrg         }
    336  1.1  mrg         else if (op == EXP.orOr)
    337  1.1  mrg         {
    338  1.1  mrg             BinExp bo = cast(BinExp)orig;
    339  1.1  mrg             BinExp be = cast(BinExp)e;
    340  1.1  mrg             assert(be);
    341  1.1  mrg             const lbefore = stack.length;
    342  1.1  mrg             bool r = impl(bo.e1, be.e1, inverted);
    343  1.1  mrg             r = r || impl(bo.e2, be.e2, inverted);
    344  1.1  mrg             if (r)
    345  1.1  mrg                 stack.setDim(lbefore); // purge added positive items
    346  1.1  mrg             return r;
    347  1.1  mrg         }
    348  1.1  mrg         else if (op == EXP.question)
    349  1.1  mrg         {
    350  1.1  mrg             CondExp co = cast(CondExp)orig;
    351  1.1  mrg             CondExp ce = cast(CondExp)e;
    352  1.1  mrg             assert(ce);
    353  1.1  mrg             if (!inverted)
    354  1.1  mrg             {
    355  1.1  mrg                 const lbefore = stack.length;
    356  1.1  mrg                 bool a = impl(co.econd, ce.econd, inverted);
    357  1.1  mrg                 a = a && impl(co.e1, ce.e1, inverted);
    358  1.1  mrg                 bool b;
    359  1.1  mrg                 if (!a)
    360  1.1  mrg                 {
    361  1.1  mrg                     b = impl(co.econd, ce.econd, !inverted);
    362  1.1  mrg                     b = b && impl(co.e2, ce.e2, inverted);
    363  1.1  mrg                 }
    364  1.1  mrg                 const r = a || b;
    365  1.1  mrg                 if (r)
    366  1.1  mrg                     stack.setDim(lbefore);
    367  1.1  mrg                 return r;
    368  1.1  mrg             }
    369  1.1  mrg             else
    370  1.1  mrg             {
    371  1.1  mrg                 bool a;
    372  1.1  mrg                 {
    373  1.1  mrg                     const lbefore = stack.length;
    374  1.1  mrg                     a = impl(co.econd, ce.econd, inverted);
    375  1.1  mrg                     a = a || impl(co.e1, ce.e1, inverted);
    376  1.1  mrg                     if (a)
    377  1.1  mrg                         stack.setDim(lbefore);
    378  1.1  mrg                 }
    379  1.1  mrg                 bool b;
    380  1.1  mrg                 if (a)
    381  1.1  mrg                 {
    382  1.1  mrg                     const lbefore = stack.length;
    383  1.1  mrg                     b = impl(co.econd, ce.econd, !inverted);
    384  1.1  mrg                     b = b || impl(co.e2, ce.e2, inverted);
    385  1.1  mrg                     if (b)
    386  1.1  mrg                         stack.setDim(lbefore);
    387  1.1  mrg                 }
    388  1.1  mrg                 return a && b;
    389  1.1  mrg             }
    390  1.1  mrg         }
    391  1.1  mrg         else // 'primitive' expression
    392  1.1  mrg         {
    393  1.1  mrg             bool value = true;
    394  1.1  mrg             foreach (fe; negatives)
    395  1.1  mrg             {
    396  1.1  mrg                 if (fe is e)
    397  1.1  mrg                 {
    398  1.1  mrg                     value = false;
    399  1.1  mrg                     break;
    400  1.1  mrg                 }
    401  1.1  mrg             }
    402  1.1  mrg             const satisfied = inverted ? !value : value;
    403  1.1  mrg             if (!satisfied)
    404  1.1  mrg                 stack.push(Item(orig, inverted));
    405  1.1  mrg             return satisfied;
    406  1.1  mrg         }
    407  1.1  mrg     }
    408  1.1  mrg 
    409  1.1  mrg     impl(original, instantiated, false);
    410  1.1  mrg 
    411  1.1  mrg     foreach (i; 0 .. stack.length)
    412  1.1  mrg     {
    413  1.1  mrg         // write the expression only
    414  1.1  mrg         buf.writestring("       ");
    415  1.1  mrg         if (stack[i].inverted)
    416  1.1  mrg             buf.writeByte('!');
    417  1.1  mrg         buf.writestring(stack[i].orig.toChars);
    418  1.1  mrg         // here with no trailing newline
    419  1.1  mrg         if (i + 1 < stack.length)
    420  1.1  mrg             buf.writenl();
    421  1.1  mrg     }
    422  1.1  mrg     return cast(uint)stack.length;
    423  1.1  mrg }
    424