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

Simplify AvoidUninstantiatedInternalClasses #6309

Merged
merged 4 commits into from
Jan 3, 2023
Merged
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public sealed override void Initialize(AnalysisContext context)
var internalTypes = new ConcurrentDictionary<INamedTypeSymbol, object?>();

var compilation = startContext.Compilation;
var entryPointContainingType = compilation.GetEntryPoint(startContext.CancellationToken)?.ContainingType;
var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(compilation);

// If the assembly being built by this compilation exposes its internals to
Expand Down Expand Up @@ -98,7 +99,8 @@ public sealed override void Initialize(AnalysisContext context)
{
var type = (INamedTypeSymbol)context.Symbol;
if (!type.IsExternallyVisible() &&
!IsOkToBeUninstantiated(type, compilation,
!IsOkToBeUninstantiated(type,
entryPointContainingType,
systemAttributeSymbol,
iConfigurationSectionHandlerSymbol,
configurationSectionSymbol,
Expand Down Expand Up @@ -282,7 +284,7 @@ private bool HasInstantiatedNestedType(INamedTypeSymbol type, IEnumerable<INamed

private static bool IsOkToBeUninstantiated(
INamedTypeSymbol type,
Compilation compilation,
INamedTypeSymbol? entryPointContainingType,
INamedTypeSymbol? systemAttributeSymbol,
INamedTypeSymbol? iConfigurationSectionHandlerSymbol,
INamedTypeSymbol? configurationSectionSymbol,
Expand All @@ -302,14 +304,8 @@ private static bool IsOkToBeUninstantiated(
return true;
}

// Ignore type generated for holding top level statements
if (type.IsTopLevelStatementsEntryPointType())
{
return true;
}

// The type containing the assembly's entry point is OK.
if (ContainsEntryPoint(type, compilation))
Copy link
Member Author

@Youssef1313 Youssef1313 Dec 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On roslyn side, this is calculated once for the compilation and cached:

https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs#L1676-L1684

@mavasani Do you think this PR can contribute to #6301 ? (the new approach is very likely more efficient, but I'm not sure if the improvement is large enough here)

I'm not sure how much the existing code was expensive. Specifically, we could have been calling GetMembers for lots of types. Did the compiler already cached them before invoking the analyzer? or does the analyzer come through this code path:

https://github.com/dotnet/roslyn/blob/1100e56a04e144352af43181df077a13957e6a58/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs#L1656-L1665

We also were doing more symbol comparison than with this PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regardless of whether or not this addresses #6301, it seems to definitely be more hardened code and should likely also improve performance.

if (SymbolEqualityComparer.Default.Equals(entryPointContainingType, type))
{
return true;
}
Expand Down Expand Up @@ -351,6 +347,7 @@ private static bool IsOkToBeUninstantiated(

return false;
}

public static bool IsMefExported(
INamedTypeSymbol type,
INamedTypeSymbol? mef1ExportAttributeSymbol,
Expand All @@ -360,81 +357,6 @@ public static bool IsMefExported(
|| (mef2ExportAttributeSymbol != null && type.HasAttribute(mef2ExportAttributeSymbol));
}

private static bool ContainsEntryPoint(INamedTypeSymbol type, Compilation compilation)
{
// If this type doesn't live in an application assembly (.exe), it can't contain
// the entry point.
if (compilation.Options.OutputKind is not OutputKind.ConsoleApplication and
not OutputKind.WindowsApplication and
not OutputKind.WindowsRuntimeApplication)
{
return false;
}

var wellKnowTypeProvider = WellKnownTypeProvider.GetOrCreate(compilation);
var taskSymbol = wellKnowTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask);
var genericTaskSymbol = wellKnowTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask1);

// TODO: Handle the case where Compilation.Options.MainTypeName matches this type.
// TODO: Test: can't have type parameters.
// TODO: Main in nested class? If allowed, what name does it have?
// TODO: Test that parameter is array of int.
Comment on lines -378 to -381
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these should be handled now by the compiler :)

return type.GetMembers("Main")
.Where(m => m is IMethodSymbol)
.Cast<IMethodSymbol>()
.Any(m => IsEntryPoint(m, taskSymbol, genericTaskSymbol));
}

private static bool IsEntryPoint(IMethodSymbol method, ITypeSymbol? taskSymbol, ITypeSymbol? genericTaskSymbol)
{
if (!method.IsStatic)
{
return false;
}

if (!IsSupportedReturnType(method, taskSymbol, genericTaskSymbol))
{
return false;
}

if (!method.Parameters.Any())
{
return true;
}

if (method.Parameters.HasMoreThan(1))
{
return false;
}

return true;
}

private static bool IsSupportedReturnType(IMethodSymbol method, ITypeSymbol? taskSymbol, ITypeSymbol? genericTaskSymbol)
{
if (method.ReturnType.SpecialType == SpecialType.System_Int32)
{
return true;
}

if (method.ReturnsVoid)
{
return true;
}

if (taskSymbol != null && Equals(method.ReturnType, taskSymbol))
{
return true;
}

if (genericTaskSymbol != null && Equals(method.ReturnType.OriginalDefinition, genericTaskSymbol) && ((INamedTypeSymbol)method.ReturnType).TypeArguments.Single().SpecialType == SpecialType.System_Int32)
{
return true;
}

return false;
}

/// <summary>
/// If a type is passed a generic argument to another type or a method that specifies that the type must have a constructor,
/// we presume that the method will be constructing the type, and add it to the list of instantiated types.
Expand Down