1
/**
2
 * Lazily evaluate static conditions for `static if`, `static assert` and template constraints.
3
 *
4
 * Copyright:   Copyright (C) 1999-2020 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.root.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 1
    if (negatives)
47 1
        negatives.setDim(0);
48

49
    bool impl(Expression e)
50
    {
51 1
        if (e.op == TOK.not)
52
        {
53 1
            NotExp ne = cast(NotExp)e;
54 1
            return !impl(ne.e1);
55
        }
56

57 1
        if (e.op == TOK.andAnd || e.op == TOK.orOr)
58
        {
59 1
            LogicalExp aae = cast(LogicalExp)e;
60 1
            bool result = impl(aae.e1);
61 1
            if (errors)
62 1
                return false;
63 1
            if (e.op == TOK.andAnd)
64
            {
65 1
                if (!result)
66 1
                    return false;
67
            }
68
            else
69
            {
70 1
                if (result)
71 1
                    return true;
72
            }
73 1
            result = impl(aae.e2);
74 1
            return !errors && result;
75
        }
76

77 1
        if (e.op == TOK.question)
78
        {
79 1
            CondExp ce = cast(CondExp)e;
80 1
            bool result = impl(ce.econd);
81 1
            if (errors)
82 0
                return false;
83 1
            Expression leg = result ? ce.e1 : ce.e2;
84 1
            result = impl(leg);
85 1
            return !errors && result;
86
        }
87

88 1
        Expression before = e;
89 1
        const uint nerrors = global.errors;
90

91 1
        sc = sc.startCTFE();
92 1
        sc.flags |= SCOPE.condition;
93

94 1
        e = e.expressionSemantic(sc);
95 1
        e = resolveProperties(sc, e);
96 1
        e = e.toBoolean(sc);
97

98 1
        sc = sc.endCTFE();
99 1
        e = e.optimize(WANTvalue);
100

101 1
        if (nerrors != global.errors ||
102 1
            e.op == TOK.error ||
103 1
            e.type.toBasetype() == Type.terror)
104
        {
105 1
            errors = true;
106 1
            return false;
107
        }
108

109 1
        e = e.ctfeInterpret();
110

111 1
        if (e.isBool(true))
112 1
            return true;
113 1
        else if (e.isBool(false))
114
        {
115 1
            if (negatives)
116 1
                negatives.push(before);
117 1
            return false;
118
        }
119

120 1
        e.error("expression `%s` is not constant", e.toChars());
121 1
        errors = true;
122 1
        return false;
123
    }
124 1
    return impl(e);
125
}
126

127
/********************************************
128
 * Format a static condition as a tree-like structure, marking failed and
129
 * bypassed expressions.
130
 * Params:
131
 *      original = original expression
132
 *      instantiated = instantiated expression
133
 *      negatives = array with negative clauses from `instantiated` expression
134
 *      full = controls whether it shows the full output or only failed parts
135
 *      itemCount = returns the number of written clauses
136
 * Returns:
137
 *      formatted string or `null` if the expressions were `null`, or if the
138
 *      instantiated expression is not based on the original one
139
 */
140
const(char)* visualizeStaticCondition(Expression original, Expression instantiated,
141
    const Expression[] negatives, bool full, ref uint itemCount)
142
{
143 1
    if (!original || !instantiated || original.loc !is instantiated.loc)
144 1
        return null;
145

146 1
    OutBuffer buf;
147

148 1
    if (full)
149 1
        itemCount = visualizeFull(original, instantiated, negatives, buf);
150
    else
151 1
        itemCount = visualizeShort(original, instantiated, negatives, buf);
152

153 1
    return buf.extractChars();
154
}
155

156
private uint visualizeFull(Expression original, Expression instantiated,
157
    const Expression[] negatives, ref OutBuffer buf)
158
{
159
    // tree-like structure; traverse and format simultaneously
160 1
    uint count;
161 1
    uint indent;
162

163
    static void printOr(uint indent, ref OutBuffer buf)
164
    {
165 0
        buf.reserve(indent * 4 + 8);
166 0
        foreach (i; 0 .. indent)
167 0
            buf.writestring("    ");
168 0
        buf.writestring("    or:\n");
169
    }
170

171
    // returns true if satisfied
172
    bool impl(Expression orig, Expression e, bool inverted, bool orOperand, bool unreached)
173
    {
174 1
        TOK op = orig.op;
175

176
        // lower all 'not' to the bottom
177
        // !(A && B) -> !A || !B
178
        // !(A || B) -> !A && !B
179 1
        if (inverted)
180
        {
181 0
            if (op == TOK.andAnd)
182 0
                op = TOK.orOr;
183 0
            else if (op == TOK.orOr)
184 0
                op = TOK.andAnd;
185
        }
186

187 1
        if (op == TOK.not)
188
        {
189 0
            NotExp no = cast(NotExp)orig;
190 0
            NotExp ne = cast(NotExp)e;
191 0
            assert(ne);
192 0
            return impl(no.e1, ne.e1, !inverted, orOperand, unreached);
193
        }
194 1
        else if (op == TOK.andAnd)
195
        {
196 0
            BinExp bo = cast(BinExp)orig;
197 0
            BinExp be = cast(BinExp)e;
198 0
            assert(be);
199 0
            const r1 = impl(bo.e1, be.e1, inverted, false, unreached);
200 0
            const r2 = impl(bo.e2, be.e2, inverted, false, unreached || !r1);
201 0
            return r1 && r2;
202
        }
203 1
        else if (op == TOK.orOr)
204
        {
205 0
            if (!orOperand) // do not indent A || B || C twice
206 0
                indent++;
207 0
            BinExp bo = cast(BinExp)orig;
208 0
            BinExp be = cast(BinExp)e;
209 0
            assert(be);
210 0
            const r1 = impl(bo.e1, be.e1, inverted, true, unreached);
211 0
            printOr(indent, buf);
212 0
            const r2 = impl(bo.e2, be.e2, inverted, true, unreached);
213 0
            if (!orOperand)
214 0
                indent--;
215 0
            return r1 || r2;
216
        }
217 1
        else if (op == TOK.question)
218
        {
219 0
            CondExp co = cast(CondExp)orig;
220 0
            CondExp ce = cast(CondExp)e;
221 0
            assert(ce);
222 0
            if (!inverted)
223
            {
224
                // rewrite (A ? B : C) as (A && B || !A && C)
225 0
                if (!orOperand)
226 0
                    indent++;
227 0
                const r1 = impl(co.econd, ce.econd, inverted, false, unreached);
228 0
                const r2 = impl(co.e1, ce.e1, inverted, false, unreached || !r1);
229 0
                printOr(indent, buf);
230 0
                const r3 = impl(co.econd, ce.econd, !inverted, false, unreached);
231 0
                const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r3);
232 0
                if (!orOperand)
233 0
                    indent--;
234 0
                return r1 && r2 || r3 && r4;
235
            }
236
            else
237
            {
238
                // rewrite !(A ? B : C) as (!A || !B) && (A || !C)
239 0
                if (!orOperand)
240 0
                    indent++;
241 0
                const r1 = impl(co.econd, ce.econd, inverted, false, unreached);
242 0
                printOr(indent, buf);
243 0
                const r2 = impl(co.e1, ce.e1, inverted, false, unreached);
244 0
                const r12 = r1 || r2;
245 0
                const r3 = impl(co.econd, ce.econd, !inverted, false, unreached || !r12);
246 0
                printOr(indent, buf);
247 0
                const r4 = impl(co.e2, ce.e2, inverted, false, unreached || !r12);
248 0
                if (!orOperand)
249 0
                    indent--;
250 0
                return (r1 || r2) && (r3 || r4);
251
            }
252
        }
253
        else // 'primitive' expression
254
        {
255 1
            buf.reserve(indent * 4 + 4);
256 1
            foreach (i; 0 .. indent)
257 0
                buf.writestring("    ");
258

259
            // find its value; it may be not computed, if there was a short circuit,
260
            // but we handle this case with `unreached` flag
261 1
            bool value = true;
262 1
            if (!unreached)
263
            {
264 1
                foreach (fe; negatives)
265
                {
266 1
                    if (fe is e)
267
                    {
268 1
                        value = false;
269 1
                        break;
270
                    }
271
                }
272
            }
273
            // write the marks first
274 1
            const satisfied = inverted ? !value : value;
275 1
            if (!satisfied && !unreached)
276 1
                buf.writestring("  > ");
277 0
            else if (unreached)
278 0
                buf.writestring("  - ");
279
            else
280 0
                buf.writestring("    ");
281
            // then the expression itself
282 1
            if (inverted)
283 0
                buf.writeByte('!');
284 1
            buf.writestring(orig.toChars);
285 1
            buf.writenl();
286 1
            count++;
287 1
            return satisfied;
288
        }
289
    }
290

291 1
    impl(original, instantiated, false, true, false);
292 1
    return count;
293
}
294

295
private uint visualizeShort(Expression original, Expression instantiated,
296
    const Expression[] negatives, ref OutBuffer buf)
297
{
298
    // simple list; somewhat similar to long version, so no comments
299
    // one difference is that it needs to hold items to display in a stack
300

301
    static struct Item
302
    {
303
        Expression orig;
304
        bool inverted;
305
    }
306

307 1
    Array!Item stack;
308

309
    bool impl(Expression orig, Expression e, bool inverted)
310
    {
311 1
        TOK op = orig.op;
312

313 1
        if (inverted)
314
        {
315 1
            if (op == TOK.andAnd)
316 1
                op = TOK.orOr;
317 1
            else if (op == TOK.orOr)
318 1
                op = TOK.andAnd;
319
        }
320

321 1
        if (op == TOK.not)
322
        {
323 1
            NotExp no = cast(NotExp)orig;
324 1
            NotExp ne = cast(NotExp)e;
325 1
            assert(ne);
326 1
            return impl(no.e1, ne.e1, !inverted);
327
        }
328 1
        else if (op == TOK.andAnd)
329
        {
330 1
            BinExp bo = cast(BinExp)orig;
331 1
            BinExp be = cast(BinExp)e;
332 1
            assert(be);
333 1
            bool r = impl(bo.e1, be.e1, inverted);
334 1
            r = r && impl(bo.e2, be.e2, inverted);
335 1
            return r;
336
        }
337 1
        else if (op == TOK.orOr)
338
        {
339 1
            BinExp bo = cast(BinExp)orig;
340 1
            BinExp be = cast(BinExp)e;
341 1
            assert(be);
342 1
            const lbefore = stack.length;
343 1
            bool r = impl(bo.e1, be.e1, inverted);
344 1
            r = r || impl(bo.e2, be.e2, inverted);
345 1
            if (r)
346 1
                stack.setDim(lbefore); // purge added positive items
347 1
            return r;
348
        }
349 1
        else if (op == TOK.question)
350
        {
351 1
            CondExp co = cast(CondExp)orig;
352 1
            CondExp ce = cast(CondExp)e;
353 1
            assert(ce);
354 1
            if (!inverted)
355
            {
356 1
                const lbefore = stack.length;
357 1
                bool a = impl(co.econd, ce.econd, inverted);
358 1
                a = a && impl(co.e1, ce.e1, inverted);
359 1
                bool b;
360 1
                if (!a)
361
                {
362 1
                    b = impl(co.econd, ce.econd, !inverted);
363 1
                    b = b && impl(co.e2, ce.e2, inverted);
364
                }
365 1
                const r = a || b;
366 1
                if (r)
367 0
                    stack.setDim(lbefore);
368 1
                return r;
369
            }
370
            else
371
            {
372 1
                bool a;
373
                {
374 1
                    const lbefore = stack.length;
375 1
                    a = impl(co.econd, ce.econd, inverted);
376 1
                    a = a || impl(co.e1, ce.e1, inverted);
377 1
                    if (a)
378 1
                        stack.setDim(lbefore);
379
                }
380 1
                bool b;
381 1
                if (a)
382
                {
383 1
                    const lbefore = stack.length;
384 1
                    b = impl(co.econd, ce.econd, !inverted);
385 1
                    b = b || impl(co.e2, ce.e2, inverted);
386 1
                    if (b)
387 1
                        stack.setDim(lbefore);
388
                }
389 1
                return a && b;
390
            }
391
        }
392
        else // 'primitive' expression
393
        {
394 1
            bool value = true;
395 1
            foreach (fe; negatives)
396
            {
397 1
                if (fe is e)
398
                {
399 1
                    value = false;
400 1
                    break;
401
                }
402
            }
403 1
            const satisfied = inverted ? !value : value;
404 1
            if (!satisfied)
405 1
                stack.push(Item(orig, inverted));
406 1
            return satisfied;
407
        }
408
    }
409

410 1
    impl(original, instantiated, false);
411

412 1
    foreach (i; 0 .. stack.length)
413
    {
414
        // write the expression only
415 1
        buf.writestring("       ");
416 1
        if (stack[i].inverted)
417 1
            buf.writeByte('!');
418 1
        buf.writestring(stack[i].orig.toChars);
419
        // here with no trailing newline
420 1
        if (i + 1 < stack.length)
421 1
            buf.writenl();
422
    }
423 1
    return cast(uint)stack.length;
424
}

Read our documentation on viewing source code .

Loading