Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial cherrypick of IsPublic changes from staging branch #1657

Merged
merged 1 commit into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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