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