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