]> git.ipfire.org Git - thirdparty/gcc.git/blob - gcc/d/dmd/staticcond.d
d: Merge upstream dmd 3982604c5, druntime bc58b1e9, phobos 12329adb6.
[thirdparty/gcc.git] / gcc / d / dmd / staticcond.d
1 /**
2 * Lazily evaluate static conditions for `static if`, `static assert` and template constraints.
3 *
4 * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
5 * Authors: $(LINK2 http://www.digitalmars.com, Walter Bright)
6 * License: $(LINK2 http://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
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.hasValue(true))
113 return true;
114 else if (opt.hasValue(false))
115 {
116 if (negatives)
117 negatives.push(before);
118 return false;
119 }
120
121 e.error("expression `%s` is not constant", e.toChars());
122 errors = true;
123 return false;
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 }