From ecf04f669f935a8e67089cafdfe2103601016dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis=20Mos=C4=81ns?= Date: Sat, 14 Mar 2026 23:33:07 +0200 Subject: [PATCH 1/4] Support `&=`, `^=`, `|=` for macros --- dstep/translator/MacroDefinition.d | 6 +++ dstep/translator/MacroParser.d | 70 ++++++++++++++++++++++++++++-- dstep/translator/TypeInference.d | 3 ++ tests/unit/MacroTranslTests.d | 34 +++++++++++++++ 4 files changed, 109 insertions(+), 4 deletions(-) diff --git a/dstep/translator/MacroDefinition.d b/dstep/translator/MacroDefinition.d index 1775c982..b943983f 100644 --- a/dstep/translator/MacroDefinition.d +++ b/dstep/translator/MacroDefinition.d @@ -343,6 +343,9 @@ string translate(Expression expression, ExpressionContext context) translateBinaryOperator!OrExpr, translateBinaryOperator!LogicalAndExpr, translateBinaryOperator!LogicalOrExpr, + translateBinaryOperator!AndAssignExpr, + translateBinaryOperator!XorAssignExpr, + translateBinaryOperator!OrAssignExpr, delegate string(CondExpr condExpr) { return format( @@ -403,6 +406,9 @@ void guessParamTypes(Expression expression, ref ExprType[string] params, ExprTyp guessBinaryOperator!OrExpr, guessBinaryOperator!LogicalAndExpr, guessBinaryOperator!LogicalOrExpr, + guessBinaryOperator!AndAssignExpr, + guessBinaryOperator!XorAssignExpr, + guessBinaryOperator!OrAssignExpr, delegate void(CondExpr condExpr) { condExpr.expr.guessParamTypes(params, type); diff --git a/dstep/translator/MacroParser.d b/dstep/translator/MacroParser.d index 8f3686e5..c4bf72a7 100644 --- a/dstep/translator/MacroParser.d +++ b/dstep/translator/MacroParser.d @@ -480,6 +480,15 @@ class LogicalAndExpr : BinaryExpr class LogicalOrExpr : BinaryExpr { } +class AndAssignExpr : BinaryExpr +{ } + +class XorAssignExpr : BinaryExpr +{ } + +class OrAssignExpr : BinaryExpr +{ } + class CondExpr { Expression expr; @@ -530,6 +539,9 @@ alias Expression = Algebraic!( OrExpr, LogicalAndExpr, LogicalOrExpr, + AndAssignExpr, + XorAssignExpr, + OrAssignExpr, CondExpr); Expression debraced(Expression expression) @@ -1032,6 +1044,56 @@ Expression parseCondExpr(ref Token[] tokens, Cursor[string] table, bool defined) return expr; } +Expression parseAssignExpr(ref Token[] tokens, Cursor[string] table, bool defined) +{ + auto local = tokens; + + Expression expr = parseCondExpr(local, table, defined); + + if (!expr.hasValue) + return Expression.init; + + string op; + + if (accept!("&=", "^=", "|=")(local, op, TokenKind.punctuation)) + { + Expression rhs = parseAssignExpr(local, table, defined); + + if (!rhs.hasValue) + return Expression.init; + + tokens = local; + + if (op == "&=") + { + auto result = new AndAssignExpr; + result.left = expr; + result.right = rhs; + result.operator = op; + return Expression(result); + } + else if (op == "^=") + { + auto result = new XorAssignExpr; + result.left = expr; + result.right = rhs; + result.operator = op; + return Expression(result); + } + else + { + auto result = new OrAssignExpr; + result.left = expr; + result.right = rhs; + result.operator = op; + return Expression(result); + } + } + + tokens = local; + return expr; +} + bool parseBasicSpecifier(ref Token[] tokens, ref string spelling, Cursor[string] table) { import std.meta : AliasSeq; @@ -1425,10 +1487,10 @@ Expression parseExpr(ref Token[] tokens, Cursor[string] table, bool defined) if (concatExpr.hasValue) return concatExpr; - auto condExpr = parseCondExpr(tokens, table, defined); + auto assignExpr = parseAssignExpr(tokens, table, defined); - if (condExpr.hasValue) - return condExpr; + if (assignExpr.hasValue) + return assignExpr; return Expression.init; } @@ -1437,7 +1499,7 @@ Expression parseExpr(ref Token[] tokens, bool defined) { Cursor[string] table; - return parseCondExpr(tokens, table, defined); + return parseAssignExpr(tokens, table, defined); } Expression parseEnumMember(Token[] tokens, Cursor[string] table) diff --git a/dstep/translator/TypeInference.d b/dstep/translator/TypeInference.d index 65934f16..e0efb559 100644 --- a/dstep/translator/TypeInference.d +++ b/dstep/translator/TypeInference.d @@ -229,6 +229,9 @@ InferredType inferExpressionType(Expression expression) inferBinaryOperator!OrExpr, inferBinaryOperator!LogicalAndExpr, inferBinaryOperator!LogicalOrExpr, + inferBinaryOperator!AndAssignExpr, + inferBinaryOperator!XorAssignExpr, + inferBinaryOperator!OrAssignExpr, delegate InferredType(CondExpr condExpr) { return commonType( diff --git a/tests/unit/MacroTranslTests.d b/tests/unit/MacroTranslTests.d index 6a7cb3c3..3026b72b 100644 --- a/tests/unit/MacroTranslTests.d +++ b/tests/unit/MacroTranslTests.d @@ -841,3 +841,37 @@ extern (D) auto fun(T)(auto ref T a) } D"); } + +// Translate compound assignment operators &=, ^=, |=. +unittest +{ + assertTME("#define FOO(a, b) a &= b", "a &= b"); + assertTME("#define FOO(a, b) a ^= b", "a ^= b"); + assertTME("#define FOO(a, b) a |= b", "a |= b"); +} + +// Compound assignment operators are right-associative. +unittest +{ + assertTME("#define FOO(a, b, c) a &= b |= c", "a &= b |= c"); + assertTME("#define FOO(a, b, c) a ^= b &= c", "a ^= b &= c"); + assertTME("#define FOO(a, b, c) a |= b ^= c", "a |= b ^= c"); + assertTME("#define FOO(a, b, c, d) a &= b |= c ^= d", "a &= b |= c ^= d"); +} + +// Compound assignment has lower precedence than conditional ?:. +unittest +{ + assertTME("#define FOO(a, b, c, d) a &= b ? c : d", "a &= b ? c : d"); + assertTME("#define FOO(a, b, c, d) a ^= b ? c : d", "a ^= b ? c : d"); + assertTME("#define FOO(a, b, c, d) a |= b ? c : d", "a |= b ? c : d"); +} + +// Compound assignment mixed with other operators. +unittest +{ + assertTME("#define FOO(a, b, c) a &= b & !c", "a &= b & !c"); + assertTME("#define FOO(a, b, c) a ^= ~b + c", "a ^= ~b + c"); + assertTME("#define FOO(a, b, c, d) d &= a |= b && c", "d &= a |= b && c"); + assertTME("#define FOO(a, b, c, d) d ^= a ? ~c % d : c * ~d ^ -(b ? c & d : d) | c", "d ^= a ? ~c % d : c * ~d ^ -(b ? c & d : d) | c"); +} From 1bcd1970fa9b1293c3e820aa0f48ae3da8b51ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis=20Mos=C4=81ns?= Date: Wed, 11 Mar 2026 22:15:10 +0200 Subject: [PATCH 2/4] Comment out compiler builtins Consider C header like: ``` #define va_copy __builtin_va_copy #define ALIGNOF(TYPE) _Alignof (TYPE) #define __unused __attribute__((__unused__)) #define __noreturn __attribute__((__noreturn__)) #define STATIC_ASSERT _Static_assert #define CONST const #define STATIC static #define __volatile volatile ``` This commit implements commenting out such compiler builtins with `// FIXME:` prefix. --- dstep/translator/MacroDefinition.d | 26 ++++++++++++++++++++++---- tests/unit/MacroTranslTests.d | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/dstep/translator/MacroDefinition.d b/dstep/translator/MacroDefinition.d index b943983f..bb3a0254 100644 --- a/dstep/translator/MacroDefinition.d +++ b/dstep/translator/MacroDefinition.d @@ -528,6 +528,7 @@ bool translateFunctAlias( { import std.algorithm.comparison : equal; import std.algorithm.iteration : map; + import std.algorithm.searching : startsWith; CallExpr* expr = definition.expr.peek!CallExpr; auto expressionContext = ExpressionContext.make(context); @@ -560,11 +561,17 @@ bool translateFunctAlias( .map!(a => a.translate(expressionContext)))) { version (D1) - enum fmt = "alias %2$s %1$s;"; + string fmt = "alias %2$s %1$s;"; else - enum fmt = "alias %1$s = %2$s;"; + string fmt = "alias %1$s = %2$s;"; - output.singleLine(fmt, definition.spelling, ident.spelling); + // Handle compiler builtins (eg. __builtin_unreachable) + string value = ident.spelling; + if (value.startsWith("__builtin_") || value == "_Alignof") + { + fmt = "// FIXME: " ~ fmt; + } + output.singleLine(fmt, definition.spelling, value); return true; } } @@ -598,6 +605,8 @@ void translateMacroDefinitionAliasOrConst( Context context, TypedMacroDefinition definition) { + import std.algorithm.searching : startsWith, canFind; + auto expressionContext = ExpressionContext.make(context); string formatString; @@ -618,10 +627,19 @@ void translateMacroDefinitionAliasOrConst( formatString = "enum %s = %s;"; } + string value = debraced.translate(expressionContext); + if (["const", "static", "volatile", "_Static_assert"].canFind(value) || + // Handle compiler builtins + value.startsWith("__builtin_") || + value.startsWith("__attribute__")) + { + formatString = "// FIXME: " ~ formatString; + } + output.singleLine( formatString, definition.definition.spelling, - debraced.translate(expressionContext)); + value); expressionContext.elevateImports(); } diff --git a/tests/unit/MacroTranslTests.d b/tests/unit/MacroTranslTests.d index 3026b72b..53e9dac0 100644 --- a/tests/unit/MacroTranslTests.d +++ b/tests/unit/MacroTranslTests.d @@ -875,3 +875,25 @@ unittest assertTME("#define FOO(a, b, c, d) d &= a |= b && c", "d &= a |= b && c"); assertTME("#define FOO(a, b, c, d) d ^= a ? ~c % d : c * ~d ^ -(b ? c & d : d) | c", "d ^= a ? ~c % d : c * ~d ^ -(b ? c & d : d) | c"); } +{ +// Comment out compiler builtins and language keywords. +unittest +{ + assertTMD(q"C +#define va_copy __builtin_va_copy +C", q"D +// FIXME: enum va_copy = __builtin_va_copy; +D"); + + assertTMD(q"C +#define fake _builtin_fake +C", q"D +enum fake = _builtin_fake; +D"); + + assertTMD(q"C +#define foo(x) __builtin_foo(x) +C", q"D +// FIXME: alias foo = __builtin_foo; +D"); +} From 2a3cdd25327758afdf5acd6f4f546a9757628b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis=20Mos=C4=81ns?= Date: Thu, 12 Mar 2026 23:19:41 +0200 Subject: [PATCH 3/4] Support custom types Consider C code like: ```c #define FIELD_PTR(Record, TYPE, Field) ((TYPE *)&((Record)->Field)) ``` Currently dstep is not able to parse such macro definition because cast's TYPE is not known at definition time but will be only known at usage time. So such definitions are just ignored. This commit implements support for custom types that doesn't need to be known at definition time. Note that it's not trivial to determine where (SYMBOL) is cast with a type or simply a parameter/value so after parsing we use `fixCasts` to try to fix these places with correct usage. --- dstep/translator/MacroDefinition.d | 8 + dstep/translator/MacroParser.d | 122 +++++++++++-- dstep/translator/Type.d | 14 +- dstep/translator/TypeInference.d | 54 ++++++ tests/functional/clang-c/Platform.d | 2 + tests/unit/MacroTranslTests.d | 88 ++++++++- tests/unit/TypeInferenceTests.d | 266 ++++++++++++++++++++++++++++ 7 files changed, 536 insertions(+), 18 deletions(-) diff --git a/dstep/translator/MacroDefinition.d b/dstep/translator/MacroDefinition.d index bb3a0254..dabf9659 100644 --- a/dstep/translator/MacroDefinition.d +++ b/dstep/translator/MacroDefinition.d @@ -328,6 +328,14 @@ string translate(Expression expression, ExpressionContext context) }, delegate string(CastExpr castExpr) { + if (!castExpr.type.isExposed && !castExpr.isParamType) + { + return format( + "%s(%s)", + castExpr.type.spelling, + castExpr.subexpr.debraced.translate(context)); + } + return format( "cast(%s) %s", translateType(context, Cursor.init, castExpr.type).makeString(), diff --git a/dstep/translator/MacroParser.d b/dstep/translator/MacroParser.d index c4bf72a7..09b75e03 100644 --- a/dstep/translator/MacroParser.d +++ b/dstep/translator/MacroParser.d @@ -414,6 +414,7 @@ class CastExpr { Type type; Expression subexpr; + bool isParamType; override string toString() { @@ -667,17 +668,24 @@ Expression parseTokenConcat(ref Token[] tokens) Expression parsePrimaryExpr(ref Token[] tokens, Cursor[string] table, bool defined) { + auto local = tokens; string spelling; - auto type = parseTypeName(tokens, table); + auto type = parseTypeName(local, table, false); - if (type.isValid) + if (type.isValid && type.isExposed) + { + tokens = local; return Expression(TypeIdentifier(type)); + } - if (accept(tokens, spelling, TokenKind.identifier)) + local = tokens; + if (accept(local, spelling, TokenKind.identifier) || + accept(local, spelling, TokenKind.keyword)) + { + tokens = local; return Expression(Identifier(spelling)); - - auto local = tokens; + } auto substrings = parseStringConcat(local); @@ -721,9 +729,9 @@ Expression parseArg(ref Token[] tokens, Cursor[string] table, bool defined) return expression; } - auto type = parseTypeName(local, table); + auto type = parseTypeName(local, table, false); - if (type.isValid) + if (type.isValid && type.isExposed) { tokens = local; expression = TypeIdentifier(type); @@ -855,7 +863,7 @@ Expression parseSizeofType(ref Token[] tokens, Cursor[string] table) if (acceptPunctuation!("(")(local)) { - Type type = parseTypeName(local, table); + Type type = parseTypeName(local, table, true); if (type.isValid && acceptPunctuation!(")")(local)) { @@ -976,7 +984,7 @@ Expression parseCastExpr(ref Token[] tokens, Cursor[string] table, bool defined) if (!accept!("(")(local, TokenKind.punctuation)) return parseUnaryExpr(tokens, table, defined); - Type type = parseTypeName(local, table); + Type type = parseTypeName(local, table, true); if (!type.isValid) return parseUnaryExpr(tokens, table, defined); @@ -1464,7 +1472,7 @@ bool parseAbstractDeclarator(ref Token[] tokens, ref Type type, Cursor[string] t return parsePointer(tokens, type); } -Type parseTypeName(ref Token[] tokens, Cursor[string] table) +Type parseTypeName(ref Token[] tokens, Cursor[string] table, bool anyType = false) { auto local = tokens; @@ -1473,6 +1481,9 @@ Type parseTypeName(ref Token[] tokens, Cursor[string] table) if (!parseSpecifierQualifierList(local, type, table)) return type; + if (anyType && acceptIdentifier(local, type.spelling)) + type.kind = CXTypeKind.unexposed; + parseAbstractDeclarator(local, type, table); tokens = local; @@ -1650,10 +1661,101 @@ MacroDefinition parsePartialMacroDefinition( else { tokens = local; + fixCasts(result); return result; } } +void fixCasts(MacroDefinition definition) +{ + if (!definition.aliasOrConst) + { + fixCasts(definition.params, definition.expr); + } +} + +void fixCasts(string[] params, ref Expression expr) +{ + import std.algorithm.searching : canFind; + + if (auto castExpr = expr.peek!CastExpr()) + { + if (!castExpr.type.isExposed()) + { + if (auto unaryExpr = castExpr.subexpr.peek!UnaryExpr()) + { + fixCasts(params, unaryExpr.subexpr); + if (unaryExpr.operator == "&") + { + auto newExpr = new AndExpr(); + newExpr.operator = unaryExpr.operator; + newExpr.left = Identifier(castExpr.type.spelling); + newExpr.right = unaryExpr.subexpr; + expr = newExpr; + } else if (["+", "-"].canFind(unaryExpr.operator)) + { + auto newExpr = new AddExpr(); + newExpr.operator = unaryExpr.operator; + newExpr.left = Identifier(castExpr.type.spelling); + newExpr.right = unaryExpr.subexpr; + expr = newExpr; + } else if (unaryExpr.operator == "*") + { + auto newExpr = new MulExpr(); + newExpr.operator = unaryExpr.operator; + newExpr.left = Identifier(castExpr.type.spelling); + newExpr.right = unaryExpr.subexpr; + expr = newExpr; + } + return; + } + else if (!params.canFind(castExpr.type.spelling)) + { + fixCasts(params, castExpr.subexpr); + + if (castExpr.subexpr.convertsTo!BinaryExpr) + { + BinaryExpr binaryExpr = castExpr.subexpr.get!(BinaryExpr); + auto newCallExpr = new CallExpr(); + newCallExpr.expr = Identifier(castExpr.type.spelling); + newCallExpr.args = [binaryExpr.left]; + binaryExpr.left = newCallExpr; + expr = castExpr.subexpr; + } + return; + } + } + castExpr.isParamType = params.canFind(castExpr.type.spelling); + fixCasts(params, castExpr.subexpr); + } else if (auto subExpr = expr.peek!SubExpr()) + { + fixCasts(params, subExpr.subexpr); + } else if (auto unaryExpr = expr.peek!UnaryExpr()) + { + fixCasts(params, unaryExpr.subexpr); + } else if (auto dotExpr = expr.peek!DotExpr()) + { + fixCasts(params, dotExpr.subexpr); + } else if (auto arrowExpr = expr.peek!ArrowExpr()) + { + fixCasts(params, arrowExpr.subexpr); + } else if (auto indexExpr = expr.peek!IndexExpr()) + { + fixCasts(params, indexExpr.subexpr); + fixCasts(params, indexExpr.index); + } else if (auto condExpr = expr.peek!CondExpr()) + { + fixCasts(params, condExpr.expr); + fixCasts(params, condExpr.left); + fixCasts(params, condExpr.right); + } else if (expr.hasValue && expr.convertsTo!(BinaryExpr)) + { + BinaryExpr binaryExpr = expr.get!BinaryExpr; + fixCasts(params, binaryExpr.left); + fixCasts(params, binaryExpr.right); + } +} + MacroDefinition[] parseMacroDefinitions(Range)( Range cursors, Cursor[string] types) diff --git a/dstep/translator/Type.d b/dstep/translator/Type.d index 3bd80b49..e47efea1 100644 --- a/dstep/translator/Type.d +++ b/dstep/translator/Type.d @@ -109,7 +109,7 @@ do default: result = translateType( context, - type.kind, + type, rewriteIdToObjcObject) .makeSourceNode(); } @@ -278,7 +278,7 @@ string translateTypedef(Context context, Type type) if (isDKeyword(type.spelling)) { - return type.spelling != translateType(context, type.canonical.kind) + return type.spelling != translateType(context, type.canonical) ? renameDKeyword(type.spelling) : type.spelling; } @@ -300,7 +300,7 @@ do if (declaration.isValid) return translateType(context, declaration, rewriteIdToObjcObject); else - return translateType(context, type.kind, rewriteIdToObjcObject) + return translateType(context, type, rewriteIdToObjcObject) .makeSourceNode(); } @@ -561,15 +561,15 @@ do return translateType(context, cursor, pointee); } -string translateType (Context context, CXTypeKind kind, bool rewriteIdToObjcObject = true) +string translateType (Context context, Type type, bool rewriteIdToObjcObject = true) { import std.conv; with (CXTypeKind) - switch (kind) + switch (type.kind) { case invalid: return ""; - case unexposed: return ""; + case unexposed: return type.spelling.empty ? "" : type.spelling; case void_: return "void"; case bool_: return "bool"; case charU: return ""; @@ -632,6 +632,6 @@ string translateType (Context context, CXTypeKind kind, bool rewriteIdToObjcObje case elaborated: return ""; - default: assert(0, "Unhandled type kind " ~ to!string(kind)); + default: assert(0, "Unhandled type kind " ~ to!string(type.kind)); } } diff --git a/dstep/translator/TypeInference.d b/dstep/translator/TypeInference.d index e0efb559..56d21791 100644 --- a/dstep/translator/TypeInference.d +++ b/dstep/translator/TypeInference.d @@ -70,6 +70,12 @@ void inferMacroSignature( void pass(T)(T operator) { } + void recurseBinary(T)(T expr) + { + inferMacroSignature(context, currentDefinition, expr.left); + inferMacroSignature(context, currentDefinition, expr.right); + } + bool isParamMeta(TypedMacroDefinition definition, string name) { import std.algorithm; @@ -122,10 +128,58 @@ void inferMacroSignature( if (updated) context.queue.insertBack(*definition); }, + delegate void(CastExpr castExpr) + { + import std.algorithm : countUntil; + + auto type = castExpr.type.isPointer ? castExpr.type.pointee : castExpr.type; + auto index = currentDefinition.params.countUntil(type.spelling); + + if (index != -1 && currentDefinition.signature.params[index].peek!Generic) + { + currentDefinition.signature.params[index] = Meta.init; + } + }, delegate void(SubExpr subExpr) { inferMacroSignature(context, currentDefinition, subExpr.subexpr); }, + delegate void(UnaryExpr unaryExpr) + { + inferMacroSignature(context, currentDefinition, unaryExpr.subexpr); + }, + delegate void(DotExpr dotExpr) + { + inferMacroSignature(context, currentDefinition, dotExpr.subexpr); + }, + delegate void(ArrowExpr arrowExpr) + { + inferMacroSignature(context, currentDefinition, arrowExpr.subexpr); + }, + delegate void(IndexExpr indexExpr) + { + inferMacroSignature(context, currentDefinition, indexExpr.subexpr); + inferMacroSignature(context, currentDefinition, indexExpr.index); + }, + delegate void(CondExpr condExpr) + { + inferMacroSignature(context, currentDefinition, condExpr.expr); + inferMacroSignature(context, currentDefinition, condExpr.left); + inferMacroSignature(context, currentDefinition, condExpr.right); + }, + recurseBinary!MulExpr, + recurseBinary!AddExpr, + recurseBinary!SftExpr, + recurseBinary!RelExpr, + recurseBinary!EqlExpr, + recurseBinary!AndExpr, + recurseBinary!XorExpr, + recurseBinary!OrExpr, + recurseBinary!LogicalAndExpr, + recurseBinary!LogicalOrExpr, + recurseBinary!AndAssignExpr, + recurseBinary!XorAssignExpr, + recurseBinary!OrAssignExpr, pass); } diff --git a/tests/functional/clang-c/Platform.d b/tests/functional/clang-c/Platform.d index 0ac640d8..4e1cc85a 100644 --- a/tests/functional/clang-c/Platform.d +++ b/tests/functional/clang-c/Platform.d @@ -17,3 +17,5 @@ extern (C): /* MSVC DLL import/export. */ +// FIXME: enum CINDEX_DEPRECATED = __attribute__(deprecated); + diff --git a/tests/unit/MacroTranslTests.d b/tests/unit/MacroTranslTests.d index 53e9dac0..55738d1f 100644 --- a/tests/unit/MacroTranslTests.d +++ b/tests/unit/MacroTranslTests.d @@ -875,7 +875,7 @@ unittest assertTME("#define FOO(a, b, c, d) d &= a |= b && c", "d &= a |= b && c"); assertTME("#define FOO(a, b, c, d) d ^= a ? ~c % d : c * ~d ^ -(b ? c & d : d) | c", "d ^= a ? ~c % d : c * ~d ^ -(b ? c & d : d) | c"); } -{ + // Comment out compiler builtins and language keywords. unittest { @@ -897,3 +897,89 @@ C", q"D // FIXME: alias foo = __builtin_foo; D"); } + +// Translate unknown identifier as function call with multiple args. +unittest +{ + assertTMD(q"C +#define FOO(a) (func)((bar)(a), (baz)(a)) +C", q"D +extern (D) auto FOO(T)(auto ref T a) +{ + return func(bar(a), baz(a)); +} +D"); +} + +unittest +{ + assertTMD(q"C +#define NEG(a) (func)(-(a)) +C", q"D +extern (D) auto NEG(T)(auto ref T a) +{ + return func(-a); +} +D"); +} + +// Translate unknown identifier as function call. +unittest +{ + assertTMD(q"C +#define FOO(a, b) (func)((baz)(~a) & (!b) + 3 * sizeof((b))) +C", q"D +extern (D) auto FOO(T0, T1)(auto ref T0 a, auto ref T1 b) +{ + return func(baz(~a) & (!b) + 3 * (b).sizeof); +} +D"); +} + +// Translate macro argument as type cast. +unittest +{ + assertTMD(q"C +#define FIELD_PTR(Record, TYPE, Field) ((TYPE *)&((Record)->Field)) +C", q"D +extern (D) auto FIELD_PTR(TYPE, T0, T2)(auto ref T0 Record, auto ref T2 Field) +{ + return cast(TYPE*) &(Record.Field); +} +D"); + + assertTMD(q"C +#define mycast(x,y) (x)y +C", q"D +extern (D) auto mycast(x, T)(auto ref T y) +{ + return cast(x) y; +} +D"); +} + +// Translate macro argument as type cast in conditional expression. +unittest +{ + assertTMD(q"C +#define VA_ARG(Marker, TYPE) ((sizeof(TYPE) < sizeof(UINTN)) ? (TYPE)(__builtin_va_arg(Marker, UINTN)) : (TYPE)(__builtin_va_arg(Marker, TYPE))) +C", q"D +extern (D) auto VA_ARG(TYPE, T)(auto ref T Marker) +{ + return (TYPE.sizeof < UINTN.sizeof) ? cast(TYPE) __builtin_va_arg(Marker, UINTN) : cast(TYPE) __builtin_va_arg(Marker, TYPE); +} +D"); +} + +// Translate unknown identifier as function call with complex arguments. +unittest +{ + assertTMD(q"C +#define FOO(a) (func)(~(a.d) ? (b[1]) : (((c) + (1)) * ((2) - (c)))) +C", q"D +extern (D) auto FOO(T)(auto ref T a) +{ + return func(~a.d ? (b[1]) : ((c + (1)) * ((2) - c))); +} +D"); +} diff --git a/tests/unit/TypeInferenceTests.d b/tests/unit/TypeInferenceTests.d index b5604773..e0e042dd 100644 --- a/tests/unit/TypeInferenceTests.d +++ b/tests/unit/TypeInferenceTests.d @@ -215,3 +215,269 @@ enum ENUM2 = QUX!int('c', 2); D"); } + +unittest +{ + assertTranslates(q"C +#define XOR(x) x ^ 0xFF +C", q"D +extern (C): + +extern (D) auto XOR(T)(auto ref T x) +{ + return x ^ 0xFF; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define LAND(x, y) x && y +C", q"D +extern (C): + +extern (D) auto LAND(T0, T1)(auto ref T0 x, auto ref T1 y) +{ + return x && y; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define ADD_CAST(x, y, z) ((x) + (z)y) +C", q"D +extern (C): + +extern (D) auto ADD_CAST(z, T0, T1)(auto ref T0 x, auto ref T1 y) +{ + return x + cast(z) y; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define UNARY_CAST(x, z) ~(z)(x) +C", q"D +extern (C): + +extern (D) auto UNARY_CAST(z, T)(auto ref T x) +{ + return ~cast(z) x; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define DOT_CAST(x, z) ((z)x).field +C", q"D +extern (C): + +extern (D) auto DOT_CAST(z, T)(auto ref T x) +{ + return (cast(z) x).field; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define ARROW_CAST(x, z) ((z)x)->field +C", q"D +extern (C): + +extern (D) auto ARROW_CAST(z, T)(auto ref T x) +{ + return (cast(z) x).field; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define INDEX_CAST(arr, TYPE, i) arr[(TYPE)i] +C", q"D +extern (C): + +extern (D) auto INDEX_CAST(TYPE, T0, T2)(auto ref T0 arr, auto ref T2 i) +{ + return arr[cast(TYPE) i]; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define SFT_CAST(TYPE, x) (TYPE)x << 1 +C", q"D +extern (C): + +extern (D) auto SFT_CAST(TYPE, T)(auto ref T x) +{ + return cast(TYPE) x << 1; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define REL_CAST(TYPE, x) (TYPE)x < 10 +C", q"D +extern (C): + +extern (D) auto REL_CAST(TYPE, T)(auto ref T x) +{ + return cast(TYPE) x < 10; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define EQL_CAST(TYPE, x) (TYPE)x == 0 +C", q"D +extern (C): + +extern (D) auto EQL_CAST(TYPE, T)(auto ref T x) +{ + return cast(TYPE) x == 0; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define MUL_CAST(TYPE, x) (TYPE)x * 2 +C", q"D +extern (C): + +extern (D) auto MUL_CAST(TYPE, T)(auto ref T x) +{ + return cast(TYPE) x * 2; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define AND_CAST(TYPE, x) (TYPE)x & 0xFF +C", q"D +extern (C): + +extern (D) auto AND_CAST(TYPE, T)(auto ref T x) +{ + return cast(TYPE) x & 0xFF; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define XOR_CAST(TYPE, x) (TYPE)x ^ 0xFF +C", q"D +extern (C): + +extern (D) auto XOR_CAST(TYPE, T)(auto ref T x) +{ + return cast(TYPE) x ^ 0xFF; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define OR_CAST(TYPE, x) (TYPE)x | 1 +C", q"D +extern (C): + +extern (D) auto OR_CAST(TYPE, T)(auto ref T x) +{ + return cast(TYPE) x | 1; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define LAND_CAST(TYPE, x) (TYPE)x && 1 +C", q"D +extern (C): + +extern (D) auto LAND_CAST(TYPE, T)(auto ref T x) +{ + return cast(TYPE) x && 1; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define LOR_CAST(TYPE, x) (TYPE)x || 0 +C", q"D +extern (C): + +extern (D) auto LOR_CAST(TYPE, T)(auto ref T x) +{ + return cast(TYPE) x || 0; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define AND_ASSIGN_CAST(TYPE, x) (TYPE)x &= 0xFF +C", q"D +extern (C): + +extern (D) auto AND_ASSIGN_CAST(TYPE, T)(auto ref T x) +{ + return cast(TYPE) x &= 0xFF; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define XOR_ASSIGN_CAST(TYPE, x) (TYPE)x ^= 0xFF +C", q"D +extern (C): + +extern (D) auto XOR_ASSIGN_CAST(TYPE, T)(auto ref T x) +{ + return cast(TYPE) x ^= 0xFF; +} +D"); +} + +unittest +{ + assertTranslates(q"C +#define OR_ASSIGN_CAST(TYPE, x) (TYPE)x |= 1 +C", q"D +extern (C): + +extern (D) auto OR_ASSIGN_CAST(TYPE, T)(auto ref T x) +{ + return cast(TYPE) x |= 1; +} +D"); +} From 795051eab591235f1d55f853be4f9df7f2d5d5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis=20Mos=C4=81ns?= Date: Sun, 15 Mar 2026 00:19:11 +0200 Subject: [PATCH 4/4] Support couple of `__*_TYPE__` types like `__SIZE_TYPE__` --- dstep/translator/MacroParser.d | 32 ++++++++++++++++++++++++++++++-- tests/unit/MacroParsingTests.d | 11 +++++++++++ tests/unit/MacroTranslTests.d | 22 ++++++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/dstep/translator/MacroParser.d b/dstep/translator/MacroParser.d index 09b75e03..459feb63 100644 --- a/dstep/translator/MacroParser.d +++ b/dstep/translator/MacroParser.d @@ -673,7 +673,7 @@ Expression parsePrimaryExpr(ref Token[] tokens, Cursor[string] table, bool defin auto type = parseTypeName(local, table, false); - if (type.isValid && type.isExposed) + if (type.isValid) { tokens = local; return Expression(TypeIdentifier(type)); @@ -1119,7 +1119,11 @@ bool parseBasicSpecifier(ref Token[] tokens, ref string spelling, Cursor[string] // "__complex__", TBD // "_Complex", TBD "bool", - "_Bool"); + "_Bool", + "__SIZE_TYPE__", + "__WCHAR_TYPE__", + "__WINT_TYPE__", + "__PTRDIFF_TYPE__"); return accept!(specifiers)(tokens, spelling); } @@ -1444,6 +1448,30 @@ bool basicSpecifierListToType(ref Type type, Set!string specifiers) return true; } + if (specifiers.contains("__SIZE_TYPE__")) + { + type = Type(CXTypeKind.unexposed, "size_t"); + return true; + } + + if (specifiers.contains("__WCHAR_TYPE__")) + { + type = Type(CXTypeKind.wChar, "wchar"); + return true; + } + + if (specifiers.contains("__WINT_TYPE__")) + { + type = Type(CXTypeKind.uShort, "ushort"); + return true; + } + + if (specifiers.contains("__PTRDIFF_TYPE__")) + { + type = Type(CXTypeKind.unexposed, "ptrdiff_t"); + return true; + } + return false; } diff --git a/tests/unit/MacroParsingTests.d b/tests/unit/MacroParsingTests.d index a77a1afc..dbb09d18 100644 --- a/tests/unit/MacroParsingTests.d +++ b/tests/unit/MacroParsingTests.d @@ -142,6 +142,17 @@ unittest localAssert("int long unsigned long", CXTypeKind.uLongLong); } +// Parse compiler types. +unittest +{ + alias localAssert = assertParsedTypeHasKind; + + localAssert("__SIZE_TYPE__", CXTypeKind.unexposed); + localAssert("__WCHAR_TYPE__", CXTypeKind.wChar); + localAssert("__WINT_TYPE__", CXTypeKind.uShort); + localAssert("__PTRDIFF_TYPE__", CXTypeKind.unexposed); +} + // Do not parse invalid types. unittest { diff --git a/tests/unit/MacroTranslTests.d b/tests/unit/MacroTranslTests.d index 55738d1f..befefb81 100644 --- a/tests/unit/MacroTranslTests.d +++ b/tests/unit/MacroTranslTests.d @@ -983,3 +983,25 @@ extern (D) auto FOO(T)(auto ref T a) } D"); } + +// Translate compiler type macros. +unittest +{ + assertTMD(q"C +#define FOO __SIZE_TYPE__ +C", q"D +alias FOO = size_t; +D"); + + assertTMD(q"C +#define FOO __WCHAR_TYPE__ +C", q"D +alias FOO = wchar; +D"); + + assertTMD(q"C +#define FOO __PTRDIFF_TYPE__ +C", q"D +alias FOO = ptrdiff_t; +D"); +}