diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/AttributeInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/AttributeInfo.cs new file mode 100644 index 0000000000000..8443100fa4682 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/AttributeInfo.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.Interop +{ + /// + /// Provides the info necessary for copying an attribute from user code to generated code. + /// + internal sealed record AttributeInfo(ManagedTypeInfo Type, SequenceEqualImmutableArray Arguments) + { + internal static AttributeInfo From(AttributeData attribute) + { + var type = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(attribute.AttributeClass); + var args = attribute.ConstructorArguments.Select(ca => ca.ToCSharpString()); + return new(type, args.ToSequenceEqualImmutableArray()); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceAndMethodsContext.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceAndMethodsContext.cs new file mode 100644 index 0000000000000..82f1e2a598706 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceAndMethodsContext.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Interop +{ + public sealed partial class ComInterfaceGenerator + { + /// + /// Represents an interface and all of the methods that need to be generated for it (methods declared on the interface and methods inherited from base interfaces). + /// + private sealed record ComInterfaceAndMethodsContext(ComInterfaceContext Interface, SequenceEqualImmutableArray Methods) + { + // Change Calc all methods to return an ordered list of all the methods and the data in comInterfaceandMethodsContext + // Have a step that runs CalculateMethodStub on each of them. + // Call GroupMethodsByInterfaceForGeneration + + /// + /// COM methods that are declared on the attributed interface declaration. + /// + public IEnumerable DeclaredMethods => Methods.Where(m => !m.IsInheritedMethod); + + /// + /// COM methods that are declared on an interface the interface inherits from. + /// + public IEnumerable ShadowingMethods => Methods.Where(m => m.IsInheritedMethod); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceContext.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceContext.cs new file mode 100644 index 0000000000000..5b570c1f0455a --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceContext.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; + +namespace Microsoft.Interop +{ + public sealed partial class ComInterfaceGenerator + { + private sealed record ComInterfaceContext(ComInterfaceInfo Info, ComInterfaceContext? Base) + { + /// + /// Takes a list of ComInterfaceInfo, and creates a list of ComInterfaceContext. + /// + public static ImmutableArray GetContexts(ImmutableArray data, CancellationToken _) + { + Dictionary symbolToInterfaceInfoMap = new(); + var accumulator = ImmutableArray.CreateBuilder(data.Length); + foreach (var iface in data) + { + symbolToInterfaceInfoMap.Add(iface.ThisInterfaceKey, iface); + } + Dictionary symbolToContextMap = new(); + + foreach (var iface in data) + { + accumulator.Add(AddContext(iface)); + } + return accumulator.MoveToImmutable(); + + ComInterfaceContext AddContext(ComInterfaceInfo iface) + { + if (symbolToContextMap.TryGetValue(iface.ThisInterfaceKey, out var cachedValue)) + { + return cachedValue; + } + + if (iface.BaseInterfaceKey is null) + { + var baselessCtx = new ComInterfaceContext(iface, null); + symbolToContextMap[iface.ThisInterfaceKey] = baselessCtx; + return baselessCtx; + } + + if (!symbolToContextMap.TryGetValue(iface.BaseInterfaceKey, out var baseContext)) + { + baseContext = AddContext(symbolToInterfaceInfoMap[iface.BaseInterfaceKey]); + } + var ctx = new ComInterfaceContext(iface, baseContext); + symbolToContextMap[iface.ThisInterfaceKey] = ctx; + return ctx; + } + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs index b29a776ce2de4..303a20aa63849 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Reflection; @@ -17,15 +17,8 @@ namespace Microsoft.Interop { [Generator] - public sealed class ComInterfaceGenerator : IIncrementalGenerator + public sealed partial class ComInterfaceGenerator : IIncrementalGenerator { - private sealed record ComInterfaceContext( - ManagedTypeInfo InterfaceType, - ContainingSyntaxContext TypeDefinitionContext, - ContainingSyntax InterfaceTypeSyntax, - int MethodStartIndex, - Guid InterfaceId); - private sealed record class GeneratedStubCodeContext( ManagedTypeInfo OriginalDefiningType, ContainingSyntaxContext ContainingSyntaxContext, @@ -44,6 +37,7 @@ public static class StepNames public const string GenerateNativeToManagedVTable = nameof(GenerateNativeToManagedVTable); public const string GenerateInterfaceInformation = nameof(GenerateInterfaceInformation); public const string GenerateIUnknownDerivedAttribute = nameof(GenerateIUnknownDerivedAttribute); + public const string GenerateShadowingMethods = nameof(GenerateShadowingMethods); } public void Initialize(IncrementalGeneratorInitializationContext context) @@ -59,283 +53,134 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Where( static modelData => modelData is not null); - var interfacesWithDiagnostics = attributedInterfaces.Select(static (data, ct) => + var interfaceSymbolAndDiagnostics = attributedInterfaces.Select(static (data, ct) => { - Diagnostic? diagnostic = GetDiagnosticIfInvalidTypeForGeneration(data.Syntax, data.Symbol); - return new { data.Syntax, data.Symbol, Diagnostic = diagnostic }; + var (info, diagnostic) = ComInterfaceInfo.From(data.Symbol, data.Syntax); + return (InterfaceInfo: info, Diagnostic: diagnostic, Symbol: data.Symbol); }); + context.RegisterDiagnostics(interfaceSymbolAndDiagnostics.Select((data, ct) => data.Diagnostic)); - // Split the types we want to generate and the ones we don't into two separate groups. - var interfacesToGenerate = interfacesWithDiagnostics.Where(static data => data.Diagnostic is null); - var invalidTypeDiagnostics = interfacesWithDiagnostics.Where(static data => data.Diagnostic is not null); - - var interfaceBaseInfo = interfacesToGenerate.Collect().SelectMany((data, ct) => - { - ImmutableArray<(int StartingOffset, ManagedTypeInfo? BaseInterface, bool IsMarkerInterface)>.Builder baseInterfaceInfo = ImmutableArray.CreateBuilder<(int, ManagedTypeInfo?, bool)>(data.Length); - // Track the calculated last offsets of the interfaces. - // If a type has invalid methods, we'll count them and issue an error when generating code for that - // interface. - Dictionary derivedNextOffset = new(SymbolEqualityComparer.Default); - foreach (var iface in data) - { - var (starting, baseType, derivedStarting) = CalculateOffsetsForInterface(iface.Symbol, derivedNextOffset); - baseInterfaceInfo.Add((starting, baseType is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(baseType) : null, starting == derivedStarting)); - } - return baseInterfaceInfo.MoveToImmutable(); - - static (int Starting, INamedTypeSymbol? BaseType, int DerivedStarting) CalculateOffsetsForInterface(INamedTypeSymbol iface, Dictionary derivedNextOffsetCache) - { - INamedTypeSymbol? baseInterface = null; - foreach (var implemented in iface.Interfaces) - { - if (implemented.GetAttributes().Any(static attr => attr.AttributeClass?.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute)) - { - // We'll filter out cases where there's multiple matching interfaces when determining - // if this is a valid candidate for generation. - Debug.Assert(baseInterface is null); - baseInterface = implemented; - } - } - - // Cache the starting offsets for each base interface so we don't have to recalculate them. - int startingOffset = 3; - if (baseInterface is not null) - { - if (!derivedNextOffsetCache.TryGetValue(baseInterface, out int offset)) - { - offset = CalculateOffsetsForInterface(baseInterface, derivedNextOffsetCache).DerivedStarting; - } - - startingOffset = offset; - } - - // This calculation isn't strictly accurate. This will count methods that aren't in the same declaring syntax as the attribute on the interface, - // but we'll emit an error later if that's a problem. We also can't detect this error if the base type is in metadata. - int ifaceDerivedNextOffset = startingOffset + iface.GetMembers().Where(static m => m is IMethodSymbol { IsStatic: false }).Count(); - derivedNextOffsetCache[iface] = ifaceDerivedNextOffset; - - return (startingOffset, baseInterface, ifaceDerivedNextOffset); - } - }); - - // Zip the interface base information back with the symbols and syntax for the interface - // to calculate the interface context. - // The generator infrastructure preserves ordering of the tables once Select statements are in use, - // so we can rely on the order matching here. - var interfaceContexts = interfacesToGenerate - .Zip(interfaceBaseInfo.Select((data, ct) => data.StartingOffset)) + var interfaceSymbolsWithoutDiagnostics = interfaceSymbolAndDiagnostics + .Where(data => data.Diagnostic is null) .Select((data, ct) => - { - var (iface, startingOffset) = data; - Guid? guid = null; - var guidAttr = iface.Symbol.GetAttributes().Where(attr => attr.AttributeClass.ToDisplayString() == TypeNames.System_Runtime_InteropServices_GuidAttribute).SingleOrDefault(); - if (guidAttr is not null) - { - string? guidstr = guidAttr.ConstructorArguments.SingleOrDefault().Value as string; - if (guidstr is not null) - guid = new Guid(guidstr); - } - return new ComInterfaceContext( - ManagedTypeInfo.CreateTypeInfoForTypeSymbol(iface.Symbol), - new ContainingSyntaxContext(iface.Syntax), - new ContainingSyntax(iface.Syntax.Modifiers, iface.Syntax.Kind(), iface.Syntax.Identifier, iface.Syntax.TypeParameterList), - startingOffset, - guid ?? Guid.Empty); - }); - - context.RegisterDiagnostics(invalidTypeDiagnostics.Select((data, ct) => data.Diagnostic)); + (data.InterfaceInfo, data.Symbol)); - // Zip the incremental interface context back with the symbols and syntax for the interface - // to calculate the methods to generate. - var interfacesWithMethods = interfacesToGenerate - .Zip(interfaceContexts) - .Select(static (data, ct) => - { - var (interfaceData, interfaceContext) = data; - Location interfaceLocation = interfaceData.Syntax.GetLocation(); - var methods = ImmutableArray.CreateBuilder<(MethodDeclarationSyntax Syntax, IMethodSymbol Symbol, int Index, Diagnostic? Diagnostic)>(); - int methodVtableOffset = interfaceContext.MethodStartIndex; - foreach (var member in interfaceData.Symbol.GetMembers()) + var interfaceContexts = interfaceSymbolsWithoutDiagnostics + .Select((data, ct) => data.InterfaceInfo!) + .Collect() + .SelectMany(ComInterfaceContext.GetContexts); + + var comMethodsAndSymbolsAndDiagnostics = interfaceSymbolsWithoutDiagnostics.Select(ComMethodInfo.GetMethodsFromInterface); + context.RegisterDiagnostics(comMethodsAndSymbolsAndDiagnostics.SelectMany(static (methodList, ct) => methodList.Select(m => m.Diagnostic))); + var methodInfoAndSymbolGroupedByInterface = comMethodsAndSymbolsAndDiagnostics + .Select(static (methods, ct) => + methods + .Where(pair => pair.Diagnostic is null) + .Select(pair => (pair.Symbol, pair.ComMethod)) + .ToSequenceEqualImmutableArray()); + + var methodInfosGroupedByInterface = methodInfoAndSymbolGroupedByInterface + .Select(static (methods, ct) => + methods.Select(pair => pair.ComMethod).ToSequenceEqualImmutableArray()); + // Create list of methods (inherited and declared) and their owning interface + var comMethodContextBuilders = interfaceContexts + .Zip(methodInfosGroupedByInterface) + .Collect() + .SelectMany(static (data, ct) => { - if (member.Kind == SymbolKind.Method && !member.IsStatic) - { - // We only support methods that are defined in the same partial interface definition as the - // [GeneratedComInterface] attribute. - // This restriction not only makes finding the syntax for a given method cheaper, - // but it also enables us to ensure that we can determine vtable method order easily. - Location? locationInAttributeSyntax = null; - foreach (var location in member.Locations) - { - if (location.SourceTree == interfaceLocation.SourceTree - && interfaceLocation.SourceSpan.Contains(location.SourceSpan)) - { - locationInAttributeSyntax = location; - } - } - - if (locationInAttributeSyntax is null) - { - methods.Add(( - null!, - (IMethodSymbol)member, - 0, - member.CreateDiagnostic( - GeneratorDiagnostics.MethodNotDeclaredInAttributedInterface, - member.ToDisplayString(), - interfaceData.Symbol.ToDisplayString()))); - } - else - { - var syntax = (MethodDeclarationSyntax)interfaceData.Syntax.FindNode(locationInAttributeSyntax.SourceSpan); - var method = (IMethodSymbol)member; - Diagnostic? diagnostic = GetDiagnosticIfInvalidMethodForGeneration(syntax, method); - methods.Add((syntax, method, diagnostic is null ? methodVtableOffset++ : 0, diagnostic)); - } - } - } - return (Interface: interfaceContext, Methods: methods.ToImmutable()); - }); - - var interfaceWithMethodsContexts = interfacesWithMethods - .Where(data => data.Methods.Length > 0) - .Select(static (data, ct) => data.Interface); - - // Marker interfaces are COM interfaces that don't have any methods. - // The lack of methods breaks the mechanism we use later to stitch back together interface-level data - // and method-level data, but that's okay because marker interfaces are much simpler. - // We'll handle them seperately because they are so simple. - var markerInterfaces = interfacesWithMethods - .Where(data => data.Methods.Length == 0) - .Select(static (data, ct) => data.Interface); - - var markerInterfaceIUnknownDerived = markerInterfaces.Select(static (context, ct) => GenerateIUnknownDerivedAttributeApplication(context)) - .WithComparer(SyntaxEquivalentComparer.Instance) - .SelectNormalized(); - - context.RegisterSourceOutput(markerInterfaces.Zip(markerInterfaceIUnknownDerived), (context, data) => - { - var (interfaceContext, iUnknownDerivedAttributeApplication) = data; - context.AddSource( - interfaceContext.InterfaceType.FullTypeName.Replace("global::", ""), - GenerateMarkerInterfaceSource(interfaceContext) + iUnknownDerivedAttributeApplication); - }); - - var methodsWithDiagnostics = interfacesWithMethods.SelectMany(static (data, ct) => data.Methods); - - // Split the methods we want to generate and the ones we don't into two separate groups. - var methodsToGenerate = methodsWithDiagnostics.Where(static data => data.Diagnostic is null); - var invalidMethodDiagnostics = methodsWithDiagnostics.Where(static data => data.Diagnostic is not null); - - context.RegisterSourceOutput(invalidMethodDiagnostics, static (context, invalidMethod) => - { - context.ReportDiagnostic(invalidMethod.Diagnostic); - }); + return ComMethodContext.CalculateAllMethods(data, ct); + }); - // Calculate all of information to generate both managed-to-unmanaged and unmanaged-to-managed stubs - // for each method. - IncrementalValuesProvider generateStubInformation = methodsToGenerate + // A dictionary isn't incremental, but it will have symbols, so it will never be incremental anyway. + var methodInfoToSymbolMap = methodInfoAndSymbolGroupedByInterface + .SelectMany((data, ct) => data) + .Collect() + .Select((data, ct) => data.ToDictionary(static x => x.ComMethod, static x => x.Symbol)); + var comMethodContexts = comMethodContextBuilders + .Combine(methodInfoToSymbolMap) .Combine(context.CreateStubEnvironmentProvider()) - .Select(static (data, ct) => new + .Select((param, ct) => { - data.Left.Syntax, - data.Left.Symbol, - data.Left.Index, - Environment = data.Right - }) - .Select( - static (data, ct) => CalculateStubInformation(data.Syntax, data.Symbol, data.Index, data.Environment, ct) - ) - .WithTrackingName(StepNames.CalculateStubInformation); + var ((data, symbolMap), env) = param; + return new ComMethodContext( + data.Method.OriginalDeclaringInterface, + data.TypeKeyOwner, + data.Method.MethodInfo, + data.Method.Index, + CalculateStubInformation(data.Method.MethodInfo.Syntax, symbolMap[data.Method.MethodInfo], data.Method.Index, env, data.TypeKeyOwner.Info.Type, ct)); + }).WithTrackingName(StepNames.CalculateStubInformation); + + var interfaceAndMethodsContexts = comMethodContexts + .Collect() + .Combine(interfaceContexts.Collect()) + .SelectMany((data, ct) => GroupComContextsForInterfaceGeneration(data.Left, data.Right, ct)); // Generate the code for the managed-to-unmanaged stubs and the diagnostics from code-generation. - var generateManagedToNativeStub = generateStubInformation - .Select( - static (data, ct) => - { - if (data.VtableIndexData.Direction is not (MarshalDirection.ManagedToUnmanaged or MarshalDirection.Bidirectional)) - { - return (GeneratedMethodContextBase)new SkippedStubContext(data.OriginalDefiningType); - } - var (methodStub, diagnostics) = VirtualMethodPointerStubGenerator.GenerateManagedToNativeStub(data); - return new GeneratedStubCodeContext(data.TypeKeyOwner, data.ContainingSyntaxContext, new(methodStub), new(diagnostics)); - } - ) - .WithTrackingName(StepNames.GenerateManagedToNativeStub); - - context.RegisterDiagnostics(generateManagedToNativeStub.SelectMany((stubInfo, ct) => stubInfo.Diagnostics.Array)); - - var managedToNativeInterfaceImplementations = generateManagedToNativeStub - .Collect() - .SelectMany(static (stubs, ct) => GroupContextsForInterfaceGeneration(stubs)) - .Select(static (interfaceGroup, ct) => GenerateImplementationInterface(interfaceGroup.Array)) + context.RegisterDiagnostics(interfaceAndMethodsContexts + .SelectMany((data, ct) => data.DeclaredMethods.SelectMany(m => m.GetManagedToUnmanagedStub().Diagnostics))); + var managedToNativeInterfaceImplementations = interfaceAndMethodsContexts + .Select(GenerateImplementationInterface) .WithTrackingName(StepNames.GenerateManagedToNativeInterfaceImplementation) .WithComparer(SyntaxEquivalentComparer.Instance) .SelectNormalized(); - // Filter the list of all stubs to only the stubs that requested unmanaged-to-managed stub generation. - IncrementalValuesProvider nativeToManagedStubContexts = - generateStubInformation - .Where(static data => data.VtableIndexData.Direction is MarshalDirection.UnmanagedToManaged or MarshalDirection.Bidirectional); - // Generate the code for the unmanaged-to-managed stubs and the diagnostics from code-generation. - var generateNativeToManagedStub = generateStubInformation - .Select( - static (data, ct) => - { - if (data.VtableIndexData.Direction is not (MarshalDirection.UnmanagedToManaged or MarshalDirection.Bidirectional)) - { - return (GeneratedMethodContextBase)new SkippedStubContext(data.OriginalDefiningType); - } - var (methodStub, diagnostics) = VirtualMethodPointerStubGenerator.GenerateNativeToManagedStub(data); - return new GeneratedStubCodeContext(data.OriginalDefiningType, data.ContainingSyntaxContext, new(methodStub), new(diagnostics)); - } - ) - .WithTrackingName(StepNames.GenerateNativeToManagedStub); - - context.RegisterDiagnostics(generateNativeToManagedStub.SelectMany((stubInfo, ct) => stubInfo.Diagnostics.Array)); - - var nativeToManagedVtableMethods = generateNativeToManagedStub - .Collect() - .SelectMany(static (stubs, ct) => GroupContextsForInterfaceGeneration(stubs)) - .Select(static (interfaceGroup, ct) => GenerateImplementationVTableMethods(interfaceGroup.Array)) + context.RegisterDiagnostics(interfaceAndMethodsContexts + .SelectMany((data, ct) => data.DeclaredMethods.SelectMany(m => m.GetNativeToManagedStub().Diagnostics))); + var nativeToManagedVtableMethods = interfaceAndMethodsContexts + .Select(GenerateImplementationVTableMethods) .WithTrackingName(StepNames.GenerateNativeToManagedVTableMethods) .WithComparer(SyntaxEquivalentComparer.Instance) .SelectNormalized(); // Generate the native interface metadata for each [GeneratedComInterface]-attributed interface. - var nativeInterfaceInformation = interfaceWithMethodsContexts - .Select(static (context, ct) => GenerateInterfaceInformation(context)) + var nativeInterfaceInformation = interfaceContexts + .Select(static (data, ct) => data.Info) + .Select(GenerateInterfaceInformation) .WithTrackingName(StepNames.GenerateInterfaceInformation) .WithComparer(SyntaxEquivalentComparer.Instance) .SelectNormalized(); + var shadowingMethods = interfaceAndMethodsContexts + .Select((data, ct) => + { + var context = data.Interface.Info; + var methods = data.ShadowingMethods.Select(m => (MemberDeclarationSyntax)m.GenerateShadow()); + var typeDecl = TypeDeclaration(context.ContainingSyntax.TypeKind, context.ContainingSyntax.Identifier) + .WithModifiers(context.ContainingSyntax.Modifiers) + .WithTypeParameterList(context.ContainingSyntax.TypeParameters) + .WithMembers(List(methods)); + return data.Interface.Info.TypeDefinitionContext.WrapMemberInContainingSyntaxWithUnsafeModifier(typeDecl); + }) + .WithTrackingName(StepNames.GenerateShadowingMethods) + .WithComparer(SyntaxEquivalentComparer.Instance) + .SelectNormalized(); + // Generate a method named CreateManagedVirtualFunctionTable on the native interface implementation // that allocates and fills in the memory for the vtable. - var nativeToManagedVtables = - generateStubInformation - .Collect() - .SelectMany(static (data, ct) => GroupContextsForInterfaceGeneration(data.CastArray())) - .Zip(interfaceBaseInfo.Where(static info => !info.IsMarkerInterface)) - .Select(static (data, ct) => GenerateImplementationVTable(ImmutableArray.CreateRange(data.Left.Array.Cast()), data.Right)) + var nativeToManagedVtables = interfaceAndMethodsContexts + .Select(GenerateImplementationVTable) .WithTrackingName(StepNames.GenerateNativeToManagedVTable) .WithComparer(SyntaxEquivalentComparer.Instance) .SelectNormalized(); - var iUnknownDerivedAttributeApplication = interfaceWithMethodsContexts - .Select(static (context, ct) => GenerateIUnknownDerivedAttributeApplication(context)) + var iUnknownDerivedAttributeApplication = interfaceContexts + .Select(static (data, ct) => data.Info) + .Select(GenerateIUnknownDerivedAttributeApplication) .WithTrackingName(StepNames.GenerateIUnknownDerivedAttribute) .WithComparer(SyntaxEquivalentComparer.Instance) .SelectNormalized(); - var filesToGenerate = interfaceWithMethodsContexts + var filesToGenerate = interfaceContexts .Zip(nativeInterfaceInformation) .Zip(managedToNativeInterfaceImplementations) .Zip(nativeToManagedVtableMethods) .Zip(nativeToManagedVtables) .Zip(iUnknownDerivedAttributeApplication) + .Zip(shadowingMethods) .Select(static (data, ct) => { - var (((((interfaceContext, interfaceInfo), managedToNativeStubs), nativeToManagedStubs), nativeToManagedVtable), iUnknownDerivedAttribute) = data; + var ((((((interfaceContext, interfaceInfo), managedToNativeStubs), nativeToManagedStubs), nativeToManagedVtable), iUnknownDerivedAttribute), shadowingMethod) = data; using StringWriter source = new(); interfaceInfo.WriteTo(source); @@ -353,7 +198,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) source.WriteLine(); source.WriteLine(); iUnknownDerivedAttribute.WriteTo(source); - return new { TypeName = interfaceContext.InterfaceType.FullTypeName, Source = source.ToString() }; + source.WriteLine(); + source.WriteLine(); + shadowingMethod.WriteTo(source); + return new { TypeName = interfaceContext.Info.Type.FullTypeName, Source = source.ToString() }; }); context.RegisterSourceOutput(filesToGenerate, (context, data) => @@ -362,7 +210,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) }); } - private static string GenerateMarkerInterfaceSource(ComInterfaceContext iface) => $$""" + private static string GenerateMarkerInterfaceSource(ComInterfaceInfo iface) => $$""" file unsafe class InterfaceInformation : global::System.Runtime.InteropServices.Marshalling.IIUnknownInterfaceType { public static global::System.Guid Iid => new(new global::System.ReadOnlySpan(new byte[] { {{string.Join(",", iface.InterfaceId.ToByteArray())}} })); @@ -375,7 +223,7 @@ public static void** ManagedVirtualMethodTable { if (m_vtable == null) { - nint* vtable = (nint*)global::System.Runtime.CompilerServices.RuntimeHelpers.AllocateTypeAssociatedMemory(typeof({{iface.InterfaceType.FullTypeName}}), sizeof(nint) * 3); + nint* vtable = (nint*)global::System.Runtime.CompilerServices.RuntimeHelpers.AllocateTypeAssociatedMemory(typeof({{iface.Type.FullTypeName}}), sizeof(nint) * 3); global::System.Runtime.InteropServices.ComWrappers.GetIUnknownImpl(out vtable[0], out vtable[1], out vtable[2]); m_vtable = (void**)vtable; } @@ -385,7 +233,7 @@ public static void** ManagedVirtualMethodTable } [global::System.Runtime.InteropServices.DynamicInterfaceCastableImplementation] - file interface InterfaceImplementation : {{iface.InterfaceType.FullTypeName}} + file interface InterfaceImplementation : {{iface.Type.FullTypeName}} {} """; @@ -396,14 +244,15 @@ public static void** ManagedVirtualMethodTable IdentifierName("InterfaceInformation"), IdentifierName("InterfaceImplementation"))); - private static MemberDeclarationSyntax GenerateIUnknownDerivedAttributeApplication(ComInterfaceContext context) + private static MemberDeclarationSyntax GenerateIUnknownDerivedAttributeApplication(ComInterfaceInfo context, CancellationToken _) => context.TypeDefinitionContext.WrapMemberInContainingSyntaxWithUnsafeModifier( - TypeDeclaration(context.InterfaceTypeSyntax.TypeKind, context.InterfaceTypeSyntax.Identifier) - .WithModifiers(context.InterfaceTypeSyntax.Modifiers) - .WithTypeParameterList(context.InterfaceTypeSyntax.TypeParameters) + TypeDeclaration(context.ContainingSyntax.TypeKind, context.ContainingSyntax.Identifier) + .WithModifiers(context.ContainingSyntax.Modifiers) + .WithTypeParameterList(context.ContainingSyntax.TypeParameters) .AddAttributeLists(AttributeList(SingletonSeparatedList(s_iUnknownDerivedAttributeTemplate)))); - private static IncrementalMethodStubGenerationContext CalculateStubInformation(MethodDeclarationSyntax syntax, IMethodSymbol symbol, int index, StubEnvironment environment, CancellationToken ct) + // Todo: extract info needed from the IMethodSymbol into MethodInfo and only pass a MethodInfo here + private static IncrementalMethodStubGenerationContext CalculateStubInformation(MethodDeclarationSyntax syntax, IMethodSymbol symbol, int index, StubEnvironment environment, ManagedTypeInfo typeKeyOwner, CancellationToken ct) { ct.ThrowIfCancellationRequested(); INamedTypeSymbol? lcidConversionAttrType = environment.Compilation.GetTypeByMetadataName(TypeNames.LCIDConversionAttribute); @@ -502,7 +351,7 @@ private static IncrementalMethodStubGenerationContext CalculateStubInformation(M unmanagedCallConvAttribute, ImmutableArray.Create(FunctionPointerUnmanagedCallingConvention(Identifier("MemberFunction")))); - var typeKeyOwner = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(symbol.ContainingType); + var declaringType = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(symbol.ContainingType); var virtualMethodIndexData = new VirtualMethodIndexData(index, ImplicitThisParameter: true, MarshalDirection.Bidirectional, true, ExceptionMarshalling.Com); @@ -511,138 +360,85 @@ private static IncrementalMethodStubGenerationContext CalculateStubInformation(M containingSyntaxContext, methodSyntaxTemplate, new MethodSignatureDiagnosticLocations(syntax), - new SequenceEqualImmutableArray(callConv, SyntaxEquivalentComparer.Instance), + callConv.ToSequenceEqualImmutableArray(SyntaxEquivalentComparer.Instance), virtualMethodIndexData, new ComExceptionMarshalling(), ComInterfaceGeneratorHelpers.CreateGeneratorFactory(environment, MarshalDirection.ManagedToUnmanaged), ComInterfaceGeneratorHelpers.CreateGeneratorFactory(environment, MarshalDirection.UnmanagedToManaged), typeKeyOwner, - new SequenceEqualImmutableArray(generatorDiagnostics.Diagnostics.ToImmutableArray()), + declaringType, + generatorDiagnostics.Diagnostics.ToSequenceEqualImmutableArray(), ComInterfaceDispatchMarshallingInfo.Instance); } - private static Diagnostic? GetDiagnosticIfInvalidTypeForGeneration(InterfaceDeclarationSyntax syntax, INamedTypeSymbol type) - { - // Verify the method has no generic types or defined implementation - // and is not marked static or sealed - if (syntax.TypeParameterList is not null) - { - return Diagnostic.Create(GeneratorDiagnostics.InvalidAttributedMethodSignature, syntax.Identifier.GetLocation(), type.Name); - } - - // Verify that the types the method is declared in are marked partial. - for (SyntaxNode? parentNode = syntax.Parent; parentNode is TypeDeclarationSyntax typeDecl; parentNode = parentNode.Parent) - { - if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) - { - return Diagnostic.Create(GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers, syntax.Identifier.GetLocation(), type.Name, typeDecl.Identifier); - } - } - var guidAttr = type.GetAttributes().Where(attr => attr.AttributeClass.ToDisplayString() == TypeNames.System_Runtime_InteropServices_GuidAttribute).SingleOrDefault(); - var interfaceTypeAttr = type.GetAttributes().Where(attr => attr.AttributeClass.ToDisplayString() == TypeNames.InterfaceTypeAttribute).SingleOrDefault(); - // Assume interfaceType is IUnknown for now - if (interfaceTypeAttr is not null - && (guidAttr is null - || guidAttr.ConstructorArguments.SingleOrDefault().Value as string is null)) - { - return Diagnostic.Create(GeneratorDiagnostics.InvalidAttributedInterfaceMissingGuidAttribute, syntax.Identifier.GetLocation(), type.ToDisplayString()); - // Missing Guid - } - - // Error if more than one GeneratedComInterface base interface type. - INamedTypeSymbol? baseInterface = null; - foreach (var implemented in type.Interfaces) - { - if (implemented.GetAttributes().Any(static attr => attr.AttributeClass?.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute)) - { - if (baseInterface is not null) - { - return Diagnostic.Create(GeneratorDiagnostics.MultipleComInterfaceBaseTypes, syntax.Identifier.GetLocation(), type.ToDisplayString()); - } - baseInterface = implemented; - } - } - - return null; - } - - private static Diagnostic? GetDiagnosticIfInvalidMethodForGeneration(MethodDeclarationSyntax syntax, IMethodSymbol method) - { - // Verify the method has no generic types or defined implementation - // and is not marked static or sealed - if (syntax.TypeParameterList is not null - || syntax.Body is not null - || syntax.Modifiers.Any(SyntaxKind.SealedKeyword)) - { - return Diagnostic.Create(GeneratorDiagnostics.InvalidAttributedMethodSignature, syntax.Identifier.GetLocation(), method.Name); - } - - // Verify the method does not have a ref return - if (method.ReturnsByRef || method.ReturnsByRefReadonly) - { - return Diagnostic.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, syntax.Identifier.GetLocation(), "ref return", method.ToDisplayString()); - } - - return null; - } - - private static ImmutableArray> GroupContextsForInterfaceGeneration(ImmutableArray contexts) + private static ImmutableArray GroupComContextsForInterfaceGeneration(ImmutableArray methods, ImmutableArray interfaces, CancellationToken ct) { + ct.ThrowIfCancellationRequested(); // We can end up with an empty set of contexts here as the compiler will call a SelectMany // after a Collect with no input entries - if (contexts.IsEmpty) + if (interfaces.IsEmpty) { - return ImmutableArray>.Empty; + return ImmutableArray.Empty; } - ImmutableArray>.Builder allGroupsBuilder = ImmutableArray.CreateBuilder>(); - // Due to how the source generator driver processes the input item tables and our limitation that methods on COM interfaces can only be defined in a single partial definition of the type, - // we can guarantee that the method contexts are ordered as follows: + // we can guarantee that, if the interface contexts are in order of I1, I2, I3, I4..., then then method contexts are ordered as follows: // - I1.M1 // - I1.M2 // - I1.M3 // - I2.M1 // - I2.M2 // - I2.M3 - // - I3.M1 + // - I4.M1 (I3 had no methods) // - etc... // This enable us to group our contexts by their containing syntax rather simply. - ManagedTypeInfo? lastSeenDefiningType = null; - ImmutableArray.Builder groupBuilder = ImmutableArray.CreateBuilder(); - foreach (var context in contexts) + var contextList = ImmutableArray.CreateBuilder(); + int methodIndex = 0; + foreach (var iface in interfaces) { - if (lastSeenDefiningType is null || lastSeenDefiningType == context.OriginalDefiningType) - { - groupBuilder.Add(context); - } - else + var methodList = ImmutableArray.CreateBuilder(); + while (methodIndex < methods.Length && methods[methodIndex].OwningInterface == iface) { - allGroupsBuilder.Add(new(groupBuilder.ToImmutable())); - groupBuilder.Clear(); - groupBuilder.Add(context); + methodList.Add(methods[methodIndex++]); } - lastSeenDefiningType = context.OriginalDefiningType; + contextList.Add(new(iface, methodList.ToImmutable().ToSequenceEqual())); } - - allGroupsBuilder.Add(new(groupBuilder.ToImmutable())); - return allGroupsBuilder.ToImmutable(); + return contextList.ToImmutable(); } private static readonly InterfaceDeclarationSyntax ImplementationInterfaceTemplate = InterfaceDeclaration("InterfaceImplementation") .WithModifiers(TokenList(Token(SyntaxKind.FileKeyword), Token(SyntaxKind.UnsafeKeyword), Token(SyntaxKind.PartialKeyword))); - private static InterfaceDeclarationSyntax GenerateImplementationInterface(ImmutableArray interfaceGroup) + + private static InterfaceDeclarationSyntax GenerateImplementationInterface(ComInterfaceAndMethodsContext interfaceGroup, CancellationToken _) { - var definingType = interfaceGroup[0].OriginalDefiningType; + var definingType = interfaceGroup.Interface.Info.Type; + var shadowImplementations = interfaceGroup.ShadowingMethods.Select(m => (Method: m, ManagedToUnmanagedStub: m.GetManagedToUnmanagedStub())) + .Where(p => p.ManagedToUnmanagedStub is GeneratedStubCodeContext) + .Select(ctx => ((GeneratedStubCodeContext)ctx.ManagedToUnmanagedStub).Stub.Node + .WithExplicitInterfaceSpecifier( + ExplicitInterfaceSpecifier(ParseName(definingType.FullTypeName)))); + var inheritedStubs = interfaceGroup.ShadowingMethods.Select(m => m.GenerateUnreachableExceptionStub()); return ImplementationInterfaceTemplate .AddBaseListTypes(SimpleBaseType(definingType.Syntax)) - .WithMembers(List(interfaceGroup.OfType().Select(context => context.Stub.Node))) + .WithMembers( + List( + interfaceGroup.DeclaredMethods + .Select(m => m.GetManagedToUnmanagedStub()) + .OfType() + .Select(ctx => ctx.Stub.Node) + .Concat(shadowImplementations) + .Concat(inheritedStubs))) .AddAttributeLists(AttributeList(SingletonSeparatedList(Attribute(ParseName(TypeNames.System_Runtime_InteropServices_DynamicInterfaceCastableImplementationAttribute))))); } - private static InterfaceDeclarationSyntax GenerateImplementationVTableMethods(ImmutableArray interfaceGroup) + private static InterfaceDeclarationSyntax GenerateImplementationVTableMethods(ComInterfaceAndMethodsContext comInterfaceAndMethods, CancellationToken _) { return ImplementationInterfaceTemplate - .WithMembers(List(interfaceGroup.OfType().Select(context => context.Stub.Node))); + .WithMembers( + List( + comInterfaceAndMethods.DeclaredMethods + .Select(m => m.GetNativeToManagedStub()) + .OfType() + .Select(context => context.Stub.Node))); } private static readonly TypeSyntax VoidStarStarSyntax = PointerType(PointerType(PredefinedType(Token(SyntaxKind.VoidKeyword)))); @@ -651,26 +447,11 @@ private static InterfaceDeclarationSyntax GenerateImplementationVTableMethods(Im private static readonly MethodDeclarationSyntax CreateManagedVirtualFunctionTableMethodTemplate = MethodDeclaration(VoidStarStarSyntax, CreateManagedVirtualFunctionTableMethodName) .AddModifiers(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword)); - private static InterfaceDeclarationSyntax GenerateImplementationVTable(ImmutableArray interfaceMethodStubs, (int StartingOffset, ManagedTypeInfo? BaseInterface, bool) baseInterfaceTypeInfo) + private static InterfaceDeclarationSyntax GenerateImplementationVTable(ComInterfaceAndMethodsContext interfaceMethods, CancellationToken _) { const string vtableLocalName = "vtable"; - var interfaceType = interfaceMethodStubs[0].OriginalDefiningType; - - ImmutableArray vtableExposedContexts = interfaceMethodStubs - .Where(c => c.VtableIndexData.Direction is MarshalDirection.UnmanagedToManaged or MarshalDirection.Bidirectional) - .ToImmutableArray(); - - // If none of the methods are exposed as part of the vtable, then don't emit - // a vtable (return null). - if (vtableExposedContexts.Length == 0) - { - return ImplementationInterfaceTemplate - .AddMembers( - CreateManagedVirtualFunctionTableMethodTemplate - .WithBody( - Block( - ReturnStatement(LiteralExpression(SyntaxKind.NullLiteralExpression))))); - } + var interfaceType = interfaceMethods.Interface.Info.Type; + var interfaceMethodStubs = interfaceMethods.DeclaredMethods.Select(m => m.GenerationContext); // void** vtable = (void**)RuntimeHelpers.AllocateTypeAssociatedMemory(, sizeof(void*) * ); var vtableDeclarationStatement = @@ -692,11 +473,12 @@ private static InterfaceDeclarationSyntax GenerateImplementationVTable(Immutable BinaryExpression( SyntaxKind.MultiplyExpression, SizeOfExpression(PointerType(PredefinedType(Token(SyntaxKind.VoidKeyword)))), - LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(1 + interfaceMethodStubs.Max(x => x.VtableIndexData.Index)))))))))))); + LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(3 + interfaceMethods.Methods.Length))))))))))); BlockSyntax fillBaseInterfaceSlots; - if (baseInterfaceTypeInfo.BaseInterface is null) + + if (interfaceMethods.Interface.Base is null) { // If we don't have a base interface, we need to manually fill in the base iUnknown slots. fillBaseInterfaceSlots = Block() @@ -798,7 +580,7 @@ private static InterfaceDeclarationSyntax GenerateImplementationVTable(Immutable MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, TypeOfExpression( - ParseTypeName(baseInterfaceTypeInfo.BaseInterface.FullTypeName)), + ParseTypeName(interfaceMethods.Interface.Base.Info.Type.FullTypeName)), //baseInterfaceTypeInfo.BaseInterface.FullTypeName)), IdentifierName("TypeHandle")))))), IdentifierName("ManagedVirtualMethodTable"))), Argument(IdentifierName(vtableLocalName)), @@ -806,7 +588,7 @@ private static InterfaceDeclarationSyntax GenerateImplementationVTable(Immutable ParenthesizedExpression( BinaryExpression(SyntaxKind.MultiplyExpression, SizeOfExpression(PointerType(PredefinedType(Token(SyntaxKind.VoidKeyword)))), - LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(baseInterfaceTypeInfo.StartingOffset)))))) + LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(interfaceMethods.ShadowingMethods.Count() + 3)))))) }))))); } @@ -828,7 +610,7 @@ private static InterfaceDeclarationSyntax GenerateImplementationVTable(Immutable .AddModifiers(Token(SyntaxKind.FileKeyword), Token(SyntaxKind.UnsafeKeyword)) .AddBaseListTypes(SimpleBaseType(ParseTypeName(TypeNames.IIUnknownInterfaceType))); - private static ClassDeclarationSyntax GenerateInterfaceInformation(ComInterfaceContext context) + private static ClassDeclarationSyntax GenerateInterfaceInformation(ComInterfaceInfo context, CancellationToken _) { const string vtableFieldName = "_vtable"; return InterfaceInformationTypeTemplate diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs new file mode 100644 index 0000000000000..cd747bc6dc1c2 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslyn.Utilities; + +namespace Microsoft.Interop +{ + public sealed partial class ComInterfaceGenerator + { + /// + /// Information about a Com interface, but not its methods. + /// + private sealed record ComInterfaceInfo( + ManagedTypeInfo Type, + string ThisInterfaceKey, // For associating interfaces to its base + string? BaseInterfaceKey, // For associating interfaces to its base + InterfaceDeclarationSyntax Declaration, + ContainingSyntaxContext TypeDefinitionContext, + ContainingSyntax ContainingSyntax, + Guid InterfaceId) + { + public static (ComInterfaceInfo? Info, Diagnostic? Diagnostic) From(INamedTypeSymbol symbol, InterfaceDeclarationSyntax syntax) + { + // Verify the method has no generic types or defined implementation + // and is not marked static or sealed + if (syntax.TypeParameterList is not null) + { + return (null, Diagnostic.Create( + GeneratorDiagnostics.InvalidAttributedMethodSignature, + syntax.Identifier.GetLocation(), + symbol.Name)); + } + + // Verify that the types the method is declared in are marked partial. + for (SyntaxNode? parentNode = syntax.Parent; parentNode is TypeDeclarationSyntax typeDecl; parentNode = parentNode.Parent) + { + if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + return (null, Diagnostic.Create( + GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers, + syntax.Identifier.GetLocation(), + symbol.Name, + typeDecl.Identifier)); + } + } + + if (!TryGetGuid(symbol, syntax, out Guid? guid, out Diagnostic? guidDiagnostic)) + return (null, guidDiagnostic); + + if (!TryGetBaseComInterface(symbol, syntax, out INamedTypeSymbol? baseSymbol, out Diagnostic? baseDiagnostic)) + return (null, baseDiagnostic); + + return (new ComInterfaceInfo( + ManagedTypeInfo.CreateTypeInfoForTypeSymbol(symbol), + symbol.ToDisplayString(), + baseSymbol?.ToDisplayString(), + syntax, + new ContainingSyntaxContext(syntax), + new ContainingSyntax(syntax.Modifiers, syntax.Kind(), syntax.Identifier, syntax.TypeParameterList), + guid ?? Guid.Empty), null); + } + + /// + /// Returns true if there is 0 or 1 base Com interfaces (i.e. the inheritance is valid), and returns false when there are 2 or more base Com interfaces and sets . + /// + private static bool TryGetBaseComInterface(INamedTypeSymbol comIface, InterfaceDeclarationSyntax syntax, out INamedTypeSymbol? baseComIface, [NotNullWhen(false)] out Diagnostic? diagnostic) + { + baseComIface = null; + foreach (var implemented in comIface.Interfaces) + { + foreach (var attr in implemented.GetAttributes()) + { + if (attr.AttributeClass?.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute) + { + if (baseComIface is not null) + { + diagnostic = Diagnostic.Create( + GeneratorDiagnostics.MultipleComInterfaceBaseTypes, + syntax.Identifier.GetLocation(), + comIface.ToDisplayString()); + return false; + } + baseComIface = implemented; + } + } + } + diagnostic = null; + return true; + } + + /// + /// Returns true and sets if the guid is present. Returns false and sets diagnostic if the guid is not present or is invalid. + /// + private static bool TryGetGuid(INamedTypeSymbol interfaceSymbol, InterfaceDeclarationSyntax syntax, [NotNullWhen(true)] out Guid? guid, [NotNullWhen(false)] out Diagnostic? diagnostic) + { + guid = null; + AttributeData? guidAttr = null; + AttributeData? _ = null; // Interface Attribute Type. We'll always assume IUnkown for now. + foreach (var attr in interfaceSymbol.GetAttributes()) + { + var attrDisplayString = attr.AttributeClass?.ToDisplayString(); + if (attrDisplayString is TypeNames.System_Runtime_InteropServices_GuidAttribute) + guidAttr = attr; + else if (attrDisplayString is TypeNames.InterfaceTypeAttribute) + _ = attr; + } + + if (guidAttr is not null + && guidAttr.ConstructorArguments.Length == 1 + && guidAttr.ConstructorArguments[0].Value is string guidStr + && Guid.TryParse(guidStr, out var result)) + { + guid = result; + } + + // Assume interfaceType is IUnknown for now + if (guid is null) + { + diagnostic = Diagnostic.Create( + GeneratorDiagnostics.InvalidAttributedInterfaceMissingGuidAttribute, + syntax.Identifier.GetLocation(), + interfaceSymbol.ToDisplayString()); + return false; + } + diagnostic = null; + return true; + } + + public override int GetHashCode() + { + // ContainingSyntax and ContainingSyntaxContext do not implement GetHashCode + return HashCode.Combine(Type, TypeDefinitionContext, InterfaceId); + } + + public bool Equals(ComInterfaceInfo other) + { + // ContainingSyntax and ContainingSyntaxContext are not used in the hash code + return Type == other.Type + && TypeDefinitionContext == other.TypeDefinitionContext + && InterfaceId == other.InterfaceId; + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodContext.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodContext.cs new file mode 100644 index 0000000000000..31708cb9b8a1a --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodContext.cs @@ -0,0 +1,167 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Microsoft.Interop +{ + public sealed partial class ComInterfaceGenerator + { + /// + /// Represents a method, its declaring interface, and its index in the interface's vtable. + /// This type contains all information necessary to generate the corresponding methods in the ComInterfaceGenerator + /// + /// + /// The interface that originally declared the method in user code + /// + /// + /// The interface that this methods is being generated for (may be different that OriginalDeclaringInterface if it is an inherited method) + /// + /// The basic information about the method. + /// The index on the interface vtable that points to this method + /// + private sealed record ComMethodContext( + ComInterfaceContext OriginalDeclaringInterface, + ComInterfaceContext OwningInterface, + ComMethodInfo MethodInfo, + int Index, + IncrementalMethodStubGenerationContext GenerationContext) + { + /// + /// A partially constructed that does not have a generated for it yet. + /// can be constructed without a reference to an ISymbol, whereas the requires an ISymbol + /// + public sealed record Builder(ComInterfaceContext OriginalDeclaringInterface, ComMethodInfo MethodInfo, int Index); + + public bool IsInheritedMethod => OriginalDeclaringInterface != OwningInterface; + + public GeneratedMethodContextBase GetManagedToUnmanagedStub() + { + if (GenerationContext.VtableIndexData.Direction is not (MarshalDirection.ManagedToUnmanaged or MarshalDirection.Bidirectional)) + { + return new SkippedStubContext(OriginalDeclaringInterface.Info.Type); + } + var (methodStub, diagnostics) = VirtualMethodPointerStubGenerator.GenerateManagedToNativeStub(GenerationContext); + return new GeneratedStubCodeContext(GenerationContext.TypeKeyOwner, GenerationContext.ContainingSyntaxContext, new(methodStub), new(diagnostics)); + } + + public GeneratedMethodContextBase GetNativeToManagedStub() + { + if (GenerationContext.VtableIndexData.Direction is not (MarshalDirection.UnmanagedToManaged or MarshalDirection.Bidirectional)) + { + return new SkippedStubContext(GenerationContext.OriginalDefiningType); + } + var (methodStub, diagnostics) = VirtualMethodPointerStubGenerator.GenerateNativeToManagedStub(GenerationContext); + return new GeneratedStubCodeContext(GenerationContext.OriginalDefiningType, GenerationContext.ContainingSyntaxContext, new(methodStub), new(diagnostics)); + } + + public MethodDeclarationSyntax GenerateUnreachableExceptionStub() + { + // DeclarationCopiedFromBaseDeclaration() => throw new UnreachableException("This method should not be reached"); + return MethodInfo.Syntax + .WithModifiers(TokenList()) + .WithAttributeLists(List()) + .WithExplicitInterfaceSpecifier(ExplicitInterfaceSpecifier( + ParseName(OriginalDeclaringInterface.Info.Type.FullTypeName))) + .WithExpressionBody(ArrowExpressionClause( + ThrowExpression( + ObjectCreationExpression( + ParseTypeName(TypeNames.UnreachableException)) + .WithArgumentList(ArgumentList())))); + } + + public MethodDeclarationSyntax GenerateShadow() + { + // DeclarationCopiedFromBaseDeclaration() + // { + // return (()this).(); + // } + var forwarder = new Forwarder(); + return MethodDeclaration(GenerationContext.SignatureContext.StubReturnType, MethodInfo.MethodName) + .WithModifiers(TokenList(Token(SyntaxKind.NewKeyword))) + .WithParameterList(ParameterList(SeparatedList(GenerationContext.SignatureContext.StubParameters))) + .WithExpressionBody( + ArrowExpressionClause( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParenthesizedExpression( + CastExpression(OriginalDeclaringInterface.Info.Type.Syntax, IdentifierName("this"))), + IdentifierName(MethodInfo.MethodName)), + ArgumentList( + SeparatedList(GenerationContext.SignatureContext.ManagedParameters.Select(p => forwarder.AsArgument(p, new ManagedStubCodeContext()))))))) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); + } + + /// + /// Returns a flat list of and it's type key owner that represents all declared methods, and inherited methods. + /// Guarantees the output will be sorted by order of interface input order, then by vtable order. + /// + public static List<(ComInterfaceContext TypeKeyOwner, Builder Method)> CalculateAllMethods(IEnumerable<(ComInterfaceContext, SequenceEqualImmutableArray)> ifaceAndDeclaredMethods, CancellationToken _) + { + // Optimization : This step technically only needs a single interface inheritance hierarchy. + // We can calculate all inheritance chains in a previous step and only pass a single inheritance chain to this method. + // This way, when a single method changes, we would only need to recalculate this for the inheritance chain in which that method exists. + + var ifaceToDeclaredMethodsMap = ifaceAndDeclaredMethods.ToDictionary(static pair => pair.Item1, static pair => pair.Item2); + var allMethodsCache = new Dictionary>(); + var accumulator = new List<(ComInterfaceContext TypeKeyOwner, Builder Method)>(); + foreach (var kvp in ifaceAndDeclaredMethods) + { + var methods = AddMethods(kvp.Item1, kvp.Item2); + foreach (var method in methods) + { + accumulator.Add((kvp.Item1, method)); + } + } + return accumulator; + + /// + /// Adds methods to a cache and returns inherited and declared methods for the interface in vtable order + /// + ImmutableArray AddMethods(ComInterfaceContext iface, IEnumerable declaredMethods) + { + if (allMethodsCache.TryGetValue(iface, out var cachedValue)) + { + return cachedValue; + } + + int startingIndex = 3; + List methods = new(); + // If we have a base interface, we should add the inherited methods to our list in vtable order + if (iface.Base is not null) + { + var baseComIface = iface.Base; + ImmutableArray baseMethods; + if (!allMethodsCache.TryGetValue(baseComIface, out var pair)) + { + baseMethods = AddMethods(baseComIface, ifaceToDeclaredMethodsMap[baseComIface]); + } + else + { + baseMethods = pair; + } + methods.AddRange(baseMethods); + startingIndex += baseMethods.Length; + } + // Then we append the declared methods in vtable order + foreach (var method in declaredMethods) + { + methods.Add(new Builder(iface, method, startingIndex++)); + } + // Cache so we don't recalculate if many interfaces inherit from the same one + var imm = methods.ToImmutableArray(); + allMethodsCache[iface] = imm; + return imm; + } + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodInfo.cs new file mode 100644 index 0000000000000..38bf19a462f6a --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodInfo.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.Interop +{ + public sealed partial class ComInterfaceGenerator + { + /// + /// Represents a method that has been determined to be a COM interface method. Only contains info immediately available from an IMethodSymbol and MethodDeclarationSyntax. + /// + private sealed record ComMethodInfo( + MethodDeclarationSyntax Syntax, + string MethodName) + { + /// + /// Returns a list of tuples of ComMethodInfo, IMethodSymbol, and Diagnostic. If ComMethodInfo is null, Diagnostic will not be null, and vice versa. + /// + public static SequenceEqualImmutableArray<(ComMethodInfo? ComMethod, IMethodSymbol Symbol, Diagnostic? Diagnostic)> GetMethodsFromInterface((ComInterfaceInfo ifaceContext, INamedTypeSymbol ifaceSymbol) data, CancellationToken ct) + { + var methods = ImmutableArray.CreateBuilder<(ComMethodInfo, IMethodSymbol, Diagnostic?)>(); + foreach (var member in data.ifaceSymbol.GetMembers()) + { + if (IsComMethodCandidate(member)) + { + methods.Add(CalculateMethodInfo(data.ifaceContext, (IMethodSymbol)member, ct)); + } + } + return methods.ToImmutable().ToSequenceEqual(); + } + + private static Diagnostic? GetDiagnosticIfInvalidMethodForGeneration(MethodDeclarationSyntax comMethodDeclaringSyntax, IMethodSymbol method) + { + // Verify the method has no generic types or defined implementation + // and is not marked static or sealed + if (comMethodDeclaringSyntax.TypeParameterList is not null + || comMethodDeclaringSyntax.Body is not null + || comMethodDeclaringSyntax.Modifiers.Any(SyntaxKind.SealedKeyword)) + { + return Diagnostic.Create(GeneratorDiagnostics.InvalidAttributedMethodSignature, comMethodDeclaringSyntax.Identifier.GetLocation(), method.Name); + } + + // Verify the method does not have a ref return + if (method.ReturnsByRef || method.ReturnsByRefReadonly) + { + return Diagnostic.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, comMethodDeclaringSyntax.Identifier.GetLocation(), "ref return", method.ToDisplayString()); + } + + return null; + } + + private static bool IsComMethodCandidate(ISymbol member) + { + return member.Kind == SymbolKind.Method && !member.IsStatic; + } + + private static (ComMethodInfo?, IMethodSymbol, Diagnostic?) CalculateMethodInfo(ComInterfaceInfo ifaceContext, IMethodSymbol method, CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + Debug.Assert(IsComMethodCandidate(method)); + + // We only support methods that are defined in the same partial interface definition as the + // [GeneratedComInterface] attribute. + // This restriction not only makes finding the syntax for a given method cheaper, + // but it also enables us to ensure that we can determine vtable method order easily. + Location interfaceLocation = ifaceContext.Declaration.GetLocation(); + Location? methodLocationInAttributedInterfaceDeclaration = null; + foreach (var methodLocation in method.Locations) + { + if (methodLocation.SourceTree == interfaceLocation.SourceTree + && interfaceLocation.SourceSpan.Contains(methodLocation.SourceSpan)) + { + methodLocationInAttributedInterfaceDeclaration = methodLocation; + break; + } + } + // TODO: this should cause a diagnostic + if (methodLocationInAttributedInterfaceDeclaration is null) + { + return (null, method, Diagnostic.Create(GeneratorDiagnostics.CannotAnalyzeMethodPattern, method.Locations.FirstOrDefault(), method.ToDisplayString())); + } + + + // Find the matching declaration syntax + MethodDeclarationSyntax? comMethodDeclaringSyntax = null; + foreach (var declaringSyntaxReference in method.DeclaringSyntaxReferences) + { + var declaringSyntax = declaringSyntaxReference.GetSyntax(ct); + Debug.Assert(declaringSyntax.IsKind(SyntaxKind.MethodDeclaration)); + if (declaringSyntax.GetLocation().SourceSpan.Contains(methodLocationInAttributedInterfaceDeclaration.SourceSpan)) + { + comMethodDeclaringSyntax = (MethodDeclarationSyntax)declaringSyntax; + break; + } + } + if (comMethodDeclaringSyntax is null) + { + return (null, method, Diagnostic.Create(GeneratorDiagnostics.CannotAnalyzeMethodPattern, method.Locations.FirstOrDefault(), method.ToDisplayString())); + } + + var diag = GetDiagnosticIfInvalidMethodForGeneration(comMethodDeclaringSyntax, method); + if (diag is not null) + { + return (null, method, diag); + } + var comMethodInfo = new ComMethodInfo(comMethodDeclaringSyntax, method.Name); + return (comMethodInfo, method, null); + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs index 9df719d19d51f..36fc49267fea0 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs @@ -23,6 +23,7 @@ public class Ids public const string MethodNotDeclaredInAttributedInterface = Prefix + "1091"; public const string InvalidGeneratedComInterfaceAttributeUsage = Prefix + "1092"; public const string MultipleComInterfaceBaseTypes = Prefix + "1093"; + public const string AnalysisFailed = Prefix + "1094"; } private const string Category = "ComInterfaceGenerator"; @@ -197,6 +198,26 @@ public class Ids isEnabledByDefault: true, description: GetResourceString(nameof(SR.MultipleComInterfaceBaseTypesDescription))); + public static readonly DiagnosticDescriptor CannotAnalyzeMethodPattern = + new DiagnosticDescriptor( + Ids.AnalysisFailed, + GetResourceString(nameof(SR.AnalysisFailedTitle)), + GetResourceString(nameof(SR.AnalysisFailedMethodMessage)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: GetResourceString(nameof(SR.AnalysisFailedDescription))); + + public static readonly DiagnosticDescriptor CannotAnalyzeInterfacePattern = + new DiagnosticDescriptor( + Ids.AnalysisFailed, + GetResourceString(nameof(SR.AnalysisFailedTitle)), + GetResourceString(nameof(SR.AnalysisFailedInterfaceMessage)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: GetResourceString(nameof(SR.AnalysisFailedDescription))); + private readonly List _diagnostics = new List(); public IEnumerable Diagnostics => _diagnostics; diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/IncrementalMethodStubGenerationContext.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/IncrementalMethodStubGenerationContext.cs index 7cd5842ba47c7..ff3d6f32d5f23 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/IncrementalMethodStubGenerationContext.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/IncrementalMethodStubGenerationContext.cs @@ -21,6 +21,7 @@ internal sealed record IncrementalMethodStubGenerationContext( MarshallingGeneratorFactoryKey<(TargetFramework TargetFramework, Version TargetFrameworkVersion)> ManagedToUnmanagedGeneratorFactory, MarshallingGeneratorFactoryKey<(TargetFramework TargetFramework, Version TargetFrameworkVersion)> UnmanagedToManagedGeneratorFactory, ManagedTypeInfo TypeKeyOwner, + ManagedTypeInfo DeclaringType, SequenceEqualImmutableArray Diagnostics, - MarshallingInfo ManagedThisMarshallingInfo) : GeneratedMethodContextBase(TypeKeyOwner, Diagnostics); + MarshallingInfo ManagedThisMarshallingInfo) : GeneratedMethodContextBase(DeclaringType, Diagnostics); } diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ManagedStubCodeContext.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ManagedStubCodeContext.cs new file mode 100644 index 0000000000000..d4a83ca851f0c --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ManagedStubCodeContext.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Interop +{ + /// + /// Stub code context for generating code that does not cross a native/managed boundary + /// + internal sealed record ManagedStubCodeContext : StubCodeContext + { + public override bool SingleFrameSpansNativeContext => throw new NotImplementedException(); + + public override bool AdditionalTemporaryStateLivesAcrossStages => throw new NotImplementedException(); + + public override (TargetFramework framework, Version version) GetTargetFramework() => throw new NotImplementedException(); + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/Strings.resx b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/Strings.resx index 0d9b0b6631e08..9c0cc44426487 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/Strings.resx +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/Strings.resx @@ -237,4 +237,16 @@ Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + Analysis for generation has failed. + \ No newline at end of file diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf index 04697d456efdf..241afaa3a4af3 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. Zdrojem generovaná volání P/Invokes budou ignorovat všechny nepodporované konfigurace. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.de.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.de.xlf index 9d06e7ad05992..37cda7bfd599b 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.de.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.de.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. Quellgenerierte P/Invokes ignorieren alle Konfigurationen, die nicht unterstützt werden. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.es.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.es.xlf index a169ab705ea4d..71b79c45dd520 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.es.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.es.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. Los P/Invoke de un generador de código fuente omitirán cualquier configuración que no esté admitida. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.fr.xlf index f8fd5f71c42c4..ed463398efa79 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.fr.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. Les P/Invokes générés par la source ignorent toute configuration qui n’est pas prise en charge. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.it.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.it.xlf index 45db75981a336..bf2b7f5ef0065 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.it.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.it.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. I P/Invoke generati dall'origine ignoreranno qualsiasi configurazione non supportata. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ja.xlf index 9ae0a4d6af416..c787e252ca99d 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ja.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. ソース生成済みの P/Invoke は、サポートされていない構成を無視します。 diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ko.xlf index 65ae4ad12efce..082dafc2a19f6 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ko.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. 소스 생성 P/Invoke는 지원되지 않는 구성을 무시합니다. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pl.xlf index 6f340f9776c2c..ec95453294f1e 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pl.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. Funkcja P/Invokes generowana przez źródło zignoruje każdą nieobsługiwaną konfigurację. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pt-BR.xlf index c12ce4422ba38..03693164c2aab 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pt-BR.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. P/Invokes gerados pela origem ignorarão qualquer configuração sem suporte. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ru.xlf index 21eefbe895001..83fef8b4d6caa 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ru.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. P/Invoke с созданием источника будут игнорировать все неподдерживаемые конфигурации. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.tr.xlf index 70597a22a7bf5..cefac4ef490f4 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.tr.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. Kaynak tarafından oluşturulan P/Invokes desteklenmeyen yapılandırmaları yok sayar. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hans.xlf index 73f74d04f4175..b8b3951366925 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hans.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. 源生成的 P/Invoke 将忽略任何不受支持的配置。 diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hant.xlf index 6ca68e2bc3414..83558aa554498 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hant.xlf @@ -2,6 +2,26 @@ + + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + The analysis required to generate code for this interface or method has failed due to an unexpected code pattern. If you are using new or unconventional syntax, consider using other syntax. + + + + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + Analysis of interface '{0}' has failed. ComInterfaceGenerator will not generate code for this interface. + + + + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + Analysis of method '{0}' has failed. ComInterfaceGenerator will not generate code for this method. + + + + Analysis for generation has failed. + Analysis for generation has failed. + + Source-generated P/Invokes will ignore any configuration that is not supported. 来源產生的 P/Invokes 將會忽略任何不支援的設定。 diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs index 13ee527043de0..1d161ff1d5f2b 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs @@ -312,6 +312,7 @@ private static IncrementalMethodStubGenerationContext CalculateStubInformation(M VtableIndexStubGeneratorHelpers.CreateGeneratorFactory(environment, MarshalDirection.ManagedToUnmanaged), VtableIndexStubGeneratorHelpers.CreateGeneratorFactory(environment, MarshalDirection.UnmanagedToManaged), interfaceType, + interfaceType, new SequenceEqualImmutableArray(generatorDiagnostics.Diagnostics.ToImmutableArray()), new ObjectUnwrapperInfo(unwrapperSyntax)); } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/HashCode.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/HashCode.cs new file mode 100644 index 0000000000000..1fce3fc765a53 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/HashCode.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; +using Roslyn.Utilities; + +namespace Microsoft.Interop +{ + /// + /// Exposes the hashing utilities from Roslyn + /// + public class HashCode + { + public static int Combine(T1 t1, T2 t2) + { + return Hash.Combine(t1 != null ? t1.GetHashCode() : 0, t2 != null ? t2.GetHashCode() : 0); + } + + public static int Combine(T1 t1, T2 t2, T3 t3) + { + int combinedHash = t1 != null ? t1.GetHashCode() : 0; + combinedHash = Hash.Combine(combinedHash, t2 != null ? t2.GetHashCode() : 0); + return Hash.Combine(combinedHash, t3 != null ? t3.GetHashCode() : 0); + } + + public static int Combine(T1 t1, T2 t2, T3 t3, T4 t4) + { + int combinedHash = t1 != null ? t1.GetHashCode() : 0; + combinedHash = Hash.Combine(combinedHash, t2 != null ? t2.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t3 != null ? t3.GetHashCode() : 0); + return Hash.Combine(combinedHash, t4 != null ? t4.GetHashCode() : 0); + } + + public static int Combine(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) + { + int combinedHash = t1 != null ? t1.GetHashCode() : 0; + combinedHash = Hash.Combine(combinedHash, t2 != null ? t2.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t3 != null ? t3.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t4 != null ? t4.GetHashCode() : 0); + return Hash.Combine(combinedHash, t5 != null ? t5.GetHashCode() : 0); + } + + public static int Combine(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) + { + int combinedHash = t1 != null ? t1.GetHashCode() : 0; + combinedHash = Hash.Combine(combinedHash, t2 != null ? t2.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t3 != null ? t3.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t4 != null ? t4.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t5 != null ? t5.GetHashCode() : 0); + return Hash.Combine(combinedHash, t6 != null ? t6.GetHashCode() : 0); + } + + public static int Combine(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) + { + int combinedHash = t1 != null ? t1.GetHashCode() : 0; + combinedHash = Hash.Combine(combinedHash, t2 != null ? t2.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t3 != null ? t3.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t4 != null ? t4.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t5 != null ? t5.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t6 != null ? t6.GetHashCode() : 0); + return Hash.Combine(combinedHash, t7 != null ? t7.GetHashCode() : 0); + } + + public static int Combine(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) + { + int combinedHash = t1 != null ? t1.GetHashCode() : 0; + combinedHash = Hash.Combine(combinedHash, t2 != null ? t2.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t3 != null ? t3.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t4 != null ? t4.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t5 != null ? t5.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t6 != null ? t6.GetHashCode() : 0); + combinedHash = Hash.Combine(combinedHash, t7 != null ? t7.GetHashCode() : 0); + return Hash.Combine(combinedHash, t8 != null ? t8.GetHashCode() : 0); + } + + public static int SequentialValuesHash(IEnumerable values) + { + int hash = 0; + foreach (var value in values) + { + hash = Hash.Combine(hash, value.GetHashCode()); + } + return hash; + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs index 069daeb130fff..d0adc407312d1 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs @@ -39,7 +39,7 @@ public static IncrementalValueProvider CreateStubEnvironmentPro public static void RegisterDiagnostics(this IncrementalGeneratorInitializationContext context, IncrementalValuesProvider diagnostics) { - context.RegisterSourceOutput(diagnostics, (context, diagnostic) => + context.RegisterSourceOutput(diagnostics.Where(diag => diag is not null), (context, diagnostic) => { context.ReportDiagnostic(diagnostic); }); diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj index a85ea9e94678f..825fb6c60b8e3 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Microsoft.Interop.SourceGeneration.csproj @@ -16,6 +16,7 @@ + diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SequenceEqualImmutableArray.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SequenceEqualImmutableArray.cs index f91d7be4ea2bc..0c49cddb4cc15 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SequenceEqualImmutableArray.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SequenceEqualImmutableArray.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -14,18 +15,43 @@ namespace Microsoft.Interop /// for many scenarios. This wrapper type allows us to use s in our other record types without having to write an Equals method /// that we may forget to update if we add new elements to the record. /// - public readonly record struct SequenceEqualImmutableArray(ImmutableArray Array, IEqualityComparer Comparer) + public readonly record struct SequenceEqualImmutableArray(ImmutableArray Array, IEqualityComparer Comparer) : IEnumerable { public SequenceEqualImmutableArray(ImmutableArray array) : this(array, EqualityComparer.Default) { } + public T this[int i] { get => Array[i]; } + + public int Length => Array.Length; + public SequenceEqualImmutableArray Insert(int index, T item) + => new SequenceEqualImmutableArray(Array.Insert(index, item), Comparer); + + public override int GetHashCode() => HashCode.SequentialValuesHash(Array); + public bool Equals(SequenceEqualImmutableArray other) { return Array.SequenceEqual(other.Array, Comparer); } - public override int GetHashCode() => throw new UnreachableException(); + public IEnumerator GetEnumerator() => ((IEnumerable)Array).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Array).GetEnumerator(); + } + + public static partial class CollectionExtensions + { + public static SequenceEqualImmutableArray ToSequenceEqualImmutableArray(this IEnumerable source, IEqualityComparer comparer) + { + return new(source.ToImmutableArray(), comparer); + } + public static SequenceEqualImmutableArray ToSequenceEqualImmutableArray(this IEnumerable source) + { + return new(source.ToImmutableArray()); + } + public static SequenceEqualImmutableArray ToSequenceEqual(this ImmutableArray source) + { + return new(source); + } } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SignatureContext.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SignatureContext.cs index d5d7526c3b45b..f39f460eaef9f 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SignatureContext.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SignatureContext.cs @@ -30,6 +30,8 @@ private SignatureContext() public ImmutableArray ElementTypeInformation { get; init; } + public IEnumerable ManagedParameters => ElementTypeInformation.Where(tpi => !TypePositionInfo.IsSpecialIndex(tpi.ManagedIndex)); + public TypeSyntax StubReturnType { get; init; } public IEnumerable StubParameters diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs index 314b8197699e2..612a881091b69 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs @@ -139,6 +139,8 @@ public static string MarshalEx(InteropGenerationOptions options) public const string ComExposedClassAttribute = "System.Runtime.InteropServices.Marshalling.ComExposedClassAttribute"; public const string IComExposedClass = "System.Runtime.InteropServices.Marshalling.IComExposedClass"; + public const string UnreachableException = "System.Diagnostics.UnreachableException"; + public const string System_Runtime_InteropServices_Marshalling_SafeHandleMarshaller_Metadata = "System.Runtime.InteropServices.Marshalling.SafeHandleMarshaller`1"; } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComInterfaceTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComInterfaceTests.cs index 494f0baf88a66..9ef4284427eb6 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComInterfaceTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComInterfaceTests.cs @@ -2,15 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections; -using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using SharedTypes.ComInterfaces; using Xunit; -using Xunit.Sdk; namespace ComInterfaceGenerator.Tests; @@ -21,7 +18,7 @@ internal unsafe partial class NativeExportsNE } -public class GeneratedComInterfaceTests +public partial class GeneratedComInterfaceTests { [Fact] public unsafe void CallNativeComObjectThroughGeneratedStub() @@ -30,22 +27,22 @@ public unsafe void CallNativeComObjectThroughGeneratedStub() var cw = new StrategyBasedComWrappers(); var obj = cw.GetOrCreateObjectForComInstance((nint)ptr, CreateObjectFlags.None); - var intObj = (IComInterface1)obj; - Assert.Equal(0, intObj.GetData()); - intObj.SetData(2); - Assert.Equal(2, intObj.GetData()); + var intObj = (IGetAndSetInt)obj; + Assert.Equal(0, intObj.GetInt()); + intObj.SetInt(2); + Assert.Equal(2, intObj.GetInt()); } [Fact] public unsafe void DerivedInterfaceTypeProvidesBaseInterfaceUnmanagedToManagedMembers() { // Make sure that we have the correct derived and base types here. - Assert.Contains(typeof(IComInterface1), typeof(IDerivedComInterface).GetInterfaces()); + Assert.Contains(typeof(IGetAndSetInt), typeof(IDerivedComInterface).GetInterfaces()); - IIUnknownDerivedDetails baseInterfaceDetails = StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(IComInterface1).TypeHandle); + IIUnknownDerivedDetails baseInterfaceDetails = StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(IGetAndSetInt).TypeHandle); IIUnknownDerivedDetails derivedInterfaceDetails = StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(IDerivedComInterface).TypeHandle); - var numBaseMethods = typeof(IComInterface1).GetMethods().Length; + var numBaseMethods = typeof(IGetAndSetInt).GetMethods().Length; var numPointersToCompare = 3 + numBaseMethods; @@ -54,4 +51,62 @@ public unsafe void DerivedInterfaceTypeProvidesBaseInterfaceUnmanagedToManagedMe Assert.True(expected.SequenceEqual(actual)); } + + [Fact] + public unsafe void CallBaseInterfaceMethod_EnsureQiCalledOnce() + { + var cw = new SingleQIComWrapper(); + var derivedImpl = new DerivedImpl(); + var nativeObj = cw.GetOrCreateComInterfaceForObject(derivedImpl, CreateComInterfaceFlags.None); + var obj = cw.GetOrCreateObjectForComInstance(nativeObj, CreateObjectFlags.None); + IDerivedComInterface iface = (IDerivedComInterface)obj; + + Assert.Equal(3, iface.GetInt()); + iface.SetInt(5); + Assert.Equal(5, iface.GetInt()); + + // https://github.com/dotnet/runtime/issues/85795 + //Assert.Equal("myName", iface.GetName()); + //iface.SetName("updated"); + //Assert.Equal("updated", iface.GetName()); + + var qiCallCountObj = obj.GetType().GetRuntimeProperties().Where(p => p.Name == "IUnknownStrategy").Single().GetValue(obj); + var countQi = (SingleQIComWrapper.CountQI)qiCallCountObj; + Assert.Equal(1, countQi.QiCallCount); + } + + [GeneratedComClass] + partial class DerivedImpl : IDerivedComInterface + { + int data = 3; + string myName = "myName"; + + public int GetInt() => data; + + public string GetName() => myName; + + public void SetInt(int n) => data = n; + + public void SetName(string name) => myName = name; + } + + class SingleQIComWrapper : StrategyBasedComWrappers + { + public class CountQI : IIUnknownStrategy + { + public CountQI(IIUnknownStrategy iUnknown) => _iUnknownStrategy = iUnknown; + private IIUnknownStrategy _iUnknownStrategy; + public int QiCallCount = 0; + public unsafe void* CreateInstancePointer(void* unknown) => _iUnknownStrategy.CreateInstancePointer(unknown); + public unsafe int QueryInterface(void* instancePtr, in Guid iid, out void* ppObj) + { + QiCallCount++; + return _iUnknownStrategy.QueryInterface(instancePtr, in iid, out ppObj); + } + public unsafe int Release(void* instancePtr) => _iUnknownStrategy.Release(instancePtr); + } + + protected override IIUnknownStrategy GetOrCreateIUnknownStrategy() + => new CountQI(base.GetOrCreateIUnknownStrategy()); + } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs index d9063c07a7860..d98fc95e6437d 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs @@ -30,6 +30,7 @@ public async Task SingleComInterface() using System.Runtime.InteropServices.Marshalling; [GeneratedComInterface] + [Guid("9D3FD745-3C90-4C10-B140-FAFB01E3541D")] partial interface INativeAPI { void Method(); @@ -48,12 +49,14 @@ public async Task MultipleComInterfaces() using System.Runtime.InteropServices.Marshalling; [GeneratedComInterface] + [Guid("9D3FD745-3C90-4C10-B140-FAFB01E3541D")] partial interface I { void Method(); void Method2(); } [GeneratedComInterface] + [Guid("734AFCEC-8862-43CB-AB29-5A7954929E23")] partial interface J { void Method(); @@ -72,16 +75,19 @@ public async Task EmptyComInterface() using System.Runtime.InteropServices.Marshalling; [GeneratedComInterface] + [Guid("9D3FD745-3C90-4C10-B140-FAFB01E3541D")] partial interface I { void Method(); void Method2(); } [GeneratedComInterface] + [Guid("734AFCEC-8862-43CB-AB29-5A7954929E23")] partial interface Empty { } [GeneratedComInterface] + [Guid("734AFCEC-8862-43CB-AB29-5A7954929E23")] partial interface J { void Method(); @@ -100,12 +106,14 @@ public async Task InheritingComInterfaces() using System.Runtime.InteropServices.Marshalling; [GeneratedComInterface] + [Guid("9D3FD745-3C90-4C10-B140-FAFB01E3541D")] partial interface I { void Method(); void Method2(); } [GeneratedComInterface] + [Guid("734AFCEC-8862-43CB-AB29-5A7954929E23")] partial interface J : I { void MethodA(); diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDerivedComInterface.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IDerivedComInterface.cs similarity index 69% rename from src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDerivedComInterface.cs rename to src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IDerivedComInterface.cs index 7df314587e077..ff32a3f632fdf 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDerivedComInterface.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IDerivedComInterface.cs @@ -5,15 +5,17 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; -namespace ComInterfaceGenerator.Tests +namespace SharedTypes.ComInterfaces { [GeneratedComInterface] - [Guid("7F0DB364-3C04-4487-9193-4BB05DC7B654")] - public partial interface IDerivedComInterface : IComInterface1 + [Guid(_guid)] + internal partial interface IDerivedComInterface : IGetAndSetInt { void SetName([MarshalUsing(typeof(Utf16StringMarshaller))] string name); [return:MarshalUsing(typeof(Utf16StringMarshaller))] string GetName(); + + internal new const string _guid = "7F0DB364-3C04-4487-9193-4BB05DC7B654"; } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IComInterface1.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IEmpty.cs similarity index 54% rename from src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IComInterface1.cs rename to src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IEmpty.cs index bb464c0147c95..cf99ae8a47530 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IComInterface1.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IEmpty.cs @@ -5,14 +5,12 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; -namespace ComInterfaceGenerator.Tests +namespace SharedTypes.ComInterfaces { [GeneratedComInterface] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid("2c3f9903-b586-46b1-881b-adfce9af47b1")] - public partial interface IComInterface1 + [Guid(_guid)] + internal partial interface Empty { - int GetData(); - void SetData(int n); + public const string _guid = "95D19F50-F2D8-4E61-884B-0A9162EA4646"; } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IGetAndSetInt.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IGetAndSetInt.cs index 2d76ef4c4f5a5..f1b46c81b7966 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IGetAndSetInt.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IGetAndSetInt.cs @@ -2,18 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; -using System.Text; -using System.Threading.Tasks; namespace SharedTypes.ComInterfaces { [GeneratedComInterface] [Guid(_guid)] - partial interface IGetAndSetInt + internal partial interface IGetAndSetInt { int GetInt(); diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IGetIntArray.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IGetIntArray.cs index 6b99c1d4bcac2..b70de07d1596b 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IGetIntArray.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/ComInterfaces/IGetIntArray.cs @@ -2,18 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; -using System.Text; -using System.Threading.Tasks; namespace SharedTypes.ComInterfaces { [GeneratedComInterface] [Guid(_guid)] - partial interface IGetIntArray + internal partial interface IGetIntArray { [return: MarshalUsing(ConstantElementCount = 10)] int[] GetInts();