diff --git a/docs/private-projection.md b/docs/private-projection.md deleted file mode 100644 index 2339944edf..0000000000 --- a/docs/private-projection.md +++ /dev/null @@ -1,19 +0,0 @@ -# Private Projection Support - -> **⚠️ This document has not been updated for CsWinRT 3.0.** Private projection support is a CsWinRT 2.x feature. Its status in CsWinRT 3.0 has not been determined yet. - -## Overview - -Support for private projections is an option on the C#/WinRT tool that allows for projections to be generated with accessibility scoped to the module generating the projection. - -## How-To - -The process is nearly identical to how you generate a projection today. The difference is a new set of build properties that look similar to the properties used to generate projections globally. -First you must set a property `CsWinRTPrivateProjection` to `true`. Then specify your includes/excludes using `CsWinRTIncludesPrivate` and `CsWinRTExcludesPrivate`. - -## Notes - -Note that this means projected types are not accessible outside the module. - -You can generate part of your projection as global and part as private, but you must take care to keep the types disjoint otherwise you will generate duplicates of the types projected as both. -Similarly, an app can't reference two libraries that both make the same private projection -- this is an ambiguity issue because types of the same name exist in different assemblies. diff --git a/nuget/Microsoft.Windows.CsWinRT.targets b/nuget/Microsoft.Windows.CsWinRT.targets index cb4a1adc7d..e199ab83ff 100644 --- a/nuget/Microsoft.Windows.CsWinRT.targets +++ b/nuget/Microsoft.Windows.CsWinRT.targets @@ -230,12 +230,10 @@ Copyright (C) Microsoft Corporation. All rights reserved. $(CsWinRTGeneratedFilesDir)cswinrt.rsp - $(CsWinRTGeneratedFilesDir)cswinrt_internal.rsp "$(CsWinRTExe)" %40"$(CsWinRTResponseFile)" - "$(CsWinRTExe)" %40"$(CsWinRTResponseFilePrivateProjection)" -input $(CsWinRTWindowsMetadata) @@ -249,26 +247,14 @@ Copyright (C) Microsoft Corporation. All rights reserved. Windows;Microsoft - Windows;Microsoft - - - - - - - -@(CsWinRTExcludePrivateItems->'-exclude %(Identity)', ' ') -@(CsWinRTIncludePrivateItems->'-include %(Identity)', ' ') - - @(CsWinRTExcludeItems->'-exclude %(Identity)', ' ') @(CsWinRTIncludeItems->'-include %(Identity)', ' ') @@ -280,15 +266,6 @@ Copyright (C) Microsoft Corporation. All rights reserved. -include WindowsRuntime.Internal - - --input $(CsWinRTInteropMetadata) --include WindowsRuntime.Internal - - - -internal - -embedded - -public_enums -public_exclusiveto -idic_exclusiveto -reference_projection @@ -301,23 +278,10 @@ $(CsWinRTWindowsMetadataInput) -output "$(CsWinRTGeneratedFilesDir.TrimEnd('\'))" $(CsWinRTFilters) $(CsWinRTIncludeWinRTInterop) -$(CsWinRTEmbeddedProjection) -$(CsWinRTEmbeddedEnums) $(CsWinRTPublicExclusiveTo) $(CsWinRTDynamicallyInterfaceCastableExclusiveTo) $(CsWinRTReferenceProjection) - - -$(CsWinRTCommandVerbosity) --target $(CsWinRTExeTFM) -$(CsWinRTWindowsMetadataInput) --input @(CsWinRTInputs->'"%(FullPath)"', ' ') --output "$(CsWinRTGeneratedFilesDir.TrimEnd('\'))" -$(CsWinRTPrivateFilters) -$(CsWinRTPrivateIncludeWinRTInterop) -$(CsWinRTInternalProjection) - <_CsWinRTRefFilterLines Include="$([System.Text.RegularExpressions.Regex]::Split('$(CsWinRTFilters)', '[\r\n]+'))" /> - <_CsWinRTRefPrivateFilterLines Include="$([System.Text.RegularExpressions.Regex]::Split('$(CsWinRTPrivateFilters)', '[\r\n]+'))" /> <_CsWinRTRefIncludes Include="$([System.String]::Copy('%(_CsWinRTRefFilterLines.Identity)').Trim().Substring(9))" Condition="$([System.String]::Copy('%(_CsWinRTRefFilterLines.Identity)').Trim().StartsWith('-include '))" /> <_CsWinRTRefExcludes Include="$([System.String]::Copy('%(_CsWinRTRefFilterLines.Identity)').Trim().Substring(9))" Condition="$([System.String]::Copy('%(_CsWinRTRefFilterLines.Identity)').Trim().StartsWith('-exclude '))" /> - - <_CsWinRTRefPrivateIncludes Include="$([System.String]::Copy('%(_CsWinRTRefPrivateFilterLines.Identity)').Trim().Substring(9))" - Condition="$([System.String]::Copy('%(_CsWinRTRefPrivateFilterLines.Identity)').Trim().StartsWith('-include '))" /> - <_CsWinRTRefPrivateExcludes Include="$([System.String]::Copy('%(_CsWinRTRefPrivateFilterLines.Identity)').Trim().Substring(9))" - Condition="$([System.String]::Copy('%(_CsWinRTRefPrivateFilterLines.Identity)').Trim().StartsWith('-exclude '))" /> <_CsWinRTRefHasWindows Include="@(_CsWinRTRefIncludes)" Condition="'%(Identity)' == 'Windows'" /> - <_CsWinRTRefPrivateHasWindows Include="@(_CsWinRTRefPrivateIncludes)" Condition="'%(Identity)' == 'Windows'" /> <_CsWinRTRefIncludes Include="WindowsRuntime.Internal" /> <_CsWinRTRefInteropInputs Include="$(CsWinRTInteropMetadata)" /> - - <_CsWinRTRefPrivateIncludes Include="WindowsRuntime.Internal" /> - <_CsWinRTRefPrivateInteropInputs Include="$(CsWinRTInteropMetadata)" /> - <_CsWinRTRefInputs Include="@(CsWinRTInputs->'%(FullPath)')" /> <_CsWinRTRefInputs Include="$(CsWinRTWindowsMetadata)" Condition="'$(CsWinRTWindowsMetadata)' != ''" /> <_CsWinRTRefInputs Include="@(_CsWinRTRefInteropInputs)" /> - - <_CsWinRTRefPrivateInputs Include="@(CsWinRTInputs->'%(FullPath)')" /> - <_CsWinRTRefPrivateInputs Include="$(CsWinRTWindowsMetadata)" Condition="'$(CsWinRTWindowsMetadata)' != ''" /> - <_CsWinRTRefPrivateInputs Include="@(_CsWinRTRefPrivateInteropInputs)" /> - + - - - - - @@ -440,7 +367,7 @@ $(CsWinRTInternalProjection) - + diff --git a/src/WinRT.Generator.Tasks/RunCsWinRTProjectionRefGenerator.cs b/src/WinRT.Generator.Tasks/RunCsWinRTProjectionRefGenerator.cs index 187ba044d7..dd47ab8a03 100644 --- a/src/WinRT.Generator.Tasks/RunCsWinRTProjectionRefGenerator.cs +++ b/src/WinRT.Generator.Tasks/RunCsWinRTProjectionRefGenerator.cs @@ -73,25 +73,6 @@ public sealed class RunCsWinRTProjectionRefGenerator : ToolTask /// public bool Component { get; set; } - /// - /// Gets or sets whether to generate a private (internal) projection. Mirrors the C++ '-internal' arg. - /// CsWinRT 3.0 leftover; preserved for OLD-target parity. - /// - public bool InternalProjection { get; set; } - - /// - /// Gets or sets whether to generate an embedded projection. Mirrors the C++ '-embedded' arg. - /// CsWinRT 3.0 leftover; preserved for OLD-target parity. - /// - public bool Embedded { get; set; } - - /// - /// Gets or sets whether to emit enums as public when used with the embedded option. - /// Mirrors the C++ '-public_enums' arg. - /// CsWinRT 3.0 leftover; preserved for OLD-target parity. - /// - public bool PublicEnums { get; set; } - /// /// Gets or sets whether to make exclusive-to interfaces public in the projection /// (default is internal). Mirrors the C++ '-public_exclusiveto' arg. @@ -258,21 +239,6 @@ protected override string GenerateResponseFileCommands() AppendResponseFileCommand(args, "--component", "true"); } - if (InternalProjection) - { - AppendResponseFileCommand(args, "--internal", "true"); - } - - if (Embedded) - { - AppendResponseFileCommand(args, "--embedded", "true"); - } - - if (PublicEnums) - { - AppendResponseFileCommand(args, "--public-enums", "true"); - } - if (PublicExclusiveTo) { AppendResponseFileCommand(args, "--public-exclusive-to", "true"); diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index a73c9e6843..52b1e960a3 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -103,9 +103,6 @@ private static ProjectionWriterOptions BuildWriterOptions(ReferenceProjectionGen AdditionExclude = args.AdditionExcludeNamespaces, Verbose = args.Verbose, Component = args.Component, - Internal = args.Internal, - Embedded = args.Embedded, - PublicEnums = args.PublicEnums, PublicExclusiveTo = args.PublicExclusiveTo, IdicExclusiveTo = args.IdicExclusiveTo, ReferenceProjection = args.ReferenceProjection, diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs index 3b9ef440b6..da06dbfa58 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs @@ -87,9 +87,6 @@ public static ReferenceProjectionGeneratorArgs ParseFromResponseFile(string path AdditionExcludeNamespaces = GetOptionalStringArrayArgument(argsMap, nameof(AdditionExcludeNamespaces)), Verbose = GetOptionalBoolArgument(argsMap, nameof(Verbose)), Component = GetOptionalBoolArgument(argsMap, nameof(Component)), - Internal = GetOptionalBoolArgument(argsMap, nameof(Internal)), - Embedded = GetOptionalBoolArgument(argsMap, nameof(Embedded)), - PublicEnums = GetOptionalBoolArgument(argsMap, nameof(PublicEnums)), PublicExclusiveTo = GetOptionalBoolArgument(argsMap, nameof(PublicExclusiveTo)), IdicExclusiveTo = GetOptionalBoolArgument(argsMap, nameof(IdicExclusiveTo)), ReferenceProjection = GetOptionalBoolArgument(argsMap, nameof(ReferenceProjection)), diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs index c5667eccff..c6a2b787c3 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs @@ -46,27 +46,6 @@ internal sealed partial class ReferenceProjectionGeneratorArgs [CommandLineArgumentName("--component")] public bool Component { get; init; } - /// - /// Gets whether to generate a private (internal) projection. - /// CsWinRT 3.0 leftover; preserved for OLD-target parity. - /// - [CommandLineArgumentName("--internal")] - public bool Internal { get; init; } - - /// - /// Gets whether to generate an embedded projection. - /// CsWinRT 3.0 leftover; preserved for OLD-target parity. - /// - [CommandLineArgumentName("--embedded")] - public bool Embedded { get; init; } - - /// - /// Gets whether to emit enums as public when used with the embedded option. - /// CsWinRT 3.0 leftover; preserved for OLD-target parity. - /// - [CommandLineArgumentName("--public-enums")] - public bool PublicEnums { get; init; } - /// Gets whether to make exclusive-to interfaces public in the projection. [CommandLineArgumentName("--public-exclusive-to")] public bool PublicExclusiveTo { get; init; } diff --git a/src/WinRT.Projection.Writer.TestRunner/Program.cs b/src/WinRT.Projection.Writer.TestRunner/Program.cs index 86dbcd2986..4b7c0efc04 100644 --- a/src/WinRT.Projection.Writer.TestRunner/Program.cs +++ b/src/WinRT.Projection.Writer.TestRunner/Program.cs @@ -40,6 +40,10 @@ public static int Main(string[] args) { return RunRsp(args[1], refMode); } + if (args.Length >= 1 && args[0] == "smoke") + { + return RunSmoke(); + } return RunSimple(args); } @@ -57,7 +61,7 @@ private static int RunRsp(string rspPath, bool refMode) var include = new System.Collections.Generic.List(); var exclude = new System.Collections.Generic.List(); string? outputFolder = null; - bool component = false, internalMode = false; + bool component = false; int maxDegreesOfParallelism = -1; var tokens = new System.Collections.Generic.List(); foreach (string raw in text.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)) @@ -83,7 +87,6 @@ private static int RunRsp(string rspPath, bool refMode) case "--exclude-namespaces": case "--exclude": if (next is not null) { exclude.AddRange(next.Split(',', StringSplitOptions.RemoveEmptyEntries)); i++; } break; case "--component": component = true; break; - case "--internal": internalMode = true; break; case "--reference-projection": refMode = true; break; case "--max-degrees-of-parallelism": case "--mdop": if (next is not null && int.TryParse(next, out int mdop)) { maxDegreesOfParallelism = mdop; i++; } break; @@ -99,7 +102,7 @@ private static int RunRsp(string rspPath, bool refMode) { InputPaths = inputs, OutputFolder = outputFolder, Include = include, Exclude = exclude, - Component = component, Internal = internalMode, + Component = component, ReferenceProjection = refMode, Verbose = false, MaxDegreesOfParallelism = maxDegreesOfParallelism, }); @@ -467,5 +470,205 @@ private static int RunCompareAuthoring(string output) Console.WriteLine($"Generated {Directory.GetFiles(output, "*.cs").Length} files in {output}"); return 0; } + + /// + /// Smoke tests for the suppressEmptyLines overload on + /// 's interpolated + /// string handler. Validates that: + /// + /// When all interpolation holes on a template line expand to empty content, the line's + /// blank residue is suppressed (no stray newline emitted). + /// When at least one hole on a line emits content, no suppression occurs. + /// Literal blank lines in the template (consecutive newlines not separated by a hole) + /// are always preserved. + /// The legacy overload (without suppressEmptyLines) behaves identically to before. + /// + /// + private static int RunSmoke() + { + int failures = 0; + + WindowsRuntime.ProjectionWriter.Writers.IndentedTextWriter Make() => + new WindowsRuntime.ProjectionWriter.Writers.IndentedTextWriter(); + + // Test cases: each is (description, action_producing_string, expected_string). + var tests = new System.Collections.Generic.List<(string desc, Func actual, string expected)> + { + // Case 1: all 3 attribute holes empty → only header remains. + ("S1: all-empty middle holes collapse to nothing", + () => { + var w = Make(); + string a = string.Empty, b = string.Empty, c = string.Empty; + w.WriteLine(isMultiline: true, $$""" + [Header] + {{a}} + {{b}} + {{c}} + public class Foo + """); + return w.ToString(); + }, + "[Header]\npublic class Foo\n"), + + // Case 2: first hole non-empty, others empty → first kept, others collapsed. + ("S2: partial-empty holes collapse only the empty ones", + () => { + var w = Make(); + string a = "[A]", b = string.Empty, c = string.Empty; + w.WriteLine(isMultiline: true, $$""" + [Header] + {{a}} + {{b}} + {{c}} + public class Foo + """); + return w.ToString(); + }, + "[Header]\n[A]\npublic class Foo\n"), + + // Case 3: all holes non-empty → every line preserved. + ("S3: all-non-empty holes preserved", + () => { + var w = Make(); + string a = "[A]", b = "[B]", c = "[C]"; + w.WriteLine(isMultiline: true, $$""" + [Header] + {{a}} + {{b}} + {{c}} + public class Foo + """); + return w.ToString(); + }, + "[Header]\n[A]\n[B]\n[C]\npublic class Foo\n"), + + // Case 4: literal blank line preserved (no hole between newlines). + ("S4: literal blank line is preserved (no hole between newlines)", + () => { + var w = Make(); + string a = "[A]"; + w.WriteLine(isMultiline: true, $$""" + Line1 + + {{a}} + Line3 + """); + return w.ToString(); + }, + "Line1\n\n[A]\nLine3\n"), + + // Case 5: multiple empty holes on a single line → still collapsed. + ("S5: multiple empty holes on one line still collapse", + () => { + var w = Make(); + string a = string.Empty, b = string.Empty; + w.WriteLine(isMultiline: true, $$""" + [Header] + {{a}}{{b}} + public class Foo + """); + return w.ToString(); + }, + "[Header]\npublic class Foo\n"), + + // Case 6: hole at start of template, empty - buffer is empty so suppress rule doesn't fire. + ("S6: leading empty hole produces a leading blank line (buffer is empty)", + () => { + var w = Make(); + string a = string.Empty; + w.WriteLine(isMultiline: true, $$""" + {{a}} + public class Foo + """); + return w.ToString(); + }, + "\npublic class Foo\n"), + + // Case 7: Write (not WriteLine) - no trailing newline appended by the call. + ("S7: Write (no trailing newline) with all-empty interior holes", + () => { + var w = Make(); + string a = string.Empty, b = string.Empty; + w.Write(isMultiline: true, $$""" + [Header] + {{a}} + {{b}} + public class Foo + """); + return w.ToString(); + }, + "[Header]\npublic class Foo"), + + // Case 8: indentation still applied to non-empty lines. + ("S8: indented writer with mixed empty/non-empty holes", + () => { + var w = Make(); + w.IncreaseIndent(); + string a = string.Empty, b = "[B]"; + w.WriteLine(isMultiline: true, $$""" + [Header] + {{a}} + {{b}} + public class Foo + """); + return w.ToString(); + }, + " [Header]\n [B]\n public class Foo\n"), + + // Case 9: literal-only multiline template (no holes) is unaffected. + ("S9: literal-only template (no holes) is unaffected", + () => { + var w = Make(); + w.WriteLine(isMultiline: true, $$""" + Line1 + Line2 + + Line4 + """); + return w.ToString(); + }, + "Line1\nLine2\n\nLine4\n"), + + // Case 10: callback-style interpolation: empty callback hole is collapsed. + ("S10: empty string callback collapsed identically to empty string hole", + () => { + var w = Make(); + string a = "[A]", b = string.Empty; + w.WriteLine(isMultiline: true, $$""" + [Header] + {{a}} + {{b}} + public class Foo + """); + return w.ToString(); + }, + "[Header]\n[A]\npublic class Foo\n"), + }; + + foreach ((string desc, Func actualFn, string expected) in tests) + { + string actual = actualFn(); + bool pass = actual == expected; + if (!pass) + { + failures++; + } + + Console.WriteLine($"{(pass ? "PASS" : "FAIL")}: {desc}"); + + if (!pass) + { + Console.WriteLine($" expected: {Escape(expected)}"); + Console.WriteLine($" actual: {Escape(actual)}"); + } + } + + Console.WriteLine(); + Console.WriteLine($"=== {tests.Count - failures}/{tests.Count} smoke tests passed ==="); + + return failures == 0 ? 0 : 1; + + static string Escape(string s) => s.Replace("\\", "\\\\").Replace("\n", "\\n").Replace("\r", "\\r").Replace("\t", "\\t"); + } } diff --git a/src/WinRT.Projection.Writer/Builders/ProjectionFileBuilder.cs b/src/WinRT.Projection.Writer/Builders/ProjectionFileBuilder.cs index 5059b38a5b..6e43119d1b 100644 --- a/src/WinRT.Projection.Writer/Builders/ProjectionFileBuilder.cs +++ b/src/WinRT.Projection.Writer/Builders/ProjectionFileBuilder.cs @@ -1,13 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Generic; -using System.Globalization; using AsmResolver.DotNet; -using AsmResolver.PE.DotNet.Metadata.Tables; using WindowsRuntime.ProjectionWriter.Errors; using WindowsRuntime.ProjectionWriter.Factories; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; @@ -25,105 +23,95 @@ internal static class ProjectionFileBuilder /// /// Dispatches type emission based on the type category. /// - public static void WriteType(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type, TypeCategory category) + public static void WriteType(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type, TypeKind kind) { - switch (category) + switch (kind) { - case TypeCategory.Class: - if (TypeCategorization.IsAttributeType(type)) - { - WriteAttribute(writer, context, type); - } - else - { - ClassFactory.WriteClass(writer, context, type); - } - + case TypeKind.Class when type.IsAttributeType: + WriteAttribute(writer, context, type); break; - case TypeCategory.Delegate: + case TypeKind.Class: + ClassFactory.WriteClass(writer, context, type); + break; + case TypeKind.Delegate: WriteDelegate(writer, context, type); break; - case TypeCategory.Enum: + case TypeKind.Enum: WriteEnum(writer, context, type); break; - case TypeCategory.Interface: + case TypeKind.Interface: InterfaceFactory.WriteInterface(writer, context, type); break; - case TypeCategory.Struct: - if (TypeCategorization.IsApiContractType(type)) - { - WriteContract(writer, context, type); - } - else - { - WriteStruct(writer, context, type); - } - + case TypeKind.Struct when type.IsApiContractType: + WriteContract(writer, context, type); + break; + case TypeKind.Struct: + WriteStruct(writer, context, type); break; default: - throw WellKnownProjectionWriterExceptions.UnknownTypeCategory(category); + throw WellKnownProjectionWriterExceptions.UnknownTypeKind(kind); } } /// /// Dispatches ABI emission based on the type category. /// - public static void WriteAbiType(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type, TypeCategory category) + public static void WriteAbiType(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type, TypeKind kind) { - switch (category) + switch (kind) { - case TypeCategory.Class: + case TypeKind.Class: AbiClassFactory.WriteAbiClass(writer, context, type); break; - case TypeCategory.Delegate: + case TypeKind.Delegate: AbiDelegateFactory.WriteAbiDelegate(writer, context, type); AbiDelegateFactory.WriteDelegateEventSourceSubclass(writer, context, type); break; - case TypeCategory.Enum: + case TypeKind.Enum: AbiEnumFactory.WriteAbiEnum(writer, context, type); break; - case TypeCategory.Interface: + case TypeKind.Interface: AbiInterfaceFactory.WriteAbiInterface(writer, context, type); break; - case TypeCategory.Struct: + case TypeKind.Struct: AbiStructFactory.WriteAbiStruct(writer, context, type); break; default: - throw WellKnownProjectionWriterExceptions.UnknownTypeCategory(category); + throw WellKnownProjectionWriterExceptions.UnknownTypeKind(kind); } } /// /// Writes a projected enum (with [Flags] when applicable). /// - public static void WriteEnum(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) + private static void WriteEnum(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { if (context.Settings.Component) { return; } - bool isFlags = TypeCategorization.IsFlagsEnum(type); + bool isFlags = type.IsFlagsEnum; string enumUnderlyingType = isFlags ? "uint" : "int"; - string accessibility = context.Settings.Internal ? "internal" : "public"; - string typeName = type.Name?.Value ?? string.Empty; + string typeName = type.GetRawName(); - writer.WriteLine(); + WriteWinRTMetadataAttributeCallback metadataAttr = MetadataAttributeFactory.WriteWinRTMetadataAttribute(type, context.Cache); + WriteValueTypeWinRTClassNameAttributeCallback valueTypeAttr = MetadataAttributeFactory.WriteValueTypeWinRTClassNameAttribute(context, type); + WriteTypeCustomAttributesCallback customAttrs = CustomAttributeFactory.WriteTypeCustomAttributes(context, type, true); + WriteComWrapperMarshallerAttributeCallback comWrappersAttr = MetadataAttributeFactory.WriteComWrapperMarshallerAttribute(context, type); + WriteWinRTReferenceTypeAttributeCallback refTypeAttr = MetadataAttributeFactory.WriteWinRTReferenceTypeAttribute(context, type); - if (isFlags) - { - writer.WriteLine("[Flags]"); - } - - MetadataAttributeFactory.WriteWinRTMetadataAttribute(writer, type, context.Cache); - MetadataAttributeFactory.WriteValueTypeWinRTClassNameAttribute(writer, context, type); - CustomAttributeFactory.WriteTypeCustomAttributes(writer, context, type, true); - MetadataAttributeFactory.WriteComWrapperMarshallerAttribute(writer, context, type); - MetadataAttributeFactory.WriteWinRTReferenceTypeAttribute(writer, context, type); + writer.WriteLine(); + writer.WriteLineIf(isFlags, "[Flags]"); + writer.WriteLine(isMultiline: true, $$""" + {{metadataAttr}} + {{valueTypeAttr}} + {{customAttrs}} + {{comWrappersAttr}} + {{refTypeAttr}} + public enum {{typeName}} : {{enumUnderlyingType}} + """); - writer.WriteLine($$""" - {{accessibility}} enum {{typeName}} : {{enumUnderlyingType}} - """, isMultiline: true); using (writer.WriteBlock()) { foreach (FieldDefinition field in type.Fields) @@ -133,62 +121,32 @@ public static void WriteEnum(IndentedTextWriter writer, ProjectionEmitContext co continue; } - string fieldName = field.Name?.Value ?? string.Empty; - string constantValue = FormatConstant(field.Constant); - // Emits per-enum-field [SupportedOSPlatform] when the field has a [ContractVersion]. + string fieldName = field.GetRawName(); + string constantValue = field.Constant.FormatLiteral(); + + // Emits per-enum-field '[SupportedOSPlatform]' when the field has a '[ContractVersion]' CustomAttributeFactory.WritePlatformAttribute(writer, context, field); + writer.WriteLine($"{fieldName} = unchecked(({enumUnderlyingType}){constantValue}),"); } } - writer.WriteLine(); - } - - /// - /// Formats a metadata Constant value as a C# literal. - /// - internal static string FormatConstant(Constant constant) - { - // The Constant.Value contains raw bytes representing the value - ElementType type = constant.Type; - byte[] data = constant.Value?.Data ?? []; - return type switch - { - ElementType.I1 => ((sbyte)data[0]).ToString(CultureInfo.InvariantCulture), - ElementType.U1 => data[0].ToString(CultureInfo.InvariantCulture), - ElementType.I2 => BitConverter.ToInt16(data, 0).ToString(CultureInfo.InvariantCulture), - ElementType.U2 => BitConverter.ToUInt16(data, 0).ToString(CultureInfo.InvariantCulture), - // I4/U4 use printf "%#0x" semantics: 0 -> "0", non-zero -> "0x" - ElementType.I4 => FormatHexAlternate((uint)BitConverter.ToInt32(data, 0)), - ElementType.U4 => FormatHexAlternate(BitConverter.ToUInt32(data, 0)), - ElementType.I8 => BitConverter.ToInt64(data, 0).ToString(CultureInfo.InvariantCulture), - ElementType.U8 => BitConverter.ToUInt64(data, 0).ToString(CultureInfo.InvariantCulture), - _ => "0" - }; - } - private static string FormatHexAlternate(uint v) - { - // Match printf "%#0x" semantics: for 0, output "0"; for non-zero, output "0x" with no padding. - if (v == 0) - { - return "0"; - } - - return "0x" + v.ToString("x", CultureInfo.InvariantCulture); + writer.WriteLine(); } /// /// Writes a projected struct. /// - public static void WriteStruct(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) + private static void WriteStruct(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { if (context.Settings.Component) { return; } - // Collect field info List<(string TypeStr, string Name, string ParamName, bool IsInterface)> fields = []; + + // Collect field info foreach (FieldDefinition field in type.Fields) { if (field.IsStatic || field.Signature is null) @@ -197,132 +155,134 @@ public static void WriteStruct(IndentedTextWriter writer, ProjectionEmitContext } TypeSemantics semantics = TypeSemanticsFactory.Get(field.Signature.FieldType); - string fieldType = TypedefNameWriter.WriteProjectionType(context, semantics); - string fieldName = field.Name?.Value ?? string.Empty; + string fieldType = TypedefNameWriter.WriteProjectionType(context, semantics).Format(); + string fieldName = field.GetRawName(); string paramName = IdentifierEscaping.ToCamelCase(fieldName); bool isInterface = false; if (semantics is TypeSemantics.Definition d) { - isInterface = TypeCategorization.GetCategory(d.Type) == TypeCategory.Interface; + isInterface = d.Type.IsInterface; } else if (semantics is TypeSemantics.GenericInstance gi) { - isInterface = TypeCategorization.GetCategory(gi.GenericType) == TypeCategory.Interface; + isInterface = gi.GenericType.IsInterface; } fields.Add((fieldType, fieldName, paramName, isInterface)); } - string projectionName = type.Name?.Value ?? string.Empty; - bool hasAddition = AdditionTypes.HasAdditionToType(type.Namespace?.Value ?? string.Empty, projectionName); + string projectionName = type.GetRawName(); + + // Header attributes + struct declaration as a single multiline template. + WriteWinRTMetadataAttributeCallback metadataAttr = MetadataAttributeFactory.WriteWinRTMetadataAttribute(type, context.Cache); + WriteValueTypeWinRTClassNameAttributeCallback valueTypeAttr = MetadataAttributeFactory.WriteValueTypeWinRTClassNameAttribute(context, type); + WriteTypeCustomAttributesCallback customAttrs = CustomAttributeFactory.WriteTypeCustomAttributes(context, type, true); + WriteComWrapperMarshallerAttributeCallback comWrappersAttr = MetadataAttributeFactory.WriteComWrapperMarshallerAttribute(context, type); + WriteWinRTReferenceTypeAttributeCallback refTypeAttr = MetadataAttributeFactory.WriteWinRTReferenceTypeAttribute(context, type); + + // We are generating all types as 'partial' so that if they have any + // custom additions, they can be added without issues. This is much + // simpler than having logic to check that (the metadata is the same). + writer.WriteLine(isMultiline: true, $$""" + {{metadataAttr}} + {{valueTypeAttr}} + {{customAttrs}} + {{comWrappersAttr}} + {{refTypeAttr}} + public partial struct {{projectionName}} : IEquatable<{{projectionName}}> + """); - // Header attributes - MetadataAttributeFactory.WriteWinRTMetadataAttribute(writer, type, context.Cache); - MetadataAttributeFactory.WriteValueTypeWinRTClassNameAttribute(writer, context, type); - CustomAttributeFactory.WriteTypeCustomAttributes(writer, context, type, true); - MetadataAttributeFactory.WriteComWrapperMarshallerAttribute(writer, context, type); - MetadataAttributeFactory.WriteWinRTReferenceTypeAttribute(writer, context, type); - writer.Write("public"); - - if (hasAddition) - { - writer.Write(" partial"); - } - - writer.WriteLine($" struct {projectionName} : IEquatable<{projectionName}>"); using (writer.WriteBlock()) { + // Emit the constructor declaration writer.Write($"public {projectionName}("); for (int i = 0; i < fields.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); - writer.Write($"{fields[i].TypeStr} "); - IdentifierEscaping.WriteEscapedIdentifier(writer, fields[i].ParamName); + WriteEscapedIdentifierCallback name = IdentifierEscaping.WriteEscapedIdentifier(fields[i].ParamName); + writer.Write($"{fields[i].TypeStr} {name}"); } + // Emit the constructor body (just assigning each field) writer.WriteLine(")"); using (writer.WriteBlock()) { foreach ((string _, string name, string paramName, bool _) in fields) { - // When the param name matches the field name (i.e. ToCamelCase couldn't change casing), - // qualify with this. to disambiguate. + // When the param name matches the field name (i.e. 'ToCamelCase' couldn't + // change casing), qualify with 'this.' to disambiguate. + WriteEscapedIdentifierCallback paramRef = IdentifierEscaping.WriteEscapedIdentifier(paramName); if (name == paramName) { - writer.Write($"this.{name} = "); - IdentifierEscaping.WriteEscapedIdentifier(writer, paramName); - writer.Write("; "); + writer.Write($"this.{name} = {paramRef}; "); } else { - writer.Write($"{name} = "); - IdentifierEscaping.WriteEscapedIdentifier(writer, paramName); - writer.Write("; "); + writer.Write($"{name} = {paramRef}; "); } } writer.WriteLine(); } - // properties + // Properties (all getters are readonly) foreach ((string typeStr, string name, string _, bool _) in fields) { - writer.WriteLine($"public {typeStr} {name}"); - using (writer.WriteBlock()) - { - writer.WriteLine("readonly get; set;"); - } + writer.WriteLine($$""" + public {{typeStr}} {{name}} + { + readonly get; set; + } + """); } - // == + // Overridden '==' operator writer.Write($"public static bool operator ==({projectionName} x, {projectionName} y) => "); + // If we have any fields, we just emit a direct comparison for each of them if (fields.Count == 0) { - writer.Write("true"); + writer.WriteLine("true;"); } else { for (int i = 0; i < fields.Count; i++) { - if (i > 0) - { - writer.Write(" && "); - } - + writer.WriteIf(i > 0, " && "); writer.Write($"x.{fields[i].Name} == y.{fields[i].Name}"); } + + writer.WriteLine(";"); } - writer.WriteLine(";"); - writer.WriteLine($"public static bool operator !=({projectionName} x, {projectionName} y) => !(x == y);"); - writer.WriteLine($"public bool Equals({projectionName} other) => this == other;"); - writer.WriteLine($"public override bool Equals(object obj) => obj is {projectionName} that && this == that;"); + // Other equality operators + writer.WriteLine($""" + public static bool operator !=({projectionName} x, {projectionName} y) => !(x == y); + public bool Equals({projectionName} other) => this == other; + public override bool Equals(object obj) => obj is {projectionName} that && this == that; + """); + + // Also override 'GetHashCode' (especially important for structs, as it avoids reflection) writer.Write("public override int GetHashCode() => "); + // If we have aby fields, just combine the hashcode of all fields if (fields.Count == 0) { - writer.Write("0"); + writer.WriteLine("0;"); } else { for (int i = 0; i < fields.Count; i++) { - if (i > 0) - { - writer.Write(" ^ "); - } + writer.WriteIf(i > 0, " ^ "); writer.Write($"{fields[i].Name}.GetHashCode()"); } - } - writer.WriteLine(";"); + writer.WriteLine(";"); + } } writer.WriteLine(); @@ -331,90 +291,83 @@ public static void WriteStruct(IndentedTextWriter writer, ProjectionEmitContext /// /// Writes a projected API contract (an empty enum stand-in). /// - public static void WriteContract(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) + private static void WriteContract(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { if (context.Settings.Component) { return; } - string typeName = type.Name?.Value ?? string.Empty; + string typeName = type.GetRawName(); CustomAttributeFactory.WriteTypeCustomAttributes(writer, context, type, false); - writer.WriteLine($$""" - {{context.Settings.InternalAccessibility}} enum {{typeName}} - { - } - """, isMultiline: true); + + writer.WriteLine($"public enum {typeName};"); } /// /// Writes a projected delegate. /// - public static void WriteDelegate(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) + private static void WriteDelegate(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { if (context.Settings.Component) { return; } - MethodDefinition? invoke = type.GetDelegateInvoke(); - - if (invoke is null) + if (type.GetDelegateInvoke() is not { } invoke) { return; } MethodSignatureInfo sig = new(invoke); - writer.WriteLine(); - MetadataAttributeFactory.WriteWinRTMetadataAttribute(writer, type, context.Cache); - CustomAttributeFactory.WriteTypeCustomAttributes(writer, context, type, false); - MetadataAttributeFactory.WriteComWrapperMarshallerAttribute(writer, context, type); + WriteWinRTMetadataAttributeCallback metadataAttr = MetadataAttributeFactory.WriteWinRTMetadataAttribute(type, context.Cache); + WriteTypeCustomAttributesCallback customAttrs = CustomAttributeFactory.WriteTypeCustomAttributes(context, type, false); + WriteComWrapperMarshallerAttributeCallback comWrappersAttr = MetadataAttributeFactory.WriteComWrapperMarshallerAttribute(context, type); + string guidAttr = context.Settings.ReferenceProjection + ? string.Empty + : $"[Guid(\"{IidExpressionGenerator.FormatGuid(type, lowerCase: false)}\")]"; + WriteProjectionReturnTypeCallback ret = MethodFactory.WriteProjectionReturnType(context, sig); + WriteTypedefNameCallback typedefName = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, false); + WriteTypeParamsCallback typeParams = TypedefNameWriter.WriteTypeParams(type); + WriteParameterListCallback parms = MethodFactory.WriteParameterList(context, sig); - if (!context.Settings.ReferenceProjection) - { - // GUID attribute - writer.Write("[Guid(\""); - IidExpressionGenerator.WriteGuid(writer, type, false); - writer.WriteLine("\")]"); - } - - writer.Write($"{context.Settings.InternalAccessibility} delegate "); - MethodFactory.WriteProjectionReturnType(writer, context, sig); - writer.Write(" "); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, false); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.Write("("); - MethodFactory.WriteParameterList(writer, context, sig); - writer.WriteLine(");"); + writer.WriteLine(); + writer.WriteLine(isMultiline: true, $$""" + {{metadataAttr}} + {{customAttrs}} + {{comWrappersAttr}} + {{guidAttr}} + public delegate {{ret}} {{typedefName}}{{typeParams}}({{parms}}); + """); } /// /// Writes a projected attribute class. /// - public static void WriteAttribute(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) + private static void WriteAttribute(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string typeName = type.Name?.Value ?? string.Empty; + string typeName = type.GetRawName(); + + WriteWinRTMetadataAttributeCallback metadataAttr = MetadataAttributeFactory.WriteWinRTMetadataAttribute(type, context.Cache); + WriteTypeCustomAttributesCallback customAttrs = CustomAttributeFactory.WriteTypeCustomAttributes(context, type, true); writer.WriteLine(); - MetadataAttributeFactory.WriteWinRTMetadataAttribute(writer, type, context.Cache); - CustomAttributeFactory.WriteTypeCustomAttributes(writer, context, type, true); - writer.WriteLine($"{context.Settings.InternalAccessibility} sealed class {typeName} : Attribute"); + writer.WriteLine(isMultiline: true, $$""" + {{metadataAttr}} + {{customAttrs}} + public sealed class {{typeName}} : Attribute + """); + using (writer.WriteBlock()) { // Constructors - foreach (MethodDefinition method in type.Methods) + foreach (MethodDefinition method in type.GetConstructors()) { - if (method.Name?.Value != ".ctor") - { - continue; - } + WriteParameterListCallback parameterList = MethodFactory.WriteParameterList(context, new MethodSignatureInfo(method)); - MethodSignatureInfo sig = new(method); - writer.Write($"public {typeName}("); - MethodFactory.WriteParameterList(writer, context, sig); - writer.WriteLine("){}"); + writer.Write($$"""public {{typeName}}({{parameterList}}) { }"""); } // Fields @@ -425,9 +378,9 @@ public static void WriteAttribute(IndentedTextWriter writer, ProjectionEmitConte continue; } - writer.Write("public "); - TypedefNameWriter.WriteProjectionType(writer, context, TypeSemanticsFactory.Get(field.Signature.FieldType)); - writer.WriteLine($" {field.Name?.Value ?? string.Empty};"); + WriteProjectionTypeCallback fieldType = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(field.Signature.FieldType)); + + writer.WriteLine($"public {fieldType} {field.Name?.Value};"); } } } diff --git a/src/WinRT.Projection.Writer/Errors/WellKnownProjectionWriterExceptions.cs b/src/WinRT.Projection.Writer/Errors/WellKnownProjectionWriterExceptions.cs index 5f914f12f2..6044cb38b0 100644 --- a/src/WinRT.Projection.Writer/Errors/WellKnownProjectionWriterExceptions.cs +++ b/src/WinRT.Projection.Writer/Errors/WellKnownProjectionWriterExceptions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.ProjectionWriter.Models; namespace WindowsRuntime.ProjectionWriter.Errors; @@ -18,27 +19,11 @@ internal static class WellKnownProjectionWriterExceptions public const string ErrorPrefix = "CSWINRTPROJECTIONGEN"; /// - /// An internal invariant about a referenced type failed. + /// A switch over the well-known TypeKind enum encountered an unrecognized member. /// - public static WellKnownProjectionWriterException InternalInvariantFailed(string message) + public static WellKnownProjectionWriterException UnknownTypeKind(TypeKind kind) { - return Exception(5001, message); - } - - /// - /// A metadata type referenced from an emission helper could not be resolved. - /// - public static WellKnownProjectionWriterException CannotResolveType(string typeName) - { - return Exception(5002, $"The type '{typeName}' could not be resolved against the metadata cache."); - } - - /// - /// A switch over the well-known TypeCategory enum encountered an unrecognized member. - /// - public static WellKnownProjectionWriterException UnknownTypeCategory(object category) - { - return Exception(5003, $"Unknown TypeCategory: {category}."); + return Exception(5003, $"Unknown type kind: '{kind}'."); } /// diff --git a/src/WinRT.Projection.Writer/Extensions/AbiTypeKindExtensions.cs b/src/WinRT.Projection.Writer/Extensions/AbiTypeKindExtensions.cs new file mode 100644 index 0000000000..2c0f06aa19 --- /dev/null +++ b/src/WinRT.Projection.Writer/Extensions/AbiTypeKindExtensions.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Models; + +namespace WindowsRuntime.ProjectionWriter; + +/// +/// Extension methods for . +/// +internal static class AbiTypeKindExtensions +{ + /// The input ABI type kind. + extension(AbiTypeKind kind) + { + /// + /// Returns whether the shape is a reference-type marshalling kind: Windows Runtime classes/interfaces, + /// delegates, generic instantiations, the corlib primitive, or + /// /IReference<T> instantiations. + /// + public bool IsReferenceType() + { + return kind is AbiTypeKind.RuntimeClassOrInterface + or AbiTypeKind.Delegate + or AbiTypeKind.Object + or AbiTypeKind.GenericInstance + or AbiTypeKind.NullableT; + } + } +} diff --git a/src/WinRT.Projection.Writer/Extensions/ConstantExtensions.cs b/src/WinRT.Projection.Writer/Extensions/ConstantExtensions.cs new file mode 100644 index 0000000000..ae7eae499d --- /dev/null +++ b/src/WinRT.Projection.Writer/Extensions/ConstantExtensions.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Globalization; +using AsmResolver.DotNet; +using AsmResolver.PE.DotNet.Metadata.Tables; + +namespace WindowsRuntime.ProjectionWriter; + +/// +/// Extension methods for . +/// +internal static class ConstantExtensions +{ + /// The input constant. + extension(Constant constant) + { + /// + /// Formats a metadata constant value as a C# literal. + /// + public string FormatLiteral() + { + static string FormatHexValue(uint value) + { + // Match printf "%#0x" semantics: for '0', output "0"; for non-zero, output "0x" with no padding + return value == 0 + ? "0" + : $"0x{value:x}"; + } + + // The data contains raw bytes representing the value + byte[] data = constant.Value?.Data ?? []; + + return constant.Type switch + { + ElementType.I1 => ((sbyte)data[0]).ToString(CultureInfo.InvariantCulture), + ElementType.U1 => data[0].ToString(CultureInfo.InvariantCulture), + ElementType.I2 => BitConverter.ToInt16(data, 0).ToString(CultureInfo.InvariantCulture), + ElementType.U2 => BitConverter.ToUInt16(data, 0).ToString(CultureInfo.InvariantCulture), + ElementType.I4 => FormatHexValue((uint)BitConverter.ToInt32(data, 0)), + ElementType.U4 => FormatHexValue(BitConverter.ToUInt32(data, 0)), + ElementType.I8 => BitConverter.ToInt64(data, 0).ToString(CultureInfo.InvariantCulture), + ElementType.U8 => BitConverter.ToUInt64(data, 0).ToString(CultureInfo.InvariantCulture), + _ => "0" + }; + } + } +} \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Extensions/CustomAttributeExtensions.cs b/src/WinRT.Projection.Writer/Extensions/CustomAttributeExtensions.cs new file mode 100644 index 0000000000..482b6b4f72 --- /dev/null +++ b/src/WinRT.Projection.Writer/Extensions/CustomAttributeExtensions.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using AsmResolver.DotNet; + +namespace WindowsRuntime.ProjectionWriter; + +/// +/// Extension methods for . +/// +internal static class CustomAttributeExtensions +{ + extension(CustomAttribute attr) + { + /// + /// Attempts to read the boxed fixed-positional argument at from + /// , applying the standard uintint + /// coercion when is and the boxed value is + /// . Returns when the attribute has no signature, + /// the index is out of range, or the boxed value cannot be converted to . + /// + /// The expected argument type (e.g. , , + /// ). + /// The zero-based positional argument index. + /// The argument value when this returns ; otherwise default. + /// on successful conversion; otherwise . + public bool TryGetFixedArgument(int index, [NotNullWhen(true)] out T? value) + { + if (attr.Signature is null || index < 0 || index >= attr.Signature.FixedArguments.Count) + { + value = default; + + return false; + } + + object? element = attr.Signature.FixedArguments[index].Element; + + if (element is T t) + { + value = t; + + return true; + } + + // Standard coercion: 'uint' -> 'int' (WinMD often stores numeric metadata values as 'uint' + // in the fixed-args list, but most call sites consume them as 'int'). + if (typeof(T) == typeof(int) && element is uint u) + { + value = (T)(object)(int)u; + + return true; + } + + value = default; + + return false; + } + } +} diff --git a/src/WinRT.Projection.Writer/Extensions/EventDefinitionExtensions.cs b/src/WinRT.Projection.Writer/Extensions/EventDefinitionExtensions.cs index 45fe02a10c..713dadb7c9 100644 --- a/src/WinRT.Projection.Writer/Extensions/EventDefinitionExtensions.cs +++ b/src/WinRT.Projection.Writer/Extensions/EventDefinitionExtensions.cs @@ -13,10 +13,13 @@ internal static class EventDefinitionExtensions extension(EventDefinition evt) { /// - /// Returns the (add, remove) accessor pair of the event. + /// Returns the event's raw metadata name, falling back to when + /// the metadata name is . Convenience for the + /// evt.Name?.Value ?? string.Empty pattern that appears at many sites. /// - /// A tuple of (Add, Remove) accessor methods, either of which may be . - public (MethodDefinition? Add, MethodDefinition? Remove) GetEventMethods() - => (evt.AddMethod, evt.RemoveMethod); + public string GetRawName() + { + return evt.Name?.Value ?? string.Empty; + } } -} \ No newline at end of file +} diff --git a/src/WinRT.Projection.Writer/Extensions/FieldDefinitionExtensions.cs b/src/WinRT.Projection.Writer/Extensions/FieldDefinitionExtensions.cs new file mode 100644 index 0000000000..d057d1a796 --- /dev/null +++ b/src/WinRT.Projection.Writer/Extensions/FieldDefinitionExtensions.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; + +namespace WindowsRuntime.ProjectionWriter; + +/// +/// Extension methods for . +/// +internal static class FieldDefinitionExtensions +{ + extension(FieldDefinition field) + { + /// + /// Returns the field's raw metadata name, falling back to when + /// the metadata name is . Convenience for the + /// field.Name?.Value ?? string.Empty pattern that appears at many sites. + /// + public string GetRawName() + { + return field.Name?.Value ?? string.Empty; + } + } +} diff --git a/src/WinRT.Projection.Writer/Extensions/IHasCustomAttributeExtensions.cs b/src/WinRT.Projection.Writer/Extensions/IHasCustomAttributeExtensions.cs index 5f85f05698..1e7f310f0b 100644 --- a/src/WinRT.Projection.Writer/Extensions/IHasCustomAttributeExtensions.cs +++ b/src/WinRT.Projection.Writer/Extensions/IHasCustomAttributeExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.References; namespace WindowsRuntime.ProjectionWriter; @@ -21,15 +22,14 @@ internal static class IHasCustomAttributeExtensions /// if a matching custom attribute is found; otherwise . public bool HasAttribute(string ns, string name) { - foreach (CustomAttribute attr in member.CustomAttributes) + foreach (CustomAttribute attribute in member.CustomAttributes) { - if (attr.Constructor?.DeclaringType is { } dt && - (dt.Namespace?.Value == ns) && - (dt.Name?.Value == name)) + if (attribute.Type?.IsTypeOf(ns, name) is true) { return true; } } + return false; } @@ -42,16 +42,57 @@ public bool HasAttribute(string ns, string name) /// The matching custom attribute, or if none is found. public CustomAttribute? GetAttribute(string ns, string name) { - foreach (CustomAttribute attr in member.CustomAttributes) + foreach (CustomAttribute attribute in member.CustomAttributes) { - if (attr.Constructor?.DeclaringType is { } dt && - (dt.Namespace?.Value == ns) && - (dt.Name?.Value == name)) + if (attribute.Type?.IsTypeOf(ns, name) is true) { - return attr; + return attribute; } } + return null; } + + /// + /// Returns the number of custom attributes on the member matching the given + /// and . + /// + /// The namespace of the attribute type. + /// The unqualified type name of the attribute. + /// The number of matching custom attributes. + public int CountAttributes(string ns, string name) + { + int count = 0; + + foreach (CustomAttribute attribute in member.CustomAttributes) + { + if (attribute.Type?.IsTypeOf(ns, name) is true) + { + count++; + } + } + + return count; + } + + /// + /// Convenience for HasAttribute(ns, name) with the namespace fixed to + /// Windows.Foundation.Metadata. + /// + /// The unqualified name of the Windows.Foundation.Metadata attribute. + public bool HasWindowsFoundationMetadataAttribute(string name) + { + return member.HasAttribute(WellKnownNamespaces.WindowsFoundationMetadata, name); + } + + /// + /// Convenience for GetAttribute(ns, name) with the namespace fixed to + /// Windows.Foundation.Metadata. + /// + /// The unqualified name of the Windows.Foundation.Metadata attribute. + public CustomAttribute? GetWindowsFoundationMetadataAttribute(string name) + { + return member.GetAttribute(WellKnownNamespaces.WindowsFoundationMetadata, name); + } } } \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Extensions/ITypeDefOrRefExtensions.cs b/src/WinRT.Projection.Writer/Extensions/ITypeDefOrRefExtensions.cs index 2df65497b6..7b5fd0d06b 100644 --- a/src/WinRT.Projection.Writer/Extensions/ITypeDefOrRefExtensions.cs +++ b/src/WinRT.Projection.Writer/Extensions/ITypeDefOrRefExtensions.cs @@ -1,7 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Diagnostics.CodeAnalysis; using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Metadata; namespace WindowsRuntime.ProjectionWriter; @@ -13,14 +17,14 @@ internal static class ITypeDefOrRefExtensions extension(ITypeDefOrRef type) { /// - /// Returns the namespace and name of the type as a tuple, with both fields - /// guaranteed to be non-: a missing namespace becomes - /// and a missing name becomes . + /// Returns the type's metadata name with any generic-arity backtick suffix stripped + /// (e.g. "IList`1" becomes "IList"). When the type has no name, returns + /// . /// - /// A tuple of (namespace, name) with both fields non-. - public (string Namespace, string Name) Names() + /// The type's stripped name. + public string GetStrippedName() { - return (type.Namespace?.Value ?? string.Empty, type.Name?.Value ?? string.Empty); + return IdentifierEscaping.StripBackticks(type.GetRawName()); } /// @@ -36,5 +40,54 @@ internal static class ITypeDefOrRefExtensions { return type.TryResolve(context, out TypeDefinition? definition) ? definition : null; } + + /// + /// Resolves to a , handling the three + /// shapes that can appear as an interface implementation's : + /// + /// If it is already a , returns it directly. + /// If it is a whose signature is a generic instance, + /// recurses on the generic's open form (). + /// If it is a , looks it up via + /// on its qualified name. + /// + /// Returns when the type cannot be resolved. + /// + /// The metadata cache used for cross-module type-reference resolution. + /// The resolved , or on failure. + public TypeDefinition? ResolveAsTypeDefinition(MetadataCache cache) + { + if (type is TypeDefinition td) + { + return td; + } + + if (type is TypeSpecification { Signature: GenericInstanceTypeSignature gi }) + { + return gi.GenericType.ResolveAsTypeDefinition(cache); + } + + if (type is TypeReference tr) + { + (string ns, string nm) = tr.Names(); + + return cache.Find(ns, nm); + } + + return null; + } + + /// + /// Attempts to extract the from + /// when it is a whose signature + /// is a generic instance. Returns otherwise. + /// + /// The extracted signature when this returns ; otherwise . + public bool TryGetGenericInstance([NotNullWhen(true)] out GenericInstanceTypeSignature? genericInstance) + { + genericInstance = (type as TypeSpecification)?.Signature as GenericInstanceTypeSignature; + + return genericInstance is not null; + } } } \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Extensions/ITypeDescriptorExtensions.cs b/src/WinRT.Projection.Writer/Extensions/ITypeDescriptorExtensions.cs new file mode 100644 index 0000000000..2dece050d6 --- /dev/null +++ b/src/WinRT.Projection.Writer/Extensions/ITypeDescriptorExtensions.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; + +namespace WindowsRuntime.ProjectionWriter; + +/// +/// Extension methods for . +/// +internal static class ITypeDescriptorExtensions +{ + extension(ITypeDescriptor type) + { + /// + /// Returns the namespace and name of the type as a tuple, with both fields + /// guaranteed to be non-: a missing namespace becomes + /// and a missing name becomes . + /// + /// A tuple of (namespace, name) with both fields non-. + public (string Namespace, string Name) Names() + { + return (type.Namespace ?? string.Empty, type.Name ?? string.Empty); + } + + /// + /// Returns the type's raw metadata namespace, falling back to when + /// the metadata namespace is . More efficient than the + /// tuple when only the namespace is needed (avoids allocating the name ). + /// + public string GetRawNamespace() + { + return type.Namespace ?? string.Empty; + } + + /// + /// Returns the type's raw metadata name, falling back to when + /// the metadata name is . More efficient than the + /// tuple when only the name is needed (avoids allocating the namespace ). + /// + public string GetRawName() + { + return type.Name ?? string.Empty; + } + } +} \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Extensions/IndentedTextWriterExtensions.cs b/src/WinRT.Projection.Writer/Extensions/IndentedTextWriterExtensions.cs deleted file mode 100644 index 6461b5f915..0000000000 --- a/src/WinRT.Projection.Writer/Extensions/IndentedTextWriterExtensions.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using WindowsRuntime.ProjectionWriter.Generation; -using WindowsRuntime.ProjectionWriter.Writers; -using static WindowsRuntime.ProjectionWriter.References.ProjectionNames; - -namespace WindowsRuntime.ProjectionWriter; - -/// -/// General-purpose extension methods for that capture -/// repeated emission micro-patterns (separator lists, well-known prefixes, etc.). -/// -internal static class IndentedTextWriterExtensions -{ - extension(IndentedTextWriter writer) - { - /// - /// Writes each item in via , with - /// emitted between consecutive items. - /// - /// The item type. - /// The items to write. - /// The separator string emitted between consecutive items (e.g. ", "). - /// A callback that emits a single item. - public void WriteSeparated(IEnumerable items, string separator, Action writeItem) - { - bool first = true; - foreach (T item in items) - { - if (!first) - { - writer.Write(separator); - } - - writeItem(writer, item); - first = false; - } - } - - /// - /// Writes each item in via , with - /// ", " emitted between consecutive items. - /// - /// The item type. - /// The items to write. - /// A callback that emits a single item. - public void WriteCommaSeparated(IEnumerable items, Action writeItem) - { - writer.WriteSeparated(items, ", ", writeItem); - } - - /// - /// Writes the C# global namespace prefix () followed by - /// . Convenience wrapper for the common - /// writer.Write(GlobalPrefix); writer.Write(typeName); pattern. - /// - /// The fully-qualified type name to emit after the global:: prefix. - public void WriteGlobal(string typeName) - { - writer.Write($"{GlobalPrefix}{typeName}"); - } - - /// - /// Writes the fully-qualified ABI namespace prefix () followed - /// by . Convenience wrapper for the common - /// writer.Write(GlobalAbiPrefix); writer.Write(typeName); pattern. - /// - /// The dot-qualified type name to emit after the global::ABI. prefix. - public void WriteGlobalAbi(string typeName) - { - writer.Write($"{GlobalAbiPrefix}{typeName}"); - } - - /// - /// Writes the projection-wide accessibility modifier ("internal" when - /// or is set; otherwise - /// "public") for an emitted top-level type. - /// - /// The active projection settings. - public void WriteAccessibility(Settings settings) - { - writer.Write(settings.InternalAccessibility); - } - - /// - /// Writes a single C# attribute application of the form [name(args)] on its own - /// line. When is or empty, the parenthesis - /// list is omitted. - /// - /// The unqualified attribute name (without the trailing Attribute suffix). - /// The argument list to render between parentheses, or for no arguments. - public void WriteAttribute(string name, string? args = null) - { - if (string.IsNullOrEmpty(args)) - { - writer.WriteLine($"[{name}]"); - } - else - { - writer.WriteLine($"[{name}({args})]"); - } - } - - /// - /// Writes a fully-qualified reference to a well-known interface IID field - /// (global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_). - /// - /// The bare interface name (e.g. "IInspectable"). - public void WriteWellKnownIIDFieldRef(string interfaceName) - { - writer.Write($"global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_{interfaceName}"); - } - } -} - diff --git a/src/WinRT.Projection.Writer/Extensions/InterfaceImplementationExtensions.cs b/src/WinRT.Projection.Writer/Extensions/InterfaceImplementationExtensions.cs index c5c86d665a..16136f1da5 100644 --- a/src/WinRT.Projection.Writer/Extensions/InterfaceImplementationExtensions.cs +++ b/src/WinRT.Projection.Writer/Extensions/InterfaceImplementationExtensions.cs @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Diagnostics.CodeAnalysis; using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Metadata; using static WindowsRuntime.ProjectionWriter.References.WellKnownAttributeNames; -using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; namespace WindowsRuntime.ProjectionWriter; @@ -20,7 +21,9 @@ internal static class InterfaceImplementationExtensions /// /// if the interface is the default interface; otherwise . public bool IsDefaultInterface() - => impl.HasAttribute(WindowsFoundationMetadata, DefaultAttribute); + { + return impl.HasWindowsFoundationMetadataAttribute(DefaultAttribute); + } /// /// Returns whether the implemented interface is marked [Overridable] (i.e. derived @@ -28,6 +31,34 @@ public bool IsDefaultInterface() /// /// if the interface is overridable; otherwise . public bool IsOverridable() - => impl.HasAttribute(WindowsFoundationMetadata, OverridableAttribute); + { + return impl.HasWindowsFoundationMetadataAttribute(OverridableAttribute); + } + + /// + /// Attempts to resolve the implemented interface to a , handling + /// the common loop-body pattern of if (impl.Interface is null) continue; followed by + /// + /// in a single call. + /// + /// The metadata cache used for cross-module type-reference resolution. + /// The resolved interface when this returns + /// ; otherwise . + /// if the interface reference resolved successfully; + /// when is or the reference + /// could not be resolved. + public bool TryResolveTypeDef(MetadataCache cache, [NotNullWhen(true)] out TypeDefinition? definition) + { + if (impl.Interface is null) + { + definition = null; + + return false; + } + + definition = impl.Interface.ResolveAsTypeDefinition(cache); + + return definition is not null; + } } } \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Extensions/MethodDefinitionExtensions.cs b/src/WinRT.Projection.Writer/Extensions/MethodDefinitionExtensions.cs index 2c093bd1ba..5e0b673215 100644 --- a/src/WinRT.Projection.Writer/Extensions/MethodDefinitionExtensions.cs +++ b/src/WinRT.Projection.Writer/Extensions/MethodDefinitionExtensions.cs @@ -4,7 +4,6 @@ using System; using AsmResolver.DotNet; using static WindowsRuntime.ProjectionWriter.References.WellKnownAttributeNames; -using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; namespace WindowsRuntime.ProjectionWriter; @@ -19,32 +18,66 @@ internal static class MethodDefinitionExtensions /// Returns whether the method is an instance constructor (i.e. its name is /// .ctor and it is marked as runtime-special). /// - /// if the method is an instance constructor; otherwise . - public bool IsConstructor() - => method.IsRuntimeSpecialName && method.Name == ".ctor"; + public bool IsConstructor => method.IsRuntimeSpecialName && method.Name == ".ctor"; /// /// Returns whether the method is special (has either SpecialName or /// RuntimeSpecialName set in its method attributes -- e.g. property accessors, /// event accessors, constructors). /// - /// if the method is marked special; otherwise . - public bool IsSpecial() - => method.IsSpecialName || method.IsRuntimeSpecialName; + public bool IsSpecial => method.IsSpecialName || method.IsRuntimeSpecialName; + + /// + /// Returns whether the method is a property getter (special method whose name starts with get_). + /// + public bool IsGetter => method.IsSpecialName && (method.Name?.Value?.StartsWith("get_", StringComparison.Ordinal) == true); + + /// + /// Returns whether the method is a property setter (special method whose name starts with put_). + /// + public bool IsSetter => method.IsSpecialName && (method.Name?.Value?.StartsWith("put_", StringComparison.Ordinal) == true); + + /// + /// Returns whether the method is an event adder (special method whose name starts with add_). + /// + public bool IsAdder => method.IsSpecialName && (method.Name?.Value?.StartsWith("add_", StringComparison.Ordinal) == true); /// /// Returns whether the method is the special remove_xxx event remover overload. /// - /// if the method is an event remover; otherwise . - public bool IsRemoveOverload() - => method.IsSpecialName && (method.Name?.Value?.StartsWith("remove_", StringComparison.Ordinal) == true); + public bool IsRemover => method.IsSpecialName && (method.Name?.Value?.StartsWith("remove_", StringComparison.Ordinal) == true); /// - /// Returns whether the method carries the [NoExceptionAttribute] or is a - /// (event removers are implicitly no-throw). + /// Returns whether the method declares no parameters. + /// + public bool IsParameterless => method.Parameters.Count == 0; + + /// + /// Returns whether the method is a parameterless instance constructor (i.e. a default + /// constructor): runtime-special, name .ctor, no parameters. + /// + public bool IsDefaultConstructor => method.IsConstructor && method.IsParameterless; + + /// + /// Returns whether the method carries the [NoExceptionAttribute] or is an + /// event remover (event removers are implicitly no-throw). /// /// if the method is documented to never throw; otherwise . - public bool IsNoExcept() - => method.IsRemoveOverload() || method.HasAttribute(WindowsFoundationMetadata, NoExceptionAttribute); + public bool IsNoExcept => method.IsRemover || method.HasWindowsFoundationMetadataAttribute(NoExceptionAttribute); + + /// + /// Returns whether the method is the special Invoke method (used for delegates). + /// + public bool IsInvoke => method.IsSpecialName && method.Name is { } name && name.AsSpan().SequenceEqual("Invoke"u8); + + /// + /// Returns the method's raw metadata name, falling back to when + /// the metadata name is . Convenience for the + /// method.Name?.Value ?? string.Empty pattern that appears at many sites. + /// + public string GetRawName() + { + return method.Name?.Value ?? string.Empty; + } } } diff --git a/src/WinRT.Projection.Writer/Extensions/ParameterCategoryExtensions.cs b/src/WinRT.Projection.Writer/Extensions/ParameterCategoryExtensions.cs new file mode 100644 index 0000000000..ddb2f22009 --- /dev/null +++ b/src/WinRT.Projection.Writer/Extensions/ParameterCategoryExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Models; + +namespace WindowsRuntime.ProjectionWriter; + +/// +/// Extension methods for . +/// +internal static class ParameterCategoryExtensions +{ + /// The input parameter category. + extension(ParameterCategory category) + { + /// + /// Returns whether the input category is an input-side array category + /// ( or ). + /// + public bool IsArrayInput() + { + return category is ParameterCategory.PassArray or ParameterCategory.FillArray; + } + + /// + /// Returns whether the input category is a non-array input parameter that the callee + /// reads ( or ) — + /// i.e. not an array, not an output parameter, and not a receive-array. This is the + /// natural complement of on the input side of the boundary. + /// + public bool IsScalarInput() + { + return category is ParameterCategory.In or ParameterCategory.Ref; + } + } +} diff --git a/src/WinRT.Projection.Writer/Extensions/ProjectionWriterExtensions.cs b/src/WinRT.Projection.Writer/Extensions/ProjectionWriterExtensions.cs index 851c19d54a..a5875bd931 100644 --- a/src/WinRT.Projection.Writer/Extensions/ProjectionWriterExtensions.cs +++ b/src/WinRT.Projection.Writer/Extensions/ProjectionWriterExtensions.cs @@ -23,7 +23,7 @@ internal static class ProjectionWriterExtensions /// The active emit context (currently unused, but reserved for future per-namespace customization). public void WriteFileHeader(ProjectionEmitContext context) { - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" //------------------------------------------------------------------------------ // // This file was generated by cswinrt.exe version {{MetadataAttributeFactory.GetVersionString()}} @@ -54,7 +54,7 @@ public void WriteFileHeader(ProjectionEmitContext context) #pragma warning disable CSWINRT3001 // "Type or member '...' is a private implementation detail" #pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type - """, isMultiline: true); + """); } /// @@ -65,12 +65,14 @@ public void WriteFileHeader(ProjectionEmitContext context) public void WriteBeginProjectedNamespace(ProjectionEmitContext context) { string nsPrefix = context.Settings.Component ? "ABI.Impl." : string.Empty; + writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" namespace {{nsPrefix}}{{context.CurrentNamespace}} { - """, isMultiline: true); + """); writer.IncreaseIndent(); + if (context.Settings.Component) { context.InAbiImplNamespace = true; @@ -86,6 +88,7 @@ public void WriteEndProjectedNamespace(ProjectionEmitContext context) { writer.DecreaseIndent(); writer.WriteLine("}"); + context.InAbiImplNamespace = false; } @@ -97,12 +100,13 @@ public void WriteEndProjectedNamespace(ProjectionEmitContext context) public void WriteBeginAbiNamespace(ProjectionEmitContext context) { writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" #pragma warning disable CA1416 namespace ABI.{{context.CurrentNamespace}} { - """, isMultiline: true); + """); writer.IncreaseIndent(); + context.InAbiNamespace = true; } @@ -115,10 +119,11 @@ namespace ABI.{{context.CurrentNamespace}} public void WriteEndAbiNamespace(ProjectionEmitContext context) { writer.DecreaseIndent(); - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ } #pragma warning restore CA1416 - """, isMultiline: true); + """); + context.InAbiNamespace = false; } } diff --git a/src/WinRT.Projection.Writer/Extensions/PropertyDefinitionExtensions.cs b/src/WinRT.Projection.Writer/Extensions/PropertyDefinitionExtensions.cs index 83daa0b3bc..e7fe6aa8f2 100644 --- a/src/WinRT.Projection.Writer/Extensions/PropertyDefinitionExtensions.cs +++ b/src/WinRT.Projection.Writer/Extensions/PropertyDefinitionExtensions.cs @@ -3,7 +3,6 @@ using AsmResolver.DotNet; using static WindowsRuntime.ProjectionWriter.References.WellKnownAttributeNames; -using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; namespace WindowsRuntime.ProjectionWriter; @@ -17,15 +16,23 @@ internal static class PropertyDefinitionExtensions /// /// Returns whether the property carries the [NoExceptionAttribute]. /// - /// if the property is documented to never throw; otherwise . - public bool IsNoExcept() - => property.HasAttribute(WindowsFoundationMetadata, NoExceptionAttribute); + public bool IsNoExcept => property.HasWindowsFoundationMetadataAttribute(NoExceptionAttribute); /// /// Returns the (getter, setter) accessor pair of the property. /// /// A tuple of (Getter, Setter) accessor methods, either of which may be . - public (MethodDefinition? Getter, MethodDefinition? Setter) GetPropertyMethods() + public (MethodDefinition? Getter, MethodDefinition? Setter) GetMethods() => (property.GetMethod, property.SetMethod); + + /// + /// Returns the property's raw metadata name, falling back to when + /// the metadata name is . Convenience for the + /// property.Name?.Value ?? string.Empty pattern that appears at many sites. + /// + public string GetRawName() + { + return property.Name?.Value ?? string.Empty; + } } } \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Extensions/SettingsExtensions.cs b/src/WinRT.Projection.Writer/Extensions/SettingsExtensions.cs deleted file mode 100644 index e74460a0de..0000000000 --- a/src/WinRT.Projection.Writer/Extensions/SettingsExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using WindowsRuntime.ProjectionWriter.Generation; - -namespace WindowsRuntime.ProjectionWriter; - -/// -/// Extension methods for . -/// -internal static class SettingsExtensions -{ - extension(Settings settings) - { - /// - /// Gets the accessibility modifier ("public" or "internal") used for - /// generated types based on the and - /// flags. - /// - public string InternalAccessibility => settings.Internal || settings.Embedded ? "internal" : "public"; - } -} diff --git a/src/WinRT.Projection.Writer/Extensions/TypeDefinitionExtensions.cs b/src/WinRT.Projection.Writer/Extensions/TypeDefinitionExtensions.cs index 11b679339b..79cf0e43ac 100644 --- a/src/WinRT.Projection.Writer/Extensions/TypeDefinitionExtensions.cs +++ b/src/WinRT.Projection.Writer/Extensions/TypeDefinitionExtensions.cs @@ -1,7 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; +using System.Collections.Generic; +using System.Globalization; using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Metadata; using static WindowsRuntime.ProjectionWriter.References.WellKnownAttributeNames; using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; @@ -14,6 +19,145 @@ internal static class TypeDefinitionExtensions { extension(TypeDefinition type) { + /// + /// Returns whether the type is a struct (a value type that isn't an enum). + /// + public bool IsStruct => type.IsValueType && !type.IsEnum; + + /// + /// Returns whether the type is a class whose immediate base is . + /// + /// + /// This check only inspects the type's immediate base. Types that inherit + /// through an intermediate base class are reported as + /// . + /// + public bool IsAttributeType + { + get + { + if (type.IsInterface || type.IsValueType || type.IsDelegate) + { + return false; + } + + if (type.BaseType is not { } baseType) + { + return false; + } + + (string ns, string name) = baseType.Names(); + + return ns == "System" && name == "Attribute"; + } + } + + /// + /// Returns whether the type is static. + /// + public bool IsStatic => type.IsAbstract && type.IsSealed; + + /// + /// Returns whether the type is an enum marked with [System.FlagsAttribute]. + /// + public bool IsFlagsEnum => type.IsEnum && type.HasAttribute("System", "FlagsAttribute"); + + /// + /// Returns whether the type has any generic type parameters (i.e. is a generic type definition). + /// + public bool IsGeneric => type.GenericParameters.Count > 0; + + /// + /// Returns whether the type is a struct marked with + /// [Windows.Foundation.Metadata.ApiContractAttribute] (i.e. a WinRT API contract). + /// + public bool IsApiContractType => type.IsStruct && type.HasWindowsFoundationMetadataAttribute("ApiContractAttribute"); + + /// + /// Returns whether the type is an interface marked with + /// [Windows.Foundation.Metadata.ExclusiveToAttribute] (i.e. an interface + /// declared "exclusive to" a single runtime class). + /// + public bool IsExclusiveTo => type.IsInterface && type.HasWindowsFoundationMetadataAttribute(ExclusiveToAttribute); + + /// + /// Returns whether the type is marked with + /// [WindowsRuntime.Internal.ProjectionInternalAttribute] (i.e. a type that + /// should be projected as internal rather than public). + /// + public bool IsProjectionInternal => type.HasAttribute(WindowsRuntimeInternal, "ProjectionInternalAttribute"); + + /// + /// Returns the type's methods filtered to exclude special-name methods (property accessors, + /// event accessors, and runtime-special methods like .ctor). + /// + /// The non-special methods in declaration order. + public IEnumerable GetNonSpecialMethods() + { + foreach (MethodDefinition method in type.Methods) + { + if (!method.IsSpecial) + { + yield return method; + } + } + } + + /// + /// Returns the constructors for the current type. + /// + public IEnumerable GetConstructors() + { + foreach (MethodDefinition method in type.Methods) + { + if (method.IsConstructor) + { + yield return method; + } + } + } + + /// + /// Returns the property accessor methods (get and set) declared by the type's + /// properties. Used to filter "regular" methods (non-property, non-event) when emitting + /// per-method code in interface bodies. + /// + public IEnumerable GetPropertyAccessors() + { + foreach (PropertyDefinition prop in type.Properties) + { + if (prop.GetMethod is MethodDefinition g) + { + yield return g; + } + + if (prop.SetMethod is MethodDefinition s) + { + yield return s; + } + } + } + + /// + /// Adds all directly-required interfaces of to + /// so they aren't re-emitted as forwarders. Used by the DIC + /// shim emitters that already cover their inherited interface members transitively + /// (e.g. IBindableVector already includes IBindableIterable's members). + /// + /// The metadata cache used to resolve interface references. + /// The accumulator set to which resolved required interface + /// definitions are added. + public void MarkRequiredInterfacesVisited(MetadataCache cache, HashSet visited) + { + foreach (InterfaceImplementation impl in type.Interfaces) + { + if (impl.TryResolveTypeDef(cache, out TypeDefinition? required)) + { + _ = visited.Add(required); + } + } + } + /// /// Returns the [Default] interface of the type (the interface whose vtable backs the /// type's IInspectable identity), or if the type does not declare one. @@ -28,6 +172,7 @@ internal static class TypeDefinitionExtensions return impl.Interface; } } + return null; } @@ -40,11 +185,12 @@ internal static class TypeDefinitionExtensions { foreach (MethodDefinition m in type.Methods) { - if (m.IsSpecialName && m.Name == "Invoke") + if (m.IsInvoke) { return m; } } + return null; } @@ -56,45 +202,41 @@ public bool HasDefaultConstructor() { foreach (MethodDefinition m in type.Methods) { - if (m.IsRuntimeSpecialName && m.Name == ".ctor" && m.Parameters.Count == 0) + if (m.IsDefaultConstructor) { return true; } } + return false; } /// - /// Returns the second positional argument (a ) of - /// [Windows.Foundation.Metadata.ContractVersionAttribute] on the type, or - /// if the attribute is missing or the argument cannot be read. + /// Returns whether the type has a base type that is not + /// (i.e. the type derives from a real WinRT/.NET class). /// - /// The contract version, or . - public int? GetContractVersion() + public bool HasNonObjectBaseType() { - CustomAttribute? attr = type.GetAttribute(WindowsFoundationMetadata, ContractVersionAttribute); - - if (attr is null) + if (type.BaseType is not { } baseType) { - return null; + return false; } - if (attr.Signature is not null && attr.Signature.FixedArguments.Count > 1) - { - object? v = attr.Signature.FixedArguments[1].Element; + (string ns, string name) = baseType.Names(); - if (v is uint u) - { - return (int)u; - } - - if (v is int i) - { - return i; - } - } + return !(ns == "System" && name == "Object"); + } - return null; + /// + /// Returns the second positional argument (a ) of + /// [Windows.Foundation.Metadata.ContractVersionAttribute] on the type, or + /// if the attribute is missing or the argument cannot be read. + /// + /// The contract version, or . + public int? GetContractVersion() + { + CustomAttribute? attr = type.GetWindowsFoundationMetadataAttribute(ContractVersionAttribute); + return attr is not null && attr.TryGetFixedArgument(1, out int v) ? v : null; } /// @@ -105,29 +247,52 @@ public bool HasDefaultConstructor() /// The version, or . public int? GetVersion() { - CustomAttribute? attr = type.GetAttribute(WindowsFoundationMetadata, VersionAttribute); + CustomAttribute? attr = type.GetWindowsFoundationMetadataAttribute(VersionAttribute); + return attr is not null && attr.TryGetFixedArgument(0, out int v) ? v : null; + } - if (attr is null) + /// + /// Tries to read the [Windows.Foundation.Metadata.GuidAttribute] on the type and reconstructs + /// it as a value. Returns if the attribute + /// is missing or its 11 positional fields (uint, ushort, ushort, then + /// 8 × byte) cannot be read. + /// + /// The parsed value, if successfully retrieved. + /// if the GUID was successfully retrieved; otherwise . + public bool TryGetWindowsRuntimeGuid(out Guid guid) + { + CustomAttribute? attr = type.GetWindowsFoundationMetadataAttribute("GuidAttribute"); + + if (attr is null || attr.Signature is null) { - return null; + guid = default; + + return false; } - if (attr.Signature is not null && attr.Signature.FixedArguments.Count > 0) - { - object? v = attr.Signature.FixedArguments[0].Element; + IList args = attr.Signature.FixedArguments; - if (v is uint u) - { - return (int)u; - } + if (args.Count < 11) + { + guid = default; - if (v is int i) - { - return i; - } + return false; } - return null; + guid = new Guid( + a: Convert.ToUInt32(args[0].Element, CultureInfo.InvariantCulture), + b: Convert.ToUInt16(args[1].Element, CultureInfo.InvariantCulture), + c: Convert.ToUInt16(args[2].Element, CultureInfo.InvariantCulture), + d: Convert.ToByte(args[3].Element, CultureInfo.InvariantCulture), + e: Convert.ToByte(args[4].Element, CultureInfo.InvariantCulture), + f: Convert.ToByte(args[5].Element, CultureInfo.InvariantCulture), + g: Convert.ToByte(args[6].Element, CultureInfo.InvariantCulture), + h: Convert.ToByte(args[7].Element, CultureInfo.InvariantCulture), + i: Convert.ToByte(args[8].Element, CultureInfo.InvariantCulture), + j: Convert.ToByte(args[9].Element, CultureInfo.InvariantCulture), + k: Convert.ToByte(args[10].Element, CultureInfo.InvariantCulture)); + + return true; } } } \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Extensions/TypeSignatureExtensions.cs b/src/WinRT.Projection.Writer/Extensions/TypeSignatureExtensions.cs index c04801bec2..b98a20625d 100644 --- a/src/WinRT.Projection.Writer/Extensions/TypeSignatureExtensions.cs +++ b/src/WinRT.Projection.Writer/Extensions/TypeSignatureExtensions.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Diagnostics.CodeAnalysis; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; +using WindowsRuntime.ProjectionWriter.Resolvers; using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; using static WindowsRuntime.ProjectionWriter.References.WellKnownTypeNames; @@ -14,8 +16,8 @@ namespace WindowsRuntime.ProjectionWriter; /// /// /// Predicates that need cross-module type resolution (e.g. IsBlittablePrimitive -/// with cross-module enum lookup, IsAnyStruct, IsComplexStruct) live in -/// and the ; +/// with cross-module enum lookup, IsBlittableStruct, IsNonBlittableStruct) live in +/// and the ; /// they are intentionally not included here. /// internal static class TypeSignatureExtensions @@ -23,9 +25,9 @@ internal static class TypeSignatureExtensions extension(TypeSignature sig) { /// - /// Returns whether the signature is the corlib primitive. + /// Returns whether the signature is the corlib primitive. /// - /// if the signature is System.String; otherwise . + /// if the signature is ; otherwise . public bool IsString() { return sig is CorLibTypeSignature corlib && corlib.ElementType == ElementType.String; @@ -34,7 +36,7 @@ public bool IsString() /// /// Returns whether the signature is the corlib primitive. /// - /// if the signature is System.Object; otherwise . + /// if the signature is ; otherwise . public bool IsObject() { return sig is CorLibTypeSignature corlib && corlib.ElementType == ElementType.Object; @@ -45,30 +47,38 @@ public bool IsObject() /// that resolves to it, including the WinRT Windows.UI.Xaml.Interop.TypeName struct /// that is mapped to it). /// - /// if the signature is the projected System.Type; otherwise . + /// + /// The System.Type branch is live (not dead, despite appearances): per the ECMA-335 + /// custom-attribute blob encoding rules, attribute constructor / field parameters typed as + /// Type are emitted as TypeRefs to System.Type directly, not to the WinRT + /// Windows.UI.Xaml.Interop.TypeName struct. The Windows Foundation metadata winmd + /// (ActivatableAttribute, ComposableAttribute, StyleTypedPropertyAttribute, + /// etc.) is the primary source of these signatures. + /// + /// if the signature is the projected ; otherwise . public bool IsSystemType() { - if (sig is TypeDefOrRefSignature td && td.Type is { } t) - { - (string ns, string name) = t.Names(); + (string ns, string name) = sig.Names(); - if (ns == "System" && name == "Type") - { - return true; - } + return (ns == "System" && name == "Type") || (ns == WindowsUIXamlInterop && name == TypeName); + } - if (ns == WindowsUIXamlInterop && name == TypeName) - { - return true; - } - } + /// + /// Returns whether the signature is the special / + /// Windows.Foundation.HResult pair (which uses an HResult struct as its ABI form + /// and requires custom marshalling via ABI.System.ExceptionMarshaller). + /// + /// if the signature is the projected HResult/; otherwise . + public bool IsHResultException() + { + (string ns, string name) = sig.Names(); - return false; + return ns == WindowsFoundation && name == HResult; } /// /// Returns whether the signature is a WinRT IReference<T> or a - /// instantiation (both project to Nullable<T> in C#). + /// instantiation (both project to in C#). /// /// if the signature is a Nullable-shaped generic instantiation; otherwise . public bool IsNullableT() @@ -78,23 +88,22 @@ public bool IsNullableT() return false; } - string ns = gi.GenericType?.Namespace?.Value ?? string.Empty; - string name = gi.GenericType?.Name?.Value ?? string.Empty; - return (ns == WindowsFoundation && name == IReferenceGeneric) - || (ns == "System" && name == NullableGeneric); + (string ns, string name) = gi.GenericType.Names(); + + return ns == WindowsFoundation && name == IReferenceGeneric; } /// /// Returns the single type argument of a generic instance type signature, or /// if the signature is not a single-arg generic instance. Used to peel the inner T from - /// Nullable<T> / IReference<T>. + /// / IReference<T>. /// /// The inner type argument, or . public TypeSignature? GetNullableInnerType() { - if (sig is GenericInstanceTypeSignature gi && gi.TypeArguments.Count == 1) + if (sig is GenericInstanceTypeSignature { TypeArguments: [var arg] }) { - return gi.TypeArguments[0]; + return arg; } return null; @@ -111,54 +120,86 @@ public bool IsGenericInstance() } /// - /// Returns whether the signature is the special System.Exception / - /// Windows.Foundation.HResult pair (which uses an HResult struct as its ABI form - /// and requires custom marshalling via ABI.System.ExceptionMarshaller). + /// Returns whether the signature has the "ABI reference-pointer" shape: it crosses the + /// ABI as a void* / IInspectable*. That covers (HSTRING), + /// any WinRT runtime class or interface (resolved via ), + /// , and any generic instance (e.g. IList<T>). /// - /// if the signature is the projected HResult/Exception; otherwise . - public bool IsHResultException() + /// The active (needed for runtime-class/interface resolution). + /// if the signature flows as a void* across the ABI; otherwise . + /// + /// Use this variant for parameter/return types. For SZ-array element types (where generic + /// instances cannot appear as elements), use instead. + /// + public bool IsAbiRefLike(AbiTypeKindResolver resolver) { - if (sig is not TypeDefOrRefSignature td || td.Type is null) - { - return false; - } + return + sig.IsString() || + sig.IsObject() || + sig.IsGenericInstance() || + resolver.IsRuntimeClassOrInterface(sig); + } - (string ns, string name) = td.Type.Names(); - return (ns == "System" && name == "Exception") - || (ns == WindowsFoundation && name == HResult); + /// + /// Returns whether the signature has the "ABI array-element reference-pointer" shape: + /// it crosses the ABI as a void* when carried as an element of an SZ-array. + /// That covers , WinRT runtime classes/interfaces, and + /// — the 3-way variant of + /// without the IsGenericInstance case, because generic instances cannot + /// appear as array elements in metadata. + /// + /// The active (needed for runtime-class/interface resolution). + /// if the signature flows as a void* when used as an SZ-array element; otherwise . + public bool IsAbiArrayElementRefLike(AbiTypeKindResolver resolver) + { + return + sig.IsString() || + sig.IsObject() || + resolver.IsRuntimeClassOrInterface(sig); } - } - extension(TypeSignature? sig) - { /// /// Strips trailing and - /// wrappers from the signature, returning the underlying signature (or - /// if the input is ). + /// wrappers from the signature, returning the underlying signature. /// /// The underlying signature with byref + custom-modifier wrappers stripped. - public TypeSignature? StripByRefAndCustomModifiers() + public TypeSignature StripByRefAndCustomModifiers() { - TypeSignature? cur = sig; + TypeSignature current = sig; + while (true) { - if (cur is CustomModifierTypeSignature cm) + if (current is ByReferenceTypeSignature br) { - cur = cm.BaseType; + current = br.BaseType; + continue; } - if (cur is ByReferenceTypeSignature br) + if (current is CustomModifierTypeSignature cm) { - cur = br.BaseType; + current = cm.BaseType; + continue; } - break; + return current; } - return cur; } + /// + /// Strips byref + custom modifiers from the input signature and returns the result + /// as an if the underlying type is one; otherwise + /// returns . + /// + public SzArrayTypeSignature? AsSzArray() + { + return sig.StripByRefAndCustomModifiers() as SzArrayTypeSignature; + } + } + + extension([NotNullWhen(true)] TypeSignature? sig) + { /// /// Returns whether the signature represents a by-reference type, peeling any /// custom-modifier wrappers (e.g. modreq[InAttribute]) before checking. @@ -167,10 +208,12 @@ public bool IsHResultException() public bool IsByRefType() { TypeSignature? cur = sig; + while (cur is CustomModifierTypeSignature cm) { cur = cm.BaseType; } + return cur is ByReferenceTypeSignature; } } diff --git a/src/WinRT.Projection.Writer/Factories/AbiClassFactory.cs b/src/WinRT.Projection.Writer/Factories/AbiClassFactory.cs index 1cd9e3cad7..ccd5c359e0 100644 --- a/src/WinRT.Projection.Writer/Factories/AbiClassFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/AbiClassFactory.cs @@ -6,6 +6,8 @@ using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Resolvers; using WindowsRuntime.ProjectionWriter.Writers; namespace WindowsRuntime.ProjectionWriter.Factories; @@ -22,7 +24,7 @@ internal static class AbiClassFactory public static void WriteAbiClass(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { // Static classes don't get a *Marshaller (no instances). - if (TypeCategorization.IsStatic(type)) + if (type.IsStatic) { return; } @@ -48,60 +50,55 @@ public static void WriteAbiClass(IndentedTextWriter writer, ProjectionEmitContex /// internal static void WriteComponentClassMarshaller(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string nameStripped = IdentifierEscaping.StripBackticks(type.Name?.Value ?? string.Empty); - string typeNs = type.Namespace?.Value ?? string.Empty; - string projectedType = $"global::{typeNs}.{nameStripped}"; + string nameStripped = type.GetStrippedName(); + string typeNs = type.GetRawNamespace(); + string projectedType = TypedefNameWriter.BuildGlobalQualifiedName(typeNs, nameStripped); ITypeDefOrRef? defaultIface = type.GetDefaultInterface(); + // If the default interface is a generic instantiation (e.g. IDictionary), emit an // UnsafeAccessor extern declaration inside ConvertToUnmanaged that fetches the IID via // WinRT.Interop's InterfaceIIDs class (since the IID for a generic instantiation is computed // at runtime). The IID expression in the call then becomes '(null)' instead of a // static InterfaceIIDs reference. - GenericInstanceTypeSignature? defaultGenericInst = null; - - if (defaultIface is TypeSpecification spec - && spec.Signature is GenericInstanceTypeSignature gi) + if (defaultIface?.TryGetGenericInstance(out GenericInstanceTypeSignature? defaultGenericInst) is not true) { - defaultGenericInst = gi; + defaultGenericInst = null; } string defaultIfaceIid; + // If the type is generic, we need to invoke the unsafe accessor (because the IID will be generated + // in the 'WinRT.Interop.dll' assembly, whereas if not we can directly use the local IID property. if (defaultGenericInst is not null) { - // Call the accessor: '>(null)'. + // Call the accessor: '>(null)' string accessorName = ObjRefNameGenerator.BuildIidPropertyNameForGenericInterface(context, defaultGenericInst); defaultIfaceIid = accessorName + "(null)"; } else { defaultIfaceIid = defaultIface is not null - ? ObjRefNameGenerator.WriteIidExpression(context, defaultIface) + ? ObjRefNameGenerator.WriteIidExpression(context, defaultIface).Format() : "default(global::System.Guid)"; } writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public static unsafe class {{nameStripped}}Marshaller { public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged({{projectedType}} value) { - """, isMultiline: true); + """); + + // Emit the '[UnsafeAccessor]' declaration (uses 'object?' since component-mode + // marshallers run inside #nullable enable) if the type is a generic type. if (defaultGenericInst is not null) { - // Emit the UnsafeAccessor declaration (uses 'object?' since component-mode - // marshallers run inside #nullable enable). - string accessorBlock = ObjRefNameGenerator.EmitUnsafeAccessorForIid(context, defaultGenericInst, isInNullableContext: true); - // Re-emit each line indented by 8 spaces. - string[] accessorLines = accessorBlock.TrimEnd('\n').Split('\n'); - foreach (string accessorLine in accessorLines) - { - writer.WriteLine($" {accessorLine}"); - } + UnsafeAccessorFactory.EmitIidAccessor(writer, context, defaultGenericInst, isInNullableContext: true); } - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" return WindowsRuntimeInterfaceMarshaller<{{projectedType}}>.ConvertToUnmanaged(value, {{defaultIfaceIid}}); } @@ -110,7 +107,7 @@ public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged({{projectedT return ({{projectedType}}?) WindowsRuntimeObjectMarshaller.ConvertToManaged(value); } } - """, isMultiline: true); + """); } /// @@ -119,37 +116,37 @@ public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged({{projectedT /// internal static void WriteAuthoringMetadataType(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string nameStripped = IdentifierEscaping.StripBackticks(type.Name?.Value ?? string.Empty); - string typeNs = type.Namespace?.Value ?? string.Empty; - string projectedType = string.IsNullOrEmpty(typeNs) ? $"global::{nameStripped}" : $"global::{typeNs}.{nameStripped}"; + string nameStripped = type.GetStrippedName(); + string typeNs = type.GetRawNamespace(); + string projectedType = TypedefNameWriter.BuildGlobalQualifiedName(typeNs, nameStripped); string fullName = string.IsNullOrEmpty(typeNs) ? nameStripped : $"{typeNs}.{nameStripped}"; - TypeCategory category = TypeCategorization.GetCategory(type); + TypeKind kind = TypeKindResolver.Resolve(type); // [WindowsRuntimeReferenceType(typeof(?))] for non-delegate, non-class types // (i.e. enums, structs, interfaces). - if (category is not (TypeCategory.Delegate or TypeCategory.Class)) + if (kind is not (TypeKind.Delegate or TypeKind.Class)) { writer.WriteLine($"[WindowsRuntimeReferenceType(typeof({projectedType}?))]"); } // [ABI..ComWrappersMarshaller] for non-struct, non-class types // (delegates, enums, interfaces). - if (category is not (TypeCategory.Struct or TypeCategory.Class)) + if (kind is not (TypeKind.Struct or TypeKind.Class)) { writer.WriteLine($"[ABI.{typeNs}.{nameStripped}ComWrappersMarshaller]"); } // [WindowsRuntimeClassName("Windows.Foundation.IReference`1<.>")] for non-class types. - if (category != TypeCategory.Class) + if (kind != TypeKind.Class) { writer.WriteLine($"[WindowsRuntimeClassName(\"Windows.Foundation.IReference`1<{fullName}>\")]"); } - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" [WindowsRuntimeMetadataTypeName("{{fullName}}")] [WindowsRuntimeMappedType(typeof({{projectedType}}))] - file static class {{nameStripped}} {} - """, isMultiline: true); + file static class {{nameStripped}}; + """); } /// @@ -162,7 +159,7 @@ public static bool EmitImplType(IndentedTextWriter writer, ProjectionEmitContext return true; } - if (TypeCategorization.IsExclusiveTo(type) && !context.Settings.PublicExclusiveTo) + if (type.IsExclusiveTo && !context.Settings.PublicExclusiveTo) { // one interface impl on the exclusive_to class is marked [Overridable] and matches // this interface. Otherwise the Impl wouldn't be reachable as a CCW. @@ -176,13 +173,11 @@ public static bool EmitImplType(IndentedTextWriter writer, ProjectionEmitContext bool hasOverridable = false; foreach (InterfaceImplementation impl in exclusiveToType.Interfaces) { - if (impl.Interface is null) + if (!impl.TryResolveTypeDef(context.Cache, out TypeDefinition? ifaceTd)) { continue; } - TypeDefinition? ifaceTd = AbiTypeHelpers.ResolveInterfaceTypeDef(context.Cache, impl.Interface); - if (ifaceTd == type && impl.IsOverridable()) { hasOverridable = true; @@ -205,15 +200,14 @@ public static bool EmitImplType(IndentedTextWriter writer, ProjectionEmitContext /// internal static void WriteClassMarshallerStub(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); - string typeNs = type.Namespace?.Value ?? string.Empty; - string fullProjected = $"global::{typeNs}.{nameStripped}"; + string nameStripped = type.GetStrippedName(); + string typeNs = type.GetRawNamespace(); + string fullProjected = TypedefNameWriter.BuildGlobalQualifiedName(typeNs, nameStripped); // Get the IID expression for the default interface (used by CreateObject). ITypeDefOrRef? defaultIface = type.GetDefaultInterface(); string defaultIfaceIid = defaultIface is not null - ? ObjRefNameGenerator.WriteIidExpression(context, defaultIface) + ? ObjRefNameGenerator.WriteIidExpression(context, defaultIface).Format() : "default(global::System.Guid)"; // Determine the marshalingType expression from the class's [MarshalingBehaviorAttribute]. @@ -224,46 +218,46 @@ internal static void WriteClassMarshallerStub(IndentedTextWriter writer, Project // For unsealed classes, the ConvertToUnmanaged path needs to know whether the default interface is // exclusive-to. - TypeDefinition? defaultIfaceTd = defaultIface is null ? null : AbiTypeHelpers.ResolveInterfaceTypeDef(context.Cache, defaultIface); - bool defaultIfaceIsExclusive = defaultIfaceTd is not null && TypeCategorization.IsExclusiveTo(defaultIfaceTd); + TypeDefinition? defaultIfaceTd = defaultIface?.ResolveAsTypeDefinition(context.Cache); + bool defaultIfaceIsExclusive = defaultIfaceTd is not null && defaultIfaceTd.IsExclusiveTo; // Public *Marshaller class - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public static unsafe class {{nameStripped}}Marshaller { public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged({{fullProjected}} value) { - """, isMultiline: true); + """); if (isSealed) { // For projected sealed runtime classes, the RCW type is always unwrappable. - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ if (value is not null) { return WindowsRuntimeComWrappersMarshal.UnwrapObjectReferenceUnsafe(value).AsValue(); } - """, isMultiline: true); + """); } else if (!defaultIfaceIsExclusive && defaultIface is not null) { - string defIfaceTypeName = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(defaultIface.ToTypeSignature(false)), TypedefNameType.Projected, false); - writer.WriteLine($$""" + string defIfaceTypeName = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(defaultIface.ToTypeSignature(false)), TypedefNameType.Projected, false).Format(); + writer.WriteLine(isMultiline: true, $$""" if (value is IWindowsRuntimeInterface<{{defIfaceTypeName}}> windowsRuntimeInterface) { return windowsRuntimeInterface.GetInterface(); } - """, isMultiline: true); + """); } else { - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ if (value is not null) { return value.GetDefaultInterface(); } - """, isMultiline: true); + """); } - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" return default; } @@ -274,11 +268,11 @@ public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged({{fullProjec } file sealed unsafe class {{nameStripped}}ComWrappersMarshallerAttribute : WindowsRuntimeComWrappersMarshallerAttribute - """, isMultiline: true); + """); using (writer.WriteBlock()) { AbiMethodBodyFactory.EmitUnsafeAccessorForDefaultIfaceIfGeneric(writer, context, defaultIface); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public override object CreateObject(void* value, out CreatedWrapperFlags wrapperFlags) { WindowsRuntimeObjectReference valueReference = WindowsRuntimeComWrappersMarshal.CreateObjectReference( @@ -289,7 +283,7 @@ public override object CreateObject(void* value, out CreatedWrapperFlags wrapper return new {{fullProjected}}(valueReference); } - """, isMultiline: true); + """); } writer.WriteLine(); @@ -297,12 +291,12 @@ public override object CreateObject(void* value, out CreatedWrapperFlags wrapper if (isSealed) { // file-scoped *ComWrappersCallback - implements IWindowsRuntimeObjectComWrappersCallback - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" file sealed unsafe class {{nameStripped}}ComWrappersCallback : IWindowsRuntimeObjectComWrappersCallback { - """, isMultiline: true); + """); AbiMethodBodyFactory.EmitUnsafeAccessorForDefaultIfaceIfGeneric(writer, context, defaultIface); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public static object CreateObject(void* value, out CreatedWrapperFlags wrapperFlags) { WindowsRuntimeObjectReference valueReference = WindowsRuntimeComWrappersMarshal.CreateObjectReferenceUnsafe( @@ -314,20 +308,20 @@ public static object CreateObject(void* value, out CreatedWrapperFlags wrapperFl return new {{fullProjected}}(valueReference); } } - """, isMultiline: true); + """); } else { // file-scoped *ComWrappersCallback - implements IWindowsRuntimeUnsealedObjectComWrappersCallback string nonProjectedRcn = $"{typeNs}.{nameStripped}"; - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" file sealed unsafe class {{nameStripped}}ComWrappersCallback : IWindowsRuntimeUnsealedObjectComWrappersCallback { - """, isMultiline: true); + """); AbiMethodBodyFactory.EmitUnsafeAccessorForDefaultIfaceIfGeneric(writer, context, defaultIface); // TryCreateObject (non-projected runtime class name match) - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public static unsafe bool TryCreateObject( void* value, ReadOnlySpan runtimeClassName, @@ -362,7 +356,7 @@ public static unsafe object CreateObject(void* value, out CreatedWrapperFlags wr return new {{fullProjected}}(valueReference); } } - """, isMultiline: true); + """); } } diff --git a/src/WinRT.Projection.Writer/Factories/AbiDelegateFactory.cs b/src/WinRT.Projection.Writer/Factories/AbiDelegateFactory.cs index 1d2f2b33a3..50bfa7e1b2 100644 --- a/src/WinRT.Projection.Writer/Factories/AbiDelegateFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/AbiDelegateFactory.cs @@ -1,15 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; using WindowsRuntime.ProjectionWriter.Models; -using WindowsRuntime.ProjectionWriter.Resolvers; using WindowsRuntime.ProjectionWriter.Writers; -using static WindowsRuntime.ProjectionWriter.References.ProjectionNames; namespace WindowsRuntime.ProjectionWriter.Factories; @@ -68,12 +66,12 @@ private static void WriteDelegateImpl(IndentedTextWriter writer, ProjectionEmitC } MethodSignatureInfo sig = new(invoke); - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); - string iidExpr = ObjRefNameGenerator.WriteIidExpression(context, type); + string nameStripped = type.GetStrippedName(); + string iidExpr = ObjRefNameGenerator.WriteIidExpression(context, type).Format(); + WriteAbiParameterTypesPointerCallback invokeParams = AbiInterfaceFactory.WriteAbiParameterTypesPointer(context, sig, includeParamNames: true); writer.WriteLine(); - writer.Write($$""" + writer.Write(isMultiline: true, $$""" internal static unsafe class {{nameStripped}}Impl { [FixedAddressValueType] @@ -92,31 +90,24 @@ public static nint Vtable } [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] - private static int Invoke( - """, isMultiline: true); - AbiInterfaceFactory.WriteAbiParameterTypesPointer(writer, context, sig, includeParamNames: true); - writer.Write(")"); + private static int Invoke({{invokeParams}}) + """); // Reuse the interface Do_Abi body emitter: delegates dispatch via __target.Invoke(...), // which is exactly the same shape as interface CCW dispatch. Pass the delegate's // projected name as 'ifaceFullName' and "Invoke" as 'methodName'. - string projectedDelegateForBody = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, true); - - if (!projectedDelegateForBody.StartsWith(GlobalPrefix, StringComparison.Ordinal)) - { - projectedDelegateForBody = GlobalPrefix + projectedDelegateForBody; - } + string projectedDelegateForBody = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, true).Format(); AbiMethodBodyFactory.EmitDoAbiBodyIfSimple(writer, context, sig, projectedDelegateForBody, "Invoke"); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public static ref readonly Guid IID { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref {{iidExpr}}; } } - """, isMultiline: true); + """); } private static void WriteDelegateVftbl(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) @@ -134,24 +125,20 @@ private static void WriteDelegateVftbl(IndentedTextWriter writer, ProjectionEmit } MethodSignatureInfo sig = new(invoke); - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); + string nameStripped = type.GetStrippedName(); + WriteAbiParameterTypesPointerCallback invokeParams = AbiInterfaceFactory.WriteAbiParameterTypesPointer(context, sig); writer.WriteLine(); - writer.Write($$""" + writer.WriteLine(isMultiline: true, $$""" [StructLayout(LayoutKind.Sequential)] internal unsafe struct {{nameStripped}}Vftbl { public delegate* unmanaged[MemberFunction] QueryInterface; public delegate* unmanaged[MemberFunction] AddRef; public delegate* unmanaged[MemberFunction] Release; - public delegate* unmanaged[MemberFunction]< - """, isMultiline: true); - AbiInterfaceFactory.WriteAbiParameterTypesPointer(writer, context, sig); - writer.WriteLine(""" - , int> Invoke; + public delegate* unmanaged[MemberFunction]<{{invokeParams}}, int> Invoke; } - """, isMultiline: true); + """); } private static void WriteNativeDelegate(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) @@ -169,31 +156,24 @@ private static void WriteNativeDelegate(IndentedTextWriter writer, ProjectionEmi } MethodSignatureInfo sig = new(invoke); - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); + string nameStripped = type.GetStrippedName(); writer.WriteLine(); - writer.Write($$""" + writer.Write(isMultiline: true, $$""" public static unsafe class {{nameStripped}}NativeDelegate { public static unsafe - """, isMultiline: true); - MethodFactory.WriteProjectionReturnType(writer, context, sig); - writer.Write($" {nameStripped}Invoke(this WindowsRuntimeObjectReference thisReference"); - - if (sig.Parameters.Count > 0) - { - writer.Write(", "); - } - - MethodFactory.WriteParameterList(writer, context, sig); - writer.Write(")"); + """); + WriteProjectionReturnTypeCallback ret = MethodFactory.WriteProjectionReturnType(context, sig); + WriteParameterListCallback parms = MethodFactory.WriteParameterList(context, sig); + string comma = sig.Parameters.Count > 0 ? ", " : ""; + writer.Write($"{ret} {nameStripped}Invoke(this WindowsRuntimeObjectReference thisReference{comma}{parms})"); // Reuse the interface caller body emitter. Delegate Invoke is at vtable slot 3 // (after QI/AddRef/Release). Functionally equivalent to the truth's // 'var abiInvoke = ((Vftbl*)*(void***)ThisPtr)->Invoke;' form, just routed // through the slot-indexed dispatch shared with interface CCW callers. - AbiMethodBodyFactory.EmitAbiMethodBodyIfSimple(writer, context, sig, slot: 3, isNoExcept: invoke.IsNoExcept()); + AbiMethodBodyFactory.EmitAbiMethodBodyIfSimple(writer, context, sig, slot: 3, isNoExcept: invoke.IsNoExcept); writer.WriteLine("}"); } @@ -205,13 +185,12 @@ private static void WriteDelegateInterfaceEntriesImpl(IndentedTextWriter writer, return; } - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); - string iidExpr = ObjRefNameGenerator.WriteIidExpression(context, type); - string iidRefExpr = ObjRefNameGenerator.WriteIidReferenceExpression(type); + string nameStripped = type.GetStrippedName(); + string iidExpr = ObjRefNameGenerator.WriteIidExpression(context, type).Format(); + WriteIidReferenceExpressionCallback iidRefExpr = ObjRefNameGenerator.WriteIidReferenceExpression(type); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" file static class {{nameStripped}}InterfaceEntriesImpl { [FixedAddressValueType] @@ -223,16 +202,23 @@ file static class {{nameStripped}}InterfaceEntriesImpl Entries.Delegate.Vtable = {{nameStripped}}Impl.Vtable; Entries.DelegateReference.IID = {{iidRefExpr}}; Entries.DelegateReference.Vtable = {{nameStripped}}ReferenceImpl.Vtable; - """, isMultiline: true); - writer.IncreaseIndent(); - writer.IncreaseIndent(); - WellKnownInterfaceEntriesEmitter.EmitDelegateReferenceWellKnownEntries(writer); - writer.DecreaseIndent(); - writer.DecreaseIndent(); - writer.WriteLine(""" + Entries.IPropertyValue.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IPropertyValue; + Entries.IPropertyValue.Vtable = global::WindowsRuntime.InteropServices.IPropertyValueImpl.OtherTypeVtable; + Entries.IStringable.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IStringable; + Entries.IStringable.Vtable = global::WindowsRuntime.InteropServices.IStringableImpl.Vtable; + Entries.IWeakReferenceSource.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IWeakReferenceSource; + Entries.IWeakReferenceSource.Vtable = global::WindowsRuntime.InteropServices.IWeakReferenceSourceImpl.Vtable; + Entries.IMarshal.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IMarshal; + Entries.IMarshal.Vtable = global::WindowsRuntime.InteropServices.IMarshalImpl.Vtable; + Entries.IAgileObject.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IAgileObject; + Entries.IAgileObject.Vtable = global::WindowsRuntime.InteropServices.IAgileObjectImpl.Vtable; + Entries.IInspectable.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IInspectable; + Entries.IInspectable.Vtable = global::WindowsRuntime.InteropServices.IInspectableImpl.Vtable; + Entries.IUnknown.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IUnknown; + Entries.IUnknown.Vtable = global::WindowsRuntime.InteropServices.IUnknownImpl.Vtable; } } - """, isMultiline: true); + """); } /// @@ -261,19 +247,13 @@ public static void WriteDelegateEventSourceSubclass(IndentedTextWriter writer, P } MethodSignatureInfo sig = new(invoke); - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); + string nameStripped = type.GetStrippedName(); // Compute the projected type name (with global::) used as the generic argument. - string projectedName = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, true); - - if (!projectedName.StartsWith(GlobalPrefix, StringComparison.Ordinal)) - { - projectedName = GlobalPrefix + projectedName; - } + string projectedName = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, true).Format(); writer.WriteLine(); - writer.Write($$""" + writer.Write(isMultiline: true, $$""" public sealed unsafe class {{nameStripped}}EventSource : EventSource<{{projectedName}}> { /// @@ -306,74 +286,38 @@ public EventState(void* thisPtr, int index) protected override {{projectedName}} GetEventInvoke() { return ( - """, isMultiline: true); + """); for (int i = 0; i < sig.Parameters.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } - - ParameterCategory pc = ParameterCategoryResolver.GetParamCategory(sig.Parameters[i]); - - if (pc == ParameterCategory.Ref) - { - writer.Write("in "); - } - else if (pc is ParameterCategory.Out or ParameterCategory.ReceiveArray) - { - writer.Write("out "); - } - - string raw = sig.Parameters[i].Parameter.Name ?? "p"; - writer.Write(CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw); + WriteParameterNameWithModifierCallback p = ClassMembersFactory.WriteParameterNameWithModifier(context, sig.Parameters[i]); + writer.Write($"{(i > 0 ? ", " : "")}{p}"); } writer.Write(") => TargetDelegate.Invoke("); for (int i = 0; i < sig.Parameters.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } - - ParameterCategory pc = ParameterCategoryResolver.GetParamCategory(sig.Parameters[i]); - - if (pc == ParameterCategory.Ref) - { - writer.Write("in "); - } - else if (pc is ParameterCategory.Out or ParameterCategory.ReceiveArray) - { - writer.Write("out "); - } - - string raw = sig.Parameters[i].Parameter.Name ?? "p"; - writer.Write(CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw); + WriteParameterNameWithModifierCallback p = ClassMembersFactory.WriteParameterNameWithModifier(context, sig.Parameters[i]); + writer.Write($"{(i > 0 ? ", " : "")}{p}"); } - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ ); } } } - """, isMultiline: true); + """); } - /// - /// Writes a marshaller stub for a delegate. - /// /// /// Emits just the <Name>Marshaller class for a delegate. /// private static void WriteDelegateMarshallerOnly(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); - string typeNs = type.Namespace?.Value ?? string.Empty; - string fullProjected = $"global::{typeNs}.{nameStripped}"; - string iidExpr = ObjRefNameGenerator.WriteIidExpression(context, type); + string nameStripped = type.GetStrippedName(); + string typeNs = type.GetRawNamespace(); + string fullProjected = TypedefNameWriter.BuildGlobalQualifiedName(typeNs, nameStripped); + string iidExpr = ObjRefNameGenerator.WriteIidExpression(context, type).Format(); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public static unsafe class {{nameStripped}}Marshaller { public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged({{fullProjected}} value) @@ -388,7 +332,7 @@ public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged({{fullProjec } #nullable disable } - """, isMultiline: true); + """); } /// @@ -400,14 +344,13 @@ public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged({{fullProjec /// private static void WriteDelegateComWrappersCallback(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); - string typeNs = type.Namespace?.Value ?? string.Empty; - string fullProjected = $"global::{typeNs}.{nameStripped}"; - string iidExpr = ObjRefNameGenerator.WriteIidExpression(context, type); + string nameStripped = type.GetStrippedName(); + string typeNs = type.GetRawNamespace(); + string fullProjected = TypedefNameWriter.BuildGlobalQualifiedName(typeNs, nameStripped); + string iidExpr = ObjRefNameGenerator.WriteIidExpression(context, type).Format(); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" file abstract unsafe class {{nameStripped}}ComWrappersCallback : IWindowsRuntimeObjectComWrappersCallback { /// @@ -421,7 +364,7 @@ public static object CreateObject(void* value, out CreatedWrapperFlags wrapperFl return new {{fullProjected}}(valueReference.{{nameStripped}}Invoke); } } - """, isMultiline: true); + """); } /// @@ -431,12 +374,11 @@ public static object CreateObject(void* value, out CreatedWrapperFlags wrapperFl /// private static void WriteDelegateComWrappersMarshallerAttribute(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); - string iidRefExpr = ObjRefNameGenerator.WriteIidReferenceExpression(type); + string nameStripped = type.GetStrippedName(); + WriteIidReferenceExpressionCallback iidRefExpr = ObjRefNameGenerator.WriteIidReferenceExpression(type); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" internal sealed unsafe class {{nameStripped}}ComWrappersMarshallerAttribute : WindowsRuntimeComWrappersMarshallerAttribute { /// @@ -460,7 +402,7 @@ public override object CreateObject(void* value, out CreatedWrapperFlags wrapper return WindowsRuntimeDelegateMarshaller.UnboxToManaged<{{nameStripped}}ComWrappersCallback>(value, in {{iidRefExpr}})!; } } - """, isMultiline: true); + """); } } diff --git a/src/WinRT.Projection.Writer/Factories/AbiInterfaceFactory.cs b/src/WinRT.Projection.Writer/Factories/AbiInterfaceFactory.cs index dea45ff2a4..a59fc7f96a 100644 --- a/src/WinRT.Projection.Writer/Factories/AbiInterfaceFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/AbiInterfaceFactory.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Generic; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; @@ -36,7 +36,7 @@ public static void WriteAbiInterface(IndentedTextWriter writer, ProjectionEmitCo WriteInterfaceMarshallerStub(writer, context, type); // For internal projections, just the static ABI methods class is enough. - if (TypeCategorization.IsProjectionInternal(type)) + if (type.IsProjectionInternal) { return; } @@ -55,6 +55,13 @@ public static void WriteAbiParameterTypesPointer(IndentedTextWriter writer, Proj WriteAbiParameterTypesPointer(writer, context, sig, includeParamNames: false); } + /// + /// A callback emitting the ABI parameter types. + public static WriteAbiParameterTypesPointerCallback WriteAbiParameterTypesPointer(ProjectionEmitContext context, MethodSignatureInfo sig, bool includeParamNames = false) + { + return new(context, sig, includeParamNames); + } + /// /// Writes the ABI parameter types for a vtable function pointer signature, optionally /// including parameter names (for method declarations vs. function pointer type lists). @@ -64,24 +71,22 @@ public static void WriteAbiParameterTypesPointer(IndentedTextWriter writer, Proj // void* thisPtr, then each param's ABI type, then return type pointer writer.Write("void*"); - if (includeParamNames) - { - writer.Write(" thisPtr"); - } + writer.WriteIf(includeParamNames, " thisPtr"); for (int i = 0; i < sig.Parameters.Count; i++) { writer.Write(", "); ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); + string paramName = p.GetRawName(); + WriteEscapedIdentifierCallback name = IdentifierEscaping.WriteEscapedIdentifier(paramName); if (p.Type is SzArrayTypeSignature) { // length pointer + value pointer. if (includeParamNames) { - writer.Write($"uint __{p.Parameter.Name ?? "param"}Size, void* "); - IdentifierEscaping.WriteEscapedIdentifier(writer, p.Parameter.Name ?? "param"); + writer.Write($"uint __{paramName}Size, void* {name}"); } else { @@ -93,64 +98,56 @@ public static void WriteAbiParameterTypesPointer(IndentedTextWriter writer, Proj // Special case: 'out T[]' is a ReceiveArray ABI signature: (uint* size, T** data). if (br.BaseType is SzArrayTypeSignature brSz && cat == ParameterCategory.ReceiveArray) { - bool isRefElemBr = brSz.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(brSz.BaseType) || brSz.BaseType.IsObject() || brSz.BaseType.IsGenericInstance(); + bool isRefElemBr = brSz.BaseType.IsAbiRefLike(context.AbiTypeKindResolver); + WriteAbiTypeCallback elemAbi = AbiTypeWriter.WriteAbiType(context, TypeSemanticsFactory.Get(brSz.BaseType)); if (includeParamNames) { - writer.Write($"uint* __{p.Parameter.Name ?? "param"}Size, "); - if (isRefElemBr) { - writer.Write("void*** "); + writer.Write($"uint* __{paramName}Size, void*** {name}"); } else { - AbiTypeWriter.WriteAbiType(writer, context, TypeSemanticsFactory.Get(brSz.BaseType)); - writer.Write("** "); + writer.Write($"uint* __{paramName}Size, {elemAbi}** {name}"); } - - IdentifierEscaping.WriteEscapedIdentifier(writer, p.Parameter.Name ?? "param"); } else { - writer.Write("uint*, "); - if (isRefElemBr) { - writer.Write("void***"); + writer.Write("uint*, void***"); } else { - AbiTypeWriter.WriteAbiType(writer, context, TypeSemanticsFactory.Get(brSz.BaseType)); - writer.Write("**"); + writer.Write($"uint*, {elemAbi}**"); } } } else { - AbiTypeWriter.WriteAbiType(writer, context, TypeSemanticsFactory.Get(br.BaseType)); - writer.Write("*"); - + WriteAbiTypeCallback abi = AbiTypeWriter.WriteAbiType(context, TypeSemanticsFactory.Get(br.BaseType)); if (includeParamNames) { - writer.Write(" "); - IdentifierEscaping.WriteEscapedIdentifier(writer, p.Parameter.Name ?? "param"); + writer.Write($"{abi}* {name}"); + } + else + { + writer.Write($"{abi}*"); } } } else { - AbiTypeWriter.WriteAbiType(writer, context, TypeSemanticsFactory.Get(p.Type)); - - if (cat is ParameterCategory.Out or ParameterCategory.Ref) + WriteAbiTypeCallback abi = AbiTypeWriter.WriteAbiType(context, TypeSemanticsFactory.Get(p.Type)); + string ptr = cat is ParameterCategory.Out or ParameterCategory.Ref ? "*" : ""; + if (includeParamNames) { - writer.Write("*"); + writer.Write($"{abi}{ptr} {name}"); } - - if (includeParamNames) + else { - writer.Write(" "); - IdentifierEscaping.WriteEscapedIdentifier(writer, p.Parameter.Name ?? "param"); + writer.Write($"{abi}{ptr}"); } } } @@ -161,30 +158,30 @@ public static void WriteAbiParameterTypesPointer(IndentedTextWriter writer, Proj writer.Write(", "); string retName = AbiTypeHelpers.GetReturnParamName(sig); string retSizeName = AbiTypeHelpers.GetReturnSizeParamName(sig); + // Special handling for SzArray return types: WinRT projects them as a (uint*, T**) pair. if (sig.ReturnType is SzArrayTypeSignature retSz) { + WriteAbiTypeCallback elemAbi = AbiTypeWriter.WriteAbiType(context, TypeSemanticsFactory.Get(retSz.BaseType)); if (includeParamNames) { - writer.Write($"uint* {retSizeName}, "); - AbiTypeWriter.WriteAbiType(writer, context, TypeSemanticsFactory.Get(retSz.BaseType)); - writer.Write($"** {retName}"); + writer.Write($"uint* {retSizeName}, {elemAbi}** {retName}"); } else { - writer.Write("uint*, "); - AbiTypeWriter.WriteAbiType(writer, context, TypeSemanticsFactory.Get(retSz.BaseType)); - writer.Write("**"); + writer.Write($"uint*, {elemAbi}**"); } } else { - AbiTypeWriter.WriteAbiType(writer, context, TypeSemanticsFactory.Get(sig.ReturnType)); - writer.Write("*"); - + WriteAbiTypeCallback retAbi = AbiTypeWriter.WriteAbiType(context, TypeSemanticsFactory.Get(sig.ReturnType)); if (includeParamNames) { - writer.Write($" {retName}"); + writer.Write($"{retAbi}* {retName}"); + } + else + { + writer.Write($"{retAbi}*"); } } } @@ -205,32 +202,30 @@ public static void WriteInterfaceVftbl(IndentedTextWriter writer, ProjectionEmit return; } - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); + string nameStripped = type.GetStrippedName(); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" [StructLayout(LayoutKind.Sequential)] internal unsafe struct {{nameStripped}}Vftbl - """, isMultiline: true); + """); using (writer.WriteBlock()) { - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ public delegate* unmanaged[MemberFunction] QueryInterface; public delegate* unmanaged[MemberFunction] AddRef; public delegate* unmanaged[MemberFunction] Release; public delegate* unmanaged[MemberFunction] GetIids; public delegate* unmanaged[MemberFunction] GetRuntimeClassName; public delegate* unmanaged[MemberFunction] GetTrustLevel; - """, isMultiline: true); + """); foreach (MethodDefinition method in type.Methods) { string vm = AbiTypeHelpers.GetVirtualMethodName(type, method); MethodSignatureInfo sig = new(method); - writer.Write("public delegate* unmanaged[MemberFunction]<"); - WriteAbiParameterTypesPointer(writer, context, sig); - writer.WriteLine($", int> {vm};"); + WriteAbiParameterTypesPointerCallback abiParams = WriteAbiParameterTypesPointer(context, sig); + writer.WriteLine($"public delegate* unmanaged[MemberFunction]<{abiParams}, int> {vm};"); } } } @@ -250,36 +245,31 @@ public static void WriteInterfaceImpl(IndentedTextWriter writer, ProjectionEmitC return; } - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); + string nameStripped = type.GetStrippedName(); writer.WriteLine(); writer.WriteLine($"public static unsafe class {nameStripped}Impl"); using IndentedTextWriter.Block __implBlock = writer.WriteBlock(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" [FixedAddressValueType] private static readonly {{nameStripped}}Vftbl Vftbl; static {{nameStripped}}Impl() { *(IInspectableVftbl*)Unsafe.AsPointer(ref Vftbl) = *(IInspectableVftbl*)IInspectableImpl.Vtable; - """, isMultiline: true); + """); foreach (MethodDefinition method in type.Methods) { string vm = AbiTypeHelpers.GetVirtualMethodName(type, method); writer.WriteLine($" Vftbl.{vm} = &Do_Abi_{vm};"); } - writer.Write(""" + writer.Write(isMultiline: true, $$""" } public static ref readonly Guid IID { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref - """, isMultiline: true); - AbiTypeHelpers.WriteIidGuidReference(writer, context, type); - writer.WriteLine(""" - ; + get => ref {{AbiTypeHelpers.WriteIidGuidReference(context, type)}}; } public static nint Vtable @@ -287,7 +277,7 @@ public static nint Vtable [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (nint)Unsafe.AsPointer(in Vftbl); } - """, isMultiline: true); + """); writer.WriteLine(); // Do_Abi_* implementations: emit real bodies for simple primitive cases, @@ -304,12 +294,11 @@ public static nint Vtable if (context.Settings.Component) { - MetadataCache cache = context.Cache; - exclusiveToOwner = AbiTypeHelpers.GetExclusiveToType(cache, type); + exclusiveToOwner = AbiTypeHelpers.GetExclusiveToType(context.Cache, type); if (exclusiveToOwner is not null) { - foreach (KeyValuePair kv in AttributedTypes.Get(exclusiveToOwner, cache)) + foreach (KeyValuePair kv in AttributedTypes.Get(exclusiveToOwner, context.Cache)) { if (kv.Value.Type == type && (kv.Value.Statics || kv.Value.Activatable)) { @@ -324,8 +313,8 @@ public static nint Vtable if (exclusiveToOwner is not null && !exclusiveIsFactoryOrStatic) { - string ownerNs = exclusiveToOwner.Namespace?.Value ?? string.Empty; - string ownerNm = IdentifierEscaping.StripBackticks(exclusiveToOwner.Name?.Value ?? string.Empty); + string ownerNs = exclusiveToOwner.GetRawNamespace(); + string ownerNm = exclusiveToOwner.GetStrippedName(); ifaceFullName = string.IsNullOrEmpty(ownerNs) ? GlobalPrefix + ownerNm : GlobalPrefix + ownerNs + "." + ownerNm; @@ -334,20 +323,15 @@ public static nint Vtable { // Factory/static interfaces in authoring mode are implemented by the generated // 'global::ABI.Impl..' type that the activation factory CCW exposes. - string ifaceNs = type.Namespace?.Value ?? string.Empty; - string ifaceNm = IdentifierEscaping.StripBackticks(type.Name?.Value ?? string.Empty); + string ifaceNs = type.GetRawNamespace(); + string ifaceNm = type.GetStrippedName(); ifaceFullName = string.IsNullOrEmpty(ifaceNs) ? "global::ABI.Impl." + ifaceNm : "global::ABI.Impl." + ifaceNs + "." + ifaceNm; } else { - ifaceFullName = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, true); - - if (!ifaceFullName.StartsWith(GlobalPrefix, StringComparison.Ordinal)) - { - ifaceFullName = GlobalPrefix + ifaceFullName; - } + ifaceFullName = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, true).Format(); } // Build a map of event add/remove methods to their event so we can emit the table field @@ -357,26 +341,14 @@ public static nint Vtable // Build sets of property accessors and event accessors so the first loop below can // iterate "regular" methods (non-property, non-event) only. Do_Abi bodies are emitted in // this order: methods first, then properties (setter before getter), then events. - HashSet propertyAccessors = []; - foreach (PropertyDefinition prop in type.Properties) - { - if (prop.GetMethod is MethodDefinition g) - { - _ = propertyAccessors.Add(g); - } - - if (prop.SetMethod is MethodDefinition s) - { - _ = propertyAccessors.Add(s); - } - } + HashSet propertyAccessors = [.. type.GetPropertyAccessors()]; // Local helper to emit a single Do_Abi method body for a given MethodDefinition. void EmitOneDoAbi(MethodDefinition method) { string vm = AbiTypeHelpers.GetVirtualMethodName(type, method); MethodSignatureInfo sig = new(method); - string mname = method.Name?.Value ?? string.Empty; + string mname = method.GetRawName(); // If this method is an event add accessor, emit the per-event ConditionalWeakTable // before the Do_Abi method. @@ -385,12 +357,11 @@ void EmitOneDoAbi(MethodDefinition method) EventTableFactory.EmitEventTableField(writer, context, evt, ifaceFullName); } - writer.Write($$""" + WriteAbiParameterTypesPointerCallback doAbiParams = WriteAbiParameterTypesPointer(context, sig, includeParamNames: true); + writer.Write(isMultiline: true, $$""" [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] - private static unsafe int Do_Abi_{{vm}}( - """, isMultiline: true); - WriteAbiParameterTypesPointer(writer, context, sig, includeParamNames: true); - writer.Write(")"); + private static unsafe int Do_Abi_{{vm}}({{doAbiParams}}) + """); if (eventMap is not null && eventMap.TryGetValue(method, out EventDefinition? evt2)) { @@ -459,7 +430,7 @@ void EmitOneDoAbi(MethodDefinition method) /// public static void WriteInterfaceMarshaller(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - if (TypeCategorization.IsExclusiveTo(type)) + if (type.IsExclusiveTo) { return; } @@ -469,48 +440,29 @@ public static void WriteInterfaceMarshaller(IndentedTextWriter writer, Projectio return; } - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); + string nameStripped = type.GetStrippedName(); + + WriteTypedefNameCallback typedefName = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, false); + WriteTypeParamsCallback typeParams = TypedefNameWriter.WriteTypeParams(type); + WriteIidGuidReferenceCallback iid = AbiTypeHelpers.WriteIidGuidReference(context, type); writer.WriteLine(); - writer.Write($$""" + writer.WriteLine(isMultiline: true, $$""" #nullable enable public static unsafe class {{nameStripped}}Marshaller { - public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged( - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, false); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.Write(""" - value) + public static WindowsRuntimeObjectReferenceValue ConvertToUnmanaged({{typedefName}}{{typeParams}} value) { - return WindowsRuntimeInterfaceMarshaller< - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, false); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.Write(">.ConvertToUnmanaged(value, "); - AbiTypeHelpers.WriteIidGuidReference(writer, context, type); - writer.Write(""" - ); + return WindowsRuntimeInterfaceMarshaller<{{typedefName}}{{typeParams}}>.ConvertToUnmanaged(value, {{iid}}); } - - public static - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, false); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.Write(""" - ? ConvertToManaged(void* value) + + public static {{typedefName}}{{typeParams}}? ConvertToManaged(void* value) { - return ( - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, false); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.WriteLine(""" - ?) WindowsRuntimeObjectMarshaller.ConvertToManaged(value); + return ({{typedefName}}{{typeParams}}?) WindowsRuntimeObjectMarshaller.ConvertToManaged(value); } } #nullable disable - """, isMultiline: true); + """); } /// @@ -520,12 +472,12 @@ public static /// private static void WriteInterfaceMarshallerStub(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); + string nameStripped = type.GetStrippedName(); + // exclusive to a class (and not opted into PublicExclusiveTo) or if it's marked // [ProjectionInternal]; public otherwise. - bool useInternal = (TypeCategorization.IsExclusiveTo(type) && !context.Settings.PublicExclusiveTo) - || TypeCategorization.IsProjectionInternal(type); + bool useInternal = (type.IsExclusiveTo && !context.Settings.PublicExclusiveTo) + || type.IsProjectionInternal; // Fast ABI: if this interface is a non-default exclusive-to interface of a fast-abi // class, skip emitting it entirely — its members are merged into the default @@ -540,11 +492,11 @@ private static void WriteInterfaceMarshallerStub(IndentedTextWriter writer, Proj // is manually projected in WinRT.Runtime, e.g. IColorHelperStatics for ColorHelper, // IColorsStatics for Colors, IFontWeightsStatics for FontWeights). the original code also // omits these because their owning class is not projected. - if (TypeCategorization.IsExclusiveTo(type)) + if (type.IsExclusiveTo) { TypeDefinition? owningClass = AbiTypeHelpers.GetExclusiveToType(context.Cache, type); - if (owningClass is not null && !context.Settings.Filter.Includes(owningClass)) + if (owningClass is not null && !context.Settings.Filter.Includes(owningClass.FullName)) { return; } @@ -553,7 +505,7 @@ private static void WriteInterfaceMarshallerStub(IndentedTextWriter writer, Proj // are inlined in the RCW class, so we skip emitting them in the Methods type. bool skipExclusiveEvents = false; - if (TypeCategorization.IsExclusiveTo(type) && !context.Settings.PublicExclusiveTo) + if (type.IsExclusiveTo && !context.Settings.PublicExclusiveTo) { TypeDefinition? classType = AbiTypeHelpers.GetExclusiveToType(context.Cache, type); @@ -561,9 +513,7 @@ private static void WriteInterfaceMarshallerStub(IndentedTextWriter writer, Proj { foreach (InterfaceImplementation impl in classType.Interfaces) { - TypeDefinition? implDef = AbiTypeHelpers.ResolveInterfaceTypeDef(context.Cache, impl.Interface!); - - if (implDef is not null && implDef == type) + if (impl.TryResolveTypeDef(context.Cache, out TypeDefinition? implDef) && implDef == type) { skipExclusiveEvents = true; break; @@ -586,6 +536,7 @@ private static void WriteInterfaceMarshallerStub(IndentedTextWriter writer, Proj if (isFastAbiDefault) { int slot = InspectableMethodCount; + // Default interface: skip its events (they're inlined in the RCW class). segments.Add((type, slot, true)); slot += AbiTypeHelpers.CountMethods(type) + AbiTypeHelpers.GetClassHierarchyIndex(context.Cache, fastAbi!.Value.Class); @@ -616,10 +567,10 @@ private static void WriteInterfaceMarshallerStub(IndentedTextWriter writer, Proj return; } - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" {{(useInternal ? "internal static class " : "public static class ")}}{{nameStripped}}Methods { - """, isMultiline: true); + """); foreach ((TypeDefinition iface, int startSlot, bool segSkipEvents) in segments) { diff --git a/src/WinRT.Projection.Writer/Factories/AbiInterfaceIDicFactory.cs b/src/WinRT.Projection.Writer/Factories/AbiInterfaceIDicFactory.cs index 3d8d4757b2..00b2689f6a 100644 --- a/src/WinRT.Projection.Writer/Factories/AbiInterfaceIDicFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/AbiInterfaceIDicFactory.cs @@ -1,16 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Generic; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; using WindowsRuntime.ProjectionWriter.Models; using WindowsRuntime.ProjectionWriter.Writers; -using static WindowsRuntime.ProjectionWriter.References.ProjectionNames; using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; namespace WindowsRuntime.ProjectionWriter.Factories; @@ -27,7 +26,7 @@ internal static class AbiInterfaceIDicFactory /// public static void WriteInterfaceIdicImpl(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - if (TypeCategorization.IsExclusiveTo(type) && !context.Settings.IdicExclusiveTo) + if (type.IsExclusiveTo && !context.Settings.IdicExclusiveTo) { return; } @@ -37,17 +36,16 @@ public static void WriteInterfaceIdicImpl(IndentedTextWriter writer, ProjectionE return; } - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); + string nameStripped = type.GetStrippedName(); + WriteTypedefNameWithTypeParamsCallback parent = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.Projected, true); + WriteGuidAttributeCallback guidAttr = InterfaceFactory.WriteGuidAttribute(type); writer.WriteLine(); - writer.WriteLine("[DynamicInterfaceCastableImplementation]"); - InterfaceFactory.WriteGuidAttribute(writer, type); - writer.WriteLine(); - writer.Write($"file interface {nameStripped} : "); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.WriteLine(); + writer.WriteLine(isMultiline: true, $$""" + [DynamicInterfaceCastableImplementation] + {{guidAttr}} + file interface {{nameStripped}} : {{parent}} + """); using (writer.WriteBlock()) { // Emit DIM bodies that dispatch through the static ABI Methods class. @@ -74,14 +72,7 @@ internal static void WriteInterfaceIdicImplMembersForRequiredInterfaces( { foreach (InterfaceImplementation impl in type.Interfaces) { - if (impl.Interface is null) - { - continue; - } - - TypeDefinition? required = AbiTypeHelpers.ResolveInterfaceTypeDef(context.Cache, impl.Interface); - - if (required is null) + if (!impl.TryResolveTypeDef(context.Cache, out TypeDefinition? required)) { continue; } @@ -100,26 +91,14 @@ internal static void WriteInterfaceIdicImplMembersForRequiredInterfaces( // Emit explicit-interface DIM forwarders for the BCL members so the DIC shim // satisfies them when queried via casts like '((IList)(WindowsRuntimeObject)this)'. EmitDicShimMappedBclForwarders(writer, context, rName); + // IBindableVector's IList forwarders already include the IEnumerable.GetEnumerator // forwarder (since IList : IEnumerable). Pre-add IBindableIterable to the visited // set so we don't emit a second GetEnumerator forwarder for it. We also walk the // required interfaces so any other (deeper) inherited mapped interface is covered. if (rName == "IBindableVector") { - foreach (InterfaceImplementation impl2 in required.Interfaces) - { - if (impl2.Interface is null) - { - continue; - } - - TypeDefinition? r2 = AbiTypeHelpers.ResolveInterfaceTypeDef(context.Cache, impl2.Interface); - - if (r2 is not null) - { - _ = visited.Add(r2); - } - } + required.MarkRequiredInterfacesVisited(context.Cache, visited); } continue; @@ -132,24 +111,12 @@ internal static void WriteInterfaceIdicImplMembersForRequiredInterfaces( { if (impl.Interface is TypeSpecification tsMap && tsMap.Signature is GenericInstanceTypeSignature giMap && giMap.TypeArguments.Count == 2) { - string keyText = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(giMap.TypeArguments[0]), TypedefNameType.Projected, true); - string valueText = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(giMap.TypeArguments[1]), TypedefNameType.Projected, true); + string keyText = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(giMap.TypeArguments[0]), TypedefNameType.Projected, true).Format(); + string valueText = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(giMap.TypeArguments[1]), TypedefNameType.Projected, true).Format(); EmitDicShimIObservableMapForwarders(writer, context, keyText, valueText); - // Mark the inherited IMap`2 / IIterable`1 as visited so they aren't re-emitted. - foreach (InterfaceImplementation impl2 in required.Interfaces) - { - if (impl2.Interface is null) - { - continue; - } - - TypeDefinition? r2 = AbiTypeHelpers.ResolveInterfaceTypeDef(context.Cache, impl2.Interface); - if (r2 is not null) - { - _ = visited.Add(r2); - } - } + // Mark the inherited IMap`2 / IIterable`1 as visited so they aren't re-emitted. + required.MarkRequiredInterfacesVisited(context.Cache, visited); } continue; @@ -159,22 +126,9 @@ internal static void WriteInterfaceIdicImplMembersForRequiredInterfaces( { if (impl.Interface is TypeSpecification tsVec && tsVec.Signature is GenericInstanceTypeSignature giVec && giVec.TypeArguments.Count == 1) { - string elementText = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(giVec.TypeArguments[0]), TypedefNameType.Projected, true); + string elementText = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(giVec.TypeArguments[0]), TypedefNameType.Projected, true).Format(); EmitDicShimIObservableVectorForwarders(writer, context, elementText); - foreach (InterfaceImplementation impl2 in required.Interfaces) - { - if (impl2.Interface is null) - { - continue; - } - - TypeDefinition? r2 = AbiTypeHelpers.ResolveInterfaceTypeDef(context.Cache, impl2.Interface); - - if (r2 is not null) - { - _ = visited.Add(r2); - } - } + required.MarkRequiredInterfacesVisited(context.Cache, visited); } continue; @@ -203,8 +157,11 @@ internal static void EmitDicShimIObservableMapForwarders(IndentedTextWriter writ string target = $"((global::System.Collections.Generic.IDictionary<{keyText}, {valueText}>)(WindowsRuntimeObject)this)"; string self = $"global::System.Collections.Generic.IDictionary<{keyText}, {valueText}>."; string icoll = $"global::System.Collections.Generic.ICollection>."; + string obsTarget = $"((global::Windows.Foundation.Collections.IObservableMap<{keyText}, {valueText}>)(WindowsRuntimeObject)this)"; + string obsSelf = $"global::Windows.Foundation.Collections.IObservableMap<{keyText}, {valueText}>."; + writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" ICollection<{{keyText}}> {{self}}Keys => {{target}}.Keys; ICollection<{{valueText}}> {{self}}Values => {{target}}.Values; int {{icoll}}Count => {{target}}.Count; @@ -225,18 +182,13 @@ internal static void EmitDicShimIObservableMapForwarders(IndentedTextWriter writ bool ICollection>.Remove(KeyValuePair<{{keyText}}, {{valueText}}> item) => {{target}}.Remove(item); IEnumerator> IEnumerable>.GetEnumerator() => {{target}}.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - """, isMultiline: true); - // IObservableMap.MapChanged event forwarder. - string obsTarget = $"((global::Windows.Foundation.Collections.IObservableMap<{keyText}, {valueText}>)(WindowsRuntimeObject)this)"; - string obsSelf = $"global::Windows.Foundation.Collections.IObservableMap<{keyText}, {valueText}>."; - writer.WriteLine(); - writer.WriteLine($$""" + event global::Windows.Foundation.Collections.MapChangedEventHandler<{{keyText}}, {{valueText}}> {{obsSelf}}MapChanged { add => {{obsTarget}}.MapChanged += value; remove => {{obsTarget}}.MapChanged -= value; } - """, isMultiline: true); + """); } /// @@ -250,8 +202,11 @@ internal static void EmitDicShimIObservableVectorForwarders(IndentedTextWriter w string target = $"((global::System.Collections.Generic.IList<{elementText}>)(WindowsRuntimeObject)this)"; string self = $"global::System.Collections.Generic.IList<{elementText}>."; string icoll = $"global::System.Collections.Generic.ICollection<{elementText}>."; + string obsTarget = $"((global::Windows.Foundation.Collections.IObservableVector<{elementText}>)(WindowsRuntimeObject)this)"; + string obsSelf = $"global::Windows.Foundation.Collections.IObservableVector<{elementText}>."; + writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" int {{icoll}}Count => {{target}}.Count; bool {{icoll}}IsReadOnly => {{target}}.IsReadOnly; {{elementText}} {{self}}this[int index] @@ -269,18 +224,13 @@ internal static void EmitDicShimIObservableVectorForwarders(IndentedTextWriter w bool {{icoll}}Remove({{elementText}} item) => {{target}}.Remove(item); IEnumerator<{{elementText}}> IEnumerable<{{elementText}}>.GetEnumerator() => {{target}}.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - """, isMultiline: true); - // IObservableVector.VectorChanged event forwarder. - string obsTarget = $"((global::Windows.Foundation.Collections.IObservableVector<{elementText}>)(WindowsRuntimeObject)this)"; - string obsSelf = $"global::Windows.Foundation.Collections.IObservableVector<{elementText}>."; - writer.WriteLine(); - writer.WriteLine($$""" + event global::Windows.Foundation.Collections.VectorChangedEventHandler<{{elementText}}> {{obsSelf}}VectorChanged { add => {{obsTarget}}.VectorChanged += value; remove => {{obsTarget}}.VectorChanged -= value; } - """, isMultiline: true); + """); } /// @@ -293,44 +243,24 @@ internal static void WriteInterfaceIdicImplMembersForInheritedInterface(Indented { // The CCW interface name (the projected interface name with global:: prefix). For the // delegating thunks we cast through this same projected interface type. - string ccwIfaceName = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, true); + string ccwIfaceName = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, true).Format(); - if (!ccwIfaceName.StartsWith(GlobalPrefix, StringComparison.Ordinal)) + foreach (MethodDefinition method in type.GetNonSpecialMethods()) { - ccwIfaceName = GlobalPrefix + ccwIfaceName; - } - - foreach (MethodDefinition method in type.Methods) - { - if (method.IsSpecial()) - { - continue; - } - MethodSignatureInfo sig = new(method); - string mname = method.Name?.Value ?? string.Empty; + string mname = method.GetRawName(); writer.WriteLine(); - MethodFactory.WriteProjectionReturnType(writer, context, sig); - writer.Write($" {ccwIfaceName}.{mname}("); - MethodFactory.WriteParameterList(writer, context, sig); - writer.Write($") => (({ccwIfaceName})(WindowsRuntimeObject)this).{mname}("); - for (int i = 0; i < sig.Parameters.Count; i++) - { - if (i > 0) - { - writer.Write(", "); - } - - ClassMembersFactory.WriteParameterNameWithModifier(writer, context, sig.Parameters[i]); - } - writer.WriteLine(");"); + WriteProjectionReturnTypeCallback ret = MethodFactory.WriteProjectionReturnType(context, sig); + WriteParameterListCallback parms = MethodFactory.WriteParameterList(context, sig); + WriteCallArgumentsCallback args = MethodFactory.WriteCallArguments(context, sig, leadingComma: false); + writer.WriteLine($"{ret} {ccwIfaceName}.{mname}({parms}) => (({ccwIfaceName})(WindowsRuntimeObject)this).{mname}({args});"); } foreach (PropertyDefinition prop in type.Properties) { - (MethodDefinition? getter, MethodDefinition? setter) = prop.GetPropertyMethods(); - string pname = prop.Name?.Value ?? string.Empty; + (MethodDefinition? getter, MethodDefinition? setter) = prop.GetMethods(); + string pname = prop.GetRawName(); string propType = InterfaceFactory.WritePropType(context, prop); writer.WriteLine(); @@ -361,17 +291,16 @@ internal static void WriteInterfaceIdicImplMembersForInheritedInterface(Indented foreach (EventDefinition evt in type.Events) { - string evtName = evt.Name?.Value ?? string.Empty; + string evtName = evt.GetRawName(); writer.WriteLine(); - writer.Write("event "); - TypedefNameWriter.WriteEventType(writer, context, evt); - writer.WriteLine($$""" - {{ccwIfaceName}}.{{evtName}} + WriteEventTypeCallback eventType = TypedefNameWriter.WriteEventType(context, evt); + writer.WriteLine(isMultiline: true, $$""" + event {{eventType}} {{ccwIfaceName}}.{{evtName}} { add => (({{ccwIfaceName}})(WindowsRuntimeObject)this).{{evtName}} += value; remove => (({{ccwIfaceName}})(WindowsRuntimeObject)this).{{evtName}} -= value; } - """, isMultiline: true); + """); } } @@ -388,15 +317,17 @@ internal static void EmitDicShimMappedBclForwarders(IndentedTextWriter writer, P switch (mappedWinRTInterfaceName) { case "IClosable": + // IClosable maps to IDisposable. Forward Dispose() to the // WindowsRuntimeObject base which has the actual implementation. writer.WriteLine(); writer.WriteLine("void global::System.IDisposable.Dispose() => ((global::System.IDisposable)(WindowsRuntimeObject)this).Dispose();"); break; case "IBindableVector": + // IList covers IList, ICollection, and IEnumerable members. writer.WriteLine(); - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ int global::System.Collections.ICollection.Count => ((global::System.Collections.IList)(WindowsRuntimeObject)this).Count; bool global::System.Collections.ICollection.IsSynchronized => ((global::System.Collections.IList)(WindowsRuntimeObject)this).IsSynchronized; object global::System.Collections.ICollection.SyncRoot => ((global::System.Collections.IList)(WindowsRuntimeObject)this).SyncRoot; @@ -418,7 +349,7 @@ internal static void EmitDicShimMappedBclForwarders(IndentedTextWriter writer, P void global::System.Collections.IList.RemoveAt(int index) => ((global::System.Collections.IList)(WindowsRuntimeObject)this).RemoveAt(index); IEnumerator IEnumerable.GetEnumerator() => ((global::System.Collections.IList)(WindowsRuntimeObject)this).GetEnumerator(); - """, isMultiline: true); + """); break; case "IBindableIterable": writer.WriteLine(); @@ -430,79 +361,57 @@ internal static void EmitDicShimMappedBclForwarders(IndentedTextWriter writer, P internal static void WriteInterfaceIdicImplMembersForInterface(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { // The CCW interface name (the projected interface name with global:: prefix). - string ccwIfaceName = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, true); - - if (!ccwIfaceName.StartsWith(GlobalPrefix, StringComparison.Ordinal)) - { - ccwIfaceName = GlobalPrefix + ccwIfaceName; - } + string ccwIfaceName = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, true).Format(); // The static ABI Methods class name. - string abiClass = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.StaticAbiClass, true); - - if (!abiClass.StartsWith(GlobalPrefix, StringComparison.Ordinal)) - { - abiClass = GlobalPrefix + abiClass; - } + string abiClass = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.StaticAbiClass, true).Format(); - foreach (MethodDefinition method in type.Methods) + foreach (MethodDefinition method in type.GetNonSpecialMethods()) { - if (method.IsSpecial()) - { - continue; - } - MethodSignatureInfo sig = new(method); - string mname = method.Name?.Value ?? string.Empty; + string mname = method.GetRawName(); writer.WriteLine(); writer.Write("unsafe "); - MethodFactory.WriteProjectionReturnType(writer, context, sig); - writer.Write($" {ccwIfaceName}.{mname}("); - MethodFactory.WriteParameterList(writer, context, sig); - writer.WriteLine($$""" + WriteProjectionReturnTypeCallback ret = MethodFactory.WriteProjectionReturnType(context, sig); + WriteParameterListCallback parms = MethodFactory.WriteParameterList(context, sig); + writer.Write($"{ret} {ccwIfaceName}.{mname}({parms}"); + writer.WriteLine(isMultiline: true, $$""" ) { var _obj = ((WindowsRuntimeObject)this).GetObjectReferenceForInterface(typeof({{ccwIfaceName}}).TypeHandle); - """, isMultiline: true); - if (sig.ReturnType is not null) - { - writer.Write("return "); - } + """); + writer.WriteIf(sig.ReturnType is not null, "return "); - writer.Write($"{abiClass}.{mname}(_obj"); - for (int i = 0; i < sig.Parameters.Count; i++) - { - writer.Write(", "); - ClassMembersFactory.WriteParameterNameWithModifier(writer, context, sig.Parameters[i]); - } - writer.WriteLine(""" + WriteCallArgumentsCallback args = MethodFactory.WriteCallArguments(context, sig, leadingComma: true); + writer.Write($"{abiClass}.{mname}(_obj{args}"); + writer.WriteLine(isMultiline: true, """ ); } - """, isMultiline: true); + """); } foreach (PropertyDefinition prop in type.Properties) { - (MethodDefinition? getter, MethodDefinition? setter) = prop.GetPropertyMethods(); - string pname = prop.Name?.Value ?? string.Empty; + (MethodDefinition? getter, MethodDefinition? setter) = prop.GetMethods(); + string pname = prop.GetRawName(); string propType = InterfaceFactory.WritePropType(context, prop); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" unsafe {{propType}} {{ccwIfaceName}}.{{pname}} { - """, isMultiline: true); + """); if (getter is not null) { - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" get { var _obj = ((WindowsRuntimeObject)this).GetObjectReferenceForInterface(typeof({{ccwIfaceName}}).TypeHandle); return {{abiClass}}.{{pname}}(_obj); } - """, isMultiline: true); + """); } if (setter is not null) @@ -513,23 +422,20 @@ internal static void WriteInterfaceIdicImplMembersForInterface(IndentedTextWrite // to the base interface where the getter actually lives if (getter is null) { - TypeDefinition? baseIfaceWithGetter = InterfaceFactory.FindPropertyInterfaceInBases(context.Cache, type, pname); - - if (baseIfaceWithGetter is not null) + if (InterfaceFactory.TryFindPropertyInBaseInterfaces(context.Cache, type, pname, out TypeDefinition? baseIfaceWithGetter)) { - writer.Write(" get { return (("); - ClassMembersFactory.WriteInterfaceTypeNameForCcw(writer, context, baseIfaceWithGetter); - writer.WriteLine($")(WindowsRuntimeObject)this).{pname}; }}"); + WriteInterfaceTypeNameForCcwCallback iface = ClassMembersFactory.WriteInterfaceTypeNameForCcw(context, baseIfaceWithGetter); + writer.WriteLine($" get {{ return (({iface})(WindowsRuntimeObject)this).{pname}; }}"); } } - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" set { var _obj = ((WindowsRuntimeObject)this).GetObjectReferenceForInterface(typeof({{ccwIfaceName}}).TypeHandle); {{abiClass}}.{{pname}}(_obj, value); } - """, isMultiline: true); + """); } writer.WriteLine("}"); } @@ -538,12 +444,11 @@ internal static void WriteInterfaceIdicImplMembersForInterface(IndentedTextWrite // dispatch through the static ABI Methods class's event accessor (returns an EventSource). foreach (EventDefinition evt in type.Events) { - string evtName = evt.Name?.Value ?? string.Empty; + string evtName = evt.GetRawName(); writer.WriteLine(); - writer.Write("event "); - TypedefNameWriter.WriteEventType(writer, context, evt); - writer.WriteLine($$""" - {{ccwIfaceName}}.{{evtName}} + WriteEventTypeCallback eventType = TypedefNameWriter.WriteEventType(context, evt); + writer.WriteLine(isMultiline: true, $$""" + event {{eventType}} {{ccwIfaceName}}.{{evtName}} { add { @@ -556,7 +461,7 @@ internal static void WriteInterfaceIdicImplMembersForInterface(IndentedTextWrite {{abiClass}}.{{evtName}}((WindowsRuntimeObject)this, _obj).Unsubscribe(value); } } - """, isMultiline: true); + """); } } diff --git a/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.DoAbi.cs b/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.DoAbi.cs index fb5938e4e6..1d738a2846 100644 --- a/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.DoAbi.cs +++ b/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.DoAbi.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; using WindowsRuntime.ProjectionWriter.Errors; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; @@ -25,30 +25,20 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection { TypeSignature? rt = sig.ReturnType; - // String params drive whether we need HString header allocation in the body. - bool hasStringParams = false; - foreach (ParameterInfo p in sig.Parameters) - { - if (p.Type.IsString()) - { - hasStringParams = true; - break; - } - } bool returnIsReceiveArrayDoAbi = rt is SzArrayTypeSignature retSzAbi - && (context.AbiTypeShapeResolver.IsBlittablePrimitive(retSzAbi.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(retSzAbi.BaseType) - || retSzAbi.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(retSzAbi.BaseType) || retSzAbi.BaseType.IsObject() - || context.AbiTypeShapeResolver.IsComplexStruct(retSzAbi.BaseType)); + && (context.AbiTypeKindResolver.IsBlittableAbiElement(retSzAbi.BaseType) + || retSzAbi.BaseType.IsAbiArrayElementRefLike(context.AbiTypeKindResolver) + || context.AbiTypeKindResolver.IsNonBlittableStruct(retSzAbi.BaseType)); bool returnIsHResultExceptionDoAbi = rt is not null && rt.IsHResultException(); bool returnIsString = rt is not null && rt.IsString(); - bool returnIsRefType = rt is not null && (context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(rt) || rt.IsObject() || rt.IsGenericInstance()); + bool returnIsRefType = rt is not null && context.AbiTypeKindResolver.IsReferenceTypeOrGenericInstance(rt); bool returnIsGenericInstance = rt is not null && rt.IsGenericInstance(); - bool returnIsBlittableStruct = rt is not null && context.AbiTypeShapeResolver.IsBlittableStruct(rt); + bool returnIsBlittableStruct = rt is not null && context.AbiTypeKindResolver.IsBlittableStruct(rt); - bool isGetter = methodName.StartsWith("get_", StringComparison.Ordinal); - bool isSetter = methodName.StartsWith("put_", StringComparison.Ordinal); - bool isAddEvent = methodName.StartsWith("add_", StringComparison.Ordinal); - bool isRemoveEvent = methodName.StartsWith("remove_", StringComparison.Ordinal); + bool isGetter = sig.Method.IsGetter; + bool isSetter = sig.Method.IsSetter; + bool isAddEvent = sig.Method.IsAdder; + bool isRemoveEvent = sig.Method.IsRemover; if (isAddEvent || isRemoveEvent) { @@ -63,12 +53,15 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection writer.WriteLine(); using (writer.WriteBlock()) { - string retParamName = AbiTypeHelpers.GetReturnParamName(sig); - string retSizeParamName = AbiTypeHelpers.GetReturnSizeParamName(sig); + MethodSignatureInfo.ReturnNameInfo retNames = sig.GetReturnNameInfo(); + string retParamName = retNames.ValuePointer; + string retSizeParamName = retNames.SizePointer; + // The local name for the unmarshalled return value uses the standard pattern // of prefixing '__' to the param name. For the default '__return_value__' param // this becomes '____return_value__'. - string retLocalName = "__" + retParamName; + string retLocalName = retNames.Local; + // at the TOP of the method body (before local declarations and the try block). The // actual call sites later in the body just reference the already-declared accessor. // For a generic-instance return type, the accessor is named ConvertToUnmanaged_. @@ -76,97 +69,78 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection // instead of the generic-instance UnsafeAccessor (V3-M7). if (returnIsGenericInstance && !(rt is not null && rt.IsNullableT())) { - string interopTypeName = InteropTypeNameWriter.EncodeInteropTypeName(rt!, TypedefNameType.ABI) + ", WinRT.Interop"; - string projectedTypeName = MethodFactory.WriteProjectedSignature(context, rt!, false); - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ConvertToUnmanaged")] - static extern WindowsRuntimeObjectReferenceValue ConvertToUnmanaged_{{retParamName}}([UnsafeAccessorType("{{interopTypeName}}")] object _, {{projectedTypeName}} value); - """, isMultiline: true); + string interopTypeName = InteropTypeNameWriter.GetInteropAssemblyQualifiedName(rt!, TypedefNameType.ABI); + WriteProjectedSignatureCallback projectedTypeName = MethodFactory.WriteProjectedSignature(context, rt!, false); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "ConvertToUnmanaged", + returnType: "WindowsRuntimeObjectReferenceValue", + functionName: $"ConvertToUnmanaged_{retParamName}", + interopType: interopTypeName, + parameterList: $", {projectedTypeName.Format()} value"); writer.WriteLine(); } // Hoist [UnsafeAccessor] declarations for Out generic-instance params: // ConvertToUnmanaged_ wraps the projected value into a WindowsRuntimeObjectReferenceValue. // The body's writeback later references these already-declared accessors. - for (int i = 0; i < sig.Parameters.Count; i++) + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.Out)) { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - - if (cat != ParameterCategory.Out) - { - continue; - } - TypeSignature uOut = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); + TypeSignature uOut = p.Type.StripByRefAndCustomModifiers(); if (!uOut.IsGenericInstance()) { continue; } - string raw = p.Parameter.Name ?? "param"; - string interopTypeName = InteropTypeNameWriter.EncodeInteropTypeName(uOut, TypedefNameType.ABI) + ", WinRT.Interop"; - string projectedTypeName = MethodFactory.WriteProjectedSignature(context, uOut, false); - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ConvertToUnmanaged")] - static extern WindowsRuntimeObjectReferenceValue ConvertToUnmanaged_{{raw}}([UnsafeAccessorType("{{interopTypeName}}")] object _, {{projectedTypeName}} value); - """, isMultiline: true); + string raw = p.GetRawName(); + string interopTypeName = InteropTypeNameWriter.GetInteropAssemblyQualifiedName(uOut, TypedefNameType.ABI); + WriteProjectedSignatureCallback projectedTypeName = MethodFactory.WriteProjectedSignature(context, uOut, false); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "ConvertToUnmanaged", + returnType: "WindowsRuntimeObjectReferenceValue", + functionName: $"ConvertToUnmanaged_{raw}", + interopType: interopTypeName, + parameterList: $", {projectedTypeName.Format()} value"); writer.WriteLine(); } // ConvertToUnmanaged_ and the return-array ConvertToUnmanaged_ to the // top of the method body, before locals and the try block. The actual call sites later // in the body reference these already-declared accessors. - for (int i = 0; i < sig.Parameters.Count; i++) + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.ReceiveArray)) { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - if (cat != ParameterCategory.ReceiveArray) - { - continue; - } - - string raw = p.Parameter.Name ?? "param"; - SzArrayTypeSignature sza = (SzArrayTypeSignature)AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); - string elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(sza.BaseType)); - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(sza.BaseType, TypedefNameType.Projected); + string raw = p.GetRawName(); + SzArrayTypeSignature sza = p.Type.AsSzArray()!; + WriteProjectionTypeCallback elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(sza.BaseType)); - _ = elementInteropArg; string marshallerPath = ArrayElementEncoder.GetArrayMarshallerInteropPath(sza.BaseType); - string elementAbi = sza.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(sza.BaseType) || sza.BaseType.IsObject() - ? "void*" - : context.AbiTypeShapeResolver.IsComplexStruct(sza.BaseType) - ? AbiTypeHelpers.GetAbiStructTypeName(writer, context, sza.BaseType) - : context.AbiTypeShapeResolver.IsBlittableStruct(sza.BaseType) - ? AbiTypeHelpers.GetBlittableStructAbiType(writer, context, sza.BaseType) - : AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, sza.BaseType); - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ConvertToUnmanaged")] - static extern void ConvertToUnmanaged_{{raw}}([UnsafeAccessorType("{{marshallerPath}}")] object _, ReadOnlySpan<{{elementProjected}}> span, out uint length, out {{elementAbi}}* data); - """, isMultiline: true); + string elementAbi = AbiTypeHelpers.GetAbiLocalTypeName(context, sza.BaseType); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "ConvertToUnmanaged", + returnType: "void", + functionName: $"ConvertToUnmanaged_{raw}", + interopType: marshallerPath, + parameterList: $", ReadOnlySpan<{elementProjected.Format()}> span, out uint length, out {elementAbi}* data"); writer.WriteLine(); } if (returnIsReceiveArrayDoAbi && rt is SzArrayTypeSignature retSzHoist) { - string elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(retSzHoist.BaseType)); - string elementAbi = retSzHoist.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(retSzHoist.BaseType) || retSzHoist.BaseType.IsObject() - ? "void*" - : context.AbiTypeShapeResolver.IsComplexStruct(retSzHoist.BaseType) - ? AbiTypeHelpers.GetAbiStructTypeName(writer, context, retSzHoist.BaseType) - : context.AbiTypeShapeResolver.IsBlittableStruct(retSzHoist.BaseType) - ? AbiTypeHelpers.GetBlittableStructAbiType(writer, context, retSzHoist.BaseType) - : AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, retSzHoist.BaseType); - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(retSzHoist.BaseType, TypedefNameType.Projected); - - _ = elementInteropArg; + WriteProjectionTypeCallback elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(retSzHoist.BaseType)); + string elementAbi = AbiTypeHelpers.GetAbiLocalTypeName(context, retSzHoist.BaseType); string marshallerPath = ArrayElementEncoder.GetArrayMarshallerInteropPath(retSzHoist.BaseType); - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ConvertToUnmanaged")] - static extern void ConvertToUnmanaged_{{retParamName}}([UnsafeAccessorType("{{marshallerPath}}")] object _, ReadOnlySpan<{{elementProjected}}> span, out uint length, out {{elementAbi}}* data); - """, isMultiline: true); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "ConvertToUnmanaged", + returnType: "void", + functionName: $"ConvertToUnmanaged_{retParamName}", + interopType: marshallerPath, + parameterList: $", ReadOnlySpan<{elementProjected.Format()}> span, out uint length, out {elementAbi}* data"); writer.WriteLine(); } @@ -177,19 +151,11 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection { writer.WriteLine($"string {retLocalName} = default;"); } - else if (returnIsRefType) - { - string projected = MethodFactory.WriteProjectedSignature(context, rt, false); - writer.WriteLine($"{projected} {retLocalName} = default;"); - } - else if (returnIsReceiveArrayDoAbi) - { - string projected = MethodFactory.WriteProjectedSignature(context, rt, false); - writer.WriteLine($"{projected} {retLocalName} = default;"); - } else { - string projected = MethodFactory.WriteProjectedSignature(context, rt, false); + // returnIsRefType, returnIsReceiveArrayDoAbi, and the default branch all emit + // the same projected type for the default-initialized return local. + WriteProjectedSignatureCallback projected = MethodFactory.WriteProjectedSignature(context, rt, false); writer.WriteLine($"{projected} {retLocalName} = default;"); } } @@ -198,10 +164,10 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection { if (returnIsReceiveArrayDoAbi) { - writer.WriteLine($$""" - *{{retParamName}} = default; + writer.WriteLine(isMultiline: true, $$""" + *{{retParamName}} = default; *{{retSizeParamName}} = default; - """, isMultiline: true); + """); } else { @@ -213,60 +179,41 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection // NOTE: Ref params (WinRT 'in T' / 'ref const T') are READ-ONLY inputs from the caller's // perspective. Do NOT zero * (it's the input value) and do NOT declare a local // (we read directly via *). - for (int i = 0; i < sig.Parameters.Count; i++) + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.Out)) { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - - if (cat != ParameterCategory.Out) - { - continue; - } - string raw = p.Parameter.Name ?? "param"; - string ptr = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; + string raw = p.GetRawName(); + string ptr = IdentifierEscaping.EscapeIdentifier(raw); writer.WriteLine($"*{ptr} = default;"); } - for (int i = 0; i < sig.Parameters.Count; i++) + + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.Out)) { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - if (cat != ParameterCategory.Out) - { - continue; - } + string raw = p.GetRawName(); - string raw = p.Parameter.Name ?? "param"; // Use the projected (non-ABI) type for the local variable. // Strip ByRef and CustomModifier wrappers to get the underlying base type. - TypeSignature underlying = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); - string projected = MethodFactory.WriteProjectedSignature(context, underlying, false); + TypeSignature underlying = p.Type.StripByRefAndCustomModifiers(); + WriteProjectedSignatureCallback projected = MethodFactory.WriteProjectedSignature(context, underlying, false); writer.WriteLine($"{projected} __{raw} = default;"); } // For each ReceiveArray parameter (out T[]), zero the destination + size out pointers // and declare a managed array local. The managed call passes 'out __' and after // the call we copy to the ABI buffer via UnsafeAccessor. - for (int i = 0; i < sig.Parameters.Count; i++) + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.ReceiveArray)) { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - if (cat != ParameterCategory.ReceiveArray) - { - continue; - } - - string raw = p.Parameter.Name ?? "param"; - string ptr = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - SzArrayTypeSignature sza = (SzArrayTypeSignature)AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); - string elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(sza.BaseType)); - writer.WriteLine($$""" + string raw = p.GetRawName(); + string ptr = IdentifierEscaping.EscapeIdentifier(raw); + SzArrayTypeSignature sza = p.Type.AsSzArray()!; + WriteProjectionTypeCallback elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(sza.BaseType)); + writer.WriteLine(isMultiline: true, $$""" *{{ptr}} = default; *__{{raw}}Size = default; {{elementProjected}}[] __{{raw}} = default; - """, isMultiline: true); + """); } // For each blittable array (PassArray / FillArray) parameter, declare a Span local that @@ -276,9 +223,9 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is not (ParameterCategory.PassArray or ParameterCategory.FillArray)) + if (!cat.IsArrayInput()) { continue; } @@ -288,10 +235,10 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection continue; } - string raw = p.Parameter.Name ?? "param"; - string ptr = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - string elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(sz.BaseType)); - bool isBlittableElem = context.AbiTypeShapeResolver.IsBlittablePrimitive(sz.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(sz.BaseType); + string raw = p.GetRawName(); + string ptr = IdentifierEscaping.EscapeIdentifier(raw); + WriteProjectionTypeCallback elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(sz.BaseType)); + bool isBlittableElem = context.AbiTypeKindResolver.IsBlittableAbiElement(sz.BaseType); if (isBlittableElem) { @@ -301,60 +248,52 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection { // Non-blittable element: InlineArray16 + ArrayPool with size from ABI. writer.WriteLine(); - writer.WriteLine($$""" - Unsafe.SkipInit(out InlineArray16<{{elementProjected}}> __{{raw}}_inlineArray); - {{elementProjected}}[] __{{raw}}_arrayFromPool = null; - Span<{{elementProjected}}> __{{raw}} = __{{raw}}Size <= 16 - ? __{{raw}}_inlineArray[..(int)__{{raw}}Size] - : (__{{raw}}_arrayFromPool = global::System.Buffers.ArrayPool<{{elementProjected}}>.Shared.Rent((int)__{{raw}}Size)); - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + Unsafe.SkipInit(out InlineArray16<{{elementProjected}}> __{{raw}}_inlineArray); + {{elementProjected}}[] __{{raw}}_arrayFromPool = null; + Span<{{elementProjected}}> __{{raw}} = __{{raw}}Size <= 16 + ? __{{raw}}_inlineArray[..(int)__{{raw}}Size] + : (__{{raw}}_arrayFromPool = global::System.Buffers.ArrayPool<{{elementProjected}}>.Shared.Rent((int)__{{raw}}Size)); + """); } } - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ try { - """, isMultiline: true); + """); + writer.IncreaseIndent(); // For non-blittable PassArray params (read-only input arrays), emit CopyToManaged_ // via UnsafeAccessor to convert the native ABI buffer into the managed Span the // delegate sees. For FillArray params, the buffer is fresh storage the user delegate // fills — the post-call writeback loop handles that. - for (int i = 0; i < sig.Parameters.Count; i++) + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.PassArray)) { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - - if (cat != ParameterCategory.PassArray) - { - continue; - } if (p.Type is not SzArrayTypeSignature szArr) { continue; } - if (context.AbiTypeShapeResolver.IsBlittablePrimitive(szArr.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsBlittableAbiElement(szArr.BaseType)) { continue; } - string raw = p.Parameter.Name ?? "param"; - string ptr = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - string elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(szArr.BaseType)); - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(szArr.BaseType, TypedefNameType.Projected); + string raw = p.GetRawName(); + string ptr = IdentifierEscaping.EscapeIdentifier(raw); + WriteProjectionTypeCallback elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(szArr.BaseType)); - _ = elementInteropArg; - // For complex structs, the data param is the ABI struct pointer (e.g. BasicStruct*). + // For non-blittable structs, the data param is the ABI struct pointer (e.g. BasicStruct*). // The Do_Abi parameter we receive is void* (per V3R3-M8), so the call-site needs an // explicit (T*) cast to bridge the type. For ref-types (string/runtime-class/object), // the data param is void** and the cast is (void**). string dataParamType; string dataCastExpr; - if (context.AbiTypeShapeResolver.IsComplexStruct(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsNonBlittableStruct(szArr.BaseType)) { - string abiStructName = AbiTypeHelpers.GetAbiStructTypeName(writer, context, szArr.BaseType); + string abiStructName = AbiTypeHelpers.GetAbiStructTypeName(context, szArr.BaseType); dataParamType = abiStructName + "* data"; dataCastExpr = "(" + abiStructName + "*)" + ptr; } @@ -364,11 +303,13 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection dataCastExpr = "(void**)" + ptr; } - writer.WriteLine($$""" + writer.IncreaseIndent(); + writer.WriteLine(isMultiline: true, $$""" [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "CopyToManaged")] static extern void CopyToManaged_{{raw}}([UnsafeAccessorType("{{ArrayElementEncoder.GetArrayMarshallerInteropPath(szArr.BaseType)}}")] object _, uint length, {{dataParamType}}, Span<{{elementProjected}}> span); CopyToManaged_{{raw}}(null, __{{raw}}Size, {{dataCastExpr}}, __{{raw}}); - """, isMultiline: true); + """); + writer.DecreaseIndent(); } // For generic instance ABI input parameters, emit local UnsafeAccessor delegates and locals @@ -379,47 +320,44 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection if (p.Type.IsNullableT()) { - // Nullable param (server-side): use Marshaller.UnboxToManaged. - string rawName = p.Parameter.Name ?? "param"; - string callName = CSharpKeywords.IsKeyword(rawName) ? "@" + rawName : rawName; - TypeSignature inner = p.Type.GetNullableInnerType()!; - string innerMarshaller = AbiTypeHelpers.GetNullableInnerMarshallerName(writer, context, inner); - writer.WriteLine($" var __arg_{rawName} = {innerMarshaller}.UnboxToManaged({callName});"); + // Nullable param: use Marshaller.UnboxToManaged. + string rawName = p.GetRawName(); + string callName = IdentifierEscaping.EscapeIdentifier(rawName); + (_, string innerMarshaller) = AbiTypeHelpers.GetNullableInnerInfo(writer, context, p.Type); + writer.WriteLine($"var __arg_{rawName} = {innerMarshaller}.UnboxToManaged({callName});"); } else if (p.Type.IsGenericInstance()) { - string rawName = p.Parameter.Name ?? "param"; - string callName = CSharpKeywords.IsKeyword(rawName) ? "@" + rawName : rawName; - string interopTypeName = InteropTypeNameWriter.EncodeInteropTypeName(p.Type, TypedefNameType.ABI) + ", WinRT.Interop"; - string projectedTypeName = MethodFactory.WriteProjectedSignature(context, p.Type, false); - writer.WriteLine($$""" + string rawName = p.GetRawName(); + string callName = IdentifierEscaping.EscapeIdentifier(rawName); + string interopTypeName = InteropTypeNameWriter.GetInteropAssemblyQualifiedName(p.Type, TypedefNameType.ABI); + WriteProjectedSignatureCallback projectedTypeName = MethodFactory.WriteProjectedSignature(context, p.Type, false); + writer.IncreaseIndent(); + writer.WriteLine(isMultiline: true, $$""" [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ConvertToManaged")] static extern {{projectedTypeName}} ConvertToManaged_arg_{{rawName}}([UnsafeAccessorType("{{interopTypeName}}")] object _, void* value); var __arg_{{rawName}} = ConvertToManaged_arg_{{rawName}}(null, {{callName}}); - """, isMultiline: true); + """); + writer.DecreaseIndent(); } } if (returnIsString) { - writer.Write($" {retLocalName} = "); + writer.Write($"{retLocalName} = "); } else if (returnIsRefType) { - writer.Write($" {retLocalName} = "); + writer.Write($"{retLocalName} = "); } else if (returnIsReceiveArrayDoAbi) { // For T[] return: assign to existing local. - writer.Write($" {retLocalName} = "); + writer.Write($"{retLocalName} = "); } else if (rt is not null) { - writer.Write($" {retLocalName} = "); - } - else - { - writer.Write(" "); + writer.Write($"{retLocalName} = "); } if (isGetter) @@ -441,17 +379,17 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection { if (i > 0) { - writer.Write(""" + writer.Write(isMultiline: true, """ , - """, isMultiline: true); + """); } ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); if (cat == ParameterCategory.Out) { - string raw = p.Parameter.Name ?? "param"; + string raw = p.GetRawName(); writer.Write($"out __{raw}"); } else if (cat == ParameterCategory.Ref) @@ -460,9 +398,9 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection // (pointer to a value the native caller owns). On the C# delegate / interface // side it's projected as 'in T'. Read directly from * via the appropriate // marshaller — DO NOT zero or write back. - string raw = p.Parameter.Name ?? "param"; - string ptr = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - TypeSignature uRef = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); + string raw = p.GetRawName(); + string ptr = IdentifierEscaping.EscapeIdentifier(raw); + TypeSignature uRef = p.Type.StripByRefAndCustomModifiers(); if (uRef.IsString()) { @@ -472,11 +410,11 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection { writer.Write($"WindowsRuntimeObjectMarshaller.ConvertToManaged(*{ptr})"); } - else if (context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(uRef)) + else if (context.AbiTypeKindResolver.IsRuntimeClassOrInterface(uRef)) { writer.Write($"{AbiTypeHelpers.GetMarshallerFullName(writer, context, uRef)}.ConvertToManaged(*{ptr})"); } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(uRef)) + else if (context.AbiTypeKindResolver.IsMappedAbiValueType(uRef)) { writer.Write($"{AbiTypeHelpers.GetMappedMarshallerName(uRef)}.ConvertToManaged(*{ptr})"); } @@ -484,13 +422,13 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection { writer.Write($"global::ABI.System.ExceptionMarshaller.ConvertToManaged(*{ptr})"); } - else if (context.AbiTypeShapeResolver.IsComplexStruct(uRef)) + else if (context.AbiTypeKindResolver.IsNonBlittableStruct(uRef)) { writer.Write($"{AbiTypeHelpers.GetMarshallerFullName(writer, context, uRef)}.ConvertToManaged(*{ptr})"); } - else if (context.AbiTypeShapeResolver.IsBlittableStruct(uRef) || context.AbiTypeShapeResolver.IsBlittablePrimitive(uRef) || context.AbiTypeShapeResolver.IsEnumType(uRef)) + else if (context.AbiTypeKindResolver.IsBlittableStruct(uRef) || context.AbiTypeKindResolver.IsBlittablePrimitive(uRef) || context.AbiTypeKindResolver.IsEnumType(uRef)) { - // Blittable/almost-blittable: ABI layout matches projected layout. + // Blittable: ABI layout matches projected layout. writer.Write($"*{ptr}"); } else @@ -498,14 +436,14 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection writer.Write($"*{ptr}"); } } - else if (cat is ParameterCategory.PassArray or ParameterCategory.FillArray) + else if (cat.IsArrayInput()) { - string raw = p.Parameter.Name ?? "param"; + string raw = p.GetRawName(); writer.Write($"__{raw}"); } else if (cat == ParameterCategory.ReceiveArray) { - string raw = p.Parameter.Name ?? "param"; + string raw = p.GetRawName(); writer.Write($"out __{raw}"); } else @@ -518,90 +456,66 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection // After call: write back out params to caller's pointer. // NOTE: Ref params (WinRT 'in T') are read-only inputs — never written back. - for (int i = 0; i < sig.Parameters.Count; i++) + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.Out)) { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - if (cat != ParameterCategory.Out) - { - continue; - } + string raw = p.GetRawName(); + string ptr = IdentifierEscaping.EscapeIdentifier(raw); + TypeSignature underlying = p.Type.StripByRefAndCustomModifiers(); + string rhs; - string raw = p.Parameter.Name ?? "param"; - string ptr = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - TypeSignature underlying = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); - writer.Write($" *{ptr} = "); // String: HStringMarshaller.ConvertToUnmanaged if (underlying.IsString()) { - writer.Write($"HStringMarshaller.ConvertToUnmanaged(__{raw})"); + rhs = $"HStringMarshaller.ConvertToUnmanaged(__{raw})"; } // Object/runtime class: .ConvertToUnmanaged(...).DetachThisPtrUnsafe() else if (underlying.IsObject()) { - writer.Write($"WindowsRuntimeObjectMarshaller.ConvertToUnmanaged(__{raw}).DetachThisPtrUnsafe()"); + rhs = $"WindowsRuntimeObjectMarshaller.ConvertToUnmanaged(__{raw}).DetachThisPtrUnsafe()"; } - else if (context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(underlying)) + else if (context.AbiTypeKindResolver.IsRuntimeClassOrInterface(underlying)) { - writer.Write($"{AbiTypeHelpers.GetMarshallerFullName(writer, context, underlying)}.ConvertToUnmanaged(__{raw}).DetachThisPtrUnsafe()"); + rhs = $"{AbiTypeHelpers.GetMarshallerFullName(writer, context, underlying)}.ConvertToUnmanaged(__{raw}).DetachThisPtrUnsafe()"; } // Generic instance (e.g. IEnumerable): use the hoisted UnsafeAccessor // 'ConvertToUnmanaged_' declared at the top of the method body. else if (underlying.IsGenericInstance()) { - writer.Write($"ConvertToUnmanaged_{raw}(null, __{raw}).DetachThisPtrUnsafe()"); + rhs = $"ConvertToUnmanaged_{raw}(null, __{raw}).DetachThisPtrUnsafe()"; } // For enums, function pointer signature uses the projected enum type, no cast needed. // For bool, cast to byte. For char, cast to ushort. - else if (context.AbiTypeShapeResolver.IsEnumType(underlying)) - { - writer.Write($"__{raw}"); - } - else if (underlying is CorLibTypeSignature corlibBool && - corlibBool.ElementType == ElementType.Boolean) - { - writer.Write($"__{raw}"); - } - else if (underlying is CorLibTypeSignature corlibChar && - corlibChar.ElementType == ElementType.Char) - { - writer.Write($"__{raw}"); - } + // For the local managed value, marshal through Marshaller.ConvertToUnmanaged + // before writing it into the *out ABI struct slot. // Non-blittable struct (e.g. authored BasicStruct with string fields): marshal // the local managed value through Marshaller.ConvertToUnmanaged before // writing it into the *out ABI struct slot.write_marshal_from_managed //: "Marshaller.ConvertToUnmanaged(local)". - else if (context.AbiTypeShapeResolver.IsComplexStruct(underlying)) + else if (context.AbiTypeKindResolver.IsNonBlittableStruct(underlying)) { - writer.Write($"{AbiTypeHelpers.GetMarshallerFullName(writer, context, underlying)}.ConvertToUnmanaged(__{raw})"); + rhs = $"{AbiTypeHelpers.GetMarshallerFullName(writer, context, underlying)}.ConvertToUnmanaged(__{raw})"; } else { - writer.Write($"__{raw}"); + // Enums, bool, char, and primitives: the local __ is already the ABI form. + rhs = $"__{raw}"; } - writer.WriteLine(";"); + writer.WriteLine($"*{ptr} = {rhs};"); } // After call: for ReceiveArray params, emit ConvertToUnmanaged_ call (the // [UnsafeAccessor] declaration was hoisted to the top of the method body). - for (int i = 0; i < sig.Parameters.Count; i++) + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.ReceiveArray)) { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - - if (cat != ParameterCategory.ReceiveArray) - { - continue; - } - string raw = p.Parameter.Name ?? "param"; - string ptr = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - writer.WriteLine($" ConvertToUnmanaged_{raw}(null, __{raw}, out *__{raw}Size, out *{ptr});"); + string raw = p.GetRawName(); + string ptr = IdentifierEscaping.EscapeIdentifier(raw); + writer.WriteLine($"ConvertToUnmanaged_{raw}(null, __{raw}, out *__{raw}Size, out *{ptr});"); } // After call: for non-blittable FillArray params (Span where T is string/runtime @@ -609,15 +523,8 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection // native ABI buffer.. // which emits 'CopyToUnmanaged_(null, __, __Size, (T*))'. // Blittable element types don't need this — the Span wraps the native buffer directly. - for (int i = 0; i < sig.Parameters.Count; i++) + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.FillArray)) { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - - if (cat != ParameterCategory.FillArray) - { - continue; - } if (p.Type is not SzArrayTypeSignature szFA) { @@ -625,155 +532,108 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection } // Blittable element types: Span wraps the native buffer; no copy-back needed. - if (context.AbiTypeShapeResolver.IsBlittablePrimitive(szFA.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(szFA.BaseType)) + if (context.AbiTypeKindResolver.IsBlittableAbiElement(szFA.BaseType)) { continue; } - string raw = p.Parameter.Name ?? "param"; - string ptr = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - string elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(szFA.BaseType)); - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(szFA.BaseType, TypedefNameType.Projected); + string raw = p.GetRawName(); + string ptr = IdentifierEscaping.EscapeIdentifier(raw); + WriteProjectionTypeCallback elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(szFA.BaseType)); - _ = elementInteropArg; - // Determine the ABI element type for the data pointer cast. - // - Strings / runtime classes / objects: void** - // - HResult exception: global::ABI.System.Exception* - // - Mapped value types (DateTime/TimeSpan): global::ABI.System.{DateTimeOffset/TimeSpan}* - // - Complex structs: * - string dataParamType; - string dataCastType; - - if (szFA.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(szFA.BaseType) || szFA.BaseType.IsObject()) - { - dataParamType = "void** data"; - dataCastType = "(void**)"; - } - else if (szFA.BaseType.IsHResultException()) - { - dataParamType = "global::ABI.System.Exception* data"; - dataCastType = "(global::ABI.System.Exception*)"; - } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(szFA.BaseType)) - { - string abiName = AbiTypeHelpers.GetMappedAbiTypeName(szFA.BaseType); - dataParamType = abiName + "* data"; - dataCastType = "(" + abiName + "*)"; - } - else - { - string abiStructName = AbiTypeHelpers.GetAbiStructTypeName(writer, context, szFA.BaseType); - dataParamType = abiStructName + "* data"; - dataCastType = "(" + abiStructName + "*)"; - } + // Determine the ABI element type for the data pointer cast (e.g. "void*" for + // ref-like elements -> "void** data"/"(void**)", or "global::ABI.Foo.Bar" for + // on-blittable structs -> "global::ABI.Foo.Bar* data"/"(global::ABI.Foo.Bar*)"). + string elementAbi = AbiTypeHelpers.GetArrayElementAbiType(context, szFA.BaseType); - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "CopyToUnmanaged")] - static extern void CopyToUnmanaged_{{raw}}([UnsafeAccessorType("{{ArrayElementEncoder.GetArrayMarshallerInteropPath(szFA.BaseType)}}")] object _, ReadOnlySpan<{{elementProjected}}> span, uint length, {{dataParamType}}); - CopyToUnmanaged_{{raw}}(null, __{{raw}}, __{{raw}}Size, {{dataCastType}}{{ptr}}); - """, isMultiline: true); + writer.IncreaseIndent(); + writer.WriteLine(isMultiline: true, $$""" + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "CopyToUnmanaged")] + static extern void CopyToUnmanaged_{{raw}}([UnsafeAccessorType("{{ArrayElementEncoder.GetArrayMarshallerInteropPath(szFA.BaseType)}}")] object _, ReadOnlySpan<{{elementProjected}}> span, uint length, {{elementAbi}}* data); + CopyToUnmanaged_{{raw}}(null, __{{raw}}, __{{raw}}Size, ({{elementAbi}}*){{ptr}}); + """); + writer.DecreaseIndent(); } if (rt is not null) { if (returnIsHResultExceptionDoAbi) { - writer.WriteLine($" *{retParamName} = global::ABI.System.ExceptionMarshaller.ConvertToUnmanaged({retLocalName});"); + writer.WriteLine($"*{retParamName} = global::ABI.System.ExceptionMarshaller.ConvertToUnmanaged({retLocalName});"); } else if (returnIsString) { - writer.WriteLine($" *{retParamName} = HStringMarshaller.ConvertToUnmanaged({retLocalName});"); + writer.WriteLine($"*{retParamName} = HStringMarshaller.ConvertToUnmanaged({retLocalName});"); } else if (returnIsRefType) { if (rt is not null && rt.IsNullableT()) { - // Nullable return (server-side): use Marshaller.BoxToUnmanaged. - TypeSignature inner = rt.GetNullableInnerType()!; - string innerMarshaller = AbiTypeHelpers.GetNullableInnerMarshallerName(writer, context, inner); - writer.WriteLine($" *{retParamName} = {innerMarshaller}.BoxToUnmanaged({retLocalName}).DetachThisPtrUnsafe();"); + // Nullable return: use Marshaller.BoxToUnmanaged. + (_, string innerMarshaller) = AbiTypeHelpers.GetNullableInnerInfo(writer, context, rt); + writer.WriteLine($"*{retParamName} = {innerMarshaller}.BoxToUnmanaged({retLocalName}).DetachThisPtrUnsafe();"); } else if (returnIsGenericInstance) { // Generic instance return: use the UnsafeAccessor static local function declared at // the top of the method body via the M12 hoisting pass; just emit the call here. - writer.WriteLine($" *{retParamName} = ConvertToUnmanaged_{retParamName}(null, {retLocalName}).DetachThisPtrUnsafe();"); + writer.WriteLine($"*{retParamName} = ConvertToUnmanaged_{retParamName}(null, {retLocalName}).DetachThisPtrUnsafe();"); } else { - writer.Write($" *{retParamName} = "); - EmitMarshallerConvertToUnmanaged(writer, context, rt!, retLocalName); - writer.WriteLine(".DetachThisPtrUnsafe();"); + EmitMarshallerConvertToUnmanagedCallback cvt = EmitMarshallerConvertToUnmanaged(context, rt!, retLocalName); + writer.WriteLine($"*{retParamName} = {cvt}.DetachThisPtrUnsafe();"); } } else if (returnIsReceiveArrayDoAbi) { // Return-receive-array: emit ConvertToUnmanaged_ call (declaration // was hoisted to the top of the method body). - writer.WriteLine($" ConvertToUnmanaged_{retParamName}(null, {retLocalName}, out *{retSizeParamName}, out *{retParamName});"); + writer.WriteLine($"ConvertToUnmanaged_{retParamName}(null, {retLocalName}, out *{retSizeParamName}, out *{retParamName});"); } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(rt)) + else if (context.AbiTypeKindResolver.IsMappedAbiValueType(rt)) { // Mapped value type return (DateTime/TimeSpan): convert via marshaller. - writer.WriteLine($" *{retParamName} = {AbiTypeHelpers.GetMappedMarshallerName(rt)}.ConvertToUnmanaged({retLocalName});"); + writer.WriteLine($"*{retParamName} = {AbiTypeHelpers.GetMappedMarshallerName(rt)}.ConvertToUnmanaged({retLocalName});"); } else if (rt.IsSystemType()) { - // System.Type return (server-side): convert managed System.Type to ABI Type struct. - writer.WriteLine($" *{retParamName} = global::ABI.System.TypeMarshaller.ConvertToUnmanaged({retLocalName});"); + // System.Type return: convert managed System.Type to ABI Type struct. + writer.WriteLine($"*{retParamName} = global::ABI.System.TypeMarshaller.ConvertToUnmanaged({retLocalName});"); } - else if (context.AbiTypeShapeResolver.IsComplexStruct(rt)) + else if (context.AbiTypeKindResolver.IsNonBlittableStruct(rt)) { - // Complex struct return (server-side): convert managed struct to ABI struct via marshaller. - writer.WriteLine($" *{retParamName} = {AbiTypeHelpers.GetMarshallerFullName(writer, context, rt)}.ConvertToUnmanaged({retLocalName});"); + // Non-blittable struct return: convert managed struct to ABI struct via marshaller. + writer.WriteLine($"*{retParamName} = {AbiTypeHelpers.GetMarshallerFullName(writer, context, rt)}.ConvertToUnmanaged({retLocalName});"); } else if (returnIsBlittableStruct) { - writer.WriteLine($" *{retParamName} = {retLocalName};"); + writer.WriteLine($"*{retParamName} = {retLocalName};"); } else { - writer.Write($" *{retParamName} = "); - - if (rt is CorLibTypeSignature corlib && - corlib.ElementType == ElementType.Boolean) - { - writer.WriteLine($"{retLocalName};"); - } - else if (rt is CorLibTypeSignature corlib2 && - corlib2.ElementType == ElementType.Char) - { - writer.WriteLine($"{retLocalName};"); - } - else if (context.AbiTypeShapeResolver.IsEnumType(rt)) - { - // Enum: function pointer signature uses the projected enum type, no cast needed. - writer.WriteLine($"{retLocalName};"); - } - else - { - writer.WriteLine($"{retLocalName};"); - } + writer.WriteLine($"*{retParamName} = {retLocalName};"); } } - writer.WriteLine(""" + writer.DecreaseIndent(); + writer.WriteLine(isMultiline: true, """ return 0; } catch (Exception __exception__) { return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(__exception__); } - """, isMultiline: true); + """); // For non-blittable PassArray params, emit finally block with ArrayPool.Shared.Return. bool hasNonBlittableArrayDoAbi = false; for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is not (ParameterCategory.PassArray or ParameterCategory.FillArray)) + if (!cat.IsArrayInput()) { continue; } @@ -783,7 +643,7 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection continue; } - if (context.AbiTypeShapeResolver.IsBlittablePrimitive(szArr.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsBlittableAbiElement(szArr.BaseType)) { continue; } @@ -794,16 +654,17 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection if (hasNonBlittableArrayDoAbi) { - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ finally { - """, isMultiline: true); + """); + writer.IncreaseIndent(); for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is not (ParameterCategory.PassArray or ParameterCategory.FillArray)) + if (!cat.IsArrayInput()) { continue; } @@ -813,26 +674,26 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection continue; } - if (context.AbiTypeShapeResolver.IsBlittablePrimitive(szArr.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsBlittableAbiElement(szArr.BaseType)) { continue; } - string raw = p.Parameter.Name ?? "param"; - string elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(szArr.BaseType)); + string raw = p.GetRawName(); + WriteProjectionTypeCallback elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(szArr.BaseType)); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" if (__{{raw}}_arrayFromPool is not null) - { - global::System.Buffers.ArrayPool<{{elementProjected}}>.Shared.Return(__{{raw}}_arrayFromPool); - } - """, isMultiline: true); + { + global::System.Buffers.ArrayPool<{{elementProjected}}>.Shared.Return(__{{raw}}_arrayFromPool); + } + """); } + writer.DecreaseIndent(); writer.WriteLine("}"); } } writer.WriteLine(); - _ = hasStringParams; } /// @@ -840,8 +701,8 @@ internal static void EmitDoAbiBodyIfSimple(IndentedTextWriter writer, Projection /// internal static void EmitDoAbiParamArgConversion(IndentedTextWriter writer, ProjectionEmitContext context, ParameterInfo p) { - string rawName = p.Parameter.Name ?? "param"; - string pname = CSharpKeywords.IsKeyword(rawName) ? "@" + rawName : rawName; + string rawName = p.GetRawName(); + string pname = IdentifierEscaping.EscapeIdentifier(rawName); if (p.Type is CorLibTypeSignature corlib && corlib.ElementType == ElementType.Boolean) @@ -864,11 +725,11 @@ internal static void EmitDoAbiParamArgConversion(IndentedTextWriter writer, Proj // local var __arg_ that holds the converted value. writer.Write($"__arg_{rawName}"); } - else if (context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(p.Type) || p.Type.IsObject()) + else if (context.AbiTypeKindResolver.IsRuntimeClassOrInterface(p.Type) || p.Type.IsObject()) { EmitMarshallerConvertToManaged(writer, context, p.Type, pname); } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(p.Type)) + else if (context.AbiTypeKindResolver.IsMappedAbiValueType(p.Type)) { // Mapped value type input (DateTime/TimeSpan): the parameter is the ABI type; // convert to the projected managed type via the marshaller. @@ -876,20 +737,20 @@ internal static void EmitDoAbiParamArgConversion(IndentedTextWriter writer, Proj } else if (p.Type.IsSystemType()) { - // System.Type input (server-side): convert ABI Type struct to System.Type. + // System.Type input: convert ABI Type struct to System.Type. writer.Write($"global::ABI.System.TypeMarshaller.ConvertToManaged({pname})"); } - else if (context.AbiTypeShapeResolver.IsComplexStruct(p.Type)) + else if (context.AbiTypeKindResolver.IsNonBlittableStruct(p.Type)) { - // Complex struct input (server-side): convert ABI struct to managed via marshaller. + // Non-blittable struct input: convert ABI struct to managed via marshaller. writer.Write($"{AbiTypeHelpers.GetMarshallerFullName(writer, context, p.Type)}.ConvertToManaged({pname})"); } - else if (context.AbiTypeShapeResolver.IsBlittableStruct(p.Type)) + else if (context.AbiTypeKindResolver.IsBlittableStruct(p.Type)) { - // Blittable / almost-blittable struct: pass directly (projected type == ABI type). + // Blittable struct: pass directly (projected type == ABI type). writer.Write(pname); } - else if (context.AbiTypeShapeResolver.IsEnumType(p.Type)) + else if (context.AbiTypeKindResolver.IsEnumType(p.Type)) { // Enum: param signature is already the projected enum type, no cast needed. writer.Write(pname); @@ -899,4 +760,4 @@ internal static void EmitDoAbiParamArgConversion(IndentedTextWriter writer, Proj writer.Write(pname); } } -} +} \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.MarshallerDispatch.cs b/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.MarshallerDispatch.cs index dbf54502a7..55eef12431 100644 --- a/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.MarshallerDispatch.cs +++ b/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.MarshallerDispatch.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Writers; @@ -25,6 +26,13 @@ internal static void EmitMarshallerConvertToUnmanaged(IndentedTextWriter writer, writer.Write($"{AbiTypeHelpers.GetMarshallerFullName(writer, context, sig)}.ConvertToUnmanaged({argName})"); } + /// + /// A callback emitting the marshaller's ConvertToUnmanaged call. + internal static EmitMarshallerConvertToUnmanagedCallback EmitMarshallerConvertToUnmanaged(ProjectionEmitContext context, TypeSignature sig, string argName) + { + return new(context, sig, argName); + } + /// /// Emits the call to the appropriate marshaller's ConvertToManaged for a runtime class / object return value. /// @@ -38,4 +46,11 @@ internal static void EmitMarshallerConvertToManaged(IndentedTextWriter writer, P writer.Write($"{AbiTypeHelpers.GetMarshallerFullName(writer, context, sig)}.ConvertToManaged({argName})"); } + + /// + /// A callback emitting the marshaller's ConvertToManaged call. + internal static EmitMarshallerConvertToManagedCallback EmitMarshallerConvertToManaged(ProjectionEmitContext context, TypeSignature sig, string argName) + { + return new(context, sig, argName); + } } diff --git a/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.MethodsClass.cs b/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.MethodsClass.cs index 8e43b0d668..0fc7617308 100644 --- a/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.MethodsClass.cs +++ b/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.MethodsClass.cs @@ -6,6 +6,7 @@ using System.Globalization; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; @@ -24,9 +25,9 @@ internal static partial class AbiMethodBodyFactory /// internal static void EmitUnsafeAccessorForDefaultIfaceIfGeneric(IndentedTextWriter writer, ProjectionEmitContext context, ITypeDefOrRef? defaultIface) { - if (defaultIface is TypeSpecification ts && ts.Signature is GenericInstanceTypeSignature gi) + if (defaultIface is not null && defaultIface.TryGetGenericInstance(out GenericInstanceTypeSignature? gi)) { - ObjRefNameGenerator.EmitUnsafeAccessorForIid(writer, context, gi); + UnsafeAccessorFactory.EmitIidAccessor(writer, context, gi); } } @@ -48,64 +49,48 @@ internal static void EmitMethodsClassMembersFor(IndentedTextWriter writer, Proje } // Emit non-special methods first (output order is unchanged from before; only the slot lookup changes). - foreach (MethodDefinition method in type.Methods) + foreach (MethodDefinition method in type.GetNonSpecialMethods()) { - if (method.IsSpecial()) - { - continue; - } - - string mname = method.Name?.Value ?? string.Empty; + string mname = method.GetRawName(); MethodSignatureInfo sig = new(method); - writer.Write(""" + writer.Write(isMultiline: true, """ [MethodImpl(MethodImplOptions.NoInlining)] public static unsafe - """, isMultiline: true); - MethodFactory.WriteProjectionReturnType(writer, context, sig); - writer.Write($" {mname}(WindowsRuntimeObjectReference thisReference"); - - if (sig.Parameters.Count > 0) - { - writer.Write(", "); - } - - MethodFactory.WriteParameterList(writer, context, sig); - writer.Write(")"); + """); + WriteProjectionReturnTypeCallback ret = MethodFactory.WriteProjectionReturnType(context, sig); + WriteParameterListCallback parms = MethodFactory.WriteParameterList(context, sig); + string comma = sig.Parameters.Count > 0 ? ", " : ""; + writer.Write($"{ret} {mname}(WindowsRuntimeObjectReference thisReference{comma}{parms})"); // Emit the body if we can handle this case. Slot comes from the method's WinMD index. - EmitAbiMethodBodyIfSimple(writer, context, sig, methodSlot[method], isNoExcept: method.IsNoExcept()); + EmitAbiMethodBodyIfSimple(writer, context, sig, methodSlot[method], isNoExcept: method.IsNoExcept); } // Emit property accessors. Each getter / setter consumes one vtable slot — looked up from the underlying method. foreach (PropertyDefinition prop in type.Properties) { - string pname = prop.Name?.Value ?? string.Empty; - (MethodDefinition? getter, MethodDefinition? setter) = prop.GetPropertyMethods(); + string pname = prop.GetRawName(); string propType = InterfaceFactory.WritePropType(context, prop); - (MethodDefinition? gMethod, MethodDefinition? sMethod) = (getter, setter); - // accessors of the property (the attribute is on the property itself, not on the - // individual accessors). - bool propIsNoExcept = prop.IsNoExcept(); - if (gMethod is not null) + if (prop.GetMethod is { } getter) { - MethodSignatureInfo getSig = new(gMethod); - writer.Write($$""" + writer.Write(isMultiline: true, $$""" [MethodImpl(MethodImplOptions.NoInlining)] public static unsafe {{propType}} {{pname}}(WindowsRuntimeObjectReference thisReference) - """, isMultiline: true); - EmitAbiMethodBodyIfSimple(writer, context, getSig, methodSlot[gMethod], isNoExcept: propIsNoExcept); + """); + + EmitAbiMethodBodyIfSimple(writer, context, new MethodSignatureInfo(getter), methodSlot[getter], isNoExcept: prop.IsNoExcept); } - if (sMethod is not null) + if (prop.SetMethod is { } setter) { - MethodSignatureInfo setSig = new(sMethod); - writer.Write($$""" + writer.Write(isMultiline: true, $$""" [MethodImpl(MethodImplOptions.NoInlining)] public static unsafe void {{pname}}(WindowsRuntimeObjectReference thisReference, {{InterfaceFactory.WritePropType(context, prop, isSetProperty: true)}} value) - """, isMultiline: true); - EmitAbiMethodBodyIfSimple(writer, context, setSig, methodSlot[sMethod], paramNameOverride: "value", isNoExcept: propIsNoExcept); + """); + + EmitAbiMethodBodyIfSimple(writer, context, new MethodSignatureInfo(setter), methodSlot[setter], paramNameOverride: "value", isNoExcept: prop.IsNoExcept); } } @@ -119,12 +104,12 @@ public static unsafe continue; } - string evtName = evt.Name?.Value ?? string.Empty; + string evtName = evt.GetRawName(); TypeSignature evtSig = evt.EventType!.ToTypeSignature(false); bool isGenericEvent = evtSig is GenericInstanceTypeSignature; // Use the add method's WinMD slot (events project as the add_X method's vmethod_index). - (MethodDefinition? addMethod, MethodDefinition? _) = evt.GetEventMethods(); + MethodDefinition? addMethod = evt.AddMethod; int eventSlot = addMethod is not null && methodSlot.TryGetValue(addMethod, out int es) ? es : 0; // Build the projected event source type name. For non-generic delegate handlers, the @@ -135,7 +120,7 @@ public static unsafe if (isGenericEvent) { - eventSourceProjectedFull = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(evtSig), TypedefNameType.EventSource, true); + eventSourceProjectedFull = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(evtSig), TypedefNameType.EventSource, true).Format(); if (!eventSourceProjectedFull.StartsWith(GlobalPrefix, StringComparison.Ordinal)) { @@ -150,7 +135,7 @@ public static unsafe if (evtSig is TypeDefOrRefSignature td) { - delegateName = td.Type?.Name?.Value ?? string.Empty; + delegateName = td.Type.GetRawName(); delegateName = IdentifierEscaping.StripBackticks(delegateName); } @@ -158,12 +143,12 @@ public static unsafe } string eventSourceInteropType = isGenericEvent - ? InteropTypeNameWriter.EncodeInteropTypeName(evtSig, TypedefNameType.EventSource) + ", WinRT.Interop" + ? InteropTypeNameWriter.GetInteropAssemblyQualifiedName(evtSig, TypedefNameType.EventSource) : string.Empty; // Emit the per-event ConditionalWeakTable static field. writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" private static ConditionalWeakTable _{{evtName}} { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -183,29 +168,35 @@ public static unsafe public static {{eventSourceProjectedFull}} {{evtName}}(object thisObject, WindowsRuntimeObjectReference thisReference) { - """, isMultiline: true); + """); if (isGenericEvent && !string.IsNullOrEmpty(eventSourceInteropType)) { - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - [return: UnsafeAccessorType("{{eventSourceInteropType}}")] - static extern object ctor(WindowsRuntimeObjectReference nativeObjectReference, int index); - - return _{{evtName}}.GetOrAdd( - key: thisObject, - valueFactory: static (_, thisReference) => Unsafe.As<{{eventSourceProjectedFull}}>(ctor(thisReference, {{eventSlot.ToString(CultureInfo.InvariantCulture)}})), - factoryArgument: thisReference); - """, isMultiline: true); + writer.IncreaseIndent(); + writer.IncreaseIndent(); + UnsafeAccessorFactory.EmitConstructorReturningObject( + writer, + interopType: eventSourceInteropType, + functionName: "ctor", + parameterList: "WindowsRuntimeObjectReference nativeObjectReference, int index"); + writer.WriteLine(); + writer.WriteLine(isMultiline: true, $$""" + return _{{evtName}}.GetOrAdd( + key: thisObject, + valueFactory: static (_, thisReference) => Unsafe.As<{{eventSourceProjectedFull}}>(ctor(thisReference, {{eventSlot.ToString(CultureInfo.InvariantCulture)}})), + factoryArgument: thisReference); + """); + writer.DecreaseIndent(); + writer.DecreaseIndent(); } else { // Non-generic delegate: directly construct. - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" return _{{evtName}}.GetOrAdd( key: thisObject, valueFactory: static (_, thisReference) => new {{eventSourceProjectedFull}}(thisReference, {{eventSlot.ToString(CultureInfo.InvariantCulture)}}), factoryArgument: thisReference); - """, isMultiline: true); + """); } writer.WriteLine(" }"); } diff --git a/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.RcwCaller.cs b/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.RcwCaller.cs index 9b7c2232fb..c19b59e3f1 100644 --- a/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.RcwCaller.cs +++ b/src/WinRT.Projection.Writer/Factories/AbiMethodBodyFactory.RcwCaller.cs @@ -7,10 +7,12 @@ using System.Text; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.References; using WindowsRuntime.ProjectionWriter.Resolvers; using WindowsRuntime.ProjectionWriter.Writers; @@ -37,28 +39,23 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec { TypeSignature? rt = sig.ReturnType; - AbiTypeShapeKind returnShape = rt is null ? AbiTypeShapeKind.Unknown : context.AbiTypeShapeResolver.Resolve(rt).Kind; + MethodSignatureMarshallingFacts facts = MethodSignatureMarshallingFacts.From(sig, context.AbiTypeKindResolver); - bool returnIsString = returnShape == AbiTypeShapeKind.String; - bool returnIsRefType = returnShape is AbiTypeShapeKind.RuntimeClassOrInterface or AbiTypeShapeKind.Delegate or AbiTypeShapeKind.Object or AbiTypeShapeKind.GenericInstance or AbiTypeShapeKind.NullableT; - bool returnIsBlittableStruct = returnShape == AbiTypeShapeKind.BlittableStruct; - bool returnIsComplexStruct = returnShape == AbiTypeShapeKind.ComplexStruct; - bool returnIsReceiveArray = rt is SzArrayTypeSignature retSzCheck - && (context.AbiTypeShapeResolver.IsBlittablePrimitive(retSzCheck.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(retSzCheck.BaseType) - || retSzCheck.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(retSzCheck.BaseType) || retSzCheck.BaseType.IsObject() - || context.AbiTypeShapeResolver.IsComplexStruct(retSzCheck.BaseType) - || retSzCheck.BaseType.IsHResultException() - || context.AbiTypeShapeResolver.IsMappedAbiValueType(retSzCheck.BaseType)); - bool returnIsHResultException = returnShape == AbiTypeShapeKind.HResultException; + bool returnIsString = facts.ReturnIsString; + bool returnIsRefType = facts.ReturnIsRefType; + bool returnIsBlittableStruct = facts.ReturnIsBlittableStruct; + bool returnIsNonBlittableStruct = facts.ReturnIsNonBlittableStruct; + bool returnIsReceiveArray = facts.ReturnIsReceiveArray; + bool returnIsHResultException = facts.ReturnIsHResultException; // Build the function pointer signature: void*, [paramAbiType...,] [retAbiType*,] int StringBuilder fp = new(); _ = fp.Append("void*"); foreach (ParameterInfo p in sig.Parameters) { - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is ParameterCategory.PassArray or ParameterCategory.FillArray) + if (cat.IsArrayInput()) { _ = fp.Append(", uint, void*"); continue; @@ -66,24 +63,24 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec if (cat == ParameterCategory.Out) { - TypeSignature uOut = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); + TypeSignature uOut = p.Type.StripByRefAndCustomModifiers(); _ = fp.Append(", "); - if (uOut.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(uOut) || uOut.IsObject() || uOut.IsGenericInstance()) + if (uOut.IsAbiRefLike(context.AbiTypeKindResolver)) { _ = fp.Append("void**"); } else if (uOut.IsSystemType()) { - _ = fp.Append("global::ABI.System.Type*"); + _ = fp.Append(WellKnownAbiTypeNames.AbiSystemTypePointer); } - else if (context.AbiTypeShapeResolver.IsComplexStruct(uOut)) + else if (context.AbiTypeKindResolver.IsNonBlittableStruct(uOut)) { - _ = fp.Append(AbiTypeHelpers.GetAbiStructTypeName(writer, context, uOut)); _ = fp.Append('*'); + _ = fp.Append(AbiTypeHelpers.GetAbiStructTypeName(context, uOut)); _ = fp.Append('*'); } - else if (context.AbiTypeShapeResolver.IsBlittableStruct(uOut)) + else if (context.AbiTypeKindResolver.IsBlittableStruct(uOut)) { - _ = fp.Append(AbiTypeHelpers.GetBlittableStructAbiType(writer, context, uOut)); _ = fp.Append('*'); + _ = fp.Append(AbiTypeHelpers.GetBlittableStructAbiType(context, uOut)); _ = fp.Append('*'); } else { @@ -95,16 +92,16 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec if (cat == ParameterCategory.Ref) { - TypeSignature uRef = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); + TypeSignature uRef = p.Type.StripByRefAndCustomModifiers(); _ = fp.Append(", "); - if (context.AbiTypeShapeResolver.IsComplexStruct(uRef)) + if (context.AbiTypeKindResolver.IsNonBlittableStruct(uRef)) { - _ = fp.Append(AbiTypeHelpers.GetAbiStructTypeName(writer, context, uRef)); _ = fp.Append('*'); + _ = fp.Append(AbiTypeHelpers.GetAbiStructTypeName(context, uRef)); _ = fp.Append('*'); } - else if (context.AbiTypeShapeResolver.IsBlittableStruct(uRef)) + else if (context.AbiTypeKindResolver.IsBlittableStruct(uRef)) { - _ = fp.Append(AbiTypeHelpers.GetBlittableStructAbiType(writer, context, uRef)); _ = fp.Append('*'); + _ = fp.Append(AbiTypeHelpers.GetBlittableStructAbiType(context, uRef)); _ = fp.Append('*'); } else { @@ -116,32 +113,9 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec if (cat == ParameterCategory.ReceiveArray) { - SzArrayTypeSignature sza = (SzArrayTypeSignature)AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); + SzArrayTypeSignature sza = p.Type.AsSzArray()!; _ = fp.Append(", uint*, "); - - if (sza.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(sza.BaseType) || sza.BaseType.IsObject()) - { - _ = fp.Append("void*"); - } - else if (sza.BaseType.IsHResultException()) - { - _ = fp.Append("global::ABI.System.Exception"); - } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(sza.BaseType)) - { - _ = fp.Append(AbiTypeHelpers.GetMappedAbiTypeName(sza.BaseType)); - } - else if (context.AbiTypeShapeResolver.IsComplexStruct(sza.BaseType)) - { - _ = fp.Append(AbiTypeHelpers.GetAbiStructTypeName(writer, context, sza.BaseType)); - } - else - { - _ = fp.Append(context.AbiTypeShapeResolver.IsBlittableStruct(sza.BaseType) - ? AbiTypeHelpers.GetBlittableStructAbiType(writer, context, sza.BaseType) - : AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, sza.BaseType)); - } - + _ = fp.Append(AbiTypeHelpers.GetArrayElementAbiType(context, sza.BaseType)); _ = fp.Append("**"); continue; } @@ -150,28 +124,28 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec if (p.Type.IsHResultException()) { - _ = fp.Append("global::ABI.System.Exception"); + _ = fp.Append(WellKnownAbiTypeNames.AbiSystemException); } - else if (p.Type.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(p.Type) || p.Type.IsObject() || p.Type.IsGenericInstance()) + else if (p.Type.IsAbiRefLike(context.AbiTypeKindResolver)) { _ = fp.Append("void*"); } else if (p.Type.IsSystemType()) { - _ = fp.Append("global::ABI.System.Type"); + _ = fp.Append(WellKnownAbiTypeNames.AbiSystemType); } - else if (context.AbiTypeShapeResolver.IsBlittableStruct(p.Type)) + else if (context.AbiTypeKindResolver.IsBlittableStruct(p.Type)) { - _ = fp.Append(AbiTypeHelpers.GetBlittableStructAbiType(writer, context, p.Type)); + _ = fp.Append(AbiTypeHelpers.GetBlittableStructAbiType(context, p.Type)); } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(p.Type)) + else if (context.AbiTypeKindResolver.IsMappedAbiValueType(p.Type)) { _ = fp.Append(AbiTypeHelpers.GetMappedAbiTypeName(p.Type)); } else { - _ = fp.Append(context.AbiTypeShapeResolver.IsComplexStruct(p.Type) - ? AbiTypeHelpers.GetAbiStructTypeName(writer, context, p.Type) + _ = fp.Append(context.AbiTypeKindResolver.IsNonBlittableStruct(p.Type) + ? AbiTypeHelpers.GetAbiStructTypeName(context, p.Type) : AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, p.Type)); } } @@ -182,32 +156,7 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec { SzArrayTypeSignature retSz = (SzArrayTypeSignature)rt; _ = fp.Append(", uint*, "); - - if (retSz.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(retSz.BaseType) || retSz.BaseType.IsObject()) - { - _ = fp.Append("void*"); - } - else if (context.AbiTypeShapeResolver.IsComplexStruct(retSz.BaseType)) - { - _ = fp.Append(AbiTypeHelpers.GetAbiStructTypeName(writer, context, retSz.BaseType)); - } - else if (retSz.BaseType.IsHResultException()) - { - _ = fp.Append("global::ABI.System.Exception"); - } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(retSz.BaseType)) - { - _ = fp.Append(AbiTypeHelpers.GetMappedAbiTypeName(retSz.BaseType)); - } - else if (context.AbiTypeShapeResolver.IsBlittableStruct(retSz.BaseType)) - { - _ = fp.Append(AbiTypeHelpers.GetBlittableStructAbiType(writer, context, retSz.BaseType)); - } - else - { - _ = fp.Append(AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, retSz.BaseType)); - } - + _ = fp.Append(AbiTypeHelpers.GetArrayElementAbiType(context, retSz.BaseType)); _ = fp.Append("**"); } else if (returnIsHResultException) @@ -224,17 +173,17 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec } else if (rt is not null && rt.IsSystemType()) { - _ = fp.Append("global::ABI.System.Type*"); + _ = fp.Append(WellKnownAbiTypeNames.AbiSystemTypePointer); } else if (returnIsBlittableStruct) { - _ = fp.Append(AbiTypeHelpers.GetBlittableStructAbiType(writer, context, rt!)); _ = fp.Append('*'); + _ = fp.Append(AbiTypeHelpers.GetBlittableStructAbiType(context, rt!)); _ = fp.Append('*'); } - else if (returnIsComplexStruct) + else if (returnIsNonBlittableStruct) { - _ = fp.Append(AbiTypeHelpers.GetAbiStructTypeName(writer, context, rt!)); _ = fp.Append('*'); + _ = fp.Append(AbiTypeHelpers.GetAbiStructTypeName(context, rt!)); _ = fp.Append('*'); } - else if (rt is not null && context.AbiTypeShapeResolver.IsMappedAbiValueType(rt)) + else if (rt is not null && context.AbiTypeKindResolver.IsMappedAbiValueType(rt)) { _ = fp.Append(AbiTypeHelpers.GetMappedAbiTypeName(rt)); _ = fp.Append('*'); } @@ -248,46 +197,49 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec _ = fp.Append(", int"); writer.WriteLine(); - writer.WriteLine(""" - { - using WindowsRuntimeObjectReferenceValue thisValue = thisReference.AsValue(); - void* ThisPtr = thisValue.GetThisPtrUnsafe(); - """, isMultiline: true); + writer.IncreaseIndent(); + writer.WriteLine(isMultiline: true, """ + { + using WindowsRuntimeObjectReferenceValue thisValue = thisReference.AsValue(); + void* ThisPtr = thisValue.GetThisPtrUnsafe(); + """); + writer.IncreaseIndent(); // Declare 'using' marshaller values for ref-type parameters (these need disposing). for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - if (context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(p.Type) || p.Type.IsObject()) + if (context.AbiTypeKindResolver.IsRuntimeClassOrInterface(p.Type) || p.Type.IsObject()) { - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - writer.Write($" using WindowsRuntimeObjectReferenceValue __{localName} = "); - EmitMarshallerConvertToUnmanaged(writer, context, p.Type, callName); - writer.WriteLine(";"); + string localName = p.GetParamLocalName(paramNameOverride); + string callName = p.GetParamName(paramNameOverride); + EmitMarshallerConvertToUnmanagedCallback cvt = EmitMarshallerConvertToUnmanaged(context, p.Type, callName); + writer.WriteLine($"using WindowsRuntimeObjectReferenceValue __{localName} = {cvt};"); } else if (p.Type.IsNullableT()) { // Nullable param: use Marshaller.BoxToUnmanaged. - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - TypeSignature inner = p.Type.GetNullableInnerType()!; - string innerMarshaller = AbiTypeHelpers.GetNullableInnerMarshallerName(writer, context, inner); - writer.WriteLine($" using WindowsRuntimeObjectReferenceValue __{localName} = {innerMarshaller}.BoxToUnmanaged({callName});"); + string localName = p.GetParamLocalName(paramNameOverride); + string callName = p.GetParamName(paramNameOverride); + (_, string innerMarshaller) = AbiTypeHelpers.GetNullableInnerInfo(writer, context, p.Type); + writer.WriteLine($"using WindowsRuntimeObjectReferenceValue __{localName} = {innerMarshaller}.BoxToUnmanaged({callName});"); } else if (p.Type.IsGenericInstance()) { // Generic instance param: emit a local UnsafeAccessor delegate to get the marshaller method. - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - string interopTypeName = InteropTypeNameWriter.EncodeInteropTypeName(p.Type, TypedefNameType.ABI) + ", WinRT.Interop"; - string projectedTypeName = MethodFactory.WriteProjectedSignature(context, p.Type, false); - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ConvertToUnmanaged")] - static extern WindowsRuntimeObjectReferenceValue ConvertToUnmanaged_{{localName}}([UnsafeAccessorType("{{interopTypeName}}")] object _, {{projectedTypeName}} value); - using WindowsRuntimeObjectReferenceValue __{{localName}} = ConvertToUnmanaged_{{localName}}(null, {{callName}}); - """, isMultiline: true); + string localName = p.GetParamLocalName(paramNameOverride); + string callName = p.GetParamName(paramNameOverride); + string interopTypeName = InteropTypeNameWriter.GetInteropAssemblyQualifiedName(p.Type, TypedefNameType.ABI); + WriteProjectedSignatureCallback projectedTypeName = MethodFactory.WriteProjectedSignature(context, p.Type, false); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "ConvertToUnmanaged", + returnType: "WindowsRuntimeObjectReferenceValue", + functionName: $"ConvertToUnmanaged_{localName}", + interopType: interopTypeName, + parameterList: $", {projectedTypeName.Format()} value"); + writer.WriteLine($"using WindowsRuntimeObjectReferenceValue __{localName} = ConvertToUnmanaged_{localName}(null, {callName});"); } } @@ -298,7 +250,7 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec { ParameterInfo p = sig.Parameters[i]; - if (ParameterCategoryResolver.GetParamCategory(p) != ParameterCategory.In) + if (ParameterCategoryResolver.Resolve(p) != ParameterCategory.In) { continue; } @@ -308,9 +260,9 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec continue; } - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - writer.WriteLine($" global::ABI.System.Exception __{localName} = global::ABI.System.ExceptionMarshaller.ConvertToUnmanaged({callName});"); + string localName = p.GetParamLocalName(paramNameOverride); + string callName = p.GetParamName(paramNameOverride); + writer.WriteLine($"global::ABI.System.Exception __{localName} = global::ABI.System.ExceptionMarshaller.ConvertToUnmanaged({callName});"); } // Declare locals for mapped value-type input parameters (DateTime/TimeSpan): convert via marshaller up-front. @@ -318,19 +270,19 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec { ParameterInfo p = sig.Parameters[i]; - if (ParameterCategoryResolver.GetParamCategory(p) != ParameterCategory.In) + if (ParameterCategoryResolver.Resolve(p) != ParameterCategory.In) { continue; } - if (!context.AbiTypeShapeResolver.IsMappedAbiValueType(p.Type)) + if (!context.AbiTypeKindResolver.IsMappedAbiValueType(p.Type)) { continue; } - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - writer.WriteLine($" {AbiTypeHelpers.GetMappedAbiTypeName(p.Type)} __{localName} = {AbiTypeHelpers.GetMappedMarshallerName(p.Type)}.ConvertToUnmanaged({callName});"); + string localName = p.GetParamLocalName(paramNameOverride); + string callName = p.GetParamName(paramNameOverride); + writer.WriteLine($"{AbiTypeHelpers.GetMappedAbiTypeName(p.Type)} __{localName} = {AbiTypeHelpers.GetMappedMarshallerName(p.Type)}.ConvertToUnmanaged({callName});"); } // Declare locals for complex-struct input parameters (e.g. ProfileUsage with nested @@ -340,100 +292,49 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is not (ParameterCategory.In or ParameterCategory.Ref)) + if (!cat.IsScalarInput()) { continue; } - TypeSignature pType = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); + TypeSignature pType = p.Type.StripByRefAndCustomModifiers(); - if (!context.AbiTypeShapeResolver.IsComplexStruct(pType)) + if (!context.AbiTypeKindResolver.IsNonBlittableStruct(pType)) { continue; } - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - writer.WriteLine($" {AbiTypeHelpers.GetAbiStructTypeName(writer, context, pType)} __{localName} = default;"); + string localName = p.GetParamLocalName(paramNameOverride); + writer.WriteLine($"{AbiTypeHelpers.GetAbiStructTypeName(context, pType)} __{localName} = default;"); } // Declare locals for Out parameters (need to be passed as &__ to the call). - for (int i = 0; i < sig.Parameters.Count; i++) - { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - - if (cat != ParameterCategory.Out) - { - continue; - } - - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - TypeSignature uOut = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); - writer.Write(" "); + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.Out)) - if (uOut.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(uOut) || uOut.IsObject() || uOut.IsGenericInstance()) - { - writer.Write("void*"); - } - else if (uOut.IsSystemType()) - { - writer.Write("global::ABI.System.Type"); - } - else if (context.AbiTypeShapeResolver.IsComplexStruct(uOut)) - { - writer.Write(AbiTypeHelpers.GetAbiStructTypeName(writer, context, uOut)); - } - else if (context.AbiTypeShapeResolver.IsBlittableStruct(uOut)) - { - writer.Write(AbiTypeHelpers.GetBlittableStructAbiType(writer, context, uOut)); - } - else - { - writer.Write(AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, uOut)); - } + { - writer.WriteLine($" __{localName} = default;"); + string localName = p.GetParamLocalName(paramNameOverride); + TypeSignature uOut = p.Type.StripByRefAndCustomModifiers(); + string abi = AbiTypeHelpers.GetAbiLocalTypeName(context, uOut); + writer.WriteLine($"{abi} __{localName} = default;"); } // Declare locals for ReceiveArray params (uint length + element pointer). - for (int i = 0; i < sig.Parameters.Count; i++) + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.ReceiveArray)) + { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - if (cat != ParameterCategory.ReceiveArray) - { - continue; - } + string localName = p.GetParamLocalName(paramNameOverride); + SzArrayTypeSignature sza = p.Type.AsSzArray()!; + writer.WriteLine($"uint __{localName}_length = default;"); + writer.WriteLine(); - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - SzArrayTypeSignature sza = (SzArrayTypeSignature)AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); - writer.WriteLine($$""" - uint __{{localName}}_length = default; - - """, isMultiline: true); // Element ABI type: void* for ref types; ABI struct for complex/blittable structs; // primitive ABI otherwise. - if (sza.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(sza.BaseType) || sza.BaseType.IsObject()) - { - writer.Write("void*"); - } - else if (context.AbiTypeShapeResolver.IsComplexStruct(sza.BaseType)) - { - writer.Write(AbiTypeHelpers.GetAbiStructTypeName(writer, context, sza.BaseType)); - } - else if (context.AbiTypeShapeResolver.IsBlittableStruct(sza.BaseType)) - { - writer.Write(AbiTypeHelpers.GetBlittableStructAbiType(writer, context, sza.BaseType)); - } - else - { - writer.Write(AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, sza.BaseType)); - } - - writer.WriteLine($"* __{localName}_data = default;"); + string elemAbi = AbiTypeHelpers.GetAbiLocalTypeName(context, sza.BaseType); + writer.WriteLine($"{elemAbi}* __{localName}_data = default;"); } // Declare InlineArray16 + ArrayPool fallback for non-blittable PassArray params @@ -442,9 +343,9 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is not (ParameterCategory.PassArray or ParameterCategory.FillArray)) + if (!cat.IsArrayInput()) { continue; } @@ -454,7 +355,7 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec continue; } - if (context.AbiTypeShapeResolver.IsBlittablePrimitive(szArr.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsBlittableAbiElement(szArr.BaseType)) { continue; } @@ -463,23 +364,18 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec // For mapped value types (DateTime/TimeSpan), use the ABI struct type. // For complex structs (e.g. authored BasicStruct with reference fields), use the ABI // struct type. For everything else (runtime classes, objects, strings), use nint. - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - string storageT = context.AbiTypeShapeResolver.IsMappedAbiValueType(szArr.BaseType) - ? AbiTypeHelpers.GetMappedAbiTypeName(szArr.BaseType) - : context.AbiTypeShapeResolver.IsComplexStruct(szArr.BaseType) - ? AbiTypeHelpers.GetAbiStructTypeName(writer, context, szArr.BaseType) - : szArr.BaseType.IsHResultException() - ? "global::ABI.System.Exception" - : "nint"; + string localName = p.GetParamLocalName(paramNameOverride); + string callName = p.GetParamName(paramNameOverride); + ArrayTempNames names = new(localName); + string storageT = AbiTypeHelpers.GetArrayElementStorageType(context, szArr.BaseType); writer.WriteLine(); - writer.WriteLine($$""" - Unsafe.SkipInit(out InlineArray16<{{storageT}}> __{{localName}}_inlineArray); - {{storageT}}[] __{{localName}}_arrayFromPool = null; - Span<{{storageT}}> __{{localName}}_span = {{callName}}.Length <= 16 - ? __{{localName}}_inlineArray[..{{callName}}.Length] - : (__{{localName}}_arrayFromPool = global::System.Buffers.ArrayPool<{{storageT}}>.Shared.Rent({{callName}}.Length)); - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + Unsafe.SkipInit(out InlineArray16<{{storageT}}> {{names.InlineArray}}); + {{storageT}}[] {{names.ArrayFromPool}} = null; + Span<{{storageT}}> {{names.Span}} = {{callName}}.Length <= 16 + ? {{names.InlineArray}}[..{{callName}}.Length] + : ({{names.ArrayFromPool}} = global::System.Buffers.ArrayPool<{{storageT}}>.Shared.Rent({{callName}}.Length)); + """); if (szArr.BaseType.IsString() && cat == ParameterCategory.PassArray) { @@ -487,184 +383,98 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec // Only required for PassArray (managed -> HSTRING conversion); FillArray's native side // fills HSTRING handles directly into the nint storage. writer.WriteLine(); - writer.WriteLine($$""" - Unsafe.SkipInit(out InlineArray16 __{{localName}}_inlineHeaderArray); - HStringHeader[] __{{localName}}_headerArrayFromPool = null; - Span __{{localName}}_headerSpan = {{callName}}.Length <= 16 - ? __{{localName}}_inlineHeaderArray[..{{callName}}.Length] - : (__{{localName}}_headerArrayFromPool = global::System.Buffers.ArrayPool.Shared.Rent({{callName}}.Length)); + writer.WriteLine(isMultiline: true, $$""" + Unsafe.SkipInit(out InlineArray16 {{names.InlineHeaderArray}}); + HStringHeader[] {{names.HeaderArrayFromPool}} = null; + Span {{names.HeaderSpan}} = {{callName}}.Length <= 16 + ? {{names.InlineHeaderArray}}[..{{callName}}.Length] + : ({{names.HeaderArrayFromPool}} = global::System.Buffers.ArrayPool.Shared.Rent({{callName}}.Length)); - Unsafe.SkipInit(out InlineArray16 __{{localName}}_inlinePinnedHandleArray); - nint[] __{{localName}}_pinnedHandleArrayFromPool = null; - Span __{{localName}}_pinnedHandleSpan = {{callName}}.Length <= 16 - ? __{{localName}}_inlinePinnedHandleArray[..{{callName}}.Length] - : (__{{localName}}_pinnedHandleArrayFromPool = global::System.Buffers.ArrayPool.Shared.Rent({{callName}}.Length)); - """, isMultiline: true); + Unsafe.SkipInit(out InlineArray16 {{names.InlinePinnedHandleArray}}); + nint[] {{names.PinnedHandleArrayFromPool}} = null; + Span {{names.PinnedHandleSpan}} = {{callName}}.Length <= 16 + ? {{names.InlinePinnedHandleArray}}[..{{callName}}.Length] + : ({{names.PinnedHandleArrayFromPool}} = global::System.Buffers.ArrayPool.Shared.Rent({{callName}}.Length)); + """); } } if (returnIsReceiveArray) { SzArrayTypeSignature retSz = (SzArrayTypeSignature)rt!; - writer.WriteLine(""" - uint __retval_length = default; - - """, isMultiline: true); - if (retSz.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(retSz.BaseType) || retSz.BaseType.IsObject()) - { - writer.Write("void*"); - } - else if (context.AbiTypeShapeResolver.IsComplexStruct(retSz.BaseType)) - { - writer.Write(AbiTypeHelpers.GetAbiStructTypeName(writer, context, retSz.BaseType)); - } - else if (retSz.BaseType.IsHResultException()) - { - writer.Write("global::ABI.System.Exception"); - } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(retSz.BaseType)) - { - writer.Write(AbiTypeHelpers.GetMappedAbiTypeName(retSz.BaseType)); - } - else if (context.AbiTypeShapeResolver.IsBlittableStruct(retSz.BaseType)) - { - writer.Write(AbiTypeHelpers.GetBlittableStructAbiType(writer, context, retSz.BaseType)); - } - else - { - writer.Write(AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, retSz.BaseType)); - } - - writer.WriteLine("* __retval_data = default;"); + writer.WriteLine("uint __retval_length = default;"); + writer.WriteLine(); + string retElemAbi = AbiTypeHelpers.GetAbiLocalTypeName(context, retSz.BaseType); + writer.WriteLine($"{retElemAbi}* __retval_data = default;"); } else if (returnIsHResultException) { - writer.WriteLine(" global::ABI.System.Exception __retval = default;"); + writer.WriteLine("global::ABI.System.Exception __retval = default;"); } else if (returnIsString || returnIsRefType) { - writer.WriteLine(" void* __retval = default;"); + writer.WriteLine("void* __retval = default;"); } else if (returnIsBlittableStruct) { - writer.WriteLine($" {AbiTypeHelpers.GetBlittableStructAbiType(writer, context, rt!)} __retval = default;"); + writer.WriteLine($"{AbiTypeHelpers.GetBlittableStructAbiType(context, rt!)} __retval = default;"); } - else if (returnIsComplexStruct) + else if (returnIsNonBlittableStruct) { - writer.WriteLine($" {AbiTypeHelpers.GetAbiStructTypeName(writer, context, rt!)} __retval = default;"); + writer.WriteLine($"{AbiTypeHelpers.GetAbiStructTypeName(context, rt!)} __retval = default;"); } - else if (rt is not null && context.AbiTypeShapeResolver.IsMappedAbiValueType(rt)) + else if (rt is not null && context.AbiTypeKindResolver.IsMappedAbiValueType(rt)) { // Mapped value type return (e.g. DateTime/TimeSpan): use the ABI struct as __retval. - writer.WriteLine($" {AbiTypeHelpers.GetMappedAbiTypeName(rt)} __retval = default;"); + writer.WriteLine($"{AbiTypeHelpers.GetMappedAbiTypeName(rt)} __retval = default;"); } else if (rt is not null && rt.IsSystemType()) { // System.Type return: use ABI Type struct as __retval. - writer.WriteLine(" global::ABI.System.Type __retval = default;"); + writer.WriteLine("global::ABI.System.Type __retval = default;"); } else if (rt is not null) { - writer.WriteLine($" {AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, rt)} __retval = default;"); + writer.WriteLine($"{AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, rt)} __retval = default;"); } // Determine if we need a try/finally (for cleanup of string/refType return or receive array // return or Out runtime class params). Input string params no longer need try/finally — // they use the HString fast-path (stack-allocated HStringReference, no free needed). - bool hasOutNeedsCleanup = false; - for (int i = 0; i < sig.Parameters.Count; i++) - { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - - if (cat != ParameterCategory.Out) - { - continue; - } - - TypeSignature uOut = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); - - if (uOut.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(uOut) || uOut.IsObject() || uOut.IsSystemType() || context.AbiTypeShapeResolver.IsComplexStruct(uOut) || uOut.IsGenericInstance()) - { - hasOutNeedsCleanup = true; - break; - } - } - bool hasReceiveArray = false; - for (int i = 0; i < sig.Parameters.Count; i++) - { - if (ParameterCategoryResolver.GetParamCategory(sig.Parameters[i]) == ParameterCategory.ReceiveArray) - { - hasReceiveArray = true; - break; - } - } - bool hasNonBlittablePassArray = false; - for (int i = 0; i < sig.Parameters.Count; i++) - { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - - if ((cat is ParameterCategory.PassArray or ParameterCategory.FillArray) - && p.Type is SzArrayTypeSignature szArrCheck - && !context.AbiTypeShapeResolver.IsBlittablePrimitive(szArrCheck.BaseType) && !context.AbiTypeShapeResolver.IsBlittableStruct(szArrCheck.BaseType) - && !context.AbiTypeShapeResolver.IsMappedAbiValueType(szArrCheck.BaseType)) - { - hasNonBlittablePassArray = true; - break; - } - } - bool hasComplexStructInput = false; - for (int i = 0; i < sig.Parameters.Count; i++) - { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - - if ((cat is ParameterCategory.In or ParameterCategory.Ref) && context.AbiTypeShapeResolver.IsComplexStruct(AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type))) - { - hasComplexStructInput = true; - break; - } - } - - // System.Type return: ABI.System.Type contains an HSTRING that must be disposed - // after marshalling to managed System.Type, otherwise the HSTRING leaks. - bool returnIsSystemTypeForCleanup = rt is not null && rt.IsSystemType(); - bool needsTryFinally = returnIsString || returnIsRefType || returnIsReceiveArray || hasOutNeedsCleanup || hasReceiveArray || returnIsComplexStruct || hasNonBlittablePassArray || hasComplexStructInput || returnIsSystemTypeForCleanup; + bool needsTryFinally = facts.NeedsTryFinally; if (needsTryFinally) { - writer.WriteLine(""" - try - { - """, isMultiline: true); + writer.WriteLine(isMultiline: true, """ + try + { + """); + writer.IncreaseIndent(); } - string indent = needsTryFinally ? " " : " "; - // Inside try (if applicable): assign complex-struct input locals via marshaller. //.: '__value = ProfileUsageMarshaller.ConvertToUnmanaged(value);' // Includes both 'in' (ParameterCategory.In) and 'in T' (ParameterCategory.Ref) forms. for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is not (ParameterCategory.In or ParameterCategory.Ref)) + if (!cat.IsScalarInput()) { continue; } - TypeSignature pType = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); + TypeSignature pType = p.Type.StripByRefAndCustomModifiers(); - if (!context.AbiTypeShapeResolver.IsComplexStruct(pType)) + if (!context.AbiTypeKindResolver.IsNonBlittableStruct(pType)) { continue; } - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - writer.WriteLine($"{indent}__{localName} = {AbiTypeHelpers.GetMarshallerFullName(writer, context, pType)}.ConvertToUnmanaged({callName});"); + string localName = p.GetParamLocalName(paramNameOverride); + string callName = p.GetParamName(paramNameOverride); + writer.WriteLine($"__{localName} = {AbiTypeHelpers.GetMarshallerFullName(writer, context, pType)}.ConvertToUnmanaged({callName});"); } // Type input params: set up TypeReference locals before the fixed block: @@ -673,7 +483,7 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec { ParameterInfo p = sig.Parameters[i]; - if (ParameterCategoryResolver.GetParamCategory(p) != ParameterCategory.In) + if (ParameterCategoryResolver.Resolve(p) != ParameterCategory.In) { continue; } @@ -683,9 +493,9 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec continue; } - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - writer.WriteLine($"{indent}global::ABI.System.TypeMarshaller.ConvertToUnmanagedUnsafe({callName}, out TypeReference __{localName});"); + string localName = p.GetParamLocalName(paramNameOverride); + string callName = p.GetParamName(paramNameOverride); + writer.WriteLine($"global::ABI.System.TypeMarshaller.ConvertToUnmanagedUnsafe({callName}, out TypeReference __{localName});"); } // Open a SINGLE fixed-block for ALL pinnable inputs: @@ -706,7 +516,7 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); if (p.Type.IsString() || p.Type.IsSystemType()) { @@ -714,7 +524,7 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec continue; } - if (cat is ParameterCategory.PassArray or ParameterCategory.FillArray) + if (cat.IsArrayInput()) { // All PassArrays (including complex structs) go in the void* combined block, // matching truth's pattern. Complex structs use a (T*) cast at the call site. @@ -723,28 +533,28 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec } // Emit typed fixed lines for Ref params. - // Skip Ref+ComplexStruct: those are marshalled via __local (no fixed needed) and + // Skip Ref+NonBlittableStruct: those are marshalled via __local (no fixed needed) and // passed as &__local at the call site (the is-value-type-in path). int typedFixedCount = 0; for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); if (cat == ParameterCategory.Ref) { - TypeSignature uRefSkip = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); + TypeSignature uRefSkip = p.Type.StripByRefAndCustomModifiers(); - if (context.AbiTypeShapeResolver.IsComplexStruct(uRefSkip)) + if (context.AbiTypeKindResolver.IsNonBlittableStruct(uRefSkip)) { continue; } - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); + string callName = p.GetParamName(paramNameOverride); + string localName = p.GetParamLocalName(paramNameOverride); TypeSignature uRef = uRefSkip; - string abiType = context.AbiTypeShapeResolver.IsBlittableStruct(uRef) ? AbiTypeHelpers.GetBlittableStructAbiType(writer, context, uRef) : AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, uRef); - writer.WriteLine($"{indent}{new string(' ', fixedNesting * 4)}fixed({abiType}* _{localName} = &{callName})"); + string abiType = context.AbiTypeKindResolver.IsBlittableStruct(uRef) ? AbiTypeHelpers.GetBlittableStructAbiType(context, uRef) : AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, uRef); + writer.WriteLine($"fixed({abiType}* _{localName} = &{callName})"); typedFixedCount++; } } @@ -756,28 +566,25 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec if (hasAnyVoidStarPinnable) { - writer.Write($"{indent}{new string(' ', fixedNesting * 4)}fixed(void* "); + writer.Write("fixed(void* "); bool first = true; for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); bool isString = p.Type.IsString(); bool isType = p.Type.IsSystemType(); - bool isPassArray = cat is ParameterCategory.PassArray or ParameterCategory.FillArray; + bool isPassArray = cat.IsArrayInput(); if (!isString && !isType && !isPassArray) { continue; } - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); + string callName = p.GetParamName(paramNameOverride); + string localName = p.GetParamLocalName(paramNameOverride); - if (!first) - { - writer.Write(", "); - } + writer.WriteIf(!first, ", "); first = false; writer.Write($"_{localName} = "); @@ -789,7 +596,7 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec else if (isPassArray) { TypeSignature elemT = ((SzArrayTypeSignature)p.Type).BaseType; - bool isBlittableElem = context.AbiTypeShapeResolver.IsBlittablePrimitive(elemT) || context.AbiTypeShapeResolver.IsBlittableStruct(elemT); + bool isBlittableElem = context.AbiTypeKindResolver.IsBlittableAbiElement(elemT); bool isStringElem = elemT.IsString(); if (isBlittableElem) @@ -815,11 +622,11 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec writer.Write(callName); } } - writer.WriteLine($$""" - ) - {{indent}}{{new string(' ', fixedNesting * 4)}}{ - """, isMultiline: true); + writer.WriteLine(")"); + writer.WriteLine("{"); fixedNesting++; + writer.IncreaseIndent(); + // Inside the body: emit HStringMarshaller calls for input string params. for (int i = 0; i < sig.Parameters.Count; i++) { @@ -828,9 +635,9 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec continue; } - string callName = AbiTypeHelpers.GetParamName(sig.Parameters[i], paramNameOverride); - string localName = AbiTypeHelpers.GetParamLocalName(sig.Parameters[i], paramNameOverride); - writer.WriteLine($"{indent}{new string(' ', fixedNesting * 4)}HStringMarshaller.ConvertToUnmanagedUnsafe((char*)_{localName}, {callName}?.Length, out HStringReference __{localName});"); + string callName = sig.Parameters[i].GetParamName(paramNameOverride); + string localName = sig.Parameters[i].GetParamLocalName(paramNameOverride); + writer.WriteLine($"HStringMarshaller.ConvertToUnmanagedUnsafe((char*)_{localName}, {callName}?.Length, out HStringReference __{localName});"); } stringPinnablesEmitted = true; } @@ -838,15 +645,14 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec { // Typed fixed lines exist but no void* combined block - we need a body block // to host them. Open a brace block after the last typed fixed line. - writer.WriteLine($"{indent}{new string(' ', fixedNesting * 4)}{{"); + writer.WriteLine("{"); fixedNesting++; + writer.IncreaseIndent(); } // Suppress unused variable warning when block above doesn't fire. _ = stringPinnablesEmitted; - string callIndent = indent + new string(' ', fixedNesting * 4); - // For non-blittable PassArray params, emit CopyToUnmanaged_ (UnsafeAccessor) and call // it to populate the inline/pooled storage from the user-supplied span. For string arrays, // use HStringArrayMarshaller.ConvertToUnmanagedUnsafe instead. @@ -855,9 +661,9 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is not (ParameterCategory.PassArray or ParameterCategory.FillArray)) + if (!cat.IsArrayInput()) { continue; } @@ -867,13 +673,14 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec continue; } - if (context.AbiTypeShapeResolver.IsBlittablePrimitive(szArr.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsBlittableAbiElement(szArr.BaseType)) { continue; } - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); + string callName = p.GetParamName(paramNameOverride); + string localName = p.GetParamLocalName(paramNameOverride); + ArrayTempNames names = new(localName); if (szArr.BaseType.IsString()) { @@ -884,13 +691,13 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec continue; } - writer.WriteLine($$""" - {{callIndent}}HStringArrayMarshaller.ConvertToUnmanagedUnsafe( - {{callIndent}} source: {{callName}}, - {{callIndent}} hstringHeaders: (HStringHeader*) _{{localName}}_inlineHeaderArray, - {{callIndent}} hstrings: __{{localName}}_span, - {{callIndent}} pinnedGCHandles: __{{localName}}_pinnedHandleSpan); - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + HStringArrayMarshaller.ConvertToUnmanagedUnsafe( + source: {{callName}}, + hstringHeaders: (HStringHeader*) _{{localName}}_inlineHeaderArray, + hstrings: {{names.Span}}, + pinnedGCHandles: {{names.PinnedHandleSpan}}); + """); } else { @@ -904,10 +711,8 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec continue; } - string elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(szArr.BaseType)); - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(szArr.BaseType, TypedefNameType.Projected); + WriteProjectionTypeCallback elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(szArr.BaseType)); - _ = elementInteropArg; // For mapped value types (DateTime/TimeSpan) and complex structs, the storage // element is the ABI struct type; the data pointer parameter type uses that // ABI struct. The fixed() opens with void* (per truth's pattern), so a cast @@ -915,19 +720,19 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec string dataParamType; string dataCastType; - if (context.AbiTypeShapeResolver.IsMappedAbiValueType(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsMappedAbiValueType(szArr.BaseType)) { dataParamType = AbiTypeHelpers.GetMappedAbiTypeName(szArr.BaseType) + "*"; dataCastType = "(" + AbiTypeHelpers.GetMappedAbiTypeName(szArr.BaseType) + "*)"; } else if (szArr.BaseType.IsHResultException()) { - dataParamType = "global::ABI.System.Exception*"; + dataParamType = WellKnownAbiTypeNames.AbiSystemExceptionPointer; dataCastType = "(global::ABI.System.Exception*)"; } - else if (context.AbiTypeShapeResolver.IsComplexStruct(szArr.BaseType)) + else if (context.AbiTypeKindResolver.IsNonBlittableStruct(szArr.BaseType)) { - string abiStructName = AbiTypeHelpers.GetAbiStructTypeName(writer, context, szArr.BaseType); + string abiStructName = AbiTypeHelpers.GetAbiStructTypeName(context, szArr.BaseType); dataParamType = abiStructName + "*"; dataCastType = "(" + abiStructName + "*)"; } @@ -937,15 +742,17 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec dataCastType = "(void**)"; } - writer.WriteLine($$""" - {{callIndent}}[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "CopyToUnmanaged")] - {{callIndent}}static extern void CopyToUnmanaged_{{localName}}([UnsafeAccessorType("{{ArrayElementEncoder.GetArrayMarshallerInteropPath(szArr.BaseType)}}")] object _, ReadOnlySpan<{{elementProjected}}> span, uint length, {{dataParamType}} data); - {{callIndent}}CopyToUnmanaged_{{localName}}(null, {{callName}}, (uint){{callName}}.Length, {{dataCastType}}_{{localName}}); - """, isMultiline: true); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "CopyToUnmanaged", + returnType: "void", + functionName: $"CopyToUnmanaged_{localName}", + interopType: ArrayElementEncoder.GetArrayMarshallerInteropPath(szArr.BaseType), + parameterList: $", ReadOnlySpan<{elementProjected.Format()}> span, uint length, {dataParamType} data"); + writer.WriteLine($"CopyToUnmanaged_{localName}(null, {callName}, (uint){callName}.Length, {dataCastType}_{localName});"); } } - writer.Write(callIndent); // method/property is [NoException] (its HRESULT is contractually S_OK). if (!isNoExcept) { @@ -960,96 +767,96 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is ParameterCategory.PassArray or ParameterCategory.FillArray) + if (cat.IsArrayInput()) { - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - writer.Write($$""" + string callName = p.GetParamName(paramNameOverride); + string localName = p.GetParamLocalName(paramNameOverride); + writer.Write(isMultiline: true, $$""" , (uint){{callName}}.Length, _{{localName}} - """, isMultiline: true); + """); continue; } if (cat == ParameterCategory.Out) { - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - writer.Write($$""" + string localName = p.GetParamLocalName(paramNameOverride); + writer.Write(isMultiline: true, $$""" , &__{{localName}} - """, isMultiline: true); + """); continue; } if (cat == ParameterCategory.ReceiveArray) { - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - writer.Write($$""" + string localName = p.GetParamLocalName(paramNameOverride); + writer.Write(isMultiline: true, $$""" , &__{{localName}}_length, &__{{localName}}_data - """, isMultiline: true); + """); continue; } if (cat == ParameterCategory.Ref) { - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - TypeSignature uRefArg = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); + string localName = p.GetParamLocalName(paramNameOverride); + TypeSignature uRefArg = p.Type.StripByRefAndCustomModifiers(); - if (context.AbiTypeShapeResolver.IsComplexStruct(uRefArg)) + if (context.AbiTypeKindResolver.IsNonBlittableStruct(uRefArg)) { // Complex struct 'in' (Ref) param: pass &__local (the marshaled ABI struct). - writer.Write($$""" + writer.Write(isMultiline: true, $$""" , &__{{localName}} - """, isMultiline: true); + """); } else { // 'in T' projected param: pass the pinned pointer. - writer.Write($$""" + writer.Write(isMultiline: true, $$""" , _{{localName}} - """, isMultiline: true); + """); } continue; } - writer.Write(""" + writer.Write(isMultiline: true, """ , - """, isMultiline: true); + """); if (p.Type.IsHResultException()) { - writer.Write($"__{AbiTypeHelpers.GetParamLocalName(p, paramNameOverride)}"); + writer.Write($"__{p.GetParamLocalName(paramNameOverride)}"); } else if (p.Type.IsString()) { - writer.Write($"__{AbiTypeHelpers.GetParamLocalName(p, paramNameOverride)}.HString"); + writer.Write($"__{p.GetParamLocalName(paramNameOverride)}.HString"); } - else if (context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(p.Type) || p.Type.IsObject() || p.Type.IsGenericInstance()) + else if (context.AbiTypeKindResolver.IsReferenceTypeOrGenericInstance(p.Type)) { - writer.Write($"__{AbiTypeHelpers.GetParamLocalName(p, paramNameOverride)}.GetThisPtrUnsafe()"); + writer.Write($"__{p.GetParamLocalName(paramNameOverride)}.GetThisPtrUnsafe()"); } else if (p.Type.IsSystemType()) { // System.Type input: pass the pre-converted ABI Type struct (via the local set up before the call). - writer.Write($"__{AbiTypeHelpers.GetParamLocalName(p, paramNameOverride)}.ConvertToUnmanagedUnsafe()"); + writer.Write($"__{p.GetParamLocalName(paramNameOverride)}.ConvertToUnmanagedUnsafe()"); } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(p.Type)) + else if (context.AbiTypeKindResolver.IsMappedAbiValueType(p.Type)) { // Mapped value-type input: pass the pre-converted ABI local. - writer.Write($"__{AbiTypeHelpers.GetParamLocalName(p, paramNameOverride)}"); + writer.Write($"__{p.GetParamLocalName(paramNameOverride)}"); } - else if (context.AbiTypeShapeResolver.IsComplexStruct(p.Type)) + else if (context.AbiTypeKindResolver.IsNonBlittableStruct(p.Type)) { // Complex struct input: pass the pre-converted ABI struct local. - writer.Write($"__{AbiTypeHelpers.GetParamLocalName(p, paramNameOverride)}"); + writer.Write($"__{p.GetParamLocalName(paramNameOverride)}"); } - else if (context.AbiTypeShapeResolver.IsBlittableStruct(p.Type)) + else if (context.AbiTypeKindResolver.IsBlittableStruct(p.Type)) { - writer.Write(AbiTypeHelpers.GetParamName(p, paramNameOverride)); + writer.Write(p.GetParamName(paramNameOverride)); } else { @@ -1059,17 +866,17 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec if (returnIsReceiveArray) { - writer.Write(""" + writer.Write(isMultiline: true, """ , &__retval_length, &__retval_data - """, isMultiline: true); + """); } else if (rt is not null) { - writer.Write(""" + writer.Write(isMultiline: true, """ , &__retval - """, isMultiline: true); + """); } // Close the vtable call. One less ')' when noexcept (no ThrowExceptionForHR wrap). @@ -1080,103 +887,70 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec // ABI-format buffer (_) which is separate from the user's Span; we need to // CopyToManaged_ to convert each ABI element back to the projected form and // store it in the user's Span.write_marshal_from_abi - // Blittable element types (primitives and almost-blittable structs) don't need this + // Blittable element types (primitives and blittable structs) don't need this // because the user's Span wraps the same memory the native side wrote to. - for (int i = 0; i < sig.Parameters.Count; i++) - { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.FillArray)) - if (cat != ParameterCategory.FillArray) - { - continue; - } + { if (p.Type is not SzArrayTypeSignature szFA) { continue; } - if (context.AbiTypeShapeResolver.IsBlittablePrimitive(szFA.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(szFA.BaseType)) + if (context.AbiTypeKindResolver.IsBlittableAbiElement(szFA.BaseType)) { continue; } - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - string elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(szFA.BaseType)); - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(szFA.BaseType, TypedefNameType.Projected); - - _ = elementInteropArg; - // Determine the ABI element type for the data pointer parameter. - // - Strings / runtime classes / objects: void** - // - HResult exception: global::ABI.System.Exception* - // - Mapped value types: global::ABI.System.{DateTimeOffset|TimeSpan}* - // - Complex structs: * - string dataParamType; - string dataCastType; - - if (szFA.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(szFA.BaseType) || szFA.BaseType.IsObject()) - { - dataParamType = "void** data"; - dataCastType = "(void**)"; - } - else if (szFA.BaseType.IsHResultException()) - { - dataParamType = "global::ABI.System.Exception* data"; - dataCastType = "(global::ABI.System.Exception*)"; - } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(szFA.BaseType)) - { - string abiName = AbiTypeHelpers.GetMappedAbiTypeName(szFA.BaseType); - dataParamType = abiName + "* data"; - dataCastType = "(" + abiName + "*)"; - } - else - { - string abiStructName = AbiTypeHelpers.GetAbiStructTypeName(writer, context, szFA.BaseType); - dataParamType = abiStructName + "* data"; - dataCastType = "(" + abiStructName + "*)"; - } - - writer.WriteLine($$""" - {{callIndent}}[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "CopyToManaged")] - {{callIndent}}static extern void CopyToManaged_{{localName}}([UnsafeAccessorType("{{ArrayElementEncoder.GetArrayMarshallerInteropPath(szFA.BaseType)}}")] object _, uint length, {{dataParamType}}, Span<{{elementProjected}}> span); - {{callIndent}}CopyToManaged_{{localName}}(null, (uint)__{{localName}}_span.Length, {{dataCastType}}_{{localName}}, {{callName}}); - """, isMultiline: true); + string callName = p.GetParamName(paramNameOverride); + string localName = p.GetParamLocalName(paramNameOverride); + ArrayTempNames names = new(localName); + WriteProjectionTypeCallback elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(szFA.BaseType)); + + // Determine the ABI element type for the data pointer parameter (e.g. "void*" for + // ref-like elements -> "void** data"/"(void**)", or "global::ABI.Foo.Bar" for complex + // structs -> "global::ABI.Foo.Bar* data"/"(global::ABI.Foo.Bar*)"). + string elementAbi = AbiTypeHelpers.GetArrayElementAbiType(context, szFA.BaseType); + + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "CopyToManaged", + returnType: "void", + functionName: $"CopyToManaged_{localName}", + interopType: ArrayElementEncoder.GetArrayMarshallerInteropPath(szFA.BaseType), + parameterList: $", uint length, {elementAbi}* data, Span<{elementProjected.Format()}> span"); + writer.WriteLine($"CopyToManaged_{localName}(null, (uint){names.Span}.Length, ({elementAbi}*)_{localName}, {callName});"); } // After call: write back Out params to caller's 'out' var. - for (int i = 0; i < sig.Parameters.Count; i++) - { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.Out)) - if (cat != ParameterCategory.Out) - { - continue; - } + { - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - TypeSignature uOut = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); + string callName = p.GetParamName(paramNameOverride); + string localName = p.GetParamLocalName(paramNameOverride); + TypeSignature uOut = p.Type.StripByRefAndCustomModifiers(); // For Out generic instance: emit inline UnsafeAccessor to ConvertToManaged_ // before the writeback. (e.g. Collection1HandlerInvoke // emits the accessor inside try, right before the assignment). if (uOut.IsGenericInstance()) { - string interopTypeName = InteropTypeNameWriter.EncodeInteropTypeName(uOut, TypedefNameType.ABI) + ", WinRT.Interop"; - string projectedTypeName = MethodFactory.WriteProjectedSignature(context, uOut, false); - writer.WriteLine($$""" - {{callIndent}}[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ConvertToManaged")] - {{callIndent}}static extern {{projectedTypeName}} ConvertToManaged_{{localName}}([UnsafeAccessorType("{{interopTypeName}}")] object _, void* value); - {{callIndent}}{{callName}} = ConvertToManaged_{{localName}}(null, __{{localName}}); - """, isMultiline: true); + string interopTypeName = InteropTypeNameWriter.GetInteropAssemblyQualifiedName(uOut, TypedefNameType.ABI); + WriteProjectedSignatureCallback projectedTypeName = MethodFactory.WriteProjectedSignature(context, uOut, false); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "ConvertToManaged", + returnType: projectedTypeName.Format(), + functionName: $"ConvertToManaged_{localName}", + interopType: interopTypeName, + parameterList: ", void* value"); + writer.WriteLine($"{callName} = ConvertToManaged_{localName}(null, __{localName});"); continue; } - writer.Write($"{callIndent}{callName} = "); + writer.Write($"{callName} = "); if (uOut.IsString()) { @@ -1186,7 +960,7 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec { writer.Write($"WindowsRuntimeObjectMarshaller.ConvertToManaged(__{localName})"); } - else if (context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(uOut)) + else if (context.AbiTypeKindResolver.IsRuntimeClassOrInterface(uOut)) { writer.Write($"{AbiTypeHelpers.GetMarshallerFullName(writer, context, uOut)}.ConvertToManaged(__{localName})"); } @@ -1194,11 +968,11 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec { writer.Write($"global::ABI.System.TypeMarshaller.ConvertToManaged(__{localName})"); } - else if (context.AbiTypeShapeResolver.IsComplexStruct(uOut)) + else if (context.AbiTypeKindResolver.IsNonBlittableStruct(uOut)) { writer.Write($"{AbiTypeHelpers.GetMarshallerFullName(writer, context, uOut)}.ConvertToManaged(__{localName})"); } - else if (context.AbiTypeShapeResolver.IsBlittableStruct(uOut)) + else if (context.AbiTypeKindResolver.IsBlittableStruct(uOut)) { writer.Write($"__{localName}"); } @@ -1210,7 +984,7 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec { writer.Write($"__{localName}"); } - else if (context.AbiTypeShapeResolver.IsEnumType(uOut)) + else if (context.AbiTypeKindResolver.IsEnumType(uOut)) { // Enum out param: __ local is already the projected enum type (since the // function pointer signature uses the projected type). No cast needed. @@ -1225,39 +999,27 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec } // Writeback for ReceiveArray params: emit a UnsafeAccessor + assign to the out param. - for (int i = 0; i < sig.Parameters.Count; i++) + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.ReceiveArray)) + { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - if (cat != ParameterCategory.ReceiveArray) - { - continue; - } + string callName = p.GetParamName(paramNameOverride); + string localName = p.GetParamLocalName(paramNameOverride); + SzArrayTypeSignature sza = p.Type.AsSzArray()!; + WriteProjectionTypeCallback elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(sza.BaseType)); - string callName = AbiTypeHelpers.GetParamName(p, paramNameOverride); - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - SzArrayTypeSignature sza = (SzArrayTypeSignature)AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); - string elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(sza.BaseType)); - // Element ABI type: void* for ref types (string/runtime class/object); ABI struct - // type for complex structs (e.g. authored BasicStruct); blittable struct ABI for - // blittable structs; primitive ABI otherwise. - string elementAbi = sza.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(sza.BaseType) || sza.BaseType.IsObject() - ? "void*" - : context.AbiTypeShapeResolver.IsComplexStruct(sza.BaseType) - ? AbiTypeHelpers.GetAbiStructTypeName(writer, context, sza.BaseType) - : context.AbiTypeShapeResolver.IsBlittableStruct(sza.BaseType) - ? AbiTypeHelpers.GetBlittableStructAbiType(writer, context, sza.BaseType) - : AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, sza.BaseType); - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(sza.BaseType, TypedefNameType.Projected); - - _ = elementInteropArg; + // Element ABI type for the `data` parameter (void* for ref types, ABI struct for + // complex structs, blittable struct ABI for blittable structs, primitive ABI otherwise). + string elementAbi = AbiTypeHelpers.GetArrayElementAbiType(context, sza.BaseType); string marshallerPath = ArrayElementEncoder.GetArrayMarshallerInteropPath(sza.BaseType); - writer.WriteLine($$""" - {{callIndent}}[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ConvertToManaged")] - {{callIndent}}static extern {{elementProjected}}[] ConvertToManaged_{{localName}}([UnsafeAccessorType("{{marshallerPath}}")] object _, uint length, {{elementAbi}}* data); - {{callIndent}}{{callName}} = ConvertToManaged_{{localName}}(null, __{{localName}}_length, __{{localName}}_data); - """, isMultiline: true); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "ConvertToManaged", + returnType: $"{elementProjected.Format()}[]", + functionName: $"ConvertToManaged_{localName}", + interopType: marshallerPath, + parameterList: $", uint length, {elementAbi}* data"); + writer.WriteLine($"{callName} = ConvertToManaged_{localName}(null, __{localName}_length, __{localName}_data);"); } if (rt is not null) @@ -1265,34 +1027,24 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec if (returnIsReceiveArray) { SzArrayTypeSignature retSz = (SzArrayTypeSignature)rt; - string elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(retSz.BaseType)); - string elementAbi = retSz.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(retSz.BaseType) || retSz.BaseType.IsObject() - ? "void*" - : context.AbiTypeShapeResolver.IsComplexStruct(retSz.BaseType) - ? AbiTypeHelpers.GetAbiStructTypeName(writer, context, retSz.BaseType) - : retSz.BaseType.IsHResultException() - ? "global::ABI.System.Exception" - : context.AbiTypeShapeResolver.IsMappedAbiValueType(retSz.BaseType) - ? AbiTypeHelpers.GetMappedAbiTypeName(retSz.BaseType) - : context.AbiTypeShapeResolver.IsBlittableStruct(retSz.BaseType) - ? AbiTypeHelpers.GetBlittableStructAbiType(writer, context, retSz.BaseType) - : AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, retSz.BaseType); - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(retSz.BaseType, TypedefNameType.Projected); - - _ = elementInteropArg; - writer.WriteLine($$""" - {{callIndent}}[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ConvertToManaged")] - {{callIndent}}static extern {{elementProjected}}[] ConvertToManaged_retval([UnsafeAccessorType("{{ArrayElementEncoder.GetArrayMarshallerInteropPath(retSz.BaseType)}}")] object _, uint length, {{elementAbi}}* data); - {{callIndent}}return ConvertToManaged_retval(null, __retval_length, __retval_data); - """, isMultiline: true); + WriteProjectionTypeCallback elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(retSz.BaseType)); + string elementAbi = AbiTypeHelpers.GetArrayElementAbiType(context, retSz.BaseType); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "ConvertToManaged", + returnType: $"{elementProjected.Format()}[]", + functionName: "ConvertToManaged_retval", + interopType: ArrayElementEncoder.GetArrayMarshallerInteropPath(retSz.BaseType), + parameterList: $", uint length, {elementAbi}* data"); + writer.WriteLine("return ConvertToManaged_retval(null, __retval_length, __retval_data);"); } else if (returnIsHResultException) { - writer.WriteLine($"{callIndent}return global::ABI.System.ExceptionMarshaller.ConvertToManaged(__retval);"); + writer.WriteLine("return global::ABI.System.ExceptionMarshaller.ConvertToManaged(__retval);"); } else if (returnIsString) { - writer.WriteLine($"{callIndent}return HStringMarshaller.ConvertToManaged(__retval);"); + writer.WriteLine("return HStringMarshaller.ConvertToManaged(__retval);"); } else if (returnIsRefType) { @@ -1300,42 +1052,41 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec { // Nullable return: use Marshaller.UnboxToManaged.; // there is no NullableMarshaller, the inner-T marshaller has UnboxToManaged. - TypeSignature inner = rt.GetNullableInnerType()!; - string innerMarshaller = AbiTypeHelpers.GetNullableInnerMarshallerName(writer, context, inner); - writer.WriteLine($"{callIndent}return {innerMarshaller}.UnboxToManaged(__retval);"); + (_, string innerMarshaller) = AbiTypeHelpers.GetNullableInnerInfo(writer, context, rt); + writer.WriteLine($"return {innerMarshaller}.UnboxToManaged(__retval);"); } else if (rt.IsGenericInstance()) { - string interopTypeName = InteropTypeNameWriter.EncodeInteropTypeName(rt, TypedefNameType.ABI) + ", WinRT.Interop"; - string projectedTypeName = MethodFactory.WriteProjectedSignature(context, rt, false); - writer.WriteLine($$""" - {{callIndent}}[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ConvertToManaged")] - {{callIndent}}static extern {{projectedTypeName}} ConvertToManaged_retval([UnsafeAccessorType("{{interopTypeName}}")] object _, void* value); - {{callIndent}}return ConvertToManaged_retval(null, __retval); - """, isMultiline: true); + string interopTypeName = InteropTypeNameWriter.GetInteropAssemblyQualifiedName(rt, TypedefNameType.ABI); + WriteProjectedSignatureCallback projectedTypeName = MethodFactory.WriteProjectedSignature(context, rt, false); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "ConvertToManaged", + returnType: projectedTypeName.Format(), + functionName: "ConvertToManaged_retval", + interopType: interopTypeName, + parameterList: ", void* value"); + writer.WriteLine("return ConvertToManaged_retval(null, __retval);"); } else { - writer.Write($"{callIndent}return "); - EmitMarshallerConvertToManaged(writer, context, rt, "__retval"); - writer.WriteLine(";"); + EmitMarshallerConvertToManagedCallback cvt = EmitMarshallerConvertToManaged(context, rt, "__retval"); + writer.WriteLine($"return {cvt};"); } } - else if (rt is not null && context.AbiTypeShapeResolver.IsMappedAbiValueType(rt)) + else if (rt is not null && context.AbiTypeKindResolver.IsMappedAbiValueType(rt)) { // Mapped value type return (e.g. DateTime/TimeSpan): convert ABI struct back via marshaller. - writer.WriteLine($"{callIndent}return {AbiTypeHelpers.GetMappedMarshallerName(rt)}.ConvertToManaged(__retval);"); + writer.WriteLine($"return {AbiTypeHelpers.GetMappedMarshallerName(rt)}.ConvertToManaged(__retval);"); } else if (rt is not null && rt.IsSystemType()) { // System.Type return: convert ABI Type struct back to System.Type via TypeMarshaller. - writer.WriteLine($"{callIndent}return global::ABI.System.TypeMarshaller.ConvertToManaged(__retval);"); + writer.WriteLine("return global::ABI.System.TypeMarshaller.ConvertToManaged(__retval);"); } else if (returnIsBlittableStruct) { - writer.Write(callIndent); - - if (rt is not null && context.AbiTypeShapeResolver.IsMappedAbiValueType(rt)) + if (rt is not null && context.AbiTypeKindResolver.IsMappedAbiValueType(rt)) { // Mapped value type return: convert ABI struct back to projected via marshaller. writer.WriteLine($"return {AbiTypeHelpers.GetMappedMarshallerName(rt)}.ConvertToManaged(__retval);"); @@ -1345,14 +1096,14 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec writer.WriteLine("return __retval;"); } } - else if (returnIsComplexStruct) + else if (returnIsNonBlittableStruct) { - writer.WriteLine($"{callIndent}return {AbiTypeHelpers.GetMarshallerFullName(writer, context, rt!)}.ConvertToManaged(__retval);"); + writer.WriteLine($"return {AbiTypeHelpers.GetMarshallerFullName(writer, context, rt!)}.ConvertToManaged(__retval);"); } else { - writer.Write($"{callIndent}return "); - string projected = MethodFactory.WriteProjectedSignature(context, rt!, false); + writer.Write("return "); + string projected = MethodFactory.WriteProjectedSignature(context, rt!, false).Format(); string abiType = AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, rt!); if (projected == abiType) @@ -1369,16 +1120,18 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec // Close fixed blocks (innermost first). for (int i = fixedNesting - 1; i >= 0; i--) { - writer.WriteLine($"{indent}{new string(' ', i * 4)}}}"); + writer.DecreaseIndent(); + writer.WriteLine("}"); } if (needsTryFinally) { - writer.WriteLine(""" - } - finally - { - """, isMultiline: true); + writer.DecreaseIndent(); + writer.WriteLine(isMultiline: true, """ + } + finally + """); + using IndentedTextWriter.Block __finallyBlock = writer.WriteBlock(); // Order matches truth: // 0. Complex-struct input param Dispose (e.g. ProfileUsageMarshaller.Dispose(__value)) @@ -1391,22 +1144,22 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is not (ParameterCategory.In or ParameterCategory.Ref)) + if (!cat.IsScalarInput()) { continue; } - TypeSignature pType = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); + TypeSignature pType = p.Type.StripByRefAndCustomModifiers(); - if (!context.AbiTypeShapeResolver.IsComplexStruct(pType)) + if (!context.AbiTypeKindResolver.IsNonBlittableStruct(pType)) { continue; } - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - writer.WriteLine($" {AbiTypeHelpers.GetMarshallerFullName(writer, context, pType)}.Dispose(__{localName});"); + string localName = p.GetParamLocalName(paramNameOverride); + writer.WriteLine($"{AbiTypeHelpers.GetMarshallerFullName(writer, context, pType)}.Dispose(__{localName});"); } // 1. Cleanup non-blittable PassArray/FillArray params: @@ -1417,9 +1170,9 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec for (int i = 0; i < sig.Parameters.Count; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is not (ParameterCategory.PassArray or ParameterCategory.FillArray)) + if (!cat.IsArrayInput()) { continue; } @@ -1429,12 +1182,12 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec continue; } - if (context.AbiTypeShapeResolver.IsBlittablePrimitive(szArr.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsBlittableAbiElement(szArr.BaseType)) { continue; } - if (context.AbiTypeShapeResolver.IsMappedAbiValueType(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsMappedAbiValueType(szArr.BaseType)) { continue; } @@ -1444,17 +1197,18 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec // HResultException ABI is just an int; per-element Dispose is a no-op (mirror // the truth: no Dispose_ emitted). Just return the inline-array's pool // using the correct element type (ABI.System.Exception, not nint). - string localNameH = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); + string localNameH = p.GetParamLocalName(paramNameOverride); writer.WriteLine(); - writer.WriteLine($$""" - if (__{{localNameH}}_arrayFromPool is not null) - { - global::System.Buffers.ArrayPool.Shared.Return(__{{localNameH}}_arrayFromPool); - } - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + if (__{{localNameH}}_arrayFromPool is not null) + { + global::System.Buffers.ArrayPool.Shared.Return(__{{localNameH}}_arrayFromPool); + } + """); continue; } - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); + string localName = p.GetParamLocalName(paramNameOverride); + ArrayTempNames names = new(localName); if (szArr.BaseType.IsString()) { @@ -1464,29 +1218,29 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec // array directly, with no per-element pinned handle / header to release. if (cat == ParameterCategory.PassArray) { - writer.WriteLine($$""" - HStringArrayMarshaller.Dispose(__{{localName}}_pinnedHandleSpan); + writer.WriteLine(isMultiline: true, $$""" + HStringArrayMarshaller.Dispose({{names.PinnedHandleSpan}}); - if (__{{localName}}_pinnedHandleArrayFromPool is not null) - { - global::System.Buffers.ArrayPool.Shared.Return(__{{localName}}_pinnedHandleArrayFromPool); - } + if ({{names.PinnedHandleArrayFromPool}} is not null) + { + global::System.Buffers.ArrayPool.Shared.Return({{names.PinnedHandleArrayFromPool}}); + } - if (__{{localName}}_headerArrayFromPool is not null) - { - global::System.Buffers.ArrayPool.Shared.Return(__{{localName}}_headerArrayFromPool); - } - """, isMultiline: true); + if ({{names.HeaderArrayFromPool}} is not null) + { + global::System.Buffers.ArrayPool.Shared.Return({{names.HeaderArrayFromPool}}); + } + """); } // Both PassArray and FillArray need the inline-array's nint pool returned. writer.WriteLine(); - writer.WriteLine($$""" - if (__{{localName}}_arrayFromPool is not null) - { - global::System.Buffers.ArrayPool.Shared.Return(__{{localName}}_arrayFromPool); - } - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + if ({{names.ArrayFromPool}} is not null) + { + global::System.Buffers.ArrayPool.Shared.Return({{names.ArrayFromPool}}); + } + """); } else { @@ -1498,9 +1252,9 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec string fixedPtrType; string disposeCastType; - if (context.AbiTypeShapeResolver.IsComplexStruct(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsNonBlittableStruct(szArr.BaseType)) { - string abiStructName = AbiTypeHelpers.GetAbiStructTypeName(writer, context, szArr.BaseType); + string abiStructName = AbiTypeHelpers.GetAbiStructTypeName(context, szArr.BaseType); disposeDataParamType = abiStructName + "*"; fixedPtrType = abiStructName + "*"; disposeCastType = string.Empty; @@ -1511,157 +1265,120 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec fixedPtrType = "void*"; disposeCastType = "(void**)"; } + writer.WriteLine(isMultiline: true, $$""" + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Dispose")] + static extern void Dispose_{{localName}}([UnsafeAccessorType("{{ArrayElementEncoder.GetArrayMarshallerInteropPath(szArr.BaseType)}}")] object _, uint length, {{disposeDataParamType}} + """); + writer.WriteIf(!disposeDataParamType.EndsWith("data", StringComparison.Ordinal), " data"); - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(szArr.BaseType, TypedefNameType.Projected); - - _ = elementInteropArg; - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Dispose")] - static extern void Dispose_{{localName}}([UnsafeAccessorType("{{ArrayElementEncoder.GetArrayMarshallerInteropPath(szArr.BaseType)}}")] object _, uint length, {{disposeDataParamType}} - """, isMultiline: true); - if (!disposeDataParamType.EndsWith("data", StringComparison.Ordinal)) - { - writer.Write(" data"); - } - - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" ); - fixed({{fixedPtrType}} _{{localName}} = __{{localName}}_span) - { - Dispose_{{localName}}(null, (uint) __{{localName}}_span.Length, {{disposeCastType}}_{{localName}}); - } - """, isMultiline: true); + fixed({{fixedPtrType}} _{{localName}} = {{names.Span}}) + { + Dispose_{{localName}}(null, (uint) {{names.Span}}.Length, {{disposeCastType}}_{{localName}}); + } + """); } // ArrayPool storage type matches the InlineArray storage (mapped ABI value type - // for DateTime/TimeSpan; ABI struct for complex structs; nint otherwise). - string poolStorageT = context.AbiTypeShapeResolver.IsMappedAbiValueType(szArr.BaseType) - ? AbiTypeHelpers.GetMappedAbiTypeName(szArr.BaseType) - : context.AbiTypeShapeResolver.IsComplexStruct(szArr.BaseType) - ? AbiTypeHelpers.GetAbiStructTypeName(writer, context, szArr.BaseType) - : "nint"; + // for DateTime/TimeSpan; ABI struct for complex structs; ABI Exception for + // HResult; nint otherwise). Same dispatch as the InlineArray16 setup. + string poolStorageT = AbiTypeHelpers.GetArrayElementStorageType(context, szArr.BaseType); writer.WriteLine(); - writer.WriteLine($$""" - if (__{{localName}}_arrayFromPool is not null) - { - global::System.Buffers.ArrayPool<{{poolStorageT}}>.Shared.Return(__{{localName}}_arrayFromPool); - } - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + if ({{names.ArrayFromPool}} is not null) + { + global::System.Buffers.ArrayPool<{{poolStorageT}}>.Shared.Return({{names.ArrayFromPool}}); + } + """); } // 2. Free Out string/object/runtime-class params. - for (int i = 0; i < sig.Parameters.Count; i++) - { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.Out)) - if (cat != ParameterCategory.Out) - { - continue; - } + { - TypeSignature uOut = AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); + TypeSignature uOut = p.Type.StripByRefAndCustomModifiers(); + string localName = p.GetParamLocalName(paramNameOverride); if (uOut.IsString()) { - writer.WriteLine($" HStringMarshaller.Free(__{localName});"); + writer.WriteLine($"HStringMarshaller.Free(__{localName});"); } - else if (uOut.IsObject() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(uOut) || uOut.IsGenericInstance()) + else if (uOut.IsObject() || context.AbiTypeKindResolver.IsRuntimeClassOrInterface(uOut) || uOut.IsGenericInstance()) { - writer.WriteLine($" WindowsRuntimeUnknownMarshaller.Free(__{localName});"); + writer.WriteLine($"WindowsRuntimeUnknownMarshaller.Free(__{localName});"); } else if (uOut.IsSystemType()) { - writer.WriteLine($" global::ABI.System.TypeMarshaller.Dispose(__{localName});"); + writer.WriteLine($"global::ABI.System.TypeMarshaller.Dispose(__{localName});"); } - else if (context.AbiTypeShapeResolver.IsComplexStruct(uOut)) + else if (context.AbiTypeKindResolver.IsNonBlittableStruct(uOut)) { - writer.WriteLine($" {AbiTypeHelpers.GetMarshallerFullName(writer, context, uOut)}.Dispose(__{localName});"); + writer.WriteLine($"{AbiTypeHelpers.GetMarshallerFullName(writer, context, uOut)}.Dispose(__{localName});"); } } // 3. Free ReceiveArray params via UnsafeAccessor. - for (int i = 0; i < sig.Parameters.Count; i++) + foreach ((_, ParameterInfo p) in sig.ParametersByCategory(ParameterCategory.ReceiveArray)) + { - ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - if (cat != ParameterCategory.ReceiveArray) - { - continue; - } + string localName = p.GetParamLocalName(paramNameOverride); + SzArrayTypeSignature sza = p.Type.AsSzArray()!; - string localName = AbiTypeHelpers.GetParamLocalName(p, paramNameOverride); - SzArrayTypeSignature sza = (SzArrayTypeSignature)AbiTypeHelpers.StripByRefAndCustomModifiers(p.Type); - // Element ABI type: void* for ref types; ABI struct for complex/blittable structs; - // primitive ABI otherwise. (Same categorization as the ConvertToManaged_ path.) - string elementAbi = sza.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(sza.BaseType) || sza.BaseType.IsObject() - ? "void*" - : context.AbiTypeShapeResolver.IsComplexStruct(sza.BaseType) - ? AbiTypeHelpers.GetAbiStructTypeName(writer, context, sza.BaseType) - : context.AbiTypeShapeResolver.IsBlittableStruct(sza.BaseType) - ? AbiTypeHelpers.GetBlittableStructAbiType(writer, context, sza.BaseType) - : AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, sza.BaseType); - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(sza.BaseType, TypedefNameType.Projected); - - _ = elementInteropArg; + // Element ABI type: same dispatch as the ConvertToManaged_ path. + string elementAbi = AbiTypeHelpers.GetArrayElementAbiType(context, sza.BaseType); string marshallerPath = ArrayElementEncoder.GetArrayMarshallerInteropPath(sza.BaseType); - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Free")] - static extern void Free_{{localName}}([UnsafeAccessorType("{{marshallerPath}}")] object _, uint length, {{elementAbi}}* data); - - Free_{{localName}}(null, __{{localName}}_length, __{{localName}}_data); - """, isMultiline: true); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "Free", + returnType: "void", + functionName: $"Free_{localName}", + interopType: marshallerPath, + parameterList: $", uint length, {elementAbi}* data"); + writer.WriteLine(); + writer.WriteLine($"Free_{localName}(null, __{localName}_length, __{localName}_data);"); } // 4. Free return value (__retval) — emitted last to match truth ordering. if (returnIsString) { - writer.WriteLine(" HStringMarshaller.Free(__retval);"); + writer.WriteLine("HStringMarshaller.Free(__retval);"); } else if (returnIsRefType) { - writer.WriteLine(" WindowsRuntimeUnknownMarshaller.Free(__retval);"); + writer.WriteLine("WindowsRuntimeUnknownMarshaller.Free(__retval);"); } - else if (returnIsComplexStruct) + else if (returnIsNonBlittableStruct) { - writer.WriteLine($" {AbiTypeHelpers.GetMarshallerFullName(writer, context, rt!)}.Dispose(__retval);"); + writer.WriteLine($"{AbiTypeHelpers.GetMarshallerFullName(writer, context, rt!)}.Dispose(__retval);"); } - else if (returnIsSystemTypeForCleanup) + else if (facts.ReturnIsSystemTypeForCleanup) { // System.Type return: dispose the ABI.System.Type's HSTRING fields. - writer.WriteLine(" global::ABI.System.TypeMarshaller.Dispose(__retval);"); + writer.WriteLine("global::ABI.System.TypeMarshaller.Dispose(__retval);"); } else if (returnIsReceiveArray) { SzArrayTypeSignature retSz = (SzArrayTypeSignature)rt!; - string elementAbi = retSz.BaseType.IsString() || context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(retSz.BaseType) || retSz.BaseType.IsObject() - ? "void*" - : context.AbiTypeShapeResolver.IsComplexStruct(retSz.BaseType) - ? AbiTypeHelpers.GetAbiStructTypeName(writer, context, retSz.BaseType) - : retSz.BaseType.IsHResultException() - ? "global::ABI.System.Exception" - : context.AbiTypeShapeResolver.IsMappedAbiValueType(retSz.BaseType) - ? AbiTypeHelpers.GetMappedAbiTypeName(retSz.BaseType) - : context.AbiTypeShapeResolver.IsBlittableStruct(retSz.BaseType) - ? AbiTypeHelpers.GetBlittableStructAbiType(writer, context, retSz.BaseType) - : AbiTypeHelpers.GetAbiPrimitiveType(context.Cache, retSz.BaseType); - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(retSz.BaseType, TypedefNameType.Projected); - - _ = elementInteropArg; - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Free")] - static extern void Free_retval([UnsafeAccessorType("{{ArrayElementEncoder.GetArrayMarshallerInteropPath(retSz.BaseType)}}")] object _, uint length, {{elementAbi}}* data); - Free_retval(null, __retval_length, __retval_data); - """, isMultiline: true); - } - - writer.WriteLine(" }"); + string elementAbi = AbiTypeHelpers.GetArrayElementAbiType(context, retSz.BaseType); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "Free", + returnType: "void", + functionName: "Free_retval", + interopType: ArrayElementEncoder.GetArrayMarshallerInteropPath(retSz.BaseType), + parameterList: $", uint length, {elementAbi}* data"); + writer.WriteLine("Free_retval(null, __retval_length, __retval_data);"); + } + } - writer.WriteLine(" }"); + writer.DecreaseIndent(); + writer.WriteLine("}"); + writer.DecreaseIndent(); } /// @@ -1669,7 +1386,8 @@ internal static void EmitAbiMethodBodyIfSimple(IndentedTextWriter writer, Projec /// internal static void EmitParamArgConversion(IndentedTextWriter writer, ProjectionEmitContext context, ParameterInfo p, string? paramNameOverride = null) { - string pname = paramNameOverride ?? p.Parameter.Name ?? "param"; + string pname = paramNameOverride ?? p.GetRawName(); + // bool: ABI is 'bool' directly; pass as-is. if (p.Type is CorLibTypeSignature corlib && corlib.ElementType == ElementType.Boolean) @@ -1685,7 +1403,7 @@ internal static void EmitParamArgConversion(IndentedTextWriter writer, Projectio } // Enums: function pointer signature uses the projected enum type, so pass directly. - else if (context.AbiTypeShapeResolver.IsEnumType(p.Type)) + else if (context.AbiTypeKindResolver.IsEnumType(p.Type)) { writer.Write(pname); } @@ -1694,4 +1412,4 @@ internal static void EmitParamArgConversion(IndentedTextWriter writer, Projectio writer.Write(pname); } } -} +} \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Factories/AbiStructFactory.cs b/src/WinRT.Projection.Writer/Factories/AbiStructFactory.cs index 972f03e4f0..076d6d8200 100644 --- a/src/WinRT.Projection.Writer/Factories/AbiStructFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/AbiStructFactory.cs @@ -3,6 +3,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; @@ -37,20 +38,16 @@ public static void WriteAbiStruct(IndentedTextWriter writer, ProjectionEmitConte // In component mode emit the [WindowsRuntimeMetadataTypeName]/[WindowsRuntimeMappedType] // attribute pair; otherwise emit the [ComWrappersMarshaller] attribute. Both branches // then emit [WindowsRuntimeClassName] + the struct definition with public ABI fields. - if (context.Settings.Component) - { - MetadataAttributeFactory.WriteWinRTMetadataTypeNameAttribute(writer, context, type); - MetadataAttributeFactory.WriteWinRTMappedTypeAttribute(writer, context, type); - } - else - { - MetadataAttributeFactory.WriteComWrapperMarshallerAttribute(writer, context, type); - } - - MetadataAttributeFactory.WriteValueTypeWinRTClassNameAttribute(writer, context, type); - writer.Write($"{context.Settings.InternalAccessibility} unsafe struct "); - writer.Write(IdentifierEscaping.StripBackticks(type.Name?.Value ?? string.Empty)); - writer.WriteLine(); + string nameStripped = type.GetStrippedName(); + string marshallerOrTypeAttrs = context.Settings.Component + ? $"{MetadataAttributeFactory.WriteWinRTMetadataTypeNameAttribute(context, type).Format()}\n{MetadataAttributeFactory.WriteWinRTMappedTypeAttribute(context, type).Format()}" + : MetadataAttributeFactory.WriteComWrapperMarshallerAttribute(context, type).Format(); + WriteValueTypeWinRTClassNameAttributeCallback valueTypeAttr = MetadataAttributeFactory.WriteValueTypeWinRTClassNameAttribute(context, type); + writer.WriteLine(isMultiline: true, $$""" + {{marshallerOrTypeAttrs}} + {{valueTypeAttr}} + public unsafe struct {{nameStripped}} + """); using (writer.WriteBlock()) { foreach (FieldDefinition field in type.Fields) @@ -61,31 +58,8 @@ public static void WriteAbiStruct(IndentedTextWriter writer, ProjectionEmitConte } TypeSignature ft = field.Signature.FieldType; - writer.Write("public "); - // Truth uses void* for string and Nullable fields, the ABI type for mapped value - // types (DateTime/TimeSpan), and the projected type for everything else (including - // enums and bool — their C# layout matches the WinRT ABI directly). - if (ft.IsString() || AbiTypeHelpers.TryGetNullablePrimitiveMarshallerName(ft, out _)) - { - writer.Write("void*"); - } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(ft)) - { - writer.Write(AbiTypeHelpers.GetMappedAbiTypeName(ft)); - } - else if (ft is TypeDefOrRefSignature tdr - && AbiTypeHelpers.TryResolveStructTypeDef(context.Cache, tdr) is TypeDefinition fieldTd - && TypeCategorization.GetCategory(fieldTd) == TypeCategory.Struct - && !AbiTypeHelpers.IsTypeBlittable(context.Cache, fieldTd)) - { - TypedefNameWriter.WriteTypedefName(writer, context, fieldTd, TypedefNameType.ABI, false); - } - else - { - MethodFactory.WriteProjectedSignature(writer, context, ft, false); - } - - writer.WriteLine($" {field.Name?.Value ?? string.Empty};"); + string fieldType = GetAbiFieldType(context, ft); + writer.WriteLine($"public {fieldType} {field.Name?.Value};"); } } writer.WriteLine(); @@ -100,4 +74,37 @@ public static void WriteAbiStruct(IndentedTextWriter writer, ProjectionEmitConte StructEnumMarshallerFactory.WriteStructEnumMarshallerClass(writer, context, type); ReferenceImplFactory.WriteReferenceImpl(writer, context, type); } + + /// + /// Returns the projected ABI field type for an unblittable / unmapped struct field. + /// Truth uses void* for string and Nullable<T> fields, the mapped ABI + /// type for mapped value types (DateTime/TimeSpan), the ABI typedef for nested non-blittable + /// structs, and the projected C# type for everything else (including enums and bool — their + /// C# layout matches the WinRT ABI directly). + /// + private static string GetAbiFieldType(ProjectionEmitContext context, TypeSignature ft) + { + if (ft.IsString() || AbiTypeHelpers.TryGetNullablePrimitiveMarshallerName(ft, out _)) + { + return "void*"; + } + + if (context.AbiTypeKindResolver.IsMappedAbiValueType(ft)) + { + return AbiTypeHelpers.GetMappedAbiTypeName(ft); + } + + if (ft is TypeDefOrRefSignature tdr + && AbiTypeHelpers.TryResolveStructTypeDef(context.Cache, tdr) is TypeDefinition fieldTd + && fieldTd.IsStruct + && !AbiTypeHelpers.IsTypeBlittable(context.Cache, fieldTd)) + { + return TypedefNameWriter.WriteTypedefName(context, fieldTd, TypedefNameType.ABI, false).Format(); + } + + // Default: emit the projected C# type via the signature writer. + using IndentedTextWriterOwner owner = IndentedTextWriterPool.GetOrCreate(); + MethodFactory.WriteProjectedSignature(owner.Writer, context, ft, false); + return owner.Writer.ToString(); + } } diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/EmitMarshallerConvertToManagedCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/EmitMarshallerConvertToManagedCallback.cs new file mode 100644 index 0000000000..694777f34a --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/EmitMarshallerConvertToManagedCallback.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct EmitMarshallerConvertToManagedCallback( + ProjectionEmitContext context, + TypeSignature sig, + string argName) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + AbiMethodBodyFactory.EmitMarshallerConvertToManaged(writer, context, sig, argName); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/EmitMarshallerConvertToUnmanagedCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/EmitMarshallerConvertToUnmanagedCallback.cs new file mode 100644 index 0000000000..3d50fa4523 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/EmitMarshallerConvertToUnmanagedCallback.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct EmitMarshallerConvertToUnmanagedCallback( + ProjectionEmitContext context, + TypeSignature sig, + string argName) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + AbiMethodBodyFactory.EmitMarshallerConvertToUnmanaged(writer, context, sig, argName); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteAbiParameterTypesPointerCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteAbiParameterTypesPointerCallback.cs new file mode 100644 index 0000000000..18a61d5439 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteAbiParameterTypesPointerCallback.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteAbiParameterTypesPointerCallback( + ProjectionEmitContext context, + MethodSignatureInfo sig, + bool includeParamNames) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + AbiInterfaceFactory.WriteAbiParameterTypesPointer(writer, context, sig, includeParamNames); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteAbiTypeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteAbiTypeCallback.cs new file mode 100644 index 0000000000..56ed8d5b00 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteAbiTypeCallback.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteAbiTypeCallback( + ProjectionEmitContext context, + TypeSemantics semantics) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + AbiTypeWriter.WriteAbiType(writer, context, semantics); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteCallArgumentsCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteCallArgumentsCallback.cs new file mode 100644 index 0000000000..056ddcf2cb --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteCallArgumentsCallback.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteCallArgumentsCallback( + ProjectionEmitContext context, + MethodSignatureInfo sig, + bool leadingComma) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + MethodFactory.WriteCallArguments(writer, context, sig, leadingComma); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteComWrapperMarshallerAttributeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteComWrapperMarshallerAttributeCallback.cs new file mode 100644 index 0000000000..7ff65f2978 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteComWrapperMarshallerAttributeCallback.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteComWrapperMarshallerAttributeCallback( + ProjectionEmitContext context, + TypeDefinition type) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + MetadataAttributeFactory.WriteComWrapperMarshallerAttributeBody(writer, context, type); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteEscapedIdentifierCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteEscapedIdentifierCallback.cs new file mode 100644 index 0000000000..b26ad786e8 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteEscapedIdentifierCallback.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteEscapedIdentifierCallback(string identifier) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + IdentifierEscaping.WriteEscapedIdentifier(writer, identifier); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteEventTypeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteEventTypeCallback.cs new file mode 100644 index 0000000000..053f22b6de --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteEventTypeCallback.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteEventTypeCallback( + ProjectionEmitContext context, + EventDefinition evt, + GenericInstanceTypeSignature? currentInstance) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + TypedefNameWriter.WriteEventType(writer, context, evt, currentInstance); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteFactoryMethodParametersCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteFactoryMethodParametersCallback.cs new file mode 100644 index 0000000000..706322a9e7 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteFactoryMethodParametersCallback.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteFactoryMethodParametersCallback( + ProjectionEmitContext context, + MethodDefinition method, + bool includeTypes) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + ComponentFactory.WriteFactoryMethodParameters(writer, context, method, includeTypes); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteFactoryReturnTypeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteFactoryReturnTypeCallback.cs new file mode 100644 index 0000000000..884daa524d --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteFactoryReturnTypeCallback.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteFactoryReturnTypeCallback( + ProjectionEmitContext context, + MethodDefinition method) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + ComponentFactory.WriteFactoryReturnType(writer, context, method); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteGuidAttributeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteGuidAttributeCallback.cs new file mode 100644 index 0000000000..da3a067f00 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteGuidAttributeCallback.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteGuidAttributeCallback(TypeDefinition type) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + InterfaceFactory.WriteGuidAttribute(writer, type); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteGuidBytesCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteGuidBytesCallback.cs new file mode 100644 index 0000000000..e2fe1d92e2 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteGuidBytesCallback.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteGuidBytesCallback(Guid guid) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + IidExpressionGenerator.WriteGuidBytes(writer, guid); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidExpressionCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidExpressionCallback.cs new file mode 100644 index 0000000000..d3f1c16c86 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidExpressionCallback.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteIidExpressionCallback( + ProjectionEmitContext context, + ITypeDefOrRef ifaceType) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + ObjRefNameGenerator.WriteIidExpression(writer, context, ifaceType); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidGuidPropertyNameCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidGuidPropertyNameCallback.cs new file mode 100644 index 0000000000..cc740db787 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidGuidPropertyNameCallback.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteIidGuidPropertyNameCallback( + ProjectionEmitContext context, + TypeDefinition type) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + IidExpressionGenerator.WriteIidGuidPropertyName(writer, context, type); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidGuidReferenceCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidGuidReferenceCallback.cs new file mode 100644 index 0000000000..c389655b55 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidGuidReferenceCallback.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteIidGuidReferenceCallback(ProjectionEmitContext context, TypeDefinition type) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + AbiTypeHelpers.WriteIidGuidReference(writer, context, type); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidReferenceExpressionCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidReferenceExpressionCallback.cs new file mode 100644 index 0000000000..57a0b5e627 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidReferenceExpressionCallback.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteIidReferenceExpressionCallback(TypeDefinition type) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + ObjRefNameGenerator.WriteIidReferenceExpression(writer, type); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidReferenceGuidPropertyNameCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidReferenceGuidPropertyNameCallback.cs new file mode 100644 index 0000000000..5434c321ce --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteIidReferenceGuidPropertyNameCallback.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteIidReferenceGuidPropertyNameCallback( + ProjectionEmitContext context, + TypeDefinition type) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + IidExpressionGenerator.WriteIidReferenceGuidPropertyName(writer, context, type); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteInterfaceTypeNameForCcwCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteInterfaceTypeNameForCcwCallback.cs new file mode 100644 index 0000000000..7cf5435605 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteInterfaceTypeNameForCcwCallback.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteInterfaceTypeNameForCcwCallback( + ProjectionEmitContext context, + ITypeDefOrRef ifaceType) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + ClassMembersFactory.WriteInterfaceTypeNameForCcw(writer, context, ifaceType); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteParameterListCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteParameterListCallback.cs new file mode 100644 index 0000000000..98aac65c3a --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteParameterListCallback.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteParameterListCallback( + ProjectionEmitContext context, + MethodSignatureInfo sig) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + MethodFactory.WriteParameterList(writer, context, sig); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteParameterNameWithModifierCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteParameterNameWithModifierCallback.cs new file mode 100644 index 0000000000..f605559e8d --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteParameterNameWithModifierCallback.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteParameterNameWithModifierCallback( + ProjectionEmitContext context, + ParameterInfo parameter) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + ClassMembersFactory.WriteParameterNameWithModifier(writer, context, parameter); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WritePlatformAttributeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WritePlatformAttributeCallback.cs new file mode 100644 index 0000000000..390feb82b7 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WritePlatformAttributeCallback.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WritePlatformAttributeCallback( + ProjectionEmitContext context, + IHasCustomAttribute member) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + CustomAttributeFactory.WritePlatformAttributeBody(writer, context, member); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectedSignatureCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectedSignatureCallback.cs new file mode 100644 index 0000000000..27e42b9503 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectedSignatureCallback.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteProjectedSignatureCallback( + ProjectionEmitContext context, + TypeSignature typeSig, + bool isParameter) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + MethodFactory.WriteProjectedSignature(writer, context, typeSig, isParameter); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectionParameterCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectionParameterCallback.cs new file mode 100644 index 0000000000..0db1f9aba9 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectionParameterCallback.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteProjectionParameterCallback( + ProjectionEmitContext context, + ParameterInfo parameter) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + MethodFactory.WriteProjectionParameter(writer, context, parameter); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectionParameterTypeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectionParameterTypeCallback.cs new file mode 100644 index 0000000000..eb17fe33c3 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectionParameterTypeCallback.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteProjectionParameterTypeCallback( + ProjectionEmitContext context, + ParameterInfo parameter) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + MethodFactory.WriteProjectionParameterType(writer, context, parameter); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectionReturnTypeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectionReturnTypeCallback.cs new file mode 100644 index 0000000000..2400d1a0bf --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectionReturnTypeCallback.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteProjectionReturnTypeCallback( + ProjectionEmitContext context, + MethodSignatureInfo sig) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + MethodFactory.WriteProjectionReturnType(writer, context, sig); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectionTypeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectionTypeCallback.cs new file mode 100644 index 0000000000..4d8f587f9a --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteProjectionTypeCallback.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteProjectionTypeCallback( + ProjectionEmitContext context, + TypeSemantics semantics) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + TypedefNameWriter.WriteProjectionType(writer, context, semantics); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypeCustomAttributesCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypeCustomAttributesCallback.cs new file mode 100644 index 0000000000..9e3db06d65 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypeCustomAttributesCallback.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteTypeCustomAttributesCallback( + ProjectionEmitContext context, + TypeDefinition type, + bool enablePlatformAttrib) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + CustomAttributeFactory.WriteTypeCustomAttributesBody(writer, context, type, enablePlatformAttrib); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypeInheritanceCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypeInheritanceCallback.cs new file mode 100644 index 0000000000..0c9a67ff7e --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypeInheritanceCallback.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteTypeInheritanceCallback( + ProjectionEmitContext context, + TypeDefinition type, + bool includeExclusiveInterface, + bool includeWindowsRuntimeObject) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + InterfaceFactory.WriteTypeInheritance(writer, context, type, includeExclusiveInterface, includeWindowsRuntimeObject); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypeNameCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypeNameCallback.cs new file mode 100644 index 0000000000..a4d400ec87 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypeNameCallback.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteTypeNameCallback( + ProjectionEmitContext context, + TypeSemantics semantics, + TypedefNameType nameType, + bool forceWriteNamespace) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + TypedefNameWriter.WriteTypeName(writer, context, semantics, nameType, forceWriteNamespace); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypeParamsCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypeParamsCallback.cs new file mode 100644 index 0000000000..15c08de11b --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypeParamsCallback.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteTypeParamsCallback(TypeDefinition type) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + TypedefNameWriter.WriteTypeParams(writer, type); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypedefNameCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypedefNameCallback.cs new file mode 100644 index 0000000000..3aa41e5aa1 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypedefNameCallback.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteTypedefNameCallback( + ProjectionEmitContext context, + TypeDefinition type, + TypedefNameType nameType, + bool forceWriteNamespace) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + TypedefNameWriter.WriteTypedefName(writer, context, type, nameType, forceWriteNamespace); + } +} + diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypedefNameWithTypeParamsCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypedefNameWithTypeParamsCallback.cs new file mode 100644 index 0000000000..194b90b7f1 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteTypedefNameWithTypeParamsCallback.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +internal readonly struct WriteTypedefNameWithTypeParamsCallback( + ProjectionEmitContext context, + TypeDefinition type, + TypedefNameType nameType, + bool forceWriteNamespace) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + TypedefNameWriter.WriteTypedefNameWithTypeParams(writer, context, type, nameType, forceWriteNamespace); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteValueTypeWinRTClassNameAttributeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteValueTypeWinRTClassNameAttributeCallback.cs new file mode 100644 index 0000000000..3eb4f4bd29 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteValueTypeWinRTClassNameAttributeCallback.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteValueTypeWinRTClassNameAttributeCallback( + ProjectionEmitContext context, + TypeDefinition type) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + MetadataAttributeFactory.WriteValueTypeWinRTClassNameAttributeBody(writer, context, type); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteWinRTMappedTypeAttributeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteWinRTMappedTypeAttributeCallback.cs new file mode 100644 index 0000000000..1ac346cdc5 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteWinRTMappedTypeAttributeCallback.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteWinRTMappedTypeAttributeCallback( + ProjectionEmitContext context, + TypeDefinition type) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + MetadataAttributeFactory.WriteWinRTMappedTypeAttributeBody(writer, context, type); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteWinRTMetadataAttributeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteWinRTMetadataAttributeCallback.cs new file mode 100644 index 0000000000..8f645a0478 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteWinRTMetadataAttributeCallback.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteWinRTMetadataAttributeCallback( + TypeDefinition type, + MetadataCache cache) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + MetadataAttributeFactory.WriteWinRTMetadataAttributeBody(writer, type, cache); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteWinRTMetadataTypeNameAttributeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteWinRTMetadataTypeNameAttributeCallback.cs new file mode 100644 index 0000000000..38f870b254 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteWinRTMetadataTypeNameAttributeCallback.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteWinRTMetadataTypeNameAttributeCallback( + ProjectionEmitContext context, + TypeDefinition type) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + MetadataAttributeFactory.WriteWinRTMetadataTypeNameAttributeBody(writer, context, type); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/Callbacks/WriteWinRTReferenceTypeAttributeCallback.cs b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteWinRTReferenceTypeAttributeCallback.cs new file mode 100644 index 0000000000..8c920cd227 --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/Callbacks/WriteWinRTReferenceTypeAttributeCallback.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories.Callbacks; + +/// +/// +/// +internal readonly struct WriteWinRTReferenceTypeAttributeCallback( + ProjectionEmitContext context, + TypeDefinition type) : IIndentedTextWriterCallback +{ + /// + public void Write(IndentedTextWriter writer) + { + MetadataAttributeFactory.WriteWinRTReferenceTypeAttributeBody(writer, context, type); + } +} diff --git a/src/WinRT.Projection.Writer/Factories/ClassFactory.cs b/src/WinRT.Projection.Writer/Factories/ClassFactory.cs index 713c0fe153..7fc416dc86 100644 --- a/src/WinRT.Projection.Writer/Factories/ClassFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/ClassFactory.cs @@ -5,12 +5,12 @@ using System.Collections.Generic; using System.Globalization; using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; using WindowsRuntime.ProjectionWriter.Models; using WindowsRuntime.ProjectionWriter.Writers; -using static WindowsRuntime.ProjectionWriter.References.ProjectionNames; using static WindowsRuntime.ProjectionWriter.References.WellKnownAttributeNames; using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; @@ -33,24 +33,7 @@ public static bool IsFastAbiClass(TypeDefinition type) { // Fast ABI is enabled when the type is marked [FastAbi]. (CsWinRT 3.0 has no // netstandard_compat gate -- it was always false in the C# port.) - return type.HasAttribute(WindowsFoundationMetadata, FastAbiAttribute); - } - - /// - /// Writes the class modifiers ('static '/'sealed '). - /// - public static void WriteClassModifiers(IndentedTextWriter writer, TypeDefinition type) - { - if (TypeCategorization.IsStatic(type)) - { - writer.Write("static "); - return; - } - - if (type.IsSealed) - { - writer.Write("sealed "); - } + return type.HasWindowsFoundationMetadataAttribute(FastAbiAttribute); } /// @@ -106,14 +89,14 @@ public static bool IsFastAbiOtherInterface(MetadataCache cache, TypeDefinition i return false; } - if (fastAbi.Value.Default is not null && InterfacesEqual(fastAbi.Value.Default, iface)) + if (fastAbi.Value.Default is not null && AbiTypeHelpers.InterfacesEqualByName(fastAbi.Value.Default, iface)) { return false; } foreach (TypeDefinition other in fastAbi.Value.Others) { - if (InterfacesEqual(other, iface)) + if (AbiTypeHelpers.InterfacesEqualByName(other, iface)) { return true; } @@ -121,36 +104,6 @@ public static bool IsFastAbiOtherInterface(MetadataCache cache, TypeDefinition i return false; } - /// - /// Returns true if is the default interface of a fast-abi class. - /// - public static bool IsFastAbiDefaultInterface(MetadataCache cache, TypeDefinition iface) - { - (TypeDefinition Class, TypeDefinition? Default, List Others)? fastAbi = GetFastAbiClassForInterface(cache, iface); - - if (fastAbi is null) - { - return false; - } - - return fastAbi.Value.Default is not null && InterfacesEqual(fastAbi.Value.Default, iface); - } - - private static bool InterfacesEqual(TypeDefinition a, TypeDefinition b) - { - if (a == b) - { - return true; - } - - return (a.Namespace?.Value ?? string.Empty) == (b.Namespace?.Value ?? string.Empty) - && (a.Name?.Value ?? string.Empty) == (b.Name?.Value ?? string.Empty); - } - - // We don't have direct access to the active Settings from a static helper that only takes - // a TypeDefinition. The fast-abi flag is purely determined by the [FastAbiAttribute] (the - // netstandard_compat gate is always false in CsWinRT 3.0 -- the flag has been removed). - /// /// Returns the [Default] interface and the [ExclusiveTo] interfaces (sorted) for fast ABI. /// @@ -177,7 +130,7 @@ public static (TypeDefinition? DefaultInterface, System.Collections.Generic.List { defaultIface = ifaceTd; } - else if (TypeCategorization.IsExclusiveTo(ifaceTd)) + else if (ifaceTd.IsExclusiveTo) { exclusiveIfaces.Add(ifaceTd); } @@ -190,8 +143,8 @@ public static (TypeDefinition? DefaultInterface, System.Collections.Generic.List // 4. Type namespace and name (ascending) exclusiveIfaces.Sort((a, b) => { - int aPrev = -CountAttributes(a, WindowsFoundationMetadata, "PreviousContractVersionAttribute"); - int bPrev = -CountAttributes(b, WindowsFoundationMetadata, "PreviousContractVersionAttribute"); + int aPrev = -a.CountAttributes(WindowsFoundationMetadata, "PreviousContractVersionAttribute"); + int bPrev = -b.CountAttributes(WindowsFoundationMetadata, "PreviousContractVersionAttribute"); if (aPrev != bPrev) { @@ -214,34 +167,19 @@ public static (TypeDefinition? DefaultInterface, System.Collections.Generic.List return aV.Value.CompareTo(bV.Value); } - string aNs = a.Namespace?.Value ?? string.Empty; - string bNs = b.Namespace?.Value ?? string.Empty; + (string aNs, string aName) = a.Names(); + (string bNs, string bName) = b.Names(); if (aNs != bNs) { return StringComparer.Ordinal.Compare(aNs, bNs); } - return StringComparer.Ordinal.Compare(a.Name?.Value ?? string.Empty, b.Name?.Value ?? string.Empty); + return StringComparer.Ordinal.Compare(aName, bName); }); return (defaultIface, exclusiveIfaces); } - private static int CountAttributes(IHasCustomAttribute member, string ns, string name) - { - int count = 0; - for (int i = 0; i < member.CustomAttributes.Count; i++) - { - CustomAttribute attr = member.CustomAttributes[i]; - ITypeDefOrRef? type = attr.Constructor?.DeclaringType; - - if (type is not null && type.Namespace == ns && type.Name == name) - { - count++; - } - } - return count; - } /// /// Returns the GC-pressure cost (in bytes) declared by [GCPressureAttribute] on , or 0 if not annotated. /// @@ -252,7 +190,7 @@ public static int GetGcPressureAmount(TypeDefinition type) return 0; } - CustomAttribute? attr = type.GetAttribute(WindowsFoundationMetadata, "GCPressureAttribute"); + CustomAttribute? attr = type.GetWindowsFoundationMetadataAttribute("GCPressureAttribute"); if (attr is null || attr.Signature is null) { @@ -289,12 +227,14 @@ public static void WriteStaticClass(IndentedTextWriter writer, ProjectionEmitCon { using (context.EnterPlatformSuppressionScope(string.Empty)) { - MetadataAttributeFactory.WriteWinRTMetadataAttribute(writer, type, context.Cache); - CustomAttributeFactory.WriteTypeCustomAttributes(writer, context, type, true); - writer.Write($"{context.Settings.InternalAccessibility} static class "); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, false); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.WriteLine(); + WriteWinRTMetadataAttributeCallback metadataAttr = MetadataAttributeFactory.WriteWinRTMetadataAttribute(type, context.Cache); + WriteTypeCustomAttributesCallback customAttrs = CustomAttributeFactory.WriteTypeCustomAttributes(context, type, true); + WriteTypedefNameWithTypeParamsCallback name = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.Projected, false); + writer.WriteLine(isMultiline: true, $$""" + {{metadataAttr}} + {{customAttrs}} + public static class {{name}} + """); using (writer.WriteBlock()) { WriteStaticClassMembers(writer, context, type); @@ -307,17 +247,13 @@ public static void WriteStaticClass(IndentedTextWriter writer, ProjectionEmitCon /// public static void WriteStaticClassMembers(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - if (context.Cache is null) - { - return; - } - // Per-property accessor state (origin tracking for getter/setter) Dictionary properties = []; + // Track the static factory ifaces we've emitted objref fields for (to dedupe) HashSet emittedObjRefs = []; - string runtimeClassFullName = (type.Namespace?.Value ?? string.Empty) + "." + (type.Name?.Value ?? string.Empty); + string runtimeClassFullName = type.FullName ?? string.Empty; foreach (KeyValuePair kv in AttributedTypes.Get(type, context.Cache)) { @@ -332,13 +268,9 @@ public static void WriteStaticClassMembers(IndentedTextWriter writer, Projection // Compute the objref name for this static factory interface. string objRef = ObjRefNameGenerator.GetObjRefName(context, staticIface); - // Compute the ABI Methods static class name (e.g. "global::ABI.Windows.System.ILauncherStaticsMethods") - string abiClass = TypedefNameWriter.WriteTypedefName(context, staticIface, TypedefNameType.StaticAbiClass, true); - if (!abiClass.StartsWith(GlobalPrefix, StringComparison.Ordinal)) - { - abiClass = GlobalPrefix + abiClass; - } + // Compute the ABI Methods static class name (e.g. "global::ABI.Windows.System.ILauncherStaticsMethods") + string abiClass = TypedefNameWriter.WriteTypedefName(context, staticIface, TypedefNameType.StaticAbiClass, true).Format(); // Emit the lazy static objref field (mirrors truth's pattern) once per static iface. if (emittedObjRefs.Add(objRef)) @@ -348,29 +280,20 @@ public static void WriteStaticClassMembers(IndentedTextWriter writer, Projection // Compute the platform attribute string from the static factory interface's // [ContractVersion] attribute - string platformAttribute = CustomAttributeFactory.WritePlatformAttribute(context, staticIface); + string platformAttribute = CustomAttributeFactory.GetPlatformAttribute(context, staticIface); // Methods - foreach (MethodDefinition method in staticIface.Methods) + foreach (MethodDefinition method in staticIface.GetNonSpecialMethods()) { - if (method.IsSpecial()) - { - continue; - } - MethodSignatureInfo sig = new(method); - string mname = method.Name?.Value ?? string.Empty; + string mname = method.GetRawName(); writer.WriteLine(); - if (!string.IsNullOrEmpty(platformAttribute)) - { - writer.Write(platformAttribute); - } + writer.WriteIf(!string.IsNullOrEmpty(platformAttribute), platformAttribute); - writer.Write("public static "); - MethodFactory.WriteProjectionReturnType(writer, context, sig); - writer.Write($" {mname}("); - MethodFactory.WriteParameterList(writer, context, sig); + WriteProjectionReturnTypeCallback ret = MethodFactory.WriteProjectionReturnType(context, sig); + WriteParameterListCallback parms = MethodFactory.WriteParameterList(context, sig); + writer.Write($"public static {ret} {mname}({parms}"); if (context.Settings.ReferenceProjection) { @@ -379,56 +302,48 @@ public static void WriteStaticClassMembers(IndentedTextWriter writer, Projection } else { - writer.Write($") => {abiClass}.{mname}({objRef}"); - for (int i = 0; i < sig.Parameters.Count; i++) - { - writer.Write(", "); - ClassMembersFactory.WriteParameterNameWithModifier(writer, context, sig.Parameters[i]); - } - writer.WriteLine(");"); + WriteCallArgumentsCallback args = MethodFactory.WriteCallArguments(context, sig, leadingComma: true); + writer.WriteLine($") => {abiClass}.{mname}({objRef}{args});"); } } // Events: dispatch via static ABI class which returns an event source. foreach (EventDefinition evt in staticIface.Events) { - string evtName = evt.Name?.Value ?? string.Empty; + string evtName = evt.GetRawName(); writer.WriteLine(); - if (!string.IsNullOrEmpty(platformAttribute)) - { - writer.Write(platformAttribute); - } + writer.WriteIf(!string.IsNullOrEmpty(platformAttribute), platformAttribute); + + WriteEventTypeCallback eventType = TypedefNameWriter.WriteEventType(context, evt); - writer.Write("public static event "); - TypedefNameWriter.WriteEventType(writer, context, evt); - writer.WriteLine($$""" - {{evtName}} - { - """, isMultiline: true); if (context.Settings.ReferenceProjection) { - // event accessor bodies become 'throw null' in reference projection mode. - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, $$""" + public static event {{eventType}} {{evtName}} + { add => throw null; remove => throw null; - """, isMultiline: true); + } + """); } else { - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" + public static event {{eventType}} {{evtName}} + { add => {{abiClass}}.{{evtName}}({{objRef}}, {{objRef}}).Subscribe(value); remove => {{abiClass}}.{{evtName}}({{objRef}}, {{objRef}}).Unsubscribe(value); - """, isMultiline: true); + } + """); } - writer.WriteLine("}"); } // Properties (merge getter/setter across interfaces, tracking origin per accessor) foreach (PropertyDefinition prop in staticIface.Properties) { - string propName = prop.Name?.Value ?? string.Empty; - (MethodDefinition? getter, MethodDefinition? setter) = prop.GetPropertyMethods(); + string propName = prop.GetRawName(); + (MethodDefinition? getter, MethodDefinition? setter) = prop.GetMethods(); string propType = InterfaceFactory.WritePropType(context, prop); if (!properties.TryGetValue(propName, out StaticPropertyAccessorState? state)) @@ -463,6 +378,7 @@ public static void WriteStaticClassMembers(IndentedTextWriter writer, Projection { StaticPropertyAccessorState s = kv.Value; writer.WriteLine(); + // when getter and setter platforms match; otherwise emit per-accessor. string getterPlat = s.GetterPlatformAttribute; string setterPlat = s.SetterPlatformAttribute; @@ -476,12 +392,10 @@ public static void WriteStaticClassMembers(IndentedTextWriter writer, Projection setterPlat = string.Empty; } - if (!string.IsNullOrEmpty(propertyPlat)) - { - writer.Write(propertyPlat); - } + writer.WriteIf(!string.IsNullOrEmpty(propertyPlat), propertyPlat); writer.Write($"public static {s.PropTypeText} {kv.Key}"); + // Getter-only -> expression body; otherwise -> accessor block (matches truth). // In ref mode, all accessor bodies emit '=> throw null;' bool getterOnly = s.HasGetter && !s.HasSetter; @@ -504,10 +418,7 @@ public static void WriteStaticClassMembers(IndentedTextWriter writer, Projection { if (s.HasGetter) { - if (!string.IsNullOrEmpty(getterPlat)) - { - writer.Write(getterPlat); - } + writer.WriteIf(!string.IsNullOrEmpty(getterPlat), getterPlat); if (context.Settings.ReferenceProjection) { @@ -521,10 +432,7 @@ public static void WriteStaticClassMembers(IndentedTextWriter writer, Projection if (s.HasSetter) { - if (!string.IsNullOrEmpty(setterPlat)) - { - writer.Write(setterPlat); - } + writer.WriteIf(!string.IsNullOrEmpty(setterPlat), setterPlat); if (context.Settings.ReferenceProjection) { @@ -547,38 +455,30 @@ public static void WriteStaticClassMembers(IndentedTextWriter writer, Projection internal static void WriteStaticFactoryObjRef(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition staticIface, string runtimeClassFullName, string objRefName) { writer.WriteLine(); - writer.WriteLine($$""" - private static WindowsRuntimeObjectReference {{objRefName}} - { - """, isMultiline: true); + if (context.Settings.ReferenceProjection) { - // the static factory objref getter body is just 'throw null;'. - writer.WriteLine(""" - get - { - throw null; - } - } - """, isMultiline: true); - return; + writer.WriteLine($"private static WindowsRuntimeObjectReference {objRefName} => throw null;"); } - writer.Write($$""" - get + else + { + WriteIidExpressionCallback iid = ObjRefNameGenerator.WriteIidExpression(context, staticIface); + + writer.WriteLine(isMultiline: true, $$""" + private static WindowsRuntimeObjectReference {{objRefName}} { - var __{{objRefName}} = field; - if (__{{objRefName}} != null && __{{objRefName}}.IsInCurrentContext) + get { - return __{{objRefName}}; + var __{{objRefName}} = field; + if (__{{objRefName}} != null && __{{objRefName}}.IsInCurrentContext) + { + return __{{objRefName}}; + } + return field = WindowsRuntimeObjectReference.GetActivationFactory("{{runtimeClassFullName}}", {{iid}}); } - return field = WindowsRuntimeObjectReference.GetActivationFactory("{{runtimeClassFullName}}", - """, isMultiline: true); - ObjRefNameGenerator.WriteIidExpression(writer, context, staticIface); - writer.WriteLine(""" - ); } - } - """, isMultiline: true); + """); + } } /// @@ -591,7 +491,7 @@ public static void WriteClass(IndentedTextWriter writer, ProjectionEmitContext c return; } - if (TypeCategorization.IsStatic(type)) + if (type.IsStatic) { WriteStaticClass(writer, context, type); return; @@ -607,22 +507,25 @@ public static void WriteClass(IndentedTextWriter writer, ProjectionEmitContext c private static void WriteClassCore(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string typeName = type.Name?.Value ?? string.Empty; + string typeName = type.GetRawName(); int gcPressure = GetGcPressureAmount(type); - // Header attributes - writer.WriteLine(); - MetadataAttributeFactory.WriteWinRTMetadataAttribute(writer, type, context.Cache); - CustomAttributeFactory.WriteTypeCustomAttributes(writer, context, type, true); - MetadataAttributeFactory.WriteComWrapperMarshallerAttribute(writer, context, type); - writer.Write($"{(context.Settings.Internal ? "internal" : "public")} "); - WriteClassModifiers(writer, type); + // Header attributes + class declaration as a single multiline template. + WriteWinRTMetadataAttributeCallback metadataAttr = MetadataAttributeFactory.WriteWinRTMetadataAttribute(type, context.Cache); + WriteTypeCustomAttributesCallback customAttrs = CustomAttributeFactory.WriteTypeCustomAttributes(context, type, true); + WriteComWrapperMarshallerAttributeCallback comWrappersAttr = MetadataAttributeFactory.WriteComWrapperMarshallerAttribute(context, type); + // are emitted as plain (non-partial) classes. - writer.Write("class "); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, false); - TypedefNameWriter.WriteTypeParams(writer, type); - InterfaceFactory.WriteTypeInheritance(writer, context, type, false, true); + string modifiers = type.IsStatic ? "static " : type.IsSealed ? "sealed " : ""; + WriteTypedefNameWithTypeParamsCallback name = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.Projected, false); + WriteTypeInheritanceCallback inheritance = InterfaceFactory.WriteTypeInheritance(context, type, false, true); writer.WriteLine(); + writer.WriteLine(isMultiline: true, $$""" + {{metadataAttr}} + {{customAttrs}} + {{comWrappersAttr}} + public {{modifiers}}class {{name}}{{inheritance}} + """); using IndentedTextWriter.Block __classBlock = writer.WriteBlock(); // ObjRef field definitions for each implemented interface. @@ -635,10 +538,10 @@ private static void WriteClassCore(IndentedTextWriter writer, ProjectionEmitCont { string ctorAccess = type.IsSealed ? "internal" : "protected internal"; writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" {{ctorAccess}} {{typeName}}(WindowsRuntimeObjectReference nativeObjectReference) : base(nativeObjectReference) - """, isMultiline: true); + """); using (writer.WriteBlock()) { if (!type.IsSealed) @@ -651,12 +554,12 @@ private static void WriteClassCore(IndentedTextWriter writer, ProjectionEmitCont if (defaultIface is not null) { string defaultObjRefName = ObjRefNameGenerator.GetObjRefName(context, defaultIface); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" if (GetType() == typeof({{typeName}})) { {{defaultObjRefName}} = NativeObjectReference; } - """, isMultiline: true); + """); } } @@ -666,7 +569,7 @@ private static void WriteClassCore(IndentedTextWriter writer, ProjectionEmitCont } } } - else if (context.Cache is not null) + else { // In ref mode, if WriteAttributedTypes will not emit any public constructors, // we need a 'private TypeName() { throw null; }' to suppress the C# compiler's @@ -711,12 +614,12 @@ private static void WriteClassCore(IndentedTextWriter writer, ProjectionEmitCont // Conditional finalizer if (gcPressure > 0) { - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" ~{{typeName}}() { GC.RemoveMemoryPressure({{gcPressure.ToString(CultureInfo.InvariantCulture)}}); } - """, isMultiline: true); + """); } // Class members from interfaces (instance methods, properties, events). @@ -724,22 +627,9 @@ private static void WriteClassCore(IndentedTextWriter writer, ProjectionEmitCont // be emitted BEFORE the public members. if (!context.Settings.ReferenceProjection) { - writer.WriteLine(); - writer.Write("protected override bool HasUnwrappableNativeObjectReference => "); - - if (!type.IsSealed) - { - writer.Write($"GetType() == typeof({typeName});"); - } - else - { - writer.Write("true;"); - } + string unwrappable = type.IsSealed ? "true" : $"GetType() == typeof({typeName});"; - writer.WriteLine(); - writer.WriteLine(); - writer.Write("protected override bool IsOverridableInterface(in Guid iid) => "); - bool firstClause = true; + List overridableClauses = []; foreach (InterfaceImplementation impl in type.Interfaces) { if (!impl.IsOverridable()) @@ -754,38 +644,28 @@ private static void WriteClassCore(IndentedTextWriter writer, ProjectionEmitCont continue; } - if (!firstClause) - { - writer.Write(" || "); - } - - firstClause = false; - ObjRefNameGenerator.WriteIidExpression(writer, context, implRef); - writer.Write(" == iid"); + WriteIidExpressionCallback iid = ObjRefNameGenerator.WriteIidExpression(context, implRef); + overridableClauses.Add($"{iid.Format()} == iid"); } // base call when type has a non-object base class - bool hasBaseClass = type.BaseType is not null - && !(type.BaseType.Namespace?.Value == "System" && type.BaseType.Name?.Value == "Object") - && !(type.BaseType.Namespace?.Value == "WindowsRuntime" && type.BaseType.Name?.Value == "WindowsRuntimeObject"); + bool hasBaseClass = type.HasNonObjectBaseType(); if (hasBaseClass) { - if (!firstClause) - { - writer.Write(" || "); - } - - writer.Write("base.IsOverridableInterface(in iid)"); - firstClause = false; + overridableClauses.Add("base.IsOverridableInterface(in iid)"); } - if (firstClause) - { - writer.Write("false"); - } + string overridableExpr = overridableClauses.Count == 0 + ? "false" + : string.Join(" || ", overridableClauses); + + writer.WriteLine(); + writer.WriteLine(isMultiline: true, $$""" + protected override bool HasUnwrappableNativeObjectReference => {{unwrappable}}{{(type.IsSealed ? ";" : "")}} - writer.WriteLine(";"); + protected override bool IsOverridableInterface(in Guid iid) => {{overridableExpr}}; + """); } ClassMembersFactory.WriteClassMembers(writer, context, type); diff --git a/src/WinRT.Projection.Writer/Factories/ClassMembersFactory.WriteClassMembers.cs b/src/WinRT.Projection.Writer/Factories/ClassMembersFactory.WriteClassMembers.cs index fac644c021..b21ff1516d 100644 --- a/src/WinRT.Projection.Writer/Factories/ClassMembersFactory.WriteClassMembers.cs +++ b/src/WinRT.Projection.Writer/Factories/ClassMembersFactory.WriteClassMembers.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Models; using WindowsRuntime.ProjectionWriter.Writers; @@ -19,12 +20,14 @@ internal static partial class ClassMembersFactory public static void WriteClassMembers(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { HashSet writtenMethods = []; + // For properties: track per-name accessor presence so we can merge get/set across interfaces. // Use insertion-order Dictionary so the per-class property emission order matches the // .winmd metadata definition order order). Dictionary propertyState = []; HashSet writtenEvents = []; HashSet writtenInterfaces = []; + // interface inside WriteInterfaceMembersRecursive (right before that interface's // members), instead of one upfront block. This interleaves the GetInterface() impls // with their corresponding interface body, matching truth's per-interface layout. @@ -34,32 +37,41 @@ public static void WriteClassMembers(IndentedTextWriter writer, ProjectionEmitCo foreach (KeyValuePair kvp in propertyState) { PropertyAccessorState s = kvp.Value; + // For generic-interface properties, emit the UnsafeAccessor static externs above the // property declaration. Note: getter and setter use the same accessor name (because // C# allows method overloading on parameter list for the static externs). if (s.HasGetter && s.GetterIsGeneric && !string.IsNullOrEmpty(s.GetterGenericInteropType)) { writer.WriteLine(); - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "{{kvp.Key}}")] - static extern {{s.GetterPropTypeText}} {{s.GetterGenericAccessorName}}([UnsafeAccessorType("{{s.GetterGenericInteropType}}")] object _, WindowsRuntimeObjectReference thisReference); - """, isMultiline: true); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: kvp.Key, + returnType: s.GetterPropTypeText, + functionName: s.GetterGenericAccessorName, + interopType: s.GetterGenericInteropType, + parameterList: ", WindowsRuntimeObjectReference thisReference"); } if (s.HasSetter && s.SetterIsGeneric && !string.IsNullOrEmpty(s.SetterGenericInteropType)) { writer.WriteLine(); - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "{{kvp.Key}}")] - static extern void {{s.SetterGenericAccessorName}}([UnsafeAccessorType("{{s.SetterGenericInteropType}}")] object _, WindowsRuntimeObjectReference thisReference, {{s.SetterPropTypeText}} value); - """, isMultiline: true); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: kvp.Key, + returnType: "void", + functionName: s.SetterGenericAccessorName, + interopType: s.SetterGenericInteropType, + parameterList: $", WindowsRuntimeObjectReference thisReference, {s.SetterPropTypeText} value"); } writer.WriteLine(); + // when getter and setter platforms match; otherwise emit per-accessor. string getterPlat = s.GetterPlatformAttribute; string setterPlat = s.SetterPlatformAttribute; string propertyPlat = string.Empty; + // If both accessor platform attributes are equal, collapse them to a single // property-level attribute. For getter-only or setter-only properties only one side // is set; compare the relevant side. @@ -73,16 +85,13 @@ public static void WriteClassMembers(IndentedTextWriter writer, ProjectionEmitCo setterPlat = string.Empty; } - if (!string.IsNullOrEmpty(propertyPlat)) - { - writer.Write(propertyPlat); - } + writer.WriteIf(!string.IsNullOrEmpty(propertyPlat), propertyPlat); writer.Write($"{s.Access}{s.MethodSpec}{s.PropTypeText} {kvp.Key}"); + // For getter-only properties, emit expression body: 'public T Prop => Expr;' // For getter+setter or setter-only, use accessor block: 'public T Prop { get => ...; set => ...; }' // In ref mode, all property bodies emit '=> throw null;' - //, 1697). bool getterOnly = s.HasGetter && !s.HasSetter; if (getterOnly) @@ -120,7 +129,7 @@ public static void WriteClassMembers(IndentedTextWriter writer, ProjectionEmitCo { if (!string.IsNullOrEmpty(getterPlat)) { - writer.Write($"{getterPlat}"); + writer.Write(getterPlat); } if (context.Settings.ReferenceProjection) @@ -148,7 +157,7 @@ public static void WriteClassMembers(IndentedTextWriter writer, ProjectionEmitCo { if (!string.IsNullOrEmpty(setterPlat)) { - writer.Write($"{setterPlat}"); + writer.Write(setterPlat); } if (context.Settings.ReferenceProjection) @@ -180,9 +189,8 @@ public static void WriteClassMembers(IndentedTextWriter writer, ProjectionEmitCo // T InterfaceName.PropName { set => PropName = value; } if (s.IsOverridable && s.OverridableInterface is not null) { - writer.Write($"{s.PropTypeText} "); - WriteInterfaceTypeNameForCcw(writer, context, s.OverridableInterface); - writer.Write($".{kvp.Key} {{"); + WriteInterfaceTypeNameForCcwCallback iface = WriteInterfaceTypeNameForCcw(context, s.OverridableInterface); + writer.Write($"{s.PropTypeText} {iface}.{kvp.Key} {{"); if (s.HasGetter) { @@ -201,22 +209,4 @@ public static void WriteClassMembers(IndentedTextWriter writer, ProjectionEmitCo // GetInterface() / GetDefaultInterface() impls are emitted per-interface inside // WriteInterfaceMembersRecursive (matches the original code's per-interface ordering). } - - private static string BuildMethodSignatureKey(string name, MethodSignatureInfo sig) - { - System.Text.StringBuilder sb = new(); - _ = sb.Append(name); - _ = sb.Append('('); - for (int i = 0; i < sig.Parameters.Count; i++) - { - if (i > 0) - { - _ = sb.Append(','); - } - - _ = sb.Append(sig.Parameters[i].Type?.FullName ?? "?"); - } - _ = sb.Append(')'); - return sb.ToString(); - } } diff --git a/src/WinRT.Projection.Writer/Factories/ClassMembersFactory.WriteInterfaceMembers.cs b/src/WinRT.Projection.Writer/Factories/ClassMembersFactory.WriteInterfaceMembers.cs index 3122670800..b837f307f8 100644 --- a/src/WinRT.Projection.Writer/Factories/ClassMembersFactory.WriteInterfaceMembers.cs +++ b/src/WinRT.Projection.Writer/Factories/ClassMembersFactory.WriteInterfaceMembers.cs @@ -4,16 +4,17 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; using WindowsRuntime.ProjectionWriter.Models; using WindowsRuntime.ProjectionWriter.Writers; using static WindowsRuntime.ProjectionWriter.References.ProjectionNames; -using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; namespace WindowsRuntime.ProjectionWriter.Factories; @@ -33,9 +34,7 @@ private static void WriteInterfaceMembersRecursive(IndentedTextWriter writer, Pr } // Resolve TypeRef to TypeDef using our cache - TypeDefinition? ifaceType = ResolveInterface(context.Cache, impl.Interface); - - if (ifaceType is null) + if (!impl.TryResolveTypeDef(context.Cache, out TypeDefinition? ifaceType)) { continue; } @@ -48,7 +47,7 @@ private static void WriteInterfaceMembersRecursive(IndentedTextWriter writer, Pr _ = writtenInterfaces.Add(ifaceType); bool isOverridable = impl.IsOverridable(); - bool isProtected = impl.HasAttribute(WindowsFoundationMetadata, "ProtectedAttribute"); + bool isProtected = impl.HasWindowsFoundationMetadataAttribute("ProtectedAttribute"); // Substitute generic type arguments using the current generic context BEFORE emitting // any references to this interface. This is critical for nested recursion: e.g. when @@ -58,7 +57,7 @@ private static void WriteInterfaceMembersRecursive(IndentedTextWriter writer, Pr ITypeDefOrRef substitutedInterface = impl.Interface; GenericInstanceTypeSignature? nextInstance = null; - if (impl.Interface is TypeSpecification ts && ts.Signature is GenericInstanceTypeSignature gi) + if (impl.Interface.TryGetGenericInstance(out GenericInstanceTypeSignature? gi)) { if (currentInstance is not null) { @@ -97,15 +96,14 @@ private static void WriteInterfaceMembersRecursive(IndentedTextWriter writer, Pr if (IsInterfaceInInheritanceList(context.Cache, impl, includeExclusiveInterface: false) && !context.Settings.ReferenceProjection) { string giObjRefName = ObjRefNameGenerator.GetObjRefName(context, substitutedInterface); + WriteInterfaceTypeNameForCcwCallback iface = WriteInterfaceTypeNameForCcw(context, substitutedInterface); writer.WriteLine(); - writer.Write("WindowsRuntimeObjectReferenceValue IWindowsRuntimeInterface<"); - WriteInterfaceTypeNameForCcw(writer, context, substitutedInterface); - writer.WriteLine($$""" - >.GetInterface() + writer.WriteLine(isMultiline: true, $$""" + WindowsRuntimeObjectReferenceValue IWindowsRuntimeInterface<{{iface}}>.GetInterface() { return {{giObjRefName}}.AsValue(); } - """, isMultiline: true); + """); } else if (impl.IsDefaultInterface() && !classType.IsSealed) { @@ -121,25 +119,20 @@ private static void WriteInterfaceMembersRecursive(IndentedTextWriter writer, Pr if (classType.BaseType is not null) { - string? baseNs = classType.BaseType.Namespace?.Value; - string? baseName = classType.BaseType.Name?.Value; - hasBaseType = !(baseNs == "System" && baseName == "Object"); + hasBaseType = classType.HasNonObjectBaseType(); } writer.WriteLine(); writer.Write("internal "); - if (hasBaseType) - { - writer.Write("new "); - } + writer.WriteIf(hasBaseType, "new "); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" WindowsRuntimeObjectReferenceValue GetDefaultInterface() { return {{giObjRefName}}.AsValue(); } - """, isMultiline: true); + """); } // For mapped interfaces with custom members output (e.g. IClosable -> IDisposable, IMap`2 @@ -175,6 +168,7 @@ private static void WriteInterfaceMembers(IndentedTextWriter writer, ProjectionE HashSet writtenMethods, IDictionary propertyState, HashSet writtenEvents) { bool sealed_ = classType.IsSealed; + // Determine accessibility and method modifier. // Overridable interfaces are emitted with 'protected' visibility, plus 'virtual' on // non-sealed classes. Sealed classes still get 'protected' (without virtual). @@ -201,7 +195,7 @@ private static void WriteInterfaceMembers(IndentedTextWriter writer, ProjectionE // into the default interface's vtable in a fixed order TypeDefinition abiInterface = ifaceType; ITypeDefOrRef abiInterfaceRef = originalInterface; - bool isFastAbiExclusive = ClassFactory.IsFastAbiClass(classType) && TypeCategorization.IsExclusiveTo(ifaceType); + bool isFastAbiExclusive = ClassFactory.IsFastAbiClass(classType) && ifaceType.IsExclusiveTo; bool isDefaultInterface = false; if (isFastAbiExclusive) @@ -229,12 +223,7 @@ private static void WriteInterfaceMembers(IndentedTextWriter writer, ProjectionE // — note this is the ungenerified Methods class for generic interfaces // The _objRef_ field name uses the full instantiated interface name so generic instantiations // (e.g. IAsyncOperation) get a per-instantiation field. - string abiClass = TypedefNameWriter.WriteTypedefName(context, abiInterface, TypedefNameType.StaticAbiClass, true); - - if (!abiClass.StartsWith(GlobalPrefix, StringComparison.Ordinal)) - { - abiClass = GlobalPrefix + abiClass; - } + string abiClass = TypedefNameWriter.WriteTypedefName(context, abiInterface, TypedefNameType.StaticAbiClass, true).Format(); string objRef = ObjRefNameGenerator.GetObjRefName(context, abiInterfaceRef); @@ -245,9 +234,9 @@ private static void WriteInterfaceMembers(IndentedTextWriter writer, ProjectionE if (isGenericInterface && currentInstance is not null) { - string projectedParent = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(currentInstance), TypedefNameType.Projected, true); + string projectedParent = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(currentInstance), TypedefNameType.Projected, true).Format(); genericParentEncoded = IidExpressionGenerator.EscapeTypeNameForIdentifier(projectedParent, stripGlobal: true); - genericInteropType = InteropTypeNameWriter.EncodeInteropTypeName(currentInstance, TypedefNameType.StaticAbiClass) + ", WinRT.Interop"; + genericInteropType = InteropTypeNameWriter.GetInteropAssemblyQualifiedName(currentInstance, TypedefNameType.StaticAbiClass); } // Compute the platform attribute string from the interface type's [ContractVersion] @@ -255,21 +244,17 @@ private static void WriteInterfaceMembers(IndentedTextWriter writer, ProjectionE // class members carry [SupportedOSPlatform("WindowsX.Y.Z.0")] mirroring the interface's // contract version. Only emitted in ref mode (WritePlatformAttribute internally returns // immediately if not ref) - string platformAttribute = CustomAttributeFactory.WritePlatformAttribute(context, ifaceType); + string platformAttribute = CustomAttributeFactory.GetPlatformAttribute(context, ifaceType); // Methods - foreach (MethodDefinition method in ifaceType.Methods) + foreach (MethodDefinition method in ifaceType.GetNonSpecialMethods()) { - if (method.IsSpecial()) - { - continue; - } + string name = method.GetRawName(); - string name = method.Name?.Value ?? string.Empty; // Track by full signature (name + each param's element-type code) to avoid trivial overload duplicates. // This prevents collapsing distinct overloads like Format(double) and Format(ulong). MethodSignatureInfo sig = new(method, genericContext); - string key = BuildMethodSignatureKey(name, sig); + string key = sig.GetDedupeKey(name); if (!writtenMethods.Add(key)) { @@ -315,60 +300,37 @@ private static void WriteInterfaceMembers(IndentedTextWriter writer, ProjectionE { // Emit UnsafeAccessor static extern + body that dispatches through it. string accessorName = genericParentEncoded + "_" + name; - writer.WriteLine(); - writer.Write($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "{{name}}")] - static extern - """, isMultiline: true); - MethodFactory.WriteProjectionReturnType(writer, context, sig); - writer.Write($" {accessorName}([UnsafeAccessorType(\"{genericInteropType}\")] object _, WindowsRuntimeObjectReference thisReference"); - for (int i = 0; i < sig.Parameters.Count; i++) - { - writer.Write(", "); - MethodFactory.WriteProjectionParameter(writer, context, sig.Parameters[i]); - } - writer.WriteLine(");"); - // string to each public method emission. In ref mode this produces e.g. - // [global::System.Runtime.Versioning.SupportedOSPlatform("Windows10.0.16299.0")]. - if (!string.IsNullOrEmpty(platformAttribute)) - { - writer.Write(platformAttribute); - } - - writer.Write($"{access}{methodSpecForThis}"); - MethodFactory.WriteProjectionReturnType(writer, context, sig); - writer.Write($" {name}("); - MethodFactory.WriteParameterList(writer, context, sig); + WriteProjectionReturnTypeCallback unsafeRet = MethodFactory.WriteProjectionReturnType(context, sig); + WriteProjectionReturnTypeCallback ret = MethodFactory.WriteProjectionReturnType(context, sig); + WriteParameterListCallback parms = MethodFactory.WriteParameterList(context, sig); + string accessorParams = string.Concat(sig.Parameters.Select(p => $", {MethodFactory.WriteProjectionParameter(context, p).Format()}")); + string body = context.Settings.ReferenceProjection + ? "throw null;" + : $"{accessorName}(null, {objRef}{MethodFactory.WriteCallArguments(context, sig, leadingComma: true).Format()});"; + string platformTrimmed = platformAttribute.TrimEnd('\r', '\n'); - if (context.Settings.ReferenceProjection) - { - // which emits 'throw null' in reference projection mode. - writer.WriteLine(") => throw null;"); - } - else - { - writer.Write($") => {accessorName}(null, {objRef}"); - for (int i = 0; i < sig.Parameters.Count; i++) - { - writer.Write(", "); - WriteParameterNameWithModifier(writer, context, sig.Parameters[i]); - } - writer.WriteLine(");"); - } + writer.WriteLine(); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: name, + returnType: unsafeRet.Format(), + functionName: accessorName, + interopType: genericInteropType, + parameterList: $", WindowsRuntimeObjectReference thisReference{accessorParams}"); + writer.WriteLine(isMultiline: true, $$""" + {{platformTrimmed}} + {{access}}{{methodSpecForThis}}{{ret}} {{name}}({{parms}}) => {{body}} + """); } else { writer.WriteLine(); - if (!string.IsNullOrEmpty(platformAttribute)) - { - writer.Write(platformAttribute); - } + writer.WriteIf(!string.IsNullOrEmpty(platformAttribute), platformAttribute); - writer.Write($"{access}{methodSpecForThis}"); - MethodFactory.WriteProjectionReturnType(writer, context, sig); - writer.Write($" {name}("); - MethodFactory.WriteParameterList(writer, context, sig); + WriteProjectionReturnTypeCallback ret = MethodFactory.WriteProjectionReturnType(context, sig); + WriteParameterListCallback parms = MethodFactory.WriteParameterList(context, sig); + writer.Write($"{access}{methodSpecForThis}{ret} {name}({parms}"); if (context.Settings.ReferenceProjection) { @@ -377,13 +339,8 @@ static extern } else { - writer.Write($") => {abiClass}.{name}({objRef}"); - for (int i = 0; i < sig.Parameters.Count; i++) - { - writer.Write(", "); - WriteParameterNameWithModifier(writer, context, sig.Parameters[i]); - } - writer.WriteLine(");"); + WriteCallArgumentsCallback args = MethodFactory.WriteCallArguments(context, sig, leadingComma: true); + writer.WriteLine($") => {abiClass}.{name}({objRef}{args});"); } } @@ -392,27 +349,13 @@ static extern if (isOverridable) { // impl as well (since it shares the same originating interface). - if (!string.IsNullOrEmpty(platformAttribute)) - { - writer.Write(platformAttribute); - } + writer.WriteIf(!string.IsNullOrEmpty(platformAttribute), platformAttribute); - MethodFactory.WriteProjectionReturnType(writer, context, sig); - writer.Write(" "); - WriteInterfaceTypeNameForCcw(writer, context, originalInterface); - writer.Write($".{name}("); - MethodFactory.WriteParameterList(writer, context, sig); - writer.Write($") => {name}("); - for (int i = 0; i < sig.Parameters.Count; i++) - { - if (i > 0) - { - writer.Write(", "); - } - - WriteParameterNameWithModifier(writer, context, sig.Parameters[i]); - } - writer.WriteLine(");"); + WriteProjectionReturnTypeCallback ret = MethodFactory.WriteProjectionReturnType(context, sig); + WriteParameterListCallback parms = MethodFactory.WriteParameterList(context, sig); + WriteInterfaceTypeNameForCcwCallback iface = WriteInterfaceTypeNameForCcw(context, originalInterface); + WriteCallArgumentsCallback args = MethodFactory.WriteCallArguments(context, sig, leadingComma: false); + writer.WriteLine($"{ret} {iface}.{name}({parms}) => {name}({args});"); } } @@ -421,8 +364,8 @@ static extern // class on the right _objRef_ field. foreach (PropertyDefinition prop in ifaceType.Properties) { - string name = prop.Name?.Value ?? string.Empty; - (MethodDefinition? getter, MethodDefinition? setter) = prop.GetPropertyMethods(); + string name = prop.GetRawName(); + (MethodDefinition? getter, MethodDefinition? setter) = prop.GetMethods(); if (!propertyState.TryGetValue(name, out PropertyAccessorState? state)) { @@ -467,7 +410,7 @@ static extern // handler type. foreach (EventDefinition evt in ifaceType.Events) { - string name = evt.Name?.Value ?? string.Empty; + string name = evt.GetRawName(); if (!writtenEvents.Add(name)) { @@ -499,7 +442,7 @@ static extern } else { - eventSourceType = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(evtSig), TypedefNameType.EventSource, false); + eventSourceType = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(evtSig), TypedefNameType.EventSource, false).Format(); } string eventSourceTypeFull = eventSourceType; @@ -511,7 +454,7 @@ static extern // The "interop" type name string for the EventSource UnsafeAccessor (only needed for generic events). string eventSourceInteropType = isGenericEvent - ? InteropTypeNameWriter.EncodeInteropTypeName(evtSig, TypedefNameType.EventSource) + ", WinRT.Interop" + ? InteropTypeNameWriter.GetInteropAssemblyQualifiedName(evtSig, TypedefNameType.EventSource) : string.Empty; // Compute vtable index = method index in the interface vtable + 6 (for IInspectable methods). @@ -534,29 +477,33 @@ static extern if (!context.Settings.ReferenceProjection && inlineEventSourceField) { writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" private {{eventSourceTypeFull}} _eventSource_{{name}} { get { - """, isMultiline: true); + """); if (isGenericEvent && !string.IsNullOrEmpty(eventSourceInteropType)) { - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.Constructor)] - [return: UnsafeAccessorType("{{eventSourceInteropType}}")] - static extern object ctor(WindowsRuntimeObjectReference nativeObjectReference, int index); - """, isMultiline: true); + writer.IncreaseIndent(); + writer.IncreaseIndent(); + UnsafeAccessorFactory.EmitConstructorReturningObject( + writer, + interopType: eventSourceInteropType, + functionName: "ctor", + parameterList: "WindowsRuntimeObjectReference nativeObjectReference, int index"); + writer.DecreaseIndent(); + writer.DecreaseIndent(); writer.WriteLine(); } - writer.Write($$""" + writer.Write(isMultiline: true, $$""" [MethodImpl(MethodImplOptions.NoInlining)] {{eventSourceTypeFull}} MakeEventSource() { _ = global::System.Threading.Interlocked.CompareExchange( location1: ref field, value: - """, isMultiline: true); + """); if (isGenericEvent) { writer.Write($"Unsafe.As<{eventSourceTypeFull}>(ctor({objRef}, {vtableIndex.ToString(CultureInfo.InvariantCulture)}))"); @@ -566,7 +513,7 @@ static extern writer.Write($"new {eventSourceTypeFull}({objRef}, {vtableIndex.ToString(CultureInfo.InvariantCulture)})"); } - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ , comparand: null); @@ -576,37 +523,31 @@ static extern return field ?? MakeEventSource(); } } - """, isMultiline: true); + """); } // Emit the public/protected event with Subscribe/Unsubscribe. writer.WriteLine(); + // string to each event emission. In ref mode this produces e.g. // [global::System.Runtime.Versioning.SupportedOSPlatform("Windows10.0.16299.0")]. - if (!string.IsNullOrEmpty(platformAttribute)) - { - writer.Write(platformAttribute); - } + writer.WriteIf(!string.IsNullOrEmpty(platformAttribute), platformAttribute); - writer.Write($"{access}{methodSpec}event "); - TypedefNameWriter.WriteEventType(writer, context, evt, currentInstance); - writer.WriteLine($$""" - {{name}} - { - """, isMultiline: true); + WriteEventTypeCallback eventType = TypedefNameWriter.WriteEventType(context, evt, currentInstance); + string accessors; if (context.Settings.ReferenceProjection) { - writer.WriteLine(""" + accessors = """ add => throw null; remove => throw null; - """, isMultiline: true); + """; } else if (inlineEventSourceField) { - writer.WriteLine($$""" + accessors = $$""" add => _eventSource_{{name}}.Subscribe(value); remove => _eventSource_{{name}}.Unsubscribe(value); - """, isMultiline: true); + """; } else { @@ -615,12 +556,17 @@ static extern // inline_event_source_field is false (the default helper-based path). // Example: Simple.Event0 (on ISimple5) becomes // add => global::ABI.test_component_fast.ISimpleMethods.Event0((WindowsRuntimeObject)this, _objRef_test_component_fast_ISimple).Subscribe(value); - writer.WriteLine($$""" + accessors = $$""" add => {{abiClass}}.{{name}}((WindowsRuntimeObject)this, {{objRef}}).Subscribe(value); remove => {{abiClass}}.{{name}}((WindowsRuntimeObject)this, {{objRef}}).Unsubscribe(value); - """, isMultiline: true); + """; } - writer.WriteLine("}"); + writer.WriteLine(isMultiline: true, $$""" + {{access}}{{methodSpec}}event {{eventType}} {{name}} + { + {{accessors}} + } + """); } } } diff --git a/src/WinRT.Projection.Writer/Factories/ClassMembersFactory.cs b/src/WinRT.Projection.Writer/Factories/ClassMembersFactory.cs index 31b8420545..7b719432bd 100644 --- a/src/WinRT.Projection.Writer/Factories/ClassMembersFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/ClassMembersFactory.cs @@ -3,6 +3,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; @@ -46,43 +47,19 @@ internal static bool IsInterfaceInInheritanceList(MetadataCache cache, Interface return true; } - TypeDefinition? td = ResolveInterface(cache, impl.Interface); - - if (td is null) + if (!impl.TryResolveTypeDef(cache, out TypeDefinition? td)) { return true; } - return !TypeCategorization.IsExclusiveTo(td); + return !td.IsExclusiveTo; } - internal static TypeDefinition? ResolveInterface(MetadataCache cache, ITypeDefOrRef typeRef) - { - if (typeRef is TypeDefinition td) - { - return td; - } - TypeDefinition? resolved = typeRef.TryResolve(cache.RuntimeContext); - - if (resolved is not null) - { - return resolved; - } - - // Fall back to local lookup by full name - if (typeRef is TypeReference tr) - { - (string ns, string name) = tr.Names(); - string fullName = string.IsNullOrEmpty(ns) ? name : ns + "." + name; - return cache.Find(fullName); - } - - if (typeRef is TypeSpecification ts && ts.Signature is GenericInstanceTypeSignature gi) - { - return ResolveInterface(cache, gi.GenericType); - } - - return null; + /// + /// A callback emitting the parameter name with its modifier. + internal static WriteParameterNameWithModifierCallback WriteParameterNameWithModifier(ProjectionEmitContext context, ParameterInfo p) + { + return new(context, p); } /// @@ -90,7 +67,7 @@ internal static bool IsInterfaceInInheritanceList(MetadataCache cache, Interface /// internal static void WriteParameterNameWithModifier(IndentedTextWriter writer, ProjectionEmitContext context, ParameterInfo p) { - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); switch (cat) { case ParameterCategory.Out: @@ -114,7 +91,7 @@ internal static void WriteInterfaceTypeNameForCcw(IndentedTextWriter writer, Pro { // If the reference is to a type in the same module, resolve to TypeDefinition so // WriteTypedefName can drop the 'global::.' prefix when the namespace matches. - if (ifaceType is not TypeDefinition && ifaceType is not TypeSpecification && context.Cache is not null) + if (ifaceType is not (TypeDefinition or TypeSpecification)) { TypeDefinition? resolved = ifaceType.TryResolve(context.Cache.RuntimeContext); @@ -132,39 +109,31 @@ internal static void WriteInterfaceTypeNameForCcw(IndentedTextWriter writer, Pro else if (ifaceType is TypeReference tr) { (string ns, string name) = tr.Names(); - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); - writer.Write($"global::{ns}.{IdentifierEscaping.StripBackticks(name)}"); + writer.Write(TypedefNameWriter.BuildGlobalQualifiedName(ns, name)); } - else if (ifaceType is TypeSpecification ts && ts.Signature is GenericInstanceTypeSignature gi) + else if (ifaceType.TryGetGenericInstance(out GenericInstanceTypeSignature? gi)) { ITypeDefOrRef gt = gi.GenericType; (string ns, string name) = gt.Names(); - MappedType? mapped = MappedTypes.Get(ns, name); + _ = MappedTypes.ApplyMapping(ref ns, ref name); - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } - - writer.Write($"global::{ns}.{IdentifierEscaping.StripBackticks(name)}<"); + writer.Write($"{TypedefNameWriter.BuildGlobalQualifiedName(ns, name)}<"); for (int i = 0; i < gi.TypeArguments.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); TypedefNameWriter.WriteTypeName(writer, context, TypeSemanticsFactory.Get(gi.TypeArguments[i]), TypedefNameType.Projected, true); } writer.Write(">"); } } + + /// + /// A callback that writes the CCW interface type name to the writer it's appended to. + internal static WriteInterfaceTypeNameForCcwCallback WriteInterfaceTypeNameForCcw(ProjectionEmitContext context, ITypeDefOrRef ifaceType) + { + return new(context, ifaceType); + } } diff --git a/src/WinRT.Projection.Writer/Factories/ComponentFactory.cs b/src/WinRT.Projection.Writer/Factories/ComponentFactory.cs index 1cbe248be8..b1df5703a6 100644 --- a/src/WinRT.Projection.Writer/Factories/ComponentFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/ComponentFactory.cs @@ -6,9 +6,12 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Resolvers; using WindowsRuntime.ProjectionWriter.Writers; namespace WindowsRuntime.ProjectionWriter.Factories; @@ -28,17 +31,17 @@ public static void AddMetadataTypeEntry(ProjectionEmitContext context, TypeDefin return; } - TypeCategory cat = TypeCategorization.GetCategory(type); + TypeKind kind = TypeKindResolver.Resolve(type); - if ((cat == TypeCategory.Class && TypeCategorization.IsStatic(type)) || - (cat == TypeCategory.Interface && TypeCategorization.IsExclusiveTo(type))) + if ((kind == TypeKind.Class && type.IsStatic) || + (kind == TypeKind.Interface && type.IsExclusiveTo)) { return; } - string typeName = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.Projected, true); + string typeName = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.Projected, true).Format(); - string metadataTypeName = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.CCW, true); + string metadataTypeName = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.CCW, true).Format(); _ = map.TryAdd(typeName, metadataTypeName); } @@ -48,18 +51,14 @@ public static void AddMetadataTypeEntry(ProjectionEmitContext context, TypeDefin /// public static void WriteFactoryClass(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string typeName = type.Name?.Value ?? string.Empty; - string typeNs = type.Namespace?.Value ?? string.Empty; - string projectedTypeName = string.IsNullOrEmpty(typeNs) - ? $"global::{IdentifierEscaping.StripBackticks(typeName)}" - : $"global::{typeNs}.{IdentifierEscaping.StripBackticks(typeName)}"; + (string typeNs, string typeName) = type.Names(); + string projectedTypeName = TypedefNameWriter.BuildGlobalQualifiedName(typeNs, typeName); string factoryTypeName = $"{IdentifierEscaping.StripBackticks(typeName)}ServerActivationFactory"; - bool isActivatable = !TypeCategorization.IsStatic(type) && type.HasDefaultConstructor(); + bool isActivatable = !type.IsStatic && type.HasDefaultConstructor(); // Build the inheritance list: factory interfaces ([Activatable]/[Static]) only. - MetadataCache cache = context.Cache; List factoryInterfaces = []; - foreach (KeyValuePair kv in AttributedTypes.Get(type, cache)) + foreach (KeyValuePair kv in AttributedTypes.Get(type, context.Cache)) { AttributedType info = kv.Value; @@ -74,12 +73,16 @@ public static void WriteFactoryClass(IndentedTextWriter writer, ProjectionEmitCo foreach (TypeDefinition iface in factoryInterfaces) { writer.Write(", "); + // CCW + non-forced namespace is the user-facing interface name (e.g. 'IButtonUtilsStatic'). TypedefNameWriter.WriteTypedefName(writer, context, iface, TypedefNameType.CCW, false); TypedefNameWriter.WriteTypeParams(writer, iface); } + string activateBody = isActivatable + ? $"return new {projectedTypeName}();" + : "throw new NotImplementedException();"; writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" { static {{factoryTypeName}}() { @@ -97,63 +100,51 @@ public static void WriteFactoryClass(IndentedTextWriter writer, ProjectionEmitCo public object ActivateInstance() { - """, isMultiline: true); - if (isActivatable) - { - writer.Write($"return new {projectedTypeName}();"); - } - else - { - writer.Write("throw new NotImplementedException();"); - } - - writer.WriteLine(); - writer.WriteLine("}"); + {{activateBody}} + } + """); // Emit factory-class members: forwarding methods/properties/events for static factory // interfaces, and constructor wrappers for activatable factory interfaces. - if (cache is not null) + foreach (KeyValuePair kv in AttributedTypes.Get(type, context.Cache)) { - foreach (KeyValuePair kv in AttributedTypes.Get(type, cache)) - { - AttributedType info = kv.Value; + AttributedType info = kv.Value; - if (info.Type is null) - { - continue; - } + if (info.Type is null) + { + continue; + } - if (info.Activatable) + if (info.Activatable) + { + foreach (MethodDefinition method in info.Type.Methods) { - foreach (MethodDefinition method in info.Type.Methods) + if (method.IsConstructor) { - if (method.IsConstructor) - { - continue; - } - - WriteFactoryActivatableMethod(writer, context, method, projectedTypeName); + continue; } + + WriteFactoryActivatableMethod(writer, context, method, projectedTypeName); } - else if (info.Statics) + } + else if (info.Statics) + { + foreach (MethodDefinition method in info.Type.Methods) { - foreach (MethodDefinition method in info.Type.Methods) + if (method.IsConstructor) { - if (method.IsConstructor) - { - continue; - } - - WriteStaticFactoryMethod(writer, context, method, projectedTypeName); - } - foreach (PropertyDefinition prop in info.Type.Properties) - { - WriteStaticFactoryProperty(writer, context, prop, projectedTypeName); - } - foreach (EventDefinition evt in info.Type.Events) - { - WriteStaticFactoryEvent(writer, context, evt, projectedTypeName); + continue; } + + WriteStaticFactoryMethod(writer, context, method, projectedTypeName); + } + foreach (PropertyDefinition prop in info.Type.Properties) + { + WriteStaticFactoryProperty(writer, context, prop, projectedTypeName); + } + foreach (EventDefinition evt in info.Type.Events) + { + WriteStaticFactoryEvent(writer, context, evt, projectedTypeName); } } } @@ -172,13 +163,11 @@ private static void WriteFactoryActivatableMethod(IndentedTextWriter writer, Pro return; } - string methodName = method.Name?.Value ?? string.Empty; + string methodName = method.GetRawName(); + WriteFactoryMethodParametersCallback typedParams = WriteFactoryMethodParameters(context, method, includeTypes: true); + WriteFactoryMethodParametersCallback nameOnlyParams = WriteFactoryMethodParameters(context, method, includeTypes: false); writer.WriteLine(); - writer.Write($"public {projectedTypeName} {methodName}("); - WriteFactoryMethodParameters(writer, context, method, includeTypes: true); - writer.Write($") => new {projectedTypeName}("); - WriteFactoryMethodParameters(writer, context, method, includeTypes: false); - writer.WriteLine(");"); + writer.WriteLine($"public {projectedTypeName} {methodName}({typedParams}) => new {projectedTypeName}({nameOnlyParams});"); } /// @@ -192,15 +181,12 @@ private static void WriteStaticFactoryMethod(IndentedTextWriter writer, Projecti return; } - string methodName = method.Name?.Value ?? string.Empty; + string methodName = method.GetRawName(); + WriteFactoryReturnTypeCallback retType = WriteFactoryReturnType(context, method); + WriteFactoryMethodParametersCallback typedParams = WriteFactoryMethodParameters(context, method, includeTypes: true); + WriteFactoryMethodParametersCallback nameOnlyParams = WriteFactoryMethodParameters(context, method, includeTypes: false); writer.WriteLine(); - writer.Write("public "); - WriteFactoryReturnType(writer, context, method); - writer.Write($" {methodName}("); - WriteFactoryMethodParameters(writer, context, method, includeTypes: true); - writer.Write($") => {projectedTypeName}.{methodName}("); - WriteFactoryMethodParameters(writer, context, method, includeTypes: false); - writer.WriteLine(");"); + writer.WriteLine($"public {retType} {methodName}({typedParams}) => {projectedTypeName}.{methodName}({nameOnlyParams});"); } /// @@ -208,34 +194,29 @@ private static void WriteStaticFactoryMethod(IndentedTextWriter writer, Projecti /// private static void WriteStaticFactoryProperty(IndentedTextWriter writer, ProjectionEmitContext context, PropertyDefinition prop, string projectedTypeName) { - string propName = prop.Name?.Value ?? string.Empty; - (MethodDefinition? getter, MethodDefinition? setter) = prop.GetPropertyMethods(); + string propName = prop.GetRawName(); + (MethodDefinition? getter, MethodDefinition? setter) = prop.GetMethods(); + string propType = GetFactoryPropertyType(context, prop); + // Single-line form when no setter is present. if (setter is null) { writer.WriteLine(); - writer.Write("public "); - WriteFactoryPropertyType(writer, context, prop); - writer.WriteLine($" {propName} => {projectedTypeName}.{propName};"); + writer.WriteLine($"public {propType} {propName} => {projectedTypeName}.{propName};"); return; } + string getterLine = getter is not null + ? $"get => {projectedTypeName}.{propName};" + : string.Empty; writer.WriteLine(); - writer.Write("public "); - WriteFactoryPropertyType(writer, context, prop); - writer.WriteLine($$""" - {{propName}} + writer.WriteLine(isMultiline: true, $$""" + public {{propType}} {{propName}} { - """, isMultiline: true); - if (getter is not null) - { - writer.WriteLine($"get => {projectedTypeName}.{propName};"); - } - - writer.WriteLine($$""" + {{getterLine}} set => {{projectedTypeName}}.{{propName}} = value; } - """, isMultiline: true); + """); } /// @@ -243,26 +224,32 @@ private static void WriteStaticFactoryProperty(IndentedTextWriter writer, Projec /// private static void WriteStaticFactoryEvent(IndentedTextWriter writer, ProjectionEmitContext context, EventDefinition evt, string projectedTypeName) { - string evtName = evt.Name?.Value ?? string.Empty; - writer.WriteLine(); - writer.Write("public event "); - - if (evt.EventType is not null) - { - TypeSemantics evtSemantics = TypeSemanticsFactory.GetFromTypeDefOrRef(evt.EventType); - TypedefNameWriter.WriteTypeName(writer, context, evtSemantics, TypedefNameType.Projected, false); - } + string evtName = evt.GetRawName(); + string evtType = evt.EventType is null + ? string.Empty + : TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.GetFromTypeDefOrRef(evt.EventType), TypedefNameType.Projected, false).Format(); - writer.WriteLine($$""" - {{evtName}} + writer.WriteLine(); + writer.WriteLine(isMultiline: true, $$""" + public event {{evtType}} {{evtName}} { add => {{projectedTypeName}}.{{evtName}} += value; remove => {{projectedTypeName}}.{{evtName}} -= value; } - """, isMultiline: true); + """); + } + + /// + /// A callback emitting the projected return type of . + public static WriteFactoryReturnTypeCallback WriteFactoryReturnType(ProjectionEmitContext context, MethodDefinition method) + { + return new(context, method); } - private static void WriteFactoryReturnType(IndentedTextWriter writer, ProjectionEmitContext context, MethodDefinition method) + /// + /// Writes the projected return type for a static-factory forwarding method. + /// + public static void WriteFactoryReturnType(IndentedTextWriter writer, ProjectionEmitContext context, MethodDefinition method) { TypeSignature? returnType = method.Signature?.ReturnType; @@ -276,20 +263,32 @@ private static void WriteFactoryReturnType(IndentedTextWriter writer, Projection TypedefNameWriter.WriteTypeName(writer, context, semantics, TypedefNameType.Projected, true); } - private static void WriteFactoryPropertyType(IndentedTextWriter writer, ProjectionEmitContext context, PropertyDefinition prop) + private static string GetFactoryPropertyType(ProjectionEmitContext context, PropertyDefinition prop) { TypeSignature? sig = prop.Signature?.ReturnType; if (sig is null) { - writer.Write("object"); return; + return "object"; } TypeSemantics semantics = TypeSemanticsFactory.Get(sig); - TypedefNameWriter.WriteTypeName(writer, context, semantics, TypedefNameType.Projected, true); + return TypedefNameWriter.WriteTypeName(context, semantics, TypedefNameType.Projected, true).Format(); } - private static void WriteFactoryMethodParameters(IndentedTextWriter writer, ProjectionEmitContext context, MethodDefinition method, bool includeTypes) + /// + /// A callback emitting the factory-method parameter list. + public static WriteFactoryMethodParametersCallback WriteFactoryMethodParameters(ProjectionEmitContext context, MethodDefinition method, bool includeTypes) + { + return new(context, method, includeTypes); + } + + /// + /// Writes the parameter list for a factory wrapper/forwarding method. When + /// is , emits 'Type name' + /// pairs; otherwise emits names only (for forwarding call sites). + /// + public static void WriteFactoryMethodParameters(IndentedTextWriter writer, ProjectionEmitContext context, MethodDefinition method, bool includeTypes) { MethodSignature? sig = method.Signature; @@ -300,19 +299,15 @@ private static void WriteFactoryMethodParameters(IndentedTextWriter writer, Proj for (int i = 0; i < sig.ParameterTypes.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); ParameterDefinition? p = method.Parameters.Count > i ? method.Parameters[i].Definition : null; string paramName = p?.Name?.Value ?? $"arg{i}"; if (includeTypes) { - TypeSemantics semantics = TypeSemanticsFactory.Get(sig.ParameterTypes[i]); - TypedefNameWriter.WriteTypeName(writer, context, semantics, TypedefNameType.Projected, true); - writer.Write($" {paramName}"); + WriteTypeNameCallback projectedType = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(sig.ParameterTypes[i]), TypedefNameType.Projected, true); + writer.Write($"{projectedType} {paramName}"); } else { @@ -331,7 +326,7 @@ public static void WriteModuleActivationFactory(IndentedTextWriter writer, IRead foreach (KeyValuePair> kv in typesByModule) { writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" namespace ABI.{{kv.Key}} { public static class ManagedExports @@ -340,7 +335,8 @@ public static class ManagedExports { switch (activatableClassId) { - """, isMultiline: true); + """); + // Sort by the type's metadata token / row index so cases appear in WinMD declaration order. List orderedTypes = [.. kv.Value]; orderedTypes.Sort((a, b) => @@ -352,19 +348,19 @@ public static class ManagedExports foreach (TypeDefinition type in orderedTypes) { (string ns, string name) = type.Names(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" case "{{ns}}.{{name}}": return global::ABI.Impl.{{ns}}.{{IdentifierEscaping.StripBackticks(name)}}ServerActivationFactory.Make(); - """, isMultiline: true); + """); } - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ default: return null; } } } } - """, isMultiline: true); + """); } } } diff --git a/src/WinRT.Projection.Writer/Factories/ConstructorFactory.AttributedTypes.cs b/src/WinRT.Projection.Writer/Factories/ConstructorFactory.AttributedTypes.cs index 1d89c7a833..1694c6611d 100644 --- a/src/WinRT.Projection.Writer/Factories/ConstructorFactory.AttributedTypes.cs +++ b/src/WinRT.Projection.Writer/Factories/ConstructorFactory.AttributedTypes.cs @@ -19,11 +19,6 @@ internal static partial class ConstructorFactory /// public static void WriteAttributedTypes(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition classType) { - if (context.Cache is null) - { - return; - } - // Track whether we need to emit the static _objRef_ field (used by // default constructors). Emit it once per class if any [Activatable] factory exists. bool needsClassObjRef = false; @@ -41,7 +36,7 @@ public static void WriteAttributedTypes(IndentedTextWriter writer, ProjectionEmi if (needsClassObjRef) { - string fullName = (classType.Namespace?.Value ?? string.Empty) + "." + (classType.Name?.Value ?? string.Empty); + string fullName = classType.FullName ?? string.Empty; string objRefName = "_objRef_" + IidExpressionGenerator.EscapeTypeNameForIdentifier(GlobalPrefix + fullName, stripGlobal: true); writer.WriteLine(); writer.Write($"private static WindowsRuntimeObjectReference {objRefName}"); @@ -54,7 +49,7 @@ public static void WriteAttributedTypes(IndentedTextWriter writer, ProjectionEmi else { writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" { get { @@ -66,7 +61,7 @@ public static void WriteAttributedTypes(IndentedTextWriter writer, ProjectionEmi return field = WindowsRuntimeObjectReference.GetActivationFactory("{{fullName}}"); } } - """, isMultiline: true); + """); } } @@ -90,27 +85,30 @@ public static void WriteAttributedTypes(IndentedTextWriter writer, ProjectionEmi /// public static void WriteFactoryConstructors(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition? factoryType, TypeDefinition classType) { - string typeName = classType.Name?.Value ?? string.Empty; + string typeName = classType.GetRawName(); int gcPressure = ClassFactory.GetGcPressureAmount(classType); if (factoryType is not null) { // Emit the factory objref property (lazy-initialized). - string factoryRuntimeClassFullName = (classType.Namespace?.Value ?? string.Empty) + "." + typeName; + string factoryRuntimeClassFullName = classType.FullName ?? string.Empty; string factoryObjRefName = ObjRefNameGenerator.GetObjRefName(context, factoryType); ClassFactory.WriteStaticFactoryObjRef(writer, context, factoryType, factoryRuntimeClassFullName, factoryObjRefName); string defaultIfaceIid = GetDefaultInterfaceIid(context, classType); string marshalingType = GetMarshalingTypeName(classType); + // Compute the platform attribute string from the activation factory interface's // [ContractVersion] attribute - string platformAttribute = CustomAttributeFactory.WritePlatformAttribute(context, factoryType); + string platformAttribute = CustomAttributeFactory.GetPlatformAttribute(context, factoryType); int methodIndex = 0; foreach (MethodDefinition method in factoryType.Methods) { - if (method.IsSpecial()) + if (method.IsSpecial) { - methodIndex++; continue; + methodIndex++; + + continue; } MethodSignatureInfo sig = new(method); @@ -120,17 +118,14 @@ public static void WriteFactoryConstructors(IndentedTextWriter writer, Projectio // Emit the public constructor. writer.WriteLine(); - if (!string.IsNullOrEmpty(platformAttribute)) - { - writer.Write(platformAttribute); - } + writer.WriteIf(!string.IsNullOrEmpty(platformAttribute), platformAttribute); writer.Write($"public unsafe {typeName}("); MethodFactory.WriteParameterList(writer, context, sig); - writer.Write(""" + writer.Write(isMultiline: true, """ ) :base( - """, isMultiline: true); + """); if (sig.Parameters.Count == 0) { writer.Write("default"); @@ -140,21 +135,18 @@ public static void WriteFactoryConstructors(IndentedTextWriter writer, Projectio writer.Write($"{callbackName}.Instance, {defaultIfaceIid}, {marshalingType}, WindowsRuntimeActivationArgsReference.CreateUnsafe(new {argsName}("); for (int i = 0; i < sig.Parameters.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); - string raw = sig.Parameters[i].Parameter.Name ?? "param"; - writer.Write(CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw); + string raw = sig.Parameters[i].GetRawName(); + writer.Write(IdentifierEscaping.EscapeIdentifier(raw)); } writer.Write("))"); } - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ ) { - """, isMultiline: true); + """); if (gcPressure > 0) { writer.WriteLine($"GC.AddMemoryPressure({gcPressure.ToString(CultureInfo.InvariantCulture)});"); @@ -176,18 +168,18 @@ public static void WriteFactoryConstructors(IndentedTextWriter writer, Projectio // No factory type means [Activatable(uint version)] - emit a default ctor that calls // the WindowsRuntimeObject base constructor with the activation factory objref. // The default interface IID is needed too. - string fullName = (classType.Namespace?.Value ?? string.Empty) + "." + typeName; + string fullName = classType.FullName ?? string.Empty; string objRefName = "_objRef_" + IidExpressionGenerator.EscapeTypeNameForIdentifier(GlobalPrefix + fullName, stripGlobal: true); // Find the default interface IID to use. string defaultIfaceIid = GetDefaultInterfaceIid(context, classType); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public {{typeName}}() :base(default(WindowsRuntimeActivationTypes.DerivedSealed), {{objRefName}}, {{defaultIfaceIid}}, {{GetMarshalingTypeName(classType)}}) { - """, isMultiline: true); + """); if (gcPressure > 0) { writer.WriteLine($"GC.AddMemoryPressure({gcPressure.ToString(CultureInfo.InvariantCulture)});"); diff --git a/src/WinRT.Projection.Writer/Factories/ConstructorFactory.Composable.cs b/src/WinRT.Projection.Writer/Factories/ConstructorFactory.Composable.cs index 58093796b2..112f3ae30c 100644 --- a/src/WinRT.Projection.Writer/Factories/ConstructorFactory.Composable.cs +++ b/src/WinRT.Projection.Writer/Factories/ConstructorFactory.Composable.cs @@ -3,6 +3,7 @@ using System.Globalization; using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Models; @@ -25,12 +26,12 @@ public static void WriteComposableConstructors(IndentedTextWriter writer, Projec return; } - string typeName = classType.Name?.Value ?? string.Empty; + string typeName = classType.GetRawName(); // Emit the factory objref + IIDs at the top so the parameterized ctors can reference it. if (composableType.Methods.Count > 0) { - string runtimeClassFullName = (classType.Namespace?.Value ?? string.Empty) + "." + typeName; + string runtimeClassFullName = classType.FullName ?? string.Empty; string factoryObjRefName = ObjRefNameGenerator.GetObjRefName(context, composableType); ClassFactory.WriteStaticFactoryObjRef(writer, context, composableType, runtimeClassFullName, factoryObjRefName); } @@ -41,16 +42,19 @@ public static void WriteComposableConstructors(IndentedTextWriter writer, Projec ITypeDefOrRef? defaultIface = classType.GetDefaultInterface(); defaultIfaceObjRef = defaultIface is not null ? ObjRefNameGenerator.GetObjRefName(context, defaultIface) : string.Empty; int gcPressure = ClassFactory.GetGcPressureAmount(classType); + // Compute the platform attribute string from the composable factory interface's // [ContractVersion] attribute - string platformAttribute = CustomAttributeFactory.WritePlatformAttribute(context, composableType); + string platformAttribute = CustomAttributeFactory.GetPlatformAttribute(context, composableType); int methodIndex = 0; foreach (MethodDefinition method in composableType.Methods) { - if (method.IsSpecial()) + if (method.IsSpecial) { - methodIndex++; continue; + methodIndex++; + + continue; } // Composable factory methods have signature like: @@ -58,6 +62,7 @@ public static void WriteComposableConstructors(IndentedTextWriter writer, Projec // For the constructor on the projected class, we exclude the trailing two params. MethodSignatureInfo sig = new(method); int userParamCount = sig.Parameters.Count >= 2 ? sig.Parameters.Count - 2 : sig.Parameters.Count; + // the callback / args type name suffix is the TOTAL ABI param count // (size(method.Signature().Parameters())), NOT the user-visible param count. Using the // total count guarantees uniqueness against other composable factory overloads that @@ -68,10 +73,7 @@ public static void WriteComposableConstructors(IndentedTextWriter writer, Projec writer.WriteLine(); - if (!string.IsNullOrEmpty(platformAttribute)) - { - writer.Write(platformAttribute); - } + writer.WriteIf(!string.IsNullOrEmpty(platformAttribute), platformAttribute); writer.Write(visibility); @@ -87,18 +89,14 @@ public static void WriteComposableConstructors(IndentedTextWriter writer, Projec writer.Write($"{typeName}("); for (int i = 0; i < userParamCount; i++) { - if (i > 0) - { - writer.Write(", "); - } - - MethodFactory.WriteProjectionParameter(writer, context, sig.Parameters[i]); + WriteProjectionParameterCallback p = MethodFactory.WriteProjectionParameter(context, sig.Parameters[i]); + writer.Write($"{(i > 0 ? ", " : "")}{p}"); } - writer.Write(""" + writer.Write(isMultiline: true, """ ) :base( - """, isMultiline: true); + """); if (isParameterless) { // base(default(WindowsRuntimeActivationTypes.DerivedComposed), , , ) @@ -110,28 +108,25 @@ public static void WriteComposableConstructors(IndentedTextWriter writer, Projec writer.Write($"{callbackName}.Instance, {defaultIfaceIid}, {marshalingType}, WindowsRuntimeActivationArgsReference.CreateUnsafe(new {argsName}("); for (int i = 0; i < userParamCount; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); - string raw = sig.Parameters[i].Parameter.Name ?? "param"; - writer.Write(CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw); + string raw = sig.Parameters[i].GetRawName(); + writer.Write(IdentifierEscaping.EscapeIdentifier(raw)); } writer.Write("))"); } - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ ) - """, isMultiline: true); + """); using (writer.WriteBlock()) { - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" if (GetType() == typeof({{typeName}})) { {{defaultIfaceObjRef}} = NativeObjectReference; } - """, isMultiline: true); + """); if (gcPressure > 0) { @@ -163,56 +158,29 @@ public static void WriteComposableConstructors(IndentedTextWriter writer, Projec ? "GC.AddMemoryPressure(" + gcPressure.ToString(CultureInfo.InvariantCulture) + ");" : string.Empty; - // 1. WindowsRuntimeActivationTypes.DerivedComposed - writer.WriteLine(); - writer.WriteLine($$""" - protected {{typeName}}(WindowsRuntimeActivationTypes.DerivedComposed _, WindowsRuntimeObjectReference activationFactoryObjectReference, in Guid iid, CreateObjectReferenceMarshalingType marshalingType) - :base(_, activationFactoryObjectReference, in iid, marshalingType) - """, isMultiline: true); - using (writer.WriteBlock()) + // Each entry pairs the constructor's parameter list with the matching ':base(...)' arg list. + (string Parameters, string BaseArgs)[] ctorSignatures = + [ + ("WindowsRuntimeActivationTypes.DerivedComposed _, WindowsRuntimeObjectReference activationFactoryObjectReference, in Guid iid, CreateObjectReferenceMarshalingType marshalingType", + "_, activationFactoryObjectReference, in iid, marshalingType"), + ("WindowsRuntimeActivationTypes.DerivedSealed _, WindowsRuntimeObjectReference activationFactoryObjectReference, in Guid iid, CreateObjectReferenceMarshalingType marshalingType", + "_, activationFactoryObjectReference, in iid, marshalingType"), + ("WindowsRuntimeActivationFactoryCallback.DerivedComposed activationFactoryCallback, in Guid iid, CreateObjectReferenceMarshalingType marshalingType, WindowsRuntimeActivationArgsReference additionalParameters", + "activationFactoryCallback, in iid, marshalingType, additionalParameters"), + ("WindowsRuntimeActivationFactoryCallback.DerivedSealed activationFactoryCallback, in Guid iid, CreateObjectReferenceMarshalingType marshalingType, WindowsRuntimeActivationArgsReference additionalParameters", + "activationFactoryCallback, in iid, marshalingType, additionalParameters"), + ]; + + foreach ((string parameters, string baseArgs) in ctorSignatures) { - if (!string.IsNullOrEmpty(gcPressureBody)) - { - writer.WriteLine(gcPressureBody); - } - } - - writer.WriteLine(); - writer.WriteLine($$""" - protected {{typeName}}(WindowsRuntimeActivationTypes.DerivedSealed _, WindowsRuntimeObjectReference activationFactoryObjectReference, in Guid iid, CreateObjectReferenceMarshalingType marshalingType) - :base(_, activationFactoryObjectReference, in iid, marshalingType) - """, isMultiline: true); - using (writer.WriteBlock()) - { - if (!string.IsNullOrEmpty(gcPressureBody)) - { - writer.WriteLine(gcPressureBody); - } - } - - writer.WriteLine(); - writer.WriteLine($$""" - protected {{typeName}}(WindowsRuntimeActivationFactoryCallback.DerivedComposed activationFactoryCallback, in Guid iid, CreateObjectReferenceMarshalingType marshalingType, WindowsRuntimeActivationArgsReference additionalParameters) - :base(activationFactoryCallback, in iid, marshalingType, additionalParameters) - """, isMultiline: true); - using (writer.WriteBlock()) - { - if (!string.IsNullOrEmpty(gcPressureBody)) - { - writer.WriteLine(gcPressureBody); - } - } - - writer.WriteLine(); - writer.WriteLine($$""" - protected {{typeName}}(WindowsRuntimeActivationFactoryCallback.DerivedSealed activationFactoryCallback, in Guid iid, CreateObjectReferenceMarshalingType marshalingType, WindowsRuntimeActivationArgsReference additionalParameters) - :base(activationFactoryCallback, in iid, marshalingType, additionalParameters) - """, isMultiline: true); - using (writer.WriteBlock()) - { - if (!string.IsNullOrEmpty(gcPressureBody)) + writer.WriteLine(); + writer.WriteLine(isMultiline: true, $$""" + protected {{typeName}}({{parameters}}) + :base({{baseArgs}}) + """); + using (writer.WriteBlock()) { - writer.WriteLine(gcPressureBody); + writer.WriteLineIf(!string.IsNullOrEmpty(gcPressureBody), gcPressureBody); } } } diff --git a/src/WinRT.Projection.Writer/Factories/ConstructorFactory.FactoryCallbacks.cs b/src/WinRT.Projection.Writer/Factories/ConstructorFactory.FactoryCallbacks.cs index f1766cefc8..cad054c562 100644 --- a/src/WinRT.Projection.Writer/Factories/ConstructorFactory.FactoryCallbacks.cs +++ b/src/WinRT.Projection.Writer/Factories/ConstructorFactory.FactoryCallbacks.cs @@ -5,6 +5,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; @@ -33,27 +34,19 @@ private static void EmitFactoryArgsStruct(IndentedTextWriter writer, ProjectionE writer.Write($"private readonly ref struct {argsName}("); for (int i = 0; i < count; i++) { - if (i > 0) - { - writer.Write(", "); - } - - MethodFactory.WriteProjectionParameter(writer, context, sig.Parameters[i]); + WriteProjectionParameterCallback p = MethodFactory.WriteProjectionParameter(context, sig.Parameters[i]); + writer.Write($"{(i > 0 ? ", " : "")}{p}"); } - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ ) { - """, isMultiline: true); + """); for (int i = 0; i < count; i++) { ParameterInfo p = sig.Parameters[i]; - string raw = p.Parameter.Name ?? "param"; - string pname = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - writer.Write(" public readonly "); - // Use the parameter's projected type (matches the constructor parameter type, including - // ReadOnlySpan/Span for array params). - MethodFactory.WriteProjectionParameterType(writer, context, p); - writer.WriteLine($" {pname} = {pname};"); + string pname = p.GetEscapedName(); + WriteProjectionParameterTypeCallback paramType = MethodFactory.WriteProjectionParameterType(context, p); + writer.WriteLine($" public readonly {paramType} {pname} = {pname};"); } writer.WriteLine("}"); } @@ -80,71 +73,75 @@ private static void EmitFactoryCallbackClass(IndentedTextWriter writer, Projecti ? "WindowsRuntimeActivationFactoryCallback.DerivedComposed" : "WindowsRuntimeActivationFactoryCallback.DerivedSealed"; writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" private sealed class {{callbackName}} : {{baseClass}} { public static readonly {{callbackName}} Instance = new(); [MethodImpl(MethodImplOptions.NoInlining)] - """, isMultiline: true); + """); + + writer.IncreaseIndent(); + if (isComposable) { // Composable Invoke signature is multi-line and includes baseInterface (in) + // innerInterface (out). - writer.WriteLine(""" - public override unsafe void Invoke( - WindowsRuntimeActivationArgsReference additionalParameters, - WindowsRuntimeObject baseInterface, - out void* innerInterface, - out void* retval) - { - """, isMultiline: true); + writer.WriteLine(isMultiline: true, """ + public override unsafe void Invoke( + WindowsRuntimeActivationArgsReference additionalParameters, + WindowsRuntimeObject baseInterface, + out void* innerInterface, + out void* retval) + { + """); } else { - // Sealed Invoke signature is multi-line.. - writer.WriteLine(""" - public override unsafe void Invoke( - WindowsRuntimeActivationArgsReference additionalParameters, - out void* retval) - { - """, isMultiline: true); + // Sealed Invoke signature is multi-line + writer.WriteLine(isMultiline: true, """ + public override unsafe void Invoke( + WindowsRuntimeActivationArgsReference additionalParameters, + out void* retval) + { + """); } // Invoke body is just 'throw null;' (no factory dispatch, no marshalling). if (context.Settings.ReferenceProjection) { + writer.DecreaseIndent(); RefModeStubFactory.EmitRefModeInvokeBody(writer); return; } - writer.WriteLine($$""" - using WindowsRuntimeObjectReferenceValue activationFactoryValue = {{factoryObjRefName}}.AsValue(); - void* ThisPtr = activationFactoryValue.GetThisPtrUnsafe(); - ref readonly {{argsName}} args = ref additionalParameters.GetValueRefUnsafe<{{argsName}}>(); - """, isMultiline: true); + writer.IncreaseIndent(); + + writer.WriteLine(isMultiline: true, $$""" + using WindowsRuntimeObjectReferenceValue activationFactoryValue = {{factoryObjRefName}}.AsValue(); + void* ThisPtr = activationFactoryValue.GetThisPtrUnsafe(); + ref readonly {{argsName}} args = ref additionalParameters.GetValueRefUnsafe<{{argsName}}>(); + """); // Bind each arg from the args struct to a local of its ABI-marshalable input type. // Bind arg locals. for (int i = 0; i < paramCount; i++) { ParameterInfo p = sig.Parameters[i]; - string raw = p.Parameter.Name ?? "param"; - string pname = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - writer.Write(" "); + string raw = p.GetRawName(); + string pname = IdentifierEscaping.EscapeIdentifier(raw); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); + // For array params, the bind type is ReadOnlySpan / Span (not the SzArray). if (cat == ParameterCategory.PassArray) { - writer.Write("ReadOnlySpan<"); - TypedefNameWriter.WriteProjectionType(writer, context, TypeSemanticsFactory.Get(((SzArrayTypeSignature)p.Type).BaseType)); - writer.Write(">"); + WriteProjectionTypeCallback elem = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(((SzArrayTypeSignature)p.Type).BaseType)); + writer.Write($"ReadOnlySpan<{elem}>"); } else if (cat == ParameterCategory.FillArray) { - writer.Write("Span<"); - TypedefNameWriter.WriteProjectionType(writer, context, TypeSemanticsFactory.Get(((SzArrayTypeSignature)p.Type).BaseType)); - writer.Write(">"); + WriteProjectionTypeCallback elem = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(((SzArrayTypeSignature)p.Type).BaseType)); + writer.Write($"Span<{elem}>"); } else { @@ -164,24 +161,26 @@ public override unsafe void Invoke( continue; } - string raw = p.Parameter.Name ?? "param"; - string pname = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; + string raw = p.GetRawName(); + string pname = IdentifierEscaping.EscapeIdentifier(raw); if (p.Type.IsNullableT()) { - TypeSignature inner = p.Type.GetNullableInnerType()!; - string innerMarshaller = AbiTypeHelpers.GetNullableInnerMarshallerName(writer, context, inner); - writer.WriteLine($" using WindowsRuntimeObjectReferenceValue __{raw} = {innerMarshaller}.BoxToUnmanaged({pname});"); + (_, string innerMarshaller) = AbiTypeHelpers.GetNullableInnerInfo(writer, context, p.Type); + writer.WriteLine($"using WindowsRuntimeObjectReferenceValue __{raw} = {innerMarshaller}.BoxToUnmanaged({pname});"); continue; } - string interopTypeName = InteropTypeNameWriter.EncodeInteropTypeName(p.Type, TypedefNameType.ABI) + ", WinRT.Interop"; - string projectedTypeName = MethodFactory.WriteProjectedSignature(context, p.Type, false); - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ConvertToUnmanaged")] - static extern WindowsRuntimeObjectReferenceValue ConvertToUnmanaged_{{raw}}([UnsafeAccessorType("{{interopTypeName}}")] object _, {{projectedTypeName}} value); - using WindowsRuntimeObjectReferenceValue __{{raw}} = ConvertToUnmanaged_{{raw}}(null, {{pname}}); - """, isMultiline: true); + string interopTypeName = InteropTypeNameWriter.GetInteropAssemblyQualifiedName(p.Type, TypedefNameType.ABI); + WriteProjectedSignatureCallback projectedTypeName = MethodFactory.WriteProjectedSignature(context, p.Type, false); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "ConvertToUnmanaged", + returnType: "WindowsRuntimeObjectReferenceValue", + functionName: $"ConvertToUnmanaged_{raw}", + interopType: interopTypeName, + parameterList: $", {projectedTypeName.Format()} value"); + writer.WriteLine($"using WindowsRuntimeObjectReferenceValue __{raw} = ConvertToUnmanaged_{raw}(null, {pname});"); } // For runtime class / object params, emit `using WindowsRuntimeObjectReferenceValue __ = ...ConvertToUnmanaged();` @@ -195,16 +194,15 @@ public override unsafe void Invoke( continue; } - if (!context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(p.Type) && !p.Type.IsObject()) + if (!context.AbiTypeKindResolver.IsRuntimeClassOrInterface(p.Type) && !p.Type.IsObject()) { continue; } - string raw = p.Parameter.Name ?? "param"; - string pname = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - writer.Write($" using WindowsRuntimeObjectReferenceValue __{raw} = "); - AbiMethodBodyFactory.EmitMarshallerConvertToUnmanaged(writer, context, p.Type, pname); - writer.WriteLine(";"); + string raw = p.GetRawName(); + string pname = IdentifierEscaping.EscapeIdentifier(raw); + EmitMarshallerConvertToUnmanagedCallback cvt = AbiMethodBodyFactory.EmitMarshallerConvertToUnmanaged(context, p.Type, pname); + writer.WriteLine($"using WindowsRuntimeObjectReferenceValue __{raw} = {cvt};"); } // For composable factories, marshal the additional `baseInterface` (which is a @@ -212,10 +210,8 @@ public override unsafe void Invoke( // using WindowsRuntimeObjectReferenceValue __baseInterface = WindowsRuntimeObjectMarshaller.ConvertToUnmanaged(baseInterface); if (isComposable) { - writer.WriteLine(""" - using WindowsRuntimeObjectReferenceValue __baseInterface = WindowsRuntimeObjectMarshaller.ConvertToUnmanaged(baseInterface); - void* __innerInterface = default; - """, isMultiline: true); + writer.WriteLine("using WindowsRuntimeObjectReferenceValue __baseInterface = WindowsRuntimeObjectMarshaller.ConvertToUnmanaged(baseInterface);"); + writer.WriteLine("void* __innerInterface = default;"); } // For mapped value-type params (DateTime, TimeSpan), emit ABI local + marshaller conversion. @@ -223,16 +219,16 @@ public override unsafe void Invoke( { ParameterInfo p = sig.Parameters[i]; - if (!context.AbiTypeShapeResolver.IsMappedAbiValueType(p.Type)) + if (!context.AbiTypeKindResolver.IsMappedAbiValueType(p.Type)) { continue; } - string raw = p.Parameter.Name ?? "param"; - string pname = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; + string raw = p.GetRawName(); + string pname = IdentifierEscaping.EscapeIdentifier(raw); string abiType = AbiTypeHelpers.GetMappedAbiTypeName(p.Type); string marshaller = AbiTypeHelpers.GetMappedMarshallerName(p.Type); - writer.WriteLine($" {abiType} __{raw} = {marshaller}.ConvertToUnmanaged({pname});"); + writer.WriteLine($"{abiType} __{raw} = {marshaller}.ConvertToUnmanaged({pname});"); } // For HResultException params, emit ABI local + ExceptionMarshaller conversion. @@ -247,9 +243,9 @@ public override unsafe void Invoke( continue; } - string raw = p.Parameter.Name ?? "param"; - string pname = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - writer.WriteLine($" global::ABI.System.Exception __{raw} = global::ABI.System.ExceptionMarshaller.ConvertToUnmanaged({pname});"); + string raw = p.GetRawName(); + string pname = IdentifierEscaping.EscapeIdentifier(raw); + writer.WriteLine($"global::ABI.System.Exception __{raw} = global::ABI.System.ExceptionMarshaller.ConvertToUnmanaged({pname});"); } // Declare InlineArray16 + ArrayPool fallback for non-blittable PassArray params @@ -258,9 +254,9 @@ public override unsafe void Invoke( for (int i = 0; i < paramCount; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is not (ParameterCategory.PassArray or ParameterCategory.FillArray)) + if (!cat.IsArrayInput()) { continue; } @@ -270,52 +266,53 @@ public override unsafe void Invoke( continue; } - if (context.AbiTypeShapeResolver.IsBlittablePrimitive(szArr.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsBlittableAbiElement(szArr.BaseType)) { continue; } hasNonBlittableArray = true; - string raw = p.Parameter.Name ?? "param"; - string callName = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; + string raw = p.GetRawName(); + string callName = IdentifierEscaping.EscapeIdentifier(raw); + ArrayTempNames names = new(raw); writer.WriteLine(); - writer.WriteLine($$""" - Unsafe.SkipInit(out InlineArray16 __{{raw}}_inlineArray); - nint[] __{{raw}}_arrayFromPool = null; - Span __{{raw}}_span = {{callName}}.Length <= 16 - ? __{{raw}}_inlineArray[..{{callName}}.Length] - : (__{{raw}}_arrayFromPool = global::System.Buffers.ArrayPool.Shared.Rent({{callName}}.Length)); - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + Unsafe.SkipInit(out InlineArray16 {{names.InlineArray}}); + nint[] {{names.ArrayFromPool}} = null; + Span {{names.Span}} = {{callName}}.Length <= 16 + ? {{names.InlineArray}}[..{{callName}}.Length] + : ({{names.ArrayFromPool}} = global::System.Buffers.ArrayPool.Shared.Rent({{callName}}.Length)); + """); if (szArr.BaseType.IsString()) { writer.WriteLine(); - writer.WriteLine($$""" - Unsafe.SkipInit(out InlineArray16 __{{raw}}_inlineHeaderArray); - HStringHeader[] __{{raw}}_headerArrayFromPool = null; - Span __{{raw}}_headerSpan = {{callName}}.Length <= 16 - ? __{{raw}}_inlineHeaderArray[..{{callName}}.Length] - : (__{{raw}}_headerArrayFromPool = global::System.Buffers.ArrayPool.Shared.Rent({{callName}}.Length)); - - Unsafe.SkipInit(out InlineArray16 __{{raw}}_inlinePinnedHandleArray); - nint[] __{{raw}}_pinnedHandleArrayFromPool = null; - Span __{{raw}}_pinnedHandleSpan = {{callName}}.Length <= 16 - ? __{{raw}}_inlinePinnedHandleArray[..{{callName}}.Length] - : (__{{raw}}_pinnedHandleArrayFromPool = global::System.Buffers.ArrayPool.Shared.Rent({{callName}}.Length)); - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + Unsafe.SkipInit(out InlineArray16 {{names.InlineHeaderArray}}); + HStringHeader[] {{names.HeaderArrayFromPool}} = null; + Span {{names.HeaderSpan}} = {{callName}}.Length <= 16 + ? {{names.InlineHeaderArray}}[..{{callName}}.Length] + : ({{names.HeaderArrayFromPool}} = global::System.Buffers.ArrayPool.Shared.Rent({{callName}}.Length)); + + Unsafe.SkipInit(out InlineArray16 {{names.InlinePinnedHandleArray}}); + nint[] {{names.PinnedHandleArrayFromPool}} = null; + Span {{names.PinnedHandleSpan}} = {{callName}}.Length <= 16 + ? {{names.InlinePinnedHandleArray}}[..{{callName}}.Length] + : ({{names.PinnedHandleArrayFromPool}} = global::System.Buffers.ArrayPool.Shared.Rent({{callName}}.Length)); + """); } } - writer.WriteLine(" void* __retval = default;"); + writer.WriteLine("void* __retval = default;"); if (hasNonBlittableArray) { - writer.WriteLine(""" - try - { - """, isMultiline: true); + writer.WriteLine(isMultiline: true, """ + try + { + """); + writer.IncreaseIndent(); } - string baseIndent = hasNonBlittableArray ? " " : " "; // For System.Type params, pre-marshal to TypeReference (must be declared OUTSIDE the // fixed() block since the fixed block pins the resulting reference). @@ -328,9 +325,9 @@ public override unsafe void Invoke( continue; } - string raw = p.Parameter.Name ?? "param"; - string pname = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - writer.WriteLine($"{baseIndent}global::ABI.System.TypeMarshaller.ConvertToUnmanagedUnsafe({pname}, out TypeReference __{raw});"); + string raw = p.GetRawName(); + string pname = IdentifierEscaping.EscapeIdentifier(raw); + writer.WriteLine($"global::ABI.System.TypeMarshaller.ConvertToUnmanagedUnsafe({pname}, out TypeReference __{raw});"); } // Open ONE combined "fixed(void* _a = ..., _b = ..., ...)" block for ALL pinnable @@ -341,13 +338,13 @@ public override unsafe void Invoke( for (int i = 0; i < paramCount; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); if (p.Type.IsString() || p.Type.IsSystemType()) { pinnableCount++; } - else if (cat is ParameterCategory.PassArray or ParameterCategory.FillArray) + else if (cat.IsArrayInput()) { pinnableCount++; } @@ -355,29 +352,25 @@ public override unsafe void Invoke( if (pinnableCount > 0) { - string indent = baseIndent; - writer.Write($"{indent}fixed(void* "); + writer.Write("fixed(void* "); bool firstPin = true; for (int i = 0; i < paramCount; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); bool isStr = p.Type.IsString(); bool isType = p.Type.IsSystemType(); - bool isArr = cat is ParameterCategory.PassArray or ParameterCategory.FillArray; + bool isArr = cat.IsArrayInput(); if (!isStr && !isType && !isArr) { continue; } - string raw = p.Parameter.Name ?? "param"; - string pname = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; + string raw = p.GetRawName(); + string pname = IdentifierEscaping.EscapeIdentifier(raw); - if (!firstPin) - { - writer.Write(", "); - } + writer.WriteIf(!firstPin, ", "); firstPin = false; writer.Write($"_{raw} = "); @@ -389,14 +382,17 @@ public override unsafe void Invoke( else if (isArr) { TypeSignature elemT = ((SzArrayTypeSignature)p.Type).BaseType; - bool isBlittableElem = context.AbiTypeShapeResolver.IsBlittablePrimitive(elemT) || context.AbiTypeShapeResolver.IsBlittableStruct(elemT); + bool isBlittableElem = context.AbiTypeKindResolver.IsBlittableAbiElement(elemT); bool isStringElem = elemT.IsString(); if (isBlittableElem) { writer.Write(pname); } - else { writer.Write($"__{raw}_span"); } + else + { + writer.Write($"__{raw}_span"); + } if (isStringElem) { @@ -409,14 +405,16 @@ public override unsafe void Invoke( writer.Write(pname); } } - writer.WriteLine($$""" + + writer.WriteLine(isMultiline: true, """ ) - {{indent}}{ - """, isMultiline: true); + { + """); + writer.IncreaseIndent(); fixedNesting = 1; + // Inside the block: emit HStringMarshaller.ConvertToUnmanagedUnsafe for each // string input. The HStringReference local lives stack-only. - string innerIndent = baseIndent + new string(' ', fixedNesting * 4); for (int i = 0; i < paramCount; i++) { ParameterInfo p = sig.Parameters[i]; @@ -426,21 +424,19 @@ public override unsafe void Invoke( continue; } - string raw = p.Parameter.Name ?? "param"; - string pname = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; - writer.WriteLine($"{innerIndent}HStringMarshaller.ConvertToUnmanagedUnsafe((char*)_{raw}, {pname}?.Length, out HStringReference __{raw});"); + string raw = p.GetRawName(); + string pname = IdentifierEscaping.EscapeIdentifier(raw); + writer.WriteLine($"HStringMarshaller.ConvertToUnmanagedUnsafe((char*)_{raw}, {pname}?.Length, out HStringReference __{raw});"); } } - string callIndent = baseIndent + new string(' ', fixedNesting * 4); - // Emit CopyToUnmanaged for non-blittable PassArray params. for (int i = 0; i < paramCount; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is not (ParameterCategory.PassArray or ParameterCategory.FillArray)) + if (!cat.IsArrayInput()) { continue; } @@ -450,51 +446,53 @@ public override unsafe void Invoke( continue; } - if (context.AbiTypeShapeResolver.IsBlittablePrimitive(szArr.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsBlittableAbiElement(szArr.BaseType)) { continue; } - string raw = p.Parameter.Name ?? "param"; - string pname = CSharpKeywords.IsKeyword(raw) ? "@" + raw : raw; + string raw = p.GetRawName(); + string pname = IdentifierEscaping.EscapeIdentifier(raw); + ArrayTempNames names = new(raw); if (szArr.BaseType.IsString()) { - writer.WriteLine($$""" - {{callIndent}}HStringArrayMarshaller.ConvertToUnmanagedUnsafe( - {{callIndent}} source: {{pname}}, - {{callIndent}} hstringHeaders: (HStringHeader*) _{{raw}}_inlineHeaderArray, - {{callIndent}} hstrings: __{{raw}}_span, - {{callIndent}} pinnedGCHandles: __{{raw}}_pinnedHandleSpan); - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + HStringArrayMarshaller.ConvertToUnmanagedUnsafe( + source: {{pname}}, + hstringHeaders: (HStringHeader*) _{{raw}}_inlineHeaderArray, + hstrings: {{names.Span}}, + pinnedGCHandles: {{names.PinnedHandleSpan}}); + """); } else { - string elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(szArr.BaseType)); - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(szArr.BaseType, TypedefNameType.Projected); - _ = elementInteropArg; - writer.WriteLine($$""" - {{callIndent}}[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "CopyToUnmanaged")] - {{callIndent}}static extern void CopyToUnmanaged_{{raw}}([UnsafeAccessorType("{{ArrayElementEncoder.GetArrayMarshallerInteropPath(szArr.BaseType)}}")] object _, ReadOnlySpan<{{elementProjected}}> span, uint length, void** data); - {{callIndent}}CopyToUnmanaged_{{raw}}(null, {{pname}}, (uint){{pname}}.Length, (void**)_{{raw}}); - """, isMultiline: true); + WriteProjectionTypeCallback elementProjected = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(szArr.BaseType)); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "CopyToUnmanaged", + returnType: "void", + functionName: $"CopyToUnmanaged_{raw}", + interopType: ArrayElementEncoder.GetArrayMarshallerInteropPath(szArr.BaseType), + parameterList: $", ReadOnlySpan<{elementProjected.Format()}> span, uint length, void** data"); + writer.WriteLine($"CopyToUnmanaged_{raw}(null, {pname}, (uint){pname}.Length, (void**)_{raw});"); } } - writer.Write($"{callIndent}RestrictedErrorInfo.ThrowExceptionForHR((*(delegate* unmanaged[MemberFunction].GetThisPtrUnsafe(). - if (context.AbiTypeShapeResolver.IsEnumType(p.Type)) + if (context.AbiTypeKindResolver.IsEnumType(p.Type)) { // No cast needed: function pointer signature uses the projected enum type. writer.Write(pname); @@ -546,11 +544,11 @@ public override unsafe void Invoke( { writer.Write($"__{raw}.ConvertToUnmanagedUnsafe()"); } - else if (context.AbiTypeShapeResolver.IsRuntimeClassOrInterface(p.Type) || p.Type.IsObject() || p.Type.IsGenericInstance()) + else if (context.AbiTypeKindResolver.IsReferenceTypeOrGenericInstance(p.Type)) { writer.Write($"__{raw}.GetThisPtrUnsafe()"); } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(p.Type)) + else if (context.AbiTypeKindResolver.IsMappedAbiValueType(p.Type)) { writer.Write($"__{raw}"); } @@ -567,44 +565,46 @@ public override unsafe void Invoke( if (isComposable) { // Pass __baseInterface.GetThisPtrUnsafe() and &__innerInterface. - writer.Write(""" + writer.Write(isMultiline: true, """ , __baseInterface.GetThisPtrUnsafe(), &__innerInterface - """, isMultiline: true); + """); } - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ , &__retval)); - """, isMultiline: true); + """); if (isComposable) { - writer.WriteLine($"{callIndent}innerInterface = __innerInterface;"); + writer.WriteLine("innerInterface = __innerInterface;"); } - writer.WriteLine($"{callIndent}retval = __retval;"); + writer.WriteLine("retval = __retval;"); // Close fixed blocks (innermost first). - for (int i = fixedNesting - 1; i >= 0; i--) + for (int i = 0; i < fixedNesting; i++) { - string indent = baseIndent + new string(' ', i * 4); - writer.WriteLine($"{indent}}}"); + writer.DecreaseIndent(); + writer.WriteLine("}"); } // Close try and emit finally with cleanup for non-blittable PassArray params. if (hasNonBlittableArray) { - writer.WriteLine(""" - } - finally - { - """, isMultiline: true); + writer.DecreaseIndent(); + writer.WriteLine(isMultiline: true, """ + } + finally + { + """); + writer.IncreaseIndent(); for (int i = 0; i < paramCount; i++) { ParameterInfo p = sig.Parameters[i]; - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); - if (cat is not (ParameterCategory.PassArray or ParameterCategory.FillArray)) + if (!cat.IsArrayInput()) { continue; } @@ -614,60 +614,67 @@ public override unsafe void Invoke( continue; } - if (context.AbiTypeShapeResolver.IsBlittablePrimitive(szArr.BaseType) || context.AbiTypeShapeResolver.IsBlittableStruct(szArr.BaseType)) + if (context.AbiTypeKindResolver.IsBlittableAbiElement(szArr.BaseType)) { continue; } - string raw = p.Parameter.Name ?? "param"; + string raw = p.GetRawName(); + ArrayTempNames names = new(raw); if (szArr.BaseType.IsString()) { writer.WriteLine(); - writer.WriteLine($$""" - HStringArrayMarshaller.Dispose(__{{raw}}_pinnedHandleSpan); - - if (__{{raw}}_pinnedHandleArrayFromPool is not null) - { - global::System.Buffers.ArrayPool.Shared.Return(__{{raw}}_pinnedHandleArrayFromPool); - } - - if (__{{raw}}_headerArrayFromPool is not null) - { - global::System.Buffers.ArrayPool.Shared.Return(__{{raw}}_headerArrayFromPool); - } - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + HStringArrayMarshaller.Dispose({{names.PinnedHandleSpan}}); + + if ({{names.PinnedHandleArrayFromPool}} is not null) + { + global::System.Buffers.ArrayPool.Shared.Return({{names.PinnedHandleArrayFromPool}}); + } + + if ({{names.HeaderArrayFromPool}} is not null) + { + global::System.Buffers.ArrayPool.Shared.Return({{names.HeaderArrayFromPool}}); + } + """); } else { - string elementInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(szArr.BaseType, TypedefNameType.Projected); - _ = elementInteropArg; writer.WriteLine(); - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "Dispose")] - static extern void Dispose_{{raw}}([UnsafeAccessorType("{{ArrayElementEncoder.GetArrayMarshallerInteropPath(szArr.BaseType)}}")] object _, uint length, void** data); - - fixed(void* _{{raw}} = __{{raw}}_span) - { - Dispose_{{raw}}(null, (uint) __{{raw}}_span.Length, (void**)_{{raw}}); - } - """, isMultiline: true); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "Dispose", + returnType: "void", + functionName: $"Dispose_{raw}", + interopType: ArrayElementEncoder.GetArrayMarshallerInteropPath(szArr.BaseType), + parameterList: ", uint length, void** data"); + writer.WriteLine(); + writer.WriteLine(isMultiline: true, $$""" + fixed(void* _{{raw}} = {{names.Span}}) + { + Dispose_{{raw}}(null, (uint) {{names.Span}}.Length, (void**)_{{raw}}); + } + """); } writer.WriteLine(); - writer.WriteLine($$""" - if (__{{raw}}_arrayFromPool is not null) - { - global::System.Buffers.ArrayPool.Shared.Return(__{{raw}}_arrayFromPool); - } - """, isMultiline: true); - } - writer.WriteLine(" }"); + writer.WriteLine(isMultiline: true, $$""" + if ({{names.ArrayFromPool}} is not null) + { + global::System.Buffers.ArrayPool.Shared.Return({{names.ArrayFromPool}}); + } + """); + } + writer.DecreaseIndent(); + writer.WriteLine("}"); } - writer.WriteLine(""" + writer.DecreaseIndent(); + writer.DecreaseIndent(); + writer.WriteLine(isMultiline: true, """ } } - """, isMultiline: true); + """); } /// @@ -682,7 +689,7 @@ private static string GetDefaultInterfaceIid(ProjectionEmitContext context, Type return "default(global::System.Guid)"; } - string result = ObjRefNameGenerator.WriteIidExpression(context, defaultIface); + string result = ObjRefNameGenerator.WriteIidExpression(context, defaultIface).Format(); return result; } } diff --git a/src/WinRT.Projection.Writer/Factories/ConstructorFactory.cs b/src/WinRT.Projection.Writer/Factories/ConstructorFactory.cs index 04b8906c79..4955ba0b44 100644 --- a/src/WinRT.Projection.Writer/Factories/ConstructorFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/ConstructorFactory.cs @@ -27,42 +27,28 @@ internal static partial class ConstructorFactory /// internal static string GetMarshalingTypeName(TypeDefinition classType) { - for (int i = 0; i < classType.CustomAttributes.Count; i++) - { - CustomAttribute attr = classType.CustomAttributes[i]; - ITypeDefOrRef? attrType = attr.Constructor?.DeclaringType; - - if (attrType is null) - { - continue; - } + CustomAttribute? attr = classType.GetAttribute(WindowsFoundationMetadata, "MarshalingBehaviorAttribute"); - if (attrType.Namespace?.Value != WindowsFoundationMetadata || - attrType.Name?.Value != "MarshalingBehaviorAttribute") - { - continue; - } + if (attr is null || attr.Signature is null) + { + return "CreateObjectReferenceMarshalingType.Unknown"; + } - if (attr.Signature is null) - { - continue; - } + for (int j = 0; j < attr.Signature.FixedArguments.Count; j++) + { + CustomAttributeArgument arg = attr.Signature.FixedArguments[j]; - for (int j = 0; j < attr.Signature.FixedArguments.Count; j++) + if (arg.Element is int v) { - CustomAttributeArgument arg = attr.Signature.FixedArguments[j]; - - if (arg.Element is int v) + return v switch { - return v switch - { - 2 => "CreateObjectReferenceMarshalingType.Agile", - 3 => "CreateObjectReferenceMarshalingType.Standard", - _ => "CreateObjectReferenceMarshalingType.Unknown", - }; - } + 2 => "CreateObjectReferenceMarshalingType.Agile", + 3 => "CreateObjectReferenceMarshalingType.Standard", + _ => "CreateObjectReferenceMarshalingType.Unknown", + }; } } + return "CreateObjectReferenceMarshalingType.Unknown"; } } diff --git a/src/WinRT.Projection.Writer/Factories/CustomAttributeFactory.cs b/src/WinRT.Projection.Writer/Factories/CustomAttributeFactory.cs index 49270e7290..34b7eee2df 100644 --- a/src/WinRT.Projection.Writer/Factories/CustomAttributeFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/CustomAttributeFactory.cs @@ -8,6 +8,7 @@ using AsmResolver; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Writers; @@ -137,6 +138,7 @@ private static string FormatCustomAttributeArg(CustomAttributeArgument arg) float f => f.ToString("R", CultureInfo.InvariantCulture) + "f", double d => d.ToString("R", CultureInfo.InvariantCulture), char c => "'" + c + "'", + // Always prepend 'global::' to typeof() arguments: when the generated file's namespace // context happens to contain a 'Windows' sub-namespace (e.g. 'TestComponentCSharp.Windows.*'), // an unqualified 'Windows.Foundation.X' would resolve to 'TestComponentCSharp.Windows.Foundation.X' @@ -246,9 +248,7 @@ private static string GetPlatform(ProjectionEmitContext context, CustomAttribute }; int contractVersion = (int)(versionRaw >> 16); - string platform = ContractPlatforms.GetPlatform(contractName, contractVersion); - - if (string.IsNullOrEmpty(platform)) + if (!ContractPlatforms.TryGetPlatform(contractName, contractVersion, out string? platform)) { return string.Empty; } @@ -276,6 +276,28 @@ private static string GetPlatform(ProjectionEmitContext context, CustomAttribute /// The active emit context. /// The member to inspect for [ContractVersion]. public static void WritePlatformAttribute(IndentedTextWriter writer, ProjectionEmitContext context, IHasCustomAttribute member) + { + int before = writer.Length; + WritePlatformAttributeBody(writer, context, member); + if (writer.Length > before) + { + writer.WriteLine(); + } + } + + /// + /// A callback emitting the attribute body (no trailing newline). Emits nothing when no [SupportedOSPlatform] applies (or when not in reference-projection mode). The blank-line suppression in the writer collapses any template line that holds only this callback when it expands to empty. + public static WritePlatformAttributeCallback WritePlatformAttribute(ProjectionEmitContext context, IHasCustomAttribute member) + { + return new(context, member); + } + + /// + /// Writes just the attribute body (no trailing newline) for + /// . + /// In non-reference-projection mode this emits nothing. + /// + internal static void WritePlatformAttributeBody(IndentedTextWriter writer, ProjectionEmitContext context, IHasCustomAttribute member) { if (!context.Settings.ReferenceProjection) { @@ -292,7 +314,7 @@ public static void WritePlatformAttribute(IndentedTextWriter writer, ProjectionE continue; } - string name = attrType.Name?.Value ?? string.Empty; + string name = attrType.GetRawName(); if (name.EndsWith("Attribute", StringComparison.Ordinal)) { @@ -305,7 +327,7 @@ public static void WritePlatformAttribute(IndentedTextWriter writer, ProjectionE if (!string.IsNullOrEmpty(platform)) { - writer.WriteLine($"[global::System.Runtime.Versioning.SupportedOSPlatform({platform})]"); + writer.Write($"[global::System.Runtime.Versioning.SupportedOSPlatform({platform})]"); return; } } @@ -316,12 +338,14 @@ public static void WritePlatformAttribute(IndentedTextWriter writer, ProjectionE /// Convenience overload of /// that leases an from , /// emits the [SupportedOSPlatform] attribute (if any) into it, and returns the - /// resulting string. Returns the empty string when no attribute is emitted. + /// resulting string (including the trailing newline). Returns the empty string when no + /// attribute is emitted. Used by callers that materialize the result once and use it + /// inside multiple calls within a loop. /// /// The active emit context. /// The member to inspect for [ContractVersion]. /// The emitted attribute, or when none. - public static string WritePlatformAttribute(ProjectionEmitContext context, IHasCustomAttribute member) + public static string GetPlatformAttribute(ProjectionEmitContext context, IHasCustomAttribute member) { using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); IndentedTextWriter writer = writerOwner.Writer; @@ -424,10 +448,7 @@ public static void WriteCustomAttributes(IndentedTextWriter writer, ProjectionEm writer.Write("("); for (int i = 0; i < kv.Value.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); writer.Write(kv.Value[i]); } @@ -439,7 +460,8 @@ public static void WriteCustomAttributes(IndentedTextWriter writer, ProjectionEm } /// - /// Writes the type-level custom attributes for . + /// Writes the projected type-level custom attributes for . Each emitted + /// attribute is on its own line, terminated with a newline. If no attributes apply, emits nothing. /// /// The writer to emit to. /// The active emit context. @@ -450,4 +472,31 @@ public static void WriteTypeCustomAttributes(IndentedTextWriter writer, Projecti WriteCustomAttributes(writer, context, type, enablePlatformAttrib); } + /// + /// A callback emitting each applicable attribute on its own line. The trailing newline of the last attribute is dropped so the callback can be interpolated into a multiline template line without producing a stray blank line. + public static WriteTypeCustomAttributesCallback WriteTypeCustomAttributes(ProjectionEmitContext context, TypeDefinition type, bool enablePlatformAttrib) + { + return new(context, type, enablePlatformAttrib); + } + + /// + /// Writes just the attribute lines (no trailing newline on the last one) for + /// . + /// Used by the callback variant so the callback can be inlined inside a multiline raw-string + /// template — the surrounding template line's own newline becomes the trailing newline. + /// + internal static void WriteTypeCustomAttributesBody(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type, bool enablePlatformAttrib) + { + int before = writer.Length; + + WriteCustomAttributes(writer, context, type, enablePlatformAttrib); + + // If anything was written, the buffer ends with a trailing newline that came from the + // last attribute's WriteLine. Trim it so the callback can be inlined into a multiline + // template line without producing a stray blank line. + if (writer.Length > before && writer.Length > 0 && writer.Back() == '\n') + { + writer.Remove(writer.Length - 1, 1); + } + } } diff --git a/src/WinRT.Projection.Writer/Factories/EventTableFactory.cs b/src/WinRT.Projection.Writer/Factories/EventTableFactory.cs index f4c459cedd..2fe822f239 100644 --- a/src/WinRT.Projection.Writer/Factories/EventTableFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/EventTableFactory.cs @@ -3,6 +3,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; @@ -25,10 +26,10 @@ internal static class EventTableFactory internal static void EmitEventTableField(IndentedTextWriter writer, ProjectionEmitContext context, EventDefinition evt, string ifaceFullName) { string evName = evt.Name?.Value ?? "Event"; - string evtType = TypedefNameWriter.WriteEventType(context, evt); + WriteEventTypeCallback evtType = TypedefNameWriter.WriteEventType(context, evt); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" private static ConditionalWeakTable<{{ifaceFullName}}, EventRegistrationTokenTable<{{evtType}}>> _{{evName}} { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -45,7 +46,7 @@ internal static void EmitEventTableField(IndentedTextWriter writer, ProjectionEm return global::System.Threading.Volatile.Read(in field) ?? MakeTable(); } } - """, isMultiline: true); + """); } /// @@ -54,10 +55,11 @@ internal static void EmitEventTableField(IndentedTextWriter writer, ProjectionEm internal static void EmitDoAbiAddEvent(IndentedTextWriter writer, ProjectionEmitContext context, EventDefinition evt, MethodSignatureInfo sig, string ifaceFullName) { string evName = evt.Name?.Value ?? "Event"; + // Handler is the (last) input parameter of the add method. The emitted parameter name in the // signature comes from WriteAbiParameterTypesPointer which uses the metadata name verbatim. string handlerRawName = sig.Parameters.Count > 0 ? (sig.Parameters[^1].Parameter.Name ?? "handler") : "handler"; - string handlerRef = CSharpKeywords.IsKeyword(handlerRawName) ? "@" + handlerRawName : handlerRawName; + string handlerRef = IdentifierEscaping.EscapeIdentifier(handlerRawName); // The cookie/token return parameter takes the metadata return param name (matches truth). string cookieName = AbiTypeHelpers.GetReturnParamName(sig); @@ -66,32 +68,38 @@ internal static void EmitDoAbiAddEvent(IndentedTextWriter writer, ProjectionEmit bool isGeneric = evtTypeSig is GenericInstanceTypeSignature; writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" { *{{cookieName}} = default; try { var __this = ComInterfaceDispatch.GetInstance<{{ifaceFullName}}>((ComInterfaceDispatch*)thisPtr); - """, isMultiline: true); + """); if (isGeneric) { - string interopTypeName = InteropTypeNameWriter.EncodeInteropTypeName(evtTypeSig, TypedefNameType.ABI) + ", WinRT.Interop"; - string projectedTypeName = MethodFactory.WriteProjectedSignature(context, evtTypeSig, false); - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ConvertToManaged")] - static extern {{projectedTypeName}} ConvertToManaged([UnsafeAccessorType("{{interopTypeName}}")] object _, void* value); - var __handler = ConvertToManaged(null, {{handlerRef}}); - """, isMultiline: true); + string interopTypeName = InteropTypeNameWriter.GetInteropAssemblyQualifiedName(evtTypeSig, TypedefNameType.ABI); + WriteProjectedSignatureCallback projectedTypeName = MethodFactory.WriteProjectedSignature(context, evtTypeSig, false); + writer.IncreaseIndent(); + writer.IncreaseIndent(); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: "ConvertToManaged", + returnType: projectedTypeName.Format(), + functionName: "ConvertToManaged", + interopType: interopTypeName, + parameterList: ", void* value"); + writer.WriteLine($"var __handler = ConvertToManaged(null, {handlerRef});"); + writer.DecreaseIndent(); + writer.DecreaseIndent(); } else { - writer.Write(" var __handler = "); - TypedefNameWriter.WriteTypeName(writer, context, TypeSemanticsFactory.Get(evtTypeSig), TypedefNameType.ABI, false); - writer.WriteLine($"Marshaller.ConvertToManaged({handlerRef});"); + WriteTypeNameCallback abiTypeName = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.Get(evtTypeSig), TypedefNameType.ABI, false); + writer.WriteLine($" var __handler = {abiTypeName}Marshaller.ConvertToManaged({handlerRef});"); } - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" *{{cookieName}} = _{{evName}}.GetOrCreateValue(__this).AddEventHandler(__handler); __this.{{evName}} += __handler; return 0; @@ -101,7 +109,7 @@ internal static void EmitDoAbiAddEvent(IndentedTextWriter writer, ProjectionEmit return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(__exception__); } } - """, isMultiline: true); + """); } /// @@ -111,10 +119,10 @@ internal static void EmitDoAbiRemoveEvent(IndentedTextWriter writer, ProjectionE { string evName = evt.Name?.Value ?? "Event"; string tokenRawName = sig.Parameters.Count > 0 ? (sig.Parameters[^1].Parameter.Name ?? "token") : "token"; - string tokenRef = CSharpKeywords.IsKeyword(tokenRawName) ? "@" + tokenRawName : tokenRawName; + string tokenRef = IdentifierEscaping.EscapeIdentifier(tokenRawName); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" { try { @@ -130,6 +138,6 @@ internal static void EmitDoAbiRemoveEvent(IndentedTextWriter writer, ProjectionE return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(__exception__); } } - """, isMultiline: true); + """); } } diff --git a/src/WinRT.Projection.Writer/Factories/InterfaceFactory.cs b/src/WinRT.Projection.Writer/Factories/InterfaceFactory.cs index dd4b9f19db..86a4f939e0 100644 --- a/src/WinRT.Projection.Writer/Factories/InterfaceFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/InterfaceFactory.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using AsmResolver; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; @@ -20,6 +22,13 @@ namespace WindowsRuntime.ProjectionWriter.Factories; /// internal static class InterfaceFactory { + /// + /// A callback emitting the [Guid("...")] attribute. + public static WriteGuidAttributeCallback WriteGuidAttribute(TypeDefinition type) + { + return new(type); + } + /// /// Writes the [Guid("...")] attribute for a type. /// @@ -54,17 +63,12 @@ public static void WriteTypeInheritance(IndentedTextWriter writer, ProjectionEmi if (hasNonObjectBase) { writer.Write(delimiter); + // Same-namespace types stay unqualified (e.g. 'AppointmentActionEntity : ActionEntity'): // only emit 'global::' when the base class lives in a different namespace. ITypeDefOrRef baseType = type.BaseType!; (string ns, string name) = baseType.Names(); - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); if (!string.IsNullOrEmpty(ns) && ns != context.CurrentNamespace) { @@ -95,15 +99,13 @@ public static void WriteTypeInheritance(IndentedTextWriter writer, ProjectionEmi if (impl.Interface is TypeDefinition ifaceTypeDef) { - isExclusive = TypeCategorization.IsExclusiveTo(ifaceTypeDef); + isExclusive = ifaceTypeDef.IsExclusiveTo; } else { - TypeDefinition? resolved = ClassMembersFactory.ResolveInterface(context.Cache, impl.Interface); - - if (resolved is not null) + if (impl.TryResolveTypeDef(context.Cache, out TypeDefinition? resolved)) { - isExclusive = TypeCategorization.IsExclusiveTo(resolved); + isExclusive = resolved.IsExclusiveTo; } } @@ -127,6 +129,13 @@ public static void WriteTypeInheritance(IndentedTextWriter writer, ProjectionEmi } } + /// + /// A callback that writes the inheritance clause to the writer it's appended to. + public static WriteTypeInheritanceCallback WriteTypeInheritance(ProjectionEmitContext context, TypeDefinition type, bool includeExclusiveInterface, bool includeWindowsRuntimeObject) + { + return new(context, type, includeExclusiveInterface, includeWindowsRuntimeObject); + } + /// /// Writes the projected name for an interface reference (TypeDefinition, TypeReference, or /// generic instance), applying mapped-type remapping (e.g., @@ -142,13 +151,7 @@ public static void WriteInterfaceTypeName(IndentedTextWriter writer, ProjectionE else if (ifaceType is TypeReference tr) { (string ns, string name) = tr.Names(); - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m1) - { - ns = m1.MappedNamespace; - name = m1.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); // Only emit the global:: prefix when the namespace doesn't match the current emit // namespace (mirrors WriteTypedefName behavior -- same-namespace stays unqualified). @@ -159,17 +162,11 @@ public static void WriteInterfaceTypeName(IndentedTextWriter writer, ProjectionE writer.Write(IdentifierEscaping.StripBackticks(name)); } - else if (ifaceType is TypeSpecification ts && ts.Signature is GenericInstanceTypeSignature gi) + else if (ifaceType.TryGetGenericInstance(out GenericInstanceTypeSignature? gi)) { ITypeDefOrRef gt = gi.GenericType; (string ns, string name) = gt.Names(); - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m2) - { - ns = m2.MappedNamespace; - name = m2.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); if (!string.IsNullOrEmpty(ns) && ns != context.CurrentNamespace) { @@ -179,10 +176,7 @@ public static void WriteInterfaceTypeName(IndentedTextWriter writer, ProjectionE writer.Write($"{IdentifierEscaping.StripBackticks(name)}<"); for (int i = 0; i < gi.TypeArguments.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); // Pass forceWriteNamespace=false so type args also respect the current namespace. TypedefNameWriter.WriteTypeName(writer, context, TypeSemanticsFactory.Get(gi.TypeArguments[i]), TypedefNameType.Projected, false); @@ -214,7 +208,7 @@ public static string WritePropType(ProjectionEmitContext context, PropertyDefini typeSig = typeSig.InstantiateGenericTypes(genericContext.Value); } - string result = MethodFactory.WriteProjectedSignature(context, typeSig, isSetProperty); + string result = MethodFactory.WriteProjectedSignature(context, typeSig, isSetProperty).Format(); return result; } @@ -223,53 +217,42 @@ public static string WritePropType(ProjectionEmitContext context, PropertyDefini /// public static void WriteInterfaceMemberSignatures(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - foreach (MethodDefinition method in type.Methods) + foreach (MethodDefinition method in type.GetNonSpecialMethods()) { - if (method.IsSpecial()) - { - continue; - } - MethodSignatureInfo sig = new(method); + // Only emit Windows.Foundation.Metadata attributes that have a projected form // (Overload, DefaultOverload, AttributeUsage, Experimental). WriteMethodCustomAttributes(writer, method); - MethodFactory.WriteProjectionReturnType(writer, context, sig); - writer.Write($" {method.Name?.Value ?? string.Empty}("); - MethodFactory.WriteParameterList(writer, context, sig); - writer.WriteLine(");"); + WriteProjectionReturnTypeCallback ret = MethodFactory.WriteProjectionReturnType(context, sig); + WriteParameterListCallback parms = MethodFactory.WriteParameterList(context, sig); + writer.WriteLine($"{ret} {method.GetRawName()}({parms});"); } foreach (PropertyDefinition prop in type.Properties) { - (MethodDefinition? getter, MethodDefinition? setter) = prop.GetPropertyMethods(); + (MethodDefinition? getter, MethodDefinition? setter) = prop.GetMethods(); + // Add 'new' when this interface has a setter-only property AND a property of the same // name exists on a base interface (typically the getter-only counterpart). This hides // the inherited member. string newKeyword = (getter is null && setter is not null - && FindPropertyInBaseInterfaces(context.Cache, type, prop.Name?.Value ?? string.Empty)) + && TryFindPropertyInBaseInterfaces(context.Cache, type, prop.GetRawName(), out _)) ? "new " : string.Empty; string propType = WritePropType(context, prop); - writer.Write($"{newKeyword}{propType} {prop.Name?.Value ?? string.Empty} {{"); + writer.Write($"{newKeyword}{propType} {prop.GetRawName()} {{"); - if (getter is not null || setter is not null) - { - writer.Write(" get;"); - } + writer.WriteIf(getter is not null || setter is not null, " get;"); - if (setter is not null) - { - writer.Write(" set;"); - } + writer.WriteIf(setter is not null, " set;"); writer.WriteLine(" }"); } foreach (EventDefinition evt in type.Events) { - writer.Write("event "); - TypedefNameWriter.WriteEventType(writer, context, evt); - writer.WriteLine($" {evt.Name?.Value ?? string.Empty};"); + WriteEventTypeCallback eventType = TypedefNameWriter.WriteEventType(context, evt); + writer.WriteLine($"event {eventType} {evt.Name?.Value};"); } } @@ -277,20 +260,22 @@ public static void WriteInterfaceMemberSignatures(IndentedTextWriter writer, Pro /// Recursively walks the base interfaces of looking for a property /// with the given . Returns true if any base interface declares /// a property with that name (used to decide whether a setter-only property in a derived - /// interface needs the new modifier to hide the base getter). + /// interface needs the new modifier to hide the base getter). When found, the base + /// interface where the property was declared is returned via . /// - private static bool FindPropertyInBaseInterfaces(MetadataCache cache, TypeDefinition type, string propName) + internal static bool TryFindPropertyInBaseInterfaces(MetadataCache cache, TypeDefinition type, string propName, [NotNullWhen(true)] out TypeDefinition? foundInterface) { if (string.IsNullOrEmpty(propName)) { + foundInterface = null; return false; } HashSet visited = []; - return FindPropertyInBaseInterfacesRecursive(cache, type, propName, visited); + return TryFindPropertyInBaseInterfacesRecursive(cache, type, propName, visited, out foundInterface); } - private static bool FindPropertyInBaseInterfacesRecursive(MetadataCache cache, TypeDefinition type, string propName, HashSet visited) + private static bool TryFindPropertyInBaseInterfacesRecursive(MetadataCache cache, TypeDefinition type, string propName, HashSet visited, [NotNullWhen(true)] out TypeDefinition? foundInterface) { foreach (InterfaceImplementation impl in type.Interfaces) { @@ -299,9 +284,7 @@ private static bool FindPropertyInBaseInterfacesRecursive(MetadataCache cache, T continue; } - TypeDefinition? baseIface = ClassMembersFactory.ResolveInterface(cache, impl.Interface); - - if (baseIface is null) + if (!impl.TryResolveTypeDef(cache, out TypeDefinition? baseIface)) { continue; } @@ -319,76 +302,21 @@ private static bool FindPropertyInBaseInterfacesRecursive(MetadataCache cache, T foreach (PropertyDefinition prop in baseIface.Properties) { - if ((prop.Name?.Value ?? string.Empty) == propName) + if (prop.GetRawName() == propName) { + foundInterface = baseIface; return true; } } - if (FindPropertyInBaseInterfacesRecursive(cache, baseIface, propName, visited)) + if (TryFindPropertyInBaseInterfacesRecursive(cache, baseIface, propName, visited, out foundInterface)) { return true; } } - return false; - } - - /// - /// Like but returns the base interface where the - /// property was found (or null if not found). - /// - internal static TypeDefinition? FindPropertyInterfaceInBases(MetadataCache cache, TypeDefinition type, string propName) - { - if (string.IsNullOrEmpty(propName)) - { - return null; - } - - HashSet visited = []; - return FindPropertyInterfaceInBasesRecursive(cache, type, propName, visited); - } - - private static TypeDefinition? FindPropertyInterfaceInBasesRecursive(MetadataCache cache, TypeDefinition type, string propName, HashSet visited) - { - foreach (InterfaceImplementation impl in type.Interfaces) - { - if (impl.Interface is null) - { - continue; - } - - TypeDefinition? baseIface = ClassMembersFactory.ResolveInterface(cache, impl.Interface); - - if (baseIface is null) - { - continue; - } - - if (baseIface == type) - { - continue; - } - - if (!visited.Add(baseIface)) - { - continue; - } - - foreach (PropertyDefinition prop in baseIface.Properties) - { - if ((prop.Name?.Value ?? string.Empty) == propName) - { - return baseIface; - } - } - TypeDefinition? deeper = FindPropertyInterfaceInBasesRecursive(cache, baseIface, propName, visited); - if (deeper is not null) - { - return deeper; - } - } - return null; + foundInterface = null; + return false; } /// @@ -421,16 +349,14 @@ private static void WriteMethodCustomAttributes(IndentedTextWriter writer, Metho } writer.Write($"[global::Windows.Foundation.Metadata.{baseName}"); + // Args: only handle string args (sufficient for [Overload(@"X")]). [DefaultOverload] has none. if (attr.Signature is not null && attr.Signature.FixedArguments.Count > 0) { writer.Write("("); for (int i = 0; i < attr.Signature.FixedArguments.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); object? val = attr.Signature.FixedArguments[i].Element; @@ -463,31 +389,32 @@ public static void WriteInterface(IndentedTextWriter writer, ProjectionEmitConte // public_exclusiveto is set (or in reference projection or component mode). if (!context.Settings.ReferenceProjection && !context.Settings.Component && - TypeCategorization.IsExclusiveTo(type) && + type.IsExclusiveTo && !context.Settings.PublicExclusiveTo && !IsDefaultOrOverridableInterfaceTypedef(context.Cache, type)) { return; } - if (context.Settings.Component && !TypeCategorization.IsExclusiveTo(type)) + if (context.Settings.Component && !type.IsExclusiveTo) { return; } writer.WriteLine(); - MetadataAttributeFactory.WriteWinRTMetadataAttribute(writer, type, context.Cache); - WriteGuidAttribute(writer, type); - writer.WriteLine(); + WriteWinRTMetadataAttributeCallback metadataAttr = MetadataAttributeFactory.WriteWinRTMetadataAttribute(type, context.Cache); + WriteGuidAttributeCallback guidAttr = WriteGuidAttribute(type); + writer.WriteLine(isMultiline: true, $$""" + {{metadataAttr}} + {{guidAttr}} + """); CustomAttributeFactory.WriteTypeCustomAttributes(writer, context, type, false); - bool isInternal = (TypeCategorization.IsExclusiveTo(type) && !context.Settings.PublicExclusiveTo) || - TypeCategorization.IsProjectionInternal(type); - writer.Write($"{(isInternal ? "internal" : "public")} interface "); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.CCW, false); - TypedefNameWriter.WriteTypeParams(writer, type); - WriteTypeInheritance(writer, context, type, false, false); - writer.WriteLine(); + bool isInternal = (type.IsExclusiveTo && !context.Settings.PublicExclusiveTo) || + type.IsProjectionInternal; + WriteTypedefNameWithTypeParamsCallback name = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.CCW, false); + WriteTypeInheritanceCallback inheritance = WriteTypeInheritance(context, type, false, false); + writer.WriteLine($"{(isInternal ? "internal" : "public")} interface {name}{inheritance}"); using (writer.WriteBlock()) { WriteInterfaceMemberSignatures(writer, context, type); @@ -498,7 +425,7 @@ public static void WriteInterface(IndentedTextWriter writer, ProjectionEmitConte /// [Overridable] interface impl on the class it's exclusive to. private static bool IsDefaultOrOverridableInterfaceTypedef(MetadataCache cache, TypeDefinition iface) { - if (!TypeCategorization.IsExclusiveTo(iface)) + if (!iface.IsExclusiveTo) { return false; } @@ -524,7 +451,7 @@ private static bool IsDefaultOrOverridableInterfaceTypedef(MetadataCache cache, continue; } - TypeDefinition? implDef = ResolveInterfaceTypeDefForExclusiveCheck(cache, implRef); + TypeDefinition? implDef = implRef.ResolveAsTypeDefinition(cache); if (implDef is not null && implDef == iface) { @@ -533,26 +460,4 @@ private static bool IsDefaultOrOverridableInterfaceTypedef(MetadataCache cache, } return false; } - - private static TypeDefinition? ResolveInterfaceTypeDefForExclusiveCheck(MetadataCache cache, ITypeDefOrRef ifaceRef) - { - if (ifaceRef is TypeDefinition td) - { - return td; - } - - if (ifaceRef is TypeReference tr) - { - (string ns, string nm) = tr.Names(); - return cache.Find(ns + "." + nm); - } - - if (ifaceRef is TypeSpecification ts && ts.Signature is GenericInstanceTypeSignature gi) - { - ITypeDefOrRef? gen = gi.GenericType; - return gen is null ? null : ResolveInterfaceTypeDefForExclusiveCheck(cache, gen); - } - - return null; - } } diff --git a/src/WinRT.Projection.Writer/Factories/MappedInterfaceStubFactory.cs b/src/WinRT.Projection.Writer/Factories/MappedInterfaceStubFactory.cs index e904a4f8c5..05ac96fba3 100644 --- a/src/WinRT.Projection.Writer/Factories/MappedInterfaceStubFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/MappedInterfaceStubFactory.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.Collections.Generic; using AsmResolver.DotNet.Signatures; using WindowsRuntime.ProjectionWriter.Generation; @@ -94,18 +95,18 @@ public static void WriteMappedInterfaceStubs(IndentedTextWriter writer, Projecti break; case "IBindableIterator": writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public bool MoveNext() => global::ABI.System.Collections.IEnumeratorMethods.MoveNext({{objRefName}}); public void Reset() => throw new NotSupportedException(); public object Current => global::ABI.System.Collections.IEnumeratorMethods.Current({{objRefName}}); - """, isMultiline: true); + """); break; case "IBindableVector": EmitNonGenericList(writer, objRefName); break; case "INotifyDataErrorInfo": writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public global::System.Collections.IEnumerable GetErrors(string propertyName) => global::ABI.System.ComponentModel.INotifyDataErrorInfoMethods.GetErrors({{objRefName}}, propertyName); public bool HasErrors {get => global::ABI.System.ComponentModel.INotifyDataErrorInfoMethods.HasErrors({{objRefName}}); } public event global::System.EventHandler ErrorsChanged @@ -113,7 +114,7 @@ public static void WriteMappedInterfaceStubs(IndentedTextWriter writer, Projecti add => global::ABI.System.ComponentModel.INotifyDataErrorInfoMethods.ErrorsChanged(this, {{objRefName}}).Subscribe(value); remove => global::ABI.System.ComponentModel.INotifyDataErrorInfoMethods.ErrorsChanged(this, {{objRefName}}).Unsubscribe(value); } - """, isMultiline: true); + """); break; } } @@ -158,17 +159,18 @@ private static void EmitGenericEnumerator(IndentedTextWriter writer, ProjectionE string prefix = "IEnumeratorMethods_" + elementId + "_"; writer.WriteLine(); - EmitUnsafeAccessor(writer, "Current", t, $"{prefix}Current", interopType, ""); - EmitUnsafeAccessor(writer, "MoveNext", "bool", $"{prefix}MoveNext", interopType, ""); + EmitUnsafeAccessors(writer, interopType, [ + new("Current", t, $"{prefix}Current", ""), + new("MoveNext", "bool", $"{prefix}MoveNext", "")]); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public bool MoveNext() => {{prefix}}MoveNext(null, {{objRefName}}); public void Reset() => throw new NotSupportedException(); - public void Dispose() {} + public void Dispose() { } public {{t}} Current => {{prefix}}Current(null, {{objRefName}}); object global::System.Collections.IEnumerator.Current => Current!; - """, isMultiline: true); + """); } private static void EmitDictionary(IndentedTextWriter writer, ProjectionEmitContext context, List args, List argSigs, string objRefName) @@ -180,11 +182,13 @@ private static void EmitDictionary(IndentedTextWriter writer, ProjectionEmitCont string k = WriteTypeNameToString(context, args[0], TypedefNameType.Projected, true); string v = WriteTypeNameToString(context, args[1], TypedefNameType.Projected, true); + // Truth uses two forms for KeyValuePair: // - 'kv' (unqualified) for plain type usages: parameters, field/return types // - 'kvNested' (fully qualified) for generic argument usages (inside IEnumerator<>, ICollection<>) string kv = $"KeyValuePair<{k}, {v}>"; string kvNested = $"global::System.Collections.Generic.KeyValuePair<{k}, {v}>"; + // Long form (always fully qualified) used for objref field-name computation // (matches the form WriteClassObjRefDefinitions emits transitively). string kvLong = kvNested; @@ -194,29 +198,31 @@ private static void EmitDictionary(IndentedTextWriter writer, ProjectionEmitCont string valInteropArg = InteropTypeNameWriter.EncodeInteropTypeName(argSigs[1], TypedefNameType.Projected); string interopType = "ABI.System.Collections.Generic.<#corlib>IDictionary'2<" + keyInteropArg + "|" + valInteropArg + ">Methods, WinRT.Interop"; string prefix = "IDictionaryMethods_" + keyId + "_" + valId + "_"; + // The IEnumerable> objref name (matches what WriteClassObjRefDefinitions emits transitively). string enumerableObjRefName = "_objRef_System_Collections_Generic_IEnumerable_" + IidExpressionGenerator.EscapeTypeNameForIdentifier(kvLong, stripGlobal: false) + "_"; writer.WriteLine(); - EmitUnsafeAccessor(writer, "Keys", $"ICollection<{k}>", $"{prefix}Keys", interopType, ""); - EmitUnsafeAccessor(writer, "Values", $"ICollection<{v}>", $"{prefix}Values", interopType, ""); - EmitUnsafeAccessor(writer, "Count", "int", $"{prefix}Count", interopType, ""); - EmitUnsafeAccessor(writer, "Item", v, $"{prefix}Item", interopType, $", {k} key"); - EmitUnsafeAccessor(writer, "Item", "void", $"{prefix}Item", interopType, $", {k} key, {v} value"); - EmitUnsafeAccessor(writer, "Add", "void", $"{prefix}Add", interopType, $", {k} key, {v} value"); - EmitUnsafeAccessor(writer, "ContainsKey", "bool", $"{prefix}ContainsKey", interopType, $", {k} key"); - EmitUnsafeAccessor(writer, "Remove", "bool", $"{prefix}Remove", interopType, $", {k} key"); - EmitUnsafeAccessor(writer, "TryGetValue", "bool", $"{prefix}TryGetValue", interopType, $", {k} key, out {v} value"); - EmitUnsafeAccessor(writer, "Add", "void", $"{prefix}Add", interopType, $", {kv} item"); - EmitUnsafeAccessor(writer, "Clear", "void", $"{prefix}Clear", interopType, ""); - EmitUnsafeAccessor(writer, "Contains", "bool", $"{prefix}Contains", interopType, $", {kv} item"); - EmitUnsafeAccessor(writer, "CopyTo", "void", $"{prefix}CopyTo", interopType, $", WindowsRuntimeObjectReference enumObjRef, {kv}[] array, int arrayIndex"); - EmitUnsafeAccessor(writer, "Remove", "bool", $"{prefix}Remove", interopType, $", {kv} item"); + EmitUnsafeAccessors(writer, interopType, [ + new("Keys", $"ICollection<{k}>", $"{prefix}Keys", ""), + new("Values", $"ICollection<{v}>", $"{prefix}Values", ""), + new("Count", "int", $"{prefix}Count", ""), + new("Item", v, $"{prefix}Item", $", {k} key"), + new("Item", "void", $"{prefix}Item", $", {k} key, {v} value"), + new("Add", "void", $"{prefix}Add", $", {k} key, {v} value"), + new("ContainsKey", "bool", $"{prefix}ContainsKey", $", {k} key"), + new("Remove", "bool", $"{prefix}Remove", $", {k} key"), + new("TryGetValue", "bool", $"{prefix}TryGetValue", $", {k} key, out {v} value"), + new("Add", "void", $"{prefix}Add", $", {kv} item"), + new("Clear", "void", $"{prefix}Clear", ""), + new("Contains", "bool", $"{prefix}Contains", $", {kv} item"), + new("CopyTo", "void", $"{prefix}CopyTo", $", WindowsRuntimeObjectReference enumObjRef, {kv}[] array, int arrayIndex"), + new("Remove", "bool", $"{prefix}Remove", $", {kv} item")]); // Public member emission order matches the WinRT IMap vtable order, NOT alphabetical. // GetEnumerator is NOT emitted here -- it's handled separately by IIterable's own // EmitGenericEnumerable invocation. - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public ICollection<{{k}}> Keys => {{prefix}}Keys(null, {{objRefName}}); public ICollection<{{v}}> Values => {{prefix}}Values(null, {{objRefName}}); public int Count => {{prefix}}Count(null, {{objRefName}}); @@ -235,7 +241,7 @@ private static void EmitDictionary(IndentedTextWriter writer, ProjectionEmitCont public bool Contains({{kv}} item) => {{prefix}}Contains(null, {{objRefName}}, item); public void CopyTo({{kv}}[] array, int arrayIndex) => {{prefix}}CopyTo(null, {{objRefName}}, {{enumerableObjRefName}}, array, arrayIndex); bool ICollection<{{kv}}>.Remove({{kv}} item) => {{prefix}}Remove(null, {{objRefName}}, item); - """, isMultiline: true); + """); } private static void EmitReadOnlyDictionary(IndentedTextWriter writer, ProjectionEmitContext context, List args, List argSigs, string objRefName) @@ -255,12 +261,13 @@ private static void EmitReadOnlyDictionary(IndentedTextWriter writer, Projection string prefix = "IReadOnlyDictionaryMethods_" + keyId + "_" + valId + "_"; writer.WriteLine(); - EmitUnsafeAccessor(writer, "Keys", $"ICollection<{k}>", $"{prefix}Keys", interopType, ""); - EmitUnsafeAccessor(writer, "Values", $"ICollection<{v}>", $"{prefix}Values", interopType, ""); - EmitUnsafeAccessor(writer, "Count", "int", $"{prefix}Count", interopType, ""); - EmitUnsafeAccessor(writer, "Item", v, $"{prefix}Item", interopType, $", {k} key"); - EmitUnsafeAccessor(writer, "ContainsKey", "bool", $"{prefix}ContainsKey", interopType, $", {k} key"); - EmitUnsafeAccessor(writer, "TryGetValue", "bool", $"{prefix}TryGetValue", interopType, $", {k} key, out {v} value"); + EmitUnsafeAccessors(writer, interopType, [ + new("Keys", $"ICollection<{k}>", $"{prefix}Keys", ""), + new("Values", $"ICollection<{v}>", $"{prefix}Values", ""), + new("Count", "int", $"{prefix}Count", ""), + new("Item", v, $"{prefix}Item", $", {k} key"), + new("ContainsKey", "bool", $"{prefix}ContainsKey", $", {k} key"), + new("TryGetValue", "bool", $"{prefix}TryGetValue", $", {k} key, out {v} value")]); // GetEnumerator is NOT emitted here -- it's handled separately by IIterable's // EmitGenericEnumerable invocation. @@ -287,17 +294,18 @@ private static void EmitReadOnlyList(IndentedTextWriter writer, ProjectionEmitCo string prefix = "IReadOnlyListMethods_" + elementId + "_"; writer.WriteLine(); - EmitUnsafeAccessor(writer, "Count", "int", $"{prefix}Count", interopType, ""); - EmitUnsafeAccessor(writer, "Item", t, $"{prefix}Item", interopType, ", int index"); + EmitUnsafeAccessors(writer, interopType, [ + new("Count", "int", $"{prefix}Count", ""), + new("Item", t, $"{prefix}Item", ", int index")]); // GetEnumerator is NOT emitted here -- it's handled separately by IIterable's // EmitGenericEnumerable invocation. writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" [global::System.Runtime.CompilerServices.IndexerName("ReadOnlyListItem")] public {{t}} this[int index] => {{prefix}}Item(null, {{objRefName}}, index); public int Count => {{prefix}}Count(null, {{objRefName}}); - """, isMultiline: true); + """); } /// @@ -305,7 +313,7 @@ private static void EmitReadOnlyList(IndentedTextWriter writer, ProjectionEmitCo /// private static string WriteTypeNameToString(ProjectionEmitContext context, TypeSemantics arg, TypedefNameType nameType, bool forceQualified) { - string result = TypedefNameWriter.WriteTypeName(context, arg, nameType, forceQualified); + string result = TypedefNameWriter.WriteTypeName(context, arg, nameType, forceQualified).Format(); return result; } @@ -333,22 +341,23 @@ private static void EmitList(IndentedTextWriter writer, ProjectionEmitContext co string prefix = "IListMethods_" + elementId + "_"; writer.WriteLine(); - EmitUnsafeAccessor(writer, "Count", "int", $"{prefix}Count", interopType, ""); - EmitUnsafeAccessor(writer, "Item", t, $"{prefix}Item", interopType, ", int index"); - EmitUnsafeAccessor(writer, "Item", "void", $"{prefix}Item", interopType, $", int index, {t} value"); - EmitUnsafeAccessor(writer, "IndexOf", "int", $"{prefix}IndexOf", interopType, $", {t} item"); - EmitUnsafeAccessor(writer, "Insert", "void", $"{prefix}Insert", interopType, $", int index, {t} item"); - EmitUnsafeAccessor(writer, "RemoveAt", "void", $"{prefix}RemoveAt", interopType, ", int index"); - EmitUnsafeAccessor(writer, "Add", "void", $"{prefix}Add", interopType, $", {t} item"); - EmitUnsafeAccessor(writer, "Clear", "void", $"{prefix}Clear", interopType, ""); - EmitUnsafeAccessor(writer, "Contains", "bool", $"{prefix}Contains", interopType, $", {t} item"); - EmitUnsafeAccessor(writer, "CopyTo", "void", $"{prefix}CopyTo", interopType, $", {t}[] array, int arrayIndex"); - EmitUnsafeAccessor(writer, "Remove", "bool", $"{prefix}Remove", interopType, $", {t} item"); + EmitUnsafeAccessors(writer, interopType, [ + new("Count", "int", $"{prefix}Count", ""), + new("Item", t, $"{prefix}Item", ", int index"), + new("Item", "void", $"{prefix}Item", $", int index, {t} value"), + new("IndexOf", "int", $"{prefix}IndexOf", $", {t} item"), + new("Insert", "void", $"{prefix}Insert", $", int index, {t} item"), + new("RemoveAt", "void", $"{prefix}RemoveAt", ", int index"), + new("Add", "void", $"{prefix}Add", $", {t} item"), + new("Clear", "void", $"{prefix}Clear", ""), + new("Contains", "bool", $"{prefix}Contains", $", {t} item"), + new("CopyTo", "void", $"{prefix}CopyTo", $", {t}[] array, int arrayIndex"), + new("Remove", "bool", $"{prefix}Remove", $", {t} item")]); // Public member emission order matches the WinRT IVector vtable order mapped to IList, // NOT alphabetical. GetEnumerator is NOT emitted here -- it's handled separately by IIterable's // own EmitGenericEnumerable invocation. - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" public int Count => {{prefix}}Count(null, {{objRefName}}); public bool IsReadOnly => false; @@ -366,7 +375,7 @@ private static void EmitList(IndentedTextWriter writer, ProjectionEmitContext co public bool Contains({{t}} item) => {{prefix}}Contains(null, {{objRefName}}, item); public void CopyTo({{t}}[] array, int arrayIndex) => {{prefix}}CopyTo(null, {{objRefName}}, array, arrayIndex); public bool Remove({{t}} item) => {{prefix}}Remove(null, {{objRefName}}, item); - """, isMultiline: true); + """); } /// @@ -375,17 +384,37 @@ private static void EmitList(IndentedTextWriter writer, ProjectionEmitContext co /// private static void EmitUnsafeAccessor(IndentedTextWriter writer, string accessName, string returnType, string functionName, string interopType, string extraParams) { - writer.WriteLine($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "{{accessName}}")] - static extern {{returnType}} {{functionName}}([UnsafeAccessorType("{{interopType}}")] object _, WindowsRuntimeObjectReference objRef{{extraParams}}); - """, isMultiline: true); + UnsafeAccessorFactory.EmitStaticMethod( + writer, + accessName: accessName, + returnType: returnType, + functionName: functionName, + interopType: interopType, + parameterList: $", WindowsRuntimeObjectReference objRef{extraParams}"); writer.WriteLine(); } + /// + /// Emits a sequence of [UnsafeAccessor] static extern declarations sharing the same + /// . Each row of is forwarded to + /// . + /// Used by the collection-stub emitters which emit table-shaped sets of accessors. + /// + private static void EmitUnsafeAccessors( + IndentedTextWriter writer, + string interopType, + params ReadOnlySpan<(string AccessName, string ReturnType, string FunctionName, string ExtraParams)> accessors) + { + foreach ((string accessName, string returnType, string functionName, string extraParams) in accessors) + { + EmitUnsafeAccessor(writer, accessName, returnType, functionName, interopType, extraParams); + } + } + private static void EmitNonGenericList(IndentedTextWriter writer, string objRefName) { writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" [global::System.Runtime.CompilerServices.IndexerName("NonGenericListItem")] public object this[int index] { @@ -405,7 +434,8 @@ public object this[int index] public void Remove(object value) => global::ABI.System.Collections.IListMethods.Remove({{objRefName}}, value); public void RemoveAt(int index) => global::ABI.System.Collections.IListMethods.RemoveAt({{objRefName}}, index); public void CopyTo(Array array, int index) => global::ABI.System.Collections.IListMethods.CopyTo({{objRefName}}, array, index); - """, isMultiline: true); + """); + // GetEnumerator is NOT emitted here -- it's handled separately by IBindableIterable's // EmitNonGenericEnumerable invocation. } diff --git a/src/WinRT.Projection.Writer/Factories/MetadataAttributeFactory.cs b/src/WinRT.Projection.Writer/Factories/MetadataAttributeFactory.cs index 5dedfc9a58..f09d0bae7f 100644 --- a/src/WinRT.Projection.Writer/Factories/MetadataAttributeFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/MetadataAttributeFactory.cs @@ -8,9 +8,12 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using WindowsRuntime.ProjectionWriter.Builders; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Resolvers; using WindowsRuntime.ProjectionWriter.Writers; namespace WindowsRuntime.ProjectionWriter.Factories; @@ -22,25 +25,6 @@ namespace WindowsRuntime.ProjectionWriter.Factories; /// internal static class MetadataAttributeFactory { - /// - /// Writes #pragma warning disable IL2026. - /// - /// The writer to emit to. - public static void WritePragmaDisableIL2026(IndentedTextWriter writer) - { - writer.WriteLine("#pragma warning disable IL2026"); - } - - /// - /// Writes #pragma warning restore IL2026. - /// - /// The writer to emit to. - public static void WritePragmaRestoreIL2026(IndentedTextWriter writer) - { - writer.WriteLine(); - writer.WriteLine("#pragma warning restore IL2026"); - } - /// /// Returns the version string embedded in the banner comment of generated files. /// MSBuild and defaults to 0.0.0-private.0). @@ -69,7 +53,7 @@ internal static string GetVersionString() /// The writer to emit the banner to. public static void WriteFileHeader(IndentedTextWriter writer) { - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" //------------------------------------------------------------------------------ // // This file was generated by cswinrt.exe version {{GetVersionString()}} @@ -78,7 +62,7 @@ public static void WriteFileHeader(IndentedTextWriter writer) // the code is regenerated. // //------------------------------------------------------------------------------ - """, isMultiline: true); + """); } /// @@ -96,16 +80,33 @@ public static string GetFileHeader() } /// - /// Writes a [WindowsRuntimeMetadata] attribute decorating with its source .winmd module name. + /// Writes a [WindowsRuntimeMetadata("<stem>")] attribute decorating with its source .winmd module name. /// /// The writer to emit to. /// The type definition. /// The metadata cache used to resolve the source module path. public static void WriteWinRTMetadataAttribute(IndentedTextWriter writer, TypeDefinition type, MetadataCache cache) + { + WriteWinRTMetadataAttributeBody(writer, type, cache); + writer.WriteLine(); + } + + /// + /// A callback emitting the attribute body (no trailing newline) so it can be interpolated into a multiline template. + public static WriteWinRTMetadataAttributeCallback WriteWinRTMetadataAttribute(TypeDefinition type, MetadataCache cache) + { + return new(type, cache); + } + + /// + /// Writes just the attribute body (no trailing newline) for . + /// Used by the callback variant to allow the attribute to be inlined inside a multiline raw-string template line. + /// + internal static void WriteWinRTMetadataAttributeBody(IndentedTextWriter writer, TypeDefinition type, MetadataCache cache) { string path = cache.GetSourcePath(type); string stem = string.IsNullOrEmpty(path) ? string.Empty : Path.GetFileNameWithoutExtension(path); - writer.WriteLine($"[WindowsRuntimeMetadata(\"{stem}\")]"); + writer.Write($"[WindowsRuntimeMetadata(\"{stem}\")]"); } /// @@ -116,10 +117,25 @@ public static void WriteWinRTMetadataAttribute(IndentedTextWriter writer, TypeDe /// The type definition. public static void WriteWinRTMetadataTypeNameAttribute(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - writer.Write("[WindowsRuntimeMetadataTypeName(\""); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.NonProjected, true); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.WriteLine("\")]"); + WriteWinRTMetadataTypeNameAttributeBody(writer, context, type); + writer.WriteLine(); + } + + /// + /// A callback emitting the attribute body (no trailing newline) so it can be interpolated into a multiline template. + public static WriteWinRTMetadataTypeNameAttributeCallback WriteWinRTMetadataTypeNameAttribute(ProjectionEmitContext context, TypeDefinition type) + { + return new(context, type); + } + + /// + /// Writes just the attribute body (no trailing newline) for . + /// + internal static void WriteWinRTMetadataTypeNameAttributeBody(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) + { + WriteTypedefNameWithTypeParamsCallback typeName = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.NonProjected, true); + + writer.Write($"""[WindowsRuntimeMetadataTypeName("{typeName}")]"""); } /// @@ -130,19 +146,56 @@ public static void WriteWinRTMetadataTypeNameAttribute(IndentedTextWriter writer /// The type definition. public static void WriteWinRTMappedTypeAttribute(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - writer.Write("[WindowsRuntimeMappedType(typeof("); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.WriteLine("))]"); + WriteWinRTMappedTypeAttributeBody(writer, context, type); + writer.WriteLine(); + } + + /// + /// A callback emitting the attribute body (no trailing newline) so it can be interpolated into a multiline template. + public static WriteWinRTMappedTypeAttributeCallback WriteWinRTMappedTypeAttribute(ProjectionEmitContext context, TypeDefinition type) + { + return new(context, type); + } + + /// + /// Writes just the attribute body (no trailing newline) for . + /// + internal static void WriteWinRTMappedTypeAttributeBody(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) + { + WriteTypedefNameWithTypeParamsCallback typeName = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.Projected, true); + + writer.Write($"[WindowsRuntimeMappedType(typeof({typeName}))]"); } /// /// Writes a [WindowsRuntimeClassName("Windows.Foundation.IReference`1<NS.Name>")] attribute for a value type. + /// Skipped entirely in reference-projection mode. /// /// The writer to emit to. /// The active emit context. /// The value type definition. public static void WriteValueTypeWinRTClassNameAttribute(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) + { + int before = writer.Length; + WriteValueTypeWinRTClassNameAttributeBody(writer, context, type); + if (writer.Length > before) + { + writer.WriteLine(); + } + } + + /// + /// A callback emitting the attribute body (no trailing newline) so it can be interpolated into a multiline template. Emits nothing in reference-projection mode (which combined with the writer's blank-line suppression collapses the would-be template line). + public static WriteValueTypeWinRTClassNameAttributeCallback WriteValueTypeWinRTClassNameAttribute(ProjectionEmitContext context, TypeDefinition type) + { + return new(context, type); + } + + /// + /// Writes just the attribute body (no trailing newline) for . + /// In reference-projection mode this emits nothing. + /// + internal static void WriteValueTypeWinRTClassNameAttributeBody(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { if (context.Settings.ReferenceProjection) { @@ -150,35 +203,77 @@ public static void WriteValueTypeWinRTClassNameAttribute(IndentedTextWriter writ } (string ns, string name) = type.Names(); - writer.WriteLine($"[WindowsRuntimeClassName(\"Windows.Foundation.IReference`1<{ns}.{name}>\")]"); + writer.Write($"[WindowsRuntimeClassName(\"Windows.Foundation.IReference`1<{ns}.{name}>\")]"); } /// /// Writes a [WindowsRuntimeReferenceType(typeof(NullableX))] attribute on a reference type. + /// Skipped entirely in reference-projection mode. /// /// The writer to emit to. /// The active emit context. /// The reference type definition. public static void WriteWinRTReferenceTypeAttribute(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) + { + int before = writer.Length; + WriteWinRTReferenceTypeAttributeBody(writer, context, type); + if (writer.Length > before) + { + writer.WriteLine(); + } + } + + /// + /// A callback emitting the attribute body (no trailing newline) so it can be interpolated into a multiline template. Emits nothing in reference-projection mode. + public static WriteWinRTReferenceTypeAttributeCallback WriteWinRTReferenceTypeAttribute(ProjectionEmitContext context, TypeDefinition type) + { + return new(context, type); + } + + /// + /// Writes just the attribute body (no trailing newline) for . + /// In reference-projection mode this emits nothing. + /// + internal static void WriteWinRTReferenceTypeAttributeBody(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { if (context.Settings.ReferenceProjection) { return; } - writer.Write("[WindowsRuntimeReferenceType(typeof("); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, false); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.WriteLine("?))]"); + WriteTypedefNameWithTypeParamsCallback typeName = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.Projected, false); + + writer.Write($"[WindowsRuntimeReferenceType(typeof({typeName}?))]"); } /// - /// Writes the [ABI.NS.NameComWrappersMarshaller] attribute. + /// Writes the [ABI.NS.NameComWrappersMarshaller] attribute. Skipped entirely in reference-projection mode. /// /// The writer to emit to. /// The active emit context. /// The type definition. public static void WriteComWrapperMarshallerAttribute(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) + { + int before = writer.Length; + WriteComWrapperMarshallerAttributeBody(writer, context, type); + if (writer.Length > before) + { + writer.WriteLine(); + } + } + + /// + /// A callback emitting the attribute body (no trailing newline) so it can be interpolated into a multiline template. Emits nothing in reference-projection mode. + public static WriteComWrapperMarshallerAttributeCallback WriteComWrapperMarshallerAttribute(ProjectionEmitContext context, TypeDefinition type) + { + return new(context, type); + } + + /// + /// Writes just the attribute body (no trailing newline) for . + /// In reference-projection mode this emits nothing. + /// + internal static void WriteComWrapperMarshallerAttributeBody(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { if (context.Settings.ReferenceProjection) { @@ -186,7 +281,7 @@ public static void WriteComWrapperMarshallerAttribute(IndentedTextWriter writer, } (string ns, string name) = type.Names(); - writer.WriteLine($"[ABI.{ns}.{IdentifierEscaping.StripBackticks(name)}ComWrappersMarshaller]"); + writer.Write($"[ABI.{ns}.{IdentifierEscaping.StripBackticks(name)}ComWrappersMarshaller]"); } /// @@ -199,47 +294,25 @@ public static void WriteComWrapperMarshallerAttribute(IndentedTextWriter writer, public static void WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { // Skip exclusive interfaces and projection-internal interfaces. - if (TypeCategorization.GetCategory(type) == TypeCategory.Interface && - (TypeCategorization.IsExclusiveTo(type) || TypeCategorization.IsProjectionInternal(type))) + if (type.IsInterface && + (type.IsExclusiveTo || type.IsProjectionInternal)) { return; } // Capture the projected type name as a string by writing into a scratch writer at indent 0. - string projectionName = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.NonProjected, true); + string projectionName = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.NonProjected, true).Format(); - writer.WriteLine(); - writer.Write($$""" - [assembly: TypeMap( - value: "{{projectionName}}", - target: typeof( - """, isMultiline: true); - if (context.Settings.Component) - { - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.ABI, true); - TypedefNameWriter.WriteTypeParams(writer, type); - } - else - { - writer.Write(projectionName); - } + // The 'target:' slot is the ABI typedef name in component mode, otherwise the projected name. + string target = (context.Settings.Component + ? TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.ABI, true) + : TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.NonProjected, true)).Format(); - writer.WriteLine($$""" - ), - trimTarget: typeof({{projectionName}}))] - """, isMultiline: true); + WriteTypeMapAttribute(writer, "WindowsRuntimeMetadataTypeMapGroup", $"\"{projectionName}\"", $"typeof({target})", $"typeof({projectionName})"); if (context.Settings.Component) { - writer.WriteLine(); - writer.Write($$""" - [assembly: TypeMapAssociation( - source: typeof({{projectionName}}), - proxy: typeof( - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.ABI, true); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.WriteLine("))]"); + WriteTypeMapAssociation(writer, "WindowsRuntimeMetadataTypeMapGroup", $"typeof({projectionName})", $"typeof({target})"); writer.WriteLine(); } } @@ -254,55 +327,24 @@ public static void WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute(Indent /// When , wraps the projected type in Windows.Foundation.IReference`1<...>. public static void WriteWinRTComWrappersTypeMapGroupAssemblyAttribute(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type, bool isValueType) { - string projectionName = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.NonProjected, true); + string projectionName = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.NonProjected, true).Format(); - writer.WriteLine(); - writer.Write(""" - [assembly: TypeMap( - value: " - """, isMultiline: true); - if (isValueType) - { - writer.Write($"Windows.Foundation.IReference`1<{projectionName}>"); - } - else - { - writer.Write(projectionName); - } + // The 'value:' slot is wrapped in 'Windows.Foundation.IReference`1<...>' for value types. + string value = isValueType ? $"Windows.Foundation.IReference`1<{projectionName}>" : projectionName; - writer.Write(""" - ", - target: typeof( - """, isMultiline: true); - if (context.Settings.Component) - { - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.ABI, true); - TypedefNameWriter.WriteTypeParams(writer, type); - } - else - { - writer.Write(projectionName); - } + // The 'target:' slot is the ABI typedef name in component mode, otherwise the projected name. + string target = (context.Settings.Component + ? TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.ABI, true) + : TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.NonProjected, true)).Format(); - writer.WriteLine($$""" - ), - trimTarget: typeof({{projectionName}}))] - """, isMultiline: true); + WriteTypeMapAttribute(writer, "WindowsRuntimeComWrappersTypeMapGroup", $"\"{value}\"", $"typeof({target})", $"typeof({projectionName})"); // For non-interface, non-struct authored types, emit proxy association. - TypeCategory cat = TypeCategorization.GetCategory(type); + TypeKind kind = TypeKindResolver.Resolve(type); - if (cat is not (TypeCategory.Interface or TypeCategory.Struct) && context.Settings.Component) + if (kind is not (TypeKind.Interface or TypeKind.Struct) && context.Settings.Component) { - writer.WriteLine(); - writer.Write($$""" - [assembly: TypeMapAssociation( - source: typeof({{projectionName}}), - proxy: typeof( - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.ABI, true); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.WriteLine("))]"); + WriteTypeMapAssociation(writer, "WindowsRuntimeComWrappersTypeMapGroup", $"typeof({projectionName})", $"typeof({target})"); writer.WriteLine(); } } @@ -323,27 +365,46 @@ public static void WriteWinRTIdicTypeMapGroupAssemblyAttribute(IndentedTextWrite } // Skip exclusive interfaces (unless idic_exclusiveto), and projection-internal types. - if ((TypeCategorization.IsExclusiveTo(type) && !context.Settings.IdicExclusiveTo) || - TypeCategorization.IsProjectionInternal(type)) + if ((type.IsExclusiveTo && !context.Settings.IdicExclusiveTo) || + type.IsProjectionInternal) { return; } + string source = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.Projected, true).Format(); + string proxy = TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.ABI, true).Format(); + + WriteTypeMapAssociation(writer, "DynamicInterfaceCastableImplementationTypeMapGroup", $"typeof({source})", $"typeof({proxy})"); writer.WriteLine(); - writer.Write(""" - [assembly: TypeMapAssociation( - source: typeof( - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.Write(""" - ), - proxy: typeof( - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.ABI, true); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.WriteLine("))]"); + } + + /// + /// Emits a single [assembly: TypeMap<>(value, target, trimTarget)] line + /// preceded by a blank line. Used by the three Write*TypeMapGroupAssemblyAttribute helpers. + /// + private static void WriteTypeMapAttribute(IndentedTextWriter writer, string group, string value, string target, string trimTarget) + { writer.WriteLine(); + writer.WriteLine(isMultiline: true, $$""" + [assembly: TypeMap<{{group}}>( + value: {{value}}, + target: {{target}}, + trimTarget: {{trimTarget}})] + """); + } + + /// + /// Emits a single [assembly: TypeMapAssociation<>(source, proxy)] line + /// preceded by a blank line. Used by the three Write*TypeMapGroupAssemblyAttribute helpers. + /// + private static void WriteTypeMapAssociation(IndentedTextWriter writer, string group, string source, string proxy) + { + writer.WriteLine(); + writer.WriteLine(isMultiline: true, $$""" + [assembly: TypeMapAssociation<{{group}}>( + source: {{source}}, + proxy: {{proxy}})] + """); } /// @@ -364,13 +425,13 @@ public static void AddDefaultInterfaceEntry(ProjectionEmitContext context, TypeD } (string typeNs, string typeName) = type.Names(); - string className = $"global::{typeNs}.{IdentifierEscaping.StripBackticks(typeName)}"; + string className = TypedefNameWriter.BuildGlobalQualifiedName(typeNs, typeName); // Resolve TypeReference -> TypeDefinition so WriteTypeName goes through the Definition // branch which knows about authored-type CCW namespacing (ABI.Impl. prefix). ITypeDefOrRef capturedIface = defaultIface; - if (capturedIface is not TypeDefinition && capturedIface is not TypeSpecification && context.Cache is not null) + if (capturedIface is not (TypeDefinition or TypeSpecification)) { TypeDefinition? resolved = capturedIface.TryResolve(context.Cache.RuntimeContext); @@ -382,7 +443,7 @@ public static void AddDefaultInterfaceEntry(ProjectionEmitContext context, TypeD // Build the interface display name via TypeSemantics so generic instantiations // (e.g. IDictionary), TypeRefs and TypeDefs are all handled correctly. - string interfaceName = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.GetFromTypeDefOrRef(capturedIface), TypedefNameType.CCW, true); + string interfaceName = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.GetFromTypeDefOrRef(capturedIface), TypedefNameType.CCW, true).Format(); _ = entries.TryAdd(className, interfaceName); } @@ -398,7 +459,7 @@ public static void AddExclusiveToInterfaceEntries(ProjectionEmitContext context, } (string typeNs, string typeName) = type.Names(); - string className = $"global::{typeNs}.{IdentifierEscaping.StripBackticks(typeName)}"; + string className = TypedefNameWriter.BuildGlobalQualifiedName(typeNs, typeName); foreach (InterfaceImplementation impl in type.Interfaces) { @@ -410,20 +471,13 @@ public static void AddExclusiveToInterfaceEntries(ProjectionEmitContext context, // Resolve the interface to a TypeDefinition for the [ExclusiveTo] check. TypeDefinition? ifaceDef = impl.Interface as TypeDefinition; - if (ifaceDef is null && context.Cache is not null) - { - ifaceDef = impl.Interface.TryResolve(context.Cache.RuntimeContext); - } + ifaceDef ??= impl.Interface.TryResolve(context.Cache.RuntimeContext); if (ifaceDef is null && impl.Interface is TypeSpecification spec && spec.Signature is GenericInstanceTypeSignature gi) { ifaceDef = gi.GenericType as TypeDefinition; - - if (ifaceDef is null && context.Cache is not null) - { - ifaceDef = gi.GenericType.TryResolve(context.Cache.RuntimeContext); - } + ifaceDef ??= gi.GenericType.TryResolve(context.Cache.RuntimeContext); } if (ifaceDef is null) @@ -431,13 +485,13 @@ public static void AddExclusiveToInterfaceEntries(ProjectionEmitContext context, continue; } - if (TypeCategorization.IsExclusiveTo(ifaceDef)) + if (ifaceDef.IsExclusiveTo) { // Resolve TypeReference -> TypeDefinition so WriteTypeName goes through the // Definition branch which knows about authored-type CCW namespacing. ITypeDefOrRef capturedIface = impl.Interface; - if (capturedIface is not TypeDefinition && capturedIface is not TypeSpecification && context.Cache is not null) + if (capturedIface is not (TypeDefinition or TypeSpecification)) { TypeDefinition? resolved = capturedIface.TryResolve(context.Cache.RuntimeContext); @@ -447,7 +501,7 @@ public static void AddExclusiveToInterfaceEntries(ProjectionEmitContext context, } } - string interfaceName = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.GetFromTypeDefOrRef(capturedIface), TypedefNameType.CCW, true); + string interfaceName = TypedefNameWriter.WriteTypeName(context, TypeSemanticsFactory.GetFromTypeDefOrRef(capturedIface), TypedefNameType.CCW, true).Format(); entries.Add(new KeyValuePair(className, interfaceName)); } } @@ -458,37 +512,29 @@ public static void AddExclusiveToInterfaceEntries(ProjectionEmitContext context, /// public static void WriteDefaultInterfacesClass(Settings settings, IReadOnlyList> sortedEntries) { - if (sortedEntries.Count == 0) - { - return; - } - - using IndentedTextWriterOwner wOwner = IndentedTextWriterPool.GetOrCreate(); - IndentedTextWriter w = wOwner.Writer; - WriteFileHeader(w); - w.WriteLine("using System;"); - w.WriteLine("using WindowsRuntime;"); - w.WriteLine(); - w.WriteLine("#pragma warning disable CSWINRT3001"); - w.WriteLine(); - w.WriteLine("namespace ABI"); - using (w.WriteBlock()) - { - foreach (KeyValuePair kv in sortedEntries) - { - w.WriteLine($"[WindowsRuntimeDefaultInterface(typeof({kv.Key}), typeof({kv.Value}))]"); - } - - w.WriteLine("internal static class WindowsRuntimeDefaultInterfaces;"); - } - - w.FlushToFile(Path.Combine(settings.OutputFolder, "WindowsRuntimeDefaultInterfaces.cs")); + WriteInterfaceMapClass(settings, sortedEntries, "WindowsRuntimeDefaultInterface", "WindowsRuntimeDefaultInterfaces", "WindowsRuntimeDefaultInterfaces.cs"); } /// /// Writes the generated WindowsRuntimeExclusiveToInterfaces.cs file. /// public static void WriteExclusiveToInterfacesClass(Settings settings, IReadOnlyList> sortedEntries) + { + WriteInterfaceMapClass(settings, sortedEntries, "WindowsRuntimeExclusiveToInterface", "WindowsRuntimeExclusiveToInterfaces", "WindowsRuntimeExclusiveToInterfaces.cs"); + } + + /// + /// Shared template for emitting an ABI namespace-level static class decorated with one + /// per entry from (mapping + /// projected type names to interface or proxy type names). Used by both + /// and . + /// + private static void WriteInterfaceMapClass( + Settings settings, + IReadOnlyList> sortedEntries, + string attributeName, + string className, + string fileName) { if (sortedEntries.Count == 0) { @@ -508,12 +554,12 @@ public static void WriteExclusiveToInterfacesClass(Settings settings, IReadOnlyL { foreach (KeyValuePair kv in sortedEntries) { - w.WriteLine($"[WindowsRuntimeExclusiveToInterface(typeof({kv.Key}), typeof({kv.Value}))]"); + w.WriteLine($"[{attributeName}(typeof({kv.Key}), typeof({kv.Value}))]"); } - w.WriteLine("internal static class WindowsRuntimeExclusiveToInterfaces;"); + w.WriteLine($"internal static class {className};"); } - w.FlushToFile(Path.Combine(settings.OutputFolder, "WindowsRuntimeExclusiveToInterfaces.cs")); + w.FlushToFile(Path.Combine(settings.OutputFolder, fileName)); } } diff --git a/src/WinRT.Projection.Writer/Factories/MethodFactory.cs b/src/WinRT.Projection.Writer/Factories/MethodFactory.cs index 216a213a12..99b2c21ffd 100644 --- a/src/WinRT.Projection.Writer/Factories/MethodFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/MethodFactory.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; -using WindowsRuntime.ProjectionWriter.Builders; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; @@ -31,16 +30,14 @@ public static void WriteProjectedSignature(IndentedTextWriter writer, Projection { // SZ arrays project as ReadOnlySpan (matches the property setter parameter // convention; pass_array semantics). + WriteProjectionTypeCallback elem = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(sz.BaseType)); if (isParameter) { - writer.Write("ReadOnlySpan<"); - TypedefNameWriter.WriteProjectionType(writer, context, TypeSemanticsFactory.Get(sz.BaseType)); - writer.Write(">"); + writer.Write($"ReadOnlySpan<{elem}>"); } else { - TypedefNameWriter.WriteProjectionType(writer, context, TypeSemanticsFactory.Get(sz.BaseType)); - writer.Write("[]"); + writer.Write($"{elem}[]"); } return; @@ -55,21 +52,11 @@ public static void WriteProjectedSignature(IndentedTextWriter writer, Projection TypedefNameWriter.WriteProjectionType(writer, context, TypeSemanticsFactory.Get(typeSig)); } - /// - /// Convenience overload of - /// that leases an from , - /// emits the projected signature into it, and returns the resulting string. - /// - /// The active emit context. - /// The signature to project. - /// When , projects SZ-arrays as (parameter convention) instead of T[]. - /// The projected signature. - public static string WriteProjectedSignature(ProjectionEmitContext context, TypeSignature typeSig, bool isParameter) + /// + /// A callback that writes the projected signature to the writer it's appended to. + public static WriteProjectedSignatureCallback WriteProjectedSignature(ProjectionEmitContext context, TypeSignature typeSig, bool isParameter) { - using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); - IndentedTextWriter writer = writerOwner.Writer; - WriteProjectedSignature(writer, context, typeSig, isParameter); - return writer.ToString(); + return new(context, typeSig, isParameter); } /// @@ -80,7 +67,7 @@ public static string WriteProjectedSignature(ProjectionEmitContext context, Type /// The parameter info. public static void WriteProjectionParameterType(IndentedTextWriter writer, ProjectionEmitContext context, ParameterInfo p) { - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); + ParameterCategory cat = ParameterCategoryResolver.Resolve(p); switch (cat) { case ParameterCategory.Out: @@ -92,14 +79,16 @@ public static void WriteProjectionParameterType(IndentedTextWriter writer, Proje WriteProjectedSignature(writer, context, p.Type, true); break; case ParameterCategory.PassArray: - writer.Write("ReadOnlySpan<"); - TypedefNameWriter.WriteProjectionType(writer, context, TypeSemanticsFactory.Get(((SzArrayTypeSignature)p.Type).BaseType)); - writer.Write(">"); + { + WriteProjectionTypeCallback elem = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(((SzArrayTypeSignature)p.Type).BaseType)); + writer.Write($"ReadOnlySpan<{elem}>"); + } break; case ParameterCategory.FillArray: - writer.Write("Span<"); - TypedefNameWriter.WriteProjectionType(writer, context, TypeSemanticsFactory.Get(((SzArrayTypeSignature)p.Type).BaseType)); - writer.Write(">"); + { + WriteProjectionTypeCallback elem = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(((SzArrayTypeSignature)p.Type).BaseType)); + writer.Write($"Span<{elem}>"); + } break; case ParameterCategory.ReceiveArray: writer.Write("out "); @@ -108,8 +97,8 @@ public static void WriteProjectionParameterType(IndentedTextWriter writer, Proje if (sz is not null) { - TypedefNameWriter.WriteProjectionType(writer, context, TypeSemanticsFactory.Get(sz.BaseType)); - writer.Write("[]"); + WriteProjectionTypeCallback elem = TypedefNameWriter.WriteProjectionType(context, TypeSemanticsFactory.Get(sz.BaseType)); + writer.Write($"{elem}[]"); } else { @@ -122,6 +111,13 @@ public static void WriteProjectionParameterType(IndentedTextWriter writer, Proje } } + /// + /// A callback that writes the projected parameter type to the writer it's appended to. + public static WriteProjectionParameterTypeCallback WriteProjectionParameterType(ProjectionEmitContext context, ParameterInfo p) + { + return new(context, p); + } + /// /// Writes the parameter name (escaped if it would clash with a C# keyword). /// @@ -129,12 +125,9 @@ public static void WriteProjectionParameterType(IndentedTextWriter writer, Proje /// The parameter info. public static void WriteParameterName(IndentedTextWriter writer, ParameterInfo p) { - string name = p.Parameter.Name ?? "param"; + string name = p.GetRawName(); - if (CSharpKeywords.IsKeyword(name)) - { - writer.Write("@"); - } + writer.WriteIf(CSharpKeywords.IsKeyword(name), "@"); writer.Write(name); } @@ -152,6 +145,13 @@ public static void WriteProjectionParameter(IndentedTextWriter writer, Projectio WriteParameterName(writer, p); } + /// + /// A callback that writes the parameter (type + name) to the writer it's appended to. + public static WriteProjectionParameterCallback WriteProjectionParameter(ProjectionEmitContext context, ParameterInfo p) + { + return new(context, p); + } + /// /// Writes the projected return type of (or "void"). /// @@ -171,6 +171,13 @@ public static void WriteProjectionReturnType(IndentedTextWriter writer, Projecti WriteProjectedSignature(writer, context, rt, false); } + /// + /// A callback that writes the projected return type to the writer it's appended to. + public static WriteProjectionReturnTypeCallback WriteProjectionReturnType(ProjectionEmitContext context, MethodSignatureInfo sig) + { + return new(context, sig); + } + /// /// Writes a comma-separated parameter list. /// @@ -181,27 +188,44 @@ public static void WriteParameterList(IndentedTextWriter writer, ProjectionEmitC { for (int i = 0; i < sig.Parameters.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); WriteProjectionParameter(writer, context, sig.Parameters[i]); } } + /// + /// A callback that writes the parameter list to the writer it's appended to. + public static WriteParameterListCallback WriteParameterList(ProjectionEmitContext context, MethodSignatureInfo sig) + { + return new(context, sig); + } + /// - /// Returns the C# literal text for a constant field's value (or empty when no constant). + /// Writes a comma-separated argument-forwarding list (parameter names with in/out modifiers). + /// When is , the list is preceded by + /// ", " (so it can be appended to a call site that already passed one or more args); + /// otherwise, the comma separator appears only between elements. /// - /// The field definition. - /// The formatted constant value, or an empty string. - public static string FormatField(FieldDefinition field) + /// The writer to emit to. + /// The active emit context. + /// The method signature whose parameters to forward. + /// When , emits a leading ", " before the first element. + public static void WriteCallArguments(IndentedTextWriter writer, ProjectionEmitContext context, MethodSignatureInfo sig, bool leadingComma) { - if (field.Constant is null) + for (int i = 0; i < sig.Parameters.Count; i++) { - return string.Empty; + WriteParameterNameWithModifierCallback p = ClassMembersFactory.WriteParameterNameWithModifier(context, sig.Parameters[i]); + string sep = (leadingComma || i > 0) ? ", " : ""; + + writer.Write($"{sep}{p}"); } + } - return ProjectionFileBuilder.FormatConstant(field.Constant); + /// + /// A callback that writes the call-argument list to the writer it's appended to. + public static WriteCallArgumentsCallback WriteCallArguments(ProjectionEmitContext context, MethodSignatureInfo sig, bool leadingComma) + { + return new(context, sig, leadingComma); } } diff --git a/src/WinRT.Projection.Writer/Factories/RefModeStubFactory.cs b/src/WinRT.Projection.Writer/Factories/RefModeStubFactory.cs index 518e09a17f..19f6b68f30 100644 --- a/src/WinRT.Projection.Writer/Factories/RefModeStubFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/RefModeStubFactory.cs @@ -20,14 +20,14 @@ internal static class RefModeStubFactory public static void EmitRefModeObjRefGetterBody(IndentedTextWriter writer) { writer.WriteLine(); - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ { get { throw null; } } - """, isMultiline: true); + """); } /// @@ -46,13 +46,15 @@ public static void EmitSyntheticPrivateCtor(IndentedTextWriter writer, string ty /// /// Emits the body of a delegate factory Invoke method in reference projection mode. /// - /// The writer to emit to. + /// The writer to emit to. Must be at the class-scope indent level on + /// entry (i.e. the inner method body and the outer class body braces are both closed by + /// this method). public static void EmitRefModeInvokeBody(IndentedTextWriter writer) { - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ throw null; } } - """, isMultiline: true); + """); } } diff --git a/src/WinRT.Projection.Writer/Factories/ReferenceImplFactory.cs b/src/WinRT.Projection.Writer/Factories/ReferenceImplFactory.cs index 99b97a9e80..6c65e09487 100644 --- a/src/WinRT.Projection.Writer/Factories/ReferenceImplFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/ReferenceImplFactory.cs @@ -3,9 +3,12 @@ using AsmResolver.DotNet; using WindowsRuntime.ProjectionWriter.Errors; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Resolvers; using WindowsRuntime.ProjectionWriter.Writers; namespace WindowsRuntime.ProjectionWriter.Factories; @@ -24,13 +27,12 @@ internal static class ReferenceImplFactory /// The type definition. public static void WriteReferenceImpl(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); + string nameStripped = type.GetStrippedName(); string visibility = context.Settings.Component ? "public" : "file"; bool blittable = AbiTypeHelpers.IsTypeBlittable(context.Cache, type); writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" {{visibility}} static unsafe class {{nameStripped}}ReferenceImpl { [FixedAddressValueType] @@ -49,16 +51,17 @@ public static nint Vtable } [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] - """, isMultiline: true); - bool isBlittableStructType = blittable && TypeCategorization.GetCategory(type) == TypeCategory.Struct; - bool isNonBlittableStructType = !blittable && TypeCategorization.GetCategory(type) == TypeCategory.Struct; + """); + bool isBlittableStructType = blittable && type.IsStruct; + bool isNonBlittableStructType = !blittable && type.IsStruct; - if ((blittable && TypeCategorization.GetCategory(type) != TypeCategory.Struct) + if ((blittable && !type.IsStruct) || isBlittableStructType) { // For blittable types and blittable structs: direct memcpy via C# struct assignment. // Even bool/char fields work because their managed layout matches the WinRT ABI. - writer.Write(""" + WriteTypedefNameCallback projected = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, true); + writer.WriteLine(isMultiline: true, $$""" public static int get_Value(void* thisPtr, void* result) { if (result is null) @@ -68,16 +71,8 @@ public static int get_Value(void* thisPtr, void* result) try { - var value = ( - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); - writer.Write(""" - )(ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr)); - *( - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); - writer.WriteLine(""" - *)result = value; + var value = ({{projected}})(ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr)); + *({{projected}}*)result = value; return 0; } catch (Exception e) @@ -85,15 +80,15 @@ public static int get_Value(void* thisPtr, void* result) return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(e); } } - """, isMultiline: true); + """); } else if (isNonBlittableStructType) { // Non-blittable struct: marshal via Marshaller.ConvertToUnmanaged then write the // (ABI) struct value into the result pointer. - string projectedName = MethodFactory.WriteProjectedSignature(context, type.ToTypeSignature(), false); - string abiName = AbiTypeHelpers.GetAbiStructTypeName(writer, context, type.ToTypeSignature()); - writer.WriteLine($$""" + WriteProjectedSignatureCallback projectedName = MethodFactory.WriteProjectedSignature(context, type.ToTypeSignature(), false); + string abiName = AbiTypeHelpers.GetAbiStructTypeName(context, type.ToTypeSignature()); + writer.WriteLine(isMultiline: true, $$""" public static int get_Value(void* thisPtr, void* result) { if (result is null) @@ -113,13 +108,13 @@ public static int get_Value(void* thisPtr, void* result) return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(e); } } - """, isMultiline: true); + """); } - else if (TypeCategorization.GetCategory(type) is TypeCategory.Class or TypeCategory.Delegate) + else if (TypeKindResolver.Resolve(type) is TypeKind.Class or TypeKind.Delegate) { // Non-blittable runtime class / delegate: marshal via Marshaller and detach. - string projectedName = MethodFactory.WriteProjectedSignature(context, type.ToTypeSignature(), false); - writer.WriteLine($$""" + WriteProjectedSignatureCallback projectedName = MethodFactory.WriteProjectedSignature(context, type.ToTypeSignature(), false); + writer.WriteLine(isMultiline: true, $$""" public static int get_Value(void* thisPtr, void* result) { if (result is null) @@ -139,31 +134,29 @@ public static int get_Value(void* thisPtr, void* result) return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(e); } } - """, isMultiline: true); + """); } else { // Defensive: should be unreachable. WriteReferenceImpl is only called for enum/struct/delegate // types (WriteAbiEnum / WriteAbiStruct / WriteAbiDelegate dispatchers). throw WellKnownProjectionWriterExceptions.UnreachableEmissionState( - $"WriteReferenceImpl: unsupported type category {TypeCategorization.GetCategory(type)} " + + $"WriteReferenceImpl: unsupported type category {TypeKindResolver.Resolve(type)} " + $"for type '{type.FullName}'. Expected enum/struct/delegate."); } // IID property: 'public static ref readonly Guid IID' pointing at the reference type's IID. + WriteIidReferenceGuidPropertyNameCallback name = IidExpressionGenerator.WriteIidReferenceGuidPropertyName(context, type); + writer.WriteLine(); - writer.Write(""" - public static ref readonly Guid IID - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref global::ABI.InterfaceIIDs. - """, isMultiline: true); - IidExpressionGenerator.WriteIidReferenceGuidPropertyName(writer, context, type); - writer.WriteLine(""" - ; + writer.WriteLine(isMultiline: true, $$""" + public static ref readonly Guid IID + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref global::ABI.InterfaceIIDs.{{name}}; + } } - } - """, isMultiline: true); + """); writer.WriteLine(); } } diff --git a/src/WinRT.Projection.Writer/Factories/StructEnumMarshallerFactory.cs b/src/WinRT.Projection.Writer/Factories/StructEnumMarshallerFactory.cs index 8a76a6670d..2723541046 100644 --- a/src/WinRT.Projection.Writer/Factories/StructEnumMarshallerFactory.cs +++ b/src/WinRT.Projection.Writer/Factories/StructEnumMarshallerFactory.cs @@ -1,11 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Resolvers; using WindowsRuntime.ProjectionWriter.Writers; namespace WindowsRuntime.ProjectionWriter.Factories; @@ -15,35 +19,53 @@ namespace WindowsRuntime.ProjectionWriter.Factories; /// internal static class StructEnumMarshallerFactory { + /// + /// Returns the instance fields of (skipping static fields and fields + /// without a signature). Used by the 4 places in this file that loop over a struct's fields. + /// + private static IEnumerable GetInstanceFields(TypeDefinition type) + { + foreach (FieldDefinition field in type.Fields) + { + if (field.IsStatic || field.Signature is null) + { + continue; + } + + yield return field; + } + } + /// /// Writes a marshaller class for a struct or enum. /// internal static void WriteStructEnumMarshallerClass(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string name = type.Name?.Value ?? string.Empty; - string nameStripped = IdentifierEscaping.StripBackticks(name); - TypeCategory cat = TypeCategorization.GetCategory(type); - // "Almost-blittable" includes blittable + bool/char fields. Excludes string/object fields. - // Use the same predicate as IsAnyStruct (which is now scoped to almost-blittable). + string nameStripped = type.GetStrippedName(); + TypeKind kind = TypeKindResolver.Resolve(type); + + // A struct field is "blittable" when its projected and ABI layouts match (no per-field + // marshalling). Detect that via AbiTypeKindResolver.IsBlittableStruct: it returns true + // for self-mapped structs with RequiresMarshaling=false and for user structs whose + // fields are all blittable. TypeDefOrRefSignature sig = type.ToTypeSignature(false) is TypeDefOrRefSignature td2 ? td2 : null!; - bool almostBlittable = cat == TypeCategory.Struct && (sig is null || context.AbiTypeShapeResolver.IsBlittableStruct(sig)); - bool isEnum = cat == TypeCategory.Enum; - // Complex structs are non-almost-blittable structs with reference fields (string, object, etc.). - bool isComplexStruct = cat == TypeCategory.Struct && !almostBlittable; + bool blittableStruct = kind == TypeKind.Struct && (sig is null || context.AbiTypeKindResolver.IsBlittableStruct(sig)); + bool isEnum = kind == TypeKind.Enum; + + // Complex structs are the remaining (non-blittable, non-mapped) structs that need a + // per-field marshaller class because at least one field is a reference type, generic + // instance, or marshalling-mapped value. + bool isNonBlittableStruct = kind == TypeKind.Struct && !blittableStruct; + // Detect Nullable reference fields to determine whether the struct's BoxToUnmanaged // call needs CreateComInterfaceFlags.TrackerSupport . bool hasReferenceFields = false; - if (isComplexStruct) + if (isNonBlittableStruct) { - foreach (FieldDefinition field in type.Fields) + foreach (FieldDefinition field in GetInstanceFields(type)) { - if (field.IsStatic || field.Signature is null) - { - continue; - } - - TypeSignature ft = field.Signature.FieldType; + TypeSignature ft = field.Signature!.FieldType; if (AbiTypeHelpers.TryGetNullablePrimitiveMarshallerName(ft, out _)) { @@ -58,54 +80,48 @@ internal static void WriteStructEnumMarshallerClass(IndentedTextWriter writer, P // public fields don't match the WinMD field layout. The truth marshaller for these // contains only BoxToUnmanaged/UnboxToManaged. (string typeNs, string typeNm) = type.Names(); - bool isMappedStruct = isComplexStruct && MappedTypes.Get(typeNs, typeNm) is not null; + bool isMappedStruct = isNonBlittableStruct && MappedTypes.Get(typeNs, typeNm) is not null; if (isMappedStruct) { - isComplexStruct = false; + isNonBlittableStruct = false; } - writer.WriteLine($$""" + WriteTypedefNameCallback abi = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.ABI, false); + WriteTypedefNameCallback projected = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.Projected, true); + + writer.WriteLine(isMultiline: true, $$""" public static unsafe class {{nameStripped}}Marshaller { - """, isMultiline: true); + """); + writer.IncreaseIndent(); - if (isComplexStruct) + if (isNonBlittableStruct) { // ConvertToUnmanaged: build ABI struct from projected struct via per-field marshalling. - writer.Write(" public static "); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.ABI, false); - writer.Write(" ConvertToUnmanaged("); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); - writer.WriteLine(""" - value) - { - return new() { - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + public static {{abi}} ConvertToUnmanaged({{projected}} value) + { + return new() { + """); + writer.IncreaseIndent(); + writer.IncreaseIndent(); bool first = true; - foreach (FieldDefinition field in type.Fields) + foreach (FieldDefinition field in GetInstanceFields(type)) { - if (field.IsStatic || field.Signature is null) - { - continue; - } - string fname = field.Name?.Value ?? ""; - TypeSignature ft = field.Signature.FieldType; + TypeSignature ft = field.Signature!.FieldType; - if (!first) - { - writer.WriteLine(","); - } + writer.WriteLineIf(!first, ","); first = false; - writer.Write($" {fname} = "); + writer.Write($"{fname} = "); if (ft.IsString()) { writer.Write($"HStringMarshaller.ConvertToUnmanaged(value.{fname})"); } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(ft)) + else if (context.AbiTypeKindResolver.IsMappedAbiValueType(ft)) { writer.Write($"{AbiTypeHelpers.GetMappedMarshallerName(ft)}.ConvertToUnmanaged(value.{fname})"); } @@ -118,11 +134,11 @@ public static unsafe class {{nameStripped}}Marshaller } else if (ft is TypeDefOrRefSignature ftd && AbiTypeHelpers.TryResolveStructTypeDef(context.Cache, ftd) is TypeDefinition fieldStructTd - && TypeCategorization.GetCategory(fieldStructTd) == TypeCategory.Struct + && fieldStructTd.IsStruct && !AbiTypeHelpers.IsTypeBlittable(context.Cache, fieldStructTd)) { // Nested non-blittable struct: marshal via its Marshaller. - writer.Write($"{IdentifierEscaping.StripBackticks(fieldStructTd.Name?.Value ?? string.Empty)}Marshaller.ConvertToUnmanaged(value.{fname})"); + writer.Write($"{fieldStructTd.GetStrippedName()}Marshaller.ConvertToUnmanaged(value.{fname})"); } else if (AbiTypeHelpers.TryGetNullablePrimitiveMarshallerName(ft, out string? nullableMarshaller)) { @@ -133,56 +149,40 @@ public static unsafe class {{nameStripped}}Marshaller writer.Write($"value.{fname}"); } } - writer.WriteLine(); - writer.Write(""" - }; - } - public static - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); - writer.Write(" ConvertToManaged("); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.ABI, false); + // - In component mode: emit object initializer with named field assignments // (positional ctor not always available on authored types). // - In non-component mode: emit positional constructor (matches the auto-generated // primary constructor on projected struct types). bool useObjectInitializer = context.Settings.Component; - writer.Write(""" - value) - { - return new - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); - writer.WriteLine(useObjectInitializer ? "(){" : "("); + writer.WriteLine(); + writer.DecreaseIndent(); + writer.WriteLine("};"); + writer.DecreaseIndent(); + writer.WriteLine("}"); + writer.WriteLine(isMultiline: true, $$""" + public static {{projected}} ConvertToManaged({{abi}} value) + { + return new {{projected}}{{(useObjectInitializer ? "(){" : "(")}} + """); + writer.IncreaseIndent(); + writer.IncreaseIndent(); first = true; - foreach (FieldDefinition field in type.Fields) + foreach (FieldDefinition field in GetInstanceFields(type)) { - if (field.IsStatic || field.Signature is null) - { - continue; - } - string fname = field.Name?.Value ?? ""; - TypeSignature ft = field.Signature.FieldType; + TypeSignature ft = field.Signature!.FieldType; - if (!first) - { - writer.WriteLine(","); - } + writer.WriteLineIf(!first, ","); first = false; - writer.Write(" "); - - if (useObjectInitializer) - { - writer.Write($"{fname} = "); - } + writer.Write(useObjectInitializer ? $"{fname} = " : ""); if (ft.IsString()) { writer.Write($"HStringMarshaller.ConvertToManaged(value.{fname})"); } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(ft)) + else if (context.AbiTypeKindResolver.IsMappedAbiValueType(ft)) { writer.Write($"{AbiTypeHelpers.GetMappedMarshallerName(ft)}.ConvertToManaged(value.{fname})"); } @@ -195,11 +195,11 @@ public static } else if (ft is TypeDefOrRefSignature ftd2 && AbiTypeHelpers.TryResolveStructTypeDef(context.Cache, ftd2) is TypeDefinition fieldStructTd2 - && TypeCategorization.GetCategory(fieldStructTd2) == TypeCategory.Struct + && fieldStructTd2.IsStruct && !AbiTypeHelpers.IsTypeBlittable(context.Cache, fieldStructTd2)) { // Nested non-blittable struct: convert via its Marshaller. - writer.Write($"{IdentifierEscaping.StripBackticks(fieldStructTd2.Name?.Value ?? string.Empty)}Marshaller.ConvertToManaged(value.{fname})"); + writer.Write($"{fieldStructTd2.GetStrippedName()}Marshaller.ConvertToManaged(value.{fname})"); } else if (AbiTypeHelpers.TryGetNullablePrimitiveMarshallerName(ft, out string? nullableMarshaller)) { @@ -211,27 +211,23 @@ public static } } writer.WriteLine(); - writer.WriteLine(useObjectInitializer ? " };" : " );"); - writer.WriteLine(" }"); - writer.Write(" public static void Dispose("); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.ABI, false); - writer.WriteLine(""" - value) - { - """, isMultiline: true); - foreach (FieldDefinition field in type.Fields) - { - if (field.IsStatic || field.Signature is null) + writer.DecreaseIndent(); + writer.WriteLine(useObjectInitializer ? "};" : ");"); + writer.DecreaseIndent(); + writer.WriteLine("}"); + writer.WriteLine(isMultiline: true, $$""" + public static void Dispose({{abi}} value) { - continue; - } - + """); + writer.IncreaseIndent(); + foreach (FieldDefinition field in GetInstanceFields(type)) + { string fname = field.Name?.Value ?? ""; - TypeSignature ft = field.Signature.FieldType; + TypeSignature ft = field.Signature!.FieldType; if (ft.IsString()) { - writer.WriteLine($" HStringMarshaller.Free(value.{fname});"); + writer.WriteLine($"HStringMarshaller.Free(value.{fname});"); } else if (ft.IsHResultException()) { @@ -239,7 +235,7 @@ public static // (the ABI representation is just an int HRESULT). Skip Dispose entirely. continue; } - else if (context.AbiTypeShapeResolver.IsMappedAbiValueType(ft)) + else if (context.AbiTypeKindResolver.IsMappedAbiValueType(ft)) { // Mapped value types (DateTime/TimeSpan) have no per-value resources to // release — the ABI representation is just an int64 @@ -247,122 +243,79 @@ public static } else if (ft is TypeDefOrRefSignature ftd3 && AbiTypeHelpers.TryResolveStructTypeDef(context.Cache, ftd3) is TypeDefinition fieldStructTd3 - && TypeCategorization.GetCategory(fieldStructTd3) == TypeCategory.Struct + && fieldStructTd3.IsStruct && !AbiTypeHelpers.IsTypeBlittable(context.Cache, fieldStructTd3)) { // Nested non-blittable struct: dispose via its Marshaller. - string nestedNs = fieldStructTd3.Namespace?.Value ?? string.Empty; - string nestedNm = IdentifierEscaping.StripBackticks(fieldStructTd3.Name?.Value ?? string.Empty); - writer.WriteLine($" global::ABI.{nestedNs}.{nestedNm}Marshaller.Dispose(value.{fname});"); + string nestedNs = fieldStructTd3.GetRawNamespace(); + string nestedNm = fieldStructTd3.GetStrippedName(); + writer.WriteLine($"global::ABI.{nestedNs}.{nestedNm}Marshaller.Dispose(value.{fname});"); } else if (AbiTypeHelpers.TryGetNullablePrimitiveMarshallerName(ft, out _)) { - writer.WriteLine($" WindowsRuntimeUnknownMarshaller.Free(value.{fname});"); + writer.WriteLine($"WindowsRuntimeUnknownMarshaller.Free(value.{fname});"); } } - writer.WriteLine(" }"); + writer.DecreaseIndent(); + writer.WriteLine("}"); } - // BoxToUnmanaged: same pattern for all (enum, almost-blittable, complex). + // BoxToUnmanaged: same pattern for all (enum, blittable struct, complex struct, mapped struct). // Truth uses CreateComInterfaceFlags.TrackerSupport when the struct has reference type // fields (Nullable, etc.) to avoid GC issues with the boxed managed object reference. - writer.Write(" public static WindowsRuntimeObjectReferenceValue BoxToUnmanaged("); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); - - if (isEnum || almostBlittable || isComplexStruct) - { - writer.Write($$""" - ? value) - { - return WindowsRuntimeValueTypeMarshaller.BoxToUnmanaged(value, CreateComInterfaceFlags.{{(hasReferenceFields ? "TrackerSupport" : "None")}}, in - """, isMultiline: true); - ObjRefNameGenerator.WriteIidReferenceExpression(writer, type); - writer.WriteLine(""" - ); - } - """, isMultiline: true); - } - else - { - // Mapped struct (Duration/KeyTime/etc.): BoxToUnmanaged is still required because the - // public projected type still routes through this marshaller (it just lacks per-field - // ConvertToUnmanaged/ConvertToManaged because the field layout doesn't match). - writer.Write(""" - ? value) - { - return WindowsRuntimeValueTypeMarshaller.BoxToUnmanaged(value, CreateComInterfaceFlags.None, in - """, isMultiline: true); - ObjRefNameGenerator.WriteIidReferenceExpression(writer, type); - writer.WriteLine(""" - ); - } - """, isMultiline: true); - } + // Mapped struct (Duration/KeyTime/etc.) variants always use None — the public projected + // type still routes through this marshaller (it just lacks per-field + // ConvertToUnmanaged/ConvertToManaged because the field layout doesn't match). + string boxFlags = (isEnum || blittableStruct || isNonBlittableStruct) && hasReferenceFields ? "TrackerSupport" : "None"; + WriteIidReferenceExpressionCallback boxIidRef = ObjRefNameGenerator.WriteIidReferenceExpression(type); - // UnboxToManaged: simple for almost-blittable; for complex, unbox to ABI struct then ConvertToManaged. - writer.Write(" public static "); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); + writer.WriteLine(isMultiline: true, $$""" + public static WindowsRuntimeObjectReferenceValue BoxToUnmanaged({{projected}}? value) + { + return WindowsRuntimeValueTypeMarshaller.BoxToUnmanaged(value, CreateComInterfaceFlags.{{boxFlags}}, in {{boxIidRef}}); + } + """); - if (isEnum || almostBlittable) + // UnboxToManaged: simple for blittable and mapped structs; for complex, unbox to + // ABI struct then ConvertToManaged. Mapped struct unboxes directly to projected type (no + // per-field ConvertToManaged needed because the projected struct's field layout matches + // the WinMD struct layout). + if (isNonBlittableStruct) { - writer.Write(""" - ? UnboxToManaged(void* value) - { - return WindowsRuntimeValueTypeMarshaller.UnboxToManaged< - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); - writer.WriteLine(""" - >(value); - } - """, isMultiline: true); - } - else if (isComplexStruct) - { - writer.WriteLine(""" - ? UnboxToManaged(void* value) - { - - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.ABI, false); - writer.Write("? abi = WindowsRuntimeValueTypeMarshaller.UnboxToManaged<"); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.ABI, false); - writer.WriteLine(""" - >(value); - return abi.HasValue ? ConvertToManaged(abi.GetValueOrDefault()) : null; - } - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + public static {{projected}}? UnboxToManaged(void* value) + { + {{abi}}? abi = WindowsRuntimeValueTypeMarshaller.UnboxToManaged<{{abi}}>(value); + return abi.HasValue ? ConvertToManaged(abi.GetValueOrDefault()) : null; + } + """); } else { - // Mapped struct: unbox directly to projected type (no per-field ConvertToManaged needed - // because the projected struct's field layout matches the WinMD struct layout). - writer.Write(""" - ? UnboxToManaged(void* value) - { - return WindowsRuntimeValueTypeMarshaller.UnboxToManaged< - """, isMultiline: true); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); - writer.WriteLine(""" - >(value); - } - """, isMultiline: true); + writer.WriteLine(isMultiline: true, $$""" + public static {{projected}}? UnboxToManaged(void* value) + { + return WindowsRuntimeValueTypeMarshaller.UnboxToManaged<{{projected}}>(value); + } + """); } + writer.DecreaseIndent(); writer.WriteLine("}"); writer.WriteLine(); // Emit the InterfaceEntriesImpl static class and the proper ComWrappersMarshallerAttribute // class derived from WindowsRuntimeComWrappersMarshallerAttribute (matches truth). - // For enums and almost-blittable structs, GetOrCreateComInterfaceForObject uses None. + // For enums and blittable structs, GetOrCreateComInterfaceForObject uses None. // For complex structs (with reference fields), it uses TrackerSupport. // For complex structs, CreateObject converts via the *Marshaller.ConvertToManaged after // unboxing to the ABI struct. - if (isEnum || almostBlittable || isComplexStruct) + if (isEnum || blittableStruct || isNonBlittableStruct) { - string iidRefExpr = ObjRefNameGenerator.WriteIidReferenceExpression(type); + WriteIidReferenceExpressionCallback iidRefExpr = ObjRefNameGenerator.WriteIidReferenceExpression(type); // InterfaceEntriesImpl - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" file static class {{nameStripped}}InterfaceEntriesImpl { [FixedAddressValueType] @@ -388,17 +341,18 @@ file static class {{nameStripped}}InterfaceEntriesImpl Entries.IUnknown.Vtable = global::WindowsRuntime.InteropServices.IUnknownImpl.Vtable; } } - """, isMultiline: true); + """); writer.WriteLine(); + // is NOT emitted for STRUCTS (the attribute is supplied by cswinrtgen instead). Enums // and other types still emit it from write_abi_enum/etc. - if (context.Settings.Component && cat == TypeCategory.Struct) + if (context.Settings.Component && kind == TypeKind.Struct) { return; } // ComWrappersMarshallerAttribute (full body) - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" internal sealed unsafe class {{nameStripped}}ComWrappersMarshallerAttribute : WindowsRuntimeComWrappersMarshallerAttribute { public override void* GetOrCreateComInterfaceForObject(object value) @@ -415,33 +369,31 @@ internal sealed unsafe class {{nameStripped}}ComWrappersMarshallerAttribute : Wi public override object CreateObject(void* value, out CreatedWrapperFlags wrapperFlags) { wrapperFlags = CreatedWrapperFlags.NonWrapping; - """, isMultiline: true); - if (isComplexStruct) + """); + writer.IncreaseIndent(); + writer.IncreaseIndent(); + if (isNonBlittableStruct) { - writer.Write($" return {nameStripped}Marshaller.ConvertToManaged(WindowsRuntimeValueTypeMarshaller.UnboxToManagedUnsafe<"); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.ABI, true); - writer.WriteLine($">(value, in {iidRefExpr}));"); + WriteTypedefNameCallback abiFq = TypedefNameWriter.WriteTypedefName(context, type, TypedefNameType.ABI, true); + writer.WriteLine($"return {nameStripped}Marshaller.ConvertToManaged(WindowsRuntimeValueTypeMarshaller.UnboxToManagedUnsafe<{abiFq}>(value, in {iidRefExpr}));"); } else { - writer.Write(" return WindowsRuntimeValueTypeMarshaller.UnboxToManagedUnsafe<"); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.Projected, true); - writer.WriteLine($">(value, in {iidRefExpr});"); + writer.WriteLine($"return WindowsRuntimeValueTypeMarshaller.UnboxToManagedUnsafe<{projected}>(value, in {iidRefExpr});"); } - - writer.WriteLine(""" - } - } - """, isMultiline: true); + writer.DecreaseIndent(); + writer.WriteLine("}"); + writer.DecreaseIndent(); + writer.WriteLine("}"); } else { // Fallback: keep the placeholder class so consumer attribute references resolve. - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" internal sealed class {{nameStripped}}ComWrappersMarshallerAttribute : global::System.Attribute { } - """, isMultiline: true); + """); } } } diff --git a/src/WinRT.Projection.Writer/Factories/UnsafeAccessorFactory.cs b/src/WinRT.Projection.Writer/Factories/UnsafeAccessorFactory.cs new file mode 100644 index 0000000000..4323b9f7fa --- /dev/null +++ b/src/WinRT.Projection.Writer/Factories/UnsafeAccessorFactory.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Factories; + +/// +/// Centralised emitters for [UnsafeAccessor] static extern declarations. Consolidates +/// the boilerplate around the attribute scaffolding so call sites only need to express the parts +/// that vary (access name, return type, function name, interop type, and parameter list). +/// +internal static class UnsafeAccessorFactory +{ + /// + /// Emits an [UnsafeAccessor(UnsafeAccessorKind.StaticMethod)] static extern declaration + /// targeting a static method on the type identified by : + /// + /// [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "{accessName}")] + /// static extern {returnType} {functionName}([UnsafeAccessorType("{interopType}")] object _{parameterList}); + /// + /// + /// The writer to emit to. + /// The metadata name of the static method being accessed. + /// The C# return type of the accessor (as a syntax string). + /// The C# name of the accessor (i.e. the local extern method). + /// The assembly-qualified interop type the accessor punches into. + /// The trailing parameter list (after the object _ parameter), + /// including the leading comma when non-empty. Pass for the parameter-less form. + public static void EmitStaticMethod( + IndentedTextWriter writer, + string accessName, + string returnType, + string functionName, + string interopType, + string parameterList) + { + writer.WriteLine(isMultiline: true, $$""" + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "{{accessName}}")] + static extern {{returnType}} {{functionName}}([UnsafeAccessorType("{{interopType}}")] object _{{parameterList}}); + """); + } + + /// + /// Emits an [UnsafeAccessor(UnsafeAccessorKind.Constructor)] static extern declaration + /// whose return type is annotated with [return: UnsafeAccessorType("{interopType}")]: + /// + /// [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + /// [return: UnsafeAccessorType("{interopType}")] + /// static extern object {functionName}({parameterList}); + /// + /// + /// The writer to emit to. + /// The assembly-qualified interop type whose constructor is being accessed. + /// The C# name of the constructor accessor (i.e. the local extern method). + /// The constructor's full parameter list (no leading comma). + public static void EmitConstructorReturningObject( + IndentedTextWriter writer, + string interopType, + string functionName, + string parameterList) + { + writer.WriteLine(isMultiline: true, $$""" + [UnsafeAccessor(UnsafeAccessorKind.Constructor)] + [return: UnsafeAccessorType("{{interopType}}")] + static extern object {{functionName}}({{parameterList}}); + """); + } + + /// + /// Emits the [UnsafeAccessor] extern method declaration that exposes the IID for a + /// generic interface instantiation. The declaration is written as a contiguous multi-line + /// chunk; on completion the writer's buffer ends with a newline. + /// + /// The writer to emit to. + /// The active emit context. + /// The generic interface instantiation whose IID accessor is being emitted. + /// When , the accessor's parameter type is + /// object? (used inside #nullable enable regions); otherwise object. + public static void EmitIidAccessor(IndentedTextWriter writer, ProjectionEmitContext context, GenericInstanceTypeSignature gi, bool isInNullableContext = false) + { + string propName = ObjRefNameGenerator.BuildIidPropertyNameForGenericInterface(context, gi); + string interopName = InteropTypeNameWriter.EncodeInteropTypeName(gi, TypedefNameType.InteropIID); + + writer.Write(isMultiline: true, $$""" + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "get_IID_{{interopName}}")] + static extern ref readonly Guid {{propName}}([UnsafeAccessorType("ABI.InterfaceIIDs, WinRT.Interop")] object + """); + + writer.WriteIf(isInNullableContext, "?"); + + writer.WriteLine(" _);"); + } + + /// + /// Convenience overload of + /// that leases an from , + /// emits the IID accessor declaration into it, and returns the resulting string. + /// + /// The active emit context. + /// The generic interface instantiation whose IID accessor is being emitted. + /// When , the accessor's parameter type is object?; otherwise object. + /// The emitted IID accessor declaration. + public static string EmitIidAccessor(ProjectionEmitContext context, GenericInstanceTypeSignature gi, bool isInNullableContext = false) + { + using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); + IndentedTextWriter writer = writerOwner.Writer; + EmitIidAccessor(writer, context, gi, isInNullableContext); + return writer.ToString(); + } +} diff --git a/src/WinRT.Projection.Writer/Generation/ProjectionEmitContext.cs b/src/WinRT.Projection.Writer/Generation/ProjectionEmitContext.cs index 36330867ee..1be2c4aa9e 100644 --- a/src/WinRT.Projection.Writer/Generation/ProjectionEmitContext.cs +++ b/src/WinRT.Projection.Writer/Generation/ProjectionEmitContext.cs @@ -51,7 +51,7 @@ internal sealed class ProjectionEmitContext(Settings settings, MetadataCache cac /// /// Gets the resolver used to classify type signatures by their ABI marshalling shape. /// - public AbiTypeShapeResolver AbiTypeShapeResolver { get; } = new AbiTypeShapeResolver(cache); + public AbiTypeKindResolver AbiTypeKindResolver { get; } = new AbiTypeKindResolver(cache); /// /// Gets a value indicating whether platform-attribute computation should suppress platforms @@ -94,9 +94,11 @@ public PlatformSuppressionScope EnterPlatformSuppressionScope(string platform) { bool prevCheck = CheckPlatform; string prevPlatform = Platform; + CheckPlatform = true; Platform = platform; - return new PlatformSuppressionScope(this, prevCheck, prevPlatform); + + return new(this, prevCheck, prevPlatform); } /// @@ -108,7 +110,7 @@ public PlatformSuppressionScope EnterPlatformSuppressionScope(string platform) private readonly bool _prevCheck; private readonly string _prevPlatform; - internal PlatformSuppressionScope(ProjectionEmitContext context, bool prevCheck, string prevPlatform) + public PlatformSuppressionScope(ProjectionEmitContext context, bool prevCheck, string prevPlatform) { _context = context; _prevCheck = prevCheck; @@ -124,6 +126,7 @@ public void Dispose() { context.CheckPlatform = _prevCheck; context.Platform = _prevPlatform; + _context = null; } } diff --git a/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.Component.cs b/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.Component.cs index 5c66a0f547..68b1260e15 100644 --- a/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.Component.cs +++ b/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.Component.cs @@ -8,7 +8,6 @@ using WindowsRuntime.ProjectionWriter.Metadata; using WindowsRuntime.ProjectionWriter.Writers; using static WindowsRuntime.ProjectionWriter.References.WellKnownAttributeNames; -using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; namespace WindowsRuntime.ProjectionWriter.Generation; @@ -41,13 +40,13 @@ internal sealed partial class ProjectionGenerator { foreach (TypeDefinition type in members.Classes) { - if (!_settings.Filter.Includes(type)) + if (!_settings.Filter.Includes(type.FullName)) { continue; } - if (type.HasAttribute(WindowsFoundationMetadata, ActivatableAttribute) || - type.HasAttribute(WindowsFoundationMetadata, StaticAttribute)) + if (type.HasWindowsFoundationMetadataAttribute(ActivatableAttribute) || + type.HasWindowsFoundationMetadataAttribute(StaticAttribute)) { _ = componentActivatable.Add(type); string moduleName = Path.GetFileNameWithoutExtension(_cache.GetSourcePath(type)); diff --git a/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.GeneratedIids.cs b/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.GeneratedIids.cs index 2b5b0e10ec..62fd1b7349 100644 --- a/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.GeneratedIids.cs +++ b/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.GeneratedIids.cs @@ -8,6 +8,8 @@ using AsmResolver.DotNet; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Resolvers; using WindowsRuntime.ProjectionWriter.Writers; namespace WindowsRuntime.ProjectionWriter.Generation; @@ -37,7 +39,7 @@ internal void WriteGeneratedInterfaceIidsFile() { foreach (TypeDefinition type in nsMembers.Classes) { - if (!_settings.Filter.Includes(type)) + if (!_settings.Filter.Includes(type.FullName)) { continue; } @@ -65,6 +67,7 @@ internal void WriteGeneratedInterfaceIidsFile() IndentedTextWriter guidIndented = guidIndentedOwner.Writer; IidExpressionGenerator.WriteInterfaceIidsBegin(guidIndented); guidIndented.IncreaseIndent(); + // Iterate namespaces in sorted order. Within each namespace, types are already sorted by SortMembersByName. // The sorted-by-namespace order produces the parent-before-child grouping in the // GeneratedInterfaceIIDs.cs output (e.g. Windows.ApplicationModel.* types before @@ -76,12 +79,12 @@ internal void WriteGeneratedInterfaceIidsFile() { bool isFactoryInterface = factoryInterfacesGlobal.Contains(type); - if (!_settings.Filter.Includes(type) && !isFactoryInterface) + if (!_settings.Filter.Includes(type.FullName) && !isFactoryInterface) { continue; } - if (TypeCategorization.IsGeneric(type)) + if (type.IsGeneric) { continue; } @@ -95,23 +98,23 @@ internal void WriteGeneratedInterfaceIidsFile() } iidWritten = true; - TypeCategory cat = TypeCategorization.GetCategory(type); - switch (cat) + TypeKind kind = TypeKindResolver.Resolve(type); + switch (kind) { - case TypeCategory.Delegate: + case TypeKind.Delegate: IidExpressionGenerator.WriteIidGuidPropertyFromSignature(guidIndented, guidContext, type); IidExpressionGenerator.WriteIidGuidPropertyFromType(guidIndented, guidContext, type); break; - case TypeCategory.Enum: + case TypeKind.Enum: IidExpressionGenerator.WriteIidGuidPropertyFromSignature(guidIndented, guidContext, type); break; - case TypeCategory.Interface: + case TypeKind.Interface: IidExpressionGenerator.WriteIidGuidPropertyFromType(guidIndented, guidContext, type); break; - case TypeCategory.Struct: + case TypeKind.Struct: IidExpressionGenerator.WriteIidGuidPropertyFromSignature(guidIndented, guidContext, type); break; - case TypeCategory.Class: + case TypeKind.Class: IidExpressionGenerator.WriteIidGuidPropertyForClassInterfaces(guidIndented, guidContext, type, interfacesFromClassesEmitted); break; } diff --git a/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.Namespace.cs b/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.Namespace.cs index 51f2601dbc..5275e36818 100644 --- a/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.Namespace.cs +++ b/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.Namespace.cs @@ -9,6 +9,8 @@ using WindowsRuntime.ProjectionWriter.Factories; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Resolvers; using WindowsRuntime.ProjectionWriter.Writers; namespace WindowsRuntime.ProjectionWriter.Generation; @@ -38,15 +40,15 @@ internal bool ProcessNamespace(string ns, NamespaceMembers members, ProjectionGe if (!_settings.ReferenceProjection) { writer.WriteLine(); - MetadataAttributeFactory.WritePragmaDisableIL2026(writer); + writer.WriteLine("#pragma warning disable IL2026"); foreach (TypeDefinition type in members.Types) { - if (!_settings.Filter.Includes(type)) + if (!_settings.Filter.Includes(type.FullName)) { continue; } - if (TypeCategorization.IsGeneric(type)) + if (type.IsGeneric) { continue; } @@ -59,11 +61,11 @@ internal bool ProcessNamespace(string ns, NamespaceMembers members, ProjectionGe continue; } - TypeCategory cat = TypeCategorization.GetCategory(type); - switch (cat) + TypeKind kind = TypeKindResolver.Resolve(type); + switch (kind) { - case TypeCategory.Class: - if (!TypeCategorization.IsStatic(type) && !TypeCategorization.IsAttributeType(type)) + case TypeKind.Class: + if (!type.IsStatic && !type.IsAttributeType) { if (_settings.Component) { @@ -76,20 +78,20 @@ internal bool ProcessNamespace(string ns, NamespaceMembers members, ProjectionGe } break; - case TypeCategory.Delegate: + case TypeKind.Delegate: MetadataAttributeFactory.WriteWinRTComWrappersTypeMapGroupAssemblyAttribute(writer, context, type, true); MetadataAttributeFactory.WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute(writer, context, type); break; - case TypeCategory.Enum: + case TypeKind.Enum: MetadataAttributeFactory.WriteWinRTComWrappersTypeMapGroupAssemblyAttribute(writer, context, type, true); MetadataAttributeFactory.WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute(writer, context, type); break; - case TypeCategory.Interface: + case TypeKind.Interface: MetadataAttributeFactory.WriteWinRTIdicTypeMapGroupAssemblyAttribute(writer, context, type); MetadataAttributeFactory.WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute(writer, context, type); break; - case TypeCategory.Struct: - if (!TypeCategorization.IsApiContractType(type)) + case TypeKind.Struct: + if (!type.IsApiContractType) { MetadataAttributeFactory.WriteWinRTComWrappersTypeMapGroupAssemblyAttribute(writer, context, type, true); MetadataAttributeFactory.WriteWinRTWindowsMetadataTypeMapGroupAssemblyAttribute(writer, context, type); @@ -99,7 +101,8 @@ internal bool ProcessNamespace(string ns, NamespaceMembers members, ProjectionGe } } - MetadataAttributeFactory.WritePragmaRestoreIL2026(writer); + writer.WriteLine(); + writer.WriteLine("#pragma warning restore IL2026"); } // Phase 2: Projected types @@ -108,24 +111,25 @@ internal bool ProcessNamespace(string ns, NamespaceMembers members, ProjectionGe foreach (TypeDefinition type in members.Types) { - if (!_settings.Filter.Includes(type)) + if (!_settings.Filter.Includes(type.FullName)) { continue; } (string ns2, string nm2) = type.Names(); + // Skip generic types and mapped types - if (MappedTypes.Get(ns2, nm2) is not null || TypeCategorization.IsGeneric(type)) + if (MappedTypes.Get(ns2, nm2) is not null || type.IsGeneric) { written = true; continue; } - // Write the projected type per category - TypeCategory category = TypeCategorization.GetCategory(type); - ProjectionFileBuilder.WriteType(writer, context, type, category); + // Write the projected type per type kind + TypeKind kind = TypeKindResolver.Resolve(type); + ProjectionFileBuilder.WriteType(writer, context, type, kind); - if (category == TypeCategory.Class && !TypeCategorization.IsAttributeType(type)) + if (kind == TypeKind.Class && !type.IsAttributeType) { MetadataAttributeFactory.AddDefaultInterfaceEntry(context, type, defaultInterfaceEntries); MetadataAttributeFactory.AddExclusiveToInterfaceEntries(context, type, exclusiveToInterfaceEntries); @@ -136,11 +140,11 @@ internal bool ProcessNamespace(string ns, NamespaceMembers members, ProjectionGe ComponentFactory.WriteFactoryClass(writer, context, type); } } - else if (category is TypeCategory.Delegate or TypeCategory.Enum or TypeCategory.Interface) + else if (kind is TypeKind.Delegate or TypeKind.Enum or TypeKind.Interface) { ComponentFactory.AddMetadataTypeEntry(context, type, authoredTypeNameToMetadataMap); } - else if (category == TypeCategory.Struct && !TypeCategorization.IsApiContractType(type)) + else if (kind == TypeKind.Struct && !type.IsApiContractType) { ComponentFactory.AddMetadataTypeEntry(context, type, authoredTypeNameToMetadataMap); } @@ -167,12 +171,12 @@ internal bool ProcessNamespace(string ns, NamespaceMembers members, ProjectionGe HashSet factoryInterfacesAllNs = []; foreach (TypeDefinition type in members.Types) { - if (!_settings.Filter.Includes(type)) + if (!_settings.Filter.Includes(type.FullName)) { continue; } - if (TypeCategorization.GetCategory(type) != TypeCategory.Class) + if (TypeKindResolver.Resolve(type) != TypeKind.Class) { continue; } @@ -182,7 +186,7 @@ internal bool ProcessNamespace(string ns, NamespaceMembers members, ProjectionGe foreach (TypeDefinition facType in factoryInterfacesAllNs) { // Only consider factory interfaces in the same namespace as we're processing. - string facNs = facType.Namespace?.Value ?? string.Empty; + string facNs = facType.GetRawNamespace(); if (facNs == ns) { @@ -195,12 +199,12 @@ internal bool ProcessNamespace(string ns, NamespaceMembers members, ProjectionGe { bool isFactoryInterface = factoryInterfacesInThisNs.Contains(type); - if (!_settings.Filter.Includes(type) && !isFactoryInterface) + if (!_settings.Filter.Includes(type.FullName) && !isFactoryInterface) { continue; } - if (TypeCategorization.IsGeneric(type)) + if (type.IsGeneric) { continue; } @@ -213,27 +217,27 @@ internal bool ProcessNamespace(string ns, NamespaceMembers members, ProjectionGe continue; } - if (TypeCategorization.IsApiContractType(type)) + if (type.IsApiContractType) { continue; } - if (TypeCategorization.IsAttributeType(type)) + if (type.IsAttributeType) { continue; } - TypeCategory category = TypeCategorization.GetCategory(type); - ProjectionFileBuilder.WriteAbiType(writer, context, type, category); + TypeKind kind = TypeKindResolver.Resolve(type); + ProjectionFileBuilder.WriteAbiType(writer, context, type, kind); } writer.WriteEndAbiNamespace(context); } // Phase 4: Custom additions to namespaces _token.ThrowIfCancellationRequested(); - if (Additions.ByNamespace.TryGetValue(ns, out string[]? resourceNames) && _settings.AdditionFilter.Includes(ns)) + if (_settings.AdditionFilter.Includes(ns)) { - foreach (string resName in resourceNames) + foreach (string resName in Additions.EnumerateByNamespace(ns)) { using Stream? stream = typeof(ProjectionWriter).Assembly.GetManifestResourceStream(resName); diff --git a/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.cs index 1fd63d2481..c7b4cf3741 100644 --- a/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Writer/Generation/ProjectionGenerator.cs @@ -70,7 +70,7 @@ public void Run() ProjectionGeneratorRunState state = new(componentActivatable, componentByModule); - // Phase 3..6: parallel emission. All file writes happen below; wrap the whole emission + // Phase 2: parallel emission. All file writes happen below; wrap the whole emission // pipeline in a single try/catch so any unexpected failure surfaces as an // UnhandledProjectionWriterException rather than a raw stack trace. try diff --git a/src/WinRT.Projection.Writer/Generation/Settings.cs b/src/WinRT.Projection.Writer/Generation/Settings.cs index 402d906bc5..fe24269ef9 100644 --- a/src/WinRT.Projection.Writer/Generation/Settings.cs +++ b/src/WinRT.Projection.Writer/Generation/Settings.cs @@ -102,21 +102,6 @@ public TypeFilter AdditionFilter /// public bool Component { get; init; } - /// - /// Gets or sets a value indicating whether projected types are emitted as internal rather than public. - /// - public bool Internal { get; init; } - - /// - /// Gets or sets a value indicating whether the projection is embedded into a consuming assembly (forces internal visibility). - /// - public bool Embedded { get; init; } - - /// - /// Gets or sets a value indicating whether projected enums are forced to public visibility (overrides ). - /// - public bool PublicEnums { get; init; } - /// /// Gets or sets a value indicating whether [ExclusiveTo] interfaces are emitted as public rather than internal. /// diff --git a/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.AbiTypeNames.cs b/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.AbiTypeNames.cs index 05d08f91ba..c549574640 100644 --- a/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.AbiTypeNames.cs +++ b/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.AbiTypeNames.cs @@ -7,6 +7,8 @@ using WindowsRuntime.ProjectionWriter.Factories; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.References; using WindowsRuntime.ProjectionWriter.Writers; using static WindowsRuntime.ProjectionWriter.References.ProjectionNames; @@ -15,9 +17,105 @@ namespace WindowsRuntime.ProjectionWriter.Helpers; internal static partial class AbiTypeHelpers { /// - /// Returns the ABI type name for a blittable struct (the projected type name). + /// Returns the ABI C# type name for a local variable that holds the ABI value of a parameter + /// or return type. Encapsulates the type-selection dispatch that's repeated across the RcwCaller + /// out-parameter, ReceiveArray-element, and SzArray-return code paths: reference types + /// (string/interface/object/generic instance) → void*; system type → global::ABI.System.Type; + /// HResult/Exception → global::ABI.System.Exception; complex struct → ABI struct name; + /// mapped value type (DateTime/TimeSpan/etc.) → mapped ABI name; blittable struct → projected + /// name; primitive → fundamental C# keyword. /// - internal static string GetBlittableStructAbiType(IndentedTextWriter writer, ProjectionEmitContext context, TypeSignature sig) + /// The active emit context. + /// The type signature whose ABI local type name is requested. + /// The ABI C# type name as a string (no trailing punctuation). + public static string GetAbiLocalTypeName(ProjectionEmitContext context, TypeSignature sig) + { + if (sig.IsAbiRefLike(context.AbiTypeKindResolver)) + { + return "void*"; + } + + if (sig.IsSystemType()) + { + return WellKnownAbiTypeNames.AbiSystemType; + } + + if (sig.IsHResultException()) + { + return WellKnownAbiTypeNames.AbiSystemException; + } + + if (context.AbiTypeKindResolver.IsNonBlittableStruct(sig)) + { + return GetAbiStructTypeName(context, sig); + } + + if (IsMappedAbiValueType(sig)) + { + return GetMappedAbiTypeName(sig); + } + + if (context.AbiTypeKindResolver.IsBlittableStruct(sig)) + { + return GetBlittableStructAbiType(context, sig); + } + + return GetAbiPrimitiveType(context.Cache, sig); + } + + /// + /// Returns the ABI C# type name for a single SZ-array element appearing as the data + /// parameter in array-marshaller UnsafeAccessor signatures (e.g. ConvertToManaged_X, + /// Free_X). Dispatched by : + /// reference-pointer → void*, HResult → global::ABI.System.Exception, mapped value + /// type → mapped ABI name, complex struct → ABI struct name, blittable struct → blittable ABI + /// struct, primitive → primitive ABI type. + /// + /// The active emit context. + /// The SZ-array element type. + /// The ABI C# type name as a string (no trailing punctuation). + public static string GetArrayElementAbiType(ProjectionEmitContext context, TypeSignature elementType) + { + return context.AbiTypeKindResolver.ClassifyArrayElement(elementType) switch + { + AbiArrayElementKind.RefLikeVoidStar => "void*", + AbiArrayElementKind.HResultException => WellKnownAbiTypeNames.AbiSystemException, + AbiArrayElementKind.MappedValueType => GetMappedAbiTypeName(elementType), + AbiArrayElementKind.NonBlittableStruct => GetAbiStructTypeName(context, elementType), + AbiArrayElementKind.BlittableStruct => GetBlittableStructAbiType(context, elementType), + _ => GetAbiPrimitiveType(context.Cache, elementType), + }; + } + + /// + /// Returns the C# type name used for the InlineArray16<T> / ArrayPool<T> + /// storage T backing a non-blittable PassArray / FillArray parameter. Strings, + /// runtime classes, and objects all collapse to nint (HSTRING handles / IInspectable + /// pointers); HResult uses global::ABI.System.Exception; mapped value types and + /// complex structs use their ABI struct name. + /// + /// The active emit context. + /// The SZ-array element type. + /// The storage C# type name as a string (no trailing punctuation). + public static string GetArrayElementStorageType(ProjectionEmitContext context, TypeSignature elementType) + { + return context.AbiTypeKindResolver.ClassifyArrayElement(elementType) switch + { + AbiArrayElementKind.MappedValueType => GetMappedAbiTypeName(elementType), + AbiArrayElementKind.NonBlittableStruct => GetAbiStructTypeName(context, elementType), + AbiArrayElementKind.HResultException => WellKnownAbiTypeNames.AbiSystemException, + _ => "nint", + }; + } + + /// + /// Returns the ABI type name for a blittable struct (the projected type name; mapped value + /// types like DateTime / TimeSpan return their mapped ABI name instead). + /// + /// The active emit context. + /// The blittable struct type signature. + /// The ABI C# type name as a string (no trailing punctuation). + public static string GetBlittableStructAbiType(ProjectionEmitContext context, TypeSignature sig) { // Mapped value types (DateTime/TimeSpan) use the ABI type, not the projected type. if (IsMappedAbiValueType(sig)) @@ -25,30 +123,29 @@ internal static string GetBlittableStructAbiType(IndentedTextWriter writer, Proj return GetMappedAbiTypeName(sig); } - string result = MethodFactory.WriteProjectedSignature(context, sig, false); + string result = MethodFactory.WriteProjectedSignature(context, sig, false).Format(); return result; } - /// Returns the ABI struct type name for a complex struct (e.g. global::ABI.Windows.Web.Http.HttpProgress). - /// When the writer is currently in the matching ABI namespace, returns just the - /// short type name (e.g. HttpProgress) to mirror the original code which uses the - /// unqualified name in same-namespace contexts. - internal static string GetAbiStructTypeName(IndentedTextWriter writer, ProjectionEmitContext context, TypeSignature sig) + /// + /// Returns the ABI struct type name for a complex struct + /// (e.g. global::ABI.Windows.Web.Http.HttpProgress). Mapped structs route through + /// their mapped namespace+name (e.g. Windows.UI.Xaml.Interop.TypeName → + /// global::ABI.System.Type). + /// + /// The active emit context. + /// The complex struct type signature. + /// The ABI C# type name as a string (no trailing punctuation). + public static string GetAbiStructTypeName(ProjectionEmitContext context, TypeSignature sig) { if (sig is TypeDefOrRefSignature td) { - string ns = td.Type?.Namespace?.Value ?? string.Empty; - string name = td.Type?.Name?.Value ?? string.Empty; + (string ns, string name) = td.Type.Names(); + // If this struct is mapped, use the mapped namespace+name (e.g. // 'Windows.UI.Xaml.Interop.TypeName' is mapped to 'System.Type', so the ABI struct // is 'global::ABI.System.Type', not 'global::ABI.Windows.UI.Xaml.Interop.TypeName'). - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); return GlobalAbiPrefix + ns + "." + IdentifierEscaping.StripBackticks(name); } @@ -59,7 +156,7 @@ internal static string GetAbiStructTypeName(IndentedTextWriter writer, Projectio /// /// Returns the C# primitive keyword (e.g. "bool", "int") for an ABI corlib element type, or when is not a primitive. /// - internal static string GetAbiPrimitiveType(MetadataCache cache, TypeSignature sig) + public static string GetAbiPrimitiveType(MetadataCache cache, TypeSignature sig) { if (sig is CorLibTypeSignature corlib) { @@ -79,12 +176,12 @@ internal static string GetAbiPrimitiveType(MetadataCache cache, TypeSignature si if (def is null && td.Type is TypeReference tr) { (string ns, string name) = tr.Names(); - def = cache.Find(ns + "." + name); + def = cache.Find(ns, name); } - if (def is not null && TypeCategorization.GetCategory(def) == TypeCategory.Enum) + if (def is not null && def.IsEnum) { - return cache is null ? "int" : GetProjectedEnumName(def); + return GetProjectedEnumName(def); } } @@ -94,17 +191,12 @@ internal static string GetAbiPrimitiveType(MetadataCache cache, TypeSignature si private static string GetProjectedEnumName(TypeDefinition def) { (string ns, string name) = def.Names(); + // Apply mapped-type translation so consumers see the projected (.NET) enum name // (e.g. Windows.UI.Xaml.Interop.NotifyCollectionChangedAction → // System.Collections.Specialized.NotifyCollectionChangedAction). Same // remapping that WriteTypedefName performs. - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); return string.IsNullOrEmpty(ns) ? GlobalPrefix + name : GlobalPrefix + ns + "." + name; } diff --git a/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.Blittability.cs b/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.Blittability.cs index 636f9a47b9..a78c12a370 100644 --- a/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.Blittability.cs +++ b/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.Blittability.cs @@ -5,6 +5,8 @@ using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Resolvers; namespace WindowsRuntime.ProjectionWriter.Helpers; @@ -15,14 +17,14 @@ internal static partial class AbiTypeHelpers /// public static bool IsTypeBlittable(MetadataCache cache, TypeDefinition type) { - TypeCategory cat = TypeCategorization.GetCategory(type); + TypeKind kind = TypeKindResolver.Resolve(type); - if (cat == TypeCategory.Enum) + if (kind == TypeKind.Enum) { return true; } - if (cat != TypeCategory.Struct) + if (kind != TypeKind.Struct) { return false; } @@ -79,8 +81,8 @@ internal static bool IsFieldTypeBlittable(MetadataCache cache, TypeSignature sig // For TypeRef/TypeDef, resolve and check blittability. if (sig is TypeDefOrRefSignature todr) { - string fNs = todr.Type?.Namespace?.Value ?? string.Empty; - string fName = todr.Type?.Name?.Value ?? string.Empty; + (string fNs, string fName) = todr.Type.Names(); + // System.Guid is a fundamental blittable type . // Same applies to System.IntPtr / UIntPtr (used in some struct layouts). if (fNs == "System" && (fName is "Guid" or "IntPtr" || fName == "UIntPtr")) @@ -105,7 +107,7 @@ internal static bool IsFieldTypeBlittable(MetadataCache cache, TypeSignature sig if (todr.Type is TypeReference tr) { (string ns, string name) = tr.Names(); - TypeDefinition? resolved = cache.Find(ns + "." + name); + TypeDefinition? resolved = cache.Find(ns, name); if (resolved is not null) { @@ -135,7 +137,7 @@ internal static bool IsFieldTypeBlittable(MetadataCache cache, TypeSignature sig if (tdr.Type is TypeReference tr) { (string ns, string name) = tr.Names(); - return cache.Find(ns + "." + name); + return cache.Find(ns, name); } return null; @@ -153,14 +155,14 @@ internal static bool IsEnumType(MetadataCache cache, TypeSignature sig) if (td.Type is TypeDefinition def) { - return TypeCategorization.GetCategory(def) == TypeCategory.Enum; + return def.IsEnum; } if (td.Type is TypeReference tr) { (string ns, string name) = tr.Names(); - TypeDefinition? resolved = cache.Find(ns + "." + name); - return resolved is not null && TypeCategorization.GetCategory(resolved) == TypeCategory.Enum; + TypeDefinition? resolved = cache.Find(ns, name); + return resolved is not null && resolved.IsEnum; } return false; @@ -176,32 +178,18 @@ internal static bool IsRuntimeClassOrInterface(MetadataCache cache, TypeSignatur // Same-module: use the resolved category directly. if (td.Type is TypeDefinition def) { - TypeCategory cat = TypeCategorization.GetCategory(def); - return cat is TypeCategory.Class or TypeCategory.Interface or TypeCategory.Delegate; + TypeKind kind = TypeKindResolver.Resolve(def); + return kind is TypeKind.Class or TypeKind.Interface or TypeKind.Delegate; } - // Cross-module typeref: try to resolve via the metadata cache to check category. - string ns = td.Type?.Namespace?.Value ?? string.Empty; - string name = td.Type?.Name?.Value ?? string.Empty; - - if (ns == "System") - { - return name switch - { - "Uri" or "Type" or "IDisposable" or "Exception" => true, - _ => false, - }; - } + // Cross-module typeref: try to resolve via the metadata cache to check category + (string ns, string name) = td.Type.Names(); + TypeDefinition? resolved = cache.Find(ns, name); - if (cache is not null) + if (resolved is not null) { - TypeDefinition? resolved = cache.Find(ns + "." + name); - - if (resolved is not null) - { - TypeCategory cat = TypeCategorization.GetCategory(resolved); - return cat is TypeCategory.Class or TypeCategory.Interface or TypeCategory.Delegate; - } + TypeKind kind = TypeKindResolver.Resolve(resolved); + return kind is TypeKind.Class or TypeKind.Interface or TypeKind.Delegate; } // Unresolved cross-assembly TypeRef (e.g. a referenced winmd we don't have loaded). @@ -241,7 +229,7 @@ ElementType.R8 or // Enum (TypeDefOrRef-based value type with non-Object base) - same module or cross-module if (sig is TypeDefOrRefSignature td) { - if (td.Type is TypeDefinition def && TypeCategorization.GetCategory(def) == TypeCategory.Enum) + if (td.Type is TypeDefinition def && def.IsEnum) { return true; } @@ -250,9 +238,9 @@ ElementType.R8 or if (td.Type is TypeReference tr) { (string ns, string name) = tr.Names(); - TypeDefinition? resolved = cache.Find(ns + "." + name); + TypeDefinition? resolved = cache.Find(ns, name); - if (resolved is not null && TypeCategorization.GetCategory(resolved) == TypeCategory.Enum) + if (resolved is not null && resolved.IsEnum) { return true; } @@ -262,14 +250,24 @@ ElementType.R8 or return false; } - /// True for any struct type that can be passed directly across the WinRT ABI - /// (no per-field marshalling required). This includes blittable structs and "almost-blittable" - /// structs that have only primitive fields like bool/char (whose C# layout matches the WinRT ABI). - /// Excludes structs with reference type fields (string/object/runtime classes/etc.). - /// True for structs that have at least one reference type field (string, generic - /// instance Nullable<T>, etc.). These need per-field marshalling via the *Marshaller class - /// (ConvertToUnmanaged/ConvertToManaged/Dispose). - internal static bool IsComplexStruct(MetadataCache cache, TypeSignature sig) + /// + /// Returns whether is a WinRT struct that needs per-field marshalling + /// via a generated *Marshaller class (ConvertToUnmanaged / ConvertToManaged + /// / Dispose). This is the inverse of restricted to + /// non-mapped structs. + /// + /// + /// A struct is "complex" iff: + /// + /// it is NOT custom-mapped (mapped structs use a hand-written ABI helper, not a + /// per-field marshaller); and + /// it has at least one instance field that is not a blittable primitive (or enum) + /// and not a nested blittable struct (i.e. a , , + /// runtime class, generic instance, custom-mapped marshalling field, or a nested + /// complex struct). + /// + /// + internal static bool IsNonBlittableStruct(MetadataCache cache, TypeSignature sig) { if (sig is not TypeDefOrRefSignature td) { @@ -282,12 +280,7 @@ internal static bool IsComplexStruct(MetadataCache cache, TypeSignature sig) { (string ns, string name) = tr.Names(); - if (ns == "System" && name == "Guid") - { - return false; - } - - def = cache.Find(ns + "." + name); + def = cache.Find(ns, name); } if (def is null) @@ -295,26 +288,25 @@ internal static bool IsComplexStruct(MetadataCache cache, TypeSignature sig) return false; } - TypeCategory cat = TypeCategorization.GetCategory(def); - - if (cat != TypeCategory.Struct) + if (!def.IsStruct) { return false; } - // RequiresMarshaling, regardless of inner field layout. So for mapped types like - // Duration, KeyTime, RepeatBehavior (RequiresMarshaling=false), they're never "complex". - string sNs = td.Type?.Namespace?.Value ?? string.Empty; - string sName = td.Type?.Name?.Value ?? string.Empty; - MappedType? sMapped = MappedTypes.Get(sNs, sName); + // Custom-mapped structs are short-circuited up front: they go through a hand-written + // ABI helper (not a per-field marshaller) regardless of inner field layout. Mapped + // structs like Duration / KeyTime / RepeatBehavior (RequiresMarshaling=false) are + // therefore never "complex". + (string sNs, string sName) = td.Type.Names(); - if (sMapped is not null) + if (MappedTypes.Get(sNs, sName) is not null) { return false; } - // A struct is "complex" if it has any field that is not a blittable primitive nor an - // almost-blittable struct (i.e. has a string/object/Nullable/etc. field). + // A struct is "complex" if it has any field that is not a blittable primitive nor a + // (recursively) blittable struct — i.e. it has a string/object/runtime class/generic + // instance/marshalling-mapped/complex-nested-struct field. foreach (FieldDefinition field in def.Fields) { if (field.IsStatic || field.Signature is null) @@ -329,20 +321,36 @@ internal static bool IsComplexStruct(MetadataCache cache, TypeSignature sig) continue; } - if (IsAnyStruct(cache, ft)) + if (IsBlittableStruct(cache, ft)) { continue; } return true; } + return false; } /// - /// Returns whether resolves to a struct type (mapped or user-defined). + /// Returns whether is a WinRT struct that flows across the ABI + /// by value with no per-field marshalling (the projected struct's memory layout matches + /// the ABI representation byte-for-byte). /// - internal static bool IsAnyStruct(MetadataCache cache, TypeSignature sig) + /// + /// A struct qualifies as blittable iff: + /// + /// it is custom-mapped with RequiresMarshaling=false (e.g. self-mapped XAML + /// structs such as Duration, KeyTime, RepeatBehavior); or + /// every instance field is itself a blittable primitive (or enum), or a nested + /// blittable struct. + /// + /// Reference-typed fields (, , runtime classes, + /// generic instances, nested complex structs, custom-mapped fields with + /// RequiresMarshaling=true such as TimeSpan or DateTimeOffset) all + /// disqualify the containing struct. Complex structs are handled by . + /// + internal static bool IsBlittableStruct(MetadataCache cache, TypeSignature sig) { if (sig is not TypeDefOrRefSignature td) { @@ -355,12 +363,14 @@ internal static bool IsAnyStruct(MetadataCache cache, TypeSignature sig) { (string ns, string name) = trEarly.Names(); + // 'System.Guid' lives in mscorlib (not in any .winmd): the cache never resolves it, + // so short-circuit to true here. Windows Runtime's 'Guid' is exactly this BCL struct. if (ns == "System" && name == "Guid") { return true; } - def = cache.Find(ns + "." + name); + def = cache.Find(ns, name); } if (def is null) @@ -370,26 +380,23 @@ internal static bool IsAnyStruct(MetadataCache cache, TypeSignature sig) // Mapped struct types short-circuit based on the mapping's RequiresMarshaling flag // (only applies to actual structs, not mapped interfaces like IAsyncAction). - if (TypeCategorization.GetCategory(def) == TypeCategory.Struct) + if (def.IsStruct) { - string sNs = td.Type?.Namespace?.Value ?? string.Empty; - string sName = td.Type?.Name?.Value ?? string.Empty; - MappedType? sMapped = MappedTypes.Get(sNs, sName); + (string sNs, string sName) = td.Type.Names(); - if (sMapped is { } sMappedVal) + if (MappedTypes.Get(sNs, sName) is MappedType mappedType) { - return !sMappedVal.RequiresMarshaling; + return !mappedType.RequiresMarshaling; } } - TypeCategory cat = TypeCategorization.GetCategory(def); - - if (cat != TypeCategory.Struct) + // If the type isn't a struct type, then by definition it isn't blittable + if (!def.IsStruct) { return false; } - // Reject if any instance field is a reference type (string/object/runtime class/etc.). + // Reject if any instance field is a reference type (string/object/runtime class/etc.) foreach (FieldDefinition field in def.Fields) { if (field.IsStatic || field.Signature is null) @@ -399,28 +406,31 @@ internal static bool IsAnyStruct(MetadataCache cache, TypeSignature sig) TypeSignature ft = field.Signature.FieldType; + // 'string' and 'object' are the only primitive types that aren't blittable if (ft is CorLibTypeSignature corlibField) { - if (corlibField.ElementType is - ElementType.String or - ElementType.Object) - { return false; } + if (corlibField.ElementType is ElementType.String or ElementType.Object) + { + return false; + } + continue; } - // Recurse: nested struct must also pass IsAnyStruct, otherwise reject. + // Recurse: a nested struct must also be blittable, otherwise reject if (IsBlittablePrimitive(cache, ft)) { continue; } - if (IsAnyStruct(cache, ft)) + if (IsBlittableStruct(cache, ft)) { continue; } return false; } + return true; } } diff --git a/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.MappedTypes.cs b/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.MappedTypes.cs index eca5c68e7e..e23050360b 100644 --- a/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.MappedTypes.cs +++ b/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.MappedTypes.cs @@ -68,6 +68,7 @@ private static bool IsMappedMarshalingValueType(TypeSignature sig, out string ma } (string ns, string name) = td.Names(); + // The set of mapped types that use the 'value-type marshaller' pattern (DateTime, TimeSpan, HResult). // Uri is also a mapped marshalling type but it's a reference type (handled via UriMarshaller separately). if (ns == WindowsFoundation) diff --git a/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.Marshallers.cs b/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.Marshallers.cs index d7ba1543f4..2c6cd73e27 100644 --- a/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.Marshallers.cs +++ b/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.Marshallers.cs @@ -27,12 +27,11 @@ internal static bool TryGetNullablePrimitiveMarshallerName(TypeSignature sig, ou } ITypeDefOrRef gt = gi.GenericType; - string ns = gt?.Namespace?.Value ?? string.Empty; - string name = gt?.Name?.Value ?? string.Empty; + (string ns, string name) = gt.Names(); + // In WinMD metadata, Nullable is encoded as Windows.Foundation.IReference. - // It only later gets projected to System.Nullable by the projection layer. - bool isNullable = (ns == "System" && name == NullableGeneric) - || (ns == WindowsFoundation && name == IReferenceGeneric); + // (It only later gets projected to System.Nullable by the projection layer.) + bool isNullable = ns == WindowsFoundation && name == IReferenceGeneric; if (!isNullable) { @@ -45,6 +44,7 @@ internal static bool TryGetNullablePrimitiveMarshallerName(TypeSignature sig, ou } TypeSignature arg = gi.TypeArguments[0]; + // Map primitive corlib element type to its ABI marshaller name. if (arg is CorLibTypeSignature corlib) { @@ -77,6 +77,24 @@ internal static bool TryGetNullablePrimitiveMarshallerName(TypeSignature sig, ou return false; } + /// + /// Bundles the inner type of a instantiation together with + /// the ABI marshaller name for that inner type. Returned by + /// . + /// + internal readonly record struct NullableInnerInfo(TypeSignature Inner, string MarshallerName); + + /// + /// Returns the inner type T of Nullable<T> together with its ABI marshaller name. + /// Caller must have verified is a Nullable<T> + /// instantiation (e.g. via IsNullableT). + /// + internal static NullableInnerInfo GetNullableInnerInfo(IndentedTextWriter writer, ProjectionEmitContext context, TypeSignature nullableSig) + { + TypeSignature inner = nullableSig.GetNullableInnerType()!; + return new NullableInnerInfo(inner, GetNullableInnerMarshallerName(writer, context, inner)); + } + /// Returns the marshaller name for the inner type T of Nullable<T>. ///.: e.g. for Nullable<DateTimeOffset> returns /// global::ABI.System.DateTimeOffsetMarshaller; for primitives like Nullable<int> @@ -121,16 +139,10 @@ internal static string GetMarshallerFullName(IndentedTextWriter writer, Projecti { if (sig is TypeDefOrRefSignature td) { - string ns = td.Type?.Namespace?.Value ?? string.Empty; - string name = td.Type?.Name?.Value ?? string.Empty; - // Apply mapped type remapping (e.g. System.Uri -> Windows.Foundation.Uri) - MappedType? mapped = MappedTypes.Get(ns, name); + (string ns, string name) = td.Type.Names(); - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } + // Apply mapped type remapping (e.g. System.Uri -> Windows.Foundation.Uri) + _ = MappedTypes.ApplyMapping(ref ns, ref name); return GlobalAbiPrefix + ns + "." + IdentifierEscaping.StripBackticks(name) + MarshallerSuffix; } diff --git a/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.cs b/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.cs index ee5a670ab6..b2bcd2a1f2 100644 --- a/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.cs +++ b/src/WinRT.Projection.Writer/Helpers/AbiTypeHelpers.cs @@ -5,13 +5,12 @@ using System.Globalization; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Metadata; using WindowsRuntime.ProjectionWriter.Models; -using WindowsRuntime.ProjectionWriter.Resolvers; using WindowsRuntime.ProjectionWriter.Writers; using static WindowsRuntime.ProjectionWriter.References.WellKnownAttributeNames; -using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; namespace WindowsRuntime.ProjectionWriter.Helpers; @@ -24,90 +23,16 @@ internal static partial class AbiTypeHelpers /// /// Returns the parent class for an interface marked [ExclusiveToAttribute(typeof(T))]. /// - internal static TypeDefinition? GetExclusiveToType(MetadataCache cache, TypeDefinition iface) + public static TypeDefinition? GetExclusiveToType(MetadataCache cache, TypeDefinition iface) { - for (int i = 0; i < iface.CustomAttributes.Count; i++) - { - CustomAttribute attr = iface.CustomAttributes[i]; - ITypeDefOrRef? attrType = attr.Constructor?.DeclaringType; - - if (attrType is null) - { - continue; - } - - if (attrType.Namespace?.Value != WindowsFoundationMetadata || - attrType.Name?.Value != ExclusiveToAttribute) - { - continue; - } - - if (attr.Signature is null) - { - continue; - } - - for (int j = 0; j < attr.Signature.FixedArguments.Count; j++) - { - CustomAttributeArgument arg = attr.Signature.FixedArguments[j]; - - if (arg.Element is TypeSignature sig) - { - string fullName = sig.FullName ?? string.Empty; - TypeDefinition? td = cache.Find(fullName); - - if (td is not null) - { - return td; - } - } - else if (arg.Element is string s) - { - TypeDefinition? td = cache.Find(s); - - if (td is not null) - { - return td; - } - } - } - } - return null; - } - - /// - /// Resolves an InterfaceImpl's interface reference to a TypeDefinition (same module or via metadata cache). - /// - internal static TypeDefinition? ResolveInterfaceTypeDef(MetadataCache cache, ITypeDefOrRef ifaceRef) - { - if (ifaceRef is TypeDefinition td) - { - return td; - } - - if (ifaceRef is TypeSpecification ts && ts.Signature is GenericInstanceTypeSignature gi) - { - ITypeDefOrRef? gen = gi.GenericType; - - if (gen is TypeDefinition gtd) - { - return gtd; - } - - if (gen is TypeReference gtr) - { - (string ns, string nm) = gtr.Names(); - return cache.Find(ns + "." + nm); - } - } + CustomAttribute? attr = iface.GetWindowsFoundationMetadataAttribute(ExclusiveToAttribute); - if (ifaceRef is TypeReference tr) + if (attr is null || !attr.TryGetFixedArgument(0, out TypeSignature? sig)) { - (string ns, string nm) = tr.Names(); - return cache.Find(ns + "." + nm); + return null; } - return null; + return cache.Find(sig.FullName ?? string.Empty); } /// @@ -131,14 +56,14 @@ public static string GetVirtualMethodName(TypeDefinition type, MethodDefinition index++; } - return (method.Name?.Value ?? string.Empty) + "_" + index.ToString(CultureInfo.InvariantCulture); + return method.GetRawName() + "_" + index.ToString(CultureInfo.InvariantCulture); } /// /// Returns the metadata-derived name for the return parameter, or the conventional /// __return_value__ placeholder when the metadata does not name it. /// - internal static string GetReturnParamName(MethodSignatureInfo sig) + public static string GetReturnParamName(MethodSignatureInfo sig) { string? n = sig.ReturnParameter?.Name?.Value; @@ -147,22 +72,13 @@ internal static string GetReturnParamName(MethodSignatureInfo sig) return "__return_value__"; } - return CSharpKeywords.IsKeyword(n) ? "@" + n : n; - } - - /// - /// Returns the local-variable name for the return parameter on the server side. - /// abi_marshaler::get_marshaler_local() which prefixes __ to the param name. - /// - internal static string GetReturnLocalName(MethodSignatureInfo sig) - { - return "__" + GetReturnParamName(sig); + return IdentifierEscaping.EscapeIdentifier(n); } /// /// Returns '__<returnName>Size' — by default '____return_value__Size' for the standard '__return_value__' return param. /// - internal static string GetReturnSizeParamName(MethodSignatureInfo sig) + public static string GetReturnSizeParamName(MethodSignatureInfo sig) { return "__" + GetReturnParamName(sig) + "Size"; } @@ -170,7 +86,7 @@ internal static string GetReturnSizeParamName(MethodSignatureInfo sig) /// /// Build a method-to-event map for add/remove accessors of a type. /// - internal static Dictionary? BuildEventMethodMap(TypeDefinition type) + public static Dictionary? BuildEventMethodMap(TypeDefinition type) { if (type.Events.Count == 0) { @@ -193,6 +109,13 @@ internal static string GetReturnSizeParamName(MethodSignatureInfo sig) return map; } + /// + /// A callback that writes the IID expression to the writer it's appended to. + public static WriteIidGuidReferenceCallback WriteIidGuidReference(ProjectionEmitContext context, TypeDefinition type) + { + return new(context, type); + } + /// /// Writes the IID GUID literal expression for the given runtime type (used by ABI emission paths). /// @@ -201,8 +124,8 @@ public static void WriteIidGuidReference(IndentedTextWriter writer, ProjectionEm if (type.GenericParameters.Count != 0) { // Generic interface IID - call the unsafe accessor - IidExpressionGenerator.WriteIidGuidPropertyName(writer, context, type); - writer.Write("(null)"); + WriteIidGuidPropertyNameCallback iidName = IidExpressionGenerator.WriteIidGuidPropertyName(context, type); + writer.Write($"{iidName}(null)"); return; } @@ -214,111 +137,18 @@ public static void WriteIidGuidReference(IndentedTextWriter writer, ProjectionEm return; } - writer.Write("global::ABI.InterfaceIIDs."); - IidExpressionGenerator.WriteIidGuidPropertyName(writer, context, type); - } - - /// - /// True if EmitNativeDelegateBody can emit a real (non-throw) body for this signature. - /// - internal static bool IsDelegateInvokeNativeSupported(MetadataCache cache, MethodSignatureInfo sig) - { - TypeSignature? rt = sig.ReturnType; - - if (rt is not null) - { - if (rt.IsHResultException()) - { - return false; - } - - if (!(IsBlittablePrimitive(cache, rt) || IsAnyStruct(cache, rt) || rt.IsString() || IsRuntimeClassOrInterface(cache, rt) || rt.IsObject() || rt.IsGenericInstance() || IsComplexStruct(cache, rt))) - { - return false; - } - } - - foreach (ParameterInfo p in sig.Parameters) - { - ParameterCategory cat = ParameterCategoryResolver.GetParamCategory(p); - - if (cat is ParameterCategory.PassArray or ParameterCategory.FillArray) - { - if (p.Type is SzArrayTypeSignature szP) - { - if (IsBlittablePrimitive(cache, szP.BaseType)) - { - continue; - } - - if (IsAnyStruct(cache, szP.BaseType)) - { - continue; - } - } - - return false; - } - - if (cat != ParameterCategory.In) - { - return false; - } - - if (p.Type.IsHResultException()) - { - return false; - } - - if (IsBlittablePrimitive(cache, p.Type)) - { - continue; - } - - if (IsAnyStruct(cache, p.Type)) - { - continue; - } - - if (p.Type.IsString()) - { - continue; - } - - if (IsRuntimeClassOrInterface(cache, p.Type)) - { - continue; - } - - if (p.Type.IsObject()) - { - continue; - } - - if (p.Type.IsGenericInstance()) - { - continue; - } - - if (IsComplexStruct(cache, p.Type)) - { - continue; - } - - return false; - } - - return true; + WriteIidGuidPropertyNameCallback name = IidExpressionGenerator.WriteIidGuidPropertyName(context, type); + writer.Write($"global::ABI.InterfaceIIDs.{name}"); } /// /// True if the interface has at least one non-special method, property, or non-skipped event. /// - internal static bool HasEmittableMembers(TypeDefinition iface, bool skipExclusiveEvents) + public static bool HasEmittableMembers(TypeDefinition iface, bool skipExclusiveEvents) { foreach (MethodDefinition m in iface.Methods) { - if (!m.IsSpecial()) + if (!m.IsSpecial) { return true; } @@ -340,7 +170,7 @@ internal static bool HasEmittableMembers(TypeDefinition iface, bool skipExclusiv /// /// Returns the number of methods (including special accessors) on the interface. /// - internal static int CountMethods(TypeDefinition iface) + public static int CountMethods(TypeDefinition iface) { return iface.Methods.Count; } @@ -348,7 +178,7 @@ internal static int CountMethods(TypeDefinition iface) /// /// Returns the number of base classes between and . /// - internal static int GetClassHierarchyIndex(MetadataCache cache, TypeDefinition classType) + public static int GetClassHierarchyIndex(MetadataCache cache, TypeDefinition classType) { if (classType.BaseType is null) { @@ -357,6 +187,9 @@ internal static int GetClassHierarchyIndex(MetadataCache cache, TypeDefinition c (string ns, string nm) = classType.BaseType.Names(); + // 'System.Object' is not in any .winmd, so resolving via the cache fails. But AsmResolver + // can still 'TryResolve' it from the corlib runtime context, which would (incorrectly) + // make a direct-Object-derivation count as depth 1 instead of 0. Short-circuit here. if (ns == "System" && nm == "Object") { return 0; @@ -367,7 +200,7 @@ internal static int GetClassHierarchyIndex(MetadataCache cache, TypeDefinition c if (baseDef is null) { baseDef = classType.BaseType.TryResolve(cache.RuntimeContext); - baseDef ??= cache.Find(string.IsNullOrEmpty(ns) ? nm : (ns + "." + nm)); + baseDef ??= cache.Find(ns, nm); } if (baseDef is null) @@ -381,64 +214,8 @@ internal static int GetClassHierarchyIndex(MetadataCache cache, TypeDefinition c /// /// Returns whether two interface types refer to the same interface by namespace+name (used to compare interfaces across module boundaries). /// - internal static bool InterfacesEqualByName(TypeDefinition a, TypeDefinition b) - { - if (a == b) - { - return true; - } - - return (a.Namespace?.Value ?? string.Empty) == (b.Namespace?.Value ?? string.Empty) - && (a.Name?.Value ?? string.Empty) == (b.Name?.Value ?? string.Empty); - } - - /// Strips ByReferenceTypeSignature and CustomModifierTypeSignature wrappers - /// to get the underlying type signature. - internal static TypeSignature StripByRefAndCustomModifiers(TypeSignature sig) - { - TypeSignature current = sig; - while (true) - { - if (current is ByReferenceTypeSignature br) - { - current = br.BaseType; - continue; - } - - if (current is CustomModifierTypeSignature cm) - { - current = cm.BaseType; - continue; - } - - return current; - } - } - - /// - /// Returns the C#-callsite name for (the metadata name, escaped with - /// @ when it collides with a C# keyword). When is - /// non-, it replaces the metadata name. - /// - /// The parameter to name. - /// Optional override (FastAbi-merged Methods classes use this to inject a synthesized name). - /// The escaped parameter name. - internal static string GetParamName(ParameterInfo p, string? paramNameOverride) - { - string name = paramNameOverride ?? p.Parameter.Name ?? "param"; - return CSharpKeywords.IsKeyword(name) ? "@" + name : name; - } - - /// - /// Returns the local-variable name for (the metadata name without - /// the C#-keyword @ escape, since helper local names like __event are valid - /// even when the underlying parameter name is a C# keyword). - /// - /// The parameter to name. - /// Optional override. - /// The unescaped local-variable name. - internal static string GetParamLocalName(ParameterInfo p, string? paramNameOverride) + public static bool InterfacesEqualByName(TypeDefinition a, TypeDefinition b) { - return paramNameOverride ?? p.Parameter.Name ?? "param"; + return a == b || a.Names() == b.Names(); } } diff --git a/src/WinRT.Projection.Writer/Helpers/AbiTypeWriter.cs b/src/WinRT.Projection.Writer/Helpers/AbiTypeWriter.cs index ea7950afc5..c2770247ae 100644 --- a/src/WinRT.Projection.Writer/Helpers/AbiTypeWriter.cs +++ b/src/WinRT.Projection.Writer/Helpers/AbiTypeWriter.cs @@ -3,8 +3,12 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.References; +using WindowsRuntime.ProjectionWriter.Resolvers; using WindowsRuntime.ProjectionWriter.Writers; using static WindowsRuntime.ProjectionWriter.References.ProjectionNames; using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; @@ -37,32 +41,33 @@ public static void WriteAbiType(IndentedTextWriter writer, ProjectionEmitContext writer.Write("global::WindowsRuntime.InteropServices.WindowsRuntimeTypeName"); break; case TypeSemantics.Definition d: - if (TypeCategorization.GetCategory(d.Type) is TypeCategory.Enum) + if (d.Type.IsEnum) { // Enums in WinRT ABI use the projected enum type directly (since their C# // layout matches their underlying integer ABI representation 1:1). TypedefNameWriter.WriteTypedefName(writer, context, d.Type, TypedefNameType.Projected, true); } - else if (TypeCategorization.GetCategory(d.Type) is TypeCategory.Struct) + else if (d.Type.IsStruct) { (string dNs, string dName) = d.Type.Names(); + // Special case: mapped value types that require ABI marshalling // (DateTime/TimeSpan -> ABI.System.DateTimeOffset/TimeSpan). if (dNs == WindowsFoundation && dName == "DateTime") { - writer.Write("global::ABI.System.DateTimeOffset"); + writer.Write(WellKnownAbiTypeNames.AbiSystemDateTimeOffset); break; } if (dNs == WindowsFoundation && dName == "TimeSpan") { - writer.Write("global::ABI.System.TimeSpan"); + writer.Write(WellKnownAbiTypeNames.AbiSystemTimeSpan); break; } if (dNs == WindowsFoundation && dName == HResult) { - writer.Write("global::ABI.System.Exception"); + writer.Write(WellKnownAbiTypeNames.AbiSystemException); break; } @@ -70,16 +75,17 @@ public static void WriteAbiType(IndentedTextWriter writer, ProjectionEmitContext { // System.Type ABI struct: maps to global::ABI.System.Type, not the // ABI.Windows.UI.Xaml.Interop.TypeName form. - writer.Write("global::ABI.System.Type"); + writer.Write(WellKnownAbiTypeNames.AbiSystemType); break; } TypeSignature dts = d.Type.ToTypeSignature(); - // "Almost-blittable" structs (with bool/char fields but no reference-type - // fields) can pass through using the projected type since the C# layout - // matches the WinRT ABI directly. Truly complex structs (with string/object/ - // Nullable fields) need the ABI struct. - if (context.AbiTypeShapeResolver.IsBlittableStruct(dts)) + + // Blittable structs (whose projected layout matches the WinRT ABI + // representation, including ones with bool/char fields) can pass through + // using the projected type. Complex structs (with string/object/runtime-class/ + // Nullable/marshalling-mapped fields) need the ABI struct. + if (context.AbiTypeKindResolver.IsBlittableStruct(dts)) { TypedefNameWriter.WriteTypedefName(writer, context, d.Type, TypedefNameType.Projected, true); } @@ -95,76 +101,76 @@ public static void WriteAbiType(IndentedTextWriter writer, ProjectionEmitContext break; case TypeSemantics.Reference r: + // Cross-module typeref: try resolving the type, applying mapped-type translation // for the field/parameter type after resolution. - if (context.Cache is not null) + (string rns, string rname) = r.Type.Names(); + + // Special case: mapped value types that require ABI marshalling. + if (rns == WindowsFoundation && rname == "DateTime") { - (string rns, string rname) = r.Type.Names(); - // Special case: mapped value types that require ABI marshalling. - if (rns == WindowsFoundation && rname == "DateTime") - { - writer.Write("global::ABI.System.DateTimeOffset"); - break; - } + writer.Write(WellKnownAbiTypeNames.AbiSystemDateTimeOffset); + break; + } - if (rns == WindowsFoundation && rname == "TimeSpan") + if (rns == WindowsFoundation && rname == "TimeSpan") + { + writer.Write(WellKnownAbiTypeNames.AbiSystemTimeSpan); + break; + } + + if (rns == WindowsFoundation && rname == HResult) + { + writer.Write(WellKnownAbiTypeNames.AbiSystemException); + break; + } + + // Look up the type by its ORIGINAL (unmapped) name in the cache. + TypeDefinition? rd = context.Cache.Find(rns, rname); + + // If not found, try the mapped name (for cases where the mapping target is in the cache). + if (rd is null) + { + if (MappedTypes.Get(rns, rname) is { } rmapped) { - writer.Write("global::ABI.System.TimeSpan"); - break; + rd = context.Cache.Find(rmapped.MappedNamespace, rmapped.MappedName); } + } - if (rns == WindowsFoundation && rname == HResult) + if (rd is not null) + { + TypeKind kind = TypeKindResolver.Resolve(rd); + + if (kind == TypeKind.Enum) { - writer.Write("global::ABI.System.Exception"); + // Enums use the projected enum type directly (C# layout == ABI layout). + TypedefNameWriter.WriteTypedefName(writer, context, rd, TypedefNameType.Projected, true); break; } - // Look up the type by its ORIGINAL (unmapped) name in the cache. - TypeDefinition? rd = context.Cache.Find(rns + "." + rname); - // If not found, try the mapped name (for cases where the mapping target is in the cache). - if (rd is null) + if (kind == TypeKind.Struct) { - if (MappedTypes.Get(rns, rname) is { } rmapped) + // Special case: HResult is mapped to System.Exception (a reference type) + // but its ABI representation is the global::ABI.System.Exception struct + // (which wraps the underlying HRESULT int). + (string rdNs, string rdName) = rd.Names(); + + if (rdNs == WindowsFoundation && rdName == HResult) { - rd = context.Cache.Find(rmapped.MappedNamespace + "." + rmapped.MappedName); + writer.Write(WellKnownAbiTypeNames.AbiSystemException); + break; } - } - - if (rd is not null) - { - TypeCategory cat = TypeCategorization.GetCategory(rd); - if (cat == TypeCategory.Enum) + if (context.AbiTypeKindResolver.IsBlittableStruct(rd.ToTypeSignature())) { - // Enums use the projected enum type directly (C# layout == ABI layout). TypedefNameWriter.WriteTypedefName(writer, context, rd, TypedefNameType.Projected, true); - break; } - - if (cat == TypeCategory.Struct) + else { - // Special case: HResult is mapped to System.Exception (a reference type) - // but its ABI representation is the global::ABI.System.Exception struct - // (which wraps the underlying HRESULT int). - (string rdNs, string rdName) = rd.Names(); - - if (rdNs == WindowsFoundation && rdName == HResult) - { - writer.Write("global::ABI.System.Exception"); - break; - } - - if (context.AbiTypeShapeResolver.IsBlittableStruct(rd.ToTypeSignature())) - { - TypedefNameWriter.WriteTypedefName(writer, context, rd, TypedefNameType.Projected, true); - } - else - { - TypedefNameWriter.WriteTypedefName(writer, context, rd, TypedefNameType.ABI, true); - } - - break; + TypedefNameWriter.WriteTypedefName(writer, context, rd, TypedefNameType.ABI, true); } + + break; } } @@ -175,7 +181,6 @@ public static void WriteAbiType(IndentedTextWriter writer, ProjectionEmitContext // void* (it's a runtime class/interface/delegate). if (r.IsValueType) { - (string rns, string rname) = r.Type.Names(); writer.Write(GlobalPrefix); if (!string.IsNullOrEmpty(rns)) @@ -198,6 +203,13 @@ public static void WriteAbiType(IndentedTextWriter writer, ProjectionEmitContext } } + /// + /// A callback that writes the ABI type to the writer it's appended to. + public static WriteAbiTypeCallback WriteAbiType(ProjectionEmitContext context, TypeSemantics semantics) + { + return new(context, semantics); + } + /// /// Returns the ABI C# type name for a fundamental type (e.g. "bool" -> "byte", "char" -> "ushort"). /// diff --git a/src/WinRT.Projection.Writer/Helpers/AdditionTypes.cs b/src/WinRT.Projection.Writer/Helpers/AdditionTypes.cs deleted file mode 100644 index 7162940aa0..0000000000 --- a/src/WinRT.Projection.Writer/Helpers/AdditionTypes.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Frozen; -using System.Collections.Generic; - -namespace WindowsRuntime.ProjectionWriter.Helpers; - -/// -/// Static lookup for the (namespace, type-name) pairs that have an associated namespace-additions -/// resource. Used by the projected-struct emitter to decide whether the struct should be projected -/// as partial (so the addition file's content can extend it). -/// -internal static class AdditionTypes -{ - private static readonly FrozenDictionary> Table = new Dictionary>(StringComparer.Ordinal) - { - ["Microsoft.UI.Xaml"] = FrozenSet.Create(StringComparer.Ordinal, "Thickness"), - ["Microsoft.UI.Xaml.Controls.Primitives"] = FrozenSet.Create(StringComparer.Ordinal, "GeneratorPosition"), - ["Microsoft.UI.Xaml.Media"] = FrozenSet.Create(StringComparer.Ordinal, "Matrix"), - ["Microsoft.UI.Xaml.Media.Animation"] = FrozenSet.Create(StringComparer.Ordinal, "KeyTime"), - ["Windows.UI"] = FrozenSet.Create(StringComparer.Ordinal, "Color"), - ["Windows.UI.Xaml"] = FrozenSet.Create(StringComparer.Ordinal, "Thickness"), - ["Windows.UI.Xaml.Controls.Primitives"] = FrozenSet.Create(StringComparer.Ordinal, "GeneratorPosition"), - ["Windows.UI.Xaml.Media"] = FrozenSet.Create(StringComparer.Ordinal, "Matrix"), - ["Windows.UI.Xaml.Media.Animation"] = FrozenSet.Create(StringComparer.Ordinal, "KeyTime"), - }.ToFrozenDictionary(StringComparer.Ordinal); - - /// - /// Returns whether the type identified by (, ) - /// has an associated namespace-additions resource. - /// - /// The type's namespace. - /// The type's short name (without generic arity). - /// if an addition exists; otherwise . - public static bool HasAdditionToType(string typeNamespace, string typeName) - { - return Table.TryGetValue(typeNamespace, out FrozenSet? names) && names.Contains(typeName); - } -} diff --git a/src/WinRT.Projection.Writer/Helpers/Additions.cs b/src/WinRT.Projection.Writer/Helpers/Additions.cs index e782a1c7d4..fc9e31f0ac 100644 --- a/src/WinRT.Projection.Writer/Helpers/Additions.cs +++ b/src/WinRT.Projection.Writer/Helpers/Additions.cs @@ -3,7 +3,6 @@ using System.Collections.Frozen; using System.Collections.Generic; -using System.Linq; namespace WindowsRuntime.ProjectionWriter.Helpers; @@ -15,43 +14,87 @@ namespace WindowsRuntime.ProjectionWriter.Helpers; internal static class Additions { /// - /// (namespace, embedded-resource-manifest-name) pairs. The manifest-name resolves to a - /// call. + /// Lookup of the embedded-resource manifest names for a given target namespace, in the + /// order they should be appended to the projection of that namespace. Each manifest name + /// resolves to a + /// call. /// - public static readonly IReadOnlyList<(string Namespace, string ResourceName)> All = - [ - ("Microsoft.UI.Dispatching", "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Dispatching.Microsoft.UI.Dispatching.DispatcherQueueSynchronizationContext.cs"), - ("Microsoft.UI.Xaml", "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.CornerRadius.cs"), - ("Microsoft.UI.Xaml", "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.Duration.cs"), - ("Microsoft.UI.Xaml", "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.GridLength.cs"), - ("Microsoft.UI.Xaml", "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.SR.cs"), - ("Microsoft.UI.Xaml", "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.Thickness.cs"), - ("Microsoft.UI.Xaml.Controls.Primitives", "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Controls.Primitives.Microsoft.UI.Xaml.Controls.Primitives.GeneratorPosition.cs"), - ("Microsoft.UI.Xaml.Media", "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Media.Microsoft.UI.Xaml.Media.Matrix.cs"), - ("Microsoft.UI.Xaml.Media.Animation", "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Media.Animation.Microsoft.UI.Xaml.Media.Animation.KeyTime.cs"), - ("Microsoft.UI.Xaml.Media.Animation", "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Media.Animation.Microsoft.UI.Xaml.Media.Animation.RepeatBehavior.cs"), - ("Microsoft.UI.Xaml.Media.Media3D", "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Media.Media3D.Microsoft.UI.Xaml.Media.Media3D.Matrix3D.cs"), - ("Windows.Storage", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.Storage.WindowsRuntimeStorageExtensions.cs"), - ("Windows.UI", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Windows.UI.Color.cs"), - ("Windows.UI.Xaml", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Windows.System.DispatcherQueueSynchronizationContext.cs"), - ("Windows.UI.Xaml", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.CornerRadius.cs"), - ("Windows.UI.Xaml", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.Duration.cs"), - ("Windows.UI.Xaml", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.GridLength.cs"), - ("Windows.UI.Xaml", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.SR.cs"), - ("Windows.UI.Xaml", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.Thickness.cs"), - ("Windows.UI.Xaml.Controls.Primitives", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Controls.Primitives.Windows.UI.Xaml.Controls.Primitives.GeneratorPosition.cs"), - ("Windows.UI.Xaml.Media", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Media.Windows.UI.Xaml.Media.Matrix.cs"), - ("Windows.UI.Xaml.Media.Animation", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Media.Animation.Windows.UI.Xaml.Media.Animation.KeyTime.cs"), - ("Windows.UI.Xaml.Media.Animation", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Media.Animation.Windows.UI.Xaml.Media.Animation.RepeatBehavior.cs"), - ("Windows.UI.Xaml.Media.Media3D", "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Media.Media3D.Windows.UI.Xaml.Media.Media3D.Matrix3D.cs"), - ]; + private static readonly FrozenDictionary ByNamespace = new Dictionary + { + ["Microsoft.UI.Dispatching"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Dispatching.Microsoft.UI.Dispatching.DispatcherQueueSynchronizationContext.cs", + ], + ["Microsoft.UI.Xaml"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.CornerRadius.cs", + "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.Duration.cs", + "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.GridLength.cs", + "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.SR.cs", + "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Microsoft.UI.Xaml.Thickness.cs", + ], + ["Microsoft.UI.Xaml.Controls.Primitives"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Controls.Primitives.Microsoft.UI.Xaml.Controls.Primitives.GeneratorPosition.cs", + ], + ["Microsoft.UI.Xaml.Media"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Media.Microsoft.UI.Xaml.Media.Matrix.cs", + ], + ["Microsoft.UI.Xaml.Media.Animation"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Media.Animation.Microsoft.UI.Xaml.Media.Animation.KeyTime.cs", + "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Media.Animation.Microsoft.UI.Xaml.Media.Animation.RepeatBehavior.cs", + ], + ["Microsoft.UI.Xaml.Media.Media3D"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Microsoft.UI.Xaml.Media.Media3D.Microsoft.UI.Xaml.Media.Media3D.Matrix3D.cs", + ], + ["Windows.Storage"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.Storage.WindowsRuntimeStorageExtensions.cs", + ], + ["Windows.UI"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Windows.UI.Color.cs", + ], + ["Windows.UI.Xaml"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Windows.System.DispatcherQueueSynchronizationContext.cs", + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.CornerRadius.cs", + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.Duration.cs", + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.GridLength.cs", + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.SR.cs", + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Windows.UI.Xaml.Thickness.cs", + ], + ["Windows.UI.Xaml.Controls.Primitives"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Controls.Primitives.Windows.UI.Xaml.Controls.Primitives.GeneratorPosition.cs", + ], + ["Windows.UI.Xaml.Media"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Media.Windows.UI.Xaml.Media.Matrix.cs", + ], + ["Windows.UI.Xaml.Media.Animation"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Media.Animation.Windows.UI.Xaml.Media.Animation.KeyTime.cs", + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Media.Animation.Windows.UI.Xaml.Media.Animation.RepeatBehavior.cs", + ], + ["Windows.UI.Xaml.Media.Media3D"] = + [ + "WindowsRuntime.ProjectionWriter.Resources.Additions.Windows.UI.Xaml.Media.Media3D.Windows.UI.Xaml.Media.Media3D.Matrix3D.cs", + ], + }.ToFrozenDictionary(); /// - /// Lookup of the manifest resource names for a given target namespace, in the same order they - /// appear in . Lookups against this map replace per-namespace linear scans of - /// in the per-namespace emission path. + /// Enumerates the embedded-resource manifest names registered for the given target namespace, + /// in the order they should be appended to the projection of that namespace. Returns an empty + /// sequence when no additions are registered for . /// - public static readonly FrozenDictionary ByNamespace = - All.GroupBy(static x => x.Namespace) - .ToFrozenDictionary(static g => g.Key, static g => g.Select(x => x.ResourceName).ToArray()); + /// The target namespace. + /// The manifest resource names; an empty sequence if none. + public static IEnumerable EnumerateByNamespace(string ns) + { + return ByNamespace.TryGetValue(ns, out string[]? resourceNames) ? resourceNames : []; + } } \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Helpers/ArrayElementEncoder.cs b/src/WinRT.Projection.Writer/Helpers/ArrayElementEncoder.cs index 549823141e..8beafbe407 100644 --- a/src/WinRT.Projection.Writer/Helpers/ArrayElementEncoder.cs +++ b/src/WinRT.Projection.Writer/Helpers/ArrayElementEncoder.cs @@ -26,6 +26,7 @@ internal static string GetArrayMarshallerInteropPath(TypeSignature elementType) // but inside the array brackets the interop generator uses the depth=0 form (assembly + just name). // Re-encode the element with the top-level form for accurate matching. string topLevelElement = EncodeArrayElementName(elementType); + // Resolve the element's namespace to determine the path prefix. string ns = AbiTypeHelpers.GetMappedNamespace(elementType); @@ -82,6 +83,7 @@ private static void EncodeArrayElementNameInto(System.Text.StringBuilder sb, Typ private static void EncodeArrayElementForTypeDef(StringBuilder sb, ITypeDefOrRef type, IList? generic_args) { (string typeNs, string typeName) = type.Names(); + // Apply mapped-type remapping (e.g. Windows.Foundation.IReference -> System.Nullable). MappedType? mapped = MappedTypes.Get(typeNs, typeName); @@ -98,6 +100,7 @@ private static void EncodeArrayElementForTypeDef(StringBuilder sb, ITypeDefOrRef // types resolve to their actual assembly name (e.g. ) instead of // defaulting to <#Windows>. _ = sb.Append(InteropTypeNameWriter.GetInteropAssemblyMarker(typeNs, typeName, mapped, type)); + // Top-level: just the type name (no namespace). _ = sb.Append(typeName); diff --git a/src/WinRT.Projection.Writer/Helpers/ArrayTempNames.cs b/src/WinRT.Projection.Writer/Helpers/ArrayTempNames.cs new file mode 100644 index 0000000000..438bdbbd9f --- /dev/null +++ b/src/WinRT.Projection.Writer/Helpers/ArrayTempNames.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WindowsRuntime.ProjectionWriter.Helpers; + +/// +/// Naming conventions for the array-temporary locals used by inline-array-or-array-pool +/// marshalling stubs (used by DoAbi / RcwCaller / ConstructorFactory). +/// Centralises the __{prefix}_inlineArray / __{prefix}_arrayFromPool / +/// __{prefix}_span name conventions so future churn lands in one place. +/// +/// The parameter/local identifier prefix (e.g. the raw or call name). +internal readonly record struct ArrayTempNames(string Prefix) +{ + /// The local-variable name for the InlineArray16<T> inline buffer. + public string InlineArray => "__" + Prefix + "_inlineArray"; + + /// The local-variable name for the T[] array-pool rental. + public string ArrayFromPool => "__" + Prefix + "_arrayFromPool"; + + /// The local-variable name for the Span<T> view of either backing store. + public string Span => "__" + Prefix + "_span"; + + /// Auxiliary triple used when string-array marshalling needs a parallel HStringHeader[] pool. + public string InlineHeaderArray => "__" + Prefix + "_inlineHeaderArray"; + + /// The companion to . + public string HeaderArrayFromPool => "__" + Prefix + "_headerArrayFromPool"; + + /// The companion to . + public string HeaderSpan => "__" + Prefix + "_headerSpan"; + + /// Auxiliary triple used when string-array marshalling needs a parallel pinned-handle nint[] pool. + public string InlinePinnedHandleArray => "__" + Prefix + "_inlinePinnedHandleArray"; + + /// The companion to . + public string PinnedHandleArrayFromPool => "__" + Prefix + "_pinnedHandleArrayFromPool"; + + /// The companion to . + public string PinnedHandleSpan => "__" + Prefix + "_pinnedHandleSpan"; +} diff --git a/src/WinRT.Projection.Writer/Helpers/AttributedTypes.cs b/src/WinRT.Projection.Writer/Helpers/AttributedTypes.cs index 0c1529cdce..814b642f5b 100644 --- a/src/WinRT.Projection.Writer/Helpers/AttributedTypes.cs +++ b/src/WinRT.Projection.Writer/Helpers/AttributedTypes.cs @@ -70,7 +70,7 @@ public static IEnumerable> Get(TypeDefiniti continue; } - string key = info.Type?.Name?.Value ?? string.Empty; + string key = info.Type?.GetRawName() ?? string.Empty; result[key] = info; } @@ -89,36 +89,12 @@ public static IEnumerable> Get(TypeDefiniti /// private static TypeDefinition? GetSystemType(CustomAttribute attr, MetadataCache cache) { - if (attr.Signature is null) + if (!attr.TryGetFixedArgument(0, out TypeSignature? sig)) { return null; } - for (int i = 0; i < attr.Signature.FixedArguments.Count; i++) - { - CustomAttributeArgument arg = attr.Signature.FixedArguments[i]; - // For System.Type args in WinMD, the value is typically a TypeSignature - if (arg.Element is TypeSignature sig) - { - string typeName = sig.FullName ?? string.Empty; - TypeDefinition? td = cache.Find(typeName); - - if (td is not null) - { - return td; - } - } - else if (arg.Element is string s) - { - TypeDefinition? td = cache.Find(s); - - if (td is not null) - { - return td; - } - } - } - return null; + return cache.Find(sig.FullName ?? string.Empty); } /// diff --git a/src/WinRT.Projection.Writer/Helpers/CSharpKeywords.cs b/src/WinRT.Projection.Writer/Helpers/CSharpKeywords.cs index b03e9ca4fa..53eacb95de 100644 --- a/src/WinRT.Projection.Writer/Helpers/CSharpKeywords.cs +++ b/src/WinRT.Projection.Writer/Helpers/CSharpKeywords.cs @@ -10,8 +10,11 @@ namespace WindowsRuntime.ProjectionWriter.Helpers; /// internal static class CSharpKeywords { - private static readonly FrozenSet Keywords = new[] - { + /// + /// The set of well-known C# keywords. + /// + private static readonly FrozenSet Keywords = + [ "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long", @@ -19,7 +22,7 @@ internal static class CSharpKeywords "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void", "volatile", "while" - }.ToFrozenSet(); + ]; /// /// Returns whether is a reserved C# language keyword. diff --git a/src/WinRT.Projection.Writer/Helpers/ContractPlatforms.cs b/src/WinRT.Projection.Writer/Helpers/ContractPlatforms.cs index d4667a180d..dea2f9eaa2 100644 --- a/src/WinRT.Projection.Writer/Helpers/ContractPlatforms.cs +++ b/src/WinRT.Projection.Writer/Helpers/ContractPlatforms.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Frozen; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace WindowsRuntime.ProjectionWriter.Helpers; @@ -12,105 +12,112 @@ namespace WindowsRuntime.ProjectionWriter.Helpers; /// internal static class ContractPlatforms { - private static readonly FrozenDictionary Table = Build(); + /// + /// The mapping of all platforms and contract versions. + /// + private static readonly FrozenDictionary Table = new Dictionary + { + ["Windows.AI.MachineLearning.MachineLearningContract"] = + [(1, "10.0.17763.0"), (2, "10.0.18362.0"), (3, "10.0.19041.0"), (4, "10.0.20348.0"), (5, "10.0.22000.0")], + ["Windows.AI.MachineLearning.Preview.MachineLearningPreviewContract"] = + [(1, "10.0.17134.0"), (2, "10.0.17763.0")], + ["Windows.ApplicationModel.Calls.Background.CallsBackgroundContract"] = + [(1, "10.0.17763.0"), (2, "10.0.18362.0"), (3, "10.0.20348.0"), (4, "10.0.22621.0")], + ["Windows.ApplicationModel.Calls.CallsPhoneContract"] = + [(4, "10.0.17763.0"), (5, "10.0.18362.0"), (6, "10.0.20348.0"), (7, "10.0.22621.0")], + ["Windows.ApplicationModel.Calls.CallsVoipContract"] = + [(1, "10.0.10586.0"), (2, "10.0.16299.0"), (3, "10.0.17134.0"), (4, "10.0.17763.0"), (5, "10.0.26100.0")], + ["Windows.ApplicationModel.CommunicationBlocking.CommunicationBlockingContract"] = + [(2, "10.0.17763.0")], + ["Windows.ApplicationModel.SocialInfo.SocialInfoContract"] = + [(1, "10.0.14393.0"), (2, "10.0.15063.0")], + ["Windows.ApplicationModel.StartupTaskContract"] = + [(2, "10.0.16299.0"), (3, "10.0.17134.0")], + ["Windows.Devices.Custom.CustomDeviceContract"] = + [(1, "10.0.16299.0")], + ["Windows.Devices.DevicesLowLevelContract"] = + [(2, "10.0.14393.0"), (3, "10.0.15063.0")], + ["Windows.Devices.Printers.PrintersContract"] = + [(1, "10.0.10586.0")], + ["Windows.Devices.SmartCards.SmartCardBackgroundTriggerContract"] = + [(3, "10.0.16299.0")], + ["Windows.Devices.SmartCards.SmartCardEmulatorContract"] = + [(5, "10.0.16299.0"), (6, "10.0.17763.0")], + ["Windows.Foundation.FoundationContract"] = + [(1, "10.0.10240.0"), (2, "10.0.10586.0"), (3, "10.0.15063.0"), (4, "10.0.19041.0")], + ["Windows.Foundation.UniversalApiContract"] = + [(1, "10.0.10240.0"), (2, "10.0.10586.0"), (3, "10.0.14393.0"), (4, "10.0.15063.0"), (5, "10.0.16299.0"), + (6, "10.0.17134.0"), (7, "10.0.17763.0"), (8, "10.0.18362.0"), (10, "10.0.19041.0"), (12, "10.0.20348.0"), + (14, "10.0.22000.0"), (15, "10.0.22621.0"), (19, "10.0.26100.0")], + ["Windows.Foundation.VelocityIntegration.VelocityIntegrationContract"] = + [(1, "10.0.17134.0")], + ["Windows.Gaming.XboxLive.StorageApiContract"] = + [(1, "10.0.16299.0")], + ["Windows.Graphics.Printing3D.Printing3DContract"] = + [(2, "10.0.10586.0"), (3, "10.0.14393.0"), (4, "10.0.16299.0")], + ["Windows.Networking.Connectivity.WwanContract"] = + [(1, "10.0.10240.0"), (2, "10.0.17134.0"), (3, "10.0.26100.0")], + ["Windows.Networking.Sockets.ControlChannelTriggerContract"] = + [(3, "10.0.17763.0")], + ["Windows.Security.Isolation.IsolatedWindowsEnvironmentContract"] = + [(1, "10.0.19041.0"), (3, "10.0.20348.0"), (4, "10.0.22621.0"), (5, "10.0.26100.0")], + ["Windows.Services.Maps.GuidanceContract"] = + [(3, "10.0.17763.0")], + ["Windows.Services.Maps.LocalSearchContract"] = + [(4, "10.0.17763.0")], + ["Windows.Services.Store.StoreContract"] = + [(1, "10.0.14393.0"), (2, "10.0.15063.0"), (3, "10.0.17134.0"), (4, "10.0.17763.0")], + ["Windows.Services.TargetedContent.TargetedContentContract"] = + [(1, "10.0.15063.0")], + ["Windows.Storage.Provider.CloudFilesContract"] = + [(4, "10.0.19041.0"), (6, "10.0.20348.0"), (7, "10.0.22621.0")], + ["Windows.System.Profile.ProfileHardwareTokenContract"] = + [(1, "10.0.14393.0")], + ["Windows.System.Profile.ProfileRetailInfoContract"] = + [(1, "10.0.20348.0")], + ["Windows.System.Profile.ProfileSharedModeContract"] = + [(1, "10.0.14393.0"), (2, "10.0.15063.0")], + ["Windows.System.Profile.SystemManufacturers.SystemManufacturersContract"] = + [(3, "10.0.17763.0")], + ["Windows.System.SystemManagementContract"] = + [(6, "10.0.17763.0"), (7, "10.0.19041.0")], + ["Windows.UI.UIAutomation.UIAutomationContract"] = + [(1, "10.0.20348.0"), (2, "10.0.22000.0")], + ["Windows.UI.ViewManagement.ViewManagementViewScalingContract"] = + [(1, "10.0.14393.0")], + ["Windows.UI.Xaml.Core.Direct.XamlDirectContract"] = + [(1, "10.0.17763.0"), (2, "10.0.18362.0"), (3, "10.0.20348.0"), (5, "10.0.22000.0")], + }.ToFrozenDictionary(); /// /// Returns the platform version (e.g., "10.0.17763.0") that introduced the given contract version, /// or empty string if not found. /// - public static string GetPlatform(string contractName, int contractVersion) + /// The contract name to use. + /// The contract version to use. + /// The resulting platform, if found. + public static bool TryGetPlatform(string contractName, int contractVersion, [NotNullWhen(true)] out string? platform) { if (!Table.TryGetValue(contractName, out (int Version, string Platform)[]? versions)) { - return string.Empty; + platform = null; + + return false; } - // Find the first version >= contractVersion. + // Find the first version greater than the input contract version for (int i = 0; i < versions.Length; i++) { if (versions[i].Version >= contractVersion) { - return versions[i].Platform; + platform = versions[i].Platform; + + return true; } } - return string.Empty; - } - private static FrozenDictionary Build() - { - Dictionary t = new(StringComparer.Ordinal) - { - ["Windows.AI.MachineLearning.MachineLearningContract"] = - [(1, "10.0.17763.0"), (2, "10.0.18362.0"), (3, "10.0.19041.0"), (4, "10.0.20348.0"), (5, "10.0.22000.0")], - ["Windows.AI.MachineLearning.Preview.MachineLearningPreviewContract"] = - [(1, "10.0.17134.0"), (2, "10.0.17763.0")], - ["Windows.ApplicationModel.Calls.Background.CallsBackgroundContract"] = - [(1, "10.0.17763.0"), (2, "10.0.18362.0"), (3, "10.0.20348.0"), (4, "10.0.22621.0")], - ["Windows.ApplicationModel.Calls.CallsPhoneContract"] = - [(4, "10.0.17763.0"), (5, "10.0.18362.0"), (6, "10.0.20348.0"), (7, "10.0.22621.0")], - ["Windows.ApplicationModel.Calls.CallsVoipContract"] = - [(1, "10.0.10586.0"), (2, "10.0.16299.0"), (3, "10.0.17134.0"), (4, "10.0.17763.0"), (5, "10.0.26100.0")], - ["Windows.ApplicationModel.CommunicationBlocking.CommunicationBlockingContract"] = - [(2, "10.0.17763.0")], - ["Windows.ApplicationModel.SocialInfo.SocialInfoContract"] = - [(1, "10.0.14393.0"), (2, "10.0.15063.0")], - ["Windows.ApplicationModel.StartupTaskContract"] = - [(2, "10.0.16299.0"), (3, "10.0.17134.0")], - ["Windows.Devices.Custom.CustomDeviceContract"] = - [(1, "10.0.16299.0")], - ["Windows.Devices.DevicesLowLevelContract"] = - [(2, "10.0.14393.0"), (3, "10.0.15063.0")], - ["Windows.Devices.Printers.PrintersContract"] = - [(1, "10.0.10586.0")], - ["Windows.Devices.SmartCards.SmartCardBackgroundTriggerContract"] = - [(3, "10.0.16299.0")], - ["Windows.Devices.SmartCards.SmartCardEmulatorContract"] = - [(5, "10.0.16299.0"), (6, "10.0.17763.0")], - ["Windows.Foundation.FoundationContract"] = - [(1, "10.0.10240.0"), (2, "10.0.10586.0"), (3, "10.0.15063.0"), (4, "10.0.19041.0")], - ["Windows.Foundation.UniversalApiContract"] = - [(1, "10.0.10240.0"), (2, "10.0.10586.0"), (3, "10.0.14393.0"), (4, "10.0.15063.0"), (5, "10.0.16299.0"), - (6, "10.0.17134.0"), (7, "10.0.17763.0"), (8, "10.0.18362.0"), (10, "10.0.19041.0"), (12, "10.0.20348.0"), - (14, "10.0.22000.0"), (15, "10.0.22621.0"), (19, "10.0.26100.0")], - ["Windows.Foundation.VelocityIntegration.VelocityIntegrationContract"] = - [(1, "10.0.17134.0")], - ["Windows.Gaming.XboxLive.StorageApiContract"] = - [(1, "10.0.16299.0")], - ["Windows.Graphics.Printing3D.Printing3DContract"] = - [(2, "10.0.10586.0"), (3, "10.0.14393.0"), (4, "10.0.16299.0")], - ["Windows.Networking.Connectivity.WwanContract"] = - [(1, "10.0.10240.0"), (2, "10.0.17134.0"), (3, "10.0.26100.0")], - ["Windows.Networking.Sockets.ControlChannelTriggerContract"] = - [(3, "10.0.17763.0")], - ["Windows.Security.Isolation.IsolatedWindowsEnvironmentContract"] = - [(1, "10.0.19041.0"), (3, "10.0.20348.0"), (4, "10.0.22621.0"), (5, "10.0.26100.0")], - ["Windows.Services.Maps.GuidanceContract"] = - [(3, "10.0.17763.0")], - ["Windows.Services.Maps.LocalSearchContract"] = - [(4, "10.0.17763.0")], - ["Windows.Services.Store.StoreContract"] = - [(1, "10.0.14393.0"), (2, "10.0.15063.0"), (3, "10.0.17134.0"), (4, "10.0.17763.0")], - ["Windows.Services.TargetedContent.TargetedContentContract"] = - [(1, "10.0.15063.0")], - ["Windows.Storage.Provider.CloudFilesContract"] = - [(4, "10.0.19041.0"), (6, "10.0.20348.0"), (7, "10.0.22621.0")], - ["Windows.System.Profile.ProfileHardwareTokenContract"] = - [(1, "10.0.14393.0")], - ["Windows.System.Profile.ProfileRetailInfoContract"] = - [(1, "10.0.20348.0")], - ["Windows.System.Profile.ProfileSharedModeContract"] = - [(1, "10.0.14393.0"), (2, "10.0.15063.0")], - ["Windows.System.Profile.SystemManufacturers.SystemManufacturersContract"] = - [(3, "10.0.17763.0")], - ["Windows.System.SystemManagementContract"] = - [(6, "10.0.17763.0"), (7, "10.0.19041.0")], - ["Windows.UI.UIAutomation.UIAutomationContract"] = - [(1, "10.0.20348.0"), (2, "10.0.22000.0")], - ["Windows.UI.ViewManagement.ViewManagementViewScalingContract"] = - [(1, "10.0.14393.0")], - ["Windows.UI.Xaml.Core.Direct.XamlDirectContract"] = - [(1, "10.0.17763.0"), (2, "10.0.18362.0"), (3, "10.0.20348.0"), (5, "10.0.22000.0")], - }; - return t.ToFrozenDictionary(StringComparer.Ordinal); + platform = null; + + return false; } } \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Helpers/IdentifierEscaping.cs b/src/WinRT.Projection.Writer/Helpers/IdentifierEscaping.cs index a1798b2155..f0158bed3a 100644 --- a/src/WinRT.Projection.Writer/Helpers/IdentifierEscaping.cs +++ b/src/WinRT.Projection.Writer/Helpers/IdentifierEscaping.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Writers; namespace WindowsRuntime.ProjectionWriter.Helpers; @@ -22,6 +23,17 @@ public static string StripBackticks(string typeName) return idx >= 0 ? typeName[..idx] : typeName; } + /// + /// Returns prefixed with @ if it is a reserved C# keyword; + /// otherwise returns it unchanged. + /// + /// The identifier to escape. + /// The escaped identifier. + public static string EscapeIdentifier(string identifier) + { + return CSharpKeywords.IsKeyword(identifier) ? "@" + identifier : identifier; + } + /// /// Writes to , prefixed with @ /// if it is a reserved C# keyword. @@ -30,14 +42,18 @@ public static string StripBackticks(string typeName) /// The identifier to write. public static void WriteEscapedIdentifier(IndentedTextWriter writer, string identifier) { - if (CSharpKeywords.IsKeyword(identifier)) - { - writer.Write("@"); - } + writer.WriteIf(CSharpKeywords.IsKeyword(identifier), "@"); writer.Write(identifier); } + /// + /// A callback that writes the escaped identifier to the writer it's appended to. + public static WriteEscapedIdentifierCallback WriteEscapedIdentifier(string identifier) + { + return new(identifier); + } + /// /// Returns the camel-case form of : if the first character is an /// upper-case ASCII letter, it is lowered; otherwise is returned @@ -61,4 +77,4 @@ public static string ToCamelCase(string name) return name; } -} \ No newline at end of file +} diff --git a/src/WinRT.Projection.Writer/Helpers/IidExpressionGenerator.cs b/src/WinRT.Projection.Writer/Helpers/IidExpressionGenerator.cs index 0b2afafa79..6424cfbbea 100644 --- a/src/WinRT.Projection.Writer/Helpers/IidExpressionGenerator.cs +++ b/src/WinRT.Projection.Writer/Helpers/IidExpressionGenerator.cs @@ -2,51 +2,35 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; using System.Globalization; +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; using WindowsRuntime.ProjectionWriter.Errors; using WindowsRuntime.ProjectionWriter.Factories; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Metadata; using WindowsRuntime.ProjectionWriter.Writers; using static WindowsRuntime.ProjectionWriter.References.ProjectionNames; -using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; namespace WindowsRuntime.ProjectionWriter.Helpers; /// -/// Helpers for emitting WinRT GUID / IID expressions: signature characters for the GUID -/// hash algorithm, the canonical hyphenated string form of a type's [Guid], and the -/// byte-list form used when initializing native IID storage. +/// Helpers for emitting WinRT GUID / IID expressions: the canonical hyphenated string form of a +/// type's [Guid], the byte-list form used when initializing native IID storage, and +/// the IID_X / IID_XReference property emissions on the generated InterfaceIIDs +/// static class. /// +/// +/// The WinRT parametric signature generation that drives the IID hash for generic instances +/// lives in ; +/// is the only consumer of it from this file. +/// internal static partial class IidExpressionGenerator { - /// - /// Returns the GUID-signature character code for a fundamental WinRT type. - /// - public static string GetFundamentalTypeGuidSignature(FundamentalType t) => t switch - { - FundamentalType.Boolean => "b1", - FundamentalType.Char => "c2", - FundamentalType.Int8 => "i1", - FundamentalType.UInt8 => "u1", - FundamentalType.Int16 => "i2", - FundamentalType.UInt16 => "u2", - FundamentalType.Int32 => "i4", - FundamentalType.UInt32 => "u4", - FundamentalType.Int64 => "i8", - FundamentalType.UInt64 => "u8", - FundamentalType.Float => "f4", - FundamentalType.Double => "f8", - FundamentalType.String => "string", - _ => throw WellKnownProjectionWriterExceptions.UnknownFundamentalType() - }; - [GeneratedRegex(@"[ :<>`,.]")] - private static partial Regex TypeNameEscapeRegex(); + private static partial Regex TypeNameEscapeRegex { get; } /// /// Escapes a type name into a C# identifier-safe form. @@ -54,7 +38,7 @@ internal static partial class IidExpressionGenerator public static string EscapeTypeNameForIdentifier(string typeName, bool stripGlobal = false, bool stripGlobalABI = false) { // Escape special chars first, then strip ONLY the prefix (not all occurrences). - string result = TypeNameEscapeRegex().Replace(typeName, "_"); + string result = TypeNameEscapeRegex.Replace(typeName, "_"); if (stripGlobalABI && typeName.StartsWith(GlobalAbiPrefix, StringComparison.Ordinal)) { @@ -69,370 +53,220 @@ public static string EscapeTypeNameForIdentifier(string typeName, bool stripGlob } /// - /// Reads the GUID values from the [GuidAttribute] of the type and returns them as a tuple. + /// Writes in canonical hyphenated string form + /// (e.g. 00000000-0000-0000-0000-000000000000). /// - public static (uint Data1, ushort Data2, ushort Data3, byte[] Data4)? GetGuidFields(TypeDefinition type) + /// The writer to emit to. + /// The GUID value. + /// When , hex digits are lower-case; otherwise upper-case. + private static void WriteGuid(IndentedTextWriter writer, Guid guid, bool lowerCase) { - CustomAttribute? attr = type.GetAttribute(WindowsFoundationMetadata, "GuidAttribute"); + Span bytes = stackalloc byte[16]; + _ = guid.TryWriteBytes(bytes); - if (attr is null || attr.Signature is null) - { - return null; - } + uint data1 = bytes[0] | ((uint)bytes[1] << 8) | ((uint)bytes[2] << 16) | ((uint)bytes[3] << 24); + ushort data2 = (ushort)(bytes[4] | (bytes[5] << 8)); + ushort data3 = (ushort)(bytes[6] | (bytes[7] << 8)); - IList args = attr.Signature.FixedArguments; + string fmt = lowerCase ? "x" : "X"; - if (args.Count < 11) - { - return null; - } + // Format: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x + writer.Write($"{data1.ToString(fmt + "8", CultureInfo.InvariantCulture)}-{data2.ToString(fmt + "4", CultureInfo.InvariantCulture)}-{data3.ToString(fmt + "4", CultureInfo.InvariantCulture)}-"); - uint data1 = ToUInt32(args[0].Element); - ushort data2 = ToUInt16(args[1].Element); - ushort data3 = ToUInt16(args[2].Element); - byte[] data4 = new byte[8]; - for (int i = 0; i < 8; i++) + for (int i = 8; i < 10; i++) { - data4[i] = ToByte(args[3 + i].Element); + writer.Write(bytes[i].ToString(fmt + "2", CultureInfo.InvariantCulture)); } - return (data1, data2, data3, data4); - static uint ToUInt32(object? v) => v switch - { - uint u => u, - int i => (uint)i, - _ => 0u - }; - static ushort ToUInt16(object? v) => v switch - { - ushort u => u, - short s => (ushort)s, - int i => (ushort)i, - _ => 0 - }; - static byte ToByte(object? v) => v switch + writer.Write("-"); + + for (int i = 10; i < 16; i++) { - byte b => b, - sbyte sb => (byte)sb, - int i => (byte)i, - _ => 0 - }; + writer.Write(bytes[i].ToString(fmt + "2", CultureInfo.InvariantCulture)); + } } /// - /// Writes the GUID for in canonical hyphenated string form. + /// Writes the GUID for in canonical hyphenated string form. Reads + /// the [Guid] attribute on the type via + /// 's GetWindowsRuntimeGuid and forwards to + /// . /// + /// The writer to emit to. + /// The type whose [Guid] attribute is read. + /// When , hex digits are lower-case; otherwise upper-case. + /// Thrown when the type has no [Guid] attribute. public static void WriteGuid(IndentedTextWriter writer, TypeDefinition type, bool lowerCase) { - (uint data1, ushort data2, ushort data3, byte[] data4) = GetGuidFields(type) ?? throw WellKnownProjectionWriterExceptions.MissingGuidAttribute($"{type.Namespace}.{type.Name}"); - string fmt = lowerCase ? "x" : "X"; - // Format: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x - writer.Write($"{data1.ToString(fmt + "8", CultureInfo.InvariantCulture)}-{data2.ToString(fmt + "4", CultureInfo.InvariantCulture)}-{data3.ToString(fmt + "4", CultureInfo.InvariantCulture)}-"); - - for (int i = 0; i < 2; i++) + if (!type.TryGetWindowsRuntimeGuid(out Guid guid)) { - writer.Write(data4[i].ToString(fmt + "2", CultureInfo.InvariantCulture)); + throw WellKnownProjectionWriterExceptions.MissingGuidAttribute($"{type.Namespace}.{type.Name}"); } - writer.Write("-"); + WriteGuid(writer, guid, lowerCase); + } - for (int i = 2; i < 8; i++) - { - writer.Write(data4[i].ToString(fmt + "2", CultureInfo.InvariantCulture)); - } + /// + /// The formatted GUID as a string. + public static string FormatGuid(TypeDefinition type, bool lowerCase) + { + using IndentedTextWriterOwner owner = IndentedTextWriterPool.GetOrCreate(); + WriteGuid(owner.Writer, type, lowerCase); + return owner.Writer.ToString(); } /// - /// Writes the GUID bytes for as a hex byte list. + /// Writes the 16 bytes of as a hex byte list (e.g. + /// 0x00, 0x00, ..., 0x00), formatted for embedding inside a C# collection + /// expression that initializes a ReadOnlySpan<byte>. /// - public static void WriteGuidBytes(IndentedTextWriter writer, TypeDefinition type) + /// The writer to emit to. + /// The GUID whose bytes are emitted. + public static void WriteGuidBytes(IndentedTextWriter writer, Guid guid) { - (uint data1, ushort data2, ushort data3, byte[] data4) = GetGuidFields(type) ?? throw WellKnownProjectionWriterExceptions.MissingGuidAttribute($"{type.Namespace}.{type.Name}"); - WriteByte(writer, (data1 >> 0) & 0xFF, true); - WriteByte(writer, (data1 >> 8) & 0xFF, false); - WriteByte(writer, (data1 >> 16) & 0xFF, false); - WriteByte(writer, (data1 >> 24) & 0xFF, false); - WriteByte(writer, (uint)((data2 >> 0) & 0xFF), false); - WriteByte(writer, (uint)((data2 >> 8) & 0xFF), false); - WriteByte(writer, (uint)((data3 >> 0) & 0xFF), false); - WriteByte(writer, (uint)((data3 >> 8) & 0xFF), false); - - for (int i = 0; i < 8; i++) + Span bytes = stackalloc byte[16]; + _ = guid.TryWriteBytes(bytes); + + for (int i = 0; i < 16; i++) { - WriteByte(writer, data4[i], false); + WriteByte(writer, bytes[i], first: i == 0); } } - private static void WriteByte(IndentedTextWriter writer, uint b, bool first) + + /// + /// A callback that writes the bytes when invoked. + public static WriteGuidBytesCallback WriteGuidBytes(Guid guid) { - if (!first) - { - writer.Write(", "); - } + return new(guid); + } + + /// + /// Writes a single byte as 0xXX (or , 0xXX when not the first byte in a list). + /// + /// The writer to emit to. + /// The byte value. + /// When , omits the leading ", " separator. + private static void WriteByte(IndentedTextWriter writer, byte b, bool first) + { + writer.WriteIf(!first, ", "); - writer.Write($"0x{(b & 0xFF).ToString("X", CultureInfo.InvariantCulture)}"); + writer.Write($"0x{b.ToString("X", CultureInfo.InvariantCulture)}"); } /// /// Writes the property name IID_X for the IID property of . /// + /// The writer to emit to. + /// The active emit context. + /// The type whose IID property name is emitted. public static void WriteIidGuidPropertyName(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string name = EscapeTypeNameForIdentifier(TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.ABI, true), true, true); + string name = EscapeTypeNameForIdentifier(TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.ABI, true).Format(), true, true); writer.Write($"IID_{name}"); } + /// + /// A callback that writes the property name when invoked. + public static WriteIidGuidPropertyNameCallback WriteIidGuidPropertyName(ProjectionEmitContext context, TypeDefinition type) + { + return new(context, type); + } + /// - /// Writes the property name IID_XReference for the reference IID property. + /// Writes the property name IID_XReference for the IReference<T> IID + /// property of . /// + /// The writer to emit to. + /// The active emit context. + /// The type whose reference IID property name is emitted. public static void WriteIidReferenceGuidPropertyName(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - string name = EscapeTypeNameForIdentifier(TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.ABI, true), true, true); + string name = EscapeTypeNameForIdentifier(TypedefNameWriter.WriteTypedefNameWithTypeParams(context, type, TypedefNameType.ABI, true).Format(), true, true); writer.Write($"IID_{name}Reference"); } + /// + /// A callback that writes the property name when invoked. + public static WriteIidReferenceGuidPropertyNameCallback WriteIidReferenceGuidPropertyName(ProjectionEmitContext context, TypeDefinition type) + { + return new(context, type); + } + /// - /// Writes a static IID property whose body is built from the [Guid] attribute bytes. + /// Writes a static IID property whose body is built from the [Guid] attribute bytes + /// of . The property is named IID_X (see + /// ) and returns + /// a ref readonly Guid backed by a stack-allocated ReadOnlySpan<byte>. /// + /// The writer to emit to. + /// The active emit context. + /// The type whose [Guid] attribute is read. + /// Thrown when the type has no [Guid] attribute. public static void WriteIidGuidPropertyFromType(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - writer.Write("public static ref readonly Guid "); - WriteIidGuidPropertyName(writer, context, type); - writer.WriteLine(); - writer.Write(""" + if (!type.TryGetWindowsRuntimeGuid(out Guid guid)) + { + throw WellKnownProjectionWriterExceptions.MissingGuidAttribute($"{type.Namespace}.{type.Name}"); + } + + WriteIidGuidPropertyNameCallback name = WriteIidGuidPropertyName(context, type); + WriteGuidBytesCallback bytes = WriteGuidBytes(guid); + + writer.WriteLine(isMultiline: true, $$""" + public static ref readonly Guid {{name}} { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - ReadOnlySpan data = - [ - - """, isMultiline: true); - WriteGuidBytes(writer, type); - writer.WriteLine(); - writer.WriteLine(""" - ]; + ReadOnlySpan data = [{{bytes}}]; + return ref Unsafe.As(ref MemoryMarshal.GetReference(data)); } } - """, isMultiline: true); + """); writer.WriteLine(); } /// - /// Writes the WinRT GUID parametric signature string for a type semantics. - /// - public static void WriteGuidSignature(IndentedTextWriter writer, ProjectionEmitContext context, TypeSemantics semantics) - { - switch (semantics) - { - case TypeSemantics.GuidType: - writer.Write("g16"); - break; - case TypeSemantics.ObjectType: - writer.Write("cinterface(IInspectable)"); - break; - case TypeSemantics.Fundamental f: - writer.Write(GetFundamentalTypeGuidSignature(f.Type)); - break; - case TypeSemantics.Definition d: - WriteGuidSignatureForType(writer, context, d.Type); - break; - case TypeSemantics.Reference r: - { - // Resolve the reference to a TypeDefinition (cross-module struct field, etc.). - (string ns, string name) = r.Type.Names(); - TypeDefinition? resolved = null; - - if (context.Cache is not null) - { - resolved = r.Type.TryResolve(context.Cache.RuntimeContext) - ?? context.Cache.Find(string.IsNullOrEmpty(ns) ? name : (ns + "." + name)); - } - - if (resolved is not null) - { - WriteGuidSignatureForType(writer, context, resolved); - } - } - break; - case TypeSemantics.GenericInstance gi: - writer.Write("pinterface({"); - WriteGuid(writer, gi.GenericType, true); - writer.Write("};"); - for (int i = 0; i < gi.GenericArgs.Count; i++) - { - if (i > 0) - { - writer.Write(";"); - } - - WriteGuidSignature(writer, context, gi.GenericArgs[i]); - } - writer.Write(")"); - break; - case TypeSemantics.GenericInstanceRef gir: - { - // Cross-module generic instance (e.g. Windows.Foundation.IReference - // appearing as a struct field). Resolve the generic type to a TypeDefinition - // so we can extract its [Guid]; recurse on each type argument. - (string ns, string name) = gir.GenericType.Names(); - TypeDefinition? resolved = null; - - if (context.Cache is not null) - { - resolved = gir.GenericType.TryResolve(context.Cache.RuntimeContext) - ?? context.Cache.Find(string.IsNullOrEmpty(ns) ? name : (ns + "." + name)); - } - - if (resolved is not null) - { - writer.Write("pinterface({"); - WriteGuid(writer, resolved, true); - writer.Write("};"); - for (int i = 0; i < gir.GenericArgs.Count; i++) - { - if (i > 0) - { - writer.Write(";"); - } - - WriteGuidSignature(writer, context, gir.GenericArgs[i]); - } - writer.Write(")"); - } - } - break; - } - } - - /// - /// Convenience overload of - /// that leases an from , - /// emits the GUID signature into it, and returns the resulting string. + /// Writes a static IID property whose body is built from the parametric GUID signature + /// (computed via + /// for the type, then wrapped in the Windows.Foundation.IReference<T> + /// pinterface to produce the IID_XReference hashed GUID). /// + /// The writer to emit to. /// The active emit context. - /// The type semantics whose GUID signature is emitted. - /// The emitted GUID signature. - public static string WriteGuidSignature(ProjectionEmitContext context, TypeSemantics semantics) + /// The type whose IReference<T> IID is emitted. + public static void WriteIidGuidPropertyFromSignature(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) { - using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); - IndentedTextWriter writer = writerOwner.Writer; - WriteGuidSignature(writer, context, semantics); - return writer.ToString(); - } + string signature = SignatureGenerator.GetSignature(context, new TypeSemantics.Definition(type)); + Guid guid; - private static void WriteGuidSignatureForType(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) - { - TypeCategory cat = TypeCategorization.GetCategory(type); - switch (cat) + // We can use an interpolated handler here to avoid allocating the 'string' for the full signature. + // The additional scope here is to ensure that the rest of the method never reuses this handler. { - case TypeCategory.Enum: - writer.Write("enum("); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.NonProjected, true); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.Write(";"); - writer.Write(TypeCategorization.IsFlagsEnum(type) ? "u4" : "i4"); - writer.Write(")"); - break; - case TypeCategory.Struct: - writer.Write("struct("); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.NonProjected, true); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.Write(";"); - bool first = true; - foreach (FieldDefinition field in type.Fields) - { - if (field.IsStatic) - { - continue; - } - - if (field.Signature is null) - { - continue; - } - - if (!first) - { - writer.Write(";"); - } - - first = false; - WriteGuidSignature(writer, context, TypeSemanticsFactory.Get(field.Signature.FieldType)); - } - writer.Write(")"); - break; - case TypeCategory.Delegate: - writer.Write("delegate({"); - WriteGuid(writer, type, true); - writer.Write("})"); - break; - case TypeCategory.Interface: - writer.Write("{"); - WriteGuid(writer, type, true); - writer.Write("}"); - break; - case TypeCategory.Class: - ITypeDefOrRef? defaultIface = type.GetDefaultInterface(); - - if (defaultIface is TypeDefinition di) - { - writer.Write("rc("); - TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.NonProjected, true); - TypedefNameWriter.WriteTypeParams(writer, type); - writer.Write(";"); - WriteGuidSignature(writer, context, new TypeSemantics.Definition(di)); - writer.Write(")"); - } - else - { - writer.Write("{"); - WriteGuid(writer, type, true); - writer.Write("}"); - } + DefaultInterpolatedStringHandler handler = $$"""pinterface({61c17706-2d65-11e0-9ae8-d48564015472};{{signature}})"""; + + guid = GuidGenerator.Generate(handler.Text); - break; + // Don't forget to clear the handler after we used it, so its rented buffer can be returned + handler.Clear(); } - } - /// - /// Writes a static IID property whose body is built from the parametric GUID signature. - /// - public static void WriteIidGuidPropertyFromSignature(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) - { - string guidSig = WriteGuidSignature(context, new TypeSemantics.Definition(type)); - string ireferenceGuidSig = "pinterface({61c17706-2d65-11e0-9ae8-d48564015472};" + guidSig + ")"; - Guid guidValue = GuidGenerator.Generate(ireferenceGuidSig); - byte[] bytes = guidValue.ToByteArray(); + WriteIidReferenceGuidPropertyNameCallback name = WriteIidReferenceGuidPropertyName(context, type); + WriteGuidBytesCallback bytes = WriteGuidBytes(guid); - writer.Write("public static ref readonly Guid "); - WriteIidReferenceGuidPropertyName(writer, context, type); - writer.WriteLine(); - writer.Write(""" + writer.WriteLine(isMultiline: true, $$""" + public static ref readonly Guid {{name}} { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - ReadOnlySpan data = - [ - - """, isMultiline: true); - for (int i = 0; i < 16; i++) - { - if (i > 0) - { - writer.Write(", "); - } + ReadOnlySpan data = [{{bytes}}]; - writer.Write($"0x{bytes[i].ToString("X", CultureInfo.InvariantCulture)}"); - } - writer.WriteLine(); - writer.WriteLine(""" - ]; return ref Unsafe.As(ref MemoryMarshal.GetReference(data)); } } - """, isMultiline: true); + """); writer.WriteLine(); } @@ -464,6 +298,7 @@ public static void WriteIidGuidPropertyForClassInterfaces(IndentedTextWriter wri } (string ns, string nm) = ifaceType.Names(); + // Skip mapped types if (MappedTypes.Get(ns, nm) is not null) { @@ -483,16 +318,17 @@ public static void WriteIidGuidPropertyForClassInterfaces(IndentedTextWriter wri } // Only emit if the interface is not in the projection (otherwise it'll be emitted naturally) - if (!context.Settings.Filter.Includes(ifaceType)) + if (!context.Settings.Filter.Includes(ifaceType.FullName)) { WriteIidGuidPropertyFromType(writer, context, ifaceType); + _ = interfacesEmitted.Add(ifaceType); } } } private static TypeDefinition? ResolveCrossModuleType(MetadataCache cache, string ns, string name) { - return cache.Find(string.IsNullOrEmpty(ns) ? name : (ns + "." + name)); + return cache.Find(ns, name); } /// @@ -501,7 +337,7 @@ public static void WriteIidGuidPropertyForClassInterfaces(IndentedTextWriter wri public static void WriteInterfaceIidsBegin(IndentedTextWriter writer) { writer.WriteLine(); - writer.WriteLine($$""" + writer.WriteLine(isMultiline: true, $$""" //------------------------------------------------------------------------------ // // This file was generated by cswinrt.exe version {{MetadataAttributeFactory.GetVersionString()}} @@ -519,7 +355,7 @@ namespace ABI; internal static class InterfaceIIDs { - """, isMultiline: true); + """); } /// diff --git a/src/WinRT.Projection.Writer/Helpers/InteropTypeNameWriter.cs b/src/WinRT.Projection.Writer/Helpers/InteropTypeNameWriter.cs index 8e9d4dceb4..efa8058da4 100644 --- a/src/WinRT.Projection.Writer/Helpers/InteropTypeNameWriter.cs +++ b/src/WinRT.Projection.Writer/Helpers/InteropTypeNameWriter.cs @@ -33,6 +33,19 @@ public static string EncodeInteropTypeName(TypeSignature sig, TypedefNameType na return sb.ToString(); } + /// + /// Returns the assembly-qualified form of , + /// suffixed with ", WinRT.Interop". This is the canonical value passed to + /// [UnsafeAccessorType] arguments. + /// + /// The type signature to encode. + /// Indicates whether to use the projected (no ABI prefix) form or + /// the ABI-prefixed marshaller form. Defaults to . + public static string GetInteropAssemblyQualifiedName(TypeSignature sig, TypedefNameType nameType = TypedefNameType.ABI) + { + return EncodeInteropTypeName(sig, nameType) + ", WinRT.Interop"; + } + /// /// Encodes an ABI interop type name for into using the format expected by WindowsRuntime.InteropServices attributes. /// @@ -142,6 +155,7 @@ private static void EncodeForTypeDef(StringBuilder sb, ITypeDefOrRef type, Typed _ = sb.Append("WindowsRuntime.InteropServices.<#CsWinRT>EventHandlerEventSource'"); _ = sb.Append(arity.ToString(CultureInfo.InvariantCulture)); + // Append the generic args (if any). if (generic_args is { Count: > 0 }) { diff --git a/src/WinRT.Projection.Writer/Helpers/MappedTypes.cs b/src/WinRT.Projection.Writer/Helpers/MappedTypes.cs index a473fff02a..fc15169eba 100644 --- a/src/WinRT.Projection.Writer/Helpers/MappedTypes.cs +++ b/src/WinRT.Projection.Writer/Helpers/MappedTypes.cs @@ -31,7 +31,237 @@ internal readonly record struct MappedType( /// internal static class MappedTypes { - private static readonly FrozenDictionary> TypeMappings = Build(); + /// + /// The mapping of all custom-mapped types. + /// + private static readonly FrozenDictionary> TypeMappings = new Dictionary> + { + ["Microsoft.UI.Xaml"] = new Dictionary + { + ["CornerRadius"] = new("CornerRadius", "Microsoft.UI.Xaml", "CornerRadius", false, false, true), + ["CornerRadiusHelper"] = new("CornerRadiusHelper", "", ""), + ["Duration"] = new("Duration", "Microsoft.UI.Xaml", "Duration", false, false, true), + ["DurationHelper"] = new("DurationHelper", "", ""), + ["GridLength"] = new("GridLength", "Microsoft.UI.Xaml", "GridLength", false, false, true), + ["GridLengthHelper"] = new("GridLengthHelper", "", ""), + ["ICornerRadiusHelper"] = new("ICornerRadiusHelper", "", ""), + ["ICornerRadiusHelperStatics"] = new("ICornerRadiusHelperStatics", "", ""), + ["IDurationHelper"] = new("IDurationHelper", "", ""), + ["IDurationHelperStatics"] = new("IDurationHelperStatics", "", ""), + ["IGridLengthHelper"] = new("IGridLengthHelper", "", ""), + ["IGridLengthHelperStatics"] = new("IGridLengthHelperStatics", "", ""), + ["IThicknessHelper"] = new("IThicknessHelper", "", ""), + ["IThicknessHelperStatics"] = new("IThicknessHelperStatics", "", ""), + ["IXamlServiceProvider"] = new("IXamlServiceProvider", "System", "IServiceProvider"), + ["ThicknessHelper"] = new("ThicknessHelper", "", ""), + }.ToFrozenDictionary(), + ["Microsoft.UI.Xaml.Controls.Primitives"] = new Dictionary + { + ["GeneratorPositionHelper"] = new("GeneratorPositionHelper", "", ""), + ["IGeneratorPositionHelper"] = new("IGeneratorPositionHelper", "", ""), + ["IGeneratorPositionHelperStatics"] = new("IGeneratorPositionHelperStatics", "", ""), + }.ToFrozenDictionary(), + ["Microsoft.UI.Xaml.Data"] = new Dictionary + { + ["DataErrorsChangedEventArgs"] = new("DataErrorsChangedEventArgs", "System.ComponentModel", "DataErrorsChangedEventArgs"), + ["INotifyDataErrorInfo"] = new("INotifyDataErrorInfo", "System.ComponentModel", "INotifyDataErrorInfo", true, true), + ["INotifyPropertyChanged"] = new("INotifyPropertyChanged", "System.ComponentModel", "INotifyPropertyChanged"), + ["PropertyChangedEventArgs"] = new("PropertyChangedEventArgs", "System.ComponentModel", "PropertyChangedEventArgs"), + ["PropertyChangedEventHandler"] = new("PropertyChangedEventHandler", "System.ComponentModel", "PropertyChangedEventHandler"), + }.ToFrozenDictionary(), + ["Microsoft.UI.Xaml.Input"] = new Dictionary + { + ["ICommand"] = new("ICommand", "System.Windows.Input", "ICommand", true), + }.ToFrozenDictionary(), + ["Microsoft.UI.Xaml.Interop"] = new Dictionary + { + ["IBindableIterable"] = new("IBindableIterable", "System.Collections", "IEnumerable", true, true), + ["IBindableIterator"] = new("IBindableIterator", "System.Collections", "IEnumerator", true, true), + ["IBindableVector"] = new("IBindableVector", "System.Collections", "IList", true, true), + ["INotifyCollectionChanged"] = new("INotifyCollectionChanged", "System.Collections.Specialized", "INotifyCollectionChanged", true), + ["NotifyCollectionChangedAction"] = new("NotifyCollectionChangedAction", "System.Collections.Specialized", "NotifyCollectionChangedAction"), + ["NotifyCollectionChangedEventArgs"] = new("NotifyCollectionChangedEventArgs", "System.Collections.Specialized", "NotifyCollectionChangedEventArgs", true), + ["NotifyCollectionChangedEventHandler"] = new("NotifyCollectionChangedEventHandler", "System.Collections.Specialized", "NotifyCollectionChangedEventHandler", true), + }.ToFrozenDictionary(), + ["Microsoft.UI.Xaml.Media"] = new Dictionary + { + ["IMatrixHelper"] = new("IMatrixHelper", "", ""), + ["IMatrixHelperStatics"] = new("IMatrixHelperStatics", "", ""), + ["MatrixHelper"] = new("MatrixHelper", "", ""), + }.ToFrozenDictionary(), + ["Microsoft.UI.Xaml.Media.Animation"] = new Dictionary + { + ["IKeyTimeHelper"] = new("IKeyTimeHelper", "", ""), + ["IKeyTimeHelperStatics"] = new("IKeyTimeHelperStatics", "", ""), + ["IRepeatBehaviorHelper"] = new("IRepeatBehaviorHelper", "", ""), + ["IRepeatBehaviorHelperStatics"] = new("IRepeatBehaviorHelperStatics", "", ""), + ["KeyTime"] = new("KeyTime", "Microsoft.UI.Xaml.Media.Animation", "KeyTime", false, false, true), + ["KeyTimeHelper"] = new("KeyTimeHelper", "", ""), + ["RepeatBehavior"] = new("RepeatBehavior", "Microsoft.UI.Xaml.Media.Animation", "RepeatBehavior", false, false, true), + ["RepeatBehaviorHelper"] = new("RepeatBehaviorHelper", "", ""), + }.ToFrozenDictionary(), + ["Microsoft.UI.Xaml.Media.Media3D"] = new Dictionary + { + ["IMatrix3DHelper"] = new("IMatrix3DHelper", "", ""), + ["IMatrix3DHelperStatics"] = new("IMatrix3DHelperStatics", "", ""), + ["Matrix3D"] = new("Matrix3D", "Microsoft.UI.Xaml.Media.Media3D", "Matrix3D", false, false, true), + ["Matrix3DHelper"] = new("Matrix3DHelper", "", ""), + }.ToFrozenDictionary(), + [WindowsFoundation] = new Dictionary + { + ["AsyncActionCompletedHandler"] = new("AsyncActionCompletedHandler", WindowsFoundation, "AsyncActionCompletedHandler"), + ["AsyncActionProgressHandler`1"] = new("AsyncActionProgressHandler`1", WindowsFoundation, "AsyncActionProgressHandler`1"), + ["AsyncActionWithProgressCompletedHandler`1"] = new("AsyncActionWithProgressCompletedHandler`1", WindowsFoundation, "AsyncActionWithProgressCompletedHandler`1"), + ["AsyncOperationCompletedHandler`1"] = new("AsyncOperationCompletedHandler`1", WindowsFoundation, "AsyncOperationCompletedHandler`1"), + ["AsyncOperationProgressHandler`2"] = new("AsyncOperationProgressHandler`2", WindowsFoundation, "AsyncOperationProgressHandler`2"), + ["AsyncOperationWithProgressCompletedHandler`2"] = new("AsyncOperationWithProgressCompletedHandler`2", WindowsFoundation, "AsyncOperationWithProgressCompletedHandler`2"), + ["AsyncStatus"] = new("AsyncStatus", WindowsFoundation, "AsyncStatus"), + ["DateTime"] = new("DateTime", "System", "DateTimeOffset", true), + ["EventHandler`1"] = new("EventHandler`1", "System", "EventHandler`1", false), + ["EventRegistrationToken"] = new("EventRegistrationToken", "WindowsRuntime.InteropServices", "EventRegistrationToken", false), + ["FoundationContract"] = new("FoundationContract", WindowsFoundation, "FoundationContract"), + [HResult] = new(HResult, "System", "Exception", true), + ["IAsyncAction"] = new("IAsyncAction", WindowsFoundation, "IAsyncAction"), + ["IAsyncActionWithProgress`1"] = new("IAsyncActionWithProgress`1", WindowsFoundation, "IAsyncActionWithProgress`1"), + ["IAsyncInfo"] = new("IAsyncInfo", WindowsFoundation, "IAsyncInfo"), + ["IAsyncOperationWithProgress`2"] = new("IAsyncOperationWithProgress`2", WindowsFoundation, "IAsyncOperationWithProgress`2"), + ["IAsyncOperation`1"] = new("IAsyncOperation`1", WindowsFoundation, "IAsyncOperation`1"), + ["IClosable"] = new("IClosable", "System", "IDisposable", true, true), + ["IMemoryBufferReference"] = new("IMemoryBufferReference", WindowsFoundation, "IMemoryBufferReference"), + ["IPropertyValue"] = new("IPropertyValue", WindowsFoundation, "IPropertyValue", true), + ["IReferenceArray`1"] = new("IReferenceArray`1", WindowsFoundation, "IReferenceArray", true), + [IReferenceGeneric] = new(IReferenceGeneric, "System", NullableGeneric, true), + ["IStringable"] = new("IStringable", WindowsFoundation, "IStringable"), + ["Point"] = new("Point", WindowsFoundation, "Point"), + ["PropertyType"] = new("PropertyType", WindowsFoundation, "PropertyType"), + ["Rect"] = new("Rect", WindowsFoundation, "Rect"), + ["Size"] = new("Size", WindowsFoundation, "Size"), + ["TimeSpan"] = new("TimeSpan", "System", "TimeSpan", true), + ["TypedEventHandler`2"] = new("TypedEventHandler`2", "System", "EventHandler`2", false), + ["UniversalApiContract"] = new("UniversalApiContract", WindowsFoundation, "UniversalApiContract"), + ["Uri"] = new("Uri", "System", "Uri", true), + }.ToFrozenDictionary(), + [WindowsFoundationCollections] = new Dictionary + { + ["CollectionChange"] = new("CollectionChange", WindowsFoundationCollections, "CollectionChange"), + ["IIterable`1"] = new("IIterable`1", "System.Collections.Generic", "IEnumerable`1", true, true), + ["IIterator`1"] = new("IIterator`1", "System.Collections.Generic", "IEnumerator`1", true, true), + ["IKeyValuePair`2"] = new("IKeyValuePair`2", "System.Collections.Generic", "KeyValuePair`2", true), + ["IMapChangedEventArgs`1"] = new("IMapChangedEventArgs`1", WindowsFoundationCollections, "IMapChangedEventArgs`1"), + ["IMapView`2"] = new("IMapView`2", "System.Collections.Generic", "IReadOnlyDictionary`2", true, true), + ["IMap`2"] = new("IMap`2", "System.Collections.Generic", "IDictionary`2", true, true), + ["IObservableMap`2"] = new("IObservableMap`2", WindowsFoundationCollections, "IObservableMap`2"), + ["IObservableVector`1"] = new("IObservableVector`1", WindowsFoundationCollections, "IObservableVector`1"), + ["IVectorChangedEventArgs"] = new("IVectorChangedEventArgs", WindowsFoundationCollections, "IVectorChangedEventArgs"), + ["IVectorView`1"] = new("IVectorView`1", "System.Collections.Generic", "IReadOnlyList`1", true, true), + ["IVector`1"] = new("IVector`1", "System.Collections.Generic", "IList`1", true, true), + ["MapChangedEventHandler`2"] = new("MapChangedEventHandler`2", WindowsFoundationCollections, "MapChangedEventHandler`2"), + ["VectorChangedEventHandler`1"] = new("VectorChangedEventHandler`1", WindowsFoundationCollections, "VectorChangedEventHandler`1"), + }.ToFrozenDictionary(), + [WindowsFoundationMetadata] = new Dictionary + { + ["ApiContractAttribute"] = new("ApiContractAttribute", WindowsFoundationMetadata, "ApiContractAttribute"), + ["AttributeTargets"] = new("AttributeTargets", "System", "AttributeTargets"), + ["AttributeUsageAttribute"] = new("AttributeUsageAttribute", "System", "AttributeUsageAttribute"), + [ContractVersionAttribute] = new(ContractVersionAttribute, WindowsFoundationMetadata, ContractVersionAttribute), + }.ToFrozenDictionary(), + ["Windows.Foundation.Numerics"] = new Dictionary + { + ["Matrix3x2"] = new("Matrix3x2", "System.Numerics", "Matrix3x2"), + ["Matrix4x4"] = new("Matrix4x4", "System.Numerics", "Matrix4x4"), + ["Plane"] = new("Plane", "System.Numerics", "Plane"), + ["Quaternion"] = new("Quaternion", "System.Numerics", "Quaternion"), + ["Vector2"] = new("Vector2", "System.Numerics", "Vector2"), + ["Vector3"] = new("Vector3", "System.Numerics", "Vector3"), + ["Vector4"] = new("Vector4", "System.Numerics", "Vector4"), + }.ToFrozenDictionary(), + ["Windows.Storage.Streams"] = new Dictionary + { + ["IBuffer"] = new("IBuffer", "Windows.Storage.Streams", "IBuffer"), + ["IInputStream"] = new("IInputStream", "Windows.Storage.Streams", "IInputStream"), + ["IOutputStream"] = new("IOutputStream", "Windows.Storage.Streams", "IOutputStream"), + ["IRandomAccessStream"] = new("IRandomAccessStream", "Windows.Storage.Streams", "IRandomAccessStream"), + ["InputStreamOptions"] = new("InputStreamOptions", "Windows.Storage.Streams", "InputStreamOptions"), + }.ToFrozenDictionary(), + ["Windows.UI.Xaml"] = new Dictionary + { + ["CornerRadius"] = new("CornerRadius", "Windows.UI.Xaml", "CornerRadius", false, false, true), + ["CornerRadiusHelper"] = new("CornerRadiusHelper", "", ""), + ["Duration"] = new("Duration", "Windows.UI.Xaml", "Duration", false, false, true), + ["DurationHelper"] = new("DurationHelper", "", ""), + ["GridLength"] = new("GridLength", "Windows.UI.Xaml", "GridLength", false, false, true), + ["GridLengthHelper"] = new("GridLengthHelper", "", ""), + ["ICornerRadiusHelper"] = new("ICornerRadiusHelper", "", ""), + ["ICornerRadiusHelperStatics"] = new("ICornerRadiusHelperStatics", "", ""), + ["IDurationHelper"] = new("IDurationHelper", "", ""), + ["IDurationHelperStatics"] = new("IDurationHelperStatics", "", ""), + ["IGridLengthHelper"] = new("IGridLengthHelper", "", ""), + ["IGridLengthHelperStatics"] = new("IGridLengthHelperStatics", "", ""), + ["IThicknessHelper"] = new("IThicknessHelper", "", ""), + ["IThicknessHelperStatics"] = new("IThicknessHelperStatics", "", ""), + ["IXamlServiceProvider"] = new("IXamlServiceProvider", "System", "IServiceProvider"), + ["ThicknessHelper"] = new("ThicknessHelper", "", ""), + }.ToFrozenDictionary(), + ["Windows.UI.Xaml.Controls.Primitives"] = new Dictionary + { + ["GeneratorPositionHelper"] = new("GeneratorPositionHelper", "", ""), + ["IGeneratorPositionHelper"] = new("IGeneratorPositionHelper", "", ""), + ["IGeneratorPositionHelperStatics"] = new("IGeneratorPositionHelperStatics", "", ""), + }.ToFrozenDictionary(), + ["Windows.UI.Xaml.Data"] = new Dictionary + { + ["DataErrorsChangedEventArgs"] = new("DataErrorsChangedEventArgs", "System.ComponentModel", "DataErrorsChangedEventArgs"), + ["INotifyDataErrorInfo"] = new("INotifyDataErrorInfo", "System.ComponentModel", "INotifyDataErrorInfo", true, true), + ["INotifyPropertyChanged"] = new("INotifyPropertyChanged", "System.ComponentModel", "INotifyPropertyChanged"), + ["PropertyChangedEventArgs"] = new("PropertyChangedEventArgs", "System.ComponentModel", "PropertyChangedEventArgs"), + ["PropertyChangedEventHandler"] = new("PropertyChangedEventHandler", "System.ComponentModel", "PropertyChangedEventHandler"), + }.ToFrozenDictionary(), + ["Windows.UI.Xaml.Input"] = new Dictionary + { + ["ICommand"] = new("ICommand", "System.Windows.Input", "ICommand", true), + }.ToFrozenDictionary(), + [WindowsUIXamlInterop] = new Dictionary + { + ["IBindableIterable"] = new("IBindableIterable", "System.Collections", "IEnumerable", true, true), + ["IBindableIterator"] = new("IBindableIterator", "System.Collections", "IEnumerator", true, true), + ["IBindableVector"] = new("IBindableVector", "System.Collections", "IList", true, true), + ["INotifyCollectionChanged"] = new("INotifyCollectionChanged", "System.Collections.Specialized", "INotifyCollectionChanged", true), + ["NotifyCollectionChangedAction"] = new("NotifyCollectionChangedAction", "System.Collections.Specialized", "NotifyCollectionChangedAction"), + ["NotifyCollectionChangedEventArgs"] = new("NotifyCollectionChangedEventArgs", "System.Collections.Specialized", "NotifyCollectionChangedEventArgs", true), + ["NotifyCollectionChangedEventHandler"] = new("NotifyCollectionChangedEventHandler", "System.Collections.Specialized", "NotifyCollectionChangedEventHandler", true), + ["TypeKind"] = new("TypeKind", WindowsUIXamlInterop, "TypeKind", true), + [TypeName] = new(TypeName, "System", "Type", true), + }.ToFrozenDictionary(), + ["Windows.UI.Xaml.Media"] = new Dictionary + { + ["IMatrixHelper"] = new("IMatrixHelper", "", ""), + ["IMatrixHelperStatics"] = new("IMatrixHelperStatics", "", ""), + ["MatrixHelper"] = new("MatrixHelper", "", ""), + }.ToFrozenDictionary(), + ["Windows.UI.Xaml.Media.Animation"] = new Dictionary + { + ["IKeyTimeHelper"] = new("IKeyTimeHelper", "", ""), + ["IKeyTimeHelperStatics"] = new("IKeyTimeHelperStatics", "", ""), + ["IRepeatBehaviorHelper"] = new("IRepeatBehaviorHelper", "", ""), + ["IRepeatBehaviorHelperStatics"] = new("IRepeatBehaviorHelperStatics", "", ""), + ["KeyTime"] = new("KeyTime", "Windows.UI.Xaml.Media.Animation", "KeyTime", false, false, true), + ["KeyTimeHelper"] = new("KeyTimeHelper", "", ""), + ["RepeatBehavior"] = new("RepeatBehavior", "Windows.UI.Xaml.Media.Animation", "RepeatBehavior", false, false, true), + ["RepeatBehaviorHelper"] = new("RepeatBehaviorHelper", "", ""), + }.ToFrozenDictionary(), + ["Windows.UI.Xaml.Media.Media3D"] = new Dictionary + { + ["IMatrix3DHelper"] = new("IMatrix3DHelper", "", ""), + ["IMatrix3DHelperStatics"] = new("IMatrix3DHelperStatics", "", ""), + ["Matrix3D"] = new("Matrix3D", "Windows.UI.Xaml.Media.Media3D", "Matrix3D", false, false, true), + ["Matrix3DHelper"] = new("Matrix3DHelper", "", ""), + }.ToFrozenDictionary(), + [WindowsRuntimeInternal] = new Dictionary + { + ["HWND"] = new("HWND", "System", "IntPtr"), + ["ProjectionInternalAttribute"] = new("ProjectionInternalAttribute", "", ""), + }.ToFrozenDictionary(), + }.ToFrozenDictionary(); /// /// Returns the entry for the type identified by @@ -53,236 +283,32 @@ internal static class MappedTypes } /// - /// Returns whether contains at least one mapped type. + /// Returns whether a mapping exists for the type identified by + /// (, ). /// /// The Windows Runtime namespace. - /// if there is at least one mapping in this namespace. - public static bool HasNamespace(string typeNamespace) => TypeMappings.ContainsKey(typeNamespace); + /// The Windows Runtime type name. + /// if a mapping exists; otherwise . + public static bool IsMapped(string typeNamespace, string typeName) + => Get(typeNamespace, typeName) is not null; - private static FrozenDictionary> Build() + /// + /// Applies the type mapping in-place if one exists: when a mapping is found for the type + /// identified by (, ), replaces + /// both fields with the mapped namespace and name. Does nothing if no mapping exists. + /// + /// The Windows Runtime namespace; replaced with the mapped namespace on a hit. + /// The Windows Runtime type name; replaced with the mapped name on a hit. + /// if a mapping was applied; otherwise . + public static bool ApplyMapping(ref string typeNamespace, ref string typeName) { - Dictionary> result = []; - - // helper to add a type entry - void Add(string ns, MappedType mt) + if (Get(typeNamespace, typeName) is { } m) { - if (!result.TryGetValue(ns, out Dictionary? bag)) - { - bag = []; - result[ns] = bag; - } - - bag[mt.AbiName] = mt; + typeNamespace = m.MappedNamespace; + typeName = m.MappedName; + return true; } - // Microsoft.UI.Xaml - Add("Microsoft.UI.Xaml", new("CornerRadius", "Microsoft.UI.Xaml", "CornerRadius", false, false, true)); - Add("Microsoft.UI.Xaml", new("CornerRadiusHelper", "", "")); - Add("Microsoft.UI.Xaml", new("Duration", "Microsoft.UI.Xaml", "Duration", false, false, true)); - Add("Microsoft.UI.Xaml", new("DurationHelper", "", "")); - Add("Microsoft.UI.Xaml", new("GridLength", "Microsoft.UI.Xaml", "GridLength", false, false, true)); - Add("Microsoft.UI.Xaml", new("GridLengthHelper", "", "")); - Add("Microsoft.UI.Xaml", new("ICornerRadiusHelper", "", "")); - Add("Microsoft.UI.Xaml", new("ICornerRadiusHelperStatics", "", "")); - Add("Microsoft.UI.Xaml", new("IDurationHelper", "", "")); - Add("Microsoft.UI.Xaml", new("IDurationHelperStatics", "", "")); - Add("Microsoft.UI.Xaml", new("IGridLengthHelper", "", "")); - Add("Microsoft.UI.Xaml", new("IGridLengthHelperStatics", "", "")); - Add("Microsoft.UI.Xaml", new("IThicknessHelper", "", "")); - Add("Microsoft.UI.Xaml", new("IThicknessHelperStatics", "", "")); - Add("Microsoft.UI.Xaml", new("IXamlServiceProvider", "System", "IServiceProvider")); - Add("Microsoft.UI.Xaml", new("ThicknessHelper", "", "")); - - // Microsoft.UI.Xaml.Controls.Primitives - Add("Microsoft.UI.Xaml.Controls.Primitives", new("GeneratorPositionHelper", "", "")); - Add("Microsoft.UI.Xaml.Controls.Primitives", new("IGeneratorPositionHelper", "", "")); - Add("Microsoft.UI.Xaml.Controls.Primitives", new("IGeneratorPositionHelperStatics", "", "")); - - // Microsoft.UI.Xaml.Data - Add("Microsoft.UI.Xaml.Data", new("DataErrorsChangedEventArgs", "System.ComponentModel", "DataErrorsChangedEventArgs")); - Add("Microsoft.UI.Xaml.Data", new("INotifyDataErrorInfo", "System.ComponentModel", "INotifyDataErrorInfo", true, true)); - Add("Microsoft.UI.Xaml.Data", new("INotifyPropertyChanged", "System.ComponentModel", "INotifyPropertyChanged")); - Add("Microsoft.UI.Xaml.Data", new("PropertyChangedEventArgs", "System.ComponentModel", "PropertyChangedEventArgs")); - Add("Microsoft.UI.Xaml.Data", new("PropertyChangedEventHandler", "System.ComponentModel", "PropertyChangedEventHandler")); - - // Microsoft.UI.Xaml.Input - Add("Microsoft.UI.Xaml.Input", new("ICommand", "System.Windows.Input", "ICommand", true)); - - // Microsoft.UI.Xaml.Interop - Add("Microsoft.UI.Xaml.Interop", new("IBindableIterable", "System.Collections", "IEnumerable", true, true)); - Add("Microsoft.UI.Xaml.Interop", new("IBindableIterator", "System.Collections", "IEnumerator", true, true)); - Add("Microsoft.UI.Xaml.Interop", new("IBindableVector", "System.Collections", "IList", true, true)); - Add("Microsoft.UI.Xaml.Interop", new("INotifyCollectionChanged", "System.Collections.Specialized", "INotifyCollectionChanged", true)); - Add("Microsoft.UI.Xaml.Interop", new("NotifyCollectionChangedAction", "System.Collections.Specialized", "NotifyCollectionChangedAction")); - Add("Microsoft.UI.Xaml.Interop", new("NotifyCollectionChangedEventArgs", "System.Collections.Specialized", "NotifyCollectionChangedEventArgs", true)); - Add("Microsoft.UI.Xaml.Interop", new("NotifyCollectionChangedEventHandler", "System.Collections.Specialized", "NotifyCollectionChangedEventHandler", true)); - - // Microsoft.UI.Xaml.Media - Add("Microsoft.UI.Xaml.Media", new("IMatrixHelper", "", "")); - Add("Microsoft.UI.Xaml.Media", new("IMatrixHelperStatics", "", "")); - Add("Microsoft.UI.Xaml.Media", new("MatrixHelper", "", "")); - - // Microsoft.UI.Xaml.Media.Animation - Add("Microsoft.UI.Xaml.Media.Animation", new("IKeyTimeHelper", "", "")); - Add("Microsoft.UI.Xaml.Media.Animation", new("IKeyTimeHelperStatics", "", "")); - Add("Microsoft.UI.Xaml.Media.Animation", new("IRepeatBehaviorHelper", "", "")); - Add("Microsoft.UI.Xaml.Media.Animation", new("IRepeatBehaviorHelperStatics", "", "")); - Add("Microsoft.UI.Xaml.Media.Animation", new("KeyTime", "Microsoft.UI.Xaml.Media.Animation", "KeyTime", false, false, true)); - Add("Microsoft.UI.Xaml.Media.Animation", new("KeyTimeHelper", "", "")); - Add("Microsoft.UI.Xaml.Media.Animation", new("RepeatBehavior", "Microsoft.UI.Xaml.Media.Animation", "RepeatBehavior", false, false, true)); - Add("Microsoft.UI.Xaml.Media.Animation", new("RepeatBehaviorHelper", "", "")); - - // Microsoft.UI.Xaml.Media.Media3D - Add("Microsoft.UI.Xaml.Media.Media3D", new("IMatrix3DHelper", "", "")); - Add("Microsoft.UI.Xaml.Media.Media3D", new("IMatrix3DHelperStatics", "", "")); - Add("Microsoft.UI.Xaml.Media.Media3D", new("Matrix3D", "Microsoft.UI.Xaml.Media.Media3D", "Matrix3D", false, false, true)); - Add("Microsoft.UI.Xaml.Media.Media3D", new("Matrix3DHelper", "", "")); - - // Windows.Foundation - Add(WindowsFoundation, new("AsyncActionCompletedHandler", WindowsFoundation, "AsyncActionCompletedHandler")); - Add(WindowsFoundation, new("AsyncActionProgressHandler`1", WindowsFoundation, "AsyncActionProgressHandler`1")); - Add(WindowsFoundation, new("AsyncActionWithProgressCompletedHandler`1", WindowsFoundation, "AsyncActionWithProgressCompletedHandler`1")); - Add(WindowsFoundation, new("AsyncOperationCompletedHandler`1", WindowsFoundation, "AsyncOperationCompletedHandler`1")); - Add(WindowsFoundation, new("AsyncOperationProgressHandler`2", WindowsFoundation, "AsyncOperationProgressHandler`2")); - Add(WindowsFoundation, new("AsyncOperationWithProgressCompletedHandler`2", WindowsFoundation, "AsyncOperationWithProgressCompletedHandler`2")); - Add(WindowsFoundation, new("AsyncStatus", WindowsFoundation, "AsyncStatus")); - Add(WindowsFoundation, new("DateTime", "System", "DateTimeOffset", true)); - Add(WindowsFoundation, new("EventHandler`1", "System", "EventHandler`1", false)); - Add(WindowsFoundation, new("EventRegistrationToken", "WindowsRuntime.InteropServices", "EventRegistrationToken", false)); - Add(WindowsFoundation, new("FoundationContract", WindowsFoundation, "FoundationContract")); - Add(WindowsFoundation, new(HResult, "System", "Exception", true)); - Add(WindowsFoundation, new("IAsyncAction", WindowsFoundation, "IAsyncAction")); - Add(WindowsFoundation, new("IAsyncActionWithProgress`1", WindowsFoundation, "IAsyncActionWithProgress`1")); - Add(WindowsFoundation, new("IAsyncInfo", WindowsFoundation, "IAsyncInfo")); - Add(WindowsFoundation, new("IAsyncOperationWithProgress`2", WindowsFoundation, "IAsyncOperationWithProgress`2")); - Add(WindowsFoundation, new("IAsyncOperation`1", WindowsFoundation, "IAsyncOperation`1")); - Add(WindowsFoundation, new("IClosable", "System", "IDisposable", true, true)); - Add(WindowsFoundation, new("IMemoryBufferReference", WindowsFoundation, "IMemoryBufferReference")); - Add(WindowsFoundation, new("IPropertyValue", WindowsFoundation, "IPropertyValue", true)); - Add(WindowsFoundation, new("IReferenceArray`1", WindowsFoundation, "IReferenceArray", true)); - Add(WindowsFoundation, new(IReferenceGeneric, "System", NullableGeneric, true)); - Add(WindowsFoundation, new("IStringable", WindowsFoundation, "IStringable")); - Add(WindowsFoundation, new("Point", WindowsFoundation, "Point")); - Add(WindowsFoundation, new("PropertyType", WindowsFoundation, "PropertyType")); - Add(WindowsFoundation, new("Rect", WindowsFoundation, "Rect")); - Add(WindowsFoundation, new("Size", WindowsFoundation, "Size")); - Add(WindowsFoundation, new("TimeSpan", "System", "TimeSpan", true)); - Add(WindowsFoundation, new("TypedEventHandler`2", "System", "EventHandler`2", false)); - Add(WindowsFoundation, new("UniversalApiContract", WindowsFoundation, "UniversalApiContract")); - Add(WindowsFoundation, new("Uri", "System", "Uri", true)); - - // Windows.Foundation.Collections - Add(WindowsFoundationCollections, new("CollectionChange", WindowsFoundationCollections, "CollectionChange")); - Add(WindowsFoundationCollections, new("IIterable`1", "System.Collections.Generic", "IEnumerable`1", true, true)); - Add(WindowsFoundationCollections, new("IIterator`1", "System.Collections.Generic", "IEnumerator`1", true, true)); - Add(WindowsFoundationCollections, new("IKeyValuePair`2", "System.Collections.Generic", "KeyValuePair`2", true)); - Add(WindowsFoundationCollections, new("IMapChangedEventArgs`1", WindowsFoundationCollections, "IMapChangedEventArgs`1")); - Add(WindowsFoundationCollections, new("IMapView`2", "System.Collections.Generic", "IReadOnlyDictionary`2", true, true)); - Add(WindowsFoundationCollections, new("IMap`2", "System.Collections.Generic", "IDictionary`2", true, true)); - Add(WindowsFoundationCollections, new("IObservableMap`2", WindowsFoundationCollections, "IObservableMap`2")); - Add(WindowsFoundationCollections, new("IObservableVector`1", WindowsFoundationCollections, "IObservableVector`1")); - Add(WindowsFoundationCollections, new("IVectorChangedEventArgs", WindowsFoundationCollections, "IVectorChangedEventArgs")); - Add(WindowsFoundationCollections, new("IVectorView`1", "System.Collections.Generic", "IReadOnlyList`1", true, true)); - Add(WindowsFoundationCollections, new("IVector`1", "System.Collections.Generic", "IList`1", true, true)); - Add(WindowsFoundationCollections, new("MapChangedEventHandler`2", WindowsFoundationCollections, "MapChangedEventHandler`2")); - Add(WindowsFoundationCollections, new("VectorChangedEventHandler`1", WindowsFoundationCollections, "VectorChangedEventHandler`1")); - - // Windows.Foundation.Metadata - Add(WindowsFoundationMetadata, new("ApiContractAttribute", WindowsFoundationMetadata, "ApiContractAttribute")); - Add(WindowsFoundationMetadata, new("AttributeTargets", "System", "AttributeTargets")); - Add(WindowsFoundationMetadata, new("AttributeUsageAttribute", "System", "AttributeUsageAttribute")); - Add(WindowsFoundationMetadata, new(ContractVersionAttribute, WindowsFoundationMetadata, ContractVersionAttribute)); - - // Windows.Foundation.Numerics - Add("Windows.Foundation.Numerics", new("Matrix3x2", "System.Numerics", "Matrix3x2")); - Add("Windows.Foundation.Numerics", new("Matrix4x4", "System.Numerics", "Matrix4x4")); - Add("Windows.Foundation.Numerics", new("Plane", "System.Numerics", "Plane")); - Add("Windows.Foundation.Numerics", new("Quaternion", "System.Numerics", "Quaternion")); - Add("Windows.Foundation.Numerics", new("Vector2", "System.Numerics", "Vector2")); - Add("Windows.Foundation.Numerics", new("Vector3", "System.Numerics", "Vector3")); - Add("Windows.Foundation.Numerics", new("Vector4", "System.Numerics", "Vector4")); - - // Windows.Storage.Streams - Add("Windows.Storage.Streams", new("IBuffer", "Windows.Storage.Streams", "IBuffer")); - Add("Windows.Storage.Streams", new("IInputStream", "Windows.Storage.Streams", "IInputStream")); - Add("Windows.Storage.Streams", new("IOutputStream", "Windows.Storage.Streams", "IOutputStream")); - Add("Windows.Storage.Streams", new("IRandomAccessStream", "Windows.Storage.Streams", "IRandomAccessStream")); - Add("Windows.Storage.Streams", new("InputStreamOptions", "Windows.Storage.Streams", "InputStreamOptions")); - - // Windows.UI.Xaml - Add("Windows.UI.Xaml", new("CornerRadius", "Windows.UI.Xaml", "CornerRadius", false, false, true)); - Add("Windows.UI.Xaml", new("CornerRadiusHelper", "", "")); - Add("Windows.UI.Xaml", new("Duration", "Windows.UI.Xaml", "Duration", false, false, true)); - Add("Windows.UI.Xaml", new("DurationHelper", "", "")); - Add("Windows.UI.Xaml", new("GridLength", "Windows.UI.Xaml", "GridLength", false, false, true)); - Add("Windows.UI.Xaml", new("GridLengthHelper", "", "")); - Add("Windows.UI.Xaml", new("ICornerRadiusHelper", "", "")); - Add("Windows.UI.Xaml", new("ICornerRadiusHelperStatics", "", "")); - Add("Windows.UI.Xaml", new("IDurationHelper", "", "")); - Add("Windows.UI.Xaml", new("IDurationHelperStatics", "", "")); - Add("Windows.UI.Xaml", new("IGridLengthHelper", "", "")); - Add("Windows.UI.Xaml", new("IGridLengthHelperStatics", "", "")); - Add("Windows.UI.Xaml", new("IThicknessHelper", "", "")); - Add("Windows.UI.Xaml", new("IThicknessHelperStatics", "", "")); - Add("Windows.UI.Xaml", new("IXamlServiceProvider", "System", "IServiceProvider")); - Add("Windows.UI.Xaml", new("ThicknessHelper", "", "")); - - // Windows.UI.Xaml.Controls.Primitives - Add("Windows.UI.Xaml.Controls.Primitives", new("GeneratorPositionHelper", "", "")); - Add("Windows.UI.Xaml.Controls.Primitives", new("IGeneratorPositionHelper", "", "")); - Add("Windows.UI.Xaml.Controls.Primitives", new("IGeneratorPositionHelperStatics", "", "")); - - // Windows.UI.Xaml.Data - Add("Windows.UI.Xaml.Data", new("DataErrorsChangedEventArgs", "System.ComponentModel", "DataErrorsChangedEventArgs")); - Add("Windows.UI.Xaml.Data", new("INotifyDataErrorInfo", "System.ComponentModel", "INotifyDataErrorInfo", true, true)); - Add("Windows.UI.Xaml.Data", new("INotifyPropertyChanged", "System.ComponentModel", "INotifyPropertyChanged")); - Add("Windows.UI.Xaml.Data", new("PropertyChangedEventArgs", "System.ComponentModel", "PropertyChangedEventArgs")); - Add("Windows.UI.Xaml.Data", new("PropertyChangedEventHandler", "System.ComponentModel", "PropertyChangedEventHandler")); - - // Windows.UI.Xaml.Input - Add("Windows.UI.Xaml.Input", new("ICommand", "System.Windows.Input", "ICommand", true)); - - // Windows.UI.Xaml.Interop - Add(WindowsUIXamlInterop, new("IBindableIterable", "System.Collections", "IEnumerable", true, true)); - Add(WindowsUIXamlInterop, new("IBindableIterator", "System.Collections", "IEnumerator", true, true)); - Add(WindowsUIXamlInterop, new("IBindableVector", "System.Collections", "IList", true, true)); - Add(WindowsUIXamlInterop, new("INotifyCollectionChanged", "System.Collections.Specialized", "INotifyCollectionChanged", true)); - Add(WindowsUIXamlInterop, new("NotifyCollectionChangedAction", "System.Collections.Specialized", "NotifyCollectionChangedAction")); - Add(WindowsUIXamlInterop, new("NotifyCollectionChangedEventArgs", "System.Collections.Specialized", "NotifyCollectionChangedEventArgs", true)); - Add(WindowsUIXamlInterop, new("NotifyCollectionChangedEventHandler", "System.Collections.Specialized", "NotifyCollectionChangedEventHandler", true)); - Add(WindowsUIXamlInterop, new("TypeKind", WindowsUIXamlInterop, "TypeKind", true)); - Add(WindowsUIXamlInterop, new(TypeName, "System", "Type", true)); - - // Windows.UI.Xaml.Media - Add("Windows.UI.Xaml.Media", new("IMatrixHelper", "", "")); - Add("Windows.UI.Xaml.Media", new("IMatrixHelperStatics", "", "")); - Add("Windows.UI.Xaml.Media", new("MatrixHelper", "", "")); - - // Windows.UI.Xaml.Media.Animation - Add("Windows.UI.Xaml.Media.Animation", new("IKeyTimeHelper", "", "")); - Add("Windows.UI.Xaml.Media.Animation", new("IKeyTimeHelperStatics", "", "")); - Add("Windows.UI.Xaml.Media.Animation", new("IRepeatBehaviorHelper", "", "")); - Add("Windows.UI.Xaml.Media.Animation", new("IRepeatBehaviorHelperStatics", "", "")); - Add("Windows.UI.Xaml.Media.Animation", new("KeyTime", "Windows.UI.Xaml.Media.Animation", "KeyTime", false, false, true)); - Add("Windows.UI.Xaml.Media.Animation", new("KeyTimeHelper", "", "")); - Add("Windows.UI.Xaml.Media.Animation", new("RepeatBehavior", "Windows.UI.Xaml.Media.Animation", "RepeatBehavior", false, false, true)); - Add("Windows.UI.Xaml.Media.Animation", new("RepeatBehaviorHelper", "", "")); - - // Windows.UI.Xaml.Media.Media3D - Add("Windows.UI.Xaml.Media.Media3D", new("IMatrix3DHelper", "", "")); - Add("Windows.UI.Xaml.Media.Media3D", new("IMatrix3DHelperStatics", "", "")); - Add("Windows.UI.Xaml.Media.Media3D", new("Matrix3D", "Windows.UI.Xaml.Media.Media3D", "Matrix3D", false, false, true)); - Add("Windows.UI.Xaml.Media.Media3D", new("Matrix3DHelper", "", "")); - - // WindowsRuntime.Internal - Add(WindowsRuntimeInternal, new("HWND", "System", "IntPtr")); - Add(WindowsRuntimeInternal, new("ProjectionInternalAttribute", "", "")); - - Dictionary> frozenInner = []; - foreach (KeyValuePair> kvp in result) - { - frozenInner[kvp.Key] = kvp.Value.ToFrozenDictionary(); - } - return frozenInner.ToFrozenDictionary(); + return false; } } \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Helpers/ObjRefNameGenerator.cs b/src/WinRT.Projection.Writer/Helpers/ObjRefNameGenerator.cs index 769231693f..ae39d3b987 100644 --- a/src/WinRT.Projection.Writer/Helpers/ObjRefNameGenerator.cs +++ b/src/WinRT.Projection.Writer/Helpers/ObjRefNameGenerator.cs @@ -5,12 +5,12 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using WindowsRuntime.ProjectionWriter.Factories; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Metadata; using WindowsRuntime.ProjectionWriter.Writers; using static WindowsRuntime.ProjectionWriter.References.ProjectionNames; using static WindowsRuntime.ProjectionWriter.References.WellKnownAttributeNames; -using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; namespace WindowsRuntime.ProjectionWriter.Helpers; @@ -31,26 +31,14 @@ public static string GetObjRefName(ProjectionEmitContext context, ITypeDefOrRef if (ifaceType is TypeDefinition td) { (string ns, string name) = td.Names(); - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); projected = GlobalPrefix + ns + "." + IdentifierEscaping.StripBackticks(name); } else if (ifaceType is TypeReference tr) { (string ns, string name) = tr.Names(); - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); projected = GlobalPrefix + ns + "." + IdentifierEscaping.StripBackticks(name); } @@ -75,13 +63,7 @@ private static void WriteFullyQualifiedInterfaceName(IndentedTextWriter writer, if (ifaceType is TypeDefinition td) { (string ns, string name) = td.Names(); - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); writer.Write(GlobalPrefix); @@ -95,13 +77,7 @@ private static void WriteFullyQualifiedInterfaceName(IndentedTextWriter writer, else if (ifaceType is TypeReference tr) { (string ns, string name) = tr.Names(); - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); writer.Write(GlobalPrefix); @@ -112,17 +88,11 @@ private static void WriteFullyQualifiedInterfaceName(IndentedTextWriter writer, writer.Write(IdentifierEscaping.StripBackticks(name)); } - else if (ifaceType is TypeSpecification ts && ts.Signature is GenericInstanceTypeSignature gi) + else if (ifaceType.TryGetGenericInstance(out GenericInstanceTypeSignature? gi)) { ITypeDefOrRef gt = gi.GenericType; (string ns, string name) = gt.Names(); - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); writer.Write(GlobalPrefix); @@ -134,10 +104,7 @@ private static void WriteFullyQualifiedInterfaceName(IndentedTextWriter writer, writer.Write($"{IdentifierEscaping.StripBackticks(name)}<"); for (int i = 0; i < gi.TypeArguments.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); // forceWriteNamespace=true so generic args also get global:: prefix. TypedefNameWriter.WriteTypeName(writer, context, TypeSemanticsFactory.Get(gi.TypeArguments[i]), TypedefNameType.Projected, true); @@ -170,7 +137,7 @@ private static string WriteFullyQualifiedInterfaceName(ProjectionEmitContext con public static void WriteIidExpression(IndentedTextWriter writer, ProjectionEmitContext context, ITypeDefOrRef ifaceType) { // Generic instantiation: use the UnsafeAccessor extern method declared above the field. - if (ifaceType is TypeSpecification ts && ts.Signature is GenericInstanceTypeSignature gi) + if (ifaceType.TryGetGenericInstance(out GenericInstanceTypeSignature? gi)) { string propName = BuildIidPropertyNameForGenericInterface(context, gi); writer.Write($"{propName}(null)"); @@ -183,15 +150,13 @@ public static void WriteIidExpression(IndentedTextWriter writer, ProjectionEmitC if (ifaceType is TypeDefinition td) { - ns = td.Namespace?.Value ?? string.Empty; - name = td.Name?.Value ?? string.Empty; - isMapped = MappedTypes.Get(ns, name) is not null; + (ns, name) = td.Names(); + isMapped = MappedTypes.IsMapped(ns, name); } else if (ifaceType is TypeReference tr) { - ns = tr.Namespace?.Value ?? string.Empty; - name = tr.Name?.Value ?? string.Empty; - isMapped = MappedTypes.Get(ns, name) is not null; + (ns, name) = tr.Names(); + isMapped = MappedTypes.IsMapped(ns, name); } else { @@ -223,20 +188,11 @@ public static void WriteIidExpression(IndentedTextWriter writer, ProjectionEmitC } } - /// - /// Convenience overload of - /// that leases an from , - /// emits the IID expression into it, and returns the resulting string. - /// - /// The active emit context. - /// The interface type whose IID expression is emitted. - /// The emitted IID expression. - public static string WriteIidExpression(ProjectionEmitContext context, ITypeDefOrRef ifaceType) + /// + /// A callback that writes the IID expression to the writer it's appended to. + public static WriteIidExpressionCallback WriteIidExpression(ProjectionEmitContext context, ITypeDefOrRef ifaceType) { - using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); - IndentedTextWriter writer = writerOwner.Writer; - WriteIidExpression(writer, context, ifaceType); - return writer.ToString(); + return new(context, ifaceType); } /// @@ -247,51 +203,10 @@ internal static string BuildIidPropertyNameForGenericInterface(ProjectionEmitCon { TypeSemantics sem = TypeSemanticsFactory.Get(gi); return "IID_" + IidExpressionGenerator.EscapeTypeNameForIdentifier( - TypedefNameWriter.WriteTypeName(context, sem, TypedefNameType.ABI, forceWriteNamespace: true), + TypedefNameWriter.WriteTypeName(context, sem, TypedefNameType.ABI, forceWriteNamespace: true).Format(), stripGlobal: true, stripGlobalABI: true); } - /// - /// Emits the [UnsafeAccessor] extern method declaration that exposes the IID for a generic - /// interface instantiation. - /// - /// The writer to emit to. - /// The active emit context. - /// The generic interface instantiation whose IID accessor is being emitted. - /// When true, the accessor's parameter type is - /// object? (used inside #nullable enable regions); otherwise object. - internal static void EmitUnsafeAccessorForIid(IndentedTextWriter writer, ProjectionEmitContext context, GenericInstanceTypeSignature gi, bool isInNullableContext = false) - { - string propName = BuildIidPropertyNameForGenericInterface(context, gi); - string interopName = InteropTypeNameWriter.EncodeInteropTypeName(gi, TypedefNameType.InteropIID); - writer.Write($$""" - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "get_IID_{{interopName}}")] - static extern ref readonly Guid {{propName}}([UnsafeAccessorType("ABI.InterfaceIIDs, WinRT.Interop")] object - """, isMultiline: true); - if (isInNullableContext) - { - writer.Write("?"); - } - - writer.WriteLine(" _);"); - } - - /// - /// Convenience overload of - /// that leases an from , - /// emits the [UnsafeAccessor] declaration into it, and returns the resulting string. - /// - /// The active emit context. - /// The generic interface instantiation whose IID accessor is being emitted. - /// When true, the accessor's parameter type is object?; otherwise object. - /// The emitted [UnsafeAccessor] declaration. - internal static string EmitUnsafeAccessorForIid(ProjectionEmitContext context, GenericInstanceTypeSignature gi, bool isInNullableContext = false) - { - using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); - IndentedTextWriter writer = writerOwner.Writer; - EmitUnsafeAccessorForIid(writer, context, gi, isInNullableContext); - return writer.ToString(); - } private static string EscapeIdentifier(string s) { System.Text.StringBuilder sb = new(s.Length); @@ -313,19 +228,11 @@ public static void WriteIidReferenceExpression(IndentedTextWriter writer, TypeDe writer.Write($"global::ABI.InterfaceIIDs.IID_{id}Reference"); } - /// - /// Convenience overload of - /// that leases an from , - /// emits the IID reference expression into it, and returns the resulting string. - /// - /// The value type whose IReference<T> IID expression is emitted. - /// The emitted IID reference expression. - public static string WriteIidReferenceExpression(TypeDefinition type) + /// + /// A callback that writes the IID reference expression to the writer it's appended to. + public static WriteIidReferenceExpressionCallback WriteIidReferenceExpression(TypeDefinition type) { - using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); - IndentedTextWriter writer = writerOwner.Writer; - WriteIidReferenceExpression(writer, type); - return writer.ToString(); + return new(type); } /// @@ -366,13 +273,13 @@ public static void WriteClassObjRefDefinitions(IndentedTextWriter writer, Projec // For FastAbi classes, skip non-default exclusive interfaces -- their methods // dispatch through the default interface's vtable so a separate objref is unnecessary. - bool isDefault = impl.HasAttribute(WindowsFoundationMetadata, DefaultAttribute); + bool isDefault = impl.HasWindowsFoundationMetadataAttribute(DefaultAttribute); if (!isDefault && ClassFactory.IsFastAbiClass(type)) { - TypeDefinition? implTypeDef = AbiTypeHelpers.ResolveInterfaceTypeDef(context.Cache, impl.Interface); + TypeDefinition? implTypeDef = impl.Interface.ResolveAsTypeDefinition(context.Cache); - if (implTypeDef is not null && TypeCategorization.IsExclusiveTo(implTypeDef)) + if (implTypeDef is not null && implTypeDef.IsExclusiveTo) { continue; } @@ -394,13 +301,13 @@ public static void WriteClassObjRefDefinitions(IndentedTextWriter writer, Projec } // Same fast-abi guard as the first pass. - bool isDefault2 = impl.HasAttribute(WindowsFoundationMetadata, DefaultAttribute); + bool isDefault2 = impl.HasWindowsFoundationMetadataAttribute(DefaultAttribute); if (!isDefault2 && ClassFactory.IsFastAbiClass(type)) { - TypeDefinition? implTypeDef = AbiTypeHelpers.ResolveInterfaceTypeDef(context.Cache, impl.Interface); + TypeDefinition? implTypeDef = impl.Interface.ResolveAsTypeDefinition(context.Cache); - if (implTypeDef is not null && TypeCategorization.IsExclusiveTo(implTypeDef)) + if (implTypeDef is not null && implTypeDef.IsExclusiveTo) { continue; } @@ -440,11 +347,12 @@ private static void EmitObjRefForInterface(IndentedTextWriter writer, Projection { // Sealed-class default interface: simple expression-bodied property pointing at NativeObjectReference. writer.WriteLine($"private WindowsRuntimeObjectReference {objRefName} => NativeObjectReference;"); + // Emit the unsafe accessor AFTER the field so it can be used to pass the IID in the // constructor for the default interface. if (gi is not null) { - EmitUnsafeAccessorForIid(writer, context, gi); + UnsafeAccessorFactory.EmitIidAccessor(writer, context, gi); } } else @@ -452,12 +360,12 @@ private static void EmitObjRefForInterface(IndentedTextWriter writer, Projection // Emit the unsafe accessor BEFORE the lazy field so it's referenced inside the As(...) call. if (gi is not null) { - EmitUnsafeAccessorForIid(writer, context, gi); + UnsafeAccessorFactory.EmitIidAccessor(writer, context, gi); } // Lazy CompareExchange pattern. For unsealed-class defaults, also emit 'init;' so the // constructor can assign NativeObjectReference for the exact-type case. - writer.Write($$""" + writer.Write(isMultiline: true, $$""" private WindowsRuntimeObjectReference {{objRefName}} { get @@ -468,9 +376,9 @@ WindowsRuntimeObjectReference MakeObjectReference() _ = global::System.Threading.Interlocked.CompareExchange( location1: ref field, value: NativeObjectReference.As( - """, isMultiline: true); + """); WriteIidExpression(writer, context, ifaceRef); - writer.WriteLine(""" + writer.WriteLine(isMultiline: true, """ ), comparand: null); @@ -479,11 +387,8 @@ WindowsRuntimeObjectReference MakeObjectReference() return field ?? MakeObjectReference(); } - """, isMultiline: true); - if (isDefault) - { - writer.WriteLine(" init;"); - } + """); + writer.WriteLineIf(isDefault, " init;"); writer.WriteLine("}"); } @@ -495,7 +400,7 @@ WindowsRuntimeObjectReference MakeObjectReference() private static void EmitTransitiveInterfaceObjRefs(IndentedTextWriter writer, ProjectionEmitContext context, ITypeDefOrRef ifaceRef, HashSet emitted) { // Resolve the interface to its TypeDefinition; if cross-module, look it up in the cache. - TypeDefinition? ifaceTd = AbiTypeHelpers.ResolveInterfaceTypeDef(context.Cache, ifaceRef); + TypeDefinition? ifaceTd = ifaceRef.ResolveAsTypeDefinition(context.Cache); if (ifaceTd is null) { @@ -505,7 +410,7 @@ private static void EmitTransitiveInterfaceObjRefs(IndentedTextWriter writer, Pr // Compute a substitution context if the parent is a closed generic instance. GenericContext? ctx = null; - if (ifaceRef is TypeSpecification ts && ts.Signature is GenericInstanceTypeSignature gi) + if (ifaceRef.TryGetGenericInstance(out GenericInstanceTypeSignature? gi)) { ctx = new GenericContext(gi, null); } diff --git a/src/WinRT.Projection.Writer/Helpers/SignatureGenerator.cs b/src/WinRT.Projection.Writer/Helpers/SignatureGenerator.cs new file mode 100644 index 0000000000..a5377e8d03 --- /dev/null +++ b/src/WinRT.Projection.Writer/Helpers/SignatureGenerator.cs @@ -0,0 +1,227 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Errors; +using WindowsRuntime.ProjectionWriter.Generation; +using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Resolvers; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionWriter.Helpers; + +/// +/// Emits the WinRT parametric type signature string used as the input to the WinRT GUID hash +/// algorithm. The signature format mirrors the cswinrt.exe C++ implementation: +/// +/// Fundamental types map to a single-character code (e.g. i4, u8, string). +/// Enums become enum(<name>;<underlying>). +/// Structs become struct(<name>;<field-signatures>). +/// Delegates become delegate({<guid>}). +/// Interfaces become {<guid>}. +/// Runtime classes become rc(<name>;<default-interface-signature>). +/// Generic instantiations become pinterface({<open-guid>};<arg-signatures>). +/// +/// +internal static class SignatureGenerator +{ + /// + /// Gets the signature for a given type. + /// + /// The active emit context. + /// The type semantics whose GUID signature is emitted. + /// The emitted GUID signature. + public static string GetSignature(ProjectionEmitContext context, TypeSemantics semantics) + { + using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); + + IndentedTextWriter writer = writerOwner.Writer; + + WriteSignature(writer, context, semantics); + + return writer.ToString(); + } + + /// + /// Returns the GUID-signature character code for a fundamental WinRT type (e.g. i4 + /// for , string for ). + /// + /// The fundamental type. + /// The signature code. + /// Thrown when + /// is not a known fundamental type. + private static string GetFundamentalTypeGuidSignature(FundamentalType type) => type switch + { + FundamentalType.Boolean => "b1", + FundamentalType.Char => "c2", + FundamentalType.Int8 => "i1", + FundamentalType.UInt8 => "u1", + FundamentalType.Int16 => "i2", + FundamentalType.UInt16 => "u2", + FundamentalType.Int32 => "i4", + FundamentalType.UInt32 => "u4", + FundamentalType.Int64 => "i8", + FundamentalType.UInt64 => "u8", + FundamentalType.Float => "f4", + FundamentalType.Double => "f8", + FundamentalType.String => "string", + _ => throw WellKnownProjectionWriterExceptions.UnknownFundamentalType() + }; + + /// + /// Writes the WinRT GUID parametric signature for into + /// . Used as input to GuidGenerator.Generate to compute the + /// IID for a generic interface instantiation, or as the inner signature of a containing + /// composite (struct field, runtime-class default interface, etc.). + /// + /// The writer to emit to. + /// The active emit context (used for cross-module type resolution). + /// The type semantics whose signature is emitted. + private static void WriteSignature(IndentedTextWriter writer, ProjectionEmitContext context, TypeSemantics semantics) + { + switch (semantics) + { + case TypeSemantics.GuidType: + writer.Write("g16"); + break; + case TypeSemantics.ObjectType: + writer.Write("cinterface(IInspectable)"); + break; + case TypeSemantics.Fundamental f: + writer.Write(GetFundamentalTypeGuidSignature(f.Type)); + break; + case TypeSemantics.Definition d: + WriteSignatureForType(writer, context, d.Type); + break; + case TypeSemantics.Reference r: + { + // Resolve the reference to a 'TypeDefinition' (cross-module struct field, etc.). + (string ns, string name) = r.Type.Names(); + TypeDefinition? resolved = r.Type.TryResolve(context.Cache.RuntimeContext) ?? context.Cache.Find(ns, name); + + if (resolved is not null) + { + WriteSignatureForType(writer, context, resolved); + } + } + break; + case TypeSemantics.GenericInstance gi: + writer.Write("pinterface({"); + IidExpressionGenerator.WriteGuid(writer, gi.GenericType, true); + writer.Write("};"); + for (int i = 0; i < gi.GenericArgs.Count; i++) + { + writer.WriteIf(i > 0, ";"); + + WriteSignature(writer, context, gi.GenericArgs[i]); + } + writer.Write(")"); + break; + case TypeSemantics.GenericInstanceRef gir: + { + // Cross-module generic instance (e.g. Windows.Foundation.IReference + // appearing as a struct field). Resolve the generic type to a TypeDefinition + // so we can extract its [Guid]; recurse on each type argument. + (string ns, string name) = gir.GenericType.Names(); + TypeDefinition? resolved = gir.GenericType.TryResolve(context.Cache.RuntimeContext) ?? context.Cache.Find(ns, name); + + if (resolved is not null) + { + writer.Write("pinterface({"); + IidExpressionGenerator.WriteGuid(writer, resolved, true); + writer.Write("};"); + for (int i = 0; i < gir.GenericArgs.Count; i++) + { + writer.WriteIf(i > 0, ";"); + + WriteSignature(writer, context, gir.GenericArgs[i]); + } + writer.Write(")"); + } + } + break; + } + } + + /// + /// Writes the GUID signature fragment for , dispatched by category: + /// enums emit enum(name;underlying), structs emit struct(name;field-sigs), + /// delegates emit delegate({guid}), interfaces emit {guid}, and runtime classes + /// emit rc(name;default-interface-sig) (or fall back to {guid} when no default + /// interface is declared). + /// + /// The writer to emit to. + /// The active emit context. + /// The type to emit a signature for. + private static void WriteSignatureForType(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type) + { + TypeKind kind = TypeKindResolver.Resolve(type); + switch (kind) + { + case TypeKind.Enum: + writer.Write("enum("); + TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.NonProjected, true); + TypedefNameWriter.WriteTypeParams(writer, type); + writer.Write(";"); + writer.Write(type.IsFlagsEnum ? "u4" : "i4"); + writer.Write(")"); + break; + case TypeKind.Struct: + writer.Write("struct("); + TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.NonProjected, true); + TypedefNameWriter.WriteTypeParams(writer, type); + writer.Write(";"); + bool first = true; + foreach (FieldDefinition field in type.Fields) + { + if (field.IsStatic) + { + continue; + } + + if (field.Signature is null) + { + continue; + } + + writer.WriteIf(!first, ";"); + + first = false; + WriteSignature(writer, context, TypeSemanticsFactory.Get(field.Signature.FieldType)); + } + writer.Write(")"); + break; + case TypeKind.Delegate: + writer.Write("delegate({"); + IidExpressionGenerator.WriteGuid(writer, type, true); + writer.Write("})"); + break; + case TypeKind.Interface: + writer.Write("{"); + IidExpressionGenerator.WriteGuid(writer, type, true); + writer.Write("}"); + break; + case TypeKind.Class: + ITypeDefOrRef? defaultIface = type.GetDefaultInterface(); + + if (defaultIface is TypeDefinition di) + { + writer.Write("rc("); + TypedefNameWriter.WriteTypedefName(writer, context, type, TypedefNameType.NonProjected, true); + TypedefNameWriter.WriteTypeParams(writer, type); + writer.Write(";"); + WriteSignature(writer, context, new TypeSemantics.Definition(di)); + writer.Write(")"); + } + else + { + writer.Write("{"); + IidExpressionGenerator.WriteGuid(writer, type, true); + writer.Write("}"); + } + + break; + } + } +} diff --git a/src/WinRT.Projection.Writer/Helpers/TypeFilter.cs b/src/WinRT.Projection.Writer/Helpers/TypeFilter.cs index 1c2d64215c..4fc9639a3a 100644 --- a/src/WinRT.Projection.Writer/Helpers/TypeFilter.cs +++ b/src/WinRT.Projection.Writer/Helpers/TypeFilter.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using AsmResolver; -using AsmResolver.DotNet; namespace WindowsRuntime.ProjectionWriter.Helpers; @@ -18,12 +16,6 @@ internal sealed class TypeFilter private readonly List _include; private readonly List _exclude; - /// - /// Gets a default that has no include and no exclude rules - /// (and therefore matches every type). - /// - public static TypeFilter Empty { get; } = new([], []); - /// /// Initializes a new with the given include and exclude prefix lists. /// @@ -35,11 +27,6 @@ public TypeFilter(IEnumerable include, IEnumerable exclude) _exclude = [.. exclude.OrderByDescending(s => s.Length)]; } - /// - /// Whether this filter matches everything by default (no include rules). - /// - public bool MatchesAllByDefault => _include.Count == 0; - /// /// Returns whether the given type name passes the include/exclude filter. /// Rules are sorted by descending prefix length (with includes winning ties over excludes); @@ -142,35 +129,4 @@ private static bool Match(string typeNamespace, string typeName, string rule) string rest = rule[(typeNamespace.Length + 1)..]; return typeName.StartsWith(rest, StringComparison.Ordinal); } - - /// - /// Returns whether the given passes the include/exclude filter. - /// Computes the type's full name (namespace.typeName) and delegates to - /// . - /// - /// The type definition to test. - /// if the type's full name is included. - public bool Includes(TypeDefinition type) - { - return Includes(GetFullName(type)); - } - - /// - /// Returns the full name of in the namespace.typeName form - /// (or just the type name when the namespace is empty). - /// - /// The type definition to format. - /// The fully-qualified type name. - public static string GetFullName(TypeDefinition type) - { - Utf8String? ns = type.Namespace; - Utf8String? name = type.Name; - - if (ns is null || ns.Length == 0) - { - return name?.Value ?? string.Empty; - } - - return ns + "." + (name?.Value ?? string.Empty); - } } \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Helpers/TypedefNameWriter.cs b/src/WinRT.Projection.Writer/Helpers/TypedefNameWriter.cs index 5128804681..9634e36972 100644 --- a/src/WinRT.Projection.Writer/Helpers/TypedefNameWriter.cs +++ b/src/WinRT.Projection.Writer/Helpers/TypedefNameWriter.cs @@ -4,6 +4,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; +using WindowsRuntime.ProjectionWriter.Factories.Callbacks; using WindowsRuntime.ProjectionWriter.Generation; using WindowsRuntime.ProjectionWriter.Metadata; using WindowsRuntime.ProjectionWriter.Writers; @@ -18,23 +19,27 @@ namespace WindowsRuntime.ProjectionWriter.Helpers; internal static class TypedefNameWriter { /// - /// Writes a fundamental (primitive) type's projected name. + /// Builds the fully-qualified global::Ns.Name form for a type, handling the empty-namespace case. + /// The is run through + /// so callers may pass either a raw metadata name (e.g. "IList`1") or an already-stripped name. /// - /// The writer to emit to. - /// The fundamental type. - public static void WriteFundamentalType(IndentedTextWriter writer, FundamentalType t) + /// The type's namespace (may be or empty for top-level types). + /// The type's name (raw or already stripped; a generic-arity backtick suffix is stripped before use). + /// The string global::Name when is null/empty, otherwise global::Ns.Name. + public static string BuildGlobalQualifiedName(string? ns, string name) { - writer.Write(FundamentalTypes.ToCSharpType(t)); + string stripped = IdentifierEscaping.StripBackticks(name); + return string.IsNullOrEmpty(ns) ? $"global::{stripped}" : $"global::{ns}.{stripped}"; } /// - /// Writes a fundamental (primitive) type's non-projected (.NET BCL) name. + /// Writes a fundamental (primitive) type's projected name. /// /// The writer to emit to. /// The fundamental type. - public static void WriteFundamentalNonProjectedType(IndentedTextWriter writer, FundamentalType t) + public static void WriteFundamentalType(IndentedTextWriter writer, FundamentalType t) { - writer.Write(FundamentalTypes.ToDotNetType(t)); + writer.Write(FundamentalTypes.ToCSharpType(t)); } /// @@ -47,7 +52,7 @@ public static void WriteFundamentalNonProjectedType(IndentedTextWriter writer, F /// When , always prepend the global::-qualified namespace prefix. public static void WriteTypedefName(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type, TypedefNameType nameType = TypedefNameType.Projected, bool forceWriteNamespace = false) { - bool authoredType = context.Settings.Component && context.Settings.Filter.Includes(type); + bool authoredType = context.Settings.Component && context.Settings.Filter.Includes(type.FullName); (string typeNamespace, string typeName) = type.Names(); if (nameType == TypedefNameType.NonProjected) @@ -66,15 +71,15 @@ public static void WriteTypedefName(IndentedTextWriter writer, ProjectionEmitCon TypedefNameType nameToWrite = nameType; - if (authoredType && TypeCategorization.IsExclusiveTo(type) && nameToWrite == TypedefNameType.Projected) + if (authoredType && type.IsExclusiveTo && nameToWrite == TypedefNameType.Projected) { nameToWrite = TypedefNameType.CCW; } // Authored interfaces that aren't exclusive use the same authored interface. if (authoredType && nameToWrite == TypedefNameType.CCW && - TypeCategorization.GetCategory(type) == TypeCategory.Interface && - !TypeCategorization.IsExclusiveTo(type)) + type.IsInterface && + !type.IsExclusiveTo) { nameToWrite = TypedefNameType.Projected; } @@ -120,42 +125,34 @@ public static void WriteTypedefName(IndentedTextWriter writer, ProjectionEmitCon } /// - /// Convenience overload of - /// that leases an from , - /// emits the typedef name into it, and returns the resulting string. - /// - /// The active emit context. - /// The type definition to emit the name of. - /// The kind of name to emit (projected, non-projected, ABI, etc.). - /// When , always prepend the global::-qualified namespace prefix. - /// The emitted typedef name. - public static string WriteTypedefName(ProjectionEmitContext context, TypeDefinition type, TypedefNameType nameType = TypedefNameType.Projected, bool forceWriteNamespace = false) - { - using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); - IndentedTextWriter writer = writerOwner.Writer; - WriteTypedefName(writer, context, type, nameType, forceWriteNamespace); - return writer.ToString(); - } - - /// - /// Convenience helper that emits the typedef name immediately followed by the generic - /// parameter list (e.g. Foo<T0, T1>) into a pooled writer and returns the - /// resulting string. Equivalent to calling + /// Writes the typedef name immediately followed by the generic parameter list + /// (e.g. Foo<T0, T1>). Equivalent to calling /// /// followed by . /// + /// The writer to emit to. /// The active emit context. /// The (potentially generic) type definition. /// The kind of name to emit. /// When , always prepend the global::-qualified namespace prefix. - /// The emitted typedef name + generic-parameter list. - public static string WriteTypedefNameWithTypeParams(ProjectionEmitContext context, TypeDefinition type, TypedefNameType nameType = TypedefNameType.Projected, bool forceWriteNamespace = false) + public static void WriteTypedefNameWithTypeParams(IndentedTextWriter writer, ProjectionEmitContext context, TypeDefinition type, TypedefNameType nameType, bool forceWriteNamespace) { - using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); - IndentedTextWriter writer = writerOwner.Writer; WriteTypedefName(writer, context, type, nameType, forceWriteNamespace); WriteTypeParams(writer, type); - return writer.ToString(); + } + + /// + /// A callback that writes the typedef name + generic-parameter list to the writer it's appended to. + public static WriteTypedefNameWithTypeParamsCallback WriteTypedefNameWithTypeParams(ProjectionEmitContext context, TypeDefinition type, TypedefNameType nameType, bool forceWriteNamespace) + { + return new(context, type, nameType, forceWriteNamespace); + } + + /// + /// A callback that writes the typedef name to the writer it's appended to. + public static WriteTypedefNameCallback WriteTypedefName(ProjectionEmitContext context, TypeDefinition type, TypedefNameType nameType, bool forceWriteNamespace) + { + return new(context, type, nameType, forceWriteNamespace); } /// @@ -173,10 +170,7 @@ public static void WriteTypeParams(IndentedTextWriter writer, TypeDefinition typ writer.Write("<"); for (int i = 0; i < type.GenericParameters.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); string? gpName = type.GenericParameters[i].Name?.Value; writer.Write(gpName ?? $"T{i}"); @@ -184,6 +178,13 @@ public static void WriteTypeParams(IndentedTextWriter writer, TypeDefinition typ writer.Write(">"); } + /// + /// A callback that writes the generic-parameter list to the writer it's appended to. + public static WriteTypeParamsCallback WriteTypeParams(TypeDefinition type) + { + return new(type); + } + /// /// Writes the typedef name + generic params for a handle. /// @@ -217,10 +218,7 @@ public static void WriteTypeName(IndentedTextWriter writer, ProjectionEmitContex writer.Write("<"); for (int i = 0; i < gi.GenericArgs.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); // Generic args ALWAYS use Projected, regardless of parent's nameType. WriteTypeName(writer, context, gi.GenericArgs[i], TypedefNameType.Projected, forceWriteNamespace); @@ -230,13 +228,7 @@ public static void WriteTypeName(IndentedTextWriter writer, ProjectionEmitContex case TypeSemantics.GenericInstanceRef gir: { (string ns, string name) = gir.GenericType.Names(); - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); if (nameType == TypedefNameType.EventSource && ns == "System") { @@ -246,10 +238,7 @@ public static void WriteTypeName(IndentedTextWriter writer, ProjectionEmitContex { writer.Write(GlobalPrefix); - if (nameType is TypedefNameType.ABI or TypedefNameType.StaticAbiClass or TypedefNameType.EventSource) - { - writer.Write("ABI."); - } + writer.WriteIf(nameType is TypedefNameType.ABI or TypedefNameType.StaticAbiClass or TypedefNameType.EventSource, "ABI."); writer.Write($"{ns}."); } @@ -268,10 +257,7 @@ public static void WriteTypeName(IndentedTextWriter writer, ProjectionEmitContex writer.Write("<"); for (int i = 0; i < gir.GenericArgs.Count; i++) { - if (i > 0) - { - writer.Write(", "); - } + writer.WriteIf(i > 0, ", "); WriteTypeName(writer, context, gir.GenericArgs[i], TypedefNameType.Projected, forceWriteNamespace); } @@ -281,13 +267,7 @@ public static void WriteTypeName(IndentedTextWriter writer, ProjectionEmitContex case TypeSemantics.Reference r: { (string ns, string name) = r.Type.Names(); - MappedType? mapped = MappedTypes.Get(ns, name); - - if (mapped is { } m) - { - ns = m.MappedNamespace; - name = m.MappedName; - } + _ = MappedTypes.ApplyMapping(ref ns, ref name); bool needsNsPrefix = !string.IsNullOrEmpty(ns) && ( forceWriteNamespace || @@ -300,10 +280,7 @@ public static void WriteTypeName(IndentedTextWriter writer, ProjectionEmitContex { writer.Write(GlobalPrefix); - if (nameType is TypedefNameType.ABI or TypedefNameType.StaticAbiClass or TypedefNameType.EventSource) - { - writer.Write("ABI."); - } + writer.WriteIf(nameType is TypedefNameType.ABI or TypedefNameType.StaticAbiClass or TypedefNameType.EventSource, "ABI."); writer.Write($"{ns}."); } @@ -337,38 +314,18 @@ public static void WriteProjectionType(IndentedTextWriter writer, ProjectionEmit WriteTypeName(writer, context, semantics, TypedefNameType.Projected, false); } - /// - /// Convenience overload of - /// that leases an from , - /// emits the type name into it, and returns the resulting string. - /// - /// The active emit context. - /// The semantic representation of the type. - /// The kind of name to emit. - /// When , always prepend the global::-qualified namespace prefix. - /// The emitted type name. - public static string WriteTypeName(ProjectionEmitContext context, TypeSemantics semantics, TypedefNameType nameType = TypedefNameType.Projected, bool forceWriteNamespace = false) + /// + /// A callback that writes the type name to the writer it's appended to. + public static WriteTypeNameCallback WriteTypeName(ProjectionEmitContext context, TypeSemantics semantics, TypedefNameType nameType, bool forceWriteNamespace) { - using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); - IndentedTextWriter writer = writerOwner.Writer; - WriteTypeName(writer, context, semantics, nameType, forceWriteNamespace); - return writer.ToString(); + return new(context, semantics, nameType, forceWriteNamespace); } - /// - /// Convenience overload of - /// that leases an from , - /// emits the projected type name into it, and returns the resulting string. - /// - /// The active emit context. - /// The semantic representation of the type. - /// The emitted projected type name. - public static string WriteProjectionType(ProjectionEmitContext context, TypeSemantics semantics) + /// + /// A callback that writes the projected type name to the writer it's appended to. + public static WriteProjectionTypeCallback WriteProjectionType(ProjectionEmitContext context, TypeSemantics semantics) { - using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); - IndentedTextWriter writer = writerOwner.Writer; - WriteProjectionType(writer, context, semantics); - return writer.ToString(); + return new(context, semantics); } /// @@ -379,20 +336,18 @@ public static string WriteProjectionType(ProjectionEmitContext context, TypeSema public static void WriteEventType(IndentedTextWriter writer, ProjectionEmitContext context, EventDefinition evt) => WriteEventType(writer, context, evt, null); - /// - /// Convenience overload of - /// that leases an from , - /// emits the event handler type into it, and returns the resulting string. - /// - /// The active emit context. - /// The event definition whose handler type is emitted. - /// The emitted event handler type name. - public static string WriteEventType(ProjectionEmitContext context, EventDefinition evt) + /// + /// A callback that writes the event handler type to the writer it's appended to. + public static WriteEventTypeCallback WriteEventType(ProjectionEmitContext context, EventDefinition evt) + { + return new(context, evt, null); + } + + /// + /// A callback that writes the event handler type to the writer it's appended to. + public static WriteEventTypeCallback WriteEventType(ProjectionEmitContext context, EventDefinition evt, GenericInstanceTypeSignature? currentInstance) { - using IndentedTextWriterOwner writerOwner = IndentedTextWriterPool.GetOrCreate(); - IndentedTextWriter writer = writerOwner.Writer; - WriteEventType(writer, context, evt, null); - return writer.ToString(); + return new(context, evt, currentInstance); } /// diff --git a/src/WinRT.Projection.Writer/Helpers/WellKnownInterfaceEntriesEmitter.cs b/src/WinRT.Projection.Writer/Helpers/WellKnownInterfaceEntriesEmitter.cs deleted file mode 100644 index cd3f469025..0000000000 --- a/src/WinRT.Projection.Writer/Helpers/WellKnownInterfaceEntriesEmitter.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using WindowsRuntime.ProjectionWriter.Writers; - -namespace WindowsRuntime.ProjectionWriter.Helpers; - -/// -/// Emits the IID/Vtable initializer pairs for the well-known WinRT interfaces (IPropertyValue, -/// IStringable, IWeakReferenceSource, IMarshal, IAgileObject, IInspectable, IUnknown) that -/// every CCW interface-entries struct exposes. -/// -internal static class WellKnownInterfaceEntriesEmitter -{ - /// - /// Emits the well-known interface entries for a delegate's reference interface entries. - /// Lines are written in the order expected by DelegateReferenceInterfaceEntries. - /// - /// The writer to emit to. - public static void EmitDelegateReferenceWellKnownEntries(IndentedTextWriter writer) - { - writer.WriteLine(""" - Entries.IPropertyValue.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IPropertyValue; - Entries.IPropertyValue.Vtable = global::WindowsRuntime.InteropServices.IPropertyValueImpl.OtherTypeVtable; - Entries.IStringable.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IStringable; - Entries.IStringable.Vtable = global::WindowsRuntime.InteropServices.IStringableImpl.Vtable; - Entries.IWeakReferenceSource.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IWeakReferenceSource; - Entries.IWeakReferenceSource.Vtable = global::WindowsRuntime.InteropServices.IWeakReferenceSourceImpl.Vtable; - Entries.IMarshal.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IMarshal; - Entries.IMarshal.Vtable = global::WindowsRuntime.InteropServices.IMarshalImpl.Vtable; - Entries.IAgileObject.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IAgileObject; - Entries.IAgileObject.Vtable = global::WindowsRuntime.InteropServices.IAgileObjectImpl.Vtable; - Entries.IInspectable.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IInspectable; - Entries.IInspectable.Vtable = global::WindowsRuntime.InteropServices.IInspectableImpl.Vtable; - Entries.IUnknown.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IUnknown; - Entries.IUnknown.Vtable = global::WindowsRuntime.InteropServices.IUnknownImpl.Vtable; - """, isMultiline: true); - } -} diff --git a/src/WinRT.Projection.Writer/Helpers/WindowsMetadataExpander.cs b/src/WinRT.Projection.Writer/Helpers/WindowsMetadataExpander.cs index 71269a79e8..8f7cb1a764 100644 --- a/src/WinRT.Projection.Writer/Helpers/WindowsMetadataExpander.cs +++ b/src/WinRT.Projection.Writer/Helpers/WindowsMetadataExpander.cs @@ -145,15 +145,16 @@ private static string TryGetSdkPath() return p2; } } - - // Both views can fail with permission errors on hardened machines, or with I/O errors - // when the registry hive is being modified concurrently by an installer. Treat any of - // those as "no SDK detected" and let the caller fall back to the path-not-found error. catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or SecurityException) { + // Both views can fail with permission errors on hardened machines, or with I/O errors + // when the registry hive is being modified concurrently by an installer. Treat any of + // those as "no SDK detected" and let the caller fall back to the path-not-found error. } + return string.Empty; } + private static string TryGetCurrentSdkVersion() { string sdkPath = TryGetSdkPath(); diff --git a/src/WinRT.Projection.Writer/Metadata/MetadataCache.cs b/src/WinRT.Projection.Writer/Metadata/MetadataCache.cs index 95769f0733..7a93c28f25 100644 --- a/src/WinRT.Projection.Writer/Metadata/MetadataCache.cs +++ b/src/WinRT.Projection.Writer/Metadata/MetadataCache.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using AsmResolver.DotNet; using WindowsRuntime.ProjectionWriter.Errors; @@ -18,7 +19,7 @@ internal sealed class MetadataCache /// Backing field for . private readonly Dictionary _namespaces = []; - /// Backing field for the global type-by-full-name index used by . + /// Backing field for the global type-by-full-name index used by . private readonly Dictionary _typesByFullName = []; /// Backing field for the type-to-source-module-path index used by . @@ -137,7 +138,8 @@ private void SortMembersByName() { foreach (NamespaceMembers members in _namespaces.Values) { - static int Compare(TypeDefinition a, TypeDefinition b) => StringComparer.Ordinal.Compare(a.Name?.Value ?? string.Empty, b.Name?.Value ?? string.Empty); + static int Compare(TypeDefinition a, TypeDefinition b) => StringComparer.Ordinal.Compare(a.GetRawName(), b.GetRawName()); + members.Types.Sort(Compare); members.Interfaces.Sort(Compare); members.Classes.Sort(Compare); @@ -172,22 +174,17 @@ private void LoadFile(string path) } // Dedupe by full type name. Multiple input .winmd files can legitimately define types - // with the same full name (e.g. WindowsRuntime.Internal types appearing in both - // WindowsRuntime.Internal.winmd and cswinrt.winmd, or types showing up in both an SDK - // contract winmd and a 3rd-party WinMD that re-exports / forwards them). First-load-wins. - string fullName = string.IsNullOrEmpty(ns) ? name : ns + "." + name; - - if (!_typesByFullName.TryAdd(fullName, type)) + // with the same full name (e.g. types showing up in both an SDK contract winmd and a + // 3rd-party WinMD that re-exports / forwards them). First-load-wins. + if (!_typesByFullName.TryAdd(type.FullName, type)) { continue; } - if (!_namespaces.TryGetValue(ns, out NamespaceMembers? members)) - { - members = new NamespaceMembers(ns); - _namespaces[ns] = members; - } + // Avoid the double lookup: just add a default entry and initialize it here if needed + ref NamespaceMembers? members = ref CollectionsMarshal.GetValueRefOrAddDefault(_namespaces, ns, out _); + members ??= new NamespaceMembers(ns); members.AddType(type); _typeToModulePath[type] = moduleFilePath; @@ -211,10 +208,20 @@ public string GetSourcePath(TypeDefinition type) } /// - /// Gets a type by full name, throwing if not found. + /// Looks up a type by its namespace and name; the namespace can be empty for global types. + /// Centralises the empty-namespace handling that is otherwise open-coded inconsistently. + /// + public TypeDefinition? Find(string typeNamespace, string typeName) + { + return Find(string.IsNullOrEmpty(typeNamespace) ? typeName : typeNamespace + "." + typeName); + } + + /// + /// Looks up a type by its namespace and name extracted from . /// - public TypeDefinition FindRequired(string fullName) + public TypeDefinition? Find(ITypeDefOrRef type) { - return Find(fullName) ?? throw WellKnownProjectionWriterExceptions.CannotResolveType(fullName); + (string ns, string name) = type.Names(); + return Find(ns, name); } } diff --git a/src/WinRT.Projection.Writer/Metadata/NamespaceMembers.cs b/src/WinRT.Projection.Writer/Metadata/NamespaceMembers.cs index 3311ebb791..563fddbe77 100644 --- a/src/WinRT.Projection.Writer/Metadata/NamespaceMembers.cs +++ b/src/WinRT.Projection.Writer/Metadata/NamespaceMembers.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Resolvers; namespace WindowsRuntime.ProjectionWriter.Metadata; @@ -46,14 +48,14 @@ internal sealed class NamespaceMembers(string name) public void AddType(TypeDefinition type) { Types.Add(type); - TypeCategory category = TypeCategorization.GetCategory(type); - switch (category) + TypeKind kind = TypeKindResolver.Resolve(type); + switch (kind) { - case TypeCategory.Interface: + case TypeKind.Interface: Interfaces.Add(type); break; - case TypeCategory.Class: - if (TypeCategorization.IsAttributeType(type)) + case TypeKind.Class: + if (type.IsAttributeType) { Attributes.Add(type); } @@ -63,11 +65,11 @@ public void AddType(TypeDefinition type) } break; - case TypeCategory.Enum: + case TypeKind.Enum: Enums.Add(type); break; - case TypeCategory.Struct: - if (TypeCategorization.IsApiContractType(type)) + case TypeKind.Struct: + if (type.IsApiContractType) { Contracts.Add(type); } @@ -77,7 +79,7 @@ public void AddType(TypeDefinition type) } break; - case TypeCategory.Delegate: + case TypeKind.Delegate: Delegates.Add(type); break; } diff --git a/src/WinRT.Projection.Writer/Metadata/TypeCategorization.cs b/src/WinRT.Projection.Writer/Metadata/TypeCategorization.cs deleted file mode 100644 index bd6ea5c02a..0000000000 --- a/src/WinRT.Projection.Writer/Metadata/TypeCategorization.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using AsmResolver; -using AsmResolver.DotNet; -using static WindowsRuntime.ProjectionWriter.References.WellKnownAttributeNames; -using static WindowsRuntime.ProjectionWriter.References.WellKnownNamespaces; - -namespace WindowsRuntime.ProjectionWriter.Metadata; - -/// -/// Categorization of a Windows Runtime type definition. -/// -internal enum TypeCategory -{ - Interface, - Class, - Enum, - Struct, - Delegate, -} - -/// -/// Static type categorization helpers, mirroring winmd::reader::get_category and various -/// -internal static class TypeCategorization -{ - /// - /// Determines a type's category (class/interface/enum/struct/delegate). - /// - public static TypeCategory GetCategory(TypeDefinition type) - { - if (type.IsInterface) - { - return TypeCategory.Interface; - } - - ITypeDefOrRef? baseType = type.BaseType; - - if (baseType is null) - { - return TypeCategory.Class; - } - - Utf8String? baseNs = baseType.Namespace; - Utf8String? baseName = baseType.Name; - - if (baseNs == "System" && baseName == "Enum") - { - return TypeCategory.Enum; - } - - if (baseNs == "System" && baseName == "ValueType") - { - return TypeCategory.Struct; - } - - if (baseNs == "System" && baseName == "MulticastDelegate") - { - return TypeCategory.Delegate; - } - - return TypeCategory.Class; - } - - /// - /// True if this is an Attribute-derived class. - /// - public static bool IsAttributeType(TypeDefinition type) - { - if (GetCategory(type) != TypeCategory.Class) - { - return false; - } - - // Check immediate base type for System.Attribute (winmd attribute types extend it directly). - ITypeDefOrRef? cur = type.BaseType; - while (cur is not null) - { - if (cur.Namespace == "System" && cur.Name == "Attribute") - { - return true; - } - - // For attributes, the base type chain is short and we typically stop at a TypeRef - // pointing to System.Attribute. We don't try to resolve further. - return false; - } - return false; - } - - /// - /// True if this is an API contract struct type. - /// - public static bool IsApiContractType(TypeDefinition type) - { - return GetCategory(type) == TypeCategory.Struct && - HasAttribute(type, WindowsFoundationMetadata, "ApiContractAttribute"); - } - - /// - /// True if this type is a static class (abstract+sealed). - /// - public static bool IsStatic(TypeDefinition type) - { - return GetCategory(type) == TypeCategory.Class && type.IsAbstract && type.IsSealed; - } - - /// - /// True if this is an interface marked [ExclusiveTo]. - /// - public static bool IsExclusiveTo(TypeDefinition type) - { - return GetCategory(type) == TypeCategory.Interface && - HasAttribute(type, WindowsFoundationMetadata, ExclusiveToAttribute); - } - - /// - /// True if this is a [Flags] enum. - /// - public static bool IsFlagsEnum(TypeDefinition type) - { - return GetCategory(type) == TypeCategory.Enum && - HasAttribute(type, "System", "FlagsAttribute"); - } - - /// - /// True if this is a generic type (has type parameters). - /// - public static bool IsGeneric(TypeDefinition type) - { - return type.GenericParameters.Count > 0; - } - - /// - /// True if this type is marked [ProjectionInternal]. - /// - public static bool IsProjectionInternal(TypeDefinition type) - { - return HasAttribute(type, WindowsRuntimeInternal, "ProjectionInternalAttribute"); - } - - /// - /// True if this type's CustomAttributes contains the given attribute. - /// - public static bool HasAttribute(IHasCustomAttribute member, string ns, string name) - { - for (int i = 0; i < member.CustomAttributes.Count; i++) - { - CustomAttribute attr = member.CustomAttributes[i]; - ITypeDefOrRef? type = attr.Constructor?.DeclaringType; - - if (type is not null && type.Namespace == ns && type.Name == name) - { - return true; - } - } - return false; - } - - /// - /// Gets the matching CustomAttribute or null. - /// - public static CustomAttribute? GetAttribute(IHasCustomAttribute member, string ns, string name) - { - for (int i = 0; i < member.CustomAttributes.Count; i++) - { - CustomAttribute attr = member.CustomAttributes[i]; - ITypeDefOrRef? type = attr.Constructor?.DeclaringType; - - if (type is not null && type.Namespace == ns && type.Name == name) - { - return attr; - } - } - return null; - } -} diff --git a/src/WinRT.Projection.Writer/Metadata/TypeSemantics.cs b/src/WinRT.Projection.Writer/Metadata/TypeSemantics.cs index 1857544bf1..67df65ddb7 100644 --- a/src/WinRT.Projection.Writer/Metadata/TypeSemantics.cs +++ b/src/WinRT.Projection.Writer/Metadata/TypeSemantics.cs @@ -12,7 +12,7 @@ namespace WindowsRuntime.ProjectionWriter.Metadata; /// /// Identifies a fundamental WinRT primitive type (those whose ABI representation matches a C# -/// primitive type, plus ). +/// primitive type, plus ). /// internal enum FundamentalType { @@ -52,7 +52,7 @@ internal enum FundamentalType /// . Double, - /// . + /// . String, } @@ -70,7 +70,7 @@ private TypeSemantics() { } public sealed record Fundamental(FundamentalType Type) : TypeSemantics; /// - /// The corlib type. + /// The corlib type. /// public sealed record ObjectType : TypeSemantics; @@ -116,12 +116,6 @@ public sealed record GenericTypeIndex(int Index) : TypeSemantics; /// The zero-based parameter index. public sealed record GenericMethodIndex(int Index) : TypeSemantics; - /// - /// A bound generic parameter token (rare; appears in nested generics). - /// - /// The generic parameter. - public sealed record BoundGenericParameter(GenericParameter Parameter) : TypeSemantics; - /// /// A reference to a type defined in another assembly. /// @@ -159,7 +153,7 @@ public static TypeSemantics Get(TypeSignature signature) /// /// Resolves an into the corresponding . - /// Recognizes the special-cased corlib types (, , + /// Recognizes the special-cased corlib types (, , /// ) and falls back to / /// for everything else. /// @@ -177,16 +171,22 @@ public static TypeSemantics GetFromTypeDefOrRef(ITypeDefOrRef type, bool isValue { (string ns, string name) = reference.Names(); + // 'System.Guid' lives in mscorlib (not in any .winmd), so cache resolution would always + // fail. Surface it as a dedicated semantic so the writers emit the BCL short name. if (ns == "System" && name == "Guid") { return new TypeSemantics.GuidType(); } + // Same handling for 'System.Object' as for 'System.Guid' above if (ns == "System" && name == "Object") { return new TypeSemantics.ObjectType(); } + // 'System.Type' appears verbatim in .winmd-s via the ECMA-335 attribute-blob encoding for + // 'Type'-valued attribute args ('[Activatable]', '[Composable]', etc.), not as the Windows + // Runtime 'TypeName' struct. Surface it as a dedicated semantic. if (ns == "System" && name == "Type") { return new TypeSemantics.SystemType(); @@ -230,6 +230,7 @@ private static TypeSemantics GetCorLib(ElementType elementType) private static TypeSemantics GetGenericInstance(GenericInstanceTypeSignature gi) { ITypeDefOrRef genericType = gi.GenericType; + // Always preserve the type arguments. List args = new(gi.TypeArguments.Count); foreach (TypeSignature arg in gi.TypeArguments) @@ -294,27 +295,4 @@ internal static class FundamentalTypes _ => "object" }; - /// - /// Returns the .NET reflection short name for (e.g. "Int32", - /// "String"), or "Object" for unrecognized cases. - /// - /// The fundamental type to format. - /// The .NET reflection short name. - public static string ToDotNetType(FundamentalType t) => t switch - { - FundamentalType.Boolean => "Boolean", - FundamentalType.Char => "Char", - FundamentalType.Int8 => "SByte", - FundamentalType.UInt8 => "Byte", - FundamentalType.Int16 => "Int16", - FundamentalType.UInt16 => "UInt16", - FundamentalType.Int32 => "Int32", - FundamentalType.UInt32 => "UInt32", - FundamentalType.Int64 => "Int64", - FundamentalType.UInt64 => "UInt64", - FundamentalType.Float => "Single", - FundamentalType.Double => "Double", - FundamentalType.String => "String", - _ => "Object" - }; } diff --git a/src/WinRT.Projection.Writer/Models/AbiArrayElementKind.cs b/src/WinRT.Projection.Writer/Models/AbiArrayElementKind.cs new file mode 100644 index 0000000000..4f2f61a480 --- /dev/null +++ b/src/WinRT.Projection.Writer/Models/AbiArrayElementKind.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WindowsRuntime.ProjectionWriter.Models; + +/// +/// Classification of an SZ-array element's ABI marshalling shape. Used by the per-array-element +/// pointer-type ladder in RcwCaller / DoAbi bodies so callers can dispatch via +/// switch instead of an if-else-if chain. +/// +internal enum AbiArrayElementKind +{ + /// The element flows as a void* at the ABI (strings, runtime classes/interfaces, objects). + RefLikeVoidStar, + + /// The element is (mapped from WinRT HResult). + HResultException, + + /// The element is a mapped value type (e.g. WinRT DateTime -> ). + MappedValueType, + + /// The element is a complex struct (has reference-typed fields that require per-field marshalling). + NonBlittableStruct, + + /// The element is a blittable struct (all fields are primitives or blittable structs). + BlittableStruct, + + /// The element is a blittable primitive (, , etc.). + BlittablePrimitive, +} \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Models/AbiTypeShapeKind.cs b/src/WinRT.Projection.Writer/Models/AbiTypeKind.cs similarity index 95% rename from src/WinRT.Projection.Writer/Models/AbiTypeShapeKind.cs rename to src/WinRT.Projection.Writer/Models/AbiTypeKind.cs index e4140cc42a..c73288e595 100644 --- a/src/WinRT.Projection.Writer/Models/AbiTypeShapeKind.cs +++ b/src/WinRT.Projection.Writer/Models/AbiTypeKind.cs @@ -7,7 +7,7 @@ namespace WindowsRuntime.ProjectionWriter.Models; /// Classification of a WinRT ABI type's marshalling shape, used to drive the writer's /// decisions about how to emit converters and parameter handling. /// -internal enum AbiTypeShapeKind +internal enum AbiTypeKind { /// /// The shape could not be determined (e.g. unresolved reference). @@ -32,7 +32,7 @@ internal enum AbiTypeShapeKind /// /// A WinRT struct with at least one reference-type field (string, generic instance, runtime class, etc.) that needs per-field marshalling via a generated *Marshaller. /// - ComplexStruct, + NonBlittableStruct, /// /// A WinRT struct that is mapped to a BCL value type and requires explicit marshalling (e.g. Windows.Foundation.DateTime -> ). @@ -40,7 +40,7 @@ internal enum AbiTypeShapeKind MappedAbiValueType, /// - /// The corlib primitive (marshalled via HSTRING). + /// The corlib primitive (marshalled via HSTRING). /// String, diff --git a/src/WinRT.Projection.Writer/Models/AbiTypeShape.cs b/src/WinRT.Projection.Writer/Models/AbiTypeShape.cs deleted file mode 100644 index bc23111db8..0000000000 --- a/src/WinRT.Projection.Writer/Models/AbiTypeShape.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using AsmResolver.DotNet.Signatures; - -namespace WindowsRuntime.ProjectionWriter.Models; - -/// -/// Immutable record describing the ABI marshalling shape of a single WinRT type signature -/// (the kind of marshalling needed, plus the underlying type signature). -/// -/// The classification of the type signature's ABI shape. -/// The original type signature this shape was derived from. -internal sealed record AbiTypeShape(AbiTypeShapeKind Kind, TypeSignature Signature); \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Models/MethodSignatureInfo.cs b/src/WinRT.Projection.Writer/Models/MethodSignatureInfo.cs index a7de374bc4..959b3d10f9 100644 --- a/src/WinRT.Projection.Writer/Models/MethodSignatureInfo.cs +++ b/src/WinRT.Projection.Writer/Models/MethodSignatureInfo.cs @@ -6,7 +6,6 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; -using WindowsRuntime.ProjectionWriter.References; namespace WindowsRuntime.ProjectionWriter.Models; @@ -94,10 +93,54 @@ public MethodSignatureInfo(MethodDefinition method, GenericContext? genericConte } /// - /// Returns the name of the return parameter, or if there is none. + /// Bundles the three derived return-parameter names commonly needed during marshalling code + /// emission: + /// + /// : the (escaped) name of the return-value out-pointer parameter. + /// : the corresponding size out-pointer parameter for receive-array returns. + /// : the conventional __<name> local-variable name for the unmarshalled return value. + /// /// - /// The default name to use when no return parameter is declared. - /// The return parameter name (or default). - public string ReturnParameterName(string defaultName = ProjectionNames.DefaultReturnParameterName) - => ReturnParameter?.Name?.Value ?? defaultName; + /// The return-value pointer parameter name (e.g. __return_value__). + /// The return-value size pointer parameter name (e.g. ____return_value__Size). + /// The local-variable name (e.g. ____return_value__). + public readonly record struct ReturnNameInfo(string ValuePointer, string SizePointer, string Local); + + /// + /// Returns the trio of derived return-parameter names used by Do_Abi / RcwCaller bodies. + /// + public ReturnNameInfo GetReturnNameInfo() + { + string valuePointer = Helpers.AbiTypeHelpers.GetReturnParamName(this); + return new ReturnNameInfo( + ValuePointer: valuePointer, + SizePointer: "__" + valuePointer + "Size", + Local: "__" + valuePointer); + } + + /// + /// Returns a deduplication key for a method named with this signature: + /// the method name followed by a comma-separated list of parameter type full names enclosed in + /// parentheses. Used to detect duplicate overloads across interface walks. + /// + /// The method's local (un-mangled) name. + public string GetDedupeKey(string name) + { + System.Text.StringBuilder sb = new(); + _ = sb.Append(name); + _ = sb.Append('('); + + for (int i = 0; i < Parameters.Count; i++) + { + if (i > 0) + { + _ = sb.Append(','); + } + + _ = sb.Append(Parameters[i].Type?.FullName ?? "?"); + } + + _ = sb.Append(')'); + return sb.ToString(); + } } diff --git a/src/WinRT.Projection.Writer/Models/MethodSignatureInfoExtensions.cs b/src/WinRT.Projection.Writer/Models/MethodSignatureInfoExtensions.cs new file mode 100644 index 0000000000..136dd27956 --- /dev/null +++ b/src/WinRT.Projection.Writer/Models/MethodSignatureInfoExtensions.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using WindowsRuntime.ProjectionWriter.Models; +using WindowsRuntime.ProjectionWriter.Resolvers; + +namespace WindowsRuntime.ProjectionWriter; + +/// +/// Category-filtered iteration helpers for . Each +/// iterator pre-computes the for the requested filter and +/// yields the (index, parameter) tuples. The index must remain available because several +/// callers use it to control comma emission or fixed-block nesting. +/// +internal static class MethodSignatureInfoExtensions +{ + extension(MethodSignatureInfo sig) + { + /// + /// Yields each (index, parameter, resolved category) triple for the method's parameters + /// in declaration order. + /// + public IEnumerable<(int Index, ParameterInfo Parameter, ParameterCategory Category)> EnumerateWithCategory() + { + for (int i = 0; i < sig.Parameters.Count; i++) + { + ParameterInfo p = sig.Parameters[i]; + yield return (i, p, ParameterCategoryResolver.Resolve(p)); + } + } + + /// + /// Yields each (index, parameter) pair whose resolved category equals + /// . + /// + public IEnumerable<(int Index, ParameterInfo Parameter)> ParametersByCategory(ParameterCategory category) + { + for (int i = 0; i < sig.Parameters.Count; i++) + { + ParameterInfo p = sig.Parameters[i]; + + if (ParameterCategoryResolver.Resolve(p) == category) + { + yield return (i, p); + } + } + } + } +} diff --git a/src/WinRT.Projection.Writer/Models/MethodSignatureMarshallingFacts.cs b/src/WinRT.Projection.Writer/Models/MethodSignatureMarshallingFacts.cs new file mode 100644 index 0000000000..13fe597bbe --- /dev/null +++ b/src/WinRT.Projection.Writer/Models/MethodSignatureMarshallingFacts.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Resolvers; + +namespace WindowsRuntime.ProjectionWriter.Models; + +/// +/// Pre-computed marshalling flags for a used by the +/// RCW-caller and Do_Abi body emitters. Bundles the return-shape classification and the +/// per-parameter-shape probes that drive try/finally emission, so the calling code only +/// needs to walk the parameter list once instead of re-deriving each flag inline. +/// +/// The resolved of the return type, or when the method returns void. +/// True when the return type is . +/// True when the return type's shape is reference-typed (per ). +/// True when the return type is a blittable struct. +/// True when the return type is a complex struct. +/// True when the return type is an SZ-array whose element is a known ABI element shape (blittable, ref-like, complex struct, HResult-exception, or mapped value type). +/// True when the return type is (mapped from WinRT HResult). +/// True when the return type is ; its ABI form holds an HSTRING that must be disposed. +/// True when at least one Out parameter's underlying type carries a resource that requires post-call cleanup (ref-like array element, System.Type, complex struct, or generic instance). +/// True when at least one parameter has ReceiveArray category. +/// True when at least one parameter has Pass/Fill-array category and its element type is neither blittable nor mapped value type. +/// True when at least one In/Ref parameter's underlying type is a complex struct (needs marshaller initialisation). +internal readonly record struct MethodSignatureMarshallingFacts( + AbiTypeKind ReturnShape, + bool ReturnIsString, + bool ReturnIsRefType, + bool ReturnIsBlittableStruct, + bool ReturnIsNonBlittableStruct, + bool ReturnIsReceiveArray, + bool ReturnIsHResultException, + bool ReturnIsSystemTypeForCleanup, + bool HasOutNeedsCleanup, + bool HasReceiveArray, + bool HasNonBlittablePassArray, + bool HasNonBlittableStructInput) +{ + /// + /// True when the emitted RCW-caller body must wrap the vtable call in a try/finally + /// to cover any of the resource-cleanup paths (return-side or parameter-side). + /// + public bool NeedsTryFinally => + ReturnIsString || ReturnIsRefType || ReturnIsReceiveArray || HasOutNeedsCleanup + || HasReceiveArray || ReturnIsNonBlittableStruct || HasNonBlittablePassArray + || HasNonBlittableStructInput || ReturnIsSystemTypeForCleanup; + + /// + /// Computes the marshalling facts for using + /// for shape classification. + /// + public static MethodSignatureMarshallingFacts From(MethodSignatureInfo sig, AbiTypeKindResolver resolver) + { + TypeSignature? rt = sig.ReturnType; + AbiTypeKind returnShape = rt is null ? AbiTypeKind.Unknown : resolver.Resolve(rt); + + bool returnIsString = returnShape == AbiTypeKind.String; + bool returnIsRefType = returnShape.IsReferenceType(); + bool returnIsBlittableStruct = returnShape == AbiTypeKind.BlittableStruct; + bool returnIsNonBlittableStruct = returnShape == AbiTypeKind.NonBlittableStruct; + bool returnIsReceiveArray = rt is SzArrayTypeSignature retSz + && resolver.IsRecognizedReceiveArrayElement(retSz.BaseType); + bool returnIsHResultException = returnShape == AbiTypeKind.HResultException; + bool returnIsSystemTypeForCleanup = rt is not null && rt.IsSystemType(); + + bool hasOutNeedsCleanup = false; + bool hasReceiveArray = false; + bool hasNonBlittablePassArray = false; + bool hasNonBlittableStructInput = false; + + foreach ((_, ParameterInfo p, ParameterCategory cat) in sig.EnumerateWithCategory()) + { + if (!hasOutNeedsCleanup && cat == ParameterCategory.Out + && resolver.RequiresOutParameterCleanup(p.Type.StripByRefAndCustomModifiers())) + { + hasOutNeedsCleanup = true; + } + + if (!hasReceiveArray && cat == ParameterCategory.ReceiveArray) + { + hasReceiveArray = true; + } + + if (!hasNonBlittablePassArray && cat.IsArrayInput() + && p.Type is SzArrayTypeSignature szArr + && !resolver.IsDirectPassArrayElement(szArr.BaseType)) + { + hasNonBlittablePassArray = true; + } + + if (!hasNonBlittableStructInput && cat.IsScalarInput() + && resolver.IsNonBlittableStruct(p.Type.StripByRefAndCustomModifiers())) + { + hasNonBlittableStructInput = true; + } + } + + return new MethodSignatureMarshallingFacts( + ReturnShape: returnShape, + ReturnIsString: returnIsString, + ReturnIsRefType: returnIsRefType, + ReturnIsBlittableStruct: returnIsBlittableStruct, + ReturnIsNonBlittableStruct: returnIsNonBlittableStruct, + ReturnIsReceiveArray: returnIsReceiveArray, + ReturnIsHResultException: returnIsHResultException, + ReturnIsSystemTypeForCleanup: returnIsSystemTypeForCleanup, + HasOutNeedsCleanup: hasOutNeedsCleanup, + HasReceiveArray: hasReceiveArray, + HasNonBlittablePassArray: hasNonBlittablePassArray, + HasNonBlittableStructInput: hasNonBlittableStructInput); + } +} \ No newline at end of file diff --git a/src/WinRT.Projection.Writer/Models/ParameterInfoExtensions.cs b/src/WinRT.Projection.Writer/Models/ParameterInfoExtensions.cs new file mode 100644 index 0000000000..63ae33de97 --- /dev/null +++ b/src/WinRT.Projection.Writer/Models/ParameterInfoExtensions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WindowsRuntime.ProjectionWriter.Models; + +/// +/// Extension methods on . +/// +internal static class ParameterInfoExtensions +{ + /// + /// Returns the parameter's raw metadata name, falling back to + /// when the metadata name is . This is the un-escaped form (no C# + /// keyword @ prefix); use it for derived identifiers such as __{rawName} + /// fixed-block locals or marshalling-state field names. For the C# keyword-escaped form, + /// use instead. + /// + /// The parameter to derive the raw name from. + /// The fallback name to use when the parameter has no metadata name. Defaults to "param". + /// The raw parameter name. + public static string GetRawName(this ParameterInfo parameter, string defaultName = "param") + { + return parameter.Parameter.Name ?? defaultName; + } + + /// + /// Returns the parameter's metadata name (or if the metadata + /// name is ) prefixed with @ if it is a reserved C# keyword. + /// Used to derive a call-argument or formal-parameter identifier from a . + /// + /// The parameter to derive the escaped name from. + /// The fallback name to use when the parameter has no metadata name. Defaults to "param". + /// The escaped parameter name. + public static string GetEscapedName(this ParameterInfo parameter, string defaultName = "param") + { + return Helpers.IdentifierEscaping.EscapeIdentifier(parameter.Parameter.Name ?? defaultName); + } + + /// + /// Returns the C#-escaped parameter name, allowing the caller to inject a synthesized + /// override (e.g. FastAbi-merged Methods classes). When + /// is non-, it replaces the metadata name as the source for escaping. + /// + /// The parameter to name. + /// Optional override; takes precedence over the metadata name when non-. + /// The escaped parameter name (with @ prefix for C# keywords). + public static string GetParamName(this ParameterInfo parameter, string? paramNameOverride) + { + string name = paramNameOverride ?? parameter.GetRawName(); + return Helpers.IdentifierEscaping.EscapeIdentifier(name); + } + + /// + /// Returns the local-variable name for : the raw metadata + /// name (no C#-keyword @ escape, since helper locals like __event remain + /// valid identifiers even when the underlying parameter name is a C# keyword). + /// + /// The parameter to name. + /// Optional override; takes precedence over the metadata name when non-. + /// The unescaped local-variable name. + public static string GetParamLocalName(this ParameterInfo parameter, string? paramNameOverride) + { + return paramNameOverride ?? parameter.GetRawName(); + } +} diff --git a/src/WinRT.Projection.Writer/Models/TypeKind.cs b/src/WinRT.Projection.Writer/Models/TypeKind.cs new file mode 100644 index 0000000000..bdbca19d42 --- /dev/null +++ b/src/WinRT.Projection.Writer/Models/TypeKind.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WindowsRuntime.ProjectionWriter.Models; + +/// +/// Indicates the kind of a given type definition. +/// +internal enum TypeKind +{ + /// + /// The type is an interface. + /// + Interface, + + /// + /// The type is a class (including static classes and runtime classes). + /// + Class, + + /// + /// The type is an enum. + /// + Enum, + + /// + /// The type is a struct (a non-enum value type). + /// + Struct, + + /// + /// The type is a delegate. + /// + Delegate, +} diff --git a/src/WinRT.Projection.Writer/ProjectionWriter.cs b/src/WinRT.Projection.Writer/ProjectionWriter.cs index 53fc163efd..943dafdee0 100644 --- a/src/WinRT.Projection.Writer/ProjectionWriter.cs +++ b/src/WinRT.Projection.Writer/ProjectionWriter.cs @@ -47,9 +47,6 @@ public static void Run(ProjectionWriterOptions options) Logger = options.Logger, MaxDegreesOfParallelism = options.MaxDegreesOfParallelism, Component = options.Component, - Internal = options.Internal, - Embedded = options.Embedded, - PublicEnums = options.PublicEnums, PublicExclusiveTo = options.PublicExclusiveTo, IdicExclusiveTo = options.IdicExclusiveTo, ReferenceProjection = options.ReferenceProjection, diff --git a/src/WinRT.Projection.Writer/ProjectionWriterOptions.cs b/src/WinRT.Projection.Writer/ProjectionWriterOptions.cs index 9a03dc9332..7f13333763 100644 --- a/src/WinRT.Projection.Writer/ProjectionWriterOptions.cs +++ b/src/WinRT.Projection.Writer/ProjectionWriterOptions.cs @@ -46,21 +46,6 @@ public sealed class ProjectionWriterOptions /// public bool Component { get; init; } - /// - /// Generate an internal (non-public) projection. - /// - public bool Internal { get; init; } - - /// - /// Generate an embedded projection. - /// - public bool Embedded { get; init; } - - /// - /// If true with embedded option, generate enums as public. - /// - public bool PublicEnums { get; init; } - /// /// Make exclusive-to interfaces public in the projection (default is internal). /// diff --git a/src/WinRT.Projection.Writer/References/ProjectionNames.cs b/src/WinRT.Projection.Writer/References/ProjectionNames.cs index 39318e768e..b2a8939115 100644 --- a/src/WinRT.Projection.Writer/References/ProjectionNames.cs +++ b/src/WinRT.Projection.Writer/References/ProjectionNames.cs @@ -44,14 +44,4 @@ internal static class ProjectionNames /// public const string IID = "IID"; - /// - /// The C# void-pointer keyword form ("void*"). - /// - public const string VoidPointer = "void*"; - - /// - /// The default placeholder name used for a method's return parameter when the - /// metadata does not declare one explicitly ("__return_value__"). - /// - public const string DefaultReturnParameterName = "__return_value__"; } diff --git a/src/WinRT.Projection.Writer/References/WellKnownAbiTypeNames.cs b/src/WinRT.Projection.Writer/References/WellKnownAbiTypeNames.cs new file mode 100644 index 0000000000..9e3e92bb61 --- /dev/null +++ b/src/WinRT.Projection.Writer/References/WellKnownAbiTypeNames.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WindowsRuntime.ProjectionWriter.References; + +/// +/// Well-known ABI type names emitted as literals in projection source. Each constant is the +/// fully-qualified C# expression (with the global:: prefix) for the corresponding +/// type as referenced from generated code. +/// +internal static class WellKnownAbiTypeNames +{ + /// The global::ABI.System.Type ABI helper struct. + public const string AbiSystemType = "global::ABI.System.Type"; + + /// The global::ABI.System.Type* pointer-to-ABI form. + public const string AbiSystemTypePointer = "global::ABI.System.Type*"; + + /// The global::ABI.System.Exception ABI helper struct. + public const string AbiSystemException = "global::ABI.System.Exception"; + + /// The global::ABI.System.Exception* pointer-to-ABI form. + public const string AbiSystemExceptionPointer = "global::ABI.System.Exception*"; + + /// The global::ABI.System.DateTimeOffset ABI helper struct. + public const string AbiSystemDateTimeOffset = "global::ABI.System.DateTimeOffset"; + + /// The global::ABI.System.TimeSpan ABI helper struct. + public const string AbiSystemTimeSpan = "global::ABI.System.TimeSpan"; +} diff --git a/src/WinRT.Projection.Writer/References/WellKnownTypeNames.cs b/src/WinRT.Projection.Writer/References/WellKnownTypeNames.cs index 5293344e58..5e1435571c 100644 --- a/src/WinRT.Projection.Writer/References/WellKnownTypeNames.cs +++ b/src/WinRT.Projection.Writer/References/WellKnownTypeNames.cs @@ -44,7 +44,7 @@ internal static class WellKnownTypeNames public const string Exception = "Exception"; /// - /// The BCL type name. + /// The BCL type name. /// public const string Object = "Object"; diff --git a/src/WinRT.Projection.Writer/Resolvers/AbiTypeKindResolver.cs b/src/WinRT.Projection.Writer/Resolvers/AbiTypeKindResolver.cs new file mode 100644 index 0000000000..b83fe53681 --- /dev/null +++ b/src/WinRT.Projection.Writer/Resolvers/AbiTypeKindResolver.cs @@ -0,0 +1,280 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.ProjectionWriter.Helpers; +using WindowsRuntime.ProjectionWriter.Metadata; +using WindowsRuntime.ProjectionWriter.Models; + +namespace WindowsRuntime.ProjectionWriter.Resolvers; + +/// +/// Classifies WinRT type signatures by their ABI marshalling shape (see ). +/// +/// +/// The resolver is constructed with a reference to the so it can +/// perform cross-module type resolution (e.g. resolving an enum that lives in a different +/// reference assembly than the one currently being projected). It is the single semantic +/// entry point for "what's the shape of this type at the ABI?" — emission paths consume the +/// resolver's classification rather than reaching for the per-shape predicates directly. +/// +/// The metadata cache used for cross-module type resolution. +internal sealed class AbiTypeKindResolver(MetadataCache cache) +{ + /// + /// Gets the metadata cache used for cross-module type resolution. + /// + public MetadataCache Cache { get; } = cache; + + /// + /// Classifies by its WinRT ABI marshalling shape. + /// + /// The type signature to classify. + /// The describing the signature's ABI shape; for signatures that don't match any known WinRT marshalling shape. + public AbiTypeKind Resolve(TypeSignature signature) + { + // Cheap top-level shape checks that don't need the cache + if (signature.IsString()) + { + return AbiTypeKind.String; + } + + if (signature.IsObject()) + { + return AbiTypeKind.Object; + } + + if (signature.IsHResultException()) + { + return AbiTypeKind.HResultException; + } + + if (signature.IsSystemType()) + { + return AbiTypeKind.SystemType; + } + + if (signature.IsNullableT()) + { + return AbiTypeKind.NullableT; + } + + if (signature is SzArrayTypeSignature) + { + return AbiTypeKind.Array; + } + + if (signature.IsGenericInstance()) + { + return AbiTypeKind.GenericInstance; + } + + // Cache-aware classifications. These are evaluated in the same order the writer's + // emission paths historically queried the inline AbiTypeHelpers predicates so the + // resolver returns the same shape the legacy code path would have inferred. + if (AbiTypeHelpers.IsMappedAbiValueType(signature)) + { + return AbiTypeKind.MappedAbiValueType; + } + + if (AbiTypeHelpers.IsBlittablePrimitive(Cache, signature)) + { + return signature is CorLibTypeSignature + ? AbiTypeKind.BlittablePrimitive + : AbiTypeKind.Enum; + } + + if (AbiTypeHelpers.IsNonBlittableStruct(Cache, signature)) + { + return AbiTypeKind.NonBlittableStruct; + } + + if (AbiTypeHelpers.IsBlittableStruct(Cache, signature)) + { + return AbiTypeKind.BlittableStruct; + } + + if (AbiTypeHelpers.IsRuntimeClassOrInterface(Cache, signature)) + { + if (signature is TypeDefOrRefSignature td && + td.Type is TypeDefinition def && + def.IsDelegate) + { + return AbiTypeKind.Delegate; + } + + return AbiTypeKind.RuntimeClassOrInterface; + } + + return AbiTypeKind.Unknown; + } + + /// + /// Returns whether is a blittable WinRT primitive (the C# primitive + /// types whose layout matches the WinRT ABI directly), OR a WinRT enum (whose ABI shape is its + /// underlying integer primitive). + /// + /// The type signature to classify. + /// if blittable primitive or enum; otherwise . + public bool IsBlittablePrimitive(TypeSignature signature) + => Resolve(signature) is AbiTypeKind.BlittablePrimitive or AbiTypeKind.Enum; + + /// + /// Returns whether is a WinRT enum (marshalled as its underlying integer). + /// + /// The type signature to classify. + /// if an enum; otherwise . + public bool IsEnumType(TypeSignature signature) + => Resolve(signature) == AbiTypeKind.Enum; + + /// + /// Returns whether is a WinRT struct that flows across the ABI by value + /// without per-field marshalling (i.e. blittable struct, including bool/char fields). + /// Complex structs (with reference fields requiring per-field marshalling) are not included. + /// + /// The type signature to classify. + /// if a blittable struct; otherwise . + public bool IsBlittableStruct(TypeSignature signature) + => Resolve(signature) == AbiTypeKind.BlittableStruct; + + /// + /// Returns whether is a WinRT struct that has at least one reference-type + /// field and therefore requires per-field marshalling via a *Marshaller class. + /// + /// The type signature to classify. + /// if a complex struct; otherwise . + public bool IsNonBlittableStruct(TypeSignature signature) + => Resolve(signature) == AbiTypeKind.NonBlittableStruct; + + /// + /// Returns whether is a WinRT runtime class, interface, or delegate + /// (i.e. flows across the ABI as IInspectable*). + /// + /// The type signature to classify. + /// if a class / interface / delegate; otherwise . + public bool IsRuntimeClassOrInterface(TypeSignature signature) + => Resolve(signature) is AbiTypeKind.RuntimeClassOrInterface or AbiTypeKind.Delegate; + + /// + /// Returns whether is a reference type that crosses the ABI as + /// an opaque pointer — either a runtime class / interface / delegate, the base + /// type, or any closed generic instance. + /// + /// The type signature to classify. + /// if a reference type or generic instance; otherwise . + public bool IsReferenceTypeOrGenericInstance(TypeSignature signature) + => IsRuntimeClassOrInterface(signature) || signature.IsObject() || signature.IsGenericInstance(); + + /// + /// Returns whether is a mapped value type that needs ABI-specific + /// marshalling (Windows.Foundation.DateTime, Windows.Foundation.TimeSpan). + /// + /// The type signature to classify. + /// if mapped; otherwise . + public bool IsMappedAbiValueType(TypeSignature signature) + => Resolve(signature) == AbiTypeKind.MappedAbiValueType; + + /// + /// Returns whether is a blittable element shape suitable for + /// direct pinning when carried as an SZ-array element (a blittable primitive or a blittable struct). + /// + /// The type signature to classify. + /// when the type is blittable in the array-element sense; otherwise . + public bool IsBlittableAbiElement(TypeSignature signature) + => IsBlittablePrimitive(signature) || IsBlittableStruct(signature); + + /// + /// Returns whether is an SZ-array element shape that flows across the + /// ABI without per-element marshalling — either a blittable element (blittable primitive or + /// blittable struct, see ) or a mapped value type (e.g. + /// WinRT DateTime -> , see ). + /// Used to gate "do we need to walk each element on input?" decisions in the array-pass paths. + /// + /// The element type to classify. + /// when the element can pass directly; otherwise . + public bool IsDirectPassArrayElement(TypeSignature signature) + => IsBlittableAbiElement(signature) || IsMappedAbiValueType(signature); + + /// + /// Returns whether is a recognised SZ-array element shape for which + /// the projection has an ABI marshalling story. This is the union of all five concrete element + /// shapes that the receive-array paths know how to handle: + /// + /// Blittable element (primitive or blittable struct) — see + /// Ref-like (string / runtime class / interface / object) — see + /// Complex struct — see + /// (mapped from WinRT HResult) — see + /// Mapped value type (e.g. WinRT DateTime / TimeSpan) — see + /// + /// Element types outside this set (e.g. generic instances, , unresolved + /// references) cannot be returned as a receive-array. + /// + /// The element type to classify. + /// when the element is one of the recognised receive-array shapes; otherwise . + public bool IsRecognizedReceiveArrayElement(TypeSignature signature) + => IsBlittableAbiElement(signature) + || signature.IsAbiArrayElementRefLike(this) + || IsNonBlittableStruct(signature) + || signature.IsHResultException() + || IsMappedAbiValueType(signature); + + /// + /// Returns whether identifies a parameter type that, when received + /// via an Out parameter, holds a resource which the caller must release in a finally + /// block. This includes: + /// + /// SZ-array element ref-like types (HSTRING / IInspectable* slots) — see + /// (whose ABI form is an HSTRING that must be freed) — see + /// Complex structs (per-field cleanup via the *Marshaller) — see + /// Generic instances (need WindowsRuntimeObjectReferenceValue dispose) — see + /// + /// Callers should pass the already-stripped parameter type (via ). + /// + /// The stripped (non-byref) parameter type to classify. + /// when the type requires finally-block cleanup on receive; otherwise . + public bool RequiresOutParameterCleanup(TypeSignature signature) + => signature.IsAbiArrayElementRefLike(this) + || signature.IsSystemType() + || IsNonBlittableStruct(signature) + || signature.IsGenericInstance(); + + /// + /// Classifies into one of the six + /// values used by the per-element pointer-type ladders. + /// This is the discriminator-only form -- callers translate the kind into the per-site + /// emission output. + /// + /// The SZ-array element type to classify. + /// The classification kind. + /// is the default for shapes that don't match any of the other branches. + public AbiArrayElementKind ClassifyArrayElement(TypeSignature elementType) + { + if (elementType.IsAbiArrayElementRefLike(this)) + { + return AbiArrayElementKind.RefLikeVoidStar; + } + + if (elementType.IsHResultException()) + { + return AbiArrayElementKind.HResultException; + } + + if (IsMappedAbiValueType(elementType)) + { + return AbiArrayElementKind.MappedValueType; + } + + if (IsNonBlittableStruct(elementType)) + { + return AbiArrayElementKind.NonBlittableStruct; + } + + if (IsBlittableStruct(elementType)) + { + return AbiArrayElementKind.BlittableStruct; + } + + return AbiArrayElementKind.BlittablePrimitive; + } +} diff --git a/src/WinRT.Projection.Writer/Resolvers/AbiTypeShapeResolver.cs b/src/WinRT.Projection.Writer/Resolvers/AbiTypeShapeResolver.cs deleted file mode 100644 index 31c92c8bc2..0000000000 --- a/src/WinRT.Projection.Writer/Resolvers/AbiTypeShapeResolver.cs +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using WindowsRuntime.ProjectionWriter.Helpers; -using WindowsRuntime.ProjectionWriter.Metadata; -using WindowsRuntime.ProjectionWriter.Models; - -namespace WindowsRuntime.ProjectionWriter.Resolvers; - -/// -/// Classifies WinRT type signatures by their ABI marshalling shape (see ). -/// -/// -/// The resolver is constructed with a reference to the so it can -/// perform cross-module type resolution (e.g. resolving an enum that lives in a different -/// reference assembly than the one currently being projected). It is the single semantic -/// entry point for "what's the shape of this type at the ABI?" — emission paths consume the -/// resolver's classification rather than reaching for the per-shape predicates directly. -/// -/// The metadata cache used for cross-module type resolution. -internal sealed class AbiTypeShapeResolver(MetadataCache cache) -{ - /// - /// Gets the metadata cache used for cross-module type resolution. - /// - public MetadataCache Cache { get; } = cache; - - /// - /// Classifies as an . - /// - /// The type signature to classify. - /// The shape classification. - public AbiTypeShape Resolve(TypeSignature signature) - { - AbiTypeShapeKind kind = ClassifyShape(signature); - return new AbiTypeShape(kind, signature); - } - - /// - /// Returns whether is a blittable WinRT primitive (the C# primitive - /// types whose layout matches the WinRT ABI directly), OR a WinRT enum (whose ABI shape is its - /// underlying integer primitive). - /// - /// The type signature to classify. - /// if blittable primitive or enum; otherwise . - public bool IsBlittablePrimitive(TypeSignature signature) - => Resolve(signature).Kind is AbiTypeShapeKind.BlittablePrimitive or AbiTypeShapeKind.Enum; - - /// - /// Returns whether is a WinRT enum (marshalled as its underlying integer). - /// - /// The type signature to classify. - /// if an enum; otherwise . - public bool IsEnumType(TypeSignature signature) - => Resolve(signature).Kind == AbiTypeShapeKind.Enum; - - /// - /// Returns whether is a WinRT struct that flows across the ABI by value - /// without per-field marshalling (i.e. blittable struct, including bool/char fields). - /// Complex structs (with reference fields requiring per-field marshalling) are not included. - /// - /// The type signature to classify. - /// if a blittable struct; otherwise . - public bool IsBlittableStruct(TypeSignature signature) - => Resolve(signature).Kind == AbiTypeShapeKind.BlittableStruct; - - /// - /// Returns whether is any WinRT struct that flows across the ABI by value - /// (either a blittable struct or a complex struct that needs per-field marshalling). - /// - /// The type signature to classify. - /// if a struct; otherwise . - public bool IsAnyStruct(TypeSignature signature) - => Resolve(signature).Kind is AbiTypeShapeKind.BlittableStruct or AbiTypeShapeKind.ComplexStruct; - - /// - /// Returns whether is a WinRT struct that has at least one reference-type - /// field and therefore requires per-field marshalling via a *Marshaller class. - /// - /// The type signature to classify. - /// if a complex struct; otherwise . - public bool IsComplexStruct(TypeSignature signature) - => Resolve(signature).Kind == AbiTypeShapeKind.ComplexStruct; - - /// - /// Returns whether is a WinRT runtime class, interface, or delegate - /// (i.e. flows across the ABI as IInspectable*). - /// - /// The type signature to classify. - /// if a class / interface / delegate; otherwise . - public bool IsRuntimeClassOrInterface(TypeSignature signature) - => Resolve(signature).Kind is AbiTypeShapeKind.RuntimeClassOrInterface or AbiTypeShapeKind.Delegate; - - /// - /// Returns whether is a mapped value type that needs ABI-specific - /// marshalling (Windows.Foundation.DateTime, Windows.Foundation.TimeSpan). - /// - /// The type signature to classify. - /// if mapped; otherwise . - public bool IsMappedAbiValueType(TypeSignature signature) - => Resolve(signature).Kind == AbiTypeShapeKind.MappedAbiValueType; - - /// - /// Inner classification routine. Returns the resolved for - /// ; returns when the - /// signature does not match any known WinRT marshalling shape (typically because of an - /// unresolved cross-module reference). - /// - /// The type signature to classify. - /// The classification kind. - private AbiTypeShapeKind ClassifyShape(TypeSignature signature) - { - // Cheap top-level shape checks that don't need the cache. - if (signature.IsString()) - { - return AbiTypeShapeKind.String; - } - - if (signature.IsObject()) - { - return AbiTypeShapeKind.Object; - } - - if (signature.IsHResultException()) - { - return AbiTypeShapeKind.HResultException; - } - - if (signature.IsSystemType()) - { - return AbiTypeShapeKind.SystemType; - } - - if (signature.IsNullableT()) - { - return AbiTypeShapeKind.NullableT; - } - - if (signature is SzArrayTypeSignature) - { - return AbiTypeShapeKind.Array; - } - - if (signature.IsGenericInstance()) - { - return AbiTypeShapeKind.GenericInstance; - } - - // Cache-aware classifications. These are evaluated in the same order the writer's - // emission paths historically queried the inline AbiTypeHelpers predicates so the - // resolver returns the same shape the legacy code path would have inferred. - if (AbiTypeHelpers.IsMappedAbiValueType(signature)) - { - return AbiTypeShapeKind.MappedAbiValueType; - } - - if (AbiTypeHelpers.IsBlittablePrimitive(Cache, signature)) - { - return signature is CorLibTypeSignature ? AbiTypeShapeKind.BlittablePrimitive : AbiTypeShapeKind.Enum; - } - - if (AbiTypeHelpers.IsComplexStruct(Cache, signature)) - { - return AbiTypeShapeKind.ComplexStruct; - } - - if (AbiTypeHelpers.IsAnyStruct(Cache, signature)) - { - return AbiTypeShapeKind.BlittableStruct; - } - - if (AbiTypeHelpers.IsRuntimeClassOrInterface(Cache, signature)) - { - if (signature is TypeDefOrRefSignature td && - td.Type is TypeDefinition def && - TypeCategorization.GetCategory(def) == TypeCategory.Delegate) - { - return AbiTypeShapeKind.Delegate; - } - - return AbiTypeShapeKind.RuntimeClassOrInterface; - } - - return AbiTypeShapeKind.Unknown; - } -} diff --git a/src/WinRT.Projection.Writer/Resolvers/ParameterCategoryResolver.cs b/src/WinRT.Projection.Writer/Resolvers/ParameterCategoryResolver.cs index 1681d09944..9b546e54bd 100644 --- a/src/WinRT.Projection.Writer/Resolvers/ParameterCategoryResolver.cs +++ b/src/WinRT.Projection.Writer/Resolvers/ParameterCategoryResolver.cs @@ -17,15 +17,17 @@ internal static class ParameterCategoryResolver /// /// The parameter to classify. /// The classified parameter category. - public static ParameterCategory GetParamCategory(ParameterInfo p) + public static ParameterCategory Resolve(ParameterInfo p) { bool isArray = p.Type is SzArrayTypeSignature; bool isOut = p.Parameter.Definition?.IsOut == true; bool isIn = p.Parameter.Definition?.IsIn == true; + // Check both the captured signature type and the parameter's own type (handles cases where // the signature is wrapped in a ByReferenceTypeSignature only on one side after substitution). // Also peel custom modifiers (e.g. modreq[InAttribute]) which can hide a ByRef beneath. bool isByRef = p.Type.IsByRefType() || p.Parameter.ParameterType.IsByRefType(); + // If byref and underlying is an array, treat as array param (PassArray/ReceiveArray/FillArray) // based on in/out flags. WinRT metadata represents 'out byte[]' as 'byte[]&' with [out]. bool isByRefArray = isByRef && p.Type.StripByRefAndCustomModifiers() is SzArrayTypeSignature; diff --git a/src/WinRT.Projection.Writer/Resolvers/TypeKindResolver.cs b/src/WinRT.Projection.Writer/Resolvers/TypeKindResolver.cs new file mode 100644 index 0000000000..409cd1c566 --- /dev/null +++ b/src/WinRT.Projection.Writer/Resolvers/TypeKindResolver.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Models; + +namespace WindowsRuntime.ProjectionWriter.Resolvers; + +/// +/// Classifies a into one of the five fundamental WinRT +/// type kinds (): class, interface, enum, struct, or delegate. +/// +internal static class TypeKindResolver +{ + /// + /// Returns the for . + /// + /// The type definition to classify. + /// The resolved . + public static TypeKind Resolve(TypeDefinition type) + { + return type switch + { + { IsInterface: true } => TypeKind.Interface, + { IsEnum: true } => TypeKind.Enum, + { IsValueType: true } => TypeKind.Struct, + { IsDelegate: true } => TypeKind.Delegate, + _ => TypeKind.Class + }; + } +} diff --git a/src/WinRT.Projection.Writer/WinRT.Projection.Writer.csproj b/src/WinRT.Projection.Writer/WinRT.Projection.Writer.csproj index cd4fb39a64..248e98bf41 100644 --- a/src/WinRT.Projection.Writer/WinRT.Projection.Writer.csproj +++ b/src/WinRT.Projection.Writer/WinRT.Projection.Writer.csproj @@ -49,6 +49,10 @@ + + + + @@ -60,7 +64,7 @@ SR.cs files would be misinterpreted by the SDK's CreateManifestResourceNames task as having Culture="SR" (treating '.SR.' as a locale identifier and embedding them as satellite resources), so we keep them on disk as '_SR.cs' and override the LogicalName to the - expected manifest path that Additions.All looks up at runtime. + expected manifest path that Additions looks up at runtime. --> +/// A callback that writes content into an . Implemented by +/// readonly value-type closures (typically constructed via factory methods on the relevant +/// helper class) so they can be passed as interpolation holes in +/// +/// (or any other handler-overload) and dispatched by +/// +/// without going through a ToString() intermediate. This keeps caller-side emission +/// as a single readable interpolated raw string while still producing zero-allocation, +/// indentation-aware output. +/// +internal interface IIndentedTextWriterCallback +{ + /// + /// Writes the callback's content into at the writer's current + /// position and indentation level. + /// + /// The writer to emit content to. + void Write(IndentedTextWriter writer); +} diff --git a/src/WinRT.Projection.Writer/Writers/IIndentedTextWriterCallbackExtensions.cs b/src/WinRT.Projection.Writer/Writers/IIndentedTextWriterCallbackExtensions.cs new file mode 100644 index 0000000000..2f24532167 --- /dev/null +++ b/src/WinRT.Projection.Writer/Writers/IIndentedTextWriterCallbackExtensions.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WindowsRuntime.ProjectionWriter.Writers; + +/// +/// Extension methods for implementations. +/// +internal static class IIndentedTextWriterCallbackExtensions +{ + /// + /// Writes the callback's content into a pooled at indent + /// level 0 and returns the resulting string. Use this overload when the caller needs + /// the emitted text as a standalone string (for example, to compose into another string or + /// to pass through APIs that take ) rather than appending it inline as + /// an interpolation hole inside a larger writer call. + /// + /// The concrete callback value type. + /// The callback to invoke. + /// The string produced by the callback. + public static string Format(this T callback) + where T : struct, IIndentedTextWriterCallback + { + using IndentedTextWriterOwner owner = IndentedTextWriterPool.GetOrCreate(); + + callback.Write(owner.Writer); + + return owner.Writer.ToString(); + } +} diff --git a/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendIfInterpolatedStringHandler.cs b/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendIfInterpolatedStringHandler.cs new file mode 100644 index 0000000000..a64fc0fe4d --- /dev/null +++ b/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendIfInterpolatedStringHandler.cs @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Text; + +#pragma warning disable IDE0038 + +namespace WindowsRuntime.ProjectionWriter.Writers; + +/// +internal partial class IndentedTextWriter +{ + /// + /// Provides a handler used by the language compiler to conditionally append interpolated strings into instances. + /// + /// + /// This handler differs from in that it accepts a leading condition argument and short-circuits + /// the entire interpolation when that condition is : no AppendLiteral or AppendFormatted call is emitted by + /// the compiler, so neither the literal segments nor the interpolation expressions are evaluated. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [InterpolatedStringHandler] + public readonly ref struct AppendIfInterpolatedStringHandler + { + /// The associated to which to append. + private readonly IndentedTextWriter _writer; + + /// When , treats the content as multiline (normalizes CRLF -> LF and indents every line). + private readonly bool _isMultiline; + + /// Creates a handler used to conditionally append an interpolated string into a . + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The associated to which to append. + /// When , writes the content, otherwise it does nothing. + /// Whether the handler is enabled. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public AppendIfInterpolatedStringHandler( + int literalLength, + int formattedCount, + IndentedTextWriter writer, + bool condition, + out bool shouldAppend) + { + if (condition) + { + _writer = writer; + _isMultiline = false; + + shouldAppend = true; + } + else + { + // We're intentionally suppressing the warning here: the writer shouldn't ever be + // used if the handler is disabled, this just further validates it (it would throw). + _writer = null!; + _isMultiline = false; + + shouldAppend = false; + } + } + + /// Creates a handler used to conditionally append an interpolated string into a . + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The associated to which to append. + /// When , writes the content, otherwise it does nothing. + /// When , treats the content as multiline (normalizes CRLF -> LF and indents every line). + /// Whether the handler is enabled. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public AppendIfInterpolatedStringHandler( + int literalLength, + int formattedCount, + IndentedTextWriter writer, + bool condition, + bool isMultiline, + out bool shouldAppend) + { + if (condition) + { + _writer = writer; + _isMultiline = isMultiline; + + shouldAppend = true; + } + else + { + _writer = null!; + _isMultiline = false; + + shouldAppend = false; + } + } + + /// Writes the specified string to the handler. + /// The string to write. + public void AppendLiteral(string value) + { + _writer.Write(_isMultiline, value); + } + + /// Writes the specified value to the handler. + /// The value to write. + /// The type of the value to write. + public void AppendFormatted(T value) + { + if (value is null) + { + return; + } + + // Handle custom callbacks first. The value-type fast path lets the JIT specialize + // the dispatch when 'T' is the concrete callback struct (e.g. WriteIidGuidReferenceCallback). + if (typeof(T).IsValueType && value is IIndentedTextWriterCallback) + { + ((IIndentedTextWriterCallback)value).Write(_writer); + + return; + } + + // Polymorphic fallback for callers that declare the local as the interface type + // (e.g. to assign one of several concrete callback structs based on a condition). + if (value is IIndentedTextWriterCallback callback) + { + callback.Write(_writer); + + return; + } + + // If the value is a 'string', write it while preserving the multiline semantics. + // Otherwise, leverage the 'StringBuilder' handler for zero-alloc interpolation. + if (value is string text) + { + _writer.Write(_isMultiline, text); + } + else + { + _ = _writer._buffer.Append($"{value}"); + } + } + + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + /// The type of the value to write. + public void AppendFormatted(T value, string? format) + { + if (value is null) + { + return; + } + + // If the value is a 'string', write it while preserving the multiline semantics. + // Otherwise, leverage the 'StringBuilder' handler for zero-alloc interpolation. + if (value is string text) + { + _writer.Write(_isMultiline, text); + } + else + { + StringBuilder.AppendInterpolatedStringHandler handler = new(0, 1, _writer._buffer); + + handler.AppendFormatted(value, format); + + _ = _writer._buffer.Append(ref handler); + } + } + + /// Writes the specified character span to the handler. + /// The span to write. + public void AppendFormatted(scoped ReadOnlySpan value) + { + _writer.Write(_isMultiline, value); + } + + /// Writes the specified value to the handler. + /// The value to write. + public void AppendFormatted(string? value) + { + if (value is null) + { + return; + } + + _writer.Write(_isMultiline, value); + } + } +} diff --git a/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendInterpolatedStringHandler.cs b/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendInterpolatedStringHandler.cs new file mode 100644 index 0000000000..7f5b629a2e --- /dev/null +++ b/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendInterpolatedStringHandler.cs @@ -0,0 +1,233 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Text; + +#pragma warning disable IDE0038 + +namespace WindowsRuntime.ProjectionWriter.Writers; + +/// +internal partial class IndentedTextWriter +{ + /// + /// Provides a handler used by the language compiler to append interpolated strings into instances. + /// + /// + /// + /// In multiline mode, the handler automatically suppresses blank lines that exist in the + /// template only because every interpolation hole on that line expanded to empty content + /// (e.g. an attribute callback that early-returns based on build configuration). Literal + /// blank lines in the source template (consecutive newlines not separated by an interpolation + /// hole) are always preserved unchanged. Both LF and CRLF source line endings are handled. + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [InterpolatedStringHandler] + public ref struct AppendInterpolatedStringHandler + { + /// The associated to which to append. + private readonly IndentedTextWriter _writer; + + /// When , treats the content as multiline (normalizes CRLF -> LF and indents every line). + private readonly bool _isMultiline; + + /// + /// Tracks whether any AppendFormatted call between the previous and the next AppendLiteral + /// emitted any content. Reset to at every AppendLiteral, set to + /// by AppendFormatted calls that grow the buffer. Used by the blank-line + /// suppression rule to decide whether a literal segment's leading newline can be dropped. + /// + private bool _anyContentBetweenLiterals; + + /// Creates a handler used to append an interpolated string into a . + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The associated to which to append. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public AppendInterpolatedStringHandler( + int literalLength, + int formattedCount, + IndentedTextWriter writer) + { + _writer = writer; + _isMultiline = false; + _anyContentBetweenLiterals = false; + } + + /// Creates a handler used to append an interpolated string into a . + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The associated to which to append. + /// When , treats the content as multiline (normalizes CRLF -> LF and indents every line). + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public AppendInterpolatedStringHandler( + int literalLength, + int formattedCount, + IndentedTextWriter writer, + bool isMultiline) + { + _writer = writer; + _isMultiline = isMultiline; + _anyContentBetweenLiterals = false; + } + + /// Writes the specified string to the handler. + /// The string to write. + public void AppendLiteral(string value) + { + ReadOnlySpan span = value; + + // Blank-line suppression rule: a literal segment that begins with a newline (LF or + // CRLF) immediately after one or more empty interpolation holes whose surrounding + // literals also ended in '\n' would emit a stray blank line. Strip the leading + // newline to collapse the would-be blank line. + // + // The rule fires only when: + // 1. The previous content in the buffer ended with '\n' (the line is "fresh"), AND + // 2. No 'AppendFormatted' call since the previous literal emitted any content, AND + // 3. This literal starts with a newline (matched as either '\n' or '\r\n', since + // raw-string literals inherit the source file's line endings, i.e. CRLF on + // Windows, LF on Unix). CR-only line endings are not supported. + // + // Literal blank lines (e.g. "...\n\n...") within a single 'AppendLiteral' are always + // preserved because they are emitted by the multiline parser in one streaming pass + // without the rule getting a chance to fire between them. + if (span.Length > 0 && + !_anyContentBetweenLiterals && + _writer._buffer.Length > 0 && + _writer._buffer[^1] == DefaultNewLine) + { + if (span[0] == '\n') + { + span = span[1..]; + } + else if (span.Length > 1 && span[0] == '\r' && span[1] == '\n') + { + span = span[2..]; + } + } + + _writer.Write(_isMultiline, span); + + // We just wrote a literal, so reset the "any content between literals" flag for next time + _anyContentBetweenLiterals = false; + } + + /// Writes the specified value to the handler. + /// The value to write. + /// The type of the value to write. + public void AppendFormatted(T value) + { + if (value is null) + { + return; + } + + int beforeLength = _writer._buffer.Length; + + // Handle custom callbacks first. The value-type fast path lets the JIT specialize + // the dispatch when 'T' is the concrete callback struct (e.g. WriteIidGuidReferenceCallback). + if (typeof(T).IsValueType && value is IIndentedTextWriterCallback) + { + ((IIndentedTextWriterCallback)value).Write(_writer); + } + else if (value is IIndentedTextWriterCallback callback) + { + // Polymorphic fallback for callers that declare the local as the interface type + // (e.g. to assign one of several concrete callback structs based on a condition). + callback.Write(_writer); + } + else if (value is string text) + { + // If the value is a 'string', write it while preserving the multiline semantics. + _writer.Write(_isMultiline, text); + } + else + { + // Otherwise, leverage the 'StringBuilder' handler for zero-alloc interpolation. + _ = _writer._buffer.Append($"{value}"); + } + + // Track whether we actually wrote any content as part of this interpolation hole + if (_writer._buffer.Length > beforeLength) + { + _anyContentBetweenLiterals = true; + } + } + + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + /// The type of the value to write. + public void AppendFormatted(T value, string? format) + { + if (value is null) + { + return; + } + + int beforeLength = _writer._buffer.Length; + + // If the value is a 'string', write it while preserving the multiline semantics. + // Otherwise, leverage the 'StringBuilder' handler for zero-alloc interpolation. + if (value is string text) + { + _writer.Write(_isMultiline, text); + } + else + { + StringBuilder.AppendInterpolatedStringHandler handler = new(0, 1, _writer._buffer); + + handler.AppendFormatted(value, format); + + _ = _writer._buffer.Append(ref handler); + } + + // Track the interpolation result (see above) + if (_writer._buffer.Length > beforeLength) + { + _anyContentBetweenLiterals = true; + } + } + + /// Writes the specified character span to the handler. + /// The span to write. + public void AppendFormatted(scoped ReadOnlySpan value) + { + int beforeLength = _writer._buffer.Length; + + _writer.Write(_isMultiline, value); + + // Track the interpolation result (see above) + if (_writer._buffer.Length > beforeLength) + { + _anyContentBetweenLiterals = true; + } + } + + /// Writes the specified value to the handler. + /// The value to write. + public void AppendFormatted(string? value) + { + if (value is null) + { + return; + } + + int beforeLength = _writer._buffer.Length; + + _writer.Write(_isMultiline, value); + + // Track the interpolation result (see above) + if (_writer._buffer.Length > beforeLength) + { + _anyContentBetweenLiterals = true; + } + } + } +} + diff --git a/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.cs b/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.cs index 751b6b6942..261a923d90 100644 --- a/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.cs +++ b/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.cs @@ -7,6 +7,7 @@ using System; using System.IO; +using System.Runtime.CompilerServices; using System.Text; namespace WindowsRuntime.ProjectionWriter.Writers; @@ -22,14 +23,14 @@ namespace WindowsRuntime.ProjectionWriter.Writers; /// equivalent design. /// /// -/// Indentation is applied per line: the first after a +/// Indentation is applied per line: the first after a /// newline (or at buffer start) prepends the current indentation string; mid-line writes do not. /// Multiline content (passed with isMultiline: true) normalizes CRLF -> LF /// and indents each line via the current indentation level. Empty lines never receive /// indentation, so raw multi-line literals with blank lines do not gain trailing whitespace. /// /// -internal sealed class IndentedTextWriter +internal sealed partial class IndentedTextWriter { /// /// The default indentation (4 spaces). @@ -71,6 +72,7 @@ public IndentedTextWriter() _currentIndentation = string.Empty; _availableIndentations = new string[4]; _availableIndentations[0] = string.Empty; + for (int i = 1; i < _availableIndentations.Length; i++) { _availableIndentations[i] = _availableIndentations[i - 1] + DefaultIndentation; @@ -99,6 +101,7 @@ public void IncreaseIndent() public void DecreaseIndent() { CurrentIndentLevel--; + _currentIndentation = _availableIndentations[CurrentIndentLevel]; } @@ -112,27 +115,70 @@ public Block WriteBlock() { WriteLine("{"); IncreaseIndent(); - return new Block(this); + + return new(this); } /// - /// Writes to the underlying buffer, applying current indentation + /// Writes an interpolated expression to the underlying buffer, applying current indentation /// at the start of each new line. /// + /// The interpolated content to write. + /// + public void Write([InterpolatedStringHandlerArgument("")] ref AppendInterpolatedStringHandler handler) + { + _ = this; + } + + /// + /// Writes an interpolated expression to the underlying buffer, applying current indentation at the start of each new line. + /// + /// When , treats as multiline (normalizes CRLF -> LF and indents every line). + /// The interpolated content to write. + /// + public void Write(bool isMultiline, [InterpolatedStringHandlerArgument("", nameof(isMultiline))] ref AppendInterpolatedStringHandler handler) + { + _ = this; + } + + /// + /// Writes to the underlying buffer, applying current indentation at the start of each new line. + /// /// The content to write. + /// + public void Write(string content) + { + Write(isMultiline: false, content.AsSpan()); + } + + /// + /// Writes to the underlying buffer, applying current indentation at the start of each new line. + /// /// When , treats as multiline (normalizes CRLF -> LF and indents every line). - public void Write(string content, bool isMultiline = false) + /// The content to write. + /// + public void Write(bool isMultiline, string content) + { + Write(isMultiline, content.AsSpan()); + } + + /// + /// Writes to the underlying buffer, applying current indentation at the start of each new line. + /// + /// The content to write. + public void Write(ReadOnlySpan content) { - Write(content.AsSpan(), isMultiline); + Write(isMultiline: false, content); } /// /// Writes to the underlying buffer, applying current indentation /// at the start of each new line. /// - /// The content to write. + /// /// When , treats as multiline (normalizes CRLF -> LF and indents every line). - public void Write(scoped ReadOnlySpan content, bool isMultiline = false) + /// The content to write. + public void Write(bool isMultiline, ReadOnlySpan content) { if (isMultiline) { @@ -175,17 +221,55 @@ public void Write(scoped ReadOnlySpan content, bool isMultiline = false) } } + /// + /// Writes an interpolated expression to the underlying buffer if is ; otherwise does nothing. + /// + /// When , writes ; otherwise this call is a no-op. + /// The interpolated content to write. + /// + public void WriteIf(bool condition, [InterpolatedStringHandlerArgument("", nameof(condition))] ref AppendIfInterpolatedStringHandler handler) + { + _ = this; + } + + /// + /// Writes an interpolated expression to the underlying buffer if is ; otherwise does nothing. + /// + /// When , writes ; otherwise this call is a no-op. + /// When , treats as multiline. + /// The interpolated content to write. + /// + public void WriteIf(bool condition, bool isMultiline, [InterpolatedStringHandlerArgument("", nameof(condition), nameof(isMultiline))] ref AppendIfInterpolatedStringHandler handler) + { + _ = this; + } + /// /// Writes to the underlying buffer if is ; otherwise does nothing. /// /// When , writes ; otherwise this call is a no-op. /// The content to write. + /// + public void WriteIf(bool condition, string content) + { + if (condition) + { + Write(isMultiline: false, content.AsSpan()); + } + } + + /// + /// Writes to the underlying buffer if is ; otherwise does nothing. + /// + /// When , writes ; otherwise this call is a no-op. /// When , treats as multiline. - public void WriteIf(bool condition, string content, bool isMultiline = false) + /// The content to write. + /// + public void WriteIf(bool condition, bool isMultiline, string content) { if (condition) { - Write(content.AsSpan(), isMultiline); + Write(isMultiline, content.AsSpan()); } } @@ -194,12 +278,27 @@ public void WriteIf(bool condition, string content, bool isMultiline = false) /// /// When , writes ; otherwise this call is a no-op. /// The content to write. + /// + public void WriteIf(bool condition, ReadOnlySpan content) + { + if (condition) + { + Write(isMultiline: false, content); + } + } + + /// + /// Writes to the underlying buffer if is ; otherwise does nothing. + /// + /// When , writes ; otherwise this call is a no-op. /// When , treats as multiline. - public void WriteIf(bool condition, scoped ReadOnlySpan content, bool isMultiline = false) + /// The content to write. + /// + public void WriteIf(bool condition, bool isMultiline, ReadOnlySpan content) { if (condition) { - Write(content, isMultiline); + Write(isMultiline, content); } } @@ -232,32 +331,66 @@ public void WriteLine(bool skipIfPresent = false) } /// - /// Writes a newline to the underlying buffer unconditionally. Equivalent to - /// WriteLine(skipIfPresent: false). + /// Writes an interpolated expression to the underlying buffer and appends a trailing newline. /// - public void WriteRawNewLine() + /// The interpolated content to write. + /// + public void WriteLine([InterpolatedStringHandlerArgument("")] ref AppendInterpolatedStringHandler handler) { - _ = _buffer.Append(DefaultNewLine); + WriteLine(); + } + + /// + /// Writes an interpolated expression to the underlying buffer and appends a trailing newline. + /// + /// When , treats as multiline. + /// The interpolated content to write. + /// + public void WriteLine(bool isMultiline, [InterpolatedStringHandlerArgument("", nameof(isMultiline))] ref AppendInterpolatedStringHandler handler) + { + WriteLine(); } /// /// Writes to the underlying buffer and appends a trailing newline. /// /// The content to write. + /// + public void WriteLine(string content) + { + WriteLine(isMultiline: false, content.AsSpan()); + } + + /// + /// Writes to the underlying buffer and appends a trailing newline. + /// /// When , treats as multiline. - public void WriteLine(string content, bool isMultiline = false) + /// The content to write. + /// + public void WriteLine(bool isMultiline, string content) { - WriteLine(content.AsSpan(), isMultiline); + WriteLine(isMultiline, content.AsSpan()); } /// /// Writes to the underlying buffer and appends a trailing newline. /// /// The content to write. + /// + public void WriteLine(ReadOnlySpan content) + { + WriteLine(isMultiline: false, content); + } + + /// + /// Writes to the underlying buffer and appends a trailing newline. + /// /// When , treats as multiline. - public void WriteLine(scoped ReadOnlySpan content, bool isMultiline = false) + /// The content to write. + /// + public void WriteLine(bool isMultiline, ReadOnlySpan content) { - Write(content, isMultiline); + Write(isMultiline, content); WriteLine(); } @@ -274,17 +407,58 @@ public void WriteLineIf(bool condition, bool skipIfPresent = false) } } + /// + /// Writes an interpolated expression to the underlying buffer followed by a newline if is ; otherwise does nothing. + /// + /// When , writes +newline; otherwise this call is a no-op. + /// The interpolated content to write. + /// + public void WriteLineIf(bool condition, [InterpolatedStringHandlerArgument("", nameof(condition))] ref AppendIfInterpolatedStringHandler handler) + { + _ = this; + } + + /// + /// Writes an interpolated expression to the underlying buffer followed by a newline if is ; otherwise does nothing. + /// + /// When , writes +newline; otherwise this call is a no-op. + /// When , treats as multiline. + /// The interpolated content to write. + /// + public void WriteLineIf(bool condition, bool isMultiline, [InterpolatedStringHandlerArgument("", nameof(condition), nameof(isMultiline))] ref AppendIfInterpolatedStringHandler handler) + { + if (condition) + { + WriteLine(); + } + } + /// /// Writes followed by a newline if is . /// /// When , writes +newline; otherwise this call is a no-op. /// The content to write. + /// + public void WriteLineIf(bool condition, string content) + { + if (condition) + { + WriteLine(isMultiline: false, content.AsSpan()); + } + } + + /// + /// Writes followed by a newline if is . + /// + /// When , writes +newline; otherwise this call is a no-op. /// When , treats as multiline. - public void WriteLineIf(bool condition, string content, bool isMultiline = false) + /// The content to write. + /// + public void WriteLineIf(bool condition, bool isMultiline, string content) { if (condition) { - WriteLine(content.AsSpan(), isMultiline); + WriteLine(isMultiline, content.AsSpan()); } } @@ -293,25 +467,28 @@ public void WriteLineIf(bool condition, string content, bool isMultiline = false /// /// When , writes +newline; otherwise this call is a no-op. /// The content to write. - /// When , treats as multiline. - public void WriteLineIf(bool condition, scoped ReadOnlySpan content, bool isMultiline = false) + /// + public void WriteLineIf(bool condition, ReadOnlySpan content) { if (condition) { - Write(content, isMultiline); - WriteLine(); + WriteLine(isMultiline: false, content); } } /// - /// Returns the current buffer contents (trimmed) and clears the buffer. + /// Writes followed by a newline if is . /// - /// The text accumulated so far, trimmed of leading/trailing whitespace. - public string ToStringAndClear() + /// When , writes +newline; otherwise this call is a no-op. + /// When , treats as multiline. + /// The content to write. + /// + public void WriteLineIf(bool condition, bool isMultiline, ReadOnlySpan content) { - string text = _buffer.ToString().Trim(); - _ = _buffer.Clear(); - return text; + if (condition) + { + WriteLine(isMultiline, content); + } } /// @@ -329,14 +506,6 @@ public string ToStringAndClear() /// public char Back() => _buffer.Length == 0 ? '\0' : _buffer[^1]; - /// - /// Returns the contents of a substring of the buffer (used for capture-and-restore patterns). - /// - /// The starting position. - /// The length of the substring to return. - /// The substring of the buffer at the requested position. - public string GetSubstring(int startIndex, int length) => _buffer.ToString(startIndex, length); - /// /// Removes a range of characters from the buffer. /// @@ -362,6 +531,7 @@ public void ResetIndent() public void Clear() { _ = _buffer.Clear(); + ResetIndent(); } @@ -401,36 +571,27 @@ public void FlushToFile(string path) _ = _buffer.Clear(); } - /// - /// Flushes the current buffer contents to a string (without trimming) and clears the buffer. - /// - /// The full buffer contents, untrimmed. - public string FlushToString() - { - string text = _buffer.ToString(); - _ = _buffer.Clear(); - return text; - } - /// /// Writes raw text to the underlying buffer, prepending current indentation if positioned /// at the start of a new line. /// /// The raw text to write. - private void WriteRawText(scoped ReadOnlySpan content) + private void WriteRawText(ReadOnlySpan content) { - // Skip writing indent for empty content so that empty lines never receive - // trailing whitespace from the indentation prefix. + // Skip writing indent for empty content so that empty lines never + // receive trailing whitespace from the indentation prefix. if (content.IsEmpty) { return; } + // Add the indentation if this is the very start of a new line if (_buffer.Length == 0 || _buffer[^1] == DefaultNewLine) { _ = _buffer.Append(_currentIndentation); } + // Append the content after the correct indentation _ = _buffer.Append(content); } diff --git a/src/cswinrt.slnx b/src/cswinrt.slnx index cbc53422e0..8416f9f3ed 100644 --- a/src/cswinrt.slnx +++ b/src/cswinrt.slnx @@ -32,7 +32,6 @@ -