/** * Performs the semantic2 stage, which deals with initializer expressions. * * Copyright: Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/semantic2.d, _semantic2.d) * Documentation: https://dlang.org/phobos/dmd_semantic2.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/semantic2.d */ module dmd.semantic2; import core.stdc.stdio; import core.stdc.string; import dmd.aggregate; import dmd.aliasthis; import dmd.arraytypes; import dmd.astcodegen; import dmd.astenums; import dmd.attrib; import dmd.blockexit; import dmd.clone; import dmd.dcast; import dmd.dclass; import dmd.declaration; import dmd.denum; import dmd.dimport; import dmd.dinterpret; import dmd.dmodule; import dmd.dscope; import dmd.dstruct; import dmd.dsymbol; import dmd.dsymbolsem; import dmd.dtemplate; import dmd.dversion; import dmd.errors; import dmd.escape; import dmd.expression; import dmd.expressionsem; import dmd.func; import dmd.globals; import dmd.id; import dmd.identifier; import dmd.init; import dmd.initsem; import dmd.hdrgen; import dmd.mtype; import dmd.nogc; import dmd.nspace; import dmd.objc; import dmd.opover; import dmd.parse; import dmd.root.filename; import dmd.common.outbuffer; import dmd.root.rmem; import dmd.rootobject; import dmd.root.utf; import dmd.sideeffect; import dmd.statementsem; import dmd.staticassert; import dmd.tokens; import dmd.statement; import dmd.target; import dmd.templateparamsem; import dmd.typesem; import dmd.visitor; enum LOG = false; /************************************* * Does semantic analysis on initializers and members of aggregates. */ void semantic2(Dsymbol dsym, Scope* sc) { scope v = new Semantic2Visitor(sc); dsym.accept(v); } private extern(C++) final class Semantic2Visitor : Visitor { alias visit = Visitor.visit; Scope* sc; this(Scope* sc) scope @safe { this.sc = sc; } override void visit(Dsymbol) {} override void visit(StaticAssert sa) { //printf("StaticAssert::semantic2() %s\n", sa.toChars()); if (const e = sa.exp.isStringExp()) { // deprecated in 2.107 deprecation(e.loc, "static assert condition cannot be a string literal"); deprecationSupplemental(e.loc, "If intentional, use `%s !is null` instead to preserve behaviour", e.toChars()); } auto sds = new ScopeDsymbol(); sc = sc.push(sds); sc.tinst = null; sc.minst = null; import dmd.staticcond; bool errors; bool result = evalStaticCondition(sc, sa.exp, sa.exp, errors); sc = sc.pop(); if (errors) { errorSupplemental(sa.loc, "while evaluating: `static assert(%s)`", sa.exp.toChars()); return; } else if (result) return; if (sa.msgs) { OutBuffer msgbuf; for (size_t i = 0; i < sa.msgs.length; i++) { Expression e = (*sa.msgs)[i]; sc = sc.startCTFE(); e = e.expressionSemantic(sc); e = resolveProperties(sc, e); sc = sc.endCTFE(); e = ctfeInterpretForPragmaMsg(e); if (e.op == EXP.error) { errorSupplemental(sa.loc, "while evaluating `static assert` argument `%s`", (*sa.msgs)[i].toChars()); return; } StringExp se = e.toStringExp(); if (se) { const slice = se.toUTF8(sc).peekString(); // Hack to keep old formatting to avoid changing error messages everywhere if (sa.msgs.length == 1) msgbuf.printf("\"%.*s\"", cast(int)slice.length, slice.ptr); else msgbuf.printf("%.*s", cast(int)slice.length, slice.ptr); } else msgbuf.printf("%s", e.toChars()); } error(sa.loc, "static assert: %s", msgbuf.extractChars()); } else error(sa.loc, "static assert: `%s` is false", sa.exp.toChars()); if (sc.tinst) sc.tinst.printInstantiationTrace(); if (!global.gag) fatal(); } override void visit(TemplateInstance tempinst) { if (tempinst.semanticRun >= PASS.semantic2) return; tempinst.semanticRun = PASS.semantic2; static if (LOG) { printf("+TemplateInstance.semantic2('%s')\n", tempinst.toChars()); scope(exit) printf("-TemplateInstance.semantic2('%s')\n", tempinst.toChars()); } if (tempinst.errors || !tempinst.members) return; TemplateDeclaration tempdecl = tempinst.tempdecl.isTemplateDeclaration(); assert(tempdecl); sc = tempdecl._scope; assert(sc); sc = sc.push(tempinst.argsym); sc = sc.push(tempinst); sc.tinst = tempinst; sc.minst = tempinst.minst; int needGagging = (tempinst.gagged && !global.gag); uint olderrors = global.errors; int oldGaggedErrors = -1; // dead-store to prevent spurious warning if (needGagging) oldGaggedErrors = global.startGagging(); for (size_t i = 0; i < tempinst.members.length; i++) { Dsymbol s = (*tempinst.members)[i]; static if (LOG) { printf("\tmember '%s', kind = '%s'\n", s.toChars(), s.kind()); } s.semantic2(sc); if (tempinst.gagged && global.errors != olderrors) break; } if (global.errors != olderrors) { if (!tempinst.errors) { if (!tempdecl.literal) .error(tempinst.loc, "%s `%s` error instantiating", tempinst.kind, tempinst.toPrettyChars); if (tempinst.tinst) tempinst.tinst.printInstantiationTrace(); } tempinst.errors = true; } if (needGagging) global.endGagging(oldGaggedErrors); sc = sc.pop(); sc.pop(); } override void visit(TemplateMixin tmix) { if (tmix.semanticRun >= PASS.semantic2) return; tmix.semanticRun = PASS.semantic2; static if (LOG) { printf("+TemplateMixin.semantic2('%s')\n", tmix.toChars()); scope(exit) printf("-TemplateMixin.semantic2('%s')\n", tmix.toChars()); } if (!tmix.members) return; assert(sc); sc = sc.push(tmix.argsym); sc = sc.push(tmix); sc.tinst = tmix; sc.minst = tmix.minst; for (size_t i = 0; i < tmix.members.length; i++) { Dsymbol s = (*tmix.members)[i]; static if (LOG) { printf("\tmember '%s', kind = '%s'\n", s.toChars(), s.kind()); } s.semantic2(sc); } sc = sc.pop(); sc.pop(); } override void visit(VarDeclaration vd) { if (vd.semanticRun < PASS.semanticdone && vd.inuse) return; //printf("VarDeclaration::semantic2('%s')\n", toChars()); sc = sc.push(); sc.varDecl = vd; scope(exit) sc = sc.pop(); if (vd.aliasTuple) // if it's a tuple { vd.aliasTuple.accept(this); vd.semanticRun = PASS.semantic2done; return; } UserAttributeDeclaration.checkGNUABITag(vd, vd._linkage); if (vd._init && !vd.toParent().isFuncDeclaration()) { vd.inuse++; /* https://issues.dlang.org/show_bug.cgi?id=20280 * * Template instances may import modules that have not * finished semantic1. */ if (!vd.type) vd.dsymbolSemantic(sc); // https://issues.dlang.org/show_bug.cgi?id=14166 // https://issues.dlang.org/show_bug.cgi?id=20417 // Don't run CTFE for the temporary variables inside typeof or __traits(compiles) vd._init = vd._init.initializerSemantic(sc, vd.type, sc.intypeof == 1 || sc.flags & SCOPE.compile ? INITnointerpret : INITinterpret); lowerStaticAAs(vd, sc); vd.inuse--; } if (vd._init && vd.storage_class & STC.manifest) { /* Cannot initializer enums with CTFE classreferences and addresses of struct literals. * Scan initializer looking for them. Issue error if found. */ if (ExpInitializer ei = vd._init.isExpInitializer()) { static bool hasInvalidEnumInitializer(Expression e) { static bool arrayHasInvalidEnumInitializer(Expressions* elems) { foreach (e; *elems) { if (e && hasInvalidEnumInitializer(e)) return true; } return false; } if (e.op == EXP.classReference) return true; if (e.op == EXP.address && (cast(AddrExp)e).e1.op == EXP.structLiteral) return true; if (e.op == EXP.arrayLiteral) return arrayHasInvalidEnumInitializer((cast(ArrayLiteralExp)e).elements); if (e.op == EXP.structLiteral) return arrayHasInvalidEnumInitializer((cast(StructLiteralExp)e).elements); if (e.op == EXP.assocArrayLiteral) { AssocArrayLiteralExp ae = cast(AssocArrayLiteralExp)e; return arrayHasInvalidEnumInitializer(ae.values) || arrayHasInvalidEnumInitializer(ae.keys); } return false; } if (hasInvalidEnumInitializer(ei.exp)) .error(vd.loc, "%s `%s` : Unable to initialize enum with class or pointer to struct. Use static const variable instead.", vd.kind, vd.toPrettyChars); } } else if (vd._init && vd.isThreadlocal()) { // Cannot initialize a thread-local class or pointer to struct variable with a literal // that itself is a thread-local reference and would need dynamic initialization also. if ((vd.type.ty == Tclass) && vd.type.isMutable() && !vd.type.isShared()) { ExpInitializer ei = vd._init.isExpInitializer(); if (ei && ei.exp.op == EXP.classReference) .error(vd.loc, "%s `%s` is a thread-local class and cannot have a static initializer. Use `static this()` to initialize instead.", vd.kind, vd.toPrettyChars); } else if (vd.type.ty == Tpointer && vd.type.nextOf().ty == Tstruct && vd.type.nextOf().isMutable() && !vd.type.nextOf().isShared()) { ExpInitializer ei = vd._init.isExpInitializer(); if (ei && ei.exp.op == EXP.address && (cast(AddrExp)ei.exp).e1.op == EXP.structLiteral) .error(vd.loc, "%s `%s` is a thread-local pointer to struct and cannot have a static initializer. Use `static this()` to initialize instead.", vd.kind, vd.toPrettyChars); } } vd.semanticRun = PASS.semantic2done; } override void visit(Module mod) { //printf("Module::semantic2('%s'): parent = %p\n", toChars(), parent); if (mod.semanticRun != PASS.semanticdone) // semantic() not completed yet - could be recursive call return; mod.semanticRun = PASS.semantic2; // Note that modules get their own scope, from scratch. // This is so regardless of where in the syntax a module // gets imported, it is unaffected by context. Scope* sc = Scope.createGlobal(mod, global.errorSink); // create root scope //printf("Module = %p\n", sc.scopesym); if (mod.members) { // Pass 2 semantic routines: do initializers and function bodies for (size_t i = 0; i < mod.members.length; i++) { Dsymbol s = (*mod.members)[i]; s.semantic2(sc); } } if (mod.userAttribDecl) { mod.userAttribDecl.semantic2(sc); } sc = sc.pop(); sc.pop(); mod.semanticRun = PASS.semantic2done; //printf("-Module::semantic2('%s'): parent = %p\n", toChars(), parent); } override void visit(FuncDeclaration fd) { if (fd.semanticRun >= PASS.semantic2done) return; if (fd.semanticRun < PASS.semanticdone && !fd.errors) { /* https://issues.dlang.org/show_bug.cgi?id=21614 * * Template instances may import modules that have not * finished semantic1. */ fd.dsymbolSemantic(sc); } assert(fd.semanticRun <= PASS.semantic2); fd.semanticRun = PASS.semantic2; //printf("FuncDeclaration::semantic2 [%s] fd: %s type: %s\n", fd.loc.toChars(), fd.toChars(), fd.type ? fd.type.toChars() : "".ptr); // Only check valid functions which have a body to avoid errors // for multiple declarations, e.g. // void foo(); // void foo(); if (fd.fbody && fd.overnext && !fd.errors) { // Always starts the lookup from 'this', because the conflicts with // previous overloads are already reported. alias f1 = fd; auto tf1 = cast(TypeFunction) f1.type; auto parent1 = f1.toParent2(); const linkage1 = f1.resolvedLinkage(); overloadApply(f1, (Dsymbol s) { auto f2 = s.isFuncDeclaration(); if (!f2 || f1 == f2 || f2.errors) return 0; // Don't have to check conflict between declaration and definition. if (f2.fbody is null) return 0; // Functions with different manglings can never conflict if (linkage1 != f2.resolvedLinkage()) return 0; // Functions with different names never conflict // (they can form overloads sets introduced by an alias) if (f1.ident != f2.ident) return 0; // Functions with different parents never conflict // (E.g. when aliasing a free function into a struct) if (parent1 != f2.toParent2()) return 0; /* Check for overload merging with base class member functions. * * class B { void foo() {} } * class D : B { * override void foo() {} // B.foo appears as f2 * alias foo = B.foo; * } */ if (f1.overrides(f2)) return 0; auto tf2 = cast(TypeFunction) f2.type; // Overloading based on storage classes if (tf1.mod != tf2.mod || ((f1.storage_class ^ f2.storage_class) & STC.static_)) return 0; // @@@DEPRECATED_2.112@@@ // This test doesn't catch identical functions that differ only // in explicit/implicit `@system` - a deprecation has now been // added below, remove `false` after deprecation period is over. const sameAttr = tf1.attributesEqual(tf2, false); const sameParams = tf1.parameterList == tf2.parameterList; // Allow the hack to declare overloads with different parameters/STC's if (parent1.isModule() && linkage1 != LINK.d && linkage1 != LINK.cpp && (!sameAttr || !sameParams) ) { .error(f2.loc, "%s `%s` cannot overload `extern(%s)` function at %s", f2.kind, f2.toPrettyChars, linkageToChars(f1._linkage), f1.loc.toChars()); return 0; } // Different parameters don't conflict in extern(C++/D) if (!sameParams) return 0; // Different attributes don't conflict in extern(D) if (!sameAttr && linkage1 == LINK.d) { // @@@DEPRECATED_2.112@@@ // Same as 2.104 deprecation, but also catching explicit/implicit `@system` // At the end of deprecation period, fix Type.attributesEqual and remove // this condition, as well as the error for extern(C) functions above. if (sameAttr != tf1.attributesEqual(tf2)) { .deprecation(f2.loc, "%s `%s` cannot overload `extern(%s)` function at %s", f2.kind, f2.toPrettyChars, linkageToChars(f1._linkage), f1.loc.toChars()); } return 0; } .error(f2.loc, "%s `%s%s` conflicts with previous declaration at %s", f2.kind(), f2.toPrettyChars(), parametersTypeToChars(tf2.parameterList), f1.loc.toChars()); f2.type = Type.terror; f2.errors = true; return 0; }); } if (!fd.type || fd.type.ty != Tfunction) return; TypeFunction f = cast(TypeFunction) fd.type; UserAttributeDeclaration.checkGNUABITag(fd, fd._linkage); //semantic for parameters' UDAs foreach (i, param; f.parameterList) { if (param && param.userAttribDecl) param.userAttribDecl.semantic2(sc); } } override void visit(Import i) { //printf("Import::semantic2('%s')\n", toChars()); if (!i.mod) return; i.mod.semantic2(null); if (i.mod.needmoduleinfo) { //printf("module5 %s because of %s\n", sc._module.toChars(), mod.toChars()); if (sc) sc._module.needmoduleinfo = 1; } } override void visit(Nspace ns) { if (ns.semanticRun >= PASS.semantic2) return; ns.semanticRun = PASS.semantic2; static if (LOG) { printf("+Nspace::semantic2('%s')\n", ns.toChars()); scope(exit) printf("-Nspace::semantic2('%s')\n", ns.toChars()); } UserAttributeDeclaration.checkGNUABITag(ns, LINK.cpp); if (!ns.members) return; assert(sc); sc = sc.push(ns); sc.linkage = LINK.cpp; foreach (s; *ns.members) { static if (LOG) { printf("\tmember '%s', kind = '%s'\n", s.toChars(), s.kind()); } s.semantic2(sc); } sc.pop(); } override void visit(AttribDeclaration ad) { Dsymbols* d = ad.include(sc); if (!d) return; Scope* sc2 = ad.newScope(sc); for (size_t i = 0; i < d.length; i++) { Dsymbol s = (*d)[i]; s.semantic2(sc2); } if (sc2 != sc) sc2.pop(); } /** * Run the DeprecatedDeclaration's semantic2 phase then its members. * * The message set via a `DeprecatedDeclaration` can be either of: * - a string literal * - an enum * - a static immutable * So we need to call ctfe to resolve it. * Afterward forwards to the members' semantic2. */ override void visit(DeprecatedDeclaration dd) { getMessage(dd); visit(cast(AttribDeclaration)dd); } override void visit(AlignDeclaration ad) { ad.getAlignment(sc); visit(cast(AttribDeclaration)ad); } override void visit(CPPNamespaceDeclaration decl) { UserAttributeDeclaration.checkGNUABITag(decl, LINK.cpp); visit(cast(AttribDeclaration)decl); } override void visit(UserAttributeDeclaration uad) { if (!uad.decl || !uad.atts || !uad.atts.length || !uad._scope) return visit(cast(AttribDeclaration)uad); Expression* lastTag; static void eval(Scope* sc, Expressions* exps, ref Expression* lastTag) { foreach (ref Expression e; *exps) { if (!e) continue; e = e.expressionSemantic(sc); if (definitelyValueParameter(e)) e = e.ctfeInterpret(); if (e.op == EXP.tuple) { TupleExp te = cast(TupleExp)e; eval(sc, te.exps, lastTag); } // Handles compiler-recognized `core.attribute.gnuAbiTag` if (UserAttributeDeclaration.isGNUABITag(e)) doGNUABITagSemantic(e, lastTag); } } uad._scope = null; eval(sc, uad.atts, lastTag); visit(cast(AttribDeclaration)uad); } override void visit(AggregateDeclaration ad) { //printf("AggregateDeclaration::semantic2(%s) type = %s, errors = %d\n", ad.toChars(), ad.type.toChars(), ad.errors); if (!ad.members) return; if (ad._scope) { .error(ad.loc, "%s `%s` has forward references", ad.kind, ad.toPrettyChars); return; } UserAttributeDeclaration.checkGNUABITag( ad, ad.classKind == ClassKind.cpp ? LINK.cpp : LINK.d); auto sc2 = ad.newScope(sc); ad.determineSize(ad.loc); for (size_t i = 0; i < ad.members.length; i++) { Dsymbol s = (*ad.members)[i]; //printf("\t[%d] %s\n", i, s.toChars()); s.semantic2(sc2); } sc2.pop(); } override void visit(ClassDeclaration cd) { /// Checks that the given class implements all methods of its interfaces. static void checkInterfaceImplementations(ClassDeclaration cd) { foreach (base; cd.interfaces) { // https://issues.dlang.org/show_bug.cgi?id=22729 // interfaces that have errors or that // inherit from interfaces that have errors // might have an uninitialized vtable if (!base.sym.vtbl.length) continue; // first entry is ClassInfo reference auto methods = base.sym.vtbl[base.sym.vtblOffset .. $]; foreach (m; methods) { auto ifd = m.isFuncDeclaration; assert(ifd); if (ifd.objc.isOptional) continue; auto type = ifd.type.toTypeFunction(); auto fd = cd.findFunc(ifd.ident, type); if (fd && !fd.isAbstract) { //printf(" found\n"); // Check that calling conventions match if (fd._linkage != ifd._linkage) .error(fd.loc, "%s `%s` linkage doesn't match interface function", fd.kind, fd.toPrettyChars); // Check that it is current //printf("newinstance = %d fd.toParent() = %s ifd.toParent() = %s\n", //newinstance, fd.toParent().toChars(), ifd.toParent().toChars()); if (fd.toParent() != cd && ifd.toParent() == base.sym) .error(cd.loc, "%s `%s` interface function `%s` is not implemented", cd.kind, cd.toPrettyChars, ifd.toFullSignature()); } else { //printf(" not found %p\n", fd); // BUG: should mark this class as abstract? if (!cd.isAbstract()) .error(cd.loc, "%s `%s` interface function `%s` is not implemented", cd.kind, cd.toPrettyChars, ifd.toFullSignature()); } } } } if (cd.semanticRun >= PASS.semantic2done) return; assert(cd.semanticRun <= PASS.semantic2); cd.semanticRun = PASS.semantic2; checkInterfaceImplementations(cd); visit(cast(AggregateDeclaration) cd); } override void visit(InterfaceDeclaration cd) { visit(cast(AggregateDeclaration) cd); } override void visit(TupleDeclaration td) { td.foreachVar((s) { s.accept(this); }); } } /** * Perform semantic analysis specific to the GNU ABI tags * * The GNU ABI tags are a feature introduced in C++11, specific to g++ * and the Itanium ABI. * They are mandatory for C++ interfacing, simply because the templated struct *`std::basic_string`, of which the ubiquitous `std::string` is a instantiation * of, uses them. * * Params: * e = Expression to perform semantic on * See `Semantic2Visitor.visit(UserAttributeDeclaration)` * lastTag = When `!is null`, we already saw an ABI tag. * To simplify implementation and reflection code, * only one ABI tag object is allowed per symbol * (but it can have multiple tags as it's an array exp). */ private void doGNUABITagSemantic(ref Expression e, ref Expression* lastTag) { import dmd.dmangle; // When `@gnuAbiTag` is used, the type will be the UDA, not the struct literal if (e.op == EXP.type) { error(e.loc, "`@%s` at least one argument expected", Id.udaGNUAbiTag.toChars()); return; } // Definition is in `core.attributes`. If it's not a struct literal, // it shouldn't have passed semantic, hence the `assert`. auto sle = e.isStructLiteralExp(); if (sle is null) { assert(global.errors); return; } // The definition of `gnuAttributes` only have 1 member, `string[] tags` assert(sle.elements && sle.elements.length == 1); // `gnuAbiTag`'s constructor is defined as `this(string[] tags...)` auto ale = (*sle.elements)[0].isArrayLiteralExp(); if (ale is null) { error(e.loc, "`@%s` at least one argument expected", Id.udaGNUAbiTag.toChars()); return; } // Check that it's the only tag on the symbol if (lastTag !is null) { const str1 = (*lastTag.isStructLiteralExp().elements)[0].toString(); const str2 = ale.toString(); error(e.loc, "only one `@%s` allowed per symbol", Id.udaGNUAbiTag.toChars()); errorSupplemental(e.loc, "instead of `@%s @%s`, use `@%s(%.*s, %.*s)`", lastTag.toChars(), e.toChars(), Id.udaGNUAbiTag.toChars(), // Avoid [ ... ] cast(int)str1.length - 2, str1.ptr + 1, cast(int)str2.length - 2, str2.ptr + 1); return; } lastTag = &e; // We already know we have a valid array literal of strings. // Now checks that elements are valid. foreach (idx, elem; *ale.elements) { const str = elem.toStringExp().peekString(); if (!str.length) { error(e.loc, "argument `%d` to `@%s` cannot be %s", cast(int)(idx + 1), Id.udaGNUAbiTag.toChars(), elem.isNullExp() ? "`null`".ptr : "empty".ptr); continue; } foreach (c; str) { if (!c.isValidMangling()) { error(e.loc, "`@%s` char `0x%02x` not allowed in mangling", Id.udaGNUAbiTag.toChars(), c); break; } } // Valid element } // Since ABI tags need to be sorted, we sort them in place // It might be surprising for users that inspects the UDAs, // but it's a concession to practicality. // Casts are unfortunately necessary as `implicitConvTo` is not // `const` (and nor is `StringExp`, by extension). static int predicate(const scope Expression* e1, const scope Expression* e2) { return (cast(Expression*)e1).toStringExp().compare((cast(Expression*)e2).toStringExp()); } ale.elements.sort!predicate; } /** * Try lower a variable's Associative Array initializer to a newaa struct * so it can be put in static data. * Params: * vd = Variable to lower * sc = Scope */ void lowerStaticAAs(VarDeclaration vd, Scope* sc) { if (vd.storage_class & STC.manifest) return; if (auto ei = vd._init.isExpInitializer()) lowerStaticAAs(ei.exp, sc); } /** * Try lower all Associative Array literals in an expression to a newaa struct * so it can be put in static data. * Params: * e = Expression to traverse * sc = Scope */ void lowerStaticAAs(Expression e, Scope* sc) { scope v = new StaticAAVisitor(sc); e.accept(v); } /// Visit Associative Array literals and lower them to structs for static initialization private extern(C++) final class StaticAAVisitor : SemanticTimeTransitiveVisitor { alias visit = SemanticTimeTransitiveVisitor.visit; Scope* sc; this(Scope* sc) scope @safe { this.sc = sc; } override void visit(AssocArrayLiteralExp aaExp) { if (!verifyHookExist(aaExp.loc, *sc, Id._aaAsStruct, "initializing static associative arrays", Id.object)) return; Expression hookFunc = new IdentifierExp(aaExp.loc, Id.empty); hookFunc = new DotIdExp(aaExp.loc, hookFunc, Id.object); hookFunc = new DotIdExp(aaExp.loc, hookFunc, Id._aaAsStruct); auto arguments = new Expressions(); arguments.push(aaExp); Expression loweredExp = new CallExp(aaExp.loc, hookFunc, arguments); sc = sc.startCTFE(); loweredExp = loweredExp.expressionSemantic(sc); loweredExp = resolveProperties(sc, loweredExp); sc = sc.endCTFE(); loweredExp = loweredExp.ctfeInterpret(); aaExp.lowering = loweredExp; } }