From d8300a84707a030a1aeba167e3f2b9a4414122e2 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sat, 28 Feb 2026 20:56:27 +0100 Subject: [PATCH 1/4] task conversion through continuation only --- .../CSharp/CSharpMethodRenderer.cs | 22 +++-- .../CSharp/CSharpTypeConversionRenderer.cs | 90 ++++++++----------- .../CSharp/DeferredExpressionRenderer.cs | 9 +- .../CSharp/JSObjectExtensionsRenderer.cs | 4 +- 4 files changed, 57 insertions(+), 68 deletions(-) diff --git a/src/TypeShim.Generator/CSharp/CSharpMethodRenderer.cs b/src/TypeShim.Generator/CSharp/CSharpMethodRenderer.cs index 799f991d..cb0e6195 100644 --- a/src/TypeShim.Generator/CSharp/CSharpMethodRenderer.cs +++ b/src/TypeShim.Generator/CSharp/CSharpMethodRenderer.cs @@ -92,7 +92,7 @@ private void RenderMethodCore(MethodInfo methodInfo) _conversionRenderer.RenderParameterTypeConversion(originalParamInfo); } - DeferredExpressionRenderer returnValueExpression = _conversionRenderer.RenderReturnTypeConversion(methodInfo.ReturnType, DeferredExpressionRenderer.From(RenderInvocationExpression)); + DeferredExpressionRenderer returnValueExpression = _conversionRenderer.RenderReturnTypeConversion(methodInfo.ReturnType, DeferredExpressionRenderer.FromUnary(RenderInvocationExpression)); if (methodInfo.ReturnType.ManagedType != KnownManagedType.Void) _ctx.Append("return "); returnValueExpression.Render(); _ctx.AppendLine(";"); @@ -143,7 +143,7 @@ private void RenderPropertyMethodCore(PropertyInfo propertyInfo, MethodInfo meth } string accessedObject = methodInfo.IsStatic ? _ctx.Class.Name : _ctx.LocalScope.GetAccessorExpression(methodInfo.Parameters.ElementAt(0)); - DeferredExpressionRenderer untypedValueExpressionRenderer = DeferredExpressionRenderer.From(() => _ctx.Append(accessedObject).Append('.').Append(propertyInfo.Name)); + DeferredExpressionRenderer untypedValueExpressionRenderer = DeferredExpressionRenderer.FromUnary(() => _ctx.Append(accessedObject).Append('.').Append(propertyInfo.Name)); DeferredExpressionRenderer typedValueExpressionRenderer = _conversionRenderer.RenderReturnTypeConversion(methodInfo.ReturnType, untypedValueExpressionRenderer); if (methodInfo.ReturnType.ManagedType != KnownManagedType.Void) // getter { @@ -266,21 +266,25 @@ Dictionary RenderJSObjectPropertyRetri Dictionary convertedTaskExpressionDict = []; foreach (PropertyInfo propertyInfo in properties) { - DeferredExpressionRenderer valueRetrievalExpressionRenderer = DeferredExpressionRenderer.From(() => { + DeferredExpressionRenderer valueRetrievalExpressionRenderer = DeferredExpressionRenderer.FromUnary(() => { _ctx.Append("jsObject.").Append(_methodResolver.ResolveJSObjectMethodName(propertyInfo.Type)) .Append("(\"").Append(propertyInfo.Name).Append("\")"); - if (!propertyInfo.Type.IsNullableType) - { + }); + + if (!propertyInfo.Type.IsNullableType) + { + DeferredExpressionRenderer nonNullableExpressionRenderer = valueRetrievalExpressionRenderer; + valueRetrievalExpressionRenderer = DeferredExpressionRenderer.FromBinary(() => { + nonNullableExpressionRenderer.Render(); _ctx.Append(" ?? throw new ArgumentException(\"Non-nullable property '") .Append(propertyInfo.Name) .Append("' missing or of invalid type\", nameof(jsObject))"); - } - }); + }); + } if (!propertyInfo.Type.RequiresTypeConversion) { convertedTaskExpressionDict.Add(propertyInfo, valueRetrievalExpressionRenderer); - } else { @@ -290,7 +294,7 @@ Dictionary RenderJSObjectPropertyRetri _ctx.Append(propertyInfo.Type.CSharpInteropTypeSyntax).Append(" tmp").Append(propertyInfo.Name).Append(" = "); valueRetrievalExpressionRenderer.Render(); _ctx.AppendLine(";"); - valueRetrievalExpressionRenderer = DeferredExpressionRenderer.From(() => { + valueRetrievalExpressionRenderer = DeferredExpressionRenderer.FromUnary(() => { _ctx.Append("tmp").Append(propertyInfo.Name); }); } diff --git a/src/TypeShim.Generator/CSharp/CSharpTypeConversionRenderer.cs b/src/TypeShim.Generator/CSharp/CSharpTypeConversionRenderer.cs index d8e512cd..6c778c04 100644 --- a/src/TypeShim.Generator/CSharp/CSharpTypeConversionRenderer.cs +++ b/src/TypeShim.Generator/CSharp/CSharpTypeConversionRenderer.cs @@ -16,7 +16,7 @@ internal void RenderParameterTypeConversion(MethodParameterInfo parameterInfo) string varName = _ctx.LocalScope.GetAccessorExpression(parameterInfo); string newVarName = $"typed_{_ctx.LocalScope.GetAccessorExpression(parameterInfo)}"; - DeferredExpressionRenderer convertedValueAccessor = RenderTypeDownConversion(parameterInfo.Type, varName, DeferredExpressionRenderer.From(() => _ctx.Append(varName)), parameterInfo.IsInjectedInstanceParameter); + DeferredExpressionRenderer convertedValueAccessor = RenderTypeDownConversion(parameterInfo.Type, varName, DeferredExpressionRenderer.FromUnary(() => _ctx.Append(varName)), parameterInfo.IsInjectedInstanceParameter); _ctx.Append(parameterInfo.Type.CSharpTypeSyntax.ToString()).Append(' ').Append(newVarName).Append(" = "); convertedValueAccessor.Render(); _ctx.AppendLine(";"); @@ -35,26 +35,24 @@ private DeferredExpressionRenderer RenderTypeDownConversion(InteropTypeInfo type if (typeInfo is { IsNullableType: true, TypeArgument.IsTaskType: true }) // Task? { - string convertedTaskExpression = RenderNullableTaskTypeConversion(typeInfo, accessorName, accessorExpressionRenderer); - return DeferredExpressionRenderer.From(() => _ctx.Append(convertedTaskExpression)); + return DeferredExpressionRenderer.FromUnary(() => RenderInlineNullableTaskTypeConversion(typeInfo, up: false, accessorExpressionRenderer)); } else if (typeInfo.IsTaskType) // Task { - string convertedTaskExpression = RenderTaskTypeConversion(typeInfo, accessorName, accessorExpressionRenderer); - return DeferredExpressionRenderer.From(() => _ctx.Append(convertedTaskExpression)); + return DeferredExpressionRenderer.FromUnary(() => RenderInlineTaskTypeConversion(typeInfo, up: false, accessorExpressionRenderer)); } else { - return DeferredExpressionRenderer.From(() => RenderInlineTypeDownConversion(typeInfo, accessorName, accessorExpressionRenderer, forceCovariantConversion: isInstanceParameter)); + return DeferredExpressionRenderer.FromUnary(() => RenderInlineTypeDownConversion(typeInfo, accessorName, accessorExpressionRenderer, forceCovariantConversion: isInstanceParameter)); } } /// - /// Renders any lines that may be required to convert the return type from interop to managed. Then returns a delegate to render the expression to access the converted value. + /// Renders any lines that may be required to convert the return type from interop to managed. /// /// /// - /// + /// a delegate to render the expression that returns the converted value. internal DeferredExpressionRenderer RenderReturnTypeConversion(InteropTypeInfo returnType, DeferredExpressionRenderer valueExpressionRenderer) { if (!returnType.RequiresTypeConversion) @@ -62,13 +60,11 @@ internal DeferredExpressionRenderer RenderReturnTypeConversion(InteropTypeInfo r if (returnType is { IsTaskType: true, TypeArgument.RequiresTypeConversion: true }) // Handle Task { - string convertedValueExpression = RenderTaskTypeConversion(returnType.AsInteropTypeInfo(), "retVal", valueExpressionRenderer); - return DeferredExpressionRenderer.From(() => _ctx.Append(convertedValueExpression)); + return DeferredExpressionRenderer.FromUnary(() => RenderInlineTaskTypeConversion(returnType, up: true, valueExpressionRenderer)); } else if (returnType is { IsNullableType: true, TypeArgument.IsTaskType: true, TypeArgument.RequiresTypeConversion: true }) // Handle Task? { - string convertedValueExpression = RenderNullableTaskTypeConversion(returnType.AsInteropTypeInfo(), "retVal", valueExpressionRenderer); - return DeferredExpressionRenderer.From(() => _ctx.Append(convertedValueExpression)); + return DeferredExpressionRenderer.FromUnary(() => RenderInlineNullableTaskTypeConversion(returnType, up: true, valueExpressionRenderer)); } else if (returnType.IsDelegateType() && returnType.ArgumentInfo is DelegateArgumentInfo argumentInfo) // Action/Action/Func @@ -77,11 +73,11 @@ internal DeferredExpressionRenderer RenderReturnTypeConversion(InteropTypeInfo r _ctx.Append(returnType.CSharpTypeSyntax).Append(" retVal = "); valueExpressionRenderer.Render(); _ctx.AppendLine(";"); - return DeferredExpressionRenderer.From(() => RenderInlineDelegateTypeUpConversion(returnType, "retVal", argumentInfo)); + return DeferredExpressionRenderer.FromUnary(() => RenderInlineDelegateTypeUpConversion(returnType, "retVal", argumentInfo)); } else { - return DeferredExpressionRenderer.From(() => RenderInlineCovariantTypeUpConversion(returnType, valueExpressionRenderer)); + return DeferredExpressionRenderer.FromUnary(() => RenderInlineCovariantTypeUpConversion(returnType, valueExpressionRenderer)); } } @@ -153,7 +149,7 @@ private void RenderInlineNullableTypeDownConversion(InteropTypeInfo typeInfo, st _ctx.Append(" is { } ") .Append(accessorName).Append("Val") .Append(" ? "); - RenderInlineTypeDownConversion(typeInfo.TypeArgument, accessorName, DeferredExpressionRenderer.From(() => _ctx.Append(accessorName).Append("Val"))); + RenderInlineTypeDownConversion(typeInfo.TypeArgument, accessorName, DeferredExpressionRenderer.FromUnary(() => _ctx.Append(accessorName).Append("Val"))); _ctx.Append(" : null"); } @@ -170,7 +166,7 @@ private void RenderInlineArrayTypeDownConversion(InteropTypeInfo typeInfo, Defer _ctx.Append("Array.ConvertAll("); accessorExpressionRenderer.Render(); _ctx.Append(", e => "); - RenderInlineTypeDownConversion(typeInfo.TypeArgument, "e", DeferredExpressionRenderer.From(() => _ctx.Append("e"))); + RenderInlineTypeDownConversion(typeInfo.TypeArgument, "e", DeferredExpressionRenderer.FromUnary(() => _ctx.Append("e"))); _ctx.Append(')'); } } @@ -179,49 +175,41 @@ private void RenderInlineArrayTypeDownConversion(InteropTypeInfo typeInfo, Defer /// returns an expression to access the converted task with. /// /// - private string RenderTaskTypeConversion(InteropTypeInfo targetTaskType, string sourceVarName, DeferredExpressionRenderer sourceTaskExpressionRenderer) + private void RenderInlineTaskTypeConversion(InteropTypeInfo targetTaskType, bool up, DeferredExpressionRenderer sourceTaskExpressionRenderer) { InteropTypeInfo taskTypeParamInfo = targetTaskType.TypeArgument ?? throw new InvalidOperationException("Task type must have a type argument for conversion."); - string tcsVarName = $"{sourceVarName}Tcs"; - _ctx.Append("TaskCompletionSource<").Append(taskTypeParamInfo.CSharpTypeSyntax.ToString()).Append("> ").Append(tcsVarName).AppendLine(" = new();"); - _ctx.Append('('); + if (sourceTaskExpressionRenderer.IsBinary) _ctx.Append('('); sourceTaskExpressionRenderer.Render(); - _ctx.AppendLine(").ContinueWith(t => {"); - - using (_ctx.Indent()) + if (sourceTaskExpressionRenderer.IsBinary) _ctx.Append(')'); + _ctx.Append(".ContinueWith(t => "); + if (up) + { + RenderInlineCovariantTypeUpConversion(taskTypeParamInfo, DeferredExpressionRenderer.FromUnary(() => _ctx.Append("t.Result"))); + } + else { - _ctx.Append("if (t.IsFaulted) ").Append(tcsVarName).AppendLine(".SetException(t.Exception.InnerExceptions);") - .Append("else if (t.IsCanceled) ").Append(tcsVarName).AppendLine(".SetCanceled();") - .Append("else ").Append(tcsVarName).Append(".SetResult("); - RenderInlineTypeDownConversion(taskTypeParamInfo, tcsVarName, DeferredExpressionRenderer.From(() => _ctx.Append("t.Result"))); - _ctx.AppendLine(");"); + RenderInlineTypeDownConversion(taskTypeParamInfo, "t", DeferredExpressionRenderer.FromUnary(() => _ctx.Append("t.Result"))); } - _ctx.AppendLine("}, TaskContinuationOptions.ExecuteSynchronously);"); - return $"{tcsVarName}.Task"; + _ctx.Append(", TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously)"); } - private string RenderNullableTaskTypeConversion(InteropTypeInfo targetNullableTaskType, string sourceVarName, DeferredExpressionRenderer sourceTaskExpressionRenderer) + private void RenderInlineNullableTaskTypeConversion(InteropTypeInfo targetNullableTaskType, bool up, DeferredExpressionRenderer sourceTaskExpressionRenderer) { - InteropTypeInfo taskTypeParamInfo = targetNullableTaskType.TypeArgument ?? throw new InvalidOperationException("Nullable type must have a type argument for conversion."); - InteropTypeInfo taskReturnTypeParamInfo = taskTypeParamInfo.TypeArgument ?? throw new InvalidOperationException("Task type must have a type argument for conversion."); - - string tcsVarName = $"{sourceVarName}Tcs"; - _ctx.Append("TaskCompletionSource<").Append(taskReturnTypeParamInfo.CSharpTypeSyntax.ToString()).Append(">? ").Append(tcsVarName).Append(" = "); - sourceTaskExpressionRenderer.Render(); - _ctx.AppendLine(" != null ? new() : null;"); + InteropTypeInfo targetTaskType = targetNullableTaskType.TypeArgument ?? throw new InvalidOperationException("Nullable type must have a type argument for conversion."); + InteropTypeInfo taskTypeParamInfo = targetTaskType.TypeArgument ?? throw new InvalidOperationException("Task type must have a type argument for conversion."); + if (sourceTaskExpressionRenderer.IsBinary) _ctx.Append('('); sourceTaskExpressionRenderer.Render(); - _ctx.AppendLine("?.ContinueWith(t => {"); - using (_ctx.Indent()) + if (sourceTaskExpressionRenderer.IsBinary) _ctx.Append(')'); + _ctx.Append("?.ContinueWith(t => "); + if (up) { - _ctx.Append("if (t.IsFaulted) ").Append(tcsVarName).AppendLine("!.SetException(t.Exception.InnerExceptions);") - .Append("else if (t.IsCanceled) ").Append(tcsVarName).AppendLine("!.SetCanceled();") - .Append("else ").Append(tcsVarName).Append("!.SetResult("); - RenderInlineTypeDownConversion(taskReturnTypeParamInfo, tcsVarName, DeferredExpressionRenderer.From(() => _ctx.Append("t.Result"))); - _ctx.AppendLine(");"); - + RenderInlineCovariantTypeUpConversion(taskTypeParamInfo, DeferredExpressionRenderer.FromUnary(() => _ctx.Append("t.Result"))); + } + else + { + RenderInlineTypeDownConversion(taskTypeParamInfo, "t", DeferredExpressionRenderer.FromUnary(() => _ctx.Append("t.Result"))); } - _ctx.AppendLine("}, TaskContinuationOptions.ExecuteSynchronously);"); - return $"{tcsVarName}?.Task"; + _ctx.Append(", TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously)"); } private void RenderInlineDelegateTypeDownConversion(DelegateArgumentInfo argumentInfo, string accessorName, DeferredExpressionRenderer accessorExpressionRenderer) @@ -234,7 +222,7 @@ private void RenderInlineDelegateTypeDownConversion(DelegateArgumentInfo argumen } _ctx.Append(") => "); - DeferredExpressionRenderer invocationExpressionRenderer = DeferredExpressionRenderer.From(() => + DeferredExpressionRenderer invocationExpressionRenderer = DeferredExpressionRenderer.FromUnary(() => { accessorExpressionRenderer.Render(); _ctx.Append('('); @@ -268,7 +256,7 @@ private void RenderInlineDelegateTypeUpConversion(InteropTypeInfo typeInfo, stri } _ctx.Append(") => "); - DeferredExpressionRenderer invocationExpressionRenderer = DeferredExpressionRenderer.From(() => + DeferredExpressionRenderer invocationExpressionRenderer = DeferredExpressionRenderer.FromUnary(() => { _ctx.Append(varName).Append('('); for (int i = 0; i < argumentInfo.ParameterTypes.Length; i++) @@ -276,7 +264,7 @@ private void RenderInlineDelegateTypeUpConversion(InteropTypeInfo typeInfo, stri if (i > 0) _ctx.Append(", "); // in body of downcasted delegate, to invoke original delegate we must upcast the types again - DeferredExpressionRenderer argNameRenderer = DeferredExpressionRenderer.From(() => _ctx.Append("arg").Append(i)); + DeferredExpressionRenderer argNameRenderer = DeferredExpressionRenderer.FromUnary(() => _ctx.Append("arg").Append(i)); if (argumentInfo.ParameterTypes[i].RequiresTypeConversion) { RenderInlineTypeDownConversion(argumentInfo.ParameterTypes[i], $"arg{i}", argNameRenderer); // TODO: refactor delegatearginfo to contain methodparameterinfo for names diff --git a/src/TypeShim.Generator/CSharp/DeferredExpressionRenderer.cs b/src/TypeShim.Generator/CSharp/DeferredExpressionRenderer.cs index 640eb603..23398bbf 100644 --- a/src/TypeShim.Generator/CSharp/DeferredExpressionRenderer.cs +++ b/src/TypeShim.Generator/CSharp/DeferredExpressionRenderer.cs @@ -2,12 +2,9 @@ internal class DeferredExpressionRenderer(Action renderAction) { + internal required bool IsBinary { get; init; } internal void Render() => renderAction(); - - public static implicit operator DeferredExpressionRenderer(Action renderAction) - { - return new DeferredExpressionRenderer(renderAction); - } - public static DeferredExpressionRenderer From(Action renderAction) => new(renderAction); + public static DeferredExpressionRenderer FromBinary(Action renderAction) => new(renderAction){ IsBinary = true }; + public static DeferredExpressionRenderer FromUnary(Action renderAction) => new(renderAction){ IsBinary = false }; } diff --git a/src/TypeShim.Generator/CSharp/JSObjectExtensionsRenderer.cs b/src/TypeShim.Generator/CSharp/JSObjectExtensionsRenderer.cs index 4f6c0539..98643f98 100644 --- a/src/TypeShim.Generator/CSharp/JSObjectExtensionsRenderer.cs +++ b/src/TypeShim.Generator/CSharp/JSObjectExtensionsRenderer.cs @@ -38,11 +38,11 @@ public void Render() private void RenderExtensionMethodForType(JSObjectExtensionInfo extensionInfo) { - DeferredExpressionRenderer marshalAsMethodNameRenderer = DeferredExpressionRenderer.From(() => + DeferredExpressionRenderer marshalAsMethodNameRenderer = DeferredExpressionRenderer.FromUnary(() => { _ctx.Append("MarshalAs").Append(extensionInfo.Name); }); - DeferredExpressionRenderer getPropertyAsMethodNameRenderer = DeferredExpressionRenderer.From(() => + DeferredExpressionRenderer getPropertyAsMethodNameRenderer = DeferredExpressionRenderer.FromUnary(() => { _ctx.Append("GetPropertyAs").Append(extensionInfo.Name).Append("Nullable"); }); From 6314dfe23868c92defd1b3364b4abb77d261ada6 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sat, 28 Feb 2026 20:56:38 +0100 Subject: [PATCH 2/4] update tests --- ...arpInteropClassRendererTests_Properties.cs | 100 +----- ...harpInteropClassRendererTests_Snapshots.cs | 32 +- ...lassRendererTests_SystemArrayReturnType.cs | 12 +- ...ssRendererTests_SystemTaskParameterType.cs | 318 +++++++++--------- ...ClassRendererTests_SystemTaskReturnType.cs | 268 +++++++-------- 5 files changed, 296 insertions(+), 434 deletions(-) diff --git a/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_Properties.cs b/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_Properties.cs index 7d813840..c1648587 100644 --- a/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_Properties.cs +++ b/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_Properties.cs @@ -915,15 +915,9 @@ public partial class C1Interop public static object ctor([JSMarshalAs] JSObject jsObject) { using var _ = jsObject; - TaskCompletionSource P1Tcs = new(); - (jsObject.GetPropertyAsObjectTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject))).ContinueWith(t => { - if (t.IsFaulted) P1Tcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) P1Tcs.SetCanceled(); - else P1Tcs.SetResult(MyClassInterop.FromObject(t.Result)); - }, TaskContinuationOptions.ExecuteSynchronously); return new C1() { - P1 = P1Tcs.Task, + P1 = (jsObject.GetPropertyAsObjectTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject))).ContinueWith(t => MyClassInterop.FromObject(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously), }; } [JSExport] @@ -931,26 +925,14 @@ public static object ctor([JSMarshalAs] JSObject jsObject) public static Task get_P1([JSMarshalAs] object instance) { C1 typed_instance = (C1)instance; - TaskCompletionSource retValTcs = new(); - (typed_instance.P1).ContinueWith(t => { - if (t.IsFaulted) retValTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) retValTcs.SetCanceled(); - else retValTcs.SetResult((object)t.Result); - }, TaskContinuationOptions.ExecuteSynchronously); - return retValTcs.Task; + return typed_instance.P1.ContinueWith(t => (object)t.Result, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); } [JSExport] [return: JSMarshalAs] public static void set_P1([JSMarshalAs] object instance, [JSMarshalAs>] Task value) { C1 typed_instance = (C1)instance; - TaskCompletionSource valueTcs = new(); - (value).ContinueWith(t => { - if (t.IsFaulted) valueTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) valueTcs.SetCanceled(); - else valueTcs.SetResult(MyClassInterop.FromObject(t.Result)); - }, TaskContinuationOptions.ExecuteSynchronously); - Task typed_value = valueTcs.Task; + Task typed_value = value.ContinueWith(t => MyClassInterop.FromObject(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); typed_instance.P1 = typed_value; } public static C1 FromObject(object obj) @@ -965,15 +947,9 @@ public static C1 FromObject(object obj) public static C1 FromJSObject(JSObject jsObject) { using var _ = jsObject; - TaskCompletionSource P1Tcs = new(); - (jsObject.GetPropertyAsObjectTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject))).ContinueWith(t => { - if (t.IsFaulted) P1Tcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) P1Tcs.SetCanceled(); - else P1Tcs.SetResult(MyClassInterop.FromObject(t.Result)); - }, TaskContinuationOptions.ExecuteSynchronously); return new C1() { - P1 = P1Tcs.Task, + P1 = (jsObject.GetPropertyAsObjectTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject))).ContinueWith(t => MyClassInterop.FromObject(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously), }; } } @@ -1032,15 +1008,9 @@ public partial class C1Interop public static object ctor([JSMarshalAs] JSObject jsObject) { using var _ = jsObject; - TaskCompletionSource P1Tcs = new(); - (jsObject.GetPropertyAsObjectNullableTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject))).ContinueWith(t => { - if (t.IsFaulted) P1Tcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) P1Tcs.SetCanceled(); - else P1Tcs.SetResult(t.Result is { } P1TcsVal ? MyClassInterop.FromObject(P1TcsVal) : null); - }, TaskContinuationOptions.ExecuteSynchronously); return new C1() { - P1 = P1Tcs.Task, + P1 = (jsObject.GetPropertyAsObjectNullableTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject))).ContinueWith(t => t.Result is { } tVal ? MyClassInterop.FromObject(tVal) : null, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously), }; } [JSExport] @@ -1048,26 +1018,14 @@ public static object ctor([JSMarshalAs] JSObject jsObject) public static Task get_P1([JSMarshalAs] object instance) { C1 typed_instance = (C1)instance; - TaskCompletionSource retValTcs = new(); - (typed_instance.P1).ContinueWith(t => { - if (t.IsFaulted) retValTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) retValTcs.SetCanceled(); - else retValTcs.SetResult(t.Result is { } retValTcsVal ? (object)retValTcsVal : null); - }, TaskContinuationOptions.ExecuteSynchronously); - return retValTcs.Task; + return typed_instance.P1.ContinueWith(t => (object?)t.Result, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); } [JSExport] [return: JSMarshalAs] public static void set_P1([JSMarshalAs] object instance, [JSMarshalAs>] Task value) { C1 typed_instance = (C1)instance; - TaskCompletionSource valueTcs = new(); - (value).ContinueWith(t => { - if (t.IsFaulted) valueTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) valueTcs.SetCanceled(); - else valueTcs.SetResult(t.Result is { } valueTcsVal ? MyClassInterop.FromObject(valueTcsVal) : null); - }, TaskContinuationOptions.ExecuteSynchronously); - Task typed_value = valueTcs.Task; + Task typed_value = value.ContinueWith(t => t.Result is { } tVal ? MyClassInterop.FromObject(tVal) : null, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); typed_instance.P1 = typed_value; } public static C1 FromObject(object obj) @@ -1082,19 +1040,13 @@ public static C1 FromObject(object obj) public static C1 FromJSObject(JSObject jsObject) { using var _ = jsObject; - TaskCompletionSource P1Tcs = new(); - (jsObject.GetPropertyAsObjectNullableTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject))).ContinueWith(t => { - if (t.IsFaulted) P1Tcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) P1Tcs.SetCanceled(); - else P1Tcs.SetResult(t.Result is { } P1TcsVal ? MyClassInterop.FromObject(P1TcsVal) : null); - }, TaskContinuationOptions.ExecuteSynchronously); return new C1() { - P1 = P1Tcs.Task, + P1 = (jsObject.GetPropertyAsObjectNullableTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject))).ContinueWith(t => t.Result is { } tVal ? MyClassInterop.FromObject(tVal) : null, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously), }; } } - + """); } @@ -1149,15 +1101,9 @@ public partial class C1Interop public static object ctor([JSMarshalAs] JSObject jsObject) { using var _ = jsObject; - TaskCompletionSource? P1Tcs = jsObject.GetPropertyAsObjectNullableTaskNullable("P1") != null ? new() : null; - jsObject.GetPropertyAsObjectNullableTaskNullable("P1")?.ContinueWith(t => { - if (t.IsFaulted) P1Tcs!.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) P1Tcs!.SetCanceled(); - else P1Tcs!.SetResult(t.Result is { } P1TcsVal ? MyClassInterop.FromObject(P1TcsVal) : null); - }, TaskContinuationOptions.ExecuteSynchronously); return new C1() { - P1 = P1Tcs?.Task, + P1 = jsObject.GetPropertyAsObjectNullableTaskNullable("P1")?.ContinueWith(t => t.Result is { } tVal ? MyClassInterop.FromObject(tVal) : null, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously), }; } [JSExport] @@ -1165,26 +1111,14 @@ public static object ctor([JSMarshalAs] JSObject jsObject) public static Task? get_P1([JSMarshalAs] object instance) { C1 typed_instance = (C1)instance; - TaskCompletionSource? retValTcs = typed_instance.P1 != null ? new() : null; - typed_instance.P1?.ContinueWith(t => { - if (t.IsFaulted) retValTcs!.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) retValTcs!.SetCanceled(); - else retValTcs!.SetResult(t.Result is { } retValTcsVal ? (object)retValTcsVal : null); - }, TaskContinuationOptions.ExecuteSynchronously); - return retValTcs?.Task; + return typed_instance.P1?.ContinueWith(t => (object?)t.Result, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); } [JSExport] [return: JSMarshalAs] public static void set_P1([JSMarshalAs] object instance, [JSMarshalAs>] Task? value) { C1 typed_instance = (C1)instance; - TaskCompletionSource? valueTcs = value != null ? new() : null; - value?.ContinueWith(t => { - if (t.IsFaulted) valueTcs!.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) valueTcs!.SetCanceled(); - else valueTcs!.SetResult(t.Result is { } valueTcsVal ? MyClassInterop.FromObject(valueTcsVal) : null); - }, TaskContinuationOptions.ExecuteSynchronously); - Task? typed_value = valueTcs?.Task; + Task? typed_value = value?.ContinueWith(t => t.Result is { } tVal ? MyClassInterop.FromObject(tVal) : null, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); typed_instance.P1 = typed_value; } public static C1 FromObject(object obj) @@ -1199,19 +1133,13 @@ public static C1 FromObject(object obj) public static C1 FromJSObject(JSObject jsObject) { using var _ = jsObject; - TaskCompletionSource? P1Tcs = jsObject.GetPropertyAsObjectNullableTaskNullable("P1") != null ? new() : null; - jsObject.GetPropertyAsObjectNullableTaskNullable("P1")?.ContinueWith(t => { - if (t.IsFaulted) P1Tcs!.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) P1Tcs!.SetCanceled(); - else P1Tcs!.SetResult(t.Result is { } P1TcsVal ? MyClassInterop.FromObject(P1TcsVal) : null); - }, TaskContinuationOptions.ExecuteSynchronously); return new C1() { - P1 = P1Tcs?.Task, + P1 = jsObject.GetPropertyAsObjectNullableTaskNullable("P1")?.ContinueWith(t => t.Result is { } tVal ? MyClassInterop.FromObject(tVal) : null, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously), }; } } - + """); } diff --git a/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_Snapshots.cs b/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_Snapshots.cs index fac6d975..d70fce64 100644 --- a/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_Snapshots.cs +++ b/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_Snapshots.cs @@ -231,15 +231,9 @@ public partial class C1Interop public static object ctor([JSMarshalAs] JSObject jsObject) { using var _ = jsObject; - TaskCompletionSource<{{typeName}}> P1Tcs = new(); - (jsObject.GetPropertyAsObjectTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject))).ContinueWith(t => { - if (t.IsFaulted) P1Tcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) P1Tcs.SetCanceled(); - else P1Tcs.SetResult(({{typeName}})t.Result); - }, TaskContinuationOptions.ExecuteSynchronously); return new C1() { - P1 = P1Tcs.Task, + P1 = (jsObject.GetPropertyAsObjectTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject))).ContinueWith(t => ({{typeName}})t.Result, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously), P2 = jsObject.GetPropertyAsInt32Nullable("P2") ?? throw new ArgumentException("Non-nullable property 'P2' missing or of invalid type", nameof(jsObject)), }; } @@ -248,26 +242,14 @@ public static object ctor([JSMarshalAs] JSObject jsObject) public static Task get_P1([JSMarshalAs] object instance) { C1 typed_instance = (C1)instance; - TaskCompletionSource retValTcs = new(); - (typed_instance.P1).ContinueWith(t => { - if (t.IsFaulted) retValTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) retValTcs.SetCanceled(); - else retValTcs.SetResult((object)t.Result); - }, TaskContinuationOptions.ExecuteSynchronously); - return retValTcs.Task; + return typed_instance.P1.ContinueWith(t => (object)t.Result, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); } [JSExport] [return: JSMarshalAs] public static void set_P1([JSMarshalAs] object instance, [JSMarshalAs>] Task value) { C1 typed_instance = (C1)instance; - TaskCompletionSource<{{typeName}}> valueTcs = new(); - (value).ContinueWith(t => { - if (t.IsFaulted) valueTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) valueTcs.SetCanceled(); - else valueTcs.SetResult(({{typeName}})t.Result); - }, TaskContinuationOptions.ExecuteSynchronously); - Task<{{typeName}}> typed_value = valueTcs.Task; + Task<{{typeName}}> typed_value = value.ContinueWith(t => ({{typeName}})t.Result, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); typed_instance.P1 = typed_value; } [JSExport] @@ -296,15 +278,9 @@ public static C1 FromObject(object obj) public static C1 FromJSObject(JSObject jsObject) { using var _ = jsObject; - TaskCompletionSource<{{typeName}}> P1Tcs = new(); - (jsObject.GetPropertyAsObjectTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject))).ContinueWith(t => { - if (t.IsFaulted) P1Tcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) P1Tcs.SetCanceled(); - else P1Tcs.SetResult(({{typeName}})t.Result); - }, TaskContinuationOptions.ExecuteSynchronously); return new C1() { - P1 = P1Tcs.Task, + P1 = (jsObject.GetPropertyAsObjectTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject))).ContinueWith(t => ({{typeName}})t.Result, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously), P2 = jsObject.GetPropertyAsInt32Nullable("P2") ?? throw new ArgumentException("Non-nullable property 'P2' missing or of invalid type", nameof(jsObject)), }; } diff --git a/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_SystemArrayReturnType.cs b/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_SystemArrayReturnType.cs index c941601d..2c7dcde0 100644 --- a/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_SystemArrayReturnType.cs +++ b/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_SystemArrayReturnType.cs @@ -182,7 +182,7 @@ public Task M1() RenderContext renderContext = new(classInfo, [classInfo], RenderOptions.CSharp); string interopClass = new CSharpInteropClassRenderer(classInfo, renderContext, new JSObjectMethodResolver([])).Render(); - Assert.That(interopClass, Is.EqualTo(""" + AssertEx.EqualOrDiff(interopClass, """ #nullable enable // TypeShim generated TypeScript interop definitions using System; @@ -196,13 +196,7 @@ public partial class C1Interop public static Task M1([JSMarshalAs] object instance) { C1 typed_instance = (C1)instance; - TaskCompletionSource retValTcs = new(); - (typed_instance.M1()).ContinueWith(t => { - if (t.IsFaulted) retValTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) retValTcs.SetCanceled(); - else retValTcs.SetResult((object)t.Result); - }, TaskContinuationOptions.ExecuteSynchronously); - return retValTcs.Task; + return typed_instance.M1().ContinueWith(t => (object)t.Result, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); } public static C1 FromObject(object obj) { @@ -214,7 +208,7 @@ public static C1 FromObject(object obj) } } -""")); +"""); } [TestCase("Version", "new Version(1,2,3,4)")] diff --git a/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_SystemTaskParameterType.cs b/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_SystemTaskParameterType.cs index 675d1bcd..d0af4f0d 100644 --- a/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_SystemTaskParameterType.cs +++ b/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_SystemTaskParameterType.cs @@ -123,38 +123,32 @@ public static void M1(Task task) // Important assertion here, Task required for interop, cannot be simply casted to Task // the return type is void so we cannot await either, hence the TaskCompletionSource-based conversion. AssertEx.EqualOrDiff(interopClass, """ -#nullable enable -// TypeShim generated TypeScript interop definitions -using System; -using System.Runtime.InteropServices.JavaScript; -using System.Threading.Tasks; -namespace N1; -public partial class C1Interop -{ - [JSExport] - [return: JSMarshalAs] - public static void M1([JSMarshalAs>] Task task) - { - TaskCompletionSource taskTcs = new(); - (task).ContinueWith(t => { - if (t.IsFaulted) taskTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) taskTcs.SetCanceled(); - else taskTcs.SetResult(MyClassInterop.FromObject(t.Result)); - }, TaskContinuationOptions.ExecuteSynchronously); - Task typed_task = taskTcs.Task; - C1.M1(typed_task); - } - public static C1 FromObject(object obj) - { - return obj switch + #nullable enable + // TypeShim generated TypeScript interop definitions + using System; + using System.Runtime.InteropServices.JavaScript; + using System.Threading.Tasks; + namespace N1; + public partial class C1Interop { - C1 instance => instance, - _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), - }; - } -} + [JSExport] + [return: JSMarshalAs] + public static void M1([JSMarshalAs>] Task task) + { + Task typed_task = task.ContinueWith(t => MyClassInterop.FromObject(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); + C1.M1(typed_task); + } + public static C1 FromObject(object obj) + { + return obj switch + { + C1 instance => instance, + _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), + }; + } + } -"""); + """); } [Test] @@ -198,39 +192,33 @@ public static void M1(Task task) RenderContext renderContext = new(classInfo, [classInfo, userClassInfo], RenderOptions.CSharp); string interopClass = new CSharpInteropClassRenderer(classInfo, renderContext, new JSObjectMethodResolver([])).Render(); - Assert.That(interopClass, Is.EqualTo(""" -#nullable enable -// TypeShim generated TypeScript interop definitions -using System; -using System.Runtime.InteropServices.JavaScript; -using System.Threading.Tasks; -namespace N1; -public partial class C1Interop -{ - [JSExport] - [return: JSMarshalAs] - public static void M1([JSMarshalAs>] Task task) - { - TaskCompletionSource taskTcs = new(); - (task).ContinueWith(t => { - if (t.IsFaulted) taskTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) taskTcs.SetCanceled(); - else taskTcs.SetResult(MyClassInterop.FromObject(t.Result)); - }, TaskContinuationOptions.ExecuteSynchronously); - Task typed_task = taskTcs.Task; - C1.M1(typed_task); - } - public static C1 FromObject(object obj) - { - return obj switch + AssertEx.EqualOrDiff(interopClass, """ + #nullable enable + // TypeShim generated TypeScript interop definitions + using System; + using System.Runtime.InteropServices.JavaScript; + using System.Threading.Tasks; + namespace N1; + public partial class C1Interop { - C1 instance => instance, - _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), - }; - } -} + [JSExport] + [return: JSMarshalAs] + public static void M1([JSMarshalAs>] Task task) + { + Task typed_task = task.ContinueWith(t => MyClassInterop.FromObject(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); + C1.M1(typed_task); + } + public static C1 FromObject(object obj) + { + return obj switch + { + C1 instance => instance, + _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), + }; + } + } -""")); + """); } [TestCase("Version", "new Version(1,2,3,4)")] @@ -262,40 +250,34 @@ public static void M1(Task<{{typeName}}> task) string interopClass = new CSharpInteropClassRenderer(classInfo, renderContext, new JSObjectMethodResolver([])).Render(); // Note: as there is no known mapping for these types, there is no 'FromObject' mapping, instead just try to cast ("taskTcs.SetResult(({{typeName}})t.Result)") - // the user shouldnt be able to do much with the object anyway as its untyped on the JS/TS side. - Assert.That(interopClass, Is.EqualTo(""" -#nullable enable -// TypeShim generated TypeScript interop definitions -using System; -using System.Runtime.InteropServices.JavaScript; -using System.Threading.Tasks; -namespace N1; -public partial class C1Interop -{ - [JSExport] - [return: JSMarshalAs] - public static void M1([JSMarshalAs>] Task task) - { - TaskCompletionSource<{{typeName}}> taskTcs = new(); - (task).ContinueWith(t => { - if (t.IsFaulted) taskTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) taskTcs.SetCanceled(); - else taskTcs.SetResult(({{typeName}})t.Result); - }, TaskContinuationOptions.ExecuteSynchronously); - Task<{{typeName}}> typed_task = taskTcs.Task; - C1.M1(typed_task); - } - public static C1 FromObject(object obj) - { - return obj switch + // this is not broadly supported, only for types in the standard library + AssertEx.EqualOrDiff(interopClass, """ + #nullable enable + // TypeShim generated TypeScript interop definitions + using System; + using System.Runtime.InteropServices.JavaScript; + using System.Threading.Tasks; + namespace N1; + public partial class C1Interop { - C1 instance => instance, - _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), - }; - } -} - -""".Replace("{{typeName}}", typeName))); + [JSExport] + [return: JSMarshalAs] + public static void M1([JSMarshalAs>] Task task) + { + Task<{{typeName}}> typed_task = task.ContinueWith(t => ({{typeName}})t.Result, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); + C1.M1(typed_task); + } + public static C1 FromObject(object obj) + { + return obj switch + { + C1 instance => instance, + _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), + }; + } + } + + """.Replace("{{typeName}}", typeName)); } [Test] @@ -326,32 +308,32 @@ public void M1(Task? p1) string interopClass = new CSharpInteropClassRenderer(classInfo, renderContext, new JSObjectMethodResolver([])).Render(); AssertEx.EqualOrDiff(interopClass, """ -#nullable enable -// TypeShim generated TypeScript interop definitions -using System; -using System.Runtime.InteropServices.JavaScript; -using System.Threading.Tasks; -namespace N1; -public partial class C1Interop -{ - [JSExport] - [return: JSMarshalAs] - public static void M1([JSMarshalAs] object instance, [JSMarshalAs>] Task? p1) - { - C1 typed_instance = (C1)instance; - typed_instance.M1(p1); - } - public static C1 FromObject(object obj) - { - return obj switch + #nullable enable + // TypeShim generated TypeScript interop definitions + using System; + using System.Runtime.InteropServices.JavaScript; + using System.Threading.Tasks; + namespace N1; + public partial class C1Interop { - C1 instance => instance, - _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), - }; - } -} + [JSExport] + [return: JSMarshalAs] + public static void M1([JSMarshalAs] object instance, [JSMarshalAs>] Task? p1) + { + C1 typed_instance = (C1)instance; + typed_instance.M1(p1); + } + public static C1 FromObject(object obj) + { + return obj switch + { + C1 instance => instance, + _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), + }; + } + } -"""); + """); } [Test] @@ -379,57 +361,57 @@ public class C1 string interopClass = new CSharpInteropClassRenderer(classInfo, renderContext, new JSObjectMethodResolver([])).Render(); AssertEx.EqualOrDiff(interopClass, """ -#nullable enable -// TypeShim generated TypeScript interop definitions -using System; -using System.Runtime.InteropServices.JavaScript; -using System.Threading.Tasks; -namespace N1; -public partial class C1Interop -{ - [JSExport] - [return: JSMarshalAs] - public static object ctor([JSMarshalAs] JSObject jsObject) - { - using var _ = jsObject; - return new C1() - { - P1 = jsObject.GetPropertyAsTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject)), - }; - } - [JSExport] - [return: JSMarshalAs>] - public static Task get_P1([JSMarshalAs] object instance) - { - C1 typed_instance = (C1)instance; - return typed_instance.P1; - } - [JSExport] - [return: JSMarshalAs] - public static void set_P1([JSMarshalAs] object instance, [JSMarshalAs>] Task value) - { - C1 typed_instance = (C1)instance; - typed_instance.P1 = value; - } - public static C1 FromObject(object obj) - { - return obj switch - { - C1 instance => instance, - JSObject jsObj => FromJSObject(jsObj), - _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), - }; - } - public static C1 FromJSObject(JSObject jsObject) - { - using var _ = jsObject; - return new C1() + #nullable enable + // TypeShim generated TypeScript interop definitions + using System; + using System.Runtime.InteropServices.JavaScript; + using System.Threading.Tasks; + namespace N1; + public partial class C1Interop { - P1 = jsObject.GetPropertyAsTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject)), - }; - } -} + [JSExport] + [return: JSMarshalAs] + public static object ctor([JSMarshalAs] JSObject jsObject) + { + using var _ = jsObject; + return new C1() + { + P1 = jsObject.GetPropertyAsTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject)), + }; + } + [JSExport] + [return: JSMarshalAs>] + public static Task get_P1([JSMarshalAs] object instance) + { + C1 typed_instance = (C1)instance; + return typed_instance.P1; + } + [JSExport] + [return: JSMarshalAs] + public static void set_P1([JSMarshalAs] object instance, [JSMarshalAs>] Task value) + { + C1 typed_instance = (C1)instance; + typed_instance.P1 = value; + } + public static C1 FromObject(object obj) + { + return obj switch + { + C1 instance => instance, + JSObject jsObj => FromJSObject(jsObj), + _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), + }; + } + public static C1 FromJSObject(JSObject jsObject) + { + using var _ = jsObject; + return new C1() + { + P1 = jsObject.GetPropertyAsTaskNullable("P1") ?? throw new ArgumentException("Non-nullable property 'P1' missing or of invalid type", nameof(jsObject)), + }; + } + } -"""); + """); } } diff --git a/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_SystemTaskReturnType.cs b/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_SystemTaskReturnType.cs index 1608388c..69b851a9 100644 --- a/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_SystemTaskReturnType.cs +++ b/src/TypeShim.Generator.Tests/CSharp/CSharpInteropClassRendererTests_SystemTaskReturnType.cs @@ -119,38 +119,32 @@ public static Task M1() RenderContext renderContext = new(classInfo, [classInfo, userClassInfo], RenderOptions.CSharp); string interopClass = new CSharpInteropClassRenderer(classInfo, renderContext, new JSObjectMethodResolver([])).Render(); - Assert.That(interopClass, Is.EqualTo(""" -#nullable enable -// TypeShim generated TypeScript interop definitions -using System; -using System.Runtime.InteropServices.JavaScript; -using System.Threading.Tasks; -namespace N1; -public partial class C1Interop -{ - [JSExport] - [return: JSMarshalAs>] - public static Task M1() - { - TaskCompletionSource retValTcs = new(); - (C1.M1()).ContinueWith(t => { - if (t.IsFaulted) retValTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) retValTcs.SetCanceled(); - else retValTcs.SetResult((object)t.Result); - }, TaskContinuationOptions.ExecuteSynchronously); - return retValTcs.Task; - } - public static C1 FromObject(object obj) - { - return obj switch + AssertEx.EqualOrDiff(interopClass, """ + #nullable enable + // TypeShim generated TypeScript interop definitions + using System; + using System.Runtime.InteropServices.JavaScript; + using System.Threading.Tasks; + namespace N1; + public partial class C1Interop { - C1 instance => instance, - _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), - }; - } -} - -""")); + [JSExport] + [return: JSMarshalAs>] + public static Task M1() + { + return C1.M1().ContinueWith(t => (object)t.Result, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); + } + public static C1 FromObject(object obj) + { + return obj switch + { + C1 instance => instance, + _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), + }; + } + } + + """); } [Test] @@ -195,39 +189,33 @@ public Task M1() RenderContext renderContext = new(classInfo, [classInfo, userClassInfo], RenderOptions.CSharp); string interopClass = new CSharpInteropClassRenderer(classInfo, renderContext, new JSObjectMethodResolver([])).Render(); - Assert.That(interopClass, Is.EqualTo(""" -#nullable enable -// TypeShim generated TypeScript interop definitions -using System; -using System.Runtime.InteropServices.JavaScript; -using System.Threading.Tasks; -namespace N1; -public partial class C1Interop -{ - [JSExport] - [return: JSMarshalAs>] - public static Task M1([JSMarshalAs] object instance) - { - C1 typed_instance = (C1)instance; - TaskCompletionSource retValTcs = new(); - (typed_instance.M1()).ContinueWith(t => { - if (t.IsFaulted) retValTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) retValTcs.SetCanceled(); - else retValTcs.SetResult((object)t.Result); - }, TaskContinuationOptions.ExecuteSynchronously); - return retValTcs.Task; - } - public static C1 FromObject(object obj) - { - return obj switch + AssertEx.EqualOrDiff(interopClass, """ + #nullable enable + // TypeShim generated TypeScript interop definitions + using System; + using System.Runtime.InteropServices.JavaScript; + using System.Threading.Tasks; + namespace N1; + public partial class C1Interop { - C1 instance => instance, - _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), - }; - } -} + [JSExport] + [return: JSMarshalAs>] + public static Task M1([JSMarshalAs] object instance) + { + C1 typed_instance = (C1)instance; + return typed_instance.M1().ContinueWith(t => (object)t.Result, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); + } + public static C1 FromObject(object obj) + { + return obj switch + { + C1 instance => instance, + _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), + }; + } + } -""")); + """); } [TestCase("Version", "new Version(1,2,3,4)")] @@ -258,38 +246,32 @@ private C1() {} RenderContext renderContext = new(classInfo, [classInfo], RenderOptions.CSharp); string interopClass = new CSharpInteropClassRenderer(classInfo, renderContext, new JSObjectMethodResolver([])).Render(); - Assert.That(interopClass, Is.EqualTo(""" -#nullable enable -// TypeShim generated TypeScript interop definitions -using System; -using System.Runtime.InteropServices.JavaScript; -using System.Threading.Tasks; -namespace N1; -public partial class C1Interop -{ - [JSExport] - [return: JSMarshalAs>] - public static Task M1() - { - TaskCompletionSource retValTcs = new(); - (C1.M1()).ContinueWith(t => { - if (t.IsFaulted) retValTcs.SetException(t.Exception.InnerExceptions); - else if (t.IsCanceled) retValTcs.SetCanceled(); - else retValTcs.SetResult((object)t.Result); - }, TaskContinuationOptions.ExecuteSynchronously); - return retValTcs.Task; - } - public static C1 FromObject(object obj) - { - return obj switch + AssertEx.EqualOrDiff(interopClass, """ + #nullable enable + // TypeShim generated TypeScript interop definitions + using System; + using System.Runtime.InteropServices.JavaScript; + using System.Threading.Tasks; + namespace N1; + public partial class C1Interop { - C1 instance => instance, - _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), - }; - } -} - -""".Replace("{{typeName}}", typeName))); + [JSExport] + [return: JSMarshalAs>] + public static Task M1() + { + return C1.M1().ContinueWith(t => (object)t.Result, TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously); + } + public static C1 FromObject(object obj) + { + return obj switch + { + C1 instance => instance, + _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), + }; + } + } + + """); } [Test] @@ -320,33 +302,33 @@ public Task M1() RenderContext renderContext = new(classInfo, [classInfo], RenderOptions.CSharp); string interopClass = new CSharpInteropClassRenderer(classInfo, renderContext, new JSObjectMethodResolver([])).Render(); - Assert.That(interopClass, Is.EqualTo(""" -#nullable enable -// TypeShim generated TypeScript interop definitions -using System; -using System.Runtime.InteropServices.JavaScript; -using System.Threading.Tasks; -namespace N1; -public partial class C1Interop -{ - [JSExport] - [return: JSMarshalAs>] - public static Task M1([JSMarshalAs] object instance) - { - C1 typed_instance = (C1)instance; - return typed_instance.M1(); - } - public static C1 FromObject(object obj) - { - return obj switch + AssertEx.EqualOrDiff(interopClass, """ + #nullable enable + // TypeShim generated TypeScript interop definitions + using System; + using System.Runtime.InteropServices.JavaScript; + using System.Threading.Tasks; + namespace N1; + public partial class C1Interop { - C1 instance => instance, - _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), - }; - } -} + [JSExport] + [return: JSMarshalAs>] + public static Task M1([JSMarshalAs] object instance) + { + C1 typed_instance = (C1)instance; + return typed_instance.M1(); + } + public static C1 FromObject(object obj) + { + return obj switch + { + C1 instance => instance, + _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), + }; + } + } -""")); + """); } [Test] @@ -377,32 +359,32 @@ private C1() {} RenderContext renderContext = new(classInfo, [classInfo], RenderOptions.CSharp); string interopClass = new CSharpInteropClassRenderer(classInfo, renderContext, new JSObjectMethodResolver([])).Render(); - Assert.That(interopClass, Is.EqualTo(""" -#nullable enable -// TypeShim generated TypeScript interop definitions -using System; -using System.Runtime.InteropServices.JavaScript; -using System.Threading.Tasks; -namespace N1; -public partial class C1Interop -{ - [JSExport] - [return: JSMarshalAs>] - public static Task? M1([JSMarshalAs] object instance) - { - C1 typed_instance = (C1)instance; - return typed_instance.M1(); - } - public static C1 FromObject(object obj) - { - return obj switch + AssertEx.EqualOrDiff(interopClass, """ + #nullable enable + // TypeShim generated TypeScript interop definitions + using System; + using System.Runtime.InteropServices.JavaScript; + using System.Threading.Tasks; + namespace N1; + public partial class C1Interop { - C1 instance => instance, - _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), - }; - } -} + [JSExport] + [return: JSMarshalAs>] + public static Task? M1([JSMarshalAs] object instance) + { + C1 typed_instance = (C1)instance; + return typed_instance.M1(); + } + public static C1 FromObject(object obj) + { + return obj switch + { + C1 instance => instance, + _ => throw new ArgumentException($"Invalid object type {obj?.GetType().ToString() ?? "null"}", nameof(obj)), + }; + } + } -""")); + """); } } From 47776bb125b4c82510a19ac1f3bc385ebdbd356d Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sat, 28 Feb 2026 20:56:47 +0100 Subject: [PATCH 3/4] remove unused method --- src/TypeShim.Generator.Tests/AssertEx.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/TypeShim.Generator.Tests/AssertEx.cs b/src/TypeShim.Generator.Tests/AssertEx.cs index 0a684c79..32c2cfb6 100644 --- a/src/TypeShim.Generator.Tests/AssertEx.cs +++ b/src/TypeShim.Generator.Tests/AssertEx.cs @@ -35,15 +35,6 @@ public static void EqualOrDiff( Assert.Fail(message); } - // Convenience for multi-line sequences (e.g., split lines and compare) - public static void EqualLinesOrDiff(IEnumerable expectedLines, IEnumerable actualLines, string? userMessage = null, int unifiedContext = 2) - { - var expected = string.Join("\n", expectedLines ?? Enumerable.Empty()); - var actual = string.Join("\n", actualLines ?? Enumerable.Empty()); - EqualOrDiff(expected, actual, normalizeLineEndings: true, trimTrailingWhitespace: false, scrubber: null, unifiedContext: unifiedContext, userMessage: userMessage); - } - - // Preprocess input to reduce noise in diffs private static string Preprocess(string input, bool normalizeLineEndings, bool trimTrailingWhitespace, Func? scrubber) { var s = input; From fa976a6047a6fde890b4d81d0d556b9bc23ec985 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sat, 28 Feb 2026 21:03:14 +0100 Subject: [PATCH 4/4] remove last of interoptypeinfo approach --- src/TypeShim.Shared/InteropTypeInfo.cs | 29 +------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/src/TypeShim.Shared/InteropTypeInfo.cs b/src/TypeShim.Shared/InteropTypeInfo.cs index 64f49ecb..8606bbc6 100644 --- a/src/TypeShim.Shared/InteropTypeInfo.cs +++ b/src/TypeShim.Shared/InteropTypeInfo.cs @@ -32,7 +32,7 @@ internal sealed class InteropTypeInfo public required TypeSyntax CSharpInteropTypeSyntax { get; init; } /// - /// Tasks and Arrays _may_ have type arguments. Nullables always do. + /// Tasks and _may_ have type arguments. Arrays and Nullables always do. /// public required InteropTypeInfo? TypeArgument { get; init; } @@ -63,33 +63,6 @@ public InteropTypeInfo GetInnermostType() } } - /// - /// Transforms this into one suitable for interop method signatures. - /// - /// - public InteropTypeInfo AsInteropTypeInfo() - { - return new InteropTypeInfo - { - IsTSExport = false, - ManagedType = this.ManagedType, - JSTypeSyntax = this.JSTypeSyntax, - CSharpTypeSyntax = this.CSharpInteropTypeSyntax, // essentially overwrite clr type with interop type - CSharpInteropTypeSyntax = this.CSharpInteropTypeSyntax, - IsTaskType = this.IsTaskType, - IsArrayType = this.IsArrayType, - IsNullableType = this.IsNullableType, - TypeArgument = TypeArgument?.AsInteropTypeInfo(), - ArgumentInfo = this.ArgumentInfo is not DelegateArgumentInfo argInfo ? null : new DelegateArgumentInfo() - { - ParameterTypes = [.. argInfo.ParameterTypes.Select(argType => argType.AsInteropTypeInfo())], - ReturnType = argInfo.ReturnType.AsInteropTypeInfo() - }, - RequiresTypeConversion = false, - SupportsTypeConversion = false, - }; - } - public static readonly InteropTypeInfo JSObjectTypeInfo = new() { IsTSExport = false,