// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Text; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; namespace Microsoft.CodeAnalysis.CSharp.Test.Utilities { /// /// Returns a string with all symbols containing NativeIntegerAttributes. /// internal sealed class NativeIntegerAttributesVisitor : CSharpSymbolVisitor { internal static string GetString(PEModuleSymbol module) { var builder = new StringBuilder(); var visitor = new NativeIntegerAttributesVisitor(builder); visitor.Visit(module); return builder.ToString(); } private readonly StringBuilder _builder; private readonly HashSet _reported; private NativeIntegerAttributesVisitor(StringBuilder builder) { _builder = builder; _reported = new HashSet(); } public override void DefaultVisit(Symbol symbol) { ReportSymbol(symbol); } public override void VisitModule(ModuleSymbol module) { Visit(module.GlobalNamespace); } public override void VisitNamespace(NamespaceSymbol @namespace) { VisitList(@namespace.GetMembers()); } public override void VisitNamedType(NamedTypeSymbol type) { ReportSymbol(type); VisitList(type.TypeParameters); foreach (var member in type.GetMembers()) { // Skip accessors since those are covered by associated symbol. if (member.IsAccessor()) continue; Visit(member); } } public override void VisitMethod(MethodSymbol method) { ReportSymbol(method); VisitList(method.TypeParameters); VisitList(method.Parameters); } public override void VisitEvent(EventSymbol @event) { ReportSymbol(@event); Visit(@event.AddMethod); Visit(@event.RemoveMethod); } public override void VisitProperty(PropertySymbol property) { ReportSymbol(property); VisitList(property.Parameters); Visit(property.GetMethod); Visit(property.SetMethod); } public override void VisitTypeParameter(TypeParameterSymbol typeParameter) { ReportSymbol(typeParameter); } private void VisitList(ImmutableArray symbols) where TSymbol : Symbol { foreach (var symbol in symbols) { Visit(symbol); } } /// /// Return the containing symbol used in the hierarchy here. Specifically, the /// hierarchy contains types, members, and parameters only, and accessors are /// considered members of the associated symbol rather than the type. /// private static Symbol GetContainingSymbol(Symbol symbol) { if (symbol.IsAccessor()) { return ((MethodSymbol)symbol).AssociatedSymbol; } var containingSymbol = symbol.ContainingSymbol; return containingSymbol?.Kind == SymbolKind.Namespace ? null : containingSymbol; } private static string GetIndentString(Symbol symbol) { int level = 0; while (true) { symbol = GetContainingSymbol(symbol); if (symbol is null) { break; } level++; } return new string(' ', level * 4); } private static readonly SymbolDisplayFormat _displayFormat = SymbolDisplayFormat.TestFormatWithConstraints. WithMemberOptions( SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeType | SymbolDisplayMemberOptions.IncludeRef | SymbolDisplayMemberOptions.IncludeExplicitInterface). WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseNativeIntegerUnderlyingType); private void ReportContainingSymbols(Symbol symbol) { symbol = GetContainingSymbol(symbol); if (symbol is null) { return; } if (_reported.Contains(symbol)) { return; } ReportContainingSymbols(symbol); _builder.Append(GetIndentString(symbol)); _builder.AppendLine(symbol.ToDisplayString(_displayFormat)); _reported.Add(symbol); } private void ReportSymbol(Symbol symbol) { var type = (symbol as TypeSymbol) ?? symbol.GetTypeOrReturnType().Type; var attribute = GetNativeIntegerAttribute((symbol is MethodSymbol method) ? method.GetReturnTypeAttributes() : symbol.GetAttributes()); Debug.Assert((type?.ContainsNativeInteger() != true) || (attribute != null)); if (attribute == null) { return; } ReportContainingSymbols(symbol); _builder.Append(GetIndentString(symbol)); _builder.Append($"{ReportAttribute(attribute)} "); _builder.AppendLine(symbol.ToDisplayString(_displayFormat)); _reported.Add(symbol); } private static Symbol GetAccessSymbol(Symbol symbol) { while (true) { switch (symbol.Kind) { case SymbolKind.Parameter: case SymbolKind.TypeParameter: symbol = symbol.ContainingSymbol; break; default: return symbol; } } } private static string ReportAttribute(CSharpAttributeData attribute) { var builder = new StringBuilder(); builder.Append("["); var name = attribute.AttributeClass.Name; if (name.EndsWith("Attribute")) name = name.Substring(0, name.Length - 9); builder.Append(name); var arguments = attribute.ConstructorArguments.ToImmutableArray(); if (arguments.Length > 0) { builder.Append("("); printValues(builder, arguments); builder.Append(")"); } builder.Append("]"); return builder.ToString(); static void printValues(StringBuilder builder, ImmutableArray values) { for (int i = 0; i < values.Length; i++) { if (i > 0) { builder.Append(", "); } printValue(builder, values[i]); } } static void printValue(StringBuilder builder, TypedConstant value) { if (value.Kind == TypedConstantKind.Array) { builder.Append("{ "); printValues(builder, value.Values); builder.Append(" }"); } else { builder.Append(value.Value); } } } private static CSharpAttributeData GetNativeIntegerAttribute(ImmutableArray attributes) => GetAttribute(attributes, "System.Runtime.CompilerServices", "NativeIntegerAttribute"); private static CSharpAttributeData GetAttribute(ImmutableArray attributes, string namespaceName, string name) { foreach (var attribute in attributes) { var containingType = attribute.AttributeConstructor.ContainingType; if (containingType.Name == name && containingType.ContainingNamespace.QualifiedName == namespaceName) { return attribute; } } return null; } } }