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