From cf673138fadcaa14f5ee680c9d9c8fd89c957933 Mon Sep 17 00:00:00 2001 From: Dennis Korpel Date: Wed, 3 Jun 2026 14:16:55 +0200 Subject: [PATCH] Fix #23143 - importC: translate alias macros to aliases A `#define ID identifier` whose body is a single declared identifier (e.g. `#define mpz_init __gmpz_init` in gmp.h) was abandoned, leaving the macro name undefined in D. Translate such object-like macros to `alias ID = identifier;` so they resolve to the function/type/variable. The target must be declared in the same translation unit (or an earlier #define): aliasing an undefined identifier would error eagerly, which system headers trigger by #define-ing keywords to compiler builtins (e.g. `#define __func__ __FUNCTION__`, `#define __restrict restrict`). Self-referential macros (glibc's `#define stdin stdin`) are skipped, as they would otherwise shadow the declaration they name. On Windows the C library's `complex` macro resolves to a struct, which ImportC now aliases; druntime's `core.stdc.complex.complex` is an alias of `creal`, so importc_compare flags the expected size difference - add it to that test's known problems. Co-Authored-By: Claude Opus 4.8 --- compiler/src/dmd/cparse.d | 35 +++++++++++++++++++ compiler/test/compilable/imports/defines.c | 9 +++++ compiler/test/compilable/testdefines.d | 6 ++++ .../importc_compare/src/importc_compare.d | 4 +++ 4 files changed, 54 insertions(+) diff --git a/compiler/src/dmd/cparse.d b/compiler/src/dmd/cparse.d index 0ca40be20743..a17ef60d4c5c 100644 --- a/compiler/src/dmd/cparse.d +++ b/compiler/src/dmd/cparse.d @@ -5657,6 +5657,18 @@ final class CParser(AST) : Parser!AST addSym(tempdecl); } + // Set of all identifiers declared at module scope. (Not just translated macros like defineTab) + // Used to decide whether `#define ID identifier` can be turned into an alias. + // Aliasing an undefined identifier would error eagerly, which system headers trigger by + // #define-ing keywords to compiler builtins (e.g. `#define __func__ __FUNCTION__`). + bool[const(void)*] declaredIdents; + if (symbols) + { + foreach (sym; (*symbols)[]) + if (sym && sym.ident) + declaredIdents[cast(const(void)*) sym.ident] = true; + } + while (p < endp) { //printf("|%s|\n", p); @@ -5788,6 +5800,13 @@ final class CParser(AST) : Parser!AST * (no additional operators that could cause precedence issues). * Rewrite to a template function: * auto ID()() { return identifier(args); } + * Or: + * #define ID identifier + * where the macro body is a single identifier declared in this + * translation unit (e.g. a function alias macro). + * https://github.com/dlang/dmd/issues/23143 + * Rewrite to an alias: + * alias ID = identifier; */ assert(!params); // would be TOK.leftParenthesis eLatch.sawErrors = false; @@ -5796,6 +5815,22 @@ final class CParser(AST) : Parser!AST break; // abandon this #define if (token.value != TOK.endOfFile) // did not consume the entire line break; + if (auto ie = exp.isIdentifierExp()) + { + // Skip self-referential macros (e.g. glibc's `#define stdin stdin`) + if (id == ie.ident) + break; + if ((cast(const(void)*) ie.ident in declaredIdents) || + (cast(void*) ie.ident in defineTab)) + { + auto t2 = new AST.TypeIdentifier(ie.loc, ie.ident); + auto ad = new AST.AliasDeclaration(scanloc, id, t2); + addSym(ad); + ++p; + continue; + } + break; + } // Only allow bare function calls to avoid precedence issues. // E.g., `#define X FUNC(5)` is safe, but `#define X FUNC(5) + 1` // would have different semantics in D vs C when used as `X * 2`. diff --git a/compiler/test/compilable/imports/defines.c b/compiler/test/compilable/imports/defines.c index a805627db67c..1819821ac0a8 100644 --- a/compiler/test/compilable/imports/defines.c +++ b/compiler/test/compilable/imports/defines.c @@ -69,3 +69,12 @@ int pr16199c() #define BARE_CALL DOUBLE(5) // bare function-like macro call (no outer parens) #define PAREN_CALL (DOUBLE(5)) // parenthesized call (already works) #define WRAPPER(x) DOUBLE(x) // function-like macro calling another macro + +// https://github.com/dlang/dmd/issues/23143 - alias macros for declared identifiers +int __real_func(int x) { return x + 1; } +#define ALIAS_FUNC __real_func // alias macro to a function +typedef int real_int; +#define ALIAS_TYPE real_int // alias macro to a type +#define ALIAS_ENUM ABC // alias macro to a manifest constant +int self_ref; +#define self_ref self_ref // self-referential macro (cf. glibc stdin), must not alias diff --git a/compiler/test/compilable/testdefines.d b/compiler/test/compilable/testdefines.d index ba193d2fdadd..0484ac0bcbed 100644 --- a/compiler/test/compilable/testdefines.d +++ b/compiler/test/compilable/testdefines.d @@ -45,3 +45,9 @@ static assert(DOUBLE(5) == 10); static assert(BARE_CALL == 10); // bare call now works static assert(PAREN_CALL == 10); // parenthesized already worked static assert(WRAPPER(3) == 6); // function-like macro calling another macro + +// https://github.com/dlang/dmd/issues/23143 - alias macros for declared identifiers +static assert(ALIAS_FUNC(4) == 5); // alias to a function +static assert(is(ALIAS_TYPE == int)); // alias to a type +static assert(ALIAS_ENUM == 12); // alias to a manifest constant +static assert(is(typeof(self_ref) == int)); // self-referential macro stays the variable diff --git a/druntime/test/importc_compare/src/importc_compare.d b/druntime/test/importc_compare/src/importc_compare.d index bf3fd14172d8..23630a2e8b83 100644 --- a/druntime/test/importc_compare/src/importc_compare.d +++ b/druntime/test/importc_compare/src/importc_compare.d @@ -28,6 +28,10 @@ immutable string[] growingTypes = [ // List of problems, which are known and should only be treated as // warnings for now. immutable ErrorFilter[] knownProblems = [ + // ImportC now translates the C library's `complex` alias macro, which on Windows resolves + // to a `{ double; double; }` struct, while druntime's `complex` is an alias of `creal`. + // The representations genuinely differ, so this is an expected difference. + ErrorFilter("core.stdc.complex.complex", "", "Windows", 0, "https://github.com/dlang/dmd/issues/23143"), ErrorFilter("core.stdc.config.c_long_double", "", "Windows", 32, ""), ErrorFilter("core.stdc.config.c_complex_real", "", "Windows", 32, ""), // complex real is two long doubles so same for x86 Windows ErrorFilter("core.stdc.config.__c_complex_real", "", "Windows", 32, ""),