2 * Evaluate compile-time conditionals, such as `static if` `version` and `debug`.
4 * Specification: $(LINK2 https://dlang.org/spec/version.html, Conditional Compilation)
6 * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
7 * Authors: $(LINK2 http://www.digitalmars.com, Walter Bright)
8 * License: $(LINK2 http://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
16 import core
.stdc
.string
;
17 import dmd
.arraytypes
;
25 import dmd
.expression
;
26 import dmd
.expressionsem
;
28 import dmd
.identifier
;
31 import dmd
.common
.outbuffer
;
32 import dmd
.root
.rootobject
;
33 import dmd
.root
.string
;
39 import dmd
.declaration
;
43 /***********************************************************
48 notComputed
, /// not computed yet
49 yes
, /// include the conditional code
50 no
, /// do not include the conditional code
53 extern (C
++) abstract class Condition
: ASTNode
59 override final DYNCAST
dyncast() const
61 return DYNCAST
.condition
;
64 extern (D
) this(const ref Loc loc
)
69 abstract Condition
syntaxCopy();
71 abstract int include(Scope
* sc
);
73 inout(DebugCondition
) isDebugCondition() inout
78 inout(VersionCondition
) isVersionCondition() inout
83 override void accept(Visitor v
)
89 /***********************************************************
90 * Implements common functionality for StaticForeachDeclaration and
91 * StaticForeachStatement This performs the necessary lowerings before
92 * dmd.statementsem.makeTupleForeach can be used to expand the
93 * corresponding `static foreach` declaration or statement.
96 extern (C
++) final class StaticForeach
: RootObject
98 extern(D
) static immutable tupleFieldName
= "tuple"; // used in lowering
103 * Not `null` iff the `static foreach` is over an aggregate. In
104 * this case, it contains the corresponding ForeachStatement. For
105 * StaticForeachDeclaration, the body is `null`.
107 ForeachStatement aggrfe
;
109 * Not `null` iff the `static foreach` is over a range. Exactly
110 * one of the `aggrefe` and `rangefe` fields is not null. See
111 * `aggrfe` field for more details.
113 ForeachRangeStatement rangefe
;
116 * true if it is necessary to expand a tuple into multiple
117 * variables (see lowerNonArrayAggregate).
119 bool needExpansion
= false;
121 extern (D
) this(const ref Loc loc
, ForeachStatement aggrfe
, ForeachRangeStatement rangefe
)
123 assert(!!aggrfe ^
!!rangefe
);
126 this.aggrfe
= aggrfe
;
127 this.rangefe
= rangefe
;
130 StaticForeach
syntaxCopy()
132 return new StaticForeach(
134 aggrfe ? aggrfe
.syntaxCopy() : null,
135 rangefe ? rangefe
.syntaxCopy() : null
139 /*****************************************
140 * Turn an aggregate which is an array into an expression tuple
141 * of its elements. I.e., lower
142 * static foreach (x; [1, 2, 3, 4]) { ... }
144 * static foreach (x; AliasSeq!(1, 2, 3, 4)) { ... }
146 private extern(D
) void lowerArrayAggregate(Scope
* sc
)
148 auto aggr
= aggrfe
.aggr
;
149 Expression el
= new ArrayLengthExp(aggr
.loc
, aggr
);
151 el
= el
.expressionSemantic(sc
);
153 el
= el
.optimize(WANTvalue
);
154 el
= el
.ctfeInterpret();
155 if (el
.op
== EXP
.int64
)
157 Expressions
*es
= void;
158 if (auto ale
= aggr
.isArrayLiteralExp())
160 // Directly use the elements of the array for the TupleExp creation
165 const length
= cast(size_t
)el
.toInteger();
166 es
= new Expressions(length
);
167 foreach (i
; 0 .. length
)
169 auto index
= new IntegerExp(loc
, i
, Type
.tsize_t
);
170 auto value
= new IndexExp(aggr
.loc
, aggr
, index
);
174 aggrfe
.aggr
= new TupleExp(aggr
.loc
, es
);
175 aggrfe
.aggr
= aggrfe
.aggr
.expressionSemantic(sc
);
176 aggrfe
.aggr
= aggrfe
.aggr
.optimize(WANTvalue
);
177 aggrfe
.aggr
= aggrfe
.aggr
.ctfeInterpret();
181 aggrfe
.aggr
= ErrorExp
.get();
185 /*****************************************
186 * Wrap a statement into a function literal and call it.
189 * loc = The source location.
192 * AST of the expression `(){ s; }()` with location loc.
194 private extern(D
) Expression
wrapAndCall(const ref Loc loc
, Statement s
)
196 auto tf
= new TypeFunction(ParameterList(), null, LINK
.default_
, 0);
197 auto fd
= new FuncLiteralDeclaration(loc
, loc
, tf
, TOK
.reserved
, null);
199 auto fe
= new FuncExp(loc
, fd
);
200 auto ce
= new CallExp(loc
, fe
, new Expressions());
204 /*****************************************
205 * Create a `foreach` statement from `aggrefe/rangefe` with given
206 * `foreach` variables and body `s`.
209 * loc = The source location.
210 * parameters = The foreach variables.
211 * s = The `foreach` body.
213 * `foreach (parameters; aggregate) s;` or
214 * `foreach (parameters; lower .. upper) s;`
215 * Where aggregate/lower, upper are as for the current StaticForeach.
217 private extern(D
) Statement
createForeach(const ref Loc loc
, Parameters
* parameters
, Statement s
)
221 return new ForeachStatement(loc
, aggrfe
.op
, parameters
, aggrfe
.aggr
.syntaxCopy(), s
, loc
);
225 assert(rangefe
&& parameters
.dim
== 1);
226 return new ForeachRangeStatement(loc
, rangefe
.op
, (*parameters
)[0], rangefe
.lwr
.syntaxCopy(), rangefe
.upr
.syntaxCopy(), s
, loc
);
230 /*****************************************
231 * For a `static foreach` with multiple loop variables, the
232 * aggregate is lowered to an array of tuples. As D does not have
233 * built-in tuples, we need a suitable tuple type. This generates
234 * a `struct` that serves as the tuple type. This type is only
235 * used during CTFE and hence its typeinfo will not go to the
239 * loc = The source location.
240 * e = The expressions we wish to store in the tuple.
241 * sc = The current scope.
243 * A struct type of the form
246 * typeof(AliasSeq!(e)) tuple;
250 private extern(D
) TypeStruct
createTupleType(const ref Loc loc
, Expressions
* e
, Scope
* sc
)
251 { // TODO: move to druntime?
252 auto sid
= Identifier
.generateId("Tuple");
253 auto sdecl
= new StructDeclaration(loc
, sid
, false);
254 sdecl
.storage_class |
= STC
.static_
;
255 sdecl
.members
= new Dsymbols();
256 auto fid
= Identifier
.idPool(tupleFieldName
.ptr
, tupleFieldName
.length
);
257 auto ty
= new TypeTypeof(loc
, new TupleExp(loc
, e
));
258 sdecl
.members
.push(new VarDeclaration(loc
, ty
, fid
, null, 0));
259 auto r
= cast(TypeStruct
)sdecl
.type
;
260 if (global
.params
.useTypeInfo
&& Type
.dtypeinfo
)
261 r
.vtinfo
= TypeInfoStructDeclaration
.create(r
); // prevent typeinfo from going to object file
265 /*****************************************
266 * Create the AST for an instantiation of a suitable tuple type.
269 * loc = The source location.
270 * type = A Tuple type, created with createTupleType.
271 * e = The expressions we wish to store in the tuple.
273 * An AST for the expression `Tuple(e)`.
276 private extern(D
) Expression
createTuple(const ref Loc loc
, TypeStruct type
, Expressions
* e
)
277 { // TODO: move to druntime?
278 return new CallExp(loc
, new TypeExp(loc
, type
), e
);
282 /*****************************************
283 * Lower any aggregate that is not an array to an array using a
284 * regular foreach loop within CTFE. If there are multiple
285 * `static foreach` loop variables, an array of tuples is
286 * generated. In thise case, the field `needExpansion` is set to
287 * true to indicate that the static foreach loop expansion will
288 * need to expand the tuples into multiple variables.
290 * For example, `static foreach (x; range) { ... }` is lowered to:
292 * static foreach (x; {
294 * foreach (x; range) return x;
296 * foreach (x; range) __res ~= x;
300 * Finally, call `lowerArrayAggregate` to turn the produced
301 * array into an expression tuple.
304 * sc = The current scope.
307 private void lowerNonArrayAggregate(Scope
* sc
)
309 auto nvars
= aggrfe ? aggrfe
.parameters
.dim
: 1;
310 auto aloc
= aggrfe ? aggrfe
.aggr
.loc
: rangefe
.lwr
.loc
;
311 // We need three sets of foreach loop variables because the
312 // lowering contains three foreach loops.
313 Parameters
*[3] pparams
= [new Parameters(), new Parameters(), new Parameters()];
314 foreach (i
; 0 .. nvars
)
316 foreach (params
; pparams
)
318 auto p
= aggrfe ?
(*aggrfe
.parameters
)[i
] : rangefe
.prm
;
319 params
.push(new Parameter(p
.storageClass
, p
.type
, p
.ident
, null, null));
323 TypeStruct tplty
= null;
324 if (nvars
== 1) // only one `static foreach` variable, generate identifiers.
328 res
[i
] = new IdentifierExp(aloc
, (*pparams
[i
])[0].ident
);
331 else // multiple `static foreach` variables, generate tuples.
335 auto e
= new Expressions(pparams
[0].dim
);
336 foreach (j
, ref elem
; *e
)
338 auto p
= (*pparams
[i
])[j
];
339 elem
= new IdentifierExp(aloc
, p
.ident
);
343 tplty
= createTupleType(aloc
, e
, sc
);
345 res
[i
] = createTuple(aloc
, tplty
, e
);
347 needExpansion
= true; // need to expand the tuples later
349 // generate remaining code for the new aggregate which is an
350 // array (see documentation comment).
354 rangefe
.lwr
= rangefe
.lwr
.expressionSemantic(sc
);
355 rangefe
.lwr
= resolveProperties(sc
, rangefe
.lwr
);
356 rangefe
.upr
= rangefe
.upr
.expressionSemantic(sc
);
357 rangefe
.upr
= resolveProperties(sc
, rangefe
.upr
);
359 rangefe
.lwr
= rangefe
.lwr
.optimize(WANTvalue
);
360 rangefe
.lwr
= rangefe
.lwr
.ctfeInterpret();
361 rangefe
.upr
= rangefe
.upr
.optimize(WANTvalue
);
362 rangefe
.upr
= rangefe
.upr
.ctfeInterpret();
364 auto s1
= new Statements();
365 auto sfe
= new Statements();
366 if (tplty
) sfe
.push(new ExpStatement(loc
, tplty
.sym
));
367 sfe
.push(new ReturnStatement(aloc
, res
[0]));
368 s1
.push(createForeach(aloc
, pparams
[0], new CompoundStatement(aloc
, sfe
)));
369 s1
.push(new ExpStatement(aloc
, new AssertExp(aloc
, IntegerExp
.literal
!0)));
370 Type ety
= new TypeTypeof(aloc
, wrapAndCall(aloc
, new CompoundStatement(aloc
, s1
)));
371 auto aty
= ety
.arrayOf();
372 auto idres
= Identifier
.generateId("__res");
373 auto vard
= new VarDeclaration(aloc
, aty
, idres
, null);
374 auto s2
= new Statements();
376 // Run 'typeof' gagged to avoid duplicate errors and if it fails just create
377 // an empty foreach to expose them.
378 uint olderrors
= global
.startGagging();
379 ety
= ety
.typeSemantic(aloc
, sc
);
380 if (global
.endGagging(olderrors
))
381 s2
.push(createForeach(aloc
, pparams
[1], null));
384 s2
.push(new ExpStatement(aloc
, vard
));
385 auto catass
= new CatAssignExp(aloc
, new IdentifierExp(aloc
, idres
), res
[1]);
386 s2
.push(createForeach(aloc
, pparams
[1], new ExpStatement(aloc
, catass
)));
387 s2
.push(new ReturnStatement(aloc
, new IdentifierExp(aloc
, idres
)));
390 Expression aggr
= void;
393 if (rangefe
&& (indexty
= ety
).isintegral())
395 rangefe
.lwr
.type
= indexty
;
396 rangefe
.upr
.type
= indexty
;
397 auto lwrRange
= getIntRange(rangefe
.lwr
);
398 auto uprRange
= getIntRange(rangefe
.upr
);
400 const lwr
= rangefe
.lwr
.toInteger();
401 auto upr
= rangefe
.upr
.toInteger();
404 if (lwrRange
.imin
<= uprRange
.imax
)
405 length
= cast(size_t
) (upr
- lwr
);
407 auto exps
= new Expressions(length
);
409 if (rangefe
.op
== TOK
.foreach_
)
411 foreach (i
; 0 .. length
)
412 (*exps
)[i
] = new IntegerExp(aloc
, lwr
+ i
, indexty
);
417 foreach (i
; 0 .. length
)
418 (*exps
)[i
] = new IntegerExp(aloc
, upr
- i
, indexty
);
420 aggr
= new ArrayLiteralExp(aloc
, indexty
.arrayOf(), exps
);
424 aggr
= wrapAndCall(aloc
, new CompoundStatement(aloc
, s2
));
426 aggr
= aggr
.expressionSemantic(sc
);
427 aggr
= resolveProperties(sc
, aggr
);
429 aggr
= aggr
.optimize(WANTvalue
);
430 aggr
= aggr
.ctfeInterpret();
433 assert(!!aggrfe ^
!!rangefe
);
434 aggrfe
= new ForeachStatement(loc
, TOK
.foreach_
, pparams
[2], aggr
,
435 aggrfe ? aggrfe
._body
: rangefe
._body
,
436 aggrfe ? aggrfe
.endloc
: rangefe
.endloc
);
438 lowerArrayAggregate(sc
); // finally, turn generated array into expression tuple
441 /*****************************************
442 * Perform `static foreach` lowerings that are necessary in order
443 * to finally expand the `static foreach` using
444 * `dmd.statementsem.makeTupleForeach`.
446 extern(D
) void prepare(Scope
* sc
)
453 aggrfe
.aggr
= aggrfe
.aggr
.expressionSemantic(sc
);
457 if (aggrfe
&& aggrfe
.aggr
.type
.toBasetype().ty
== Terror
)
464 if (aggrfe
&& aggrfe
.aggr
.type
.toBasetype().ty
== Tarray
)
466 lowerArrayAggregate(sc
);
470 lowerNonArrayAggregate(sc
);
475 /*****************************************
477 * `true` iff ready to call `dmd.statementsem.makeTupleForeach`.
479 extern(D
) bool ready()
481 return aggrfe
&& aggrfe
.aggr
&& aggrfe
.aggr
.type
&& aggrfe
.aggr
.type
.toBasetype().ty
== Ttuple
;
485 /***********************************************************
487 extern (C
++) class DVCondition
: Condition
493 extern (D
) this(const ref Loc loc
, Module mod
, uint level
, Identifier ident
)
501 override final DVCondition
syntaxCopy()
503 return this; // don't need to copy
506 override void accept(Visitor v
)
512 /***********************************************************
514 extern (C
++) final class DebugCondition
: DVCondition
517 * Add an user-supplied identifier to the list of global debug identifiers
519 * Can be called from either the driver or a `debug = Ident;` statement.
520 * Unlike version identifier, there isn't any reserved debug identifier
521 * so no validation takes place.
524 * ident = identifier to add
526 deprecated("Kept for C++ compat - Use the string overload instead")
527 static void addGlobalIdent(const(char)* ident
)
529 addGlobalIdent(ident
[0 .. ident
.strlen
]);
533 extern(D
) static void addGlobalIdent(string ident
)
535 // Overload necessary for string literals
536 addGlobalIdent(cast(const(char)[])ident
);
541 extern(D
) static void addGlobalIdent(const(char)[] ident
)
543 if (!global
.debugids
)
544 global
.debugids
= new Identifiers();
545 global
.debugids
.push(Identifier
.idPool(ident
));
550 * Instantiate a new `DebugCondition`
553 * mod = Module this node belongs to
554 * level = Minimum global level this condition needs to pass.
555 * Only used if `ident` is `null`.
556 * ident = Identifier required for this condition to pass.
557 * If `null`, this conditiion will use an integer level.
558 * loc = Location in the source file
560 extern (D
) this(const ref Loc loc
, Module mod
, uint level
, Identifier ident
)
562 super(loc
, mod
, level
, ident
);
565 override int include(Scope
* sc
)
567 //printf("DebugCondition::include() level = %d, debuglevel = %d\n", level, global.params.debuglevel);
568 if (inc == Include
.notComputed
)
571 bool definedInModule
= false;
574 if (findCondition(mod
.debugids
, ident
))
577 definedInModule
= true;
579 else if (findCondition(global
.debugids
, ident
))
583 if (!mod
.debugidsNot
)
584 mod
.debugidsNot
= new Identifiers();
585 mod
.debugidsNot
.push(ident
);
588 else if (level
<= global
.params
.debuglevel || level
<= mod
.debuglevel
)
590 if (!definedInModule
)
591 printDepsConditional(sc
, this, "depsDebug ");
593 return (inc == Include
.yes
);
596 override inout(DebugCondition
) isDebugCondition() inout
601 override void accept(Visitor v
)
606 override const(char)* toChars() const
608 return ident ? ident
.toChars() : "debug".ptr
;
613 * Node to represent a version condition
615 * A version condition is of the form:
617 * version (Identifier)
620 * This class also provides means to add version identifier
621 * to the list of global (cross module) identifiers.
623 extern (C
++) final class VersionCondition
: DVCondition
626 * Check if a given version identifier is reserved.
629 * ident = identifier being checked
632 * `true` if it is reserved, `false` otherwise
634 extern(D
) private static bool isReserved(const(char)[] ident
)
636 // This list doesn't include "D_*" versions, see the last return
643 case "Alpha_HardFloat":
644 case "Alpha_SoftFloat":
647 case "ARM_HardFloat":
648 case "ARM_SoftFloat":
656 case "CppRuntime_Clang":
657 case "CppRuntime_DigitalMars":
658 case "CppRuntime_Gcc":
659 case "CppRuntime_Microsoft":
660 case "CppRuntime_Sun":
661 case "CRuntime_Bionic":
662 case "CRuntime_DigitalMars":
663 case "CRuntime_Glibc":
664 case "CRuntime_Microsoft":
665 case "CRuntime_Musl":
666 case "CRuntime_Newlib":
667 case "CRuntime_UClibc":
668 case "CRuntime_WASI":
692 case "MIPS_HardFloat":
697 case "MIPS_SoftFloat":
710 case "PPC_HardFloat":
711 case "PPC_SoftFloat":
722 case "SPARC_HardFloat":
723 case "SPARC_SoftFloat":
741 // Anything that starts with "D_" is reserved
742 return (ident
.length
>= 2 && ident
[0 .. 2] == "D_");
747 * Raises an error if a version identifier is reserved.
749 * Called when setting a version identifier, e.g. `-version=identifier`
750 * parameter to the compiler or `version = Foo` in user code.
753 * loc = Where the identifier is set
754 * ident = identifier being checked (ident[$] must be '\0')
756 extern(D
) static void checkReserved(const ref Loc loc
, const(char)[] ident
)
758 if (isReserved(ident
))
759 error(loc
, "version identifier `%s` is reserved and cannot be set",
764 * Add an user-supplied global identifier to the list
766 * Only called from the driver for `-version=Ident` parameters.
767 * Will raise an error if the identifier is reserved.
770 * ident = identifier to add
772 deprecated("Kept for C++ compat - Use the string overload instead")
773 static void addGlobalIdent(const(char)* ident
)
775 addGlobalIdent(ident
[0 .. ident
.strlen
]);
779 extern(D
) static void addGlobalIdent(string ident
)
781 // Overload necessary for string literals
782 addGlobalIdent(cast(const(char)[])ident
);
787 extern(D
) static void addGlobalIdent(const(char)[] ident
)
789 checkReserved(Loc
.initial
, ident
);
790 addPredefinedGlobalIdent(ident
);
794 * Add any global identifier to the list, without checking
797 * Only called from the driver after platform detection,
801 * ident = identifier to add (ident[$] must be '\0')
803 deprecated("Kept for C++ compat - Use the string overload instead")
804 static void addPredefinedGlobalIdent(const(char)* ident
)
806 addPredefinedGlobalIdent(ident
.toDString());
810 extern(D
) static void addPredefinedGlobalIdent(string ident
)
812 // Forward: Overload necessary for string literal
813 addPredefinedGlobalIdent(cast(const(char)[])ident
);
818 extern(D
) static void addPredefinedGlobalIdent(const(char)[] ident
)
820 if (!global
.versionids
)
821 global
.versionids
= new Identifiers();
822 global
.versionids
.push(Identifier
.idPool(ident
));
826 * Instantiate a new `VersionCondition`
829 * mod = Module this node belongs to
830 * level = Minimum global level this condition needs to pass.
831 * Only used if `ident` is `null`.
832 * ident = Identifier required for this condition to pass.
833 * If `null`, this conditiion will use an integer level.
834 * loc = Location in the source file
836 extern (D
) this(const ref Loc loc
, Module mod
, uint level
, Identifier ident
)
838 super(loc
, mod
, level
, ident
);
841 override int include(Scope
* sc
)
843 //printf("VersionCondition::include() level = %d, versionlevel = %d\n", level, global.params.versionlevel);
844 //if (ident) printf("\tident = '%s'\n", ident.toChars());
845 if (inc == Include
.notComputed
)
848 bool definedInModule
= false;
851 if (findCondition(mod
.versionids
, ident
))
854 definedInModule
= true;
856 else if (findCondition(global
.versionids
, ident
))
860 if (!mod
.versionidsNot
)
861 mod
.versionidsNot
= new Identifiers();
862 mod
.versionidsNot
.push(ident
);
865 else if (level
<= global
.params
.versionlevel || level
<= mod
.versionlevel
)
867 if (!definedInModule
&&
868 (!ident ||
(!isReserved(ident
.toString()) && ident
!= Id
._unittest
&& ident
!= Id
._assert
)))
870 printDepsConditional(sc
, this, "depsVersion ");
873 return (inc == Include
.yes
);
876 override inout(VersionCondition
) isVersionCondition() inout
881 override void accept(Visitor v
)
886 override const(char)* toChars() const
888 return ident ? ident
.toChars() : "version".ptr
;
892 /***********************************************************
894 extern (C
++) final class StaticIfCondition
: Condition
898 extern (D
) this(const ref Loc loc
, Expression exp
)
904 override StaticIfCondition
syntaxCopy()
906 return new StaticIfCondition(loc
, exp
.syntaxCopy());
909 override int include(Scope
* sc
)
911 // printf("StaticIfCondition::include(sc = %p) this=%p inc = %d\n", sc, this, inc);
916 inc = Include
.no
; // so we don't see the error message again
920 if (inc == Include
.notComputed
)
924 error(loc
, "`static if` conditional cannot be at global scope");
929 import dmd
.staticcond
;
933 return errorReturn();
935 bool result
= evalStaticCondition(sc
, exp
, exp
, errors
);
937 // Prevent repeated condition evaluation.
938 // See: fail_compilation/fail7815.d
939 if (inc != Include
.notComputed
)
940 return (inc == Include
.yes
);
942 return errorReturn();
948 return (inc == Include
.yes
);
951 override void accept(Visitor v
)
956 override const(char)* toChars() const
958 return exp ? exp
.toChars() : "static if".ptr
;
963 /****************************************
964 * Find `ident` in an array of identifiers.
966 * ids = array of identifiers
967 * ident = identifier to search for
971 bool findCondition(Identifiers
* ids
, Identifier ident
) @safe nothrow pure
984 // Helper for printing dependency information
985 private void printDepsConditional(Scope
* sc
, DVCondition condition
, const(char)[] depType
)
987 if (!global
.params
.moduleDeps || global
.params
.moduleDepsFile
)
989 OutBuffer
* ob
= global
.params
.moduleDeps
;
990 Module imod
= sc ? sc
._module
: condition
.mod
;
993 ob
.writestring(depType
);
994 ob
.writestring(imod
.toPrettyChars());
995 ob
.writestring(" (");
996 escapePath(ob
, imod
.srcfile
.toChars());
997 ob
.writestring(") : ");
999 ob
.writestring(condition
.ident
.toString());
1001 ob
.print(condition
.level
);