d: Merge dmd, druntime d8e3976a58, phobos 7a6e95688
[official-gcc.git] / gcc / d / dmd / staticcond.d
blob72afe0296415544c4d7b5fa021796c9f143c3cca
1 /**
2 * Lazily evaluate static conditions for `static if`, `static assert` and template constraints.
4 * Copyright: Copyright (C) 1999-2024 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
12 module dmd.staticcond;
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.optimize;
26 import dmd.root.array;
27 import dmd.common.outbuffer;
28 import dmd.tokens;
32 /********************************************
33 * Semantically analyze and then evaluate a static condition at compile time.
34 * This is special because short circuit operators &&, || and ?: at the top
35 * level are not semantically analyzed if the result of the expression is not
36 * necessary.
37 * Params:
38 * sc = instantiating scope
39 * original = original expression, for error messages
40 * e = resulting expression
41 * errors = set to `true` if errors occurred
42 * negatives = array to store negative clauses
43 * Returns:
44 * true if evaluates to true
46 bool evalStaticCondition(Scope* sc, Expression original, Expression e, out bool errors, Expressions* negatives = null)
48 if (negatives)
49 negatives.setDim(0);
51 bool impl(Expression e)
53 if (e.isNotExp())
55 NotExp ne = cast(NotExp)e;
56 return !impl(ne.e1);
59 if (e.op == EXP.andAnd || e.op == EXP.orOr)
61 LogicalExp aae = cast(LogicalExp)e;
62 bool result = impl(aae.e1);
63 if (errors)
64 return false;
65 if (e.op == EXP.andAnd)
67 if (!result)
68 return false;
70 else
72 if (result)
73 return true;
75 result = impl(aae.e2);
76 return !errors && result;
79 if (e.op == EXP.question)
81 CondExp ce = cast(CondExp)e;
82 bool result = impl(ce.econd);
83 if (errors)
84 return false;
85 Expression leg = result ? ce.e1 : ce.e2;
86 result = impl(leg);
87 return !errors && result;
90 Expression before = e;
91 const uint nerrors = global.errors;
93 sc = sc.startCTFE();
94 sc.flags |= SCOPE.condition;
96 e = e.expressionSemantic(sc);
97 e = resolveProperties(sc, e);
98 e = e.toBoolean(sc);
100 sc = sc.endCTFE();
101 e = e.optimize(WANTvalue);
103 if (nerrors != global.errors ||
104 e.isErrorExp() ||
105 e.type.toBasetype() == Type.terror)
107 errors = true;
108 return false;
111 e = e.ctfeInterpret();
113 const opt = e.toBool();
114 if (opt.isEmpty())
116 if (!e.type.isTypeError())
117 error(e.loc, "expression `%s` is not constant", e.toChars());
118 errors = true;
119 return false;
122 if (negatives && !opt.get())
123 negatives.push(before);
124 return opt.get();
126 return impl(e);
129 /********************************************
130 * Format a static condition as a tree-like structure, marking failed and
131 * bypassed expressions.
132 * Params:
133 * original = original expression
134 * instantiated = instantiated expression
135 * negatives = array with negative clauses from `instantiated` expression
136 * full = controls whether it shows the full output or only failed parts
137 * itemCount = returns the number of written clauses
138 * Returns:
139 * formatted string or `null` if the expressions were `null`, or if the
140 * instantiated expression is not based on the original one
142 const(char)* visualizeStaticCondition(Expression original, Expression instantiated,
143 const Expression[] negatives, bool full, ref uint itemCount)
145 if (!original || !instantiated || original.loc !is instantiated.loc)
146 return null;
148 OutBuffer buf;
150 if (full)
151 itemCount = visualizeFull(original, instantiated, negatives, buf);
152 else
153 itemCount = visualizeShort(original, instantiated, negatives, buf);
155 return buf.extractChars();
158 private uint visualizeFull(Expression original, Expression instantiated,
159 const Expression[] negatives, ref OutBuffer buf)
161 // tree-like structure; traverse and format simultaneously
162 uint count;
163 uint indent;
165 static void printOr(uint indent, ref OutBuffer buf)
167 buf.reserve(indent * 4 + 8);
168 foreach (i; 0 .. indent)
169 buf.writestring(" ");
170 buf.writestring(" or:\n");
173 // returns true if satisfied
174 bool impl(Expression orig, Expression e, bool inverted, bool orOperand, bool unreached)
176 EXP op = orig.op;
178 // lower all 'not' to the bottom
179 // !(A && B) -> !A || !B
180 // !(A || B) -> !A && !B
181 if (inverted)
183 if (op == EXP.andAnd)
184 op = EXP.orOr;
185 else if (op == EXP.orOr)
186 op = EXP.andAnd;
189 if (op == EXP.not)
191 NotExp no = cast(NotExp)orig;
192 NotExp ne = cast(NotExp)e;
193 assert(ne);
194 return impl(no.e1, ne.e1, !inverted, orOperand, unreached);
196 else if (op == EXP.andAnd)
198 BinExp bo = cast(BinExp)orig;
199 BinExp be = cast(BinExp)e;
200 assert(be);
201 const r1 = impl(bo.e1, be.e1, inverted, false, unreached);
202 const r2 = impl(bo.e2, be.e2, inverted, false, unreached || !r1);
203 return r1 && r2;
205 else if (op == EXP.orOr)
207 if (!orOperand) // do not indent A || B || C twice
208 indent++;
209 BinExp bo = cast(BinExp)orig;
210 BinExp be = cast(BinExp)e;
211 assert(be);
212 const r1 = impl(bo.e1, be.e1, inverted, true, unreached);
213 printOr(indent, buf);
214 const r2 = impl(bo.e2, be.e2, inverted, true, unreached);
215 if (!orOperand)
216 indent--;
217 return r1 || r2;
219 else if (op == EXP.question)
221 CondExp co = cast(CondExp)orig;
222 CondExp ce = cast(CondExp)e;
223 assert(ce);
224 if (!inverted)
226 // rewrite (A ? B : C) as (A && B || !A && C)
227 if (!orOperand)
228 indent++;
229 const r1 = impl(co.econd, ce.econd, inverted, false, unreached);
230 const r2 = impl(co.e1, ce.e1, inverted, false, unreached || !r1);
231 printOr(indent, buf);
232 const r3 = impl(co.econd, ce.econd, !inverted, false, unreached);
233 const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r3);
234 if (!orOperand)
235 indent--;
236 return r1 && r2 || r3 && r4;
238 else
240 // rewrite !(A ? B : C) as (!A || !B) && (A || !C)
241 if (!orOperand)
242 indent++;
243 const r1 = impl(co.econd, ce.econd, inverted, false, unreached);
244 printOr(indent, buf);
245 const r2 = impl(co.e1, ce.e1, inverted, false, unreached);
246 const r12 = r1 || r2;
247 const r3 = impl(co.econd, ce.econd, !inverted, false, unreached || !r12);
248 printOr(indent, buf);
249 const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r12);
250 if (!orOperand)
251 indent--;
252 return (r1 || r2) && (r3 || r4);
255 else // 'primitive' expression
257 buf.reserve(indent * 4 + 4);
258 foreach (i; 0 .. indent)
259 buf.writestring(" ");
261 // find its value; it may be not computed, if there was a short circuit,
262 // but we handle this case with `unreached` flag
263 bool value = true;
264 if (!unreached)
266 foreach (fe; negatives)
268 if (fe is e)
270 value = false;
271 break;
275 // write the marks first
276 const satisfied = inverted ? !value : value;
277 if (!satisfied && !unreached)
278 buf.writestring(" > ");
279 else if (unreached)
280 buf.writestring(" - ");
281 else
282 buf.writestring(" ");
283 // then the expression itself
284 if (inverted)
285 buf.writeByte('!');
286 buf.writestring(orig.toChars);
287 buf.writenl();
288 count++;
289 return satisfied;
293 impl(original, instantiated, false, true, false);
294 return count;
297 private uint visualizeShort(Expression original, Expression instantiated,
298 const Expression[] negatives, ref OutBuffer buf)
300 // simple list; somewhat similar to long version, so no comments
301 // one difference is that it needs to hold items to display in a stack
303 static struct Item
305 Expression orig;
306 bool inverted;
309 Array!Item stack;
311 bool impl(Expression orig, Expression e, bool inverted)
313 EXP op = orig.op;
315 if (inverted)
317 if (op == EXP.andAnd)
318 op = EXP.orOr;
319 else if (op == EXP.orOr)
320 op = EXP.andAnd;
323 if (op == EXP.not)
325 NotExp no = cast(NotExp)orig;
326 NotExp ne = cast(NotExp)e;
327 assert(ne);
328 return impl(no.e1, ne.e1, !inverted);
330 else if (op == EXP.andAnd)
332 BinExp bo = cast(BinExp)orig;
333 BinExp be = cast(BinExp)e;
334 assert(be);
335 bool r = impl(bo.e1, be.e1, inverted);
336 r = r && impl(bo.e2, be.e2, inverted);
337 return r;
339 else if (op == EXP.orOr)
341 BinExp bo = cast(BinExp)orig;
342 BinExp be = cast(BinExp)e;
343 assert(be);
344 const lbefore = stack.length;
345 bool r = impl(bo.e1, be.e1, inverted);
346 r = r || impl(bo.e2, be.e2, inverted);
347 if (r)
348 stack.setDim(lbefore); // purge added positive items
349 return r;
351 else if (op == EXP.question)
353 CondExp co = cast(CondExp)orig;
354 CondExp ce = cast(CondExp)e;
355 assert(ce);
356 if (!inverted)
358 const lbefore = stack.length;
359 bool a = impl(co.econd, ce.econd, inverted);
360 a = a && impl(co.e1, ce.e1, inverted);
361 bool b;
362 if (!a)
364 b = impl(co.econd, ce.econd, !inverted);
365 b = b && impl(co.e2, ce.e2, inverted);
367 const r = a || b;
368 if (r)
369 stack.setDim(lbefore);
370 return r;
372 else
374 bool a;
376 const lbefore = stack.length;
377 a = impl(co.econd, ce.econd, inverted);
378 a = a || impl(co.e1, ce.e1, inverted);
379 if (a)
380 stack.setDim(lbefore);
382 bool b;
383 if (a)
385 const lbefore = stack.length;
386 b = impl(co.econd, ce.econd, !inverted);
387 b = b || impl(co.e2, ce.e2, inverted);
388 if (b)
389 stack.setDim(lbefore);
391 return a && b;
394 else // 'primitive' expression
396 bool value = true;
397 foreach (fe; negatives)
399 if (fe is e)
401 value = false;
402 break;
405 const satisfied = inverted ? !value : value;
406 if (!satisfied)
407 stack.push(Item(orig, inverted));
408 return satisfied;
412 impl(original, instantiated, false);
414 foreach (i; 0 .. stack.length)
416 // write the expression only
417 buf.writestring(" ");
418 if (stack[i].inverted)
419 buf.writeByte('!');
420 buf.writestring(stack[i].orig.toChars);
421 // here with no trailing newline
422 if (i + 1 < stack.length)
423 buf.writenl();
425 return cast(uint)stack.length;