/** * This module contains the implementation of the C++ header generation available through * the command line switch -Hc. * * Copyright: Copyright (C) 1999-2022 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/dtohd, _dtoh.d) * Documentation: https://dlang.org/phobos/dmd_dtoh.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dtoh.d */ module dmd.dtoh; import core.stdc.stdio; import core.stdc.string; import core.stdc.ctype; import dmd.astcodegen; import dmd.astenums; import dmd.arraytypes; import dmd.attrib; import dmd.dsymbol; import dmd.errors; import dmd.globals; import dmd.hdrgen; import dmd.identifier; import dmd.root.filename; import dmd.visitor; import dmd.tokens; import dmd.common.outbuffer; import dmd.utils; //debug = Debug_DtoH; // Generate asserts to validate the header //debug = Debug_DtoH_Checks; /** * Generates a C++ header containing bindings for all `extern(C[++])` declarations * found in the supplied modules. * * Params: * ms = the modules * * Notes: * - the header is written to `/` * or `stdout` if no explicit file was specified * - bindings conform to the C++ standard defined in `global.params.cplusplus` * - ignored declarations are mentioned in a comment if `global.params.doCxxHdrGeneration` * is set to `CxxHeaderMode.verbose` */ extern(C++) void genCppHdrFiles(ref Modules ms) { initialize(); OutBuffer fwd; OutBuffer done; OutBuffer decl; // enable indent by spaces on buffers fwd.doindent = true; fwd.spaces = true; decl.doindent = true; decl.spaces = true; scope v = new ToCppBuffer(&fwd, &done, &decl); // Conditionally include another buffer for sanity checks debug (Debug_DtoH_Checks) { OutBuffer check; check.doindent = true; check.spaces = true; v.checkbuf = ✓ } OutBuffer buf; buf.doindent = true; buf.spaces = true; foreach (m; ms) m.accept(v); if (global.params.doCxxHdrGeneration == CxxHeaderMode.verbose) buf.printf("// Automatically generated by %s Compiler v%d", global.vendor.ptr, global.versionNumber()); else buf.printf("// Automatically generated by %s Compiler", global.vendor.ptr); buf.writenl(); buf.writenl(); buf.writestringln("#pragma once"); buf.writenl(); hashInclude(buf, ""); hashInclude(buf, ""); hashInclude(buf, ""); hashInclude(buf, ""); // buf.writestring(buf, "#include \n"); // buf.writestring("#include \n"); // Emit array compatibility because extern(C++) types may have slices // as members (as opposed to function parameters) buf.writestring(` #ifdef CUSTOM_D_ARRAY_TYPE #define _d_dynamicArray CUSTOM_D_ARRAY_TYPE #else /// Represents a D [] array template struct _d_dynamicArray final { size_t length; T *ptr; _d_dynamicArray() : length(0), ptr(NULL) { } _d_dynamicArray(size_t length_in, T *ptr_in) : length(length_in), ptr(ptr_in) { } T& operator[](const size_t idx) { assert(idx < length); return ptr[idx]; } const T& operator[](const size_t idx) const { assert(idx < length); return ptr[idx]; } }; #endif `); if (v.hasReal) { hashIf(buf, "!defined(_d_real)"); { hashDefine(buf, "_d_real long double"); } hashEndIf(buf); } buf.writenl(); // buf.writestringln("// fwd:"); buf.write(&fwd); if (fwd.length > 0) buf.writenl(); // buf.writestringln("// done:"); buf.write(&done); // buf.writestringln("// decl:"); buf.write(&decl); debug (Debug_DtoH_Checks) { // buf.writestringln("// check:"); buf.writestring(` #if OFFSETS template size_t getSlotNumber(int dummy, ...) { T c; va_list ap; va_start(ap, dummy); void *f = va_arg(ap, void*); for (size_t i = 0; ; i++) { if ( (*(void***)&c)[i] == f) return i; } va_end(ap); } void testOffsets() { `); buf.write(&check); buf.writestring(` } #endif `); } // prevent trailing newlines version (Windows) while (buf.length >= 4 && buf[$-4..$] == "\r\n\r\n") buf.remove(buf.length - 2, 2); else while (buf.length >= 2 && buf[$-2..$] == "\n\n") buf.remove(buf.length - 1, 1); if (global.params.cxxhdrname is null) { // Write to stdout; assume it succeeds size_t n = fwrite(buf[].ptr, 1, buf.length, stdout); assert(n == buf.length); // keep gcc happy about return values } else { const(char)[] name = FileName.combine(global.params.cxxhdrdir, global.params.cxxhdrname); writeFile(Loc.initial, name, buf[]); } } private: /**************************************************** * Visitor that writes bindings for `extern(C[++]` declarations. */ extern(C++) final class ToCppBuffer : Visitor { alias visit = Visitor.visit; public: enum EnumKind { Int, Numeric, String, Enum, Other } /// Namespace providing the actual AST nodes alias AST = ASTCodegen; /// Visited nodes bool[void*] visited; /// Forward declared nodes (which might not be emitted yet) bool[void*] forwarded; /// Buffer for forward declarations OutBuffer* fwdbuf; /// Buffer for integrity checks debug (Debug_DtoH_Checks) OutBuffer* checkbuf; /// Buffer for declarations that must emitted before the currently /// visited node but can't be forward declared (see `includeSymbol`) OutBuffer* donebuf; /// Default buffer for the currently visited declaration OutBuffer* buf; /// The generated header uses `real` emitted as `_d_real`? bool hasReal; /// The generated header should contain comments for skipped declarations? const bool printIgnored; /// State specific to the current context which depends /// on the currently visited node and it's parents static struct Context { /// Default linkage in the current scope (e.g. LINK.c inside `extern(C) { ... }`) LINK linkage = LINK.d; /// Enclosing class / struct / union AST.AggregateDeclaration adparent; /// Enclosing template declaration AST.TemplateDeclaration tdparent; /// Identifier of the currently visited `VarDeclaration` /// (required to write variables of funtion pointers) Identifier ident; /// Original type of the currently visited declaration AST.Type origType; /// Last written visibility level applying to the current scope AST.Visibility.Kind currentVisibility; /// Currently applicable storage classes AST.STC storageClass; /// How many symbols were ignored int ignoredCounter; /// Currently visited types are required by another declaration /// and hence must be emitted bool mustEmit; /// Processing a type that can be forward referenced bool forwarding; /// Inside of an anonymous struct/union (AnonDeclaration) bool inAnonymousDecl; } /// Informations about the current context in the AST Context context; alias context this; this(OutBuffer* fwdbuf, OutBuffer* donebuf, OutBuffer* buf) { this.fwdbuf = fwdbuf; this.donebuf = donebuf; this.buf = buf; this.printIgnored = global.params.doCxxHdrGeneration == CxxHeaderMode.verbose; } /** * Emits `dsym` into `donebuf` s.t. it is declared before the currently * visited symbol that written to `buf`. * * Temporarily clears `context` to behave as if it was visited normally. */ private void includeSymbol(AST.Dsymbol dsym) { debug (Debug_DtoH) { printf("[includeSymbol(AST.Dsymbol) enter] %s\n", dsym.toChars()); scope(exit) printf("[includeSymbol(AST.Dsymbol) exit] %s\n", dsym.toChars()); } auto ptr = cast(void*) dsym in visited; if (ptr && *ptr) return; // Temporary replacement for `buf` which is appended to `donebuf` OutBuffer decl; decl.doindent = true; decl.spaces = true; scope (exit) donebuf.write(&decl); auto ctxStash = this.context; auto bufStash = this.buf; this.context = Context.init; this.buf = &decl; this.mustEmit = true; dsym.accept(this); this.context = ctxStash; this.buf = bufStash; } /// Determines what kind of enum `type` is (see `EnumKind`) private EnumKind getEnumKind(AST.Type type) { if (type) switch (type.ty) { case AST.Tint32: return EnumKind.Int; case AST.Tbool, AST.Tchar, AST.Twchar, AST.Tdchar, AST.Tint8, AST.Tuns8, AST.Tint16, AST.Tuns16, AST.Tuns32, AST.Tint64, AST.Tuns64: return EnumKind.Numeric; case AST.Tarray: if (type.isString()) return EnumKind.String; break; case AST.Tenum: return EnumKind.Enum; default: break; } return EnumKind.Other; } /// Determines the type used to represent `type` in C++. /// Returns: `const [w,d]char*` for `[w,d]string` or `type` private AST.Type determineEnumType(AST.Type type) { if (auto arr = type.isTypeDArray()) { switch (arr.next.ty) { case AST.Tchar: return AST.Type.tchar.constOf.pointerTo; case AST.Twchar: return AST.Type.twchar.constOf.pointerTo; case AST.Tdchar: return AST.Type.tdchar.constOf.pointerTo; default: break; } } return type; } /// Writes a final `;` and insert an empty line outside of aggregates private void writeDeclEnd() { buf.writestringln(";"); if (!adparent) buf.writenl(); } /// Writes the corresponding access specifier if necessary private void writeProtection(const AST.Visibility.Kind kind) { // Don't write visibility for global declarations if (!adparent || inAnonymousDecl) return; string token; switch(kind) with(AST.Visibility.Kind) { case none, private_: if (this.currentVisibility == AST.Visibility.Kind.private_) return; this.currentVisibility = AST.Visibility.Kind.private_; token = "private:"; break; case package_, protected_: if (this.currentVisibility == AST.Visibility.Kind.protected_) return; this.currentVisibility = AST.Visibility.Kind.protected_; token = "protected:"; break; case undefined, public_, export_: if (this.currentVisibility == AST.Visibility.Kind.public_) return; this.currentVisibility = AST.Visibility.Kind.public_; token = "public:"; break; default: printf("Unexpected visibility: %d!\n", kind); assert(0); } buf.level--; buf.writestringln(token); buf.level++; } /** * Writes an identifier into `buf` and checks for reserved identifiers. The * parameter `canFix` determines how this function handles C++ keywords: * * `false` => Raise a warning and print the identifier as-is * `true` => Append an underscore to the identifier * * Params: * s = the symbol denoting the identifier * canFixup = whether the identifier may be changed without affecting * binary compatibility */ private void writeIdentifier(const AST.Dsymbol s, const bool canFix = false) { if (const mn = getMangleOverride(s)) return buf.writestring(mn); writeIdentifier(s.ident, s.loc, s.kind(), canFix); } /** Overload of `writeIdentifier` used for all AST nodes not descending from Dsymbol **/ private void writeIdentifier(const Identifier ident, const Loc loc, const char* kind, const bool canFix = false) { bool needsFix; void warnCxxCompat(const(char)* reason) { if (canFix) { needsFix = true; return; } __gshared bool warned = false; warning(loc, "%s `%s` is a %s", kind, ident.toChars(), reason); if (!warned) { warningSupplemental(loc, "The generated C++ header will contain " ~ "identifiers that are keywords in C++"); warned = true; } } if (global.params.warnings != DiagnosticReporting.off || canFix) { // Warn about identifiers that are keywords in C++. if (auto kc = keywordClass(ident)) warnCxxCompat(kc); } buf.writestring(ident.toString()); if (needsFix) buf.writeByte('_'); } /// Checks whether `t` is a type that can be exported to C++ private bool isSupportedType(AST.Type t) { if (!t) { assert(tdparent); return true; } switch (t.ty) { // Nested types case AST.Tarray: case AST.Tsarray: case AST.Tpointer: case AST.Treference: case AST.Tdelegate: return isSupportedType((cast(AST.TypeNext) t).next); // Function pointers case AST.Tfunction: { auto tf = cast(AST.TypeFunction) t; if (!isSupportedType(tf.next)) return false; foreach (_, param; tf.parameterList) { if (!isSupportedType(param.type)) return false; } return true; } // Noreturn has a different mangling case AST.Tnoreturn: // _Imaginary is C only. case AST.Timaginary32: case AST.Timaginary64: case AST.Timaginary80: return false; default: return true; } } override void visit(AST.Dsymbol s) { debug (Debug_DtoH) { mixin(traceVisit!s); import dmd.asttypename; printf("[AST.Dsymbol enter] %s\n", s.astTypeName().ptr); } } override void visit(AST.Import i) { debug (Debug_DtoH) mixin(traceVisit!i); /// Writes `using = ` into `buf` const(char*) writeImport(AST.Dsymbol sym, const Identifier alias_) { /// `using` was introduced in C++ 11 and only works for types... if (global.params.cplusplus < CppStdRevision.cpp11) return "requires C++11"; if (auto ad = sym.isAliasDeclaration()) { sym = ad.toAlias(); ad = sym.isAliasDeclaration(); // Might be an alias to a basic type if (ad && !ad.aliassym && ad.type) goto Emit; } // Restricted to types and other aliases if (!sym.isScopeDsymbol() && !sym.isAggregateDeclaration()) return "only supports types"; // Write `using = `` Emit: buf.writestring("using "); writeIdentifier(alias_, i.loc, "renamed import"); buf.writestring(" = "); // Start at module scope to avoid collisions with local symbols if (this.context.adparent) buf.writestring("::"); buf.writestring(sym.ident.toString()); writeDeclEnd(); return null; } // Only missing without semantic analysis // FIXME: Templates need work due to missing parent & imported module if (!i.parent) { assert(tdparent); ignored("`%s` because it's inside of a template declaration", i.toChars()); return; } // Non-public imports don't create new symbols, include as needed if (i.visibility.kind < AST.Visibility.Kind.public_) return; // Symbols from static imports should be emitted inline if (i.isstatic) return; const isLocal = !i.parent.isModule(); // Need module for symbol lookup assert(i.mod); // Emit an alias for each public module member if (isLocal && i.names.length == 0) { assert(i.mod.symtab); // Sort alphabetically s.t. slight changes in semantic don't cause // massive changes in the order of declarations AST.Dsymbols entries; entries.reserve(i.mod.symtab.length); foreach (entry; i.mod.symtab.tab.asRange) { // Skip anonymous / invisible members import dmd.access : symbolIsVisible; if (!entry.key.isAnonymous() && symbolIsVisible(i, entry.value)) entries.push(entry.value); } // Seperate function because of a spurious dual-context deprecation static int compare(const AST.Dsymbol* a, const AST.Dsymbol* b) { return strcmp(a.ident.toChars(), b.ident.toChars()); } entries.sort!compare(); foreach (sym; entries) { includeSymbol(sym); if (auto err = writeImport(sym, sym.ident)) ignored("public import for `%s` because `using` %s", sym.ident.toChars(), err); } return; } // Include all public imports and emit using declarations for each alias foreach (const idx, name; i.names) { // Search the imported symbol auto sym = i.mod.search(Loc.initial, name); assert(sym); // Missing imports should error during semantic includeSymbol(sym); // Detect the assigned name for renamed import auto alias_ = i.aliases[idx]; if (!alias_) continue; if (auto err = writeImport(sym, alias_)) ignored("renamed import `%s = %s` because `using` %s", alias_.toChars(), name.toChars(), err); } } override void visit(AST.AttribDeclaration pd) { debug (Debug_DtoH) mixin(traceVisit!pd); Dsymbols* decl = pd.include(null); if (!decl) return; foreach (s; *decl) { if (adparent || s.visible().kind >= AST.Visibility.Kind.public_) s.accept(this); } } override void visit(AST.StorageClassDeclaration scd) { debug (Debug_DtoH) mixin(traceVisit!scd); const stcStash = this.storageClass; this.storageClass |= scd.stc; visit(cast(AST.AttribDeclaration) scd); this.storageClass = stcStash; } override void visit(AST.LinkDeclaration ld) { debug (Debug_DtoH) mixin(traceVisit!ld); auto save = linkage; linkage = ld.linkage; visit(cast(AST.AttribDeclaration)ld); linkage = save; } override void visit(AST.CPPMangleDeclaration md) { debug (Debug_DtoH) mixin(traceVisit!md); const oldLinkage = this.linkage; this.linkage = LINK.cpp; visit(cast(AST.AttribDeclaration) md); this.linkage = oldLinkage; } override void visit(AST.Module m) { debug (Debug_DtoH) mixin(traceVisit!m); foreach (s; *m.members) { if (s.visible().kind < AST.Visibility.Kind.public_) continue; s.accept(this); } } override void visit(AST.FuncDeclaration fd) { debug (Debug_DtoH) mixin(traceVisit!fd); if (cast(void*)fd in visited) return; // printf("FuncDeclaration %s %s\n", fd.toPrettyChars(), fd.type.toChars()); visited[cast(void*)fd] = true; // silently ignore non-user-defined destructors if (fd.isGenerated && fd.isDtorDeclaration()) return; // Note that tf might be null for templated (member) functions auto tf = cast(AST.TypeFunction)fd.type; if ((tf && (tf.linkage != LINK.c || adparent) && tf.linkage != LINK.cpp) || (!tf && fd.isPostBlitDeclaration())) { ignored("function %s because of linkage", fd.toPrettyChars()); return checkFunctionNeedsPlaceholder(fd); } if (fd.mangleOverride && tf && tf.linkage != LINK.c) { ignored("function %s because C++ doesn't support explicit mangling", fd.toPrettyChars()); return checkFunctionNeedsPlaceholder(fd); } if (!adparent && !fd.fbody) { ignored("function %s because it is extern", fd.toPrettyChars()); return; } if (fd.visibility.kind == AST.Visibility.Kind.none || fd.visibility.kind == AST.Visibility.Kind.private_) { ignored("function %s because it is private", fd.toPrettyChars()); return; } if (tf && !isSupportedType(tf.next)) { ignored("function %s because its return type cannot be mapped to C++", fd.toPrettyChars()); return checkFunctionNeedsPlaceholder(fd); } if (tf) foreach (i, fparam; tf.parameterList) { if (!isSupportedType(fparam.type)) { ignored("function %s because one of its parameters has type `%s` which cannot be mapped to C++", fd.toPrettyChars(), fparam.type.toChars()); return checkFunctionNeedsPlaceholder(fd); } } writeProtection(fd.visibility.kind); if (tf && tf.linkage == LINK.c) buf.writestring("extern \"C\" "); else if (!adparent) buf.writestring("extern "); if (adparent && fd.isStatic()) buf.writestring("static "); else if (adparent && ( // Virtual functions in non-templated classes (fd.vtblIndex != -1 && !fd.isOverride()) || // Virtual functions in templated classes (fd.vtblIndex still -1) (tdparent && adparent.isClassDeclaration() && !(this.storageClass & AST.STC.final_ || fd.isFinal)))) buf.writestring("virtual "); debug (Debug_DtoH_Checks) if (adparent && !tdparent) { auto s = adparent.search(Loc.initial, fd.ident); auto cd = adparent.isClassDeclaration(); if (!(adparent.storage_class & AST.STC.abstract_) && !(cd && cd.isAbstract()) && s is fd && !fd.overnext) { const cn = adparent.ident.toChars(); const fn = fd.ident.toChars(); const vi = fd.vtblIndex; checkbuf.printf("assert(getSlotNumber <%s>(0, &%s::%s) == %d);", cn, cn, fn, vi); checkbuf.writenl(); } } if (adparent && fd.isDisabled && global.params.cplusplus < CppStdRevision.cpp11) writeProtection(AST.Visibility.Kind.private_); funcToBuffer(tf, fd); // FIXME: How to determine if fd is const without tf? if (adparent && tf && (tf.isConst() || tf.isImmutable())) { bool fdOverridesAreConst = true; foreach (fdv; fd.foverrides) { auto tfv = cast(AST.TypeFunction)fdv.type; if (!tfv.isConst() && !tfv.isImmutable()) { fdOverridesAreConst = false; break; } } buf.writestring(fdOverridesAreConst ? " const" : " /* const */"); } if (adparent && fd.isAbstract()) buf.writestring(" = 0"); if (adparent && fd.isDisabled && global.params.cplusplus >= CppStdRevision.cpp11) buf.writestring(" = delete"); buf.writestringln(";"); if (adparent && fd.isDisabled && global.params.cplusplus < CppStdRevision.cpp11) writeProtection(AST.Visibility.Kind.public_); if (!adparent) buf.writenl(); } /++ + Checks whether `fd` is a function that requires a dummy declaration + instead of simply emitting the declaration (because it would cause + ABI / behaviour issues). This includes: + + - virtual functions to ensure proper vtable layout + - destructors that would break RAII +/ private void checkFunctionNeedsPlaceholder(AST.FuncDeclaration fd) { // Omit redundant declarations - the slot was already // reserved in the base class if (fd.isVirtual() && fd.isIntroducing()) { // Hide placeholders because they are not ABI compatible writeProtection(AST.Visibility.Kind.private_); __gshared int counter; // Ensure unique names in all cases buf.printf("virtual void __vtable_slot_%u();", counter++); buf.writenl(); } else if (fd.isDtorDeclaration()) { // Create inaccessible dtor to prevent code from keeping instances that // need to be destroyed on the C++ side (but cannot call the dtor) writeProtection(AST.Visibility.Kind.private_); buf.writeByte('~'); buf.writestring(adparent.ident.toString()); buf.writestringln("();"); } } override void visit(AST.UnitTestDeclaration utd) { debug (Debug_DtoH) mixin(traceVisit!utd); } override void visit(AST.VarDeclaration vd) { debug (Debug_DtoH) mixin(traceVisit!vd); if (!shouldEmitAndMarkVisited(vd)) return; // Tuple field are expanded into multiple VarDeclarations // (we'll visit them later) if (vd.type && vd.type.isTypeTuple()) return; if (vd.originalType && vd.type == AST.Type.tsize_t) origType = vd.originalType; scope(exit) origType = null; if (!vd.alignment.isDefault() && !vd.alignment.isUnknown()) { buf.printf("// Ignoring var %s alignment %d", vd.toChars(), vd.alignment.get()); buf.writenl(); } // Determine the variable type which might be missing inside of // template declarations. Infer the type from the initializer then AST.Type type = vd.type; if (!type) { assert(tdparent); // Just a precaution, implicit type without initializer should be rejected if (!vd._init) return; if (auto ei = vd._init.isExpInitializer()) type = ei.exp.type; // Can happen if the expression needs further semantic if (!type) { ignored("%s because the type could not be determined", vd.toPrettyChars()); return; } // Apply const/immutable to the inferred type if (vd.storage_class & (AST.STC.const_ | AST.STC.immutable_)) type = type.constOf(); } if (vd.storage_class & AST.STC.manifest) { EnumKind kind = getEnumKind(type); if (vd.visibility.kind == AST.Visibility.Kind.none || vd.visibility.kind == AST.Visibility.Kind.private_) { ignored("enum `%s` because it is `%s`.", vd.toPrettyChars(), AST.visibilityToChars(vd.visibility.kind)); return; } writeProtection(vd.visibility.kind); final switch (kind) { case EnumKind.Int, EnumKind.Numeric: // 'enum : type' is only available from C++-11 onwards. if (global.params.cplusplus < CppStdRevision.cpp11) goto case; buf.writestring("enum : "); determineEnumType(type).accept(this); buf.writestring(" { "); writeIdentifier(vd, true); buf.writestring(" = "); auto ie = AST.initializerToExpression(vd._init).isIntegerExp(); visitInteger(ie.toInteger(), type); buf.writestring(" };"); break; case EnumKind.String, EnumKind.Enum: buf.writestring("static "); auto target = determineEnumType(type); target.accept(this); buf.writestring(" const "); writeIdentifier(vd, true); buf.writestring(" = "); auto e = AST.initializerToExpression(vd._init); printExpressionFor(target, e); buf.writestring(";"); break; case EnumKind.Other: ignored("enum `%s` because type `%s` is currently not supported for enum constants.", vd.toPrettyChars(), type.toChars()); return; } buf.writenl(); buf.writenl(); return; } if (vd.storage_class & (AST.STC.static_ | AST.STC.extern_ | AST.STC.gshared) || vd.parent && vd.parent.isModule()) { const vdLinkage = vd.resolvedLinkage(); if (vdLinkage != LINK.c && vdLinkage != LINK.cpp && !(tdparent && (this.linkage == LINK.c || this.linkage == LINK.cpp))) { ignored("variable %s because of linkage", vd.toPrettyChars()); return; } if (vd.mangleOverride && vdLinkage != LINK.c) { ignored("variable %s because C++ doesn't support explicit mangling", vd.toPrettyChars()); return; } if (!isSupportedType(type)) { ignored("variable %s because its type cannot be mapped to C++", vd.toPrettyChars()); return; } if (auto kc = keywordClass(vd.ident)) { ignored("variable %s because its name is a %s", vd.toPrettyChars(), kc); return; } writeProtection(vd.visibility.kind); if (vdLinkage == LINK.c) buf.writestring("extern \"C\" "); else if (!adparent) buf.writestring("extern "); if (adparent) buf.writestring("static "); typeToBuffer(type, vd); writeDeclEnd(); return; } if (adparent) { writeProtection(vd.visibility.kind); typeToBuffer(type, vd, true); buf.writestringln(";"); debug (Debug_DtoH_Checks) { checkbuf.level++; const pn = adparent.ident.toChars(); const vn = vd.ident.toChars(); const vo = vd.offset; checkbuf.printf("assert(offsetof(%s, %s) == %d);", pn, vn, vo); checkbuf.writenl(); checkbuf.level--; } return; } visit(cast(AST.Dsymbol)vd); } override void visit(AST.TypeInfoDeclaration tid) { debug (Debug_DtoH) mixin(traceVisit!tid); } override void visit(AST.AliasDeclaration ad) { debug (Debug_DtoH) mixin(traceVisit!ad); if (!shouldEmitAndMarkVisited(ad)) return; writeProtection(ad.visibility.kind); if (auto t = ad.type) { if (t.ty == AST.Tdelegate || t.ty == AST.Tident) { visit(cast(AST.Dsymbol)ad); return; } // for function pointers we need to original type if (ad.originalType && ad.type.ty == AST.Tpointer && (cast(AST.TypePointer)t).nextOf.ty == AST.Tfunction) { origType = ad.originalType; } scope(exit) origType = null; buf.writestring("typedef "); typeToBuffer(origType !is null ? origType : t, ad); writeDeclEnd(); return; } if (!ad.aliassym) { assert(0); } if (auto ti = ad.aliassym.isTemplateInstance()) { visitTi(ti); return; } if (auto sd = ad.aliassym.isStructDeclaration()) { buf.writestring("typedef "); sd.type.accept(this); buf.writestring(" "); writeIdentifier(ad); writeDeclEnd(); return; } else if (auto td = ad.aliassym.isTemplateDeclaration()) { if (global.params.cplusplus < CppStdRevision.cpp11) { ignored("%s because `using` declarations require C++ 11", ad.toPrettyChars()); return; } printTemplateParams(td); buf.writestring("using "); writeIdentifier(ad); buf.writestring(" = "); writeFullName(td); buf.writeByte('<'); foreach (const idx, const p; *td.parameters) { if (idx) buf.writestring(", "); writeIdentifier(p.ident, p.loc, "parameter", true); } buf.writestringln(">;"); return; } auto fd = ad.aliassym.isFuncDeclaration(); if (fd && (fd.isGenerated() || fd.isDtorDeclaration())) { // Ignore. It's taken care of while visiting FuncDeclaration return; } // Recognize member function aliases, e.g. alias visit = Parent.visit; if (adparent && fd) { auto pd = fd.isMember(); if (!pd) { ignored("%s because free functions cannot be aliased in C++", ad.toPrettyChars()); } else if (global.params.cplusplus < CppStdRevision.cpp11) { ignored("%s because `using` declarations require C++ 11", ad.toPrettyChars()); } else if (ad.ident != fd.ident) { ignored("%s because `using` cannot rename functions in aggregates", ad.toPrettyChars()); } else if (fd.toAliasFunc().parent.isTemplateMixin()) { // Member's of template mixins are directly emitted into the aggregate } else { buf.writestring("using "); // Print prefix of the base class if this function originates from a superclass // because alias might be resolved through multiple classes, e.g. // e.g. for alias visit = typeof(super).visit in the visitors if (!fd.isIntroducing()) printPrefix(ad.toParent().isClassDeclaration().baseClass); else printPrefix(pd); buf.writestring(fd.ident.toChars()); buf.writestringln(";"); } return; } ignored("%s %s", ad.aliassym.kind(), ad.aliassym.toPrettyChars()); } override void visit(AST.Nspace ns) { debug (Debug_DtoH) mixin(traceVisit!ns); handleNspace(ns, ns.members); } override void visit(AST.CPPNamespaceDeclaration ns) { debug (Debug_DtoH) mixin(traceVisit!ns); handleNspace(ns, ns.decl); } /// Writes the namespace declaration and visits all members private void handleNspace(AST.Dsymbol namespace, Dsymbols* members) { buf.writestring("namespace "); writeIdentifier(namespace); buf.writenl(); buf.writestring("{"); buf.writenl(); buf.level++; foreach(decl;(*members)) { decl.accept(this); } buf.level--; buf.writestring("}"); buf.writenl(); } override void visit(AST.AnonDeclaration ad) { debug (Debug_DtoH) mixin(traceVisit!ad); const anonStash = inAnonymousDecl; inAnonymousDecl = true; scope (exit) inAnonymousDecl = anonStash; buf.writestringln(ad.isunion ? "union" : "struct"); buf.writestringln("{"); buf.level++; foreach (s; *ad.decl) { s.accept(this); } buf.level--; buf.writestringln("};"); } private bool memberField(AST.VarDeclaration vd) { if (!vd.type || !vd.type.deco || !vd.ident) return false; if (!vd.isField()) return false; if (vd.type.ty == AST.Tfunction) return false; if (vd.type.ty == AST.Tsarray) return false; return true; } override void visit(AST.StructDeclaration sd) { debug (Debug_DtoH) mixin(traceVisit!sd); if (!shouldEmitAndMarkVisited(sd)) return; const ignoredStash = this.ignoredCounter; scope (exit) this.ignoredCounter = ignoredStash; pushAlignToBuffer(sd.alignment); writeProtection(sd.visibility.kind); const structAsClass = sd.cppmangle == CPPMANGLE.asClass; if (sd.isUnionDeclaration()) buf.writestring("union "); else buf.writestring(structAsClass ? "class " : "struct "); writeIdentifier(sd); if (!sd.members) { buf.writestringln(";"); buf.writenl(); return; } // D structs are always final if (!sd.isUnionDeclaration()) buf.writestring(" final"); buf.writenl(); buf.writestring("{"); const protStash = this.currentVisibility; this.currentVisibility = structAsClass ? AST.Visibility.Kind.private_ : AST.Visibility.Kind.public_; scope (exit) this.currentVisibility = protStash; buf.level++; buf.writenl(); auto save = adparent; adparent = sd; foreach (m; *sd.members) { m.accept(this); } // Generate default ctor if (!sd.noDefaultCtor && !sd.isUnionDeclaration()) { writeProtection(AST.Visibility.Kind.public_); buf.printf("%s()", sd.ident.toChars()); size_t varCount; bool first = true; buf.level++; foreach (m; *sd.members) { if (auto vd = m.isVarDeclaration()) { if (!memberField(vd)) continue; varCount++; if (!vd._init && !vd.type.isTypeBasic() && !vd.type.isTypePointer && !vd.type.isTypeStruct && !vd.type.isTypeClass && !vd.type.isTypeDArray && !vd.type.isTypeSArray) { continue; } if (vd._init && vd._init.isVoidInitializer()) continue; if (first) { buf.writestringln(" :"); first = false; } else { buf.writestringln(","); } writeIdentifier(vd, true); buf.writeByte('('); if (vd._init) { auto e = AST.initializerToExpression(vd._init); printExpressionFor(vd.type, e, true); } buf.printf(")"); } } buf.level--; buf.writenl(); buf.writestringln("{"); buf.writestringln("}"); auto ctor = sd.ctor ? sd.ctor.isFuncDeclaration() : null; if (varCount && (!ctor || ctor.storage_class & AST.STC.disable)) { buf.printf("%s(", sd.ident.toChars()); first = true; foreach (m; *sd.members) { if (auto vd = m.isVarDeclaration()) { if (!memberField(vd)) continue; if (!first) buf.writestring(", "); assert(vd.type); assert(vd.ident); typeToBuffer(vd.type, vd, true); // Don't print default value for first parameter to not clash // with the default ctor defined above if (!first) { buf.writestring(" = "); printExpressionFor(vd.type, findDefaultInitializer(vd)); } first = false; } } buf.writestring(") :"); buf.level++; buf.writenl(); first = true; foreach (m; *sd.members) { if (auto vd = m.isVarDeclaration()) { if (!memberField(vd)) continue; if (first) first = false; else buf.writestringln(","); writeIdentifier(vd, true); buf.writeByte('('); writeIdentifier(vd, true); buf.writeByte(')'); } } buf.writenl(); buf.writestringln("{}"); buf.level--; } } buf.level--; adparent = save; buf.writestringln("};"); popAlignToBuffer(sd.alignment); buf.writenl(); // Workaround because size triggers a forward-reference error // for struct templates (the size is undetermined even if the // size doesn't depend on the parameters) debug (Debug_DtoH_Checks) if (!tdparent) { checkbuf.level++; const sn = sd.ident.toChars(); const sz = sd.size(Loc.initial); checkbuf.printf("assert(sizeof(%s) == %llu);", sn, sz); checkbuf.writenl(); checkbuf.level--; } } /// Starts a custom alignment section using `#pragma pack` if /// `alignment` specifies a custom alignment private void pushAlignToBuffer(structalign_t alignment) { // DMD ensures alignment is a power of two //assert(alignment > 0 && ((alignment & (alignment - 1)) == 0), // "Invalid alignment size"); // When no alignment is specified, `uint.max` is the default // FIXME: alignment is 0 for structs templated members if (alignment.isDefault() || (tdparent && alignment.isUnknown())) { return; } buf.printf("#pragma pack(push, %d)", alignment.get()); buf.writenl(); } /// Ends a custom alignment section using `#pragma pack` if /// `alignment` specifies a custom alignment private void popAlignToBuffer(structalign_t alignment) { if (alignment.isDefault() || (tdparent && alignment.isUnknown())) return; buf.writestringln("#pragma pack(pop)"); } override void visit(AST.ClassDeclaration cd) { debug (Debug_DtoH) mixin(traceVisit!cd); if (cd.baseClass && shouldEmit(cd)) includeSymbol(cd.baseClass); if (!shouldEmitAndMarkVisited(cd)) return; writeProtection(cd.visibility.kind); const classAsStruct = cd.cppmangle == CPPMANGLE.asStruct; buf.writestring(classAsStruct ? "struct " : "class "); writeIdentifier(cd); if (cd.storage_class & AST.STC.final_ || (tdparent && this.storageClass & AST.STC.final_)) buf.writestring(" final"); assert(cd.baseclasses); foreach (i, base; *cd.baseclasses) { buf.writestring(i == 0 ? " : public " : ", public "); // Base classes/interfaces might depend on template parameters, // e.g. class A(T) : B!T { ... } if (base.sym is null) { base.type.accept(this); } else { writeFullName(base.sym); } } if (!cd.members) { buf.writestring(";"); buf.writenl(); buf.writenl(); return; } buf.writenl(); buf.writestringln("{"); const protStash = this.currentVisibility; this.currentVisibility = classAsStruct ? AST.Visibility.Kind.public_ : AST.Visibility.Kind.private_; scope (exit) this.currentVisibility = protStash; auto save = adparent; adparent = cd; buf.level++; foreach (m; *cd.members) { m.accept(this); } buf.level--; adparent = save; buf.writestringln("};"); buf.writenl(); } override void visit(AST.EnumDeclaration ed) { debug (Debug_DtoH) mixin(traceVisit!ed); if (!shouldEmitAndMarkVisited(ed)) return; if (ed.isSpecial()) { //ignored("%s because it is a special C++ type", ed.toPrettyChars()); return; } // we need to know a bunch of stuff about the enum... bool isAnonymous = ed.ident is null; const isOpaque = !ed.members; AST.Type type = ed.memtype; if (!type && !isOpaque) { // check all keys have matching type foreach (_m; *ed.members) { auto m = _m.isEnumMember(); if (!type) type = m.type; else if (m.type !is type) { type = null; break; } } } EnumKind kind = getEnumKind(type); if (isOpaque) { // Opaque enums were introduced in C++ 11 (workaround?) if (global.params.cplusplus < CppStdRevision.cpp11) { ignored("%s because opaque enums require C++ 11", ed.toPrettyChars()); return; } // Opaque enum defaults to int but the type might not be set else if (!type) { kind = EnumKind.Int; } // Cannot apply namespace workaround for non-integral types else if (kind != EnumKind.Int && kind != EnumKind.Numeric) { ignored("enum %s because of its base type", ed.toPrettyChars()); return; } } // determine if this is an enum, or just a group of manifest constants bool manifestConstants = !isOpaque && (!type || (isAnonymous && kind == EnumKind.Other)); assert(!manifestConstants || isAnonymous); writeProtection(ed.visibility.kind); // write the enum header if (!manifestConstants) { if (kind == EnumKind.Int || kind == EnumKind.Numeric) { buf.writestring("enum"); // D enums are strong enums, but there exists only a direct mapping // with 'enum class' from C++-11 onwards. if (global.params.cplusplus >= CppStdRevision.cpp11) { if (!isAnonymous) { buf.writestring(" class "); writeIdentifier(ed); } if (kind == EnumKind.Numeric) { buf.writestring(" : "); determineEnumType(type).accept(this); } } else if (!isAnonymous) { buf.writeByte(' '); writeIdentifier(ed); } } else { buf.writestring("namespace"); if(!isAnonymous) { buf.writeByte(' '); writeIdentifier(ed); } } // Opaque enums have no members, hence skip the body if (isOpaque) { buf.writestringln(";"); return; } else { buf.writenl(); buf.writestringln("{"); } } // emit constant for each member if (!manifestConstants) buf.level++; foreach (_m; *ed.members) { auto m = _m.isEnumMember(); AST.Type memberType = type ? type : m.type; const EnumKind memberKind = type ? kind : getEnumKind(memberType); if (!manifestConstants && (kind == EnumKind.Int || kind == EnumKind.Numeric)) { // C++-98 compatible enums must use the typename as a prefix to avoid // collisions with other identifiers in scope. For consistency with D, // the enum member `Type.member` is emitted as `Type_member` in C++-98. if (!isAnonymous && global.params.cplusplus < CppStdRevision.cpp11) { writeIdentifier(ed); buf.writeByte('_'); } writeIdentifier(m, true); buf.writestring(" = "); auto ie = cast(AST.IntegerExp)m.value; visitInteger(ie.toInteger(), memberType); buf.writestring(","); } else if (global.params.cplusplus >= CppStdRevision.cpp11 && manifestConstants && (memberKind == EnumKind.Int || memberKind == EnumKind.Numeric)) { buf.writestring("enum : "); determineEnumType(memberType).accept(this); buf.writestring(" { "); writeIdentifier(m, true); buf.writestring(" = "); auto ie = cast(AST.IntegerExp)m.value; visitInteger(ie.toInteger(), memberType); buf.writestring(" };"); } else { buf.writestring("static "); auto target = determineEnumType(memberType); target.accept(this); buf.writestring(" const "); writeIdentifier(m, true); buf.writestring(" = "); printExpressionFor(target, m.origValue); buf.writestring(";"); } buf.writenl(); } if (!manifestConstants) buf.level--; // write the enum tail if (!manifestConstants) buf.writestring("};"); buf.writenl(); buf.writenl(); } override void visit(AST.EnumMember em) { assert(em.ed); // Members of anonymous members are reachable without referencing the // EnumDeclaration, e.g. public import foo : someEnumMember; if (em.ed.isAnonymous()) { visit(em.ed); return; } assert(false, "This node type should be handled in the EnumDeclaration"); } /** * Prints a member/parameter/variable declaration into `buf`. * * Params: * t = the type (used if `this.origType` is null) * s = the symbol denoting the identifier * canFixup = whether the identifier may be changed without affecting * binary compatibility (forwarded to `writeIdentifier`) */ private void typeToBuffer(AST.Type t, AST.Dsymbol s, const bool canFixup = false) { debug (Debug_DtoH) { printf("[typeToBuffer(AST.Type, AST.Dsymbol) enter] %s sym %s\n", t.toChars(), s.toChars()); scope(exit) printf("[typeToBuffer(AST.Type, AST.Dsymbol) exit] %s sym %s\n", t.toChars(), s.toChars()); } // The context pointer (represented as `ThisDeclaration`) is named // `this` but accessible via `outer` if (auto td = s.isThisDeclaration()) { import dmd.id; this.ident = Id.outer; } else this.ident = s.ident; auto type = origType !is null ? origType : t; AST.Dsymbol customLength; // Check for quirks that are usually resolved during semantic if (tdparent) { // Declarations within template declarations might use TypeAArray // instead of TypeSArray when the length is not an IntegerExp, // e.g. int[SOME_CONSTANT] if (auto taa = type.isTypeAArray()) { // Try to resolve the symbol from the key if it's not an actual type Identifier id; if (auto ti = taa.index.isTypeIdentifier()) id = ti.ident; if (id) { auto sym = findSymbol(id, adparent ? adparent : tdparent); if (!sym) { // Couldn't resolve, assume actual AA } else if (AST.isType(sym)) { // a real associative array, forward to visit } else if (auto vd = sym.isVarDeclaration()) { // Actually a static array with length symbol customLength = sym; type = taa.next; // visit the element type, length is written below } else { printf("Resolved unexpected symbol while determining static array length: %s\n", sym.toChars()); fflush(stdout); fatal(); } } } } type.accept(this); if (this.ident) { buf.writeByte(' '); // Custom identifier doesn't need further checks if (this.ident !is s.ident) buf.writestring(this.ident.toString()); else writeIdentifier(s, canFixup); } this.ident = null; // Size is either taken from the type or resolved above auto tsa = t.isTypeSArray(); if (tsa || customLength) { buf.writeByte('['); if (tsa) tsa.dim.accept(this); else writeFullName(customLength); buf.writeByte(']'); } else if (t.isTypeNoreturn()) buf.writestring("[0]"); } override void visit(AST.Type t) { debug (Debug_DtoH) mixin(traceVisit!t); printf("Invalid type: %s\n", t.toPrettyChars()); assert(0); } override void visit(AST.TypeNoreturn t) { debug (Debug_DtoH) mixin(traceVisit!t); buf.writestring("/* noreturn */ char"); } override void visit(AST.TypeIdentifier t) { debug (Debug_DtoH) mixin(traceVisit!t); // Try to resolve the referenced symbol if (auto sym = findSymbol(t.ident)) ensureDeclared(outermostSymbol(sym)); if (t.idents.length) buf.writestring("typename "); writeIdentifier(t.ident, t.loc, "type", tdparent !is null); foreach (arg; t.idents) { buf.writestring("::"); import dmd.root.rootobject; // Is this even possible? if (arg.dyncast != DYNCAST.identifier) { printf("arg.dyncast() = %d\n", arg.dyncast()); assert(false); } buf.writestring((cast(Identifier) arg).toChars()); } } override void visit(AST.TypeNull t) { debug (Debug_DtoH) mixin(traceVisit!t); if (global.params.cplusplus >= CppStdRevision.cpp11) buf.writestring("nullptr_t"); else buf.writestring("void*"); } override void visit(AST.TypeTypeof t) { debug (Debug_DtoH) mixin(traceVisit!t); assert(t.exp); if (t.exp.type) { t.exp.type.accept(this); } else if (t.exp.isThisExp()) { // Short circuit typeof(this) => assert(adparent); buf.writestring(adparent.ident.toChars()); } else { // Relying on C++'s typeof might produce wrong results // but it's the best we've got here. buf.writestring("typeof("); t.exp.accept(this); buf.writeByte(')'); } } override void visit(AST.TypeBasic t) { debug (Debug_DtoH) mixin(traceVisit!t); if (t.isConst() || t.isImmutable()) buf.writestring("const "); string typeName; switch (t.ty) { case AST.Tvoid: typeName = "void"; break; case AST.Tbool: typeName = "bool"; break; case AST.Tchar: typeName = "char"; break; case AST.Twchar: typeName = "char16_t"; break; case AST.Tdchar: typeName = "char32_t"; break; case AST.Tint8: typeName = "int8_t"; break; case AST.Tuns8: typeName = "uint8_t"; break; case AST.Tint16: typeName = "int16_t"; break; case AST.Tuns16: typeName = "uint16_t"; break; case AST.Tint32: typeName = "int32_t"; break; case AST.Tuns32: typeName = "uint32_t"; break; case AST.Tint64: typeName = "int64_t"; break; case AST.Tuns64: typeName = "uint64_t"; break; case AST.Tfloat32: typeName = "float"; break; case AST.Tfloat64: typeName = "double"; break; case AST.Tfloat80: typeName = "_d_real"; hasReal = true; break; case AST.Tcomplex32: typeName = "_Complex float"; break; case AST.Tcomplex64: typeName = "_Complex double"; break; case AST.Tcomplex80: typeName = "_Complex _d_real"; hasReal = true; break; // ???: This is not strictly correct, but it should be ignored // in all places where it matters most (variables, functions, ...). case AST.Timaginary32: typeName = "float"; break; case AST.Timaginary64: typeName = "double"; break; case AST.Timaginary80: typeName = "_d_real"; hasReal = true; break; default: //t.print(); assert(0); } buf.writestring(typeName); } override void visit(AST.TypePointer t) { debug (Debug_DtoH) mixin(traceVisit!t); auto ts = t.next.isTypeStruct(); if (ts && !strcmp(ts.sym.ident.toChars(), "__va_list_tag")) { buf.writestring("va_list"); return; } // Pointer targets can be forward referenced const fwdSave = forwarding; forwarding = true; scope (exit) forwarding = fwdSave; t.next.accept(this); if (t.next.ty != AST.Tfunction) buf.writeByte('*'); if (t.isConst() || t.isImmutable()) buf.writestring(" const"); } override void visit(AST.TypeSArray t) { debug (Debug_DtoH) mixin(traceVisit!t); t.next.accept(this); } override void visit(AST.TypeAArray t) { debug (Debug_DtoH) mixin(traceVisit!t); AST.Type.tvoidptr.accept(this); } override void visit(AST.TypeFunction tf) { debug (Debug_DtoH) mixin(traceVisit!tf); tf.next.accept(this); buf.writeByte('('); buf.writeByte('*'); if (ident) buf.writestring(ident.toChars()); ident = null; buf.writeByte(')'); buf.writeByte('('); foreach (i, fparam; tf.parameterList) { if (i) buf.writestring(", "); fparam.accept(this); } if (tf.parameterList.varargs) { if (tf.parameterList.parameters.dim && tf.parameterList.varargs == 1) buf.writestring(", "); buf.writestring("..."); } buf.writeByte(')'); } /// Writes the type that represents `ed` into `buf`. /// (Might not be `ed` for special enums or enums that were emitted as namespaces) private void enumToBuffer(AST.EnumDeclaration ed) { debug (Debug_DtoH) mixin(traceVisit!ed); if (ed.isSpecial()) { if (ed.ident == DMDType.c_long) buf.writestring("long"); else if (ed.ident == DMDType.c_ulong) buf.writestring("unsigned long"); else if (ed.ident == DMDType.c_longlong) buf.writestring("long long"); else if (ed.ident == DMDType.c_ulonglong) buf.writestring("unsigned long long"); else if (ed.ident == DMDType.c_long_double) buf.writestring("long double"); else if (ed.ident == DMDType.c_char) buf.writestring("char"); else if (ed.ident == DMDType.c_wchar_t) buf.writestring("wchar_t"); else if (ed.ident == DMDType.c_complex_float) buf.writestring("_Complex float"); else if (ed.ident == DMDType.c_complex_double) buf.writestring("_Complex double"); else if (ed.ident == DMDType.c_complex_real) buf.writestring("_Complex long double"); else { //ed.print(); assert(0); } return; } const kind = getEnumKind(ed.memtype); // Check if the enum was emitted as a real enum if (kind == EnumKind.Int || kind == EnumKind.Numeric) { writeFullName(ed); } else { // Use the base type if the enum was emitted as a namespace buf.printf("/* %s */ ", ed.ident.toChars()); ed.memtype.accept(this); } } override void visit(AST.TypeEnum t) { debug (Debug_DtoH) mixin(traceVisit!t); if (t.isConst() || t.isImmutable()) buf.writestring("const "); enumToBuffer(t.sym); } override void visit(AST.TypeStruct t) { debug (Debug_DtoH) mixin(traceVisit!t); if (t.isConst() || t.isImmutable()) buf.writestring("const "); writeFullName(t.sym); } override void visit(AST.TypeDArray t) { debug (Debug_DtoH) mixin(traceVisit!t); if (t.isConst() || t.isImmutable()) buf.writestring("const "); buf.writestring("_d_dynamicArray< "); t.next.accept(this); buf.writestring(" >"); } override void visit(AST.TypeInstance t) { visitTi(t.tempinst); } private void visitTi(AST.TemplateInstance ti) { debug (Debug_DtoH) mixin(traceVisit!ti); // Ensure that the TD appears before the instance if (auto td = findTemplateDeclaration(ti)) ensureDeclared(td); foreach (o; *ti.tiargs) { if (!AST.isType(o)) return; } buf.writestring(ti.name.toChars()); buf.writeByte('<'); foreach (i, o; *ti.tiargs) { if (i) buf.writestring(", "); if (auto tt = AST.isType(o)) { tt.accept(this); } else { //ti.print(); //o.print(); assert(0); } } buf.writestring(" >"); } override void visit(AST.TemplateDeclaration td) { debug (Debug_DtoH) mixin(traceVisit!td); if (!shouldEmitAndMarkVisited(td)) return; if (!td.parameters || !td.onemember || (!td.onemember.isStructDeclaration && !td.onemember.isClassDeclaration && !td.onemember.isFuncDeclaration)) { visit(cast(AST.Dsymbol)td); return; } // Explicitly disallow templates with non-type parameters or specialization. foreach (p; *td.parameters) { if (!p.isTemplateTypeParameter() || p.specialization()) { visit(cast(AST.Dsymbol)td); return; } } auto save = tdparent; tdparent = td; const bookmark = buf.length; printTemplateParams(td); const oldIgnored = this.ignoredCounter; td.onemember.accept(this); // Remove "template<...>" if the symbol could not be emitted if (oldIgnored != this.ignoredCounter) buf.setsize(bookmark); tdparent = save; } /// Writes the template<...> header for the supplied template declaration private void printTemplateParams(const AST.TemplateDeclaration td) { buf.writestring("template <"); bool first = true; foreach (p; *td.parameters) { if (first) first = false; else buf.writestring(", "); buf.writestring("typename "); writeIdentifier(p.ident, p.loc, "template parameter", true); } buf.writestringln(">"); } /// Emit declarations of the TemplateMixin in the current scope override void visit(AST.TemplateMixin tm) { debug (Debug_DtoH) mixin(traceVisit!tm); auto members = tm.members; // members are missing for instances inside of TemplateDeclarations, e.g. // template Foo(T) { mixin Bar!T; } if (!members) { if (auto td = findTemplateDeclaration(tm)) members = td.members; // Emit members of the template else return; // Cannot emit mixin } foreach (s; *members) { // kind is undefined without semantic const kind = s.visible().kind; if (kind == AST.Visibility.Kind.public_ || kind == AST.Visibility.Kind.undefined) s.accept(this); } } /** * Finds a symbol with the identifier `name` by iterating the linked list of parent * symbols, starting from `context`. * * Returns: the symbol or `null` if missing */ private AST.Dsymbol findSymbol(Identifier name, AST.Dsymbol context) { // Follow the declaration context for (auto par = context; par; par = par.toParentDecl()) { // Check that `name` doesn't refer to a template parameter if (auto td = par.isTemplateDeclaration()) { foreach (const p; *td.parameters) { if (p.ident == name) return null; } } if (auto mem = findMember(par, name)) { return mem; } } return null; } /// ditto private AST.Dsymbol findSymbol(Identifier name) { AST.Dsymbol sym; if (adparent) sym = findSymbol(name, adparent); if (!sym && tdparent) sym = findSymbol(name, tdparent); return sym; } /// Finds the template declaration for instance `ti` private AST.TemplateDeclaration findTemplateDeclaration(AST.TemplateInstance ti) { if (ti.tempdecl) return ti.tempdecl.isTemplateDeclaration(); assert(tdparent); // Only missing inside of templates // Search for the TemplateDeclaration, starting from the enclosing scope // if known or the enclosing template. auto sym = findSymbol(ti.name, ti.parent ? ti.parent : tdparent); return sym ? sym.isTemplateDeclaration() : null; } override void visit(AST.TypeClass t) { debug (Debug_DtoH) mixin(traceVisit!t); // Classes are emitted as pointer and hence can be forwarded const fwdSave = forwarding; forwarding = true; scope (exit) forwarding = fwdSave; if (t.isConst() || t.isImmutable()) buf.writestring("const "); writeFullName(t.sym); buf.writeByte('*'); if (t.isConst() || t.isImmutable()) buf.writestring(" const"); } /** * Writes the function signature to `buf`. * * Params: * fd = the function to print * tf = fd's type */ private void funcToBuffer(AST.TypeFunction tf, AST.FuncDeclaration fd) { debug (Debug_DtoH) { printf("[funcToBuffer(AST.TypeFunction) enter] %s\n", fd.toChars()); scope(exit) printf("[funcToBuffer(AST.TypeFunction) exit] %s\n", fd.toChars()); } auto originalType = cast(AST.TypeFunction)fd.originalType; if (fd.isCtorDeclaration() || fd.isDtorDeclaration()) { if (fd.isDtorDeclaration()) { buf.writeByte('~'); } buf.writestring(adparent.toChars()); if (!tf) { assert(fd.isDtorDeclaration()); buf.writestring("()"); return; } } else { import dmd.root.string : toDString; assert(tf.next, fd.loc.toChars().toDString()); tf.next == AST.Type.tsize_t ? originalType.next.accept(this) : tf.next.accept(this); if (tf.isref) buf.writeByte('&'); buf.writeByte(' '); writeIdentifier(fd); } buf.writeByte('('); foreach (i, fparam; tf.parameterList) { if (i) buf.writestring(", "); if (fparam.type == AST.Type.tsize_t && originalType) { fparam = originalType.parameterList[i]; } fparam.accept(this); } if (tf.parameterList.varargs) { if (tf.parameterList.parameters.dim && tf.parameterList.varargs == 1) buf.writestring(", "); buf.writestring("..."); } buf.writeByte(')'); } override void visit(AST.Parameter p) { debug (Debug_DtoH) mixin(traceVisit!p); ident = p.ident; { // Reference parameters can be forwarded const fwdStash = this.forwarding; this.forwarding = !!(p.storageClass & AST.STC.ref_); p.type.accept(this); this.forwarding = fwdStash; } if (p.storageClass & AST.STC.ref_) buf.writeByte('&'); buf.writeByte(' '); if (ident) // FIXME: Parameter is missing a Loc writeIdentifier(ident, Loc.initial, "parameter", true); ident = null; if (p.defaultArg) { //printf("%s %d\n", p.defaultArg.toChars, p.defaultArg.op); buf.writestring(" = "); printExpressionFor(p.type, p.defaultArg); } } /** * Prints `exp` as an expression of type `target` while inserting * appropriate code when implicit conversion does not translate * directly to C++, e.g. from an enum to its base type. * * Params: * target = the type `exp` is converted to * exp = the expression to print * isCtor = if `exp` is a ctor argument */ private void printExpressionFor(AST.Type target, AST.Expression exp, const bool isCtor = false) { /// Determines if a static_cast is required static bool needsCast(AST.Type target, AST.Expression exp) { // import std.stdio; // writefln("%s:%s: target = %s, type = %s (%s)", exp.loc.linnum, exp.loc.charnum, target, exp.type, exp.op); auto source = exp.type; // DotVarExp resolve conversions, e.g from an enum to its base type if (auto dve = exp.isDotVarExp()) source = dve.var.type; if (!source) // Defensively assume that the cast is required return true; // Conversions from enum class to base type require static_cast if (global.params.cplusplus >= CppStdRevision.cpp11 && source.isTypeEnum && !target.isTypeEnum) return true; return false; } // Slices are emitted as a special struct, hence we need to fix up // any expression initialising a slice variable/member if (auto ta = target.isTypeDArray()) { if (exp.isNullExp()) { if (isCtor) { // Don't emit, use default ctor } else if (global.params.cplusplus >= CppStdRevision.cpp11) { // Prefer initializer list buf.writestring("{}"); } else { // Write __d_dynamic_array() visit(ta); buf.writestring("()"); } return; } if (auto se = exp.isStringExp()) { // Rewrite as + pair optionally // wrapped in a initializer list/ctor call const initList = global.params.cplusplus >= CppStdRevision.cpp11; if (!isCtor) { if (initList) buf.writestring("{ "); else { visit(ta); buf.writestring("( "); } } buf.printf("%zu, ", se.len); visit(se); if (!isCtor) buf.writestring(initList ? " }" : " )"); return; } } else if (auto ce = exp.isCastExp()) { buf.writeByte('('); if (ce.to) ce.to.accept(this); else if (ce.e1.type) // Try the expression type with modifiers in case of cast(const) in templates ce.e1.type.castMod(ce.mod).accept(this); else // Fallback, not necessarily correct but the best we've got here target.accept(this); buf.writestring(") "); ce.e1.accept(this); } else if (needsCast(target, exp)) { buf.writestring("static_cast<"); target.accept(this); buf.writestring(">("); exp.accept(this); buf.writeByte(')'); } else { exp.accept(this); } } override void visit(AST.Expression e) { debug (Debug_DtoH) mixin(traceVisit!e); // Valid in most cases, others should be overriden below // to use the appropriate operators (:: and ->) buf.writestring(e.toString()); } override void visit(AST.UnaExp e) { debug (Debug_DtoH) mixin(traceVisit!e); buf.writestring(expToString(e.op)); e.e1.accept(this); } override void visit(AST.BinExp e) { debug (Debug_DtoH) mixin(traceVisit!e); e.e1.accept(this); buf.writeByte(' '); buf.writestring(expToString(e.op)); buf.writeByte(' '); e.e2.accept(this); } /// Translates operator `op` into the C++ representation private extern(D) static string expToString(const EXP op) { switch (op) with (EXP) { case identity: return "=="; case notIdentity: return "!="; default: return EXPtoString(op); } } override void visit(AST.VarExp e) { debug (Debug_DtoH) mixin(traceVisit!e); // Local members don't need another prefix and might've been renamed if (e.var.isThis()) { includeSymbol(e.var); writeIdentifier(e.var, true); } else writeFullName(e.var); } /// Partially prints the FQN including parent aggregates private void printPrefix(AST.Dsymbol var) { if (!var || var is adparent || var.isModule()) return; writeFullName(var); buf.writestring("::"); } override void visit(AST.CallExp e) { debug (Debug_DtoH) mixin(traceVisit!e); // Dereferencing function pointers requires additional braces: (*f)(args) const isFp = e.e1.isPtrExp(); if (isFp) buf.writeByte('('); else if (e.f) includeSymbol(outermostSymbol(e.f)); e.e1.accept(this); if (isFp) buf.writeByte(')'); assert(e.arguments); buf.writeByte('('); foreach (i, arg; *e.arguments) { if (i) buf.writestring(", "); arg.accept(this); } buf.writeByte(')'); } override void visit(AST.DotVarExp e) { debug (Debug_DtoH) mixin(traceVisit!e); if (auto sym = symbolFromType(e.e1.type)) includeSymbol(outermostSymbol(sym)); // Accessing members through a pointer? if (auto pe = e.e1.isPtrExp) { pe.e1.accept(this); buf.writestring("->"); } else { e.e1.accept(this); buf.writeByte('.'); } // Should only be used to access non-static members assert(e.var.isThis()); writeIdentifier(e.var, true); } override void visit(AST.DotIdExp e) { debug (Debug_DtoH) mixin(traceVisit!e); e.e1.accept(this); buf.writestring("::"); buf.writestring(e.ident.toChars()); } override void visit(AST.ScopeExp e) { debug (Debug_DtoH) mixin(traceVisit!e); // Usually a template instance in a TemplateDeclaration if (auto ti = e.sds.isTemplateInstance()) visitTi(ti); else writeFullName(e.sds); } override void visit(AST.NullExp e) { debug (Debug_DtoH) mixin(traceVisit!e); if (global.params.cplusplus >= CppStdRevision.cpp11) buf.writestring("nullptr"); else buf.writestring("NULL"); } override void visit(AST.ArrayLiteralExp e) { debug (Debug_DtoH) mixin(traceVisit!e); buf.writestring("arrayliteral"); } override void visit(AST.StringExp e) { debug (Debug_DtoH) mixin(traceVisit!e); if (e.sz == 2) buf.writeByte('u'); else if (e.sz == 4) buf.writeByte('U'); buf.writeByte('"'); foreach (i; 0 .. e.len) { writeCharLiteral(*buf, e.getCodeUnit(i)); } buf.writeByte('"'); } override void visit(AST.RealExp e) { debug (Debug_DtoH) mixin(traceVisit!e); import dmd.root.ctfloat : CTFloat; // Special case NaN and Infinity because floatToBuffer // uses D literals (`nan` and `infinity`) if (CTFloat.isNaN(e.value)) { buf.writestring("NAN"); } else if (CTFloat.isInfinity(e.value)) { if (e.value < CTFloat.zero) buf.writeByte('-'); buf.writestring("INFINITY"); } else { import dmd.hdrgen; // Hex floating point literals were introduced in C++ 17 const allowHex = global.params.cplusplus >= CppStdRevision.cpp17; floatToBuffer(e.type, e.value, buf, allowHex); } } override void visit(AST.IntegerExp e) { debug (Debug_DtoH) mixin(traceVisit!e); visitInteger(e.toInteger, e.type); } /// Writes `v` as type `t` into `buf` private void visitInteger(dinteger_t v, AST.Type t) { debug (Debug_DtoH) mixin(traceVisit!t); switch (t.ty) { case AST.Tenum: auto te = cast(AST.TypeEnum)t; buf.writestring("("); enumToBuffer(te.sym); buf.writestring(")"); visitInteger(v, te.sym.memtype); break; case AST.Tbool: buf.writestring(v ? "true" : "false"); break; case AST.Tint8: buf.printf("%d", cast(byte)v); break; case AST.Tuns8: buf.printf("%uu", cast(ubyte)v); break; case AST.Tint16: buf.printf("%d", cast(short)v); break; case AST.Tuns16: case AST.Twchar: buf.printf("%uu", cast(ushort)v); break; case AST.Tint32: case AST.Tdchar: buf.printf("%d", cast(int)v); break; case AST.Tuns32: buf.printf("%uu", cast(uint)v); break; case AST.Tint64: buf.printf("%lldLL", v); break; case AST.Tuns64: buf.printf("%lluLLU", v); break; case AST.Tchar: if (v > 0x20 && v < 0x80) buf.printf("'%c'", cast(int)v); else buf.printf("%uu", cast(ubyte)v); break; default: //t.print(); assert(0); } } override void visit(AST.StructLiteralExp sle) { debug (Debug_DtoH) mixin(traceVisit!sle); const isUnion = sle.sd.isUnionDeclaration(); sle.sd.type.accept(this); buf.writeByte('('); foreach(i, e; *sle.elements) { if (i) buf.writestring(", "); auto vd = sle.sd.fields[i]; // Expression may be null for unspecified elements if (!e) e = findDefaultInitializer(vd); printExpressionFor(vd.type, e); // Only emit the initializer of the first union member if (isUnion) break; } buf.writeByte(')'); } /// Finds the default initializer for the given VarDeclaration private static AST.Expression findDefaultInitializer(AST.VarDeclaration vd) { if (vd._init && !vd._init.isVoidInitializer()) return AST.initializerToExpression(vd._init); else if (auto ts = vd.type.isTypeStruct()) { if (!ts.sym.noDefaultCtor && !ts.sym.isUnionDeclaration()) { // Generate a call to the default constructor that we've generated. auto sle = new AST.StructLiteralExp(Loc.initial, ts.sym, new AST.Expressions(0)); sle.type = vd.type; return sle; } else return vd.type.defaultInitLiteral(Loc.initial); } else return vd.type.defaultInitLiteral(Loc.initial); } static if (__VERSION__ < 2092) { private void ignored(const char* format, ...) nothrow { this.ignoredCounter++; import core.stdc.stdarg; if (!printIgnored) return; va_list ap; va_start(ap, format); buf.writestring("// Ignored "); buf.vprintf(format, ap); buf.writenl(); va_end(ap); } } else { /// Writes a formatted message into `buf` if `printIgnored` is true /// and increments `ignoredCounter` pragma(printf) private void ignored(const char* format, ...) nothrow { this.ignoredCounter++; import core.stdc.stdarg; if (!printIgnored) return; va_list ap; va_start(ap, format); buf.writestring("// Ignored "); buf.vprintf(format, ap); buf.writenl(); va_end(ap); } } /** * Determines whether `s` should be emitted. This requires that `sym` * - is `extern(C[++]`) * - is not instantiated from a template (visits the `TemplateDeclaration` instead) * * Params: * sym = the symbol * * Returns: whether `sym` should be emitted */ private bool shouldEmit(AST.Dsymbol sym) { import dmd.aggregate : ClassKind; debug (Debug_DtoH) { printf("[shouldEmitAndMarkVisited enter] %s\n", sym.toPrettyChars()); scope(exit) printf("[shouldEmitAndMarkVisited exit] %s\n", sym.toPrettyChars()); } // Template *instances* should not be emitted if (sym.isInstantiated()) return false; // Matching linkage (except extern(C) classes which don't make sense) if (linkage == LINK.cpp || (linkage == LINK.c && !sym.isClassDeclaration())) return true; // Check against the internal information which might be missing, e.g. inside of template declarations if (auto dec = sym.isDeclaration()) { const l = dec.resolvedLinkage(); return l == LINK.cpp || l == LINK.c; } if (auto ad = sym.isAggregateDeclaration()) return ad.classKind == ClassKind.cpp; return false; } /** * Determines whether `s` should be emitted. This requires that `sym` * - was not visited before * - is `extern(C[++]`) * - is not instantiated from a template (visits the `TemplateDeclaration` instead) * The result is cached in the visited nodes array. * * Params: * sym = the symbol * * Returns: whether `sym` should be emitted **/ private bool shouldEmitAndMarkVisited(AST.Dsymbol sym) { debug (Debug_DtoH) { printf("[shouldEmitAndMarkVisited enter] %s\n", sym.toPrettyChars()); scope(exit) printf("[shouldEmitAndMarkVisited exit] %s\n", sym.toPrettyChars()); } auto statePtr = (cast(void*) sym) in visited; // `sym` was already emitted or skipped and isn't required if (statePtr && (*statePtr || !mustEmit)) return false; // Template *instances* should not be emitted, forward to the declaration if (auto ti = sym.isInstantiated()) { auto td = findTemplateDeclaration(ti); assert(td); visit(td); return false; } // Required or matching linkage (except extern(C) classes which don't make sense) bool res = mustEmit || linkage == LINK.cpp || (linkage == LINK.c && !sym.isClassDeclaration()); if (!res) { // Check against the internal information which might be missing, e.g. inside of template declarations if (auto dec = sym.isDeclaration()) { const l = dec.resolvedLinkage(); res = (l == LINK.cpp || l == LINK.c); } } // Remember result for later calls if (statePtr) *statePtr = res; else visited[(cast(void*) sym)] = res; // Print a warning when the symbol is ignored for the first time // Might not be correct if it is required by symbol the is visited // AFTER the current node if (!statePtr && !res) ignored("%s %s because of linkage", sym.kind(), sym.toPrettyChars()); return res; } /** * Ensures that `sym` is declared before the current position in `buf` by * either creating a forward reference in `fwdbuf` if possible or * calling `includeSymbol` to emit the entire declaration into `donebuf`. */ private void ensureDeclared(AST.Dsymbol sym) { auto par = sym.toParent2(); auto ed = sym.isEnumDeclaration(); // Eagerly include the symbol if we cannot create a valid forward declaration // Forwarding of scoped enums requires C++11 or above if (!forwarding || (par && !par.isModule()) || (ed && global.params.cplusplus < CppStdRevision.cpp11)) { // Emit the entire enclosing declaration if any includeSymbol(outermostSymbol(sym)); return; } auto ti = sym.isInstantiated(); auto td = ti ? findTemplateDeclaration(ti) : null; auto check = cast(void*) (td ? td : sym); // Omit redundant fwd-declaration if we already emitted the entire declaration if (visited.get(check, false)) return; // Already created a fwd-declaration? if (check in forwarded) return; forwarded[check] = true; // Print template<...> if (ti) { auto bufSave = buf; buf = fwdbuf; printTemplateParams(td); buf = bufSave; } // Determine the kind of symbol that is forwared: struct, ... const(char)* kind; if (auto ad = sym.isAggregateDeclaration()) { // Look for extern(C++, class) if (ad.cppmangle == CPPMANGLE.def) kind = ad.kind(); else if (ad.cppmangle == CPPMANGLE.asStruct) kind = "struct"; else kind = "class"; } else if (ed) { // Only called from enumToBuffer, so should always be emitted as an actual enum kind = "enum class"; } else kind = sym.kind(); // Should be unreachable but just to be sure fwdbuf.writestring(kind); fwdbuf.writeByte(' '); fwdbuf.writestring(sym.toChars()); fwdbuf.writestringln(";"); } /** * Writes the qualified name of `sym` into `buf` including parent * symbols and template parameters. * * Params: * sym = the symbol * mustInclude = whether sym may not be forward declared */ private void writeFullName(AST.Dsymbol sym, const bool mustInclude = false) in { assert(sym); assert(sym.ident, sym.toString()); // Should never be called directly with a TI, only onemember assert(!sym.isTemplateInstance(), sym.toString()); } do { debug (Debug_DtoH) { printf("[writeFullName enter] %s\n", sym.toPrettyChars()); scope(exit) printf("[writeFullName exit] %s\n", sym.toPrettyChars()); } // Explicit `pragma(mangle, "` overrides the declared name if (auto mn = getMangleOverride(sym)) return buf.writestring(mn); /// Checks whether `sym` is nested in `par` and hence doesn't need the FQN static bool isNestedIn(AST.Dsymbol sym, AST.Dsymbol par) { while (par) { if (sym is par) return true; par = par.toParent(); } return false; } AST.TemplateInstance ti; bool nested; // Check if the `sym` is nested into another symbol and hence requires `Parent::sym` if (auto par = sym.toParent()) { // toParent() yields the template instance if `sym` is the onemember of a TI ti = par.isTemplateInstance(); // Skip the TI because Foo!int.Foo is folded into Foo if (ti) par = ti.toParent(); // Prefix the name with any enclosing declaration // Stop at either module or enclosing aggregate nested = !par.isModule(); if (nested && !isNestedIn(par, adparent)) { writeFullName(par, true); buf.writestring("::"); } } if (!nested) { // Cannot forward the symbol when called recursively // for a nested symbol if (mustInclude) includeSymbol(sym); else ensureDeclared(sym); } if (ti) visitTi(ti); else buf.writestring(sym.ident.toString()); } /// Returns: Explicit mangling for `sym` if present extern(D) static const(char)[] getMangleOverride(const AST.Dsymbol sym) { if (auto decl = sym.isDeclaration()) return decl.mangleOverride; return null; } } /// Namespace for identifiers used to represent special enums in C++ struct DMDType { __gshared Identifier c_long; __gshared Identifier c_ulong; __gshared Identifier c_longlong; __gshared Identifier c_ulonglong; __gshared Identifier c_long_double; __gshared Identifier c_char; __gshared Identifier c_wchar_t; __gshared Identifier c_complex_float; __gshared Identifier c_complex_double; __gshared Identifier c_complex_real; static void _init() { c_long = Identifier.idPool("__c_long"); c_ulong = Identifier.idPool("__c_ulong"); c_longlong = Identifier.idPool("__c_longlong"); c_ulonglong = Identifier.idPool("__c_ulonglong"); c_long_double = Identifier.idPool("__c_long_double"); c_wchar_t = Identifier.idPool("__c_wchar_t"); c_char = Identifier.idPool("__c_char"); c_complex_float = Identifier.idPool("__c_complex_float"); c_complex_double = Identifier.idPool("__c_complex_double"); c_complex_real = Identifier.idPool("__c_complex_real"); } } /// Initializes all data structures used by the header generator void initialize() { __gshared bool initialized; if (!initialized) { initialized = true; DMDType._init(); } } /// Writes `#if ` into the supplied buffer void hashIf(ref OutBuffer buf, string content) { buf.writestring("#if "); buf.writestringln(content); } /// Writes `#elif ` into the supplied buffer void hashElIf(ref OutBuffer buf, string content) { buf.writestring("#elif "); buf.writestringln(content); } /// Writes `#endif` into the supplied buffer void hashEndIf(ref OutBuffer buf) { buf.writestringln("#endif"); } /// Writes `#define ` into the supplied buffer void hashDefine(ref OutBuffer buf, string content) { buf.writestring("#define "); buf.writestringln(content); } /// Writes `#include ` into the supplied buffer void hashInclude(ref OutBuffer buf, string content) { buf.writestring("#include "); buf.writestringln(content); } /// Determines whether `ident` is a reserved keyword in C++ /// Returns: the kind of keyword or `null` const(char*) keywordClass(const Identifier ident) { if (!ident) return null; const name = ident.toString(); switch (name) { // C++ operators case "and": case "and_eq": case "bitand": case "bitor": case "compl": case "not": case "not_eq": case "or": case "or_eq": case "xor": case "xor_eq": return "special operator in C++"; // C++ keywords case "_Complex": case "const_cast": case "delete": case "dynamic_cast": case "explicit": case "friend": case "inline": case "mutable": case "namespace": case "operator": case "register": case "reinterpret_cast": case "signed": case "static_cast": case "typedef": case "typename": case "unsigned": case "using": case "virtual": case "volatile": return "keyword in C++"; // Common macros imported by this header // stddef.h case "offsetof": case "NULL": return "default macro in C++"; // C++11 keywords case "alignas": case "alignof": case "char16_t": case "char32_t": case "constexpr": case "decltype": case "noexcept": case "nullptr": case "static_assert": case "thread_local": case "wchar_t": if (global.params.cplusplus >= CppStdRevision.cpp11) return "keyword in C++11"; return null; // C++20 keywords case "char8_t": case "consteval": case "constinit": // Concepts-related keywords case "concept": case "requires": // Coroutines-related keywords case "co_await": case "co_yield": case "co_return": if (global.params.cplusplus >= CppStdRevision.cpp20) return "keyword in C++20"; return null; default: // Identifiers starting with __ are reserved if (name.length >= 2 && name[0..2] == "__") return "reserved identifier in C++"; return null; } } /// Finds the outermost symbol if `sym` is nested. /// Returns `sym` if it appears at module scope ASTCodegen.Dsymbol outermostSymbol(ASTCodegen.Dsymbol sym) { assert(sym); while (true) { auto par = sym.toParent(); if (!par || par.isModule()) return sym; sym = par; } } /// Fetches the symbol for user-defined types from the type `t` /// if `t` is either `TypeClass`, `TypeStruct` or `TypeEnum` ASTCodegen.Dsymbol symbolFromType(ASTCodegen.Type t) { if (auto tc = t.isTypeClass()) return tc.sym; if (auto ts = t.isTypeStruct()) return ts.sym; if (auto te = t.isTypeEnum()) return te.sym; return null; } /** * Searches `sym` for a member with the given name. * * This method usually delegates to `Dsymbol.search` but might also * manually check the members if the symbol did not receive semantic * analysis. * * Params: * sym = symbol to search * name = identifier of the requested symbol * * Returns: the symbol or `null` if not found */ ASTCodegen.Dsymbol findMember(ASTCodegen.Dsymbol sym, Identifier name) { if (auto mem = sym.search(Loc.initial, name, ASTCodegen.IgnoreErrors)) return mem; // search doesn't work for declarations inside of uninstantiated // `TemplateDeclaration`s due to the missing symtab. if (sym.semanticRun >= ASTCodegen.PASS.semanticdone) return null; // Manually check the members if present auto sds = sym.isScopeDsymbol(); if (!sds || !sds.members) return null; /// Recursively searches for `name` without entering nested aggregates, ... static ASTCodegen.Dsymbol search(ASTCodegen.Dsymbols* members, Identifier name) { foreach (mem; *members) { if (mem.ident == name) return mem; // Look inside of private:, ... if (auto ad = mem.isAttribDeclaration()) { if (auto s = search(ad.decl, name)) return s; } } return null; } return search(sds.members, name); } debug (Debug_DtoH) { /// Generates code to trace the entry and exit of the enclosing `visit` function string traceVisit(alias node)() { const type = typeof(node).stringof; const method = __traits(hasMember, node, "toPrettyChars") ? "toPrettyChars" : "toChars"; const arg = __traits(identifier, node) ~ '.' ~ method; return `printf("[` ~ type ~ ` enter] %s\n", ` ~ arg ~ `()); scope(exit) printf("[` ~ type ~ ` exit] %s\n", ` ~ arg ~ `());`; } }