Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,16 @@ private void VisitFunctionDecl(FunctionDecl functionDecl)
var returnType = functionDecl.ReturnType;
var returnTypeName = GetRemappedTypeName(functionDecl, cxxRecordDecl, returnType, out var nativeTypeName);

var isCompoundAssignmentOperator = IsLatestCompoundAssignmentOperator(functionDecl, out var compoundAssignmentOperatorToken);

if (isCompoundAssignmentOperator)
{
// Project onto a C# 14 user-defined compound-assignment operator; the trailing
// `return this`/`*this` is dropped when the body is emitted below.
escapedName = $"operator {compoundAssignmentOperatorToken}";
returnTypeName = "void";
}

if (isManualImport && !_config.WithClasses.ContainsKey(name))
{
var parameters = functionDecl.Parameters;
Expand All @@ -690,11 +700,11 @@ private void VisitFunctionDecl(FunctionDecl functionDecl)
entryPoint = functionDecl.IsExternC ? GetCursorName(functionDecl) : functionDecl.Handle.Mangling.CString;
}

var needsReturnFixup = (cxxMethodDecl is not null) && NeedsReturnFixup(cxxMethodDecl);
var needsReturnFixup = !isCompoundAssignmentOperator && (cxxMethodDecl is not null) && NeedsReturnFixup(cxxMethodDecl);

var desc = new FunctionOrDelegateDesc {
AccessSpecifier = accessSpecifier,
NativeTypeName = nativeTypeName,
NativeTypeName = isCompoundAssignmentOperator ? null! : nativeTypeName,
EscapedName = escapedName,
ParentName = parentName,
EntryPoint = entryPoint,
Expand Down Expand Up @@ -880,7 +890,17 @@ private void VisitFunctionDecl(FunctionDecl functionDecl)
var currentContext = _context.AddLast((compoundStmt, null));

_outputBuilder.BeginConstructorInitializers();
VisitStmts(compoundStmt.Body);

if (isCompoundAssignmentOperator)
{
// Drop the trailing `return this`/`*this`; the C# operator returns void.
VisitStmts([.. compoundStmt.Body.SkipLast(1)]);
}
else
{
VisitStmts(compoundStmt.Body);
}

_outputBuilder.EndConstructorInitializers();

Debug.Assert(_context.Last == currentContext);
Expand Down Expand Up @@ -1841,7 +1861,13 @@ private bool IsConstant(string targetTypeName, Expr initExpr)
}

// case CX_StmtClass_ChooseExpr:
// case CX_StmtClass_CompoundLiteralExpr:

case CX_StmtClass_CompoundLiteralExpr:
{
var compoundLiteralExpr = (CompoundLiteralExpr)initExpr;
return IsConstant(targetTypeName, compoundLiteralExpr.Initializer);
}

// case CX_StmtClass_ConceptSpecializationExpr:
// case CX_StmtClass_ConvertVectorExpr:
// case CX_StmtClass_CoawaitExpr:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,13 @@ private void VisitImplicitValueInitExpr(ImplicitValueInitExpr implicitValueInitE
StopCSharpCode();
}

private void VisitCompoundLiteralExpr(CompoundLiteralExpr compoundLiteralExpr)
{
// A C compound literal `(T){ ... }` has no distinct C# spelling; it projects as the
// object/collection initializer emitted by its inner initializer list.
Visit(compoundLiteralExpr.Initializer);
}

private void VisitInitListExpr(InitListExpr initListExpr)
{
var outputBuilder = StartCSharpCode();
Expand Down Expand Up @@ -2736,7 +2743,13 @@ private void VisitStmt(Stmt stmt)
}

// case CX_StmtClass_ChooseExpr:
// case CX_StmtClass_CompoundLiteralExpr:

case CX_StmtClass_CompoundLiteralExpr:
{
VisitCompoundLiteralExpr((CompoundLiteralExpr)stmt);
break;
}

// case CX_StmtClass_ConvertVectorExpr:
// case CX_StmtClass_CoawaitExpr:
// case CX_StmtClass_CoyieldExpr:
Expand Down
133 changes: 133 additions & 0 deletions sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2311,13 +2311,146 @@ private bool TryRemapOperatorName(ref string name, FunctionDecl functionDecl)
return true;
}

// C++ compound-assignment operators have no friendly C# projection, so they are
// emitted under their .NET metadata names, consistent with `operator=` above.

case "operator+=":
{
name = "op_AdditionAssignment";
return true;
}

case "operator-=":
{
name = "op_SubtractionAssignment";
return true;
}

case "operator*=":
{
name = "op_MultiplicationAssignment";
return true;
}

case "operator/=":
{
name = "op_DivisionAssignment";
return true;
}

case "operator%=":
{
name = "op_ModulusAssignment";
return true;
}

case "operator&=":
{
name = "op_BitwiseAndAssignment";
return true;
}

case "operator|=":
{
name = "op_BitwiseOrAssignment";
return true;
}

case "operator^=":
{
name = "op_ExclusiveOrAssignment";
return true;
}

case "operator<<=":
{
name = "op_LeftShiftAssignment";
return true;
}

case "operator>>=":
{
name = "op_RightShiftAssignment";
return true;
}

default:
{
return false;
}
}
}

private bool IsLatestCompoundAssignmentOperator(FunctionDecl functionDecl, [NotNullWhen(true)] out string? operatorToken)
{
operatorToken = null;

// C# 14 lets a compound-assignment operator be user-defined as an instance member with a
// void return that mutates in place. The C++ operator can project onto it only when it
// returns a pointer/reference to the declaring type and its body just returns `this`/`*this`,
// which is then dropped (see https://github.com/dotnet/ClangSharp/issues/821).
if (!_config.GenerateLatestCode || functionDecl is not CXXMethodDecl cxxMethodDecl || !cxxMethodDecl.IsInstance)
{
return false;
}

var parent = cxxMethodDecl.Parent;

if (parent is null)
{
return false;
}

var name = GetCursorName(functionDecl);

operatorToken = name switch {
"operator+=" or "operator-=" or "operator*=" or "operator/=" or "operator%=" or
"operator&=" or "operator|=" or "operator^=" or "operator<<=" or "operator>>=" => name["operator".Length..],
_ => null,
};

if (operatorToken is null)
{
return false;
}

var returnType = functionDecl.ReturnType.CanonicalType;

if (returnType is not (PointerType or ReferenceType) || (returnType.PointeeType.CanonicalType.AsCXXRecordDecl != parent))
{
operatorToken = null;
return false;
}

if (functionDecl.Body is not CompoundStmt compoundStmt || compoundStmt.BodyBack is not ReturnStmt returnStmt)
{
operatorToken = null;
return false;
}

var retValue = returnStmt.RetValue;

if (retValue is null || !ReturnsThis(retValue))
{
operatorToken = null;
return false;
}

return true;

static bool ReturnsThis(Expr retValue)
{
var expr = retValue.IgnoreParenCasts;

if (expr is UnaryOperator unaryOperator && (unaryOperator.Opcode == CXUnaryOperator_Deref))
{
expr = unaryOperator.SubExpr.IgnoreParenCasts;
}

return expr is CXXThisExpr;
}
}

private void UncheckStmt(string targetTypeName, Stmt stmt)
{
Debug.Assert(_outputBuilder is not null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace ClangSharp.Test
{
public partial struct b3Vec3
{
public float x;

public float y;

public float z;
}

public static partial class Methods
{
[return: NativeTypeName("struct b3Vec3")]
public static b3Vec3 b3Negate([NativeTypeName("struct b3Vec3")] b3Vec3 a)
{
return new b3Vec3
{
x = -a.x,
y = -a.y,
z = -a.z,
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Runtime.CompilerServices;

namespace ClangSharp.Test
{
public unsafe partial struct MyStruct
{
public int value;

[return: NativeTypeName("MyStruct &")]
public MyStruct* op_AdditionAssignment(MyStruct rhs)
{
value += rhs.value;
return (MyStruct*)Unsafe.AsPointer(ref this);
}

[return: NativeTypeName("MyStruct &")]
public MyStruct* op_MultiplicationAssignment(int scale)
{
value *= scale;
return (MyStruct*)Unsafe.AsPointer(ref this);
}

[return: NativeTypeName("MyStruct &")]
public MyStruct* op_DivisionAssignment([NativeTypeName("MyStruct &")] MyStruct* other)
{
value /= other->value;
return other;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Runtime.CompilerServices;

namespace ClangSharp.Test
{
public unsafe partial struct MyStruct
{
public int value;

[return: NativeTypeName("MyStruct &")]
public MyStruct* op_AdditionAssignment(MyStruct rhs)
{
value += rhs.value;
return (MyStruct*)Unsafe.AsPointer(ref this);
}

[return: NativeTypeName("MyStruct &")]
public MyStruct* op_MultiplicationAssignment(int scale)
{
value *= scale;
return (MyStruct*)Unsafe.AsPointer(ref this);
}

[return: NativeTypeName("MyStruct &")]
public MyStruct* op_DivisionAssignment([NativeTypeName("MyStruct &")] MyStruct* other)
{
value /= other->value;
return other;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace ClangSharp.Test
{
public unsafe partial struct MyStruct
{
public int value;

public void operator +=(MyStruct rhs)
{
value += rhs.value;
}

public void operator *=(int scale)
{
value *= scale;
}

[return: NativeTypeName("MyStruct &")]
public MyStruct* op_DivisionAssignment([NativeTypeName("MyStruct &")] MyStruct* other)
{
value /= other->value;
return other;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace ClangSharp.Test
{
public unsafe partial struct MyStruct
{
public int value;

public void operator +=(MyStruct rhs)
{
value += rhs.value;
}

public void operator *=(int scale)
{
value *= scale;
}

[return: NativeTypeName("MyStruct &")]
public MyStruct* op_DivisionAssignment([NativeTypeName("MyStruct &")] MyStruct* other)
{
value /= other->value;
return other;
}
}
}
Loading
Loading