File: Symbols\Attributes\SourceAttributeData.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.
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Reflection.Metadata;
using System.Diagnostics.CodeAnalysis;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    /// <summary>
    /// Represents a Source custom attribute specification
    /// </summary>
    internal sealed class SourceAttributeData : CSharpAttributeData
    {
        private readonly CSharpCompilation _compilation;
        private readonly NamedTypeSymbol _attributeClass;
        private readonly MethodSymbol? _attributeConstructor;
        private readonly ImmutableArray<TypedConstant> _constructorArguments;
        private readonly ImmutableArray<int> _constructorArgumentsSourceIndices;
        private readonly ImmutableArray<KeyValuePair<string, TypedConstant>> _namedArguments;
        private readonly bool _isConditionallyOmitted;
        private readonly bool _hasErrors;
        private readonly SyntaxReference _applicationNode;
 
        private SourceAttributeData(
            CSharpCompilation compilation,
            SyntaxReference applicationNode,
            NamedTypeSymbol attributeClass,
            MethodSymbol? attributeConstructor,
            ImmutableArray<TypedConstant> constructorArguments,
            ImmutableArray<int> constructorArgumentsSourceIndices,
            ImmutableArray<KeyValuePair<string, TypedConstant>> namedArguments,
            bool hasErrors,
            bool isConditionallyOmitted)
        {
            Debug.Assert(compilation is object);
            Debug.Assert(applicationNode is object);
            Debug.Assert(!isConditionallyOmitted || attributeClass is object && attributeClass.IsConditional);
            Debug.Assert(!constructorArguments.IsDefault);
            Debug.Assert(!namedArguments.IsDefault);
            Debug.Assert(constructorArgumentsSourceIndices.IsDefault ||
                constructorArgumentsSourceIndices.Any() && constructorArgumentsSourceIndices.Length == constructorArguments.Length);
            Debug.Assert(attributeConstructor is object || hasErrors);
 
            _compilation = compilation;
            _attributeClass = attributeClass;
            _attributeConstructor = attributeConstructor;
            _constructorArguments = constructorArguments;
            _constructorArgumentsSourceIndices = constructorArgumentsSourceIndices;
            _namedArguments = namedArguments;
            _isConditionallyOmitted = isConditionallyOmitted;
            _hasErrors = hasErrors;
            _applicationNode = applicationNode;
        }
 
        internal SourceAttributeData(CSharpCompilation compilation, AttributeSyntax attributeSyntax, NamedTypeSymbol attributeClass, MethodSymbol? attributeConstructor, bool hasErrors)
            : this(
            compilation,
            attributeSyntax,
            attributeClass,
            attributeConstructor,
            constructorArguments: ImmutableArray<TypedConstant>.Empty,
            constructorArgumentsSourceIndices: default,
            namedArguments: ImmutableArray<KeyValuePair<string, TypedConstant>>.Empty,
            hasErrors: hasErrors,
            isConditionallyOmitted: false)
        {
        }
 
        internal SourceAttributeData(
            CSharpCompilation compilation,
            AttributeSyntax attributeSyntax,
            NamedTypeSymbol attributeClass,
            MethodSymbol? attributeConstructor,
            ImmutableArray<TypedConstant> constructorArguments,
            ImmutableArray<int> constructorArgumentsSourceIndices,
            ImmutableArray<KeyValuePair<string, TypedConstant>> namedArguments,
            bool hasErrors,
            bool isConditionallyOmitted)
            : this(compilation, attributeSyntax.GetReference(), attributeClass, attributeConstructor, constructorArguments, constructorArgumentsSourceIndices, namedArguments, hasErrors, isConditionallyOmitted)
        {
        }
 
        public override NamedTypeSymbol AttributeClass
        {
            get
            {
                return _attributeClass;
            }
        }
 
        public override MethodSymbol? AttributeConstructor
        {
            get
            {
                return _attributeConstructor;
            }
        }
 
        public override SyntaxReference ApplicationSyntaxReference
        {
            get
            {
                return _applicationNode;
            }
        }
 
        /// <summary>
        /// If the <see cref="CSharpAttributeData.ConstructorArguments"/> contains any named constructor arguments or default value arguments,
        /// it returns an array representing each argument's source argument index. A value of -1 indicates default value argument.
        /// Otherwise, returns null.
        /// </summary>
        internal ImmutableArray<int> ConstructorArgumentsSourceIndices
        {
            get
            {
                return _constructorArgumentsSourceIndices;
            }
        }
 
        internal CSharpSyntaxNode GetAttributeArgumentSyntax(int parameterIndex)
        {
            // This method is only called when decoding (non-erroneous) well-known attributes.
            Debug.Assert(!this.HasErrors);
            Debug.Assert(this.AttributeConstructor is object);
            Debug.Assert(parameterIndex >= 0);
            Debug.Assert(parameterIndex < this.AttributeConstructor.ParameterCount);
 
            var attributeSyntax = (AttributeSyntax)_applicationNode.GetSyntax();
 
            if (_constructorArgumentsSourceIndices.IsDefault)
            {
                // We have no named ctor arguments AND no default arguments.
                Debug.Assert(attributeSyntax.ArgumentList != null);
                Debug.Assert(this.AttributeConstructor.ParameterCount <= attributeSyntax.ArgumentList.Arguments.Count);
 
                return attributeSyntax.ArgumentList.Arguments[parameterIndex];
            }
            else
            {
                int sourceArgIndex = _constructorArgumentsSourceIndices[parameterIndex];
 
                if (sourceArgIndex == -1)
                {
                    // -1 signifies optional parameter whose default argument is used, or
                    // an empty params array.
                    Debug.Assert(this.AttributeConstructor.Parameters[parameterIndex].IsOptional ||
                                 this.AttributeConstructor.Parameters[parameterIndex].IsParamsArray);
                    return attributeSyntax.Name;
                }
                else
                {
                    Debug.Assert(sourceArgIndex >= 0);
                    Debug.Assert(sourceArgIndex < attributeSyntax.ArgumentList!.Arguments.Count);
                    return attributeSyntax.ArgumentList.Arguments[sourceArgIndex];
                }
            }
        }
 
        internal override Location GetAttributeArgumentLocation(int parameterIndex)
        {
            return GetAttributeArgumentSyntax(parameterIndex).Location;
        }
 
        internal override bool IsConditionallyOmitted
        {
            get
            {
                return _isConditionallyOmitted;
            }
        }
 
        internal SourceAttributeData WithOmittedCondition(bool isConditionallyOmitted)
        {
            if (this.IsConditionallyOmitted == isConditionallyOmitted)
            {
                return this;
            }
            else
            {
                return new SourceAttributeData(this._compilation, this.ApplicationSyntaxReference, this.AttributeClass, this.AttributeConstructor, this.CommonConstructorArguments,
                    this.ConstructorArgumentsSourceIndices, this.CommonNamedArguments, this.HasErrors, isConditionallyOmitted);
            }
        }
 
        [MemberNotNullWhen(false, nameof(AttributeConstructor))]
        internal override bool HasErrors
        {
            get
            {
                return _hasErrors;
            }
        }
 
        internal override DiagnosticInfo? ErrorInfo => null; // Binder reported errors
 
        protected internal sealed override ImmutableArray<TypedConstant> CommonConstructorArguments
        {
            get { return _constructorArguments; }
        }
 
        protected internal sealed override ImmutableArray<KeyValuePair<string, TypedConstant>> CommonNamedArguments
        {
            get { return _namedArguments; }
        }
 
        /// <summary>
        /// This method finds an attribute by metadata name and signature. The algorithm for signature matching is similar to the one
        /// in Module.GetTargetAttributeSignatureIndex. Note, the signature matching is limited to primitive types
        /// and System.Type.  It will not match an arbitrary signature but it is sufficient to match the signatures of the current set of
        /// well known attributes.
        /// </summary>
        /// <param name="description">The attribute to match.</param>
        internal override int GetTargetAttributeSignatureIndex(AttributeDescription description)
        {
            return GetTargetAttributeSignatureIndex(_compilation, AttributeClass, AttributeConstructor, description);
        }
 
        internal static int GetTargetAttributeSignatureIndex(CSharpCompilation compilation, NamedTypeSymbol attributeClass, MethodSymbol? attributeConstructor, AttributeDescription description)
        {
            if (!IsTargetAttribute(attributeClass, description.Namespace, description.Name))
            {
                return -1;
            }
 
            var ctor = attributeConstructor;
 
            // Ensure that the attribute data really has a constructor before comparing the signature.
            if (ctor is null)
            {
                return -1;
            }
 
            // Lazily loaded System.Type type symbol
            TypeSymbol? lazySystemType = null;
 
            ImmutableArray<ParameterSymbol> parameters = ctor.Parameters;
 
            for (int signatureIndex = 0; signatureIndex < description.Signatures.Length; signatureIndex++)
            {
                byte[] targetSignature = description.Signatures[signatureIndex];
 
                if (matches(targetSignature, parameters, ref lazySystemType))
                {
                    return signatureIndex;
                }
            }
 
            return -1;
 
            bool matches(byte[] targetSignature, ImmutableArray<ParameterSymbol> parameters, ref TypeSymbol? lazySystemType)
            {
                if (targetSignature[0] != (byte)SignatureAttributes.Instance)
                {
                    return false;
                }
 
                byte parameterCount = targetSignature[1];
                if (parameterCount != parameters.Length)
                {
                    return false;
                }
 
                if ((SignatureTypeCode)targetSignature[2] != SignatureTypeCode.Void)
                {
                    return false;
                }
 
                int parameterIndex = 0;
                for (int signatureByteIndex = 3; signatureByteIndex < targetSignature.Length; signatureByteIndex++)
                {
                    if (parameterIndex >= parameters.Length)
                    {
                        return false;
                    }
 
                    TypeSymbol parameterType = parameters[parameterIndex].Type;
                    SpecialType specType = parameterType.SpecialType;
                    byte targetType = targetSignature[signatureByteIndex];
 
                    if (targetType == (byte)SignatureTypeCode.TypeHandle)
                    {
                        signatureByteIndex++;
 
                        if (parameterType.Kind != SymbolKind.NamedType && parameterType.Kind != SymbolKind.ErrorType)
                        {
                            return false;
                        }
 
                        var namedType = (NamedTypeSymbol)parameterType;
                        AttributeDescription.TypeHandleTargetInfo targetInfo = AttributeDescription.TypeHandleTargets[targetSignature[signatureByteIndex]];
 
                        // Compare name and containing symbol name. Uses HasNameQualifier
                        // extension method to avoid string allocations.
                        if (!string.Equals(namedType.MetadataName, targetInfo.Name, System.StringComparison.Ordinal) ||
                            !namedType.HasNameQualifier(targetInfo.Namespace))
                        {
                            return false;
                        }
 
                        targetType = (byte)targetInfo.Underlying;
 
                        if (parameterType.IsEnumType())
                        {
                            specType = parameterType.GetEnumUnderlyingType()!.SpecialType;
                        }
                    }
                    else if (targetType != (byte)SignatureTypeCode.SZArray && parameterType.IsArray())
                    {
                        if (targetSignature[signatureByteIndex - 1] != (byte)SignatureTypeCode.SZArray)
                        {
                            return false;
                        }
 
                        specType = ((ArrayTypeSymbol)parameterType).ElementType.SpecialType;
                    }
 
                    switch (targetType)
                    {
                        case (byte)SignatureTypeCode.Boolean:
                            if (specType != SpecialType.System_Boolean)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.Char:
                            if (specType != SpecialType.System_Char)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.SByte:
                            if (specType != SpecialType.System_SByte)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.Byte:
                            if (specType != SpecialType.System_Byte)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.Int16:
                            if (specType != SpecialType.System_Int16)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.UInt16:
                            if (specType != SpecialType.System_UInt16)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.Int32:
                            if (specType != SpecialType.System_Int32)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.UInt32:
                            if (specType != SpecialType.System_UInt32)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.Int64:
                            if (specType != SpecialType.System_Int64)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.UInt64:
                            if (specType != SpecialType.System_UInt64)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.Single:
                            if (specType != SpecialType.System_Single)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.Double:
                            if (specType != SpecialType.System_Double)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.String:
                            if (specType != SpecialType.System_String)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.Object:
                            if (specType != SpecialType.System_Object)
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SerializationTypeCode.Type:
                            lazySystemType ??= compilation.GetWellKnownType(WellKnownType.System_Type);
 
                            if (!TypeSymbol.Equals(parameterType, lazySystemType, TypeCompareKind.ConsiderEverything))
                            {
                                return false;
                            }
                            parameterIndex += 1;
                            break;
 
                        case (byte)SignatureTypeCode.SZArray:
                            // Skip over and check the next byte
                            if (!parameterType.IsArray())
                            {
                                return false;
                            }
                            break;
 
                        default:
                            return false;
                    }
                }
 
                return true;
            }
        }
 
        internal override bool IsTargetAttribute(string namespaceName, string typeName)
        {
            return IsTargetAttribute(AttributeClass, namespaceName, typeName);
        }
 
        /// <summary>
        /// Compares the namespace and type name with the attribute's namespace and type name.
        /// Returns true if they are the same.
        /// </summary>
        internal static bool IsTargetAttribute(NamedTypeSymbol attributeClass, string namespaceName, string typeName)
        {
            Debug.Assert(attributeClass is object);
 
            if (!attributeClass.Name.Equals(typeName))
            {
                return false;
            }
 
            if (attributeClass.IsErrorType() && !(attributeClass is MissingMetadataTypeSymbol))
            {
                // Can't guarantee complete name information.
                return false;
            }
 
            return attributeClass.HasNameQualifier(namespaceName);
        }
    }
}