Skip to content

Commit 9cf9847

Browse files
authored
Partial cherrypick of IsPublic changes in 38a732b (#1657)
1 parent f937163 commit 9cf9847

File tree

3 files changed

+103
-16
lines changed

3 files changed

+103
-16
lines changed

src/Authoring/WinRT.SourceGenerator/DiagnosticUtils.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,12 @@ private void CheckDeclarations()
8787
foreach (var declaration in syntaxReceiver.Declarations)
8888
{
8989
var model = _context.Compilation.GetSemanticModel(declaration.SyntaxTree);
90+
var symbol = model.GetDeclaredSymbol(declaration);
9091

9192
// Check symbol information for whether it is public to properly detect partial types
92-
// which can leave out modifier.
93-
if (model.GetDeclaredSymbol(declaration).DeclaredAccessibility != Accessibility.Public)
93+
// which can leave out modifier. Also ignore nested types not effectively public
94+
if (symbol.DeclaredAccessibility != Accessibility.Public ||
95+
(symbol is ITypeSymbol typeSymbol && !typeSymbol.IsPubliclyAccessible()))
9496
{
9597
continue;
9698
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Microsoft.CodeAnalysis;
4+
5+
#nullable enable
6+
7+
namespace Generator;
8+
9+
/// <summary>
10+
/// Extensions for symbol types.
11+
/// </summary>
12+
internal static class SymbolExtensions
13+
{
14+
/// <summary>
15+
/// Checks whether a given type symbol is publicly accessible (ie. it's public and not nested in any non public type).
16+
/// </summary>
17+
/// <param name="type">The type symbol to check for public accessibility.</param>
18+
/// <returns>Whether <paramref name="type"/> is publicly accessible.</returns>
19+
public static bool IsPubliclyAccessible(this ITypeSymbol type)
20+
{
21+
for (ITypeSymbol? currentType = type; currentType is not null; currentType = currentType.ContainingType)
22+
{
23+
// If any type in the type hierarchy is not public, the type is not public.
24+
// This makes sure to detect public types nested into eg. a private type.
25+
if (currentType.DeclaredAccessibility is not Accessibility.Public)
26+
{
27+
return false;
28+
}
29+
}
30+
31+
return true;
32+
}
33+
34+
/// <summary>
35+
/// Checks whether a given symbol is an explicit interface implementation of a member of an internal interface (or more than one).
36+
/// </summary>
37+
/// <param name="symbol">The input member symbol to check.</param>
38+
/// <returns>Whether <paramref name="symbol"/> is an explicit interface implementation of internal interfaces.</returns>
39+
public static bool IsExplicitInterfaceImplementationOfInternalInterfaces(this ISymbol symbol)
40+
{
41+
static bool IsAnyContainingTypePublic(IEnumerable<ISymbol> symbols)
42+
{
43+
return symbols.Any(static symbol => symbol.ContainingType!.IsPubliclyAccessible());
44+
}
45+
46+
return symbol switch
47+
{
48+
IMethodSymbol { ExplicitInterfaceImplementations: { Length: > 0 } methods } => !IsAnyContainingTypePublic(methods),
49+
IPropertySymbol { ExplicitInterfaceImplementations: { Length: > 0 } properties } => !IsAnyContainingTypePublic(properties),
50+
IEventSymbol { ExplicitInterfaceImplementations: { Length: > 0 } events } => !IsAnyContainingTypePublic(events),
51+
_ => false
52+
};
53+
}
54+
}

src/Authoring/WinRT.SourceGenerator/WinRTTypeWriter.cs

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,21 +1303,38 @@ Symbol GetType(string type, bool isGeneric = false, int genericIndex = -1, bool
13031303

13041304
private IEnumerable<INamedTypeSymbol> GetInterfaces(INamedTypeSymbol symbol, bool includeInterfacesWithoutMappings = false)
13051305
{
1306-
HashSet<INamedTypeSymbol> interfaces = new HashSet<INamedTypeSymbol>();
1307-
foreach (var @interface in symbol.Interfaces)
1306+
HashSet<INamedTypeSymbol> interfaces = new();
1307+
1308+
// Gather all interfaces that are publicly accessible. We specifically need to exclude interfaces
1309+
// that are not public, as eg. those might be used for additional cloaked WinRT/COM interfaces.
1310+
// Ignoring them here makes sure that they're not processed to be part of the .winmd file.
1311+
void GatherPubliclyAccessibleInterfaces(ITypeSymbol symbol)
13081312
{
1309-
interfaces.Add(@interface);
1310-
interfaces.UnionWith(@interface.AllInterfaces);
1313+
foreach (var @interface in symbol.Interfaces)
1314+
{
1315+
if (@interface.IsPubliclyAccessible())
1316+
{
1317+
_ = interfaces.Add(@interface);
1318+
}
1319+
1320+
// We're not using AllInterfaces on purpose: we only want to gather all interfaces but not
1321+
// from the base type. That's handled below to skip types that are already WinRT projections.
1322+
foreach (var @interface2 in @interface.AllInterfaces)
1323+
{
1324+
if (@interface2.IsPubliclyAccessible())
1325+
{
1326+
_ = interfaces.Add(@interface2);
1327+
}
1328+
}
1329+
}
13111330
}
13121331

1332+
GatherPubliclyAccessibleInterfaces(symbol);
1333+
13131334
var baseType = symbol.BaseType;
13141335
while (baseType != null && !IsWinRTType(baseType))
13151336
{
1316-
interfaces.UnionWith(baseType.Interfaces);
1317-
foreach (var @interface in baseType.Interfaces)
1318-
{
1319-
interfaces.UnionWith(@interface.AllInterfaces);
1320-
}
1337+
GatherPubliclyAccessibleInterfaces(baseType);
13211338

13221339
baseType = baseType.BaseType;
13231340
}
@@ -2010,6 +2027,13 @@ void AddComponentType(INamedTypeSymbol type, Action visitTypeDeclaration = null)
20102027
}
20112028
else
20122029
{
2030+
// Special case: skip members that are explicitly implementing internal interfaces.
2031+
// This allows implementing classic COM internal interfaces with non-WinRT signatures.
2032+
if (member.IsExplicitInterfaceImplementationOfInternalInterfaces())
2033+
{
2034+
continue;
2035+
}
2036+
20132037
if (member is IMethodSymbol method &&
20142038
(method.MethodKind == MethodKind.Ordinary ||
20152039
method.MethodKind == MethodKind.ExplicitInterfaceImplementation ||
@@ -2736,12 +2760,19 @@ public void FinalizeGeneration()
27362760
}
27372761
}
27382762

2739-
public bool IsPublic(ISymbol type)
2763+
public bool IsPublic(ISymbol symbol)
27402764
{
2741-
return type.DeclaredAccessibility == Accessibility.Public ||
2742-
type is IMethodSymbol method && !method.ExplicitInterfaceImplementations.IsDefaultOrEmpty ||
2743-
type is IPropertySymbol property && !property.ExplicitInterfaceImplementations.IsDefaultOrEmpty ||
2744-
type is IEventSymbol @event && !@event.ExplicitInterfaceImplementations.IsDefaultOrEmpty;
2765+
// Check that the type has either public accessibility, or is an explicit interface implementation
2766+
if (symbol.DeclaredAccessibility == Accessibility.Public ||
2767+
symbol is IMethodSymbol method && !method.ExplicitInterfaceImplementations.IsDefaultOrEmpty ||
2768+
symbol is IPropertySymbol property && !property.ExplicitInterfaceImplementations.IsDefaultOrEmpty ||
2769+
symbol is IEventSymbol @event && !@event.ExplicitInterfaceImplementations.IsDefaultOrEmpty)
2770+
{
2771+
// If we have a containing type, we also check that it's publicly accessible
2772+
return symbol.ContainingType is not { } containingType || containingType.IsPubliclyAccessible();
2773+
}
2774+
2775+
return false;
27452776
}
27462777

27472778
public void GetNamespaceAndTypename(string qualifiedName, out string @namespace, out string typename)

0 commit comments

Comments
 (0)