-
Notifications
You must be signed in to change notification settings - Fork 107
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
Skip interfaces not publicly accessible in authoring scenarios #1394
Merged
Sergio0694
merged 15 commits into
staging/AOT
from
user/sergiopedri/skip-authored-internal-interfaces
Nov 22, 2023
Merged
Changes from 14 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
b8cfafd
Add internal COM interfaces to authoring test
Sergio0694 a14e2f4
Add MixedWinRTClassicCOM authoring tests
Sergio0694 b4fa88d
Skip interface types not publicly accessible
Sergio0694 9705c8d
Minor code refactoring
Sergio0694 f8160f9
Suppress diagnostics for not publicly accessible types
Sergio0694 31be043
Use fully qualified name for [Guid] to avoid conflicts
Sergio0694 a8c5da0
Remmove collection expressions in projection attributes
Sergio0694 27ee63c
Skip processing explicit members of internal interfaces
Sergio0694 18ab74d
Skip processing symbols nested in internal types
Sergio0694 3c8a1d8
Add ABI types for AOT generator
Sergio0694 4a80a91
Restore original order/filtering to gather interfaces
Sergio0694 d627ff8
Fix build errors in AuthoringConsumptionTest
Sergio0694 43b0323
Add TestMixedWinRTCOMWrapper to activation manifest
Sergio0694 2e03391
Fix unit test
Sergio0694 6f91bee
Fix typos in ABI method names
Sergio0694 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
src/Authoring/WinRT.SourceGenerator/Extensions/SymbolExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1204,21 +1204,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 && !GeneratorHelper.IsWinRTType(baseType)) | ||
{ | ||
interfaces.UnionWith(baseType.Interfaces); | ||
foreach (var @interface in baseType.Interfaces) | ||
{ | ||
interfaces.UnionWith(@interface.AllInterfaces); | ||
} | ||
GatherPubliclyAccessibleInterfaces(baseType); | ||
|
||
baseType = baseType.BaseType; | ||
} | ||
|
@@ -1911,6 +1928,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 || | ||
|
@@ -2683,12 +2707,19 @@ typeDeclaration.Node is INamedTypeSymbol symbol && | |
} | ||
} | ||
|
||
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 && [email protected]; | ||
// 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 && [email protected]) | ||
{ | ||
// 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) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am trying to understand this, should this have been an
&&
rather than a||
or am I misunderstanding whatis not {}
does.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So,
is not { }
matches if the expression is not an instance (it's likeis null
, but also declares a local). So:containingType
isnull
, returntrue
containingType != null
), returntrue
if it's publicly accessibleIf we used
&&
, we'd be trying to accesscontainingType
when the first expression already matched, but if that's the case, thencontainingType
is not defined (because the expression matches onis not
). In fact, if you changed it to&&
, the code would just not compile, ascontainingType
would be uninitialized in that case 🙂