1 /**
2  * Evaluate compile-time conditionals, such as `static if` `version` and `debug`.
3  *
4  * Specification: $(LINK2 https://dlang.org/spec/version.html, Conditional Compilation)
5  *
6  * Copyright:   Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
7  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
8  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/cond.d, _cond.d)
10  * Documentation:  https://dlang.org/phobos/dmd_cond.html
11  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/cond.d
12  */
13 
14 module dmd.cond;
15 
16 import core.stdc.string;
17 import dmd.arraytypes;
18 import dmd.astenums;
19 import dmd.ast_node;
20 import dmd.dcast;
21 import dmd.dinterpret;
22 import dmd.dmodule;
23 import dmd.dscope;
24 import dmd.dsymbol;
25 import dmd.errors;
26 import dmd.expression;
27 import dmd.expressionsem;
28 import dmd.globals;
29 import dmd.identifier;
30 import dmd.location;
31 import dmd.mtype;
32 import dmd.typesem;
33 import dmd.common.outbuffer;
34 import dmd.rootobject;
35 import dmd.root.string;
36 import dmd.tokens;
37 import dmd.utils;
38 import dmd.visitor;
39 import dmd.id;
40 import dmd.statement;
41 import dmd.declaration;
42 import dmd.dstruct;
43 import dmd.func;
44 
45 /***********************************************************
46  */
47 
48 enum Include : ubyte
49 {
50     notComputed,        /// not computed yet
51     yes,                /// include the conditional code
52     no,                 /// do not include the conditional code
53 }
54 
55 extern (C++) abstract class Condition : ASTNode
56 {
57     Loc loc;
58 
59     Include inc;
60 
61     override final DYNCAST dyncast() const
62     {
63         return DYNCAST.condition;
64     }
65 
66     extern (D) this(const ref Loc loc) @safe
67     {
68         this.loc = loc;
69     }
70 
71     abstract Condition syntaxCopy();
72 
73     abstract int include(Scope* sc);
74 
75     inout(DebugCondition) isDebugCondition() inout
76     {
77         return null;
78     }
79 
80     inout(VersionCondition) isVersionCondition() inout
81     {
82         return null;
83     }
84 
85     inout(StaticIfCondition) isStaticIfCondition() inout
86     {
87         return null;
88     }
89 
90     override void accept(Visitor v)
91     {
92         v.visit(this);
93     }
94 }
95 
96 /***********************************************************
97  * Implements common functionality for StaticForeachDeclaration and
98  * StaticForeachStatement This performs the necessary lowerings before
99  * dmd.statementsem.makeTupleForeach can be used to expand the
100  * corresponding `static foreach` declaration or statement.
101  */
102 
103 extern (C++) final class StaticForeach : RootObject
104 {
105     extern(D) static immutable tupleFieldName = "tuple"; // used in lowering
106 
107     Loc loc;
108 
109     /***************
110      * Not `null` iff the `static foreach` is over an aggregate. In
111      * this case, it contains the corresponding ForeachStatement. For
112      * StaticForeachDeclaration, the body is `null`.
113     */
114     ForeachStatement aggrfe;
115     /***************
116      * Not `null` iff the `static foreach` is over a range. Exactly
117      * one of the `aggrefe` and `rangefe` fields is not null. See
118      * `aggrfe` field for more details.
119      */
120     ForeachRangeStatement rangefe;
121 
122     /***************
123      * true if it is necessary to expand a tuple into multiple
124      * variables (see lowerNonArrayAggregate).
125      */
126     bool needExpansion = false;
127 
128     extern (D) this(const ref Loc loc, ForeachStatement aggrfe, ForeachRangeStatement rangefe) @safe
129     {
130         assert(!!aggrfe ^ !!rangefe);
131 
132         this.loc = loc;
133         this.aggrfe = aggrfe;
134         this.rangefe = rangefe;
135     }
136 
137     extern (D) StaticForeach syntaxCopy()
138     {
139         return new StaticForeach(
140             loc,
141             aggrfe ? aggrfe.syntaxCopy() : null,
142             rangefe ? rangefe.syntaxCopy() : null
143         );
144     }
145 
146     /*****************************************
147      * Turn an aggregate which is an array into an expression tuple
148      * of its elements. I.e., lower
149      *     static foreach (x; [1, 2, 3, 4]) { ... }
150      * to
151      *     static foreach (x; AliasSeq!(1, 2, 3, 4)) { ... }
152      */
153     private extern(D) void lowerArrayAggregate(Scope* sc)
154     {
155         auto aggr = aggrfe.aggr;
156         Expression el = new ArrayLengthExp(aggr.loc, aggr);
157         sc = sc.startCTFE();
158         el = el.expressionSemantic(sc);
159         sc = sc.endCTFE();
160         el = el.optimize(WANTvalue);
161         el = el.ctfeInterpret();
162         if (el.op == EXP.int64)
163         {
164             Expressions *es = void;
165             if (auto ale = aggr.isArrayLiteralExp())
166             {
167                 // Directly use the elements of the array for the TupleExp creation
168                 es = ale.elements;
169             }
170             else
171             {
172                 const length = cast(size_t)el.toInteger();
173                 es = new Expressions(length);
174                 foreach (i; 0 .. length)
175                 {
176                     auto index = new IntegerExp(loc, i, Type.tsize_t);
177                     auto value = new IndexExp(aggr.loc, aggr, index);
178                     (*es)[i] = value;
179                 }
180             }
181             aggrfe.aggr = new TupleExp(aggr.loc, es);
182             aggrfe.aggr = aggrfe.aggr.expressionSemantic(sc);
183             aggrfe.aggr = aggrfe.aggr.optimize(WANTvalue);
184             aggrfe.aggr = aggrfe.aggr.ctfeInterpret();
185         }
186         else
187         {
188             aggrfe.aggr = ErrorExp.get();
189         }
190     }
191 
192     /*****************************************
193      * Wrap a statement into a function literal and call it.
194      *
195      * Params:
196      *     loc = The source location.
197      *     s  = The statement.
198      * Returns:
199      *     AST of the expression `(){ s; }()` with location loc.
200      */
201     private extern(D) Expression wrapAndCall(const ref Loc loc, Statement s)
202     {
203         auto tf = new TypeFunction(ParameterList(), null, LINK.default_, 0);
204         auto fd = new FuncLiteralDeclaration(loc, loc, tf, TOK.reserved, null);
205         fd.fbody = s;
206         auto fe = new FuncExp(loc, fd);
207         auto ce = new CallExp(loc, fe, new Expressions());
208         return ce;
209     }
210 
211     /*****************************************
212      * Create a `foreach` statement from `aggrefe/rangefe` with given
213      * `foreach` variables and body `s`.
214      *
215      * Params:
216      *     loc = The source location.
217      *     parameters = The foreach variables.
218      *     s = The `foreach` body.
219      * Returns:
220      *     `foreach (parameters; aggregate) s;` or
221      *     `foreach (parameters; lower .. upper) s;`
222      *     Where aggregate/lower, upper are as for the current StaticForeach.
223      */
224     private extern(D) Statement createForeach(const ref Loc loc, Parameters* parameters, Statement s)
225     {
226         if (aggrfe)
227         {
228             return new ForeachStatement(loc, aggrfe.op, parameters, aggrfe.aggr, s, loc);
229         }
230         else
231         {
232             assert(rangefe && parameters.length == 1);
233             return new ForeachRangeStatement(loc, rangefe.op, (*parameters)[0], rangefe.lwr, rangefe.upr, s, loc);
234         }
235     }
236 
237     /*****************************************
238      * For a `static foreach` with multiple loop variables, the
239      * aggregate is lowered to an array of tuples. As D does not have
240      * built-in tuples, we need a suitable tuple type. This generates
241      * a `struct` that serves as the tuple type. This type is only
242      * used during CTFE and hence its typeinfo will not go to the
243      * object file.
244      *
245      * Params:
246      *     loc = The source location.
247      *     e = The expressions we wish to store in the tuple.
248      *     sc  = The current scope.
249      * Returns:
250      *     A struct type of the form
251      *         struct Tuple
252      *         {
253      *             typeof(AliasSeq!(e)) tuple;
254      *         }
255      */
256 
257     private extern(D) TypeStruct createTupleType(const ref Loc loc, Expressions* e, Scope* sc)
258     {   // TODO: move to druntime?
259         auto sid = Identifier.generateId("Tuple");
260         auto sdecl = new StructDeclaration(loc, sid, false);
261         sdecl.storage_class |= STC.static_;
262         sdecl.members = new Dsymbols();
263         auto fid = Identifier.idPool(tupleFieldName);
264         auto ty = new TypeTypeof(loc, new TupleExp(loc, e));
265         sdecl.members.push(new VarDeclaration(loc, ty, fid, null, 0));
266         auto r = cast(TypeStruct)sdecl.type;
267         if (global.params.useTypeInfo && Type.dtypeinfo)
268             r.vtinfo = TypeInfoStructDeclaration.create(r); // prevent typeinfo from going to object file
269         return r;
270     }
271 
272     /*****************************************
273      * Create the AST for an instantiation of a suitable tuple type.
274      *
275      * Params:
276      *     loc = The source location.
277      *     type = A Tuple type, created with createTupleType.
278      *     e = The expressions we wish to store in the tuple.
279      * Returns:
280      *     An AST for the expression `Tuple(e)`.
281      */
282 
283     private extern(D) Expression createTuple(const ref Loc loc, TypeStruct type, Expressions* e) @safe
284     {   // TODO: move to druntime?
285         return new CallExp(loc, new TypeExp(loc, type), e);
286     }
287 
288 
289     /*****************************************
290      * Lower any aggregate that is not an array to an array using a
291      * regular foreach loop within CTFE.  If there are multiple
292      * `static foreach` loop variables, an array of tuples is
293      * generated. In thise case, the field `needExpansion` is set to
294      * true to indicate that the static foreach loop expansion will
295      * need to expand the tuples into multiple variables.
296      *
297      * For example, `static foreach (x; range) { ... }` is lowered to:
298      *
299      *     static foreach (x; {
300      *         typeof({
301      *             foreach (x; range) return x;
302      *         }())[] __res;
303      *         foreach (x; range) __res ~= x;
304      *         return __res;
305      *     }()) { ... }
306      *
307      * Finally, call `lowerArrayAggregate` to turn the produced
308      * array into an expression tuple.
309      *
310      * Params:
311      *     sc = The current scope.
312      */
313 
314     private void lowerNonArrayAggregate(Scope* sc)
315     {
316         auto nvars = aggrfe ? aggrfe.parameters.length : 1;
317         auto aloc = aggrfe ? aggrfe.aggr.loc : rangefe.lwr.loc;
318         // We need three sets of foreach loop variables because the
319         // lowering contains three foreach loops.
320         Parameters*[3] pparams = [new Parameters(), new Parameters(), new Parameters()];
321         foreach (i; 0 .. nvars)
322         {
323             foreach (params; pparams)
324             {
325                 auto p = aggrfe ? (*aggrfe.parameters)[i] : rangefe.prm;
326                 params.push(new Parameter(aloc, p.storageClass, p.type, p.ident, null, null));
327             }
328         }
329         Expression[2] res;
330         TypeStruct tplty = null;
331         if (nvars == 1) // only one `static foreach` variable, generate identifiers.
332         {
333             foreach (i; 0 .. 2)
334             {
335                 res[i] = new IdentifierExp(aloc, (*pparams[i])[0].ident);
336             }
337         }
338         else // multiple `static foreach` variables, generate tuples.
339         {
340             foreach (i; 0 .. 2)
341             {
342                 auto e = new Expressions(pparams[0].length);
343                 foreach (j, ref elem; *e)
344                 {
345                     auto p = (*pparams[i])[j];
346                     elem = new IdentifierExp(aloc, p.ident);
347                 }
348                 if (!tplty)
349                 {
350                     tplty = createTupleType(aloc, e, sc);
351                 }
352                 res[i] = createTuple(aloc, tplty, e);
353             }
354             needExpansion = true; // need to expand the tuples later
355         }
356         // generate remaining code for the new aggregate which is an
357         // array (see documentation comment).
358         if (rangefe)
359         {
360             sc = sc.startCTFE();
361             rangefe.lwr = rangefe.lwr.expressionSemantic(sc);
362             rangefe.lwr = resolveProperties(sc, rangefe.lwr);
363             rangefe.upr = rangefe.upr.expressionSemantic(sc);
364             rangefe.upr = resolveProperties(sc, rangefe.upr);
365             sc = sc.endCTFE();
366             rangefe.lwr = rangefe.lwr.optimize(WANTvalue);
367             rangefe.lwr = rangefe.lwr.ctfeInterpret();
368             rangefe.upr = rangefe.upr.optimize(WANTvalue);
369             rangefe.upr = rangefe.upr.ctfeInterpret();
370         }
371         auto s1 = new Statements();
372         auto sfe = new Statements();
373         if (tplty) sfe.push(new ExpStatement(loc, tplty.sym));
374         sfe.push(new ReturnStatement(aloc, res[0]));
375         s1.push(createForeach(aloc, pparams[0], new CompoundStatement(aloc, sfe)));
376         s1.push(new ExpStatement(aloc, new AssertExp(aloc, IntegerExp.literal!0)));
377         Type ety = new TypeTypeof(aloc, wrapAndCall(aloc, new CompoundStatement(aloc, s1)));
378         auto aty = ety.arrayOf();
379         auto idres = Identifier.generateId("__res");
380         auto vard = new VarDeclaration(aloc, aty, idres, null, STC.temp);
381         auto s2 = new Statements();
382 
383         // Run 'typeof' gagged to avoid duplicate errors and if it fails just create
384         // an empty foreach to expose them.
385         uint olderrors = global.startGagging();
386         ety = ety.typeSemantic(aloc, sc);
387         if (global.endGagging(olderrors))
388             s2.push(createForeach(aloc, pparams[1], null));
389         else
390         {
391             s2.push(new ExpStatement(aloc, vard));
392             auto catass = new CatAssignExp(aloc, new IdentifierExp(aloc, idres), res[1]);
393             s2.push(createForeach(aloc, pparams[1], new ExpStatement(aloc, catass)));
394             s2.push(new ReturnStatement(aloc, new IdentifierExp(aloc, idres)));
395         }
396 
397         Expression aggr = void;
398         Type indexty = void;
399 
400         if (rangefe && (indexty = ety).isintegral())
401         {
402             rangefe.lwr.type = indexty;
403             rangefe.upr.type = indexty;
404             auto lwrRange = getIntRange(rangefe.lwr);
405             auto uprRange = getIntRange(rangefe.upr);
406 
407             const lwr = rangefe.lwr.toInteger();
408             auto  upr = rangefe.upr.toInteger();
409             size_t length = 0;
410 
411             if (lwrRange.imin <= uprRange.imax)
412                     length = cast(size_t) (upr - lwr);
413 
414             auto exps = new Expressions(length);
415 
416             if (rangefe.op == TOK.foreach_)
417             {
418                 foreach (i; 0 .. length)
419                     (*exps)[i] = new IntegerExp(aloc, lwr + i, indexty);
420             }
421             else
422             {
423                 --upr;
424                 foreach (i; 0 .. length)
425                     (*exps)[i] = new IntegerExp(aloc, upr - i, indexty);
426             }
427             aggr = new ArrayLiteralExp(aloc, indexty.arrayOf(), exps);
428         }
429         else
430         {
431             aggr = wrapAndCall(aloc, new CompoundStatement(aloc, s2));
432             sc = sc.startCTFE();
433             aggr = aggr.expressionSemantic(sc);
434             aggr = resolveProperties(sc, aggr);
435             sc = sc.endCTFE();
436             aggr = aggr.optimize(WANTvalue);
437             aggr = aggr.ctfeInterpret();
438         }
439 
440         assert(!!aggrfe ^ !!rangefe);
441         aggrfe = new ForeachStatement(loc, TOK.foreach_, pparams[2], aggr,
442                                       aggrfe ? aggrfe._body : rangefe._body,
443                                       aggrfe ? aggrfe.endloc : rangefe.endloc);
444         rangefe = null;
445         lowerArrayAggregate(sc); // finally, turn generated array into expression tuple
446     }
447 
448     /*****************************************
449      * Perform `static foreach` lowerings that are necessary in order
450      * to finally expand the `static foreach` using
451      * `dmd.statementsem.makeTupleForeach`.
452      */
453     extern(D) void prepare(Scope* sc)
454     {
455         assert(sc);
456 
457         if (aggrfe)
458         {
459             sc = sc.startCTFE();
460             aggrfe.aggr = aggrfe.aggr.expressionSemantic(sc);
461             sc = sc.endCTFE();
462         }
463 
464         if (aggrfe && aggrfe.aggr.type.toBasetype().ty == Terror)
465         {
466             return;
467         }
468 
469         if (!ready())
470         {
471             if (aggrfe && aggrfe.aggr.type.toBasetype().ty == Tarray)
472             {
473                 lowerArrayAggregate(sc);
474             }
475             else
476             {
477                 lowerNonArrayAggregate(sc);
478             }
479         }
480     }
481 
482     /*****************************************
483      * Returns:
484      *     `true` iff ready to call `dmd.statementsem.makeTupleForeach`.
485      */
486     extern(D) bool ready()
487     {
488         return aggrfe && aggrfe.aggr && aggrfe.aggr.type && aggrfe.aggr.type.toBasetype().ty == Ttuple;
489     }
490 }
491 
492 /***********************************************************
493  */
494 extern (C++) class DVCondition : Condition
495 {
496     uint level;
497     Identifier ident;
498     Module mod;
499 
500     extern (D) this(const ref Loc loc, Module mod, uint level, Identifier ident) @safe
501     {
502         super(loc);
503         this.mod = mod;
504         this.level = level;
505         this.ident = ident;
506     }
507 
508     override final DVCondition syntaxCopy()
509     {
510         return this; // don't need to copy
511     }
512 
513     override void accept(Visitor v)
514     {
515         v.visit(this);
516     }
517 }
518 
519 /***********************************************************
520  */
521 extern (C++) final class DebugCondition : DVCondition
522 {
523     /**
524      * Add an user-supplied identifier to the list of global debug identifiers
525      *
526      * Can be called from either the driver or a `debug = Ident;` statement.
527      * Unlike version identifier, there isn't any reserved debug identifier
528      * so no validation takes place.
529      *
530      * Params:
531      *   ident = identifier to add
532      */
533     deprecated("Kept for C++ compat - Use the string overload instead")
534     static void addGlobalIdent(const(char)* ident)
535     {
536         addGlobalIdent(ident[0 .. ident.strlen]);
537     }
538 
539     /// Ditto
540     extern(D) static void addGlobalIdent(string ident)
541     {
542         // Overload necessary for string literals
543         addGlobalIdent(cast(const(char)[])ident);
544     }
545 
546 
547     /// Ditto
548     extern(D) static void addGlobalIdent(const(char)[] ident)
549     {
550         if (!global.debugids)
551             global.debugids = new Identifiers();
552         global.debugids.push(Identifier.idPool(ident));
553     }
554 
555 
556     /**
557      * Instantiate a new `DebugCondition`
558      *
559      * Params:
560      *   mod = Module this node belongs to
561      *   level = Minimum global level this condition needs to pass.
562      *           Only used if `ident` is `null`.
563      *   ident = Identifier required for this condition to pass.
564      *           If `null`, this conditiion will use an integer level.
565      *  loc = Location in the source file
566      */
567     extern (D) this(const ref Loc loc, Module mod, uint level, Identifier ident) @safe
568     {
569         super(loc, mod, level, ident);
570     }
571 
572     override int include(Scope* sc)
573     {
574         //printf("DebugCondition::include() level = %d, debuglevel = %d\n", level, global.params.debuglevel);
575         if (inc == Include.notComputed)
576         {
577             inc = Include.no;
578             bool definedInModule = false;
579             if (ident)
580             {
581                 if (findCondition(mod.debugids, ident))
582                 {
583                     inc = Include.yes;
584                     definedInModule = true;
585                 }
586                 else if (findCondition(global.debugids, ident))
587                     inc = Include.yes;
588                 else
589                 {
590                     if (!mod.debugidsNot)
591                         mod.debugidsNot = new Identifiers();
592                     mod.debugidsNot.push(ident);
593                 }
594             }
595             else if (level <= global.params.debuglevel || level <= mod.debuglevel)
596                 inc = Include.yes;
597             if (!definedInModule)
598                 printDepsConditional(sc, this, "depsDebug ");
599         }
600         return (inc == Include.yes);
601     }
602 
603     override inout(DebugCondition) isDebugCondition() inout
604     {
605         return this;
606     }
607 
608     override void accept(Visitor v)
609     {
610         v.visit(this);
611     }
612 
613     override const(char)* toChars() const
614     {
615         return ident ? ident.toChars() : "debug".ptr;
616     }
617 }
618 
619 /**
620  * Node to represent a version condition
621  *
622  * A version condition is of the form:
623  * ---
624  * version (Identifier)
625  * ---
626  * In user code.
627  * This class also provides means to add version identifier
628  * to the list of global (cross module) identifiers.
629  */
630 extern (C++) final class VersionCondition : DVCondition
631 {
632     /**
633      * Check if a given version identifier is reserved.
634      *
635      * Params:
636      *   ident = identifier being checked
637      *
638      * Returns:
639      *   `true` if it is reserved, `false` otherwise
640      */
641     extern(D) private static bool isReserved(const(char)[] ident) @safe
642     {
643         // This list doesn't include "D_*" versions, see the last return
644         switch (ident)
645         {
646             case "AArch64":
647             case "AIX":
648             case "all":
649             case "Alpha":
650             case "Alpha_HardFloat":
651             case "Alpha_SoftFloat":
652             case "Android":
653             case "ARM":
654             case "ARM_HardFloat":
655             case "ARM_SoftFloat":
656             case "ARM_SoftFP":
657             case "ARM_Thumb":
658             case "AsmJS":
659             case "assert":
660             case "AVR":
661             case "BigEndian":
662             case "BSD":
663             case "CppRuntime_Clang":
664             case "CppRuntime_DigitalMars":
665             case "CppRuntime_Gcc":
666             case "CppRuntime_Microsoft":
667             case "CppRuntime_Sun":
668             case "CRuntime_Bionic":
669             case "CRuntime_DigitalMars":
670             case "CRuntime_Glibc":
671             case "CRuntime_Microsoft":
672             case "CRuntime_Musl":
673             case "CRuntime_Newlib":
674             case "CRuntime_UClibc":
675             case "CRuntime_WASI":
676             case "Cygwin":
677             case "DigitalMars":
678             case "DragonFlyBSD":
679             case "Emscripten":
680             case "ELFv1":
681             case "ELFv2":
682             case "Epiphany":
683             case "FreeBSD":
684             case "FreeStanding":
685             case "GNU":
686             case "Haiku":
687             case "HPPA":
688             case "HPPA64":
689             case "Hurd":
690             case "IA64":
691             case "iOS":
692             case "LDC":
693             case "linux":
694             case "LittleEndian":
695             case "LoongArch32":
696             case "LoongArch64":
697             case "LoongArch_HardFloat":
698             case "LoongArch_SoftFloat":
699             case "MinGW":
700             case "MIPS32":
701             case "MIPS64":
702             case "MIPS_EABI":
703             case "MIPS_HardFloat":
704             case "MIPS_N32":
705             case "MIPS_N64":
706             case "MIPS_O32":
707             case "MIPS_O64":
708             case "MIPS_SoftFloat":
709             case "MSP430":
710             case "NetBSD":
711             case "none":
712             case "NVPTX":
713             case "NVPTX64":
714             case "OpenBSD":
715             case "OSX":
716             case "PlayStation":
717             case "PlayStation4":
718             case "Posix":
719             case "PPC":
720             case "PPC64":
721             case "PPC_HardFloat":
722             case "PPC_SoftFloat":
723             case "RISCV32":
724             case "RISCV64":
725             case "S390":
726             case "S390X":
727             case "SDC":
728             case "SH":
729             case "SkyOS":
730             case "Solaris":
731             case "SPARC":
732             case "SPARC64":
733             case "SPARC_HardFloat":
734             case "SPARC_SoftFloat":
735             case "SPARC_V8Plus":
736             case "SystemZ":
737             case "SysV3":
738             case "SysV4":
739             case "TVOS":
740             case "unittest":
741             case "VisionOS":
742             case "WASI":
743             case "WatchOS":
744             case "WebAssembly":
745             case "Win32":
746             case "Win64":
747             case "Windows":
748             case "X86":
749             case "X86_64":
750                 return true;
751 
752             default:
753                 // Anything that starts with "D_" is reserved
754                 return (ident.length >= 2 && ident[0 .. 2] == "D_");
755         }
756     }
757 
758     /**
759      * Raises an error if a version identifier is reserved.
760      *
761      * Called when setting a version identifier, e.g. `-version=identifier`
762      * parameter to the compiler or `version = Foo` in user code.
763      *
764      * Params:
765      *   loc = Where the identifier is set
766      *   ident = identifier being checked (ident[$] must be '\0')
767      */
768     extern(D) static void checkReserved(const ref Loc loc, const(char)[] ident)
769     {
770         if (isReserved(ident))
771             error(loc, "version identifier `%s` is reserved and cannot be set",
772                   ident.ptr);
773     }
774 
775     /**
776      * Add an user-supplied global identifier to the list
777      *
778      * Only called from the driver for `-version=Ident` parameters.
779      * Will raise an error if the identifier is reserved.
780      *
781      * Params:
782      *   ident = identifier to add
783      */
784     deprecated("Kept for C++ compat - Use the string overload instead")
785     static void addGlobalIdent(const(char)* ident)
786     {
787         addGlobalIdent(ident[0 .. ident.strlen]);
788     }
789 
790     /// Ditto
791     extern(D) static void addGlobalIdent(string ident)
792     {
793         // Overload necessary for string literals
794         addGlobalIdent(cast(const(char)[])ident);
795     }
796 
797 
798     /// Ditto
799     extern(D) static void addGlobalIdent(const(char)[] ident)
800     {
801         checkReserved(Loc.initial, ident);
802         addPredefinedGlobalIdent(ident);
803     }
804 
805     /**
806      * Add any global identifier to the list, without checking
807      * if it's predefined
808      *
809      * Only called from the driver after platform detection,
810      * and internally.
811      *
812      * Params:
813      *   ident = identifier to add (ident[$] must be '\0')
814      */
815     deprecated("Kept for C++ compat - Use the string overload instead")
816     static void addPredefinedGlobalIdent(const(char)* ident)
817     {
818         addPredefinedGlobalIdent(ident.toDString());
819     }
820 
821     /// Ditto
822     extern(D) static void addPredefinedGlobalIdent(string ident)
823     {
824         // Forward: Overload necessary for string literal
825         addPredefinedGlobalIdent(cast(const(char)[])ident);
826     }
827 
828 
829     /// Ditto
830     extern(D) static void addPredefinedGlobalIdent(const(char)[] ident)
831     {
832         if (!global.versionids)
833             global.versionids = new Identifiers();
834         global.versionids.push(Identifier.idPool(ident));
835     }
836 
837     /**
838      * Instantiate a new `VersionCondition`
839      *
840      * Params:
841      *   mod = Module this node belongs to
842      *   level = Minimum global level this condition needs to pass.
843      *           Only used if `ident` is `null`.
844      *   ident = Identifier required for this condition to pass.
845      *           If `null`, this conditiion will use an integer level.
846      *  loc = Location in the source file
847      */
848     extern (D) this(const ref Loc loc, Module mod, uint level, Identifier ident) @safe
849     {
850         super(loc, mod, level, ident);
851     }
852 
853     override int include(Scope* sc)
854     {
855         //printf("VersionCondition::include() level = %d, versionlevel = %d\n", level, global.params.versionlevel);
856         //if (ident) printf("\tident = '%s'\n", ident.toChars());
857         if (inc == Include.notComputed)
858         {
859             inc = Include.no;
860             bool definedInModule = false;
861             if (ident)
862             {
863                 if (findCondition(mod.versionids, ident))
864                 {
865                     inc = Include.yes;
866                     definedInModule = true;
867                 }
868                 else if (findCondition(global.versionids, ident))
869                     inc = Include.yes;
870                 else
871                 {
872                     if (!mod.versionidsNot)
873                         mod.versionidsNot = new Identifiers();
874                     mod.versionidsNot.push(ident);
875                 }
876             }
877             else if (level <= global.params.versionlevel || level <= mod.versionlevel)
878                 inc = Include.yes;
879             if (!definedInModule &&
880                 (!ident || (!isReserved(ident.toString()) && ident != Id._unittest && ident != Id._assert)))
881             {
882                 printDepsConditional(sc, this, "depsVersion ");
883             }
884         }
885         return (inc == Include.yes);
886     }
887 
888     override inout(VersionCondition) isVersionCondition() inout
889     {
890         return this;
891     }
892 
893     override void accept(Visitor v)
894     {
895         v.visit(this);
896     }
897 
898     override const(char)* toChars() const
899     {
900         return ident ? ident.toChars() : "version".ptr;
901     }
902 }
903 
904 /***********************************************************
905  */
906 extern (C++) final class StaticIfCondition : Condition
907 {
908     Expression exp;
909 
910     extern (D) this(const ref Loc loc, Expression exp) @safe
911     {
912         super(loc);
913         this.exp = exp;
914     }
915 
916     override StaticIfCondition syntaxCopy()
917     {
918         return new StaticIfCondition(loc, exp.syntaxCopy());
919     }
920 
921     override int include(Scope* sc)
922     {
923         // printf("StaticIfCondition::include(sc = %p) this=%p inc = %d\n", sc, this, inc);
924 
925         int errorReturn()
926         {
927             if (!global.gag)
928                 inc = Include.no; // so we don't see the error message again
929             return 0;
930         }
931 
932         if (inc == Include.notComputed)
933         {
934             if (!sc)
935             {
936                 error(loc, "`static if` conditional cannot be at global scope");
937                 inc = Include.no;
938                 return 0;
939             }
940 
941             import dmd.staticcond;
942             bool errors;
943 
944             bool result = evalStaticCondition(sc, exp, exp, errors);
945 
946             // Prevent repeated condition evaluation.
947             // See: fail_compilation/fail7815.d
948             if (inc != Include.notComputed)
949                 return (inc == Include.yes);
950             if (errors)
951                 return errorReturn();
952             if (result)
953                 inc = Include.yes;
954             else
955                 inc = Include.no;
956         }
957         return (inc == Include.yes);
958     }
959 
960     override void accept(Visitor v)
961     {
962         v.visit(this);
963     }
964 
965     override inout(StaticIfCondition) isStaticIfCondition() inout
966     {
967         return this;
968     }
969 
970     override const(char)* toChars() const
971     {
972         return exp ? exp.toChars() : "static if".ptr;
973     }
974 }
975 
976 
977 /****************************************
978  * Find `ident` in an array of identifiers.
979  * Params:
980  *      ids = array of identifiers
981  *      ident = identifier to search for
982  * Returns:
983  *      true if found
984  */
985 bool findCondition(Identifiers* ids, Identifier ident) @safe nothrow pure
986 {
987     if (ids)
988     {
989         foreach (id; *ids)
990         {
991             if (id == ident)
992                 return true;
993         }
994     }
995     return false;
996 }
997 
998 // Helper for printing dependency information
999 private void printDepsConditional(Scope* sc, DVCondition condition, const(char)[] depType)
1000 {
1001     if (!global.params.moduleDeps.buffer || global.params.moduleDeps.name)
1002         return;
1003     OutBuffer* ob = global.params.moduleDeps.buffer;
1004     Module imod = sc ? sc._module : condition.mod;
1005     if (!imod)
1006         return;
1007     ob.writestring(depType);
1008     ob.writestring(imod.toPrettyChars());
1009     ob.writestring(" (");
1010     escapePath(ob, imod.srcfile.toChars());
1011     ob.writestring(") : ");
1012     if (condition.ident)
1013         ob.writestring(condition.ident.toString());
1014     else
1015         ob.print(condition.level);
1016     ob.writeByte('\n');
1017 }