| /** |
| * 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 `<global.params.cxxhdrdir>/<global.params.cxxhdrfile>` |
| * 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.cxxhdr.fullOutput) |
| 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, "<assert.h>"); |
| hashInclude(buf, "<math.h>"); |
| hashInclude(buf, "<stddef.h>"); |
| hashInclude(buf, "<stdint.h>"); |
| // buf.writestring(buf, "#include <stdio.h>\n"); |
| // buf.writestring("#include <string.h>\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<typename T> |
| 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 <class T> |
| 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.cxxhdr.name 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.cxxhdr.dir, global.params.cxxhdr.name); |
| 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.cxxhdr.fullOutput; |
| } |
| |
| /** |
| * 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 <alias_> = <sym.ident>` 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 <alias_> = `<sym>` |
| 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); |
| if (adparent) |
| { |
| if (tf && (tf.isConst() || tf.isImmutable())) |
| buf.writestring(" const"); |
| if (global.params.cplusplus >= CppStdRevision.cpp11) |
| { |
| if (fd.vtblIndex != -1 && !(adparent.storage_class & AST.STC.final_) && fd.isFinalFunc()) |
| buf.writestring(" final"); |
| if (fd.isOverride()) |
| buf.writestring(" override"); |
| } |
| if (fd.isAbstract()) |
| buf.writestring(" = 0"); |
| else if (global.params.cplusplus >= CppStdRevision.cpp11 && fd.isDisabled()) |
| 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()) |
| { |
| assert(vd.aliassym); |
| vd.toAlias().accept(this); |
| 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 (vd; sd.fields) |
| { |
| if (!memberField(vd) || vd.overlapped) |
| 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 (vd; sd.fields) |
| { |
| if (!memberField(vd) || vd.overlapped) |
| 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 (vd; sd.fields) |
| { |
| if (!memberField(vd) || vd.overlapped) |
| 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"); |
| } |
| |
| override void visit(AST.TupleDeclaration tup) |
| { |
| debug (Debug_DtoH) mixin(traceVisit!tup); |
| |
| tup.foreachVar((s) { s.accept(this); }); |
| } |
| |
| /** |
| * 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) => <Aggregate name> |
| 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_ | AST.STC.out_)) |
| 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<TYPE>() |
| visit(ta); |
| buf.writestring("()"); |
| } |
| return; |
| } |
| |
| if (auto se = exp.isStringExp()) |
| { |
| // Rewrite as <length> + <literal> 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) <some aggregate> |
| 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, "<some string>` 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<int> |
| 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 |
| <
|