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

Logging generator: Handle multiple type definitions #5352

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
2 changes: 1 addition & 1 deletion src/Generators/Microsoft.Gen.Logging/Emission/Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private void GenType(LoggingType lt)

private void GenAttributeClassifications(LoggingType lt)
{
// gather all the claassification attributes referenced by the logging type
// gather all the classification attributes referenced by the logging type
var classificationAttrs = new HashSet<string>();
foreach (var lm in lt.Methods)
{
Expand Down
15 changes: 11 additions & 4 deletions src/Generators/Microsoft.Gen.Logging/LoggingGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand All @@ -29,11 +28,19 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => HandleAnnotatedTypes(source.Item1, source.Item2, spc));
}

private static void HandleAnnotatedTypes(Compilation compilation, IEnumerable<TypeDeclarationSyntax> types, SourceProductionContext context)
private static void HandleAnnotatedTypes(Compilation compilation, ImmutableArray<TypeDeclarationSyntax> types, SourceProductionContext context)
{
var p = new Parsing.Parser(compilation, context.ReportDiagnostic, context.CancellationToken);
if (types.IsDefaultOrEmpty)
{
// nothing to do yet
return;
}

ImmutableHashSet<TypeDeclarationSyntax> distinctTypes = types.ToImmutableHashSet();

var p = new Parser(compilation, context.ReportDiagnostic, context.CancellationToken);
IReadOnlyList<Model.LoggingType> logTypes = p.GetLogTypes(distinctTypes);

var logTypes = p.GetLogTypes(types.Distinct());
if (logTypes.Count > 0)
{
var e = new Emission.Emitter();
Expand Down
29 changes: 15 additions & 14 deletions src/Generators/Microsoft.Gen.Logging/Parsing/SymbolLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.Gen.Shared;

namespace Microsoft.Gen.Logging.Parsing;

Expand Down Expand Up @@ -52,16 +53,16 @@ internal static class SymbolLoader
Compilation compilation,
Action<DiagnosticDescriptor, Location?, object?[]?> diagCallback)
{
var loggerSymbol = compilation.GetTypeByMetadataName(ILoggerType);
var logLevelSymbol = compilation.GetTypeByMetadataName(LogLevelType);
var loggerMessageAttributeSymbol = compilation.GetTypeByMetadataName(LoggerMessageAttribute);
var logPropertiesAttributeSymbol = compilation.GetTypeByMetadataName(LogPropertiesAttribute);
var tagProviderAttributeSymbol = compilation.GetTypeByMetadataName(TagProviderAttribute);
var tagNameAttributeSymbol = compilation.GetTypeByMetadataName(TagNameAttribute);
var tagCollectorSymbol = compilation.GetTypeByMetadataName(ITagCollectorType);
var logPropertyIgnoreAttributeSymbol = compilation.GetTypeByMetadataName(LogPropertyIgnoreAttribute);
var dataClassificationAttribute = compilation.GetTypeByMetadataName(DataClassificationAttribute);
var noDataClassificationAttribute = compilation.GetTypeByMetadataName(NoDataClassificationAttribute);
var loggerSymbol = compilation.GetBestTypeByMetadataName(ILoggerType);
dariusclay marked this conversation as resolved.
Show resolved Hide resolved
var logLevelSymbol = compilation.GetBestTypeByMetadataName(LogLevelType);
var loggerMessageAttributeSymbol = compilation.GetBestTypeByMetadataName(LoggerMessageAttribute);
var logPropertiesAttributeSymbol = compilation.GetBestTypeByMetadataName(LogPropertiesAttribute);
var tagProviderAttributeSymbol = compilation.GetBestTypeByMetadataName(TagProviderAttribute);
var tagNameAttributeSymbol = compilation.GetBestTypeByMetadataName(TagNameAttribute);
var tagCollectorSymbol = compilation.GetBestTypeByMetadataName(ITagCollectorType);
var logPropertyIgnoreAttributeSymbol = compilation.GetBestTypeByMetadataName(LogPropertyIgnoreAttribute);
var dataClassificationAttribute = compilation.GetBestTypeByMetadataName(DataClassificationAttribute);
var noDataClassificationAttribute = compilation.GetBestTypeByMetadataName(NoDataClassificationAttribute);

#pragma warning disable S1067 // Expressions should not be too complex
if (loggerSymbol == null
Expand All @@ -78,22 +79,22 @@ internal static class SymbolLoader
}
#pragma warning restore S1067 // Expressions should not be too complex

var exceptionSymbol = compilation.GetTypeByMetadataName(ExceptionType);
var exceptionSymbol = compilation.GetBestTypeByMetadataName(ExceptionType);
if (exceptionSymbol == null)
{
diagCallback(DiagDescriptors.MissingRequiredType, null, new object[] { ExceptionType });
return null;
}

var enumerableSymbol = compilation.GetSpecialType(SpecialType.System_Collections_IEnumerable);
var formatProviderSymbol = compilation.GetTypeByMetadataName(IFormatProviderType)!;
var spanFormattableSymbol = compilation.GetTypeByMetadataName(ISpanFormattableType);
var formatProviderSymbol = compilation.GetBestTypeByMetadataName(IFormatProviderType)!;
var spanFormattableSymbol = compilation.GetBestTypeByMetadataName(ISpanFormattableType);

var ignorePropsSymbols = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);

foreach (var ign in _ignored)
{
var s = compilation.GetTypeByMetadataName(ign);
var s = compilation.GetBestTypeByMetadataName(ign);
if (s != null)
{
_ = ignorePropsSymbols.Add(s);
Expand Down
9 changes: 6 additions & 3 deletions src/Generators/Shared/GeneratorUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ internal static class GeneratorUtilities
private const string CompilationOutputPath = "build_property.outputpath";
private const string CurrentProjectPath = "build_property.projectdir";

public static string GeneratedCodeAttribute { get; } = $"global::System.CodeDom.Compiler.GeneratedCodeAttribute(" +
$"\"{typeof(GeneratorUtilities).Assembly.GetName().Name}\", " +
$"\"{typeof(GeneratorUtilities).Assembly.GetName().Version}\")";
public static string AssemblyName { get; } = typeof(GeneratorUtilities).Assembly.GetName().Name;

public static string CurrentVersion { get; } = typeof(GeneratorUtilities).Assembly.GetName().Version.ToString();

public static string GeneratedCodeAttribute { get; } =
$"global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"{AssemblyName}\", \"{CurrentVersion}\")";

public static string FilePreamble { get; } = @$"
// <auto-generated/>
Expand Down
208 changes: 208 additions & 0 deletions src/Generators/Shared/RoslynExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
dariusclay marked this conversation as resolved.
Show resolved Hide resolved
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

#pragma warning disable CA1716
dariusclay marked this conversation as resolved.
Show resolved Hide resolved
namespace Microsoft.Gen.Shared;
#pragma warning restore CA1716

#if !SHARED_PROJECT
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
#endif
internal static class RoslynExtensions
{
/// <summary>
/// Gets a type by its metadata name to use for code analysis within a <see cref="Compilation"/>. This method
/// attempts to find the "best" symbol to use for code analysis, which is the symbol matching the first of the
/// following rules.
///
/// <list type="number">
/// <item><description>
/// If only one type with the given name is found within the compilation and its referenced assemblies, that
/// type is returned regardless of accessibility.
/// </description></item>
/// <item><description>
/// If the current <paramref name="compilation"/> defines the symbol, that symbol is returned.
/// </description></item>
/// <item><description>
/// If exactly one referenced assembly defines the symbol in a manner that makes it visible to the current
/// <paramref name="compilation"/>, that symbol is returned.
/// </description></item>
/// <item><description>
/// Otherwise, this method returns <see langword="null"/>.
/// </description></item>
/// </list>
/// </summary>
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
/// <param name="fullyQualifiedMetadataName">The fully-qualified metadata type name to find.</param>
/// <returns>The symbol to use for code analysis; otherwise, <see langword="null"/>.</returns>
// Copied from: https://github.com/dotnet/roslyn/blob/af7b0ebe2b0ed5c335a928626c25620566372dd1/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs
public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName)
{
// Try to get the unique type with this name, ignoring accessibility
var type = compilation.GetTypeByMetadataName(fullyQualifiedMetadataName);

// Otherwise, try to get the unique type with this name originally defined in 'compilation'
type ??= compilation.Assembly?.GetTypeByMetadataName(fullyQualifiedMetadataName);

// Otherwise, try to get the unique accessible type with this name from a reference
if (type is null)
{
if (compilation.Assembly is null)
{
// nothing to do
return null;
}

foreach (var module in compilation.Assembly.Modules)
{
foreach (var referencedAssembly in module.ReferencedAssemblySymbols)
{
var currentType = referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName);
if (currentType is null)
{
continue;
}

switch (currentType.GetResultantVisibility())
{
case SymbolVisibility.Public:
case SymbolVisibility.Internal when referencedAssembly.GivesAccessTo(compilation.Assembly):
break;

default:
continue;
}

if (type is object)
{
// Multiple visible types with the same metadata name are present
return null;
}

type = currentType;
}
}
}

return type;
}

/// <summary>
/// A thin wrapper over <see cref="GetBestTypeByMetadataName(Compilation, string)"/>,
/// but taking the type itself rather than the fully-qualified metadata type name.
/// </summary>
/// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
/// <param name="type">The type to find.</param>
public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, Type type) =>
type.IsArray || type.FullName is null
? throw new ArgumentException("The input type must correspond to a named type symbol.")
: GetBestTypeByMetadataName(compilation, type.FullName);

public static ImmutableArray<T> ToImmutableArray<T>(this ReadOnlySpan<T> span)
{
#pragma warning disable S109 // Magic numbers should not be used
switch (span.Length)
{
case 0:
return ImmutableArray<T>.Empty;
case 1:
return ImmutableArray.Create(span[0]);
case 2:
return ImmutableArray.Create(span[0], span[1]);
case 3:
return ImmutableArray.Create(span[0], span[1], span[2]);
case 4:
return ImmutableArray.Create(span[0], span[1], span[2], span[3]);
default:
var builder = ImmutableArray.CreateBuilder<T>(span.Length);
foreach (var item in span)
{
builder.Add(item);
}

return builder.MoveToImmutable();
}
#pragma warning restore S109 // Magic numbers should not be used
}

public static SimpleNameSyntax GetUnqualifiedName(this NameSyntax name)
=> name switch
{
AliasQualifiedNameSyntax alias => alias.Name,
QualifiedNameSyntax qualified => qualified.Right,
SimpleNameSyntax simple => simple,
_ => throw new InvalidOperationException("Unreachable"),
};

// Copied from: https://github.com/dotnet/roslyn/blob/af7b0ebe2b0ed5c335a928626c25620566372dd1/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs
private static SymbolVisibility GetResultantVisibility(this ISymbol symbol)
{
// Start by assuming it's visible.
SymbolVisibility visibility = SymbolVisibility.Public;

switch (symbol.Kind)
{
case SymbolKind.Alias:
// Aliases are uber private. They're only visible in the same file that they
// were declared in.
return SymbolVisibility.Private;

case SymbolKind.Parameter:
// Parameters are only as visible as their containing symbol
return GetResultantVisibility(symbol.ContainingSymbol);

case SymbolKind.TypeParameter:
// Type Parameters are private.
return SymbolVisibility.Private;
}

while (symbol is not null && symbol.Kind != SymbolKind.Namespace)
{
switch (symbol.DeclaredAccessibility)
{
// If we see anything private, then the symbol is private.
case Accessibility.NotApplicable:
case Accessibility.Private:
return SymbolVisibility.Private;

// If we see anything internal, then knock it down from public to
// internal.
case Accessibility.Internal:
case Accessibility.ProtectedAndInternal:
visibility = SymbolVisibility.Internal;
break;

// For anything else (Public, Protected, ProtectedOrInternal), the
// symbol stays at the level we've gotten so far.
}

symbol = symbol.ContainingSymbol;
}

return visibility;
}

// Copied from: https://github.com/dotnet/roslyn/blob/af7b0ebe2b0ed5c335a928626c25620566372dd1/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolVisibility.cs
#pragma warning disable CA1027 // Mark enums with FlagsAttribute
private enum SymbolVisibility
#pragma warning restore CA1027 // Mark enums with FlagsAttribute
{
Public = 0,
Internal = 1,
Private = 2,
Friend = Internal,
}

internal static bool HasAttributeSuffix(this string name, bool isCaseSensitive)
{
const string AttributeSuffix = "Attribute";

var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
return name.Length > AttributeSuffix.Length && name.EndsWith(AttributeSuffix, comparison);
}
}
65 changes: 65 additions & 0 deletions test/Generators/Microsoft.Gen.Logging/Unit/CompilationHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Extensions.Compliance.Classification;
using Microsoft.Extensions.Compliance.Testing;
using Microsoft.Extensions.Diagnostics.Enrichment;
using Microsoft.Extensions.Logging;

namespace Microsoft.Gen.Logging.Test;

public static class CompilationHelper
{
public static Compilation CreateCompilation(
string source,
MetadataReference[]? additionalReferences = null,
string assemblyName = "TestAssembly")
{
string corelib = Assembly.GetAssembly(typeof(object))!.Location;
string runtimeDir = Path.GetDirectoryName(corelib)!;

var refs = new List<MetadataReference>();
refs.Add(MetadataReference.CreateFromFile(corelib));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "netstandard.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "System.Runtime.dll")));
refs.Add(MetadataReference.CreateFromFile(typeof(ILogger).Assembly.Location));
refs.Add(MetadataReference.CreateFromFile(typeof(LoggerMessageAttribute).Assembly.Location));
refs.Add(MetadataReference.CreateFromFile(typeof(IEnrichmentTagCollector).Assembly.Location));
refs.Add(MetadataReference.CreateFromFile(typeof(DataClassification).Assembly.Location));
refs.Add(MetadataReference.CreateFromFile(typeof(PrivateDataAttribute).Assembly.Location));
refs.Add(MetadataReference.CreateFromFile(typeof(BigInteger).Assembly.Location));

if (additionalReferences is not null)
{
foreach (MetadataReference reference in additionalReferences)
{
refs.Add(reference);
}
}

return CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source) },
references: refs.ToArray(),
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
}

public static byte[] CreateAssemblyImage(Compilation compilation)
{
MemoryStream ms = new MemoryStream();
var emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
throw new InvalidOperationException();
}

return ms.ToArray();
}
}
Loading