Skip to content

Commit 92324c4

Browse files
committed
Move nullable handling into CppInteropFunction.
1 parent 30c8f4a commit 92324c4

4 files changed

Lines changed: 29 additions & 123 deletions

File tree

Reinterop~/Constructors.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ private static void GenerateSingleNonStatic(CppGenerationContext context, TypeTo
6464
));
6565

6666
// Constructor definition
67-
IReadOnlyList<CppStatement> body = recipe.Body(outParameterTypeName: definition.Type.Name)!;
67+
IReadOnlyList<CppStatement> body = recipe.Body(outParameterTypeName: definition.Type.Name);
6868
definition.Elements.Add(new(
6969
Content:
7070
$$"""

Reinterop~/CppInteropFunction.cs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -149,18 +149,27 @@ public IReadOnlyList<CppArgument> CallArguments(string? outParameterTypeName = n
149149

150150
/// <summary>
151151
/// Builds the call+return body, automatically choosing between a void call, a value-returning
152-
/// call, and a struct-return-rewrite call (with the result produced via an out-parameter of type
153-
/// <paramref name="outParameterTypeName"/>, which defaults to <see cref="ReturnType"/> itself).
154-
/// Returns null for the one shape not modeled here: a Nullable-wrapped struct-return rewrite,
155-
/// which returns a "resultIsValid" flag rather than using an exception out-parameter alone -
156-
/// callers must still build that shape by hand (and pass a null body to <see cref="AddToGeneration"/>).
152+
/// call, a struct-return-rewrite call (with the result produced via an out-parameter of type
153+
/// <paramref name="outParameterTypeName"/>, which defaults to <see cref="ReturnType"/> itself),
154+
/// and a Nullable-wrapped struct-return-rewrite call (which additionally returns a "resultIsValid"
155+
/// flag, becoming "resultIsValid ? std::make_optional(...) : std::nullopt").
157156
/// </summary>
158-
public IReadOnlyList<CppStatement>? Body(string? outParameterTypeName = null, string resultVariableName = "result")
157+
public IReadOnlyList<CppStatement> Body(string? outParameterTypeName = null, string resultVariableName = "result")
159158
{
159+
CppExpression functionPointer = new CppIdentifier(Name);
160+
160161
if (HasStructRewrite && ReturnType.Kind == InteropTypeKind.Nullable)
161-
return null;
162+
{
163+
CppType elementType = ReturnType.GenericArguments!.First();
164+
IReadOnlyList<CppArgument> nullableArguments = CallArguments(outParameterTypeName ?? elementType.GetFullyQualifiedName());
165+
string convertedResult = ReturnType.GetConversionFromInteropType(_context, resultVariableName);
166+
return CppInterop.CallManagedFunction(
167+
functionPointer, nullableArguments,
168+
resultTypeName: "auto",
169+
returnExpression: new CppRaw($"resultIsValid ? std::make_optional(std::move({convertedResult})) : std::nullopt"),
170+
resultVariableName: "resultIsValid");
171+
}
162172

163-
CppExpression functionPointer = new CppIdentifier(Name);
164173
IReadOnlyList<CppArgument> arguments = CallArguments(outParameterTypeName);
165174

166175
bool isVoid = ReturnType.Name == "void" && !ReturnType.Flags.HasFlag(CppTypeFlags.Pointer);
@@ -178,23 +187,21 @@ public IReadOnlyList<CppArgument> CallArguments(string? outParameterTypeName = n
178187
/// Adds everything needed to expose this interop function: the interop function pointer field
179188
/// (declaration, out-of-line definition initialized to nullptr, and startup init registration),
180189
/// the wrapped function's own declaration (unless <see cref="WithoutDeclaration"/> was used), and
181-
/// (if <paramref name="body"/> is non-null) its definition. A null <paramref name="body"/> (see
182-
/// <see cref="Body"/>) means the caller still needs to add a hand-built definition of its own.
190+
/// its definition.
183191
/// </summary>
184192
public void AddToGeneration(
185193
GeneratedResult result,
186194
string name,
187195
string csharpName,
188196
string csharpContent,
189-
IReadOnlyList<CppStatement>? body)
197+
IReadOnlyList<CppStatement> body)
190198
{
191199
AddInteropFunctionPointer(result, result.CppDefinition.Type.GetFullyQualifiedName(false), csharpName, csharpContent);
192200

193201
if (!_skipDeclaration)
194202
AddDeclaration(result, name);
195203

196-
if (body != null)
197-
AddDefinition(result, name, body);
204+
AddDefinition(result, name, body);
198205
}
199206

200207
/// <summary>

Reinterop~/Fields.cs

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ private static void GenerateFieldAccessors(CppGenerationContext context, TypeToG
6868

6969
private static void GenerateSingleFieldAccessors(CppGenerationContext context, TypeToGenerate item, IFieldSymbol field, GeneratedResult result)
7070
{
71-
GeneratedCppDefinition definition = result.CppDefinition;
72-
7371
CppType fieldType = CppType.FromCSharp(context, field.Type);
7472
CppType setType = fieldType.AsParameterType();
7573
CppType getType = fieldType.AsReturnType();
@@ -82,44 +80,7 @@ private static void GenerateSingleFieldAccessors(CppGenerationContext context, T
8280
var (getCsName, getCsContent) = Interop.CreateCSharpDelegateInit(context, item.Type, field, isGet: true);
8381
var (setCsName, setCsContent) = Interop.CreateCSharpDelegateInit(context, item.Type, field, isGet: false);
8482

85-
// The Nullable-with-struct-rewrite case (getter returns a bool "is valid" flag alongside an
86-
// out-parameter) isn't modeled by CppInteropFunction.Body, so it's still built as a plain
87-
// string template - same deliberate scope exclusion as Methods.cs.
88-
IReadOnlyList<CppStatement>? getBody = getRecipe.Body();
89-
getRecipe.AddToGeneration(result, field.Name, getCsName, getCsContent, getBody);
90-
91-
if (getBody == null)
92-
{
93-
var parameterPassStrings = getRecipe.InteropParameters.Select(parameter => parameter.Type.GetConversionToInteropType(context, parameter.CallSiteName));
94-
parameterPassStrings = parameterPassStrings.Concat(new[] { "&reinteropException" }).Where(s => !string.IsNullOrEmpty(s));
95-
96-
string[] invocation = new[]
97-
{
98-
$"void* reinteropException = nullptr;",
99-
$"{getType.GenericArguments.FirstOrDefault().GetFullyQualifiedName()} result;",
100-
$"std::uint8_t resultIsValid = Field_get_{field.Name}({string.Join(", ", parameterPassStrings)});",
101-
$"if (reinteropException != nullptr) {{",
102-
$" throw Reinterop::ReinteropNativeException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));",
103-
$"}}",
104-
$"return resultIsValid ? std::make_optional(std::move({getType.GetConversionFromInteropType(context, "result")})) : std::nullopt;"
105-
};
106-
107-
definition.Elements.Add(new(
108-
Content:
109-
$$"""
110-
{{getType.GetFullyQualifiedName()}} {{definition.Type.Name}}::{{field.Name}}(){{(field.IsStatic ? "" : " const")}} {
111-
{{GenerationUtility.JoinAndIndent(invocation, " ")}}
112-
}
113-
""",
114-
TypeDefinitionsReferenced: new[]
115-
{
116-
definition.Type,
117-
getType,
118-
CppObjectHandle.GetCppType(context),
119-
CppReinteropException.GetCppType(context)
120-
}
121-
));
122-
}
83+
getRecipe.AddToGeneration(result, field.Name, getCsName, getCsContent, getRecipe.Body());
12384

12485
IReadOnlyList<CppStatement> setterBody = CppInterop.CallManagedFunction(
12586
new CppIdentifier($"Field_set_{field.Name}"), setRecipe.CallArguments());

Reinterop~/Methods.cs

Lines changed: 7 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,14 @@ public static void GenerateSingleMethod(CppGenerationContext context, TypeToGene
4242

4343
string interopName = $"Call{method.Name}_{Interop.HashParameters(method.Parameters, method.TypeArguments)}";
4444

45+
// If this is an instance method, pass the current object as the first (implicit "thiz") parameter.
46+
CppType? instanceType = method.IsStatic ? null : result.CppDefinition.Type.AsParameterType();
47+
CppInteropFunction recipe = new CppInteropFunction(context, interopName, parameters, returnType, instanceType);
48+
4549
// For op_Equality/op_Inequality, the interop function itself is private, and a public operator==/!= is added below to call it.
4650
bool addOperator = method.MethodKind == MethodKind.UserDefinedOperator && (method.Name == "op_Equality" || method.Name == "op_Inequality");
51+
if (addOperator)
52+
recipe.AsPrivate();
4753

4854
if (method.IsGenericMethod)
4955
{
@@ -68,82 +74,14 @@ public static void GenerateSingleMethod(CppGenerationContext context, TypeToGene
6874
));
6975
}
7076

71-
// Parameters of generic type are always passed as const references for maximum compatibility
72-
Debug.Assert(parameters.Length == genericMethod.Parameters.Length);
73-
for (int i = 0; i < parameters.Length && i < genericMethod.Parameters.Length; ++i)
74-
{
75-
IParameterSymbol genericParameter = genericMethod.Parameters[i];
76-
CppInteropParameter parameter = parameters[i];
77-
78-
if (genericParameter.Type.TypeKind == TypeKind.TypeParameter && (!parameter.Type.Flags.HasFlag(CppTypeFlags.Reference) || !parameter.Type.Flags.HasFlag(CppTypeFlags.Const)))
79-
{
80-
parameters[i] = parameter with { Type = parameter.Type.AsConstReference() };
81-
}
82-
}
83-
}
84-
85-
// If this is an instance method, pass the current object as the first (implicit "thiz") parameter.
86-
CppType? instanceType = method.IsStatic ? null : result.CppDefinition.Type.AsParameterType();
87-
CppInteropFunction recipe = new CppInteropFunction(context, interopName, parameters, returnType, instanceType);
88-
89-
if (addOperator)
90-
recipe.AsPrivate();
91-
92-
if (method.IsGenericMethod)
93-
{
9477
// The generic template's own declaration was added above; only its specialization's definition is needed here.
9578
recipe.WithoutDeclaration().AsTemplateSpecialization(method.TypeArguments.Select(t => CppType.FromCSharp(context, t)));
9679
}
9780

9881
// A private, static field of function pointer type that will call into a managed delegate
9982
// for this method, initialized at startup, plus the method's own declaration and definition.
10083
var (csName, csContent) = Interop.CreateCSharpDelegateInit(context, item.Type, method, interopName);
101-
IReadOnlyList<CppStatement>? body = recipe.Body();
102-
recipe.AddToGeneration(result, method.Name, csName, csContent, body);
103-
104-
if (body == null)
105-
{
106-
// The Nullable-with-struct-rewrite case (returns a "resultIsValid" flag alongside an
107-
// out-parameter) isn't modeled by CppInteropFunction.Body, so it's still built as a
108-
// plain string template.
109-
var parameterPassStrings = recipe.InteropParameters.Select(parameter => parameter.Type.GetConversionToInteropType(context, parameter.CallSiteName));
110-
parameterPassStrings = parameterPassStrings.Concat(new[] { "&reinteropException" }).Where(s => !string.IsNullOrEmpty(s));
111-
112-
string[] invocation = new[]
113-
{
114-
$"{returnType.GenericArguments.FirstOrDefault().GetFullyQualifiedName()} result;",
115-
$"std::uint8_t resultIsValid = {interopName}({string.Join(", ", parameterPassStrings)});"
116-
};
117-
string returnStatement = $"return resultIsValid ? std::make_optional(std::move({returnType.GetConversionFromInteropType(context, "result")})) : std::nullopt;";
118-
119-
string modifiers = method.IsStatic ? "static " : "";
120-
string afterModifiers = method.IsStatic ? "" : " const";
121-
string templatePrefix = method.IsGenericMethod ? "template <> " : "";
122-
string templateSpecialization = method.IsGenericMethod
123-
? $"<{string.Join(", ", method.TypeArguments.Select(t => CppType.FromCSharp(context, t).GetFullyQualifiedName()))}>"
124-
: "";
125-
string typeTemplateSpecialization = CppInteropFunction.GetTypeTemplateSpecialization(definition.Type);
126-
127-
definition.Elements.Add(new(
128-
Content:
129-
$$"""
130-
{{templatePrefix}}{{returnType.GetFullyQualifiedName()}} {{definition.Type.Name}}{{typeTemplateSpecialization}}::{{method.Name}}{{templateSpecialization}}({{recipe.ParameterListDeclaration()}}){{afterModifiers}} {
131-
void* reinteropException = nullptr;
132-
{{GenerationUtility.JoinAndIndent(invocation, " ")}}
133-
if (reinteropException != nullptr)
134-
throw Reinterop::ReinteropNativeException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));
135-
{{returnStatement}}
136-
}
137-
""",
138-
TypeDefinitionsReferenced: new[]
139-
{
140-
definition.Type,
141-
returnType,
142-
CppObjectHandle.GetCppType(context),
143-
CppReinteropException.GetCppType(context)
144-
}.Concat(recipe.ParameterTypes)
145-
));
146-
}
84+
recipe.AddToGeneration(result, method.Name, csName, csContent, recipe.Body());
14785

14886
if (addOperator)
14987
{

0 commit comments

Comments
 (0)