Skip to content

Commit

Permalink
Partial cherrypick of IsPublic changes in 38a732b (#1657)
Browse files Browse the repository at this point in the history
  • Loading branch information
manodasanW authored Jul 3, 2024
1 parent f937163 commit 9cf9847
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 16 deletions.
6 changes: 4 additions & 2 deletions src/Authoring/WinRT.SourceGenerator/DiagnosticUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
54 changes: 54 additions & 0 deletions src/Authoring/WinRT.SourceGenerator/Extensions/SymbolExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;

#nullable enable

namespace Generator;

/// <summary>
/// Extensions for symbol types.
/// </summary>
internal static class SymbolExtensions
{
/// <summary>
/// Checks whether a given type symbol is publicly accessible (ie. it's public and not nested in any non public type).
/// </summary>
/// <param name="type">The type symbol to check for public accessibility.</param>
/// <returns>Whether <paramref name="type"/> is publicly accessible.</returns>
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;
}

/// <summary>
/// Checks whether a given symbol is an explicit interface implementation of a member of an internal interface (or more than one).
/// </summary>
/// <param name="symbol">The input member symbol to check.</param>
/// <returns>Whether <paramref name="symbol"/> is an explicit interface implementation of internal interfaces.</returns>
public static bool IsExplicitInterfaceImplementationOfInternalInterfaces(this ISymbol symbol)
{
static bool IsAnyContainingTypePublic(IEnumerable<ISymbol> 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
};
}
}
59 changes: 45 additions & 14 deletions src/Authoring/WinRT.SourceGenerator/WinRTTypeWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1303,21 +1303,38 @@ Symbol GetType(string type, bool isGeneric = false, int genericIndex = -1, bool

private IEnumerable<INamedTypeSymbol> GetInterfaces(INamedTypeSymbol symbol, bool includeInterfacesWithoutMappings = false)
{
HashSet<INamedTypeSymbol> interfaces = new HashSet<INamedTypeSymbol>();
foreach (var @interface in symbol.Interfaces)
HashSet<INamedTypeSymbol> 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;
}
Expand Down Expand Up @@ -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 ||
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 9cf9847

Please sign in to comment.