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-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
16 import core
.stdc
.string
;
17 import dmd
.arraytypes
;
25 import dmd
.expression
;
26 import dmd
.expressionsem
;
28 import dmd
.identifier
;
32 import dmd
.common
.outbuffer
;
33 import dmd
.root
.rootobject
;
34 import dmd
.root
.string
;
40 import dmd
.declaration
;
44 /***********************************************************
49 notComputed
, /// not computed yet
50 yes
, /// include the conditional code
51 no
, /// do not include the conditional code
54 extern (C
++) abstract class Condition
: ASTNode
60 override final DYNCAST
dyncast() const
62 return DYNCAST
.condition
;
65 extern (D
) this(const ref Loc loc
) @safe
70 abstract Condition
syntaxCopy();
72 abstract int include(Scope
* sc
);
74 inout(DebugCondition
) isDebugCondition() inout
79 inout(VersionCondition
) isVersionCondition() inout
84 inout(StaticIfCondition
) isStaticIfCondition() inout
89 override void accept(Visitor v
)
95 /***********************************************************
96 * Implements common functionality for StaticForeachDeclaration and
97 * StaticForeachStatement This performs the necessary lowerings before
98 * dmd.statementsem.makeTupleForeach can be used to expand the
99 * corresponding `static foreach` declaration or statement.
102 extern (C
++) final class StaticForeach
: RootObject
104 extern(D
) static immutable tupleFieldName
= "tuple"; // used in lowering
109 * Not `null` iff the `static foreach` is over an aggregate. In
110 * this case, it contains the corresponding ForeachStatement. For
111 * StaticForeachDeclaration, the body is `null`.
113 ForeachStatement aggrfe
;
115 * Not `null` iff the `static foreach` is over a range. Exactly
116 * one of the `aggrefe` and `rangefe` fields is not null. See
117 * `aggrfe` field for more details.
119 ForeachRangeStatement rangefe
;
122 * true if it is necessary to expand a tuple into multiple
123 * variables (see lowerNonArrayAggregate).
125 bool needExpansion
= false;
127 extern (D
) this(const ref Loc loc
, ForeachStatement aggrfe
, ForeachRangeStatement rangefe
) @safe
129 assert(!!aggrfe ^
!!rangefe
);
132 this.aggrfe
= aggrfe
;
133 this.rangefe
= rangefe
;
136 StaticForeach
syntaxCopy()
138 return new StaticForeach(
140 aggrfe ? aggrfe
.syntaxCopy() : null,
141 rangefe ? rangefe
.syntaxCopy() : null
145 /*****************************************
146 * Turn an aggregate which is an array into an expression tuple
147 * of its elements. I.e., lower
148 * static foreach (x; [1, 2, 3, 4]) { ... }
150 * static foreach (x; AliasSeq!(1, 2, 3, 4)) { ... }
152 private extern(D
) void lowerArrayAggregate(Scope
* sc
)
154 auto aggr
= aggrfe
.aggr
;
155 Expression el
= new ArrayLengthExp(aggr
.loc
, aggr
);
157 el
= el
.expressionSemantic(sc
);
159 el
= el
.optimize(WANTvalue
);
160 el
= el
.ctfeInterpret();
161 if (el
.op
== EXP
.int64
)
163 Expressions
*es
= void;
164 if (auto ale
= aggr
.isArrayLiteralExp())
166 // Directly use the elements of the array for the TupleExp creation
171 const length
= cast(size_t
)el
.toInteger();
172 es
= new Expressions(length
);
173 foreach (i
; 0 .. length
)
175 auto index
= new IntegerExp(loc
, i
, Type
.tsize_t
);
176 auto value
= new IndexExp(aggr
.loc
, aggr
, index
);
180 aggrfe
.aggr
= new TupleExp(aggr
.loc
, es
);
181 aggrfe
.aggr
= aggrfe
.aggr
.expressionSemantic(sc
);
182 aggrfe
.aggr
= aggrfe
.aggr
.optimize(WANTvalue
);
183 aggrfe
.aggr
= aggrfe
.aggr
.ctfeInterpret();
187 aggrfe
.aggr
= ErrorExp
.get();
191 /*****************************************
192 * Wrap a statement into a function literal and call it.
195 * loc = The source location.
198 * AST of the expression `(){ s; }()` with location loc.
200 private extern(D
) Expression
wrapAndCall(const ref Loc loc
, Statement s
)
202 auto tf
= new TypeFunction(ParameterList(), null, LINK
.default_
, 0);
203 auto fd
= new FuncLiteralDeclaration(loc
, loc
, tf
, TOK
.reserved
, null);
205 auto fe
= new FuncExp(loc
, fd
);
206 auto ce
= new CallExp(loc
, fe
, new Expressions());
210 /*****************************************
211 * Create a `foreach` statement from `aggrefe/rangefe` with given
212 * `foreach` variables and body `s`.
215 * loc = The source location.
216 * parameters = The foreach variables.
217 * s = The `foreach` body.
219 * `foreach (parameters; aggregate) s;` or
220 * `foreach (parameters; lower .. upper) s;`
221 * Where aggregate/lower, upper are as for the current StaticForeach.
223 private extern(D
) Statement
createForeach(const ref Loc loc
, Parameters
* parameters
, Statement s
)
227 return new ForeachStatement(loc
, aggrfe
.op
, parameters
, aggrfe
.aggr
, s
, loc
);
231 assert(rangefe
&& parameters
.length
== 1);
232 return new ForeachRangeStatement(loc
, rangefe
.op
, (*parameters
)[0], rangefe
.lwr
, rangefe
.upr
, s
, loc
);
236 /*****************************************
237 * For a `static foreach` with multiple loop variables, the
238 * aggregate is lowered to an array of tuples. As D does not have
239 * built-in tuples, we need a suitable tuple type. This generates
240 * a `struct` that serves as the tuple type. This type is only
241 * used during CTFE and hence its typeinfo will not go to the
245 * loc = The source location.
246 * e = The expressions we wish to store in the tuple.
247 * sc = The current scope.
249 * A struct type of the form
252 * typeof(AliasSeq!(e)) tuple;
256 private extern(D
) TypeStruct
createTupleType(const ref Loc loc
, Expressions
* e
, Scope
* sc
)
257 { // TODO: move to druntime?
258 auto sid
= Identifier
.generateId("Tuple");
259 auto sdecl
= new StructDeclaration(loc
, sid
, false);
260 sdecl
.storage_class |
= STC
.static_
;
261 sdecl
.members
= new Dsymbols();
262 auto fid
= Identifier
.idPool(tupleFieldName
);
263 auto ty
= new TypeTypeof(loc
, new TupleExp(loc
, e
));
264 sdecl
.members
.push(new VarDeclaration(loc
, ty
, fid
, null, 0));
265 auto r
= cast(TypeStruct
)sdecl
.type
;
266 if (global
.params
.useTypeInfo
&& Type
.dtypeinfo
)
267 r
.vtinfo
= TypeInfoStructDeclaration
.create(r
); // prevent typeinfo from going to object file
271 /*****************************************
272 * Create the AST for an instantiation of a suitable tuple type.
275 * loc = The source location.
276 * type = A Tuple type, created with createTupleType.
277 * e = The expressions we wish to store in the tuple.
279 * An AST for the expression `Tuple(e)`.
282 private extern(D
) Expression
createTuple(const ref Loc loc
, TypeStruct type
, Expressions
* e
) @safe
283 { // TODO: move to druntime?
284 return new CallExp(loc
, new TypeExp(loc
, type
), e
);
288 /*****************************************
289 * Lower any aggregate that is not an array to an array using a
290 * regular foreach loop within CTFE. If there are multiple
291 * `static foreach` loop variables, an array of tuples is
292 * generated. In thise case, the field `needExpansion` is set to
293 * true to indicate that the static foreach loop expansion will
294 * need to expand the tuples into multiple variables.
296 * For example, `static foreach (x; range) { ... }` is lowered to:
298 * static foreach (x; {
300 * foreach (x; range) return x;
302 * foreach (x; range) __res ~= x;
306 * Finally, call `lowerArrayAggregate` to turn the produced
307 * array into an expression tuple.
310 * sc = The current scope.
313 private void lowerNonArrayAggregate(Scope
* sc
)
315 auto nvars
= aggrfe ? aggrfe
.parameters
.length
: 1;
316 auto aloc
= aggrfe ? aggrfe
.aggr
.loc
: rangefe
.lwr
.loc
;
317 // We need three sets of foreach loop variables because the
318 // lowering contains three foreach loops.
319 Parameters
*[3] pparams
= [new Parameters(), new Parameters(), new Parameters()];
320 foreach (i
; 0 .. nvars
)
322 foreach (params
; pparams
)
324 auto p
= aggrfe ?
(*aggrfe
.parameters
)[i
] : rangefe
.prm
;
325 params
.push(new Parameter(aloc
, p
.storageClass
, p
.type
, p
.ident
, null, null));
329 TypeStruct tplty
= null;
330 if (nvars
== 1) // only one `static foreach` variable, generate identifiers.
334 res
[i
] = new IdentifierExp(aloc
, (*pparams
[i
])[0].ident
);
337 else // multiple `static foreach` variables, generate tuples.
341 auto e
= new Expressions(pparams
[0].length
);
342 foreach (j
, ref elem
; *e
)
344 auto p
= (*pparams
[i
])[j
];
345 elem
= new IdentifierExp(aloc
, p
.ident
);
349 tplty
= createTupleType(aloc
, e
, sc
);
351 res
[i
] = createTuple(aloc
, tplty
, e
);
353 needExpansion
= true; // need to expand the tuples later
355 // generate remaining code for the new aggregate which is an
356 // array (see documentation comment).
360 rangefe
.lwr
= rangefe
.lwr
.expressionSemantic(sc
);
361 rangefe
.lwr
= resolveProperties(sc
, rangefe
.lwr
);
362 rangefe
.upr
= rangefe
.upr
.expressionSemantic(sc
);
363 rangefe
.upr
= resolveProperties(sc
, rangefe
.upr
);
365 rangefe
.lwr
= rangefe
.lwr
.optimize(WANTvalue
);
366 rangefe
.lwr
= rangefe
.lwr
.ctfeInterpret();
367 rangefe
.upr
= rangefe
.upr
.optimize(WANTvalue
);
368 rangefe
.upr
= rangefe
.upr
.ctfeInterpret();
370 auto s1
= new Statements();
371 auto sfe
= new Statements();
372 if (tplty
) sfe
.push(new ExpStatement(loc
, tplty
.sym
));
373 sfe
.push(new ReturnStatement(aloc
, res
[0]));
374 s1
.push(createForeach(aloc
, pparams
[0], new CompoundStatement(aloc
, sfe
)));
375 s1
.push(new ExpStatement(aloc
, new AssertExp(aloc
, IntegerExp
.literal
!0)));
376 Type ety
= new TypeTypeof(aloc
, wrapAndCall(aloc
, new CompoundStatement(aloc
, s1
)));
377 auto aty
= ety
.arrayOf();
378 auto idres
= Identifier
.generateId("__res");
379 auto vard
= new VarDeclaration(aloc
, aty
, idres
, null, STC
.temp
);
380 auto s2
= new Statements();
382 // Run 'typeof' gagged to avoid duplicate errors and if it fails just create
383 // an empty foreach to expose them.
384 uint olderrors
= global
.startGagging();
385 ety
= ety
.typeSemantic(aloc
, sc
);
386 if (global
.endGagging(olderrors
))
387 s2
.push(createForeach(aloc
, pparams
[1], null));
390 s2
.push(new ExpStatement(aloc
, vard
));
391 auto catass
= new CatAssignExp(aloc
, new IdentifierExp(aloc
, idres
), res
[1]);
392 s2
.push(createForeach(aloc
, pparams
[1], new ExpStatement(aloc
, catass
)));
393 s2
.push(new ReturnStatement(aloc
, new IdentifierExp(aloc
, idres
)));
396 Expression aggr
= void;
399 if (rangefe
&& (indexty
= ety
).isintegral())
401 rangefe
.lwr
.type
= indexty
;
402 rangefe
.upr
.type
= indexty
;
403 auto lwrRange
= getIntRange(rangefe
.lwr
);
404 auto uprRange
= getIntRange(rangefe
.upr
);
406 const lwr
= rangefe
.lwr
.toInteger();
407 auto upr
= rangefe
.upr
.toInteger();
410 if (lwrRange
.imin
<= uprRange
.imax
)
411 length
= cast(size_t
) (upr
- lwr
);
413 auto exps
= new Expressions(length
);
415 if (rangefe
.op
== TOK
.foreach_
)
417 foreach (i
; 0 .. length
)
418 (*exps
)[i
] = new IntegerExp(aloc
, lwr
+ i
, indexty
);
423 foreach (i
; 0 .. length
)
424 (*exps
)[i
] = new IntegerExp(aloc
, upr
- i
, indexty
);
426 aggr
= new ArrayLiteralExp(aloc
, indexty
.arrayOf(), exps
);
430 aggr
= wrapAndCall(aloc
, new CompoundStatement(aloc
, s2
));
432 aggr
= aggr
.expressionSemantic(sc
);
433 aggr
= resolveProperties(sc
, aggr
);
435 aggr
= aggr
.optimize(WANTvalue
);
436 aggr
= aggr
.ctfeInterpret();
439 assert(!!aggrfe ^
!!rangefe
);
440 aggrfe
= new ForeachStatement(loc
, TOK
.foreach_
, pparams
[2], aggr
,
441 aggrfe ? aggrfe
._body
: rangefe
._body
,
442 aggrfe ? aggrfe
.endloc
: rangefe
.endloc
);
444 lowerArrayAggregate(sc
); // finally, turn generated array into expression tuple
447 /*****************************************
448 * Perform `static foreach` lowerings that are necessary in order
449 * to finally expand the `static foreach` using
450 * `dmd.statementsem.makeTupleForeach`.
452 extern(D
) void prepare(Scope
* sc
)
459 aggrfe
.aggr
= aggrfe
.aggr
.expressionSemantic(sc
);
463 if (aggrfe
&& aggrfe
.aggr
.type
.toBasetype().ty
== Terror
)
470 if (aggrfe
&& aggrfe
.aggr
.type
.toBasetype().ty
== Tarray
)
472 lowerArrayAggregate(sc
);
476 lowerNonArrayAggregate(sc
);
481 /*****************************************
483 * `true` iff ready to call `dmd.statementsem.makeTupleForeach`.
485 extern(D
) bool ready()
487 return aggrfe
&& aggrfe
.aggr
&& aggrfe
.aggr
.type
&& aggrfe
.aggr
.type
.toBasetype().ty
== Ttuple
;
491 /***********************************************************
493 extern (C
++) class DVCondition
: Condition
499 extern (D
) this(const ref Loc loc
, Module mod
, uint level
, Identifier ident
) @safe
507 override final DVCondition
syntaxCopy()
509 return this; // don't need to copy
512 override void accept(Visitor v
)
518 /***********************************************************
520 extern (C
++) final class DebugCondition
: DVCondition
523 * Add an user-supplied identifier to the list of global debug identifiers
525 * Can be called from either the driver or a `debug = Ident;` statement.
526 * Unlike version identifier, there isn't any reserved debug identifier
527 * so no validation takes place.
530 * ident = identifier to add
532 deprecated("Kept for C++ compat - Use the string overload instead")
533 static void addGlobalIdent(const(char)* ident
)
535 addGlobalIdent(ident
[0 .. ident
.strlen
]);
539 extern(D
) static void addGlobalIdent(string ident
)
541 // Overload necessary for string literals
542 addGlobalIdent(cast(const(char)[])ident
);
547 extern(D
) static void addGlobalIdent(const(char)[] ident
)
549 if (!global
.debugids
)
550 global
.debugids
= new Identifiers();
551 global
.debugids
.push(Identifier
.idPool(ident
));
556 * Instantiate a new `DebugCondition`
559 * mod = Module this node belongs to
560 * level = Minimum global level this condition needs to pass.
561 * Only used if `ident` is `null`.
562 * ident = Identifier required for this condition to pass.
563 * If `null`, this conditiion will use an integer level.
564 * loc = Location in the source file
566 extern (D
) this(const ref Loc loc
, Module mod
, uint level
, Identifier ident
) @safe
568 super(loc
, mod
, level
, ident
);
571 override int include(Scope
* sc
)
573 //printf("DebugCondition::include() level = %d, debuglevel = %d\n", level, global.params.debuglevel);
574 if (inc == Include
.notComputed
)
577 bool definedInModule
= false;
580 if (findCondition(mod
.debugids
, ident
))
583 definedInModule
= true;
585 else if (findCondition(global
.debugids
, ident
))
589 if (!mod
.debugidsNot
)
590 mod
.debugidsNot
= new Identifiers();
591 mod
.debugidsNot
.push(ident
);
594 else if (level
<= global
.params
.debuglevel || level
<= mod
.debuglevel
)
596 if (!definedInModule
)
597 printDepsConditional(sc
, this, "depsDebug ");
599 return (inc == Include
.yes
);
602 override inout(DebugCondition
) isDebugCondition() inout
607 override void accept(Visitor v
)
612 override const(char)* toChars() const
614 return ident ? ident
.toChars() : "debug".ptr
;
619 * Node to represent a version condition
621 * A version condition is of the form:
623 * version (Identifier)
626 * This class also provides means to add version identifier
627 * to the list of global (cross module) identifiers.
629 extern (C
++) final class VersionCondition
: DVCondition
632 * Check if a given version identifier is reserved.
635 * ident = identifier being checked
638 * `true` if it is reserved, `false` otherwise
640 extern(D
) private static bool isReserved(const(char)[] ident
) @safe
642 // This list doesn't include "D_*" versions, see the last return
649 case "Alpha_HardFloat":
650 case "Alpha_SoftFloat":
653 case "ARM_HardFloat":
654 case "ARM_SoftFloat":
662 case "CppRuntime_Clang":
663 case "CppRuntime_DigitalMars":
664 case "CppRuntime_Gcc":
665 case "CppRuntime_Microsoft":
666 case "CppRuntime_Sun":
667 case "CRuntime_Bionic":
668 case "CRuntime_DigitalMars":
669 case "CRuntime_Glibc":
670 case "CRuntime_Microsoft":
671 case "CRuntime_Musl":
672 case "CRuntime_Newlib":
673 case "CRuntime_UClibc":
674 case "CRuntime_WASI":
696 case "LoongArch_HardFloat":
697 case "LoongArch_SoftFloat":
702 case "MIPS_HardFloat":
707 case "MIPS_SoftFloat":
720 case "PPC_HardFloat":
721 case "PPC_SoftFloat":
732 case "SPARC_HardFloat":
733 case "SPARC_SoftFloat":
752 // Anything that starts with "D_" is reserved
753 return (ident
.length
>= 2 && ident
[0 .. 2] == "D_");
758 * Raises an error if a version identifier is reserved.
760 * Called when setting a version identifier, e.g. `-version=identifier`
761 * parameter to the compiler or `version = Foo` in user code.
764 * loc = Where the identifier is set
765 * ident = identifier being checked (ident[$] must be '\0')
767 extern(D
) static void checkReserved(const ref Loc loc
, const(char)[] ident
)
769 if (isReserved(ident
))
770 error(loc
, "version identifier `%s` is reserved and cannot be set",
775 * Add an user-supplied global identifier to the list
777 * Only called from the driver for `-version=Ident` parameters.
778 * Will raise an error if the identifier is reserved.
781 * ident = identifier to add
783 deprecated("Kept for C++ compat - Use the string overload instead")
784 static void addGlobalIdent(const(char)* ident
)
786 addGlobalIdent(ident
[0 .. ident
.strlen
]);
790 extern(D
) static void addGlobalIdent(string ident
)
792 // Overload necessary for string literals
793 addGlobalIdent(cast(const(char)[])ident
);
798 extern(D
) static void addGlobalIdent(const(char)[] ident
)
800 checkReserved(Loc
.initial
, ident
);
801 addPredefinedGlobalIdent(ident
);
805 * Add any global identifier to the list, without checking
808 * Only called from the driver after platform detection,
812 * ident = identifier to add (ident[$] must be '\0')
814 deprecated("Kept for C++ compat - Use the string overload instead")
815 static void addPredefinedGlobalIdent(const(char)* ident
)
817 addPredefinedGlobalIdent(ident
.toDString());
821 extern(D
) static void addPredefinedGlobalIdent(string ident
)
823 // Forward: Overload necessary for string literal
824 addPredefinedGlobalIdent(cast(const(char)[])ident
);
829 extern(D
) static void addPredefinedGlobalIdent(const(char)[] ident
)
831 if (!global
.versionids
)
832 global
.versionids
= new Identifiers();
833 global
.versionids
.push(Identifier
.idPool(ident
));
837 * Instantiate a new `VersionCondition`
840 * mod = Module this node belongs to
841 * level = Minimum global level this condition needs to pass.
842 * Only used if `ident` is `null`.
843 * ident = Identifier required for this condition to pass.
844 * If `null`, this conditiion will use an integer level.
845 * loc = Location in the source file
847 extern (D
) this(const ref Loc loc
, Module mod
, uint level
, Identifier ident
) @safe
849 super(loc
, mod
, level
, ident
);
852 override int include(Scope
* sc
)
854 //printf("VersionCondition::include() level = %d, versionlevel = %d\n", level, global.params.versionlevel);
855 //if (ident) printf("\tident = '%s'\n", ident.toChars());
856 if (inc == Include
.notComputed
)
859 bool definedInModule
= false;
862 if (findCondition(mod
.versionids
, ident
))
865 definedInModule
= true;
867 else if (findCondition(global
.versionids
, ident
))
871 if (!mod
.versionidsNot
)
872 mod
.versionidsNot
= new Identifiers();
873 mod
.versionidsNot
.push(ident
);
876 else if (level
<= global
.params
.versionlevel || level
<= mod
.versionlevel
)
878 if (!definedInModule
&&
879 (!ident ||
(!isReserved(ident
.toString()) && ident
!= Id
._unittest
&& ident
!= Id
._assert
)))
881 printDepsConditional(sc
, this, "depsVersion ");
884 return (inc == Include
.yes
);
887 override inout(VersionCondition
) isVersionCondition() inout
892 override void accept(Visitor v
)
897 override const(char)* toChars() const
899 return ident ? ident
.toChars() : "version".ptr
;
903 /***********************************************************
905 extern (C
++) final class StaticIfCondition
: Condition
909 extern (D
) this(const ref Loc loc
, Expression exp
) @safe
915 override StaticIfCondition
syntaxCopy()
917 return new StaticIfCondition(loc
, exp
.syntaxCopy());
920 override int include(Scope
* sc
)
922 // printf("StaticIfCondition::include(sc = %p) this=%p inc = %d\n", sc, this, inc);
927 inc = Include
.no
; // so we don't see the error message again
931 if (inc == Include
.notComputed
)
935 error(loc
, "`static if` conditional cannot be at global scope");
940 import dmd
.staticcond
;
943 bool result
= evalStaticCondition(sc
, exp
, exp
, errors
);
945 // Prevent repeated condition evaluation.
946 // See: fail_compilation/fail7815.d
947 if (inc != Include
.notComputed
)
948 return (inc == Include
.yes
);
950 return errorReturn();
956 return (inc == Include
.yes
);
959 override void accept(Visitor v
)
964 override inout(StaticIfCondition
) isStaticIfCondition() inout
969 override const(char)* toChars() const
971 return exp ? exp
.toChars() : "static if".ptr
;
976 /****************************************
977 * Find `ident` in an array of identifiers.
979 * ids = array of identifiers
980 * ident = identifier to search for
984 bool findCondition(Identifiers
* ids
, Identifier ident
) @safe nothrow pure
997 // Helper for printing dependency information
998 private void printDepsConditional(Scope
* sc
, DVCondition condition
, const(char)[] depType
)
1000 if (!global
.params
.moduleDeps
.buffer || global
.params
.moduleDeps
.name
)
1002 OutBuffer
* ob
= global
.params
.moduleDeps
.buffer
;
1003 Module imod
= sc ? sc
._module
: condition
.mod
;
1006 ob
.writestring(depType
);
1007 ob
.writestring(imod
.toPrettyChars());
1008 ob
.writestring(" (");
1009 escapePath(ob
, imod
.srcfile
.toChars());
1010 ob
.writestring(") : ");
1011 if (condition
.ident
)
1012 ob
.writestring(condition
.ident
.toString());
1014 ob
.print(condition
.level
);