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
;
20 import dmd
.expression
;
21 import dmd
.expressionsem
;
23 import dmd
.identifier
;
26 import dmd
.root
.array
;
27 import dmd
.common
.outbuffer
;
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
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
44 * true if evaluates to true
46 bool evalStaticCondition(Scope
* sc
, Expression original
, Expression e
, out bool errors
, Expressions
* negatives
= null)
51 bool impl(Expression e
)
55 NotExp ne
= cast(NotExp
)e
;
59 if (e
.op
== EXP
.andAnd || e
.op
== EXP
.orOr
)
61 LogicalExp aae
= cast(LogicalExp
)e
;
62 bool result
= impl(aae
.e1
);
65 if (e
.op
== EXP
.andAnd
)
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
);
85 Expression leg
= result ? ce
.e1
: ce
.e2
;
87 return !errors
&& result
;
90 Expression before
= e
;
91 const uint nerrors
= global
.errors
;
94 sc
.flags |
= SCOPE
.condition
;
96 e
= e
.expressionSemantic(sc
);
97 e
= resolveProperties(sc
, e
);
101 e
= e
.optimize(WANTvalue
);
103 if (nerrors
!= global
.errors ||
105 e
.type
.toBasetype() == Type
.terror
)
111 e
= e
.ctfeInterpret();
113 const opt
= e
.toBool();
116 if (!e
.type
.isTypeError())
117 error(e
.loc
, "expression `%s` is not constant", e
.toChars());
122 if (negatives
&& !opt
.get())
123 negatives
.push(before
);
129 /********************************************
130 * Format a static condition as a tree-like structure, marking failed and
131 * bypassed expressions.
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
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
)
151 itemCount
= visualizeFull(original
, instantiated
, negatives
, buf
);
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
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
)
178 // lower all 'not' to the bottom
179 // !(A && B) -> !A || !B
180 // !(A || B) -> !A && !B
183 if (op
== EXP
.andAnd
)
185 else if (op
== EXP
.orOr
)
191 NotExp no
= cast(NotExp
)orig
;
192 NotExp ne
= cast(NotExp
)e
;
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
;
201 const r1
= impl(bo
.e1
, be
.e1
, inverted
, false, unreached
);
202 const r2
= impl(bo
.e2
, be
.e2
, inverted
, false, unreached ||
!r1
);
205 else if (op
== EXP
.orOr
)
207 if (!orOperand
) // do not indent A || B || C twice
209 BinExp bo
= cast(BinExp
)orig
;
210 BinExp be
= cast(BinExp
)e
;
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
);
219 else if (op
== EXP
.question
)
221 CondExp co
= cast(CondExp
)orig
;
222 CondExp ce
= cast(CondExp
)e
;
226 // rewrite (A ? B : C) as (A && B || !A && C)
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
);
236 return r1
&& r2 || r3
&& r4
;
240 // rewrite !(A ? B : C) as (!A || !B) && (A || !C)
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
);
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
266 foreach (fe
; negatives
)
275 // write the marks first
276 const satisfied
= inverted ?
!value
: value
;
277 if (!satisfied
&& !unreached
)
278 buf
.writestring(" > ");
280 buf
.writestring(" - ");
282 buf
.writestring(" ");
283 // then the expression itself
286 buf
.writestring(orig
.toChars
);
293 impl(original
, instantiated
, false, true, false);
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
311 bool impl(Expression orig
, Expression e
, bool inverted
)
317 if (op
== EXP
.andAnd
)
319 else if (op
== EXP
.orOr
)
325 NotExp no
= cast(NotExp
)orig
;
326 NotExp ne
= cast(NotExp
)e
;
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
;
335 bool r
= impl(bo
.e1
, be
.e1
, inverted
);
336 r
= r
&& impl(bo
.e2
, be
.e2
, inverted
);
339 else if (op
== EXP
.orOr
)
341 BinExp bo
= cast(BinExp
)orig
;
342 BinExp be
= cast(BinExp
)e
;
344 const lbefore
= stack
.length
;
345 bool r
= impl(bo
.e1
, be
.e1
, inverted
);
346 r
= r ||
impl(bo
.e2
, be
.e2
, inverted
);
348 stack
.setDim(lbefore
); // purge added positive items
351 else if (op
== EXP
.question
)
353 CondExp co
= cast(CondExp
)orig
;
354 CondExp ce
= cast(CondExp
)e
;
358 const lbefore
= stack
.length
;
359 bool a
= impl(co
.econd
, ce
.econd
, inverted
);
360 a
= a
&& impl(co
.e1
, ce
.e1
, inverted
);
364 b
= impl(co
.econd
, ce
.econd
, !inverted
);
365 b
= b
&& impl(co
.e2
, ce
.e2
, inverted
);
369 stack
.setDim(lbefore
);
376 const lbefore
= stack
.length
;
377 a
= impl(co
.econd
, ce
.econd
, inverted
);
378 a
= a ||
impl(co
.e1
, ce
.e1
, inverted
);
380 stack
.setDim(lbefore
);
385 const lbefore
= stack
.length
;
386 b
= impl(co
.econd
, ce
.econd
, !inverted
);
387 b
= b ||
impl(co
.e2
, ce
.e2
, inverted
);
389 stack
.setDim(lbefore
);
394 else // 'primitive' expression
397 foreach (fe
; negatives
)
405 const satisfied
= inverted ?
!value
: value
;
407 stack
.push(Item(orig
, inverted
));
412 impl(original
, instantiated
, false);
414 foreach (i
; 0 .. stack
.length
)
416 // write the expression only
417 buf
.writestring(" ");
418 if (stack
[i
].inverted
)
420 buf
.writestring(stack
[i
].orig
.toChars
);
421 // here with no trailing newline
422 if (i
+ 1 < stack
.length
)
425 return cast(uint)stack
.length
;