File: Symbols\Source\QuickAttributeChecker.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    /// <summary>
    /// The QuickAttributeChecker applies a simple fast heuristic for determining probable
    /// attributes of certain kinds without binding attribute types, just by looking at the final syntax of an
    /// attribute usage.
    /// </summary>
    /// <remarks>
    /// It works by maintaining a dictionary of all possible simple names that might map to the given
    /// attribute.
    /// </remarks>
    internal sealed class QuickAttributeChecker
    {
        private readonly Dictionary<string, QuickAttributes> _nameToAttributeMap;
        private static QuickAttributeChecker _lazyPredefinedQuickAttributeChecker;
 
#if DEBUG
        private bool _sealed;
#endif
 
        internal static QuickAttributeChecker Predefined
        {
            get
            {
                if (_lazyPredefinedQuickAttributeChecker is null)
                {
                    Interlocked.CompareExchange(ref _lazyPredefinedQuickAttributeChecker, CreatePredefinedQuickAttributeChecker(), null);
                }
 
                return _lazyPredefinedQuickAttributeChecker;
            }
        }
 
        private static QuickAttributeChecker CreatePredefinedQuickAttributeChecker()
        {
            var result = new QuickAttributeChecker();
            result.AddName(AttributeDescription.TypeIdentifierAttribute.Name, QuickAttributes.TypeIdentifier);
            result.AddName(AttributeDescription.TypeForwardedToAttribute.Name, QuickAttributes.TypeForwardedTo);
            result.AddName(AttributeDescription.AssemblyKeyNameAttribute.Name, QuickAttributes.AssemblyKeyName);
            result.AddName(AttributeDescription.AssemblyKeyFileAttribute.Name, QuickAttributes.AssemblyKeyFile);
            result.AddName(AttributeDescription.AssemblySignatureKeyAttribute.Name, QuickAttributes.AssemblySignatureKey);
 
#if DEBUG
            result._sealed = true;
#endif
            return result;
        }
 
        private QuickAttributeChecker()
        {
            _nameToAttributeMap = new Dictionary<string, QuickAttributes>(StringComparer.Ordinal);
            // NOTE: caller must seal
        }
 
        private QuickAttributeChecker(QuickAttributeChecker previous)
        {
            _nameToAttributeMap = new Dictionary<string, QuickAttributes>(previous._nameToAttributeMap, StringComparer.Ordinal);
            // NOTE: caller must seal
        }
 
        private void AddName(string name, QuickAttributes newAttributes)
        {
#if DEBUG
            Debug.Assert(!_sealed);
#endif
            var currentValue = QuickAttributes.None;
            _nameToAttributeMap.TryGetValue(name, out currentValue);
 
            QuickAttributes newValue = newAttributes | currentValue;
            _nameToAttributeMap[name] = newValue;
        }
 
        internal QuickAttributeChecker AddAliasesIfAny(SyntaxList<UsingDirectiveSyntax> usingsSyntax, bool onlyGlobalAliases = false)
        {
            if (usingsSyntax.Count == 0)
            {
                return this;
            }
 
            QuickAttributeChecker newChecker = null;
 
            foreach (var usingDirective in usingsSyntax)
            {
                if (usingDirective.Alias != null &&
                    usingDirective.Name != null &&
                    (!onlyGlobalAliases || usingDirective.GlobalKeyword.IsKind(SyntaxKind.GlobalKeyword)))
                {
                    string name = usingDirective.Alias.Name.Identifier.ValueText;
                    string target = usingDirective.Name.GetUnqualifiedName().Identifier.ValueText;
 
                    if (_nameToAttributeMap.TryGetValue(target, out var foundAttributes))
                    {
                        // copy the QuickAttributes from alias target to alias name
                        (newChecker ?? (newChecker = new QuickAttributeChecker(this))).AddName(name, foundAttributes);
                    }
                }
            }
 
            if (newChecker != null)
            {
#if DEBUG
                newChecker._sealed = true;
#endif
                return newChecker;
            }
 
            return this;
        }
 
        public bool IsPossibleMatch(AttributeSyntax attr, QuickAttributes pattern)
        {
#if DEBUG
            Debug.Assert(_sealed);
#endif
            string name = attr.Name.GetUnqualifiedName().Identifier.ValueText;
            QuickAttributes foundAttributes;
 
            // We allow "Name" to bind to "NameAttribute"
            if (_nameToAttributeMap.TryGetValue(name, out foundAttributes) ||
                _nameToAttributeMap.TryGetValue(name + "Attribute", out foundAttributes))
            {
                return (foundAttributes & pattern) != 0;
            }
 
            return false;
        }
    }
 
    [Flags]
    internal enum QuickAttributes : byte
    {
        None = 0,
        TypeIdentifier = 1 << 0,
        TypeForwardedTo = 1 << 1,
        AssemblyKeyName = 1 << 2,
        AssemblyKeyFile = 1 << 3,
        AssemblySignatureKey = 1 << 4,
        Last = AssemblySignatureKey,
    }
 
    internal static class QuickAttributeHelpers
    {
        /// <summary>
        /// Returns the <see cref="QuickAttributes"/> that corresponds to the particular type 
        /// <paramref name="name"/> passed in.  If <paramref name="inAttribute"/> is <see langword="true"/>
        /// then the name will be checked both as-is as well as with the 'Attribute' suffix.
        /// </summary>
        public static QuickAttributes GetQuickAttributes(string name, bool inAttribute)
        {
            // Update this code if we add new quick attributes.
            Debug.Assert(QuickAttributes.Last == QuickAttributes.AssemblySignatureKey);
 
            var result = QuickAttributes.None;
            if (matches(AttributeDescription.TypeIdentifierAttribute))
            {
                result |= QuickAttributes.TypeIdentifier;
            }
            else if (matches(AttributeDescription.TypeForwardedToAttribute))
            {
                result |= QuickAttributes.TypeForwardedTo;
            }
            else if (matches(AttributeDescription.AssemblyKeyNameAttribute))
            {
                result |= QuickAttributes.AssemblyKeyName;
            }
            else if (matches(AttributeDescription.AssemblyKeyFileAttribute))
            {
                result |= QuickAttributes.AssemblyKeyFile;
            }
            else if (matches(AttributeDescription.AssemblySignatureKeyAttribute))
            {
                result |= QuickAttributes.AssemblySignatureKey;
            }
 
            return result;
 
            bool matches(AttributeDescription attributeDescription)
            {
                Debug.Assert(attributeDescription.Name.EndsWith(nameof(System.Attribute)));
 
                if (name == attributeDescription.Name)
                {
                    return true;
                }
 
                // In an attribute context the name might be referenced as the full name (like 'TypeForwardedToAttribute')
                // or the short name (like 'TypeForwardedTo').
                if (inAttribute &&
                    (name.Length + nameof(System.Attribute).Length) == attributeDescription.Name.Length &&
                    attributeDescription.Name.StartsWith(name))
                {
                    return true;
                }
 
                return false;
            }
        }
    }
}