From 9cf98477de24a7eab7e6a5412cbe5a51e5afe79e Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Tue, 2 Jul 2024 20:42:02 -0700 Subject: [PATCH] Partial cherrypick of IsPublic changes in 38a732b027acdb1b4b0770f42cf582bb595e4a3a (#1657) --- .../WinRT.SourceGenerator/DiagnosticUtils.cs | 6 +- .../Extensions/SymbolExtensions.cs | 54 +++++++++++++++++ .../WinRT.SourceGenerator/WinRTTypeWriter.cs | 59 ++++++++++++++----- 3 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 src/Authoring/WinRT.SourceGenerator/Extensions/SymbolExtensions.cs diff --git a/src/Authoring/WinRT.SourceGenerator/DiagnosticUtils.cs b/src/Authoring/WinRT.SourceGenerator/DiagnosticUtils.cs index 04765f34f..397a6fc12 100644 --- a/src/Authoring/WinRT.SourceGenerator/DiagnosticUtils.cs +++ b/src/Authoring/WinRT.SourceGenerator/DiagnosticUtils.cs @@ -87,10 +87,12 @@ private void CheckDeclarations() foreach (var declaration in syntaxReceiver.Declarations) { var model = _context.Compilation.GetSemanticModel(declaration.SyntaxTree); + var symbol = model.GetDeclaredSymbol(declaration); // Check symbol information for whether it is public to properly detect partial types - // which can leave out modifier. - if (model.GetDeclaredSymbol(declaration).DeclaredAccessibility != Accessibility.Public) + // which can leave out modifier. Also ignore nested types not effectively public + if (symbol.DeclaredAccessibility != Accessibility.Public || + (symbol is ITypeSymbol typeSymbol && !typeSymbol.IsPubliclyAccessible())) { continue; } diff --git a/src/Authoring/WinRT.SourceGenerator/Extensions/SymbolExtensions.cs b/src/Authoring/WinRT.SourceGenerator/Extensions/SymbolExtensions.cs new file mode 100644 index 000000000..5a8f443d2 --- /dev/null +++ b/src/Authoring/WinRT.SourceGenerator/Extensions/SymbolExtensions.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; + +#nullable enable + +namespace Generator; + +/// +/// Extensions for symbol types. +/// +internal static class SymbolExtensions +{ + /// + /// Checks whether a given type symbol is publicly accessible (ie. it's public and not nested in any non public type). + /// + /// The type symbol to check for public accessibility. + /// Whether is publicly accessible. + public static bool IsPubliclyAccessible(this ITypeSymbol type) + { + for (ITypeSymbol? currentType = type; currentType is not null; currentType = currentType.ContainingType) + { + // If any type in the type hierarchy is not public, the type is not public. + // This makes sure to detect public types nested into eg. a private type. + if (currentType.DeclaredAccessibility is not Accessibility.Public) + { + return false; + } + } + + return true; + } + + /// + /// Checks whether a given symbol is an explicit interface implementation of a member of an internal interface (or more than one). + /// + /// The input member symbol to check. + /// Whether is an explicit interface implementation of internal interfaces. + public static bool IsExplicitInterfaceImplementationOfInternalInterfaces(this ISymbol symbol) + { + static bool IsAnyContainingTypePublic(IEnumerable symbols) + { + return symbols.Any(static symbol => symbol.ContainingType!.IsPubliclyAccessible()); + } + + return symbol switch + { + IMethodSymbol { ExplicitInterfaceImplementations: { Length: > 0 } methods } => !IsAnyContainingTypePublic(methods), + IPropertySymbol { ExplicitInterfaceImplementations: { Length: > 0 } properties } => !IsAnyContainingTypePublic(properties), + IEventSymbol { ExplicitInterfaceImplementations: { Length: > 0 } events } => !IsAnyContainingTypePublic(events), + _ => false + }; + } +} diff --git a/src/Authoring/WinRT.SourceGenerator/WinRTTypeWriter.cs b/src/Authoring/WinRT.SourceGenerator/WinRTTypeWriter.cs index f52aff52e..cf585ff22 100644 --- a/src/Authoring/WinRT.SourceGenerator/WinRTTypeWriter.cs +++ b/src/Authoring/WinRT.SourceGenerator/WinRTTypeWriter.cs @@ -1303,21 +1303,38 @@ Symbol GetType(string type, bool isGeneric = false, int genericIndex = -1, bool private IEnumerable GetInterfaces(INamedTypeSymbol symbol, bool includeInterfacesWithoutMappings = false) { - HashSet interfaces = new HashSet(); - foreach (var @interface in symbol.Interfaces) + HashSet interfaces = new(); + + // Gather all interfaces that are publicly accessible. We specifically need to exclude interfaces + // that are not public, as eg. those might be used for additional cloaked WinRT/COM interfaces. + // Ignoring them here makes sure that they're not processed to be part of the .winmd file. + void GatherPubliclyAccessibleInterfaces(ITypeSymbol symbol) { - interfaces.Add(@interface); - interfaces.UnionWith(@interface.AllInterfaces); + foreach (var @interface in symbol.Interfaces) + { + if (@interface.IsPubliclyAccessible()) + { + _ = interfaces.Add(@interface); + } + + // We're not using AllInterfaces on purpose: we only want to gather all interfaces but not + // from the base type. That's handled below to skip types that are already WinRT projections. + foreach (var @interface2 in @interface.AllInterfaces) + { + if (@interface2.IsPubliclyAccessible()) + { + _ = interfaces.Add(@interface2); + } + } + } } + GatherPubliclyAccessibleInterfaces(symbol); + var baseType = symbol.BaseType; while (baseType != null && !IsWinRTType(baseType)) { - interfaces.UnionWith(baseType.Interfaces); - foreach (var @interface in baseType.Interfaces) - { - interfaces.UnionWith(@interface.AllInterfaces); - } + GatherPubliclyAccessibleInterfaces(baseType); baseType = baseType.BaseType; } @@ -2010,6 +2027,13 @@ void AddComponentType(INamedTypeSymbol type, Action visitTypeDeclaration = null) } else { + // Special case: skip members that are explicitly implementing internal interfaces. + // This allows implementing classic COM internal interfaces with non-WinRT signatures. + if (member.IsExplicitInterfaceImplementationOfInternalInterfaces()) + { + continue; + } + if (member is IMethodSymbol method && (method.MethodKind == MethodKind.Ordinary || method.MethodKind == MethodKind.ExplicitInterfaceImplementation || @@ -2736,12 +2760,19 @@ public void FinalizeGeneration() } } - public bool IsPublic(ISymbol type) + public bool IsPublic(ISymbol symbol) { - return type.DeclaredAccessibility == Accessibility.Public || - type is IMethodSymbol method && !method.ExplicitInterfaceImplementations.IsDefaultOrEmpty || - type is IPropertySymbol property && !property.ExplicitInterfaceImplementations.IsDefaultOrEmpty || - type is IEventSymbol @event && !@event.ExplicitInterfaceImplementations.IsDefaultOrEmpty; + // Check that the type has either public accessibility, or is an explicit interface implementation + if (symbol.DeclaredAccessibility == Accessibility.Public || + symbol is IMethodSymbol method && !method.ExplicitInterfaceImplementations.IsDefaultOrEmpty || + symbol is IPropertySymbol property && !property.ExplicitInterfaceImplementations.IsDefaultOrEmpty || + symbol is IEventSymbol @event && !@event.ExplicitInterfaceImplementations.IsDefaultOrEmpty) + { + // If we have a containing type, we also check that it's publicly accessible + return symbol.ContainingType is not { } containingType || containingType.IsPubliclyAccessible(); + } + + return false; } public void GetNamespaceAndTypename(string qualifiedName, out string @namespace, out string typename)