|
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class Binder
{
#region Bind All Attributes
// Method to bind attributes types early for all attributes to enable early decoding of some well-known attributes used within the binder.
// Note: attributesToBind contains merged attributes from all the different syntax locations (e.g. for named types, partial methods, etc.).
// Note: Additionally, the attributes with non-matching target specifier for the given owner symbol have been filtered out, i.e. Binder.MatchAttributeTarget method returned true.
// For example, if were binding attributes on delegate type symbol for below code snippet:
// [A1]
// [return: A2]
// public delegate void Goo();
// attributesToBind will only contain first attribute syntax.
internal static void BindAttributeTypes(
ImmutableArray<Binder> binders, ImmutableArray<AttributeSyntax> attributesToBind, Symbol ownerSymbol, NamedTypeSymbol[] boundAttributeTypes,
Action<AttributeSyntax>? beforeAttributePartBound,
Action<AttributeSyntax>? afterAttributePartBound,
BindingDiagnosticBag diagnostics)
{
Debug.Assert(binders.Any());
Debug.Assert(attributesToBind.Any());
Debug.Assert((object)ownerSymbol != null);
Debug.Assert(binders.Length == attributesToBind.Length);
RoslynDebug.Assert(boundAttributeTypes != null);
for (int i = 0; i < attributesToBind.Length; i++)
{
// Some types may have been bound by an earlier stage.
if (boundAttributeTypes[i] is null)
{
var binder = binders[i];
AttributeSyntax attributeToBind = attributesToBind[i];
beforeAttributePartBound?.Invoke(attributeToBind);
// BindType for AttributeSyntax's name is handled specially during lookup, see Binder.LookupAttributeType.
// When looking up a name in attribute type context, we generate a diagnostic + error type if it is not an attribute type, i.e. named type deriving from System.Attribute.
// Hence we can assume here that BindType returns a NamedTypeSymbol.
var boundType = binder.BindType(attributeToBind.Name, diagnostics);
var boundTypeSymbol = (NamedTypeSymbol)boundType.Type;
// Check the attribute type (unless the attribute type is already an error).
if (boundTypeSymbol.TypeKind != TypeKind.Error)
{
binder.CheckDisallowedAttributeDependentType(boundType, attributeToBind.Name, diagnostics);
}
boundAttributeTypes[i] = boundTypeSymbol;
afterAttributePartBound?.Invoke(attributeToBind);
}
}
}
// Method to bind all attributes (attribute arguments and constructor)
internal static void GetAttributes(
ImmutableArray<Binder> binders,
ImmutableArray<AttributeSyntax> attributesToBind,
ImmutableArray<NamedTypeSymbol> boundAttributeTypes,
CSharpAttributeData?[] attributeDataArray,
BoundAttribute?[]? boundAttributeArray,
Action<AttributeSyntax>? beforeAttributePartBound,
Action<AttributeSyntax>? afterAttributePartBound,
BindingDiagnosticBag diagnostics)
{
Debug.Assert(binders.Any());
Debug.Assert(attributesToBind.Any());
Debug.Assert(boundAttributeTypes.Any());
Debug.Assert(binders.Length == attributesToBind.Length);
Debug.Assert(boundAttributeTypes.Length == attributesToBind.Length);
RoslynDebug.Assert(attributeDataArray != null);
for (int i = 0; i < attributesToBind.Length; i++)
{
AttributeSyntax attributeSyntax = attributesToBind[i];
NamedTypeSymbol boundAttributeType = boundAttributeTypes[i];
Binder binder = binders[i];
var attribute = (SourceAttributeData?)attributeDataArray[i];
if (attribute == null)
{
(attributeDataArray[i], var boundAttribute) = binder.GetAttribute(attributeSyntax, boundAttributeType, beforeAttributePartBound, afterAttributePartBound, diagnostics);
if (boundAttributeArray is not null)
{
boundAttributeArray[i] = boundAttribute;
}
}
else
{
Debug.Assert(boundAttributeArray is null || boundAttributeArray[i] is not null);
// attributesBuilder might contain some early bound well-known attributes, which had no errors.
// We don't rebind the early bound attributes, but need to compute isConditionallyOmitted.
// Note that AttributeData.IsConditionallyOmitted is required only during emit, but must be computed here as
// its value depends on the values of conditional symbols, which in turn depends on the source file where the attribute is applied.
Debug.Assert(!attribute.HasErrors);
Debug.Assert(attribute.AttributeClass is object);
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = binder.GetNewCompoundUseSiteInfo(diagnostics);
bool isConditionallyOmitted = binder.IsAttributeConditionallyOmitted(attribute.AttributeClass, attributeSyntax.SyntaxTree, ref useSiteInfo);
diagnostics.Add(attributeSyntax, useSiteInfo);
attributeDataArray[i] = attribute.WithOmittedCondition(isConditionallyOmitted);
}
}
}
#endregion
#region Bind Single Attribute
internal (CSharpAttributeData, BoundAttribute) GetAttribute(
AttributeSyntax node, NamedTypeSymbol boundAttributeType,
Action<AttributeSyntax>? beforeAttributePartBound,
Action<AttributeSyntax>? afterAttributePartBound,
BindingDiagnosticBag diagnostics)
{
beforeAttributePartBound?.Invoke(node);
var boundAttribute = new ExecutableCodeBinder(node, this.ContainingMemberOrLambda, this).BindAttribute(node, boundAttributeType, (this as ContextualAttributeBinder)?.AttributedMember, diagnostics);
afterAttributePartBound?.Invoke(node);
return (GetAttribute(boundAttribute, diagnostics), boundAttribute);
}
internal BoundAttribute BindAttribute(AttributeSyntax node, NamedTypeSymbol attributeType, Symbol? attributedMember, BindingDiagnosticBag diagnostics)
{
return BindAttributeCore(this.GetRequiredBinder(node), node, attributeType, attributedMember, diagnostics);
}
private Binder SkipSemanticModelBinder()
{
Binder result = this;
while (result.IsSemanticModelBinder)
{
result = result.Next!;
}
return result;
}
private static BoundAttribute BindAttributeCore(Binder binder, AttributeSyntax node, NamedTypeSymbol attributeType, Symbol? attributedMember, BindingDiagnosticBag diagnostics)
{
Debug.Assert(binder.SkipSemanticModelBinder() == binder.GetRequiredBinder(node).SkipSemanticModelBinder());
binder = binder.WithAdditionalFlags(BinderFlags.AttributeArgument);
// If attribute name bound to an error type with a single named type
// candidate symbol, we want to bind the attribute constructor
// and arguments with that named type to generate better semantic info.
// CONSIDER: Do we need separate code paths for IDE and
// CONSIDER: batch compilation scenarios? Above mentioned scenario
// CONSIDER: is not useful for batch compilation.
NamedTypeSymbol attributeTypeForBinding = attributeType;
LookupResultKind resultKind = LookupResultKind.Viable;
if (attributeTypeForBinding.IsErrorType())
{
var errorType = (ErrorTypeSymbol)attributeTypeForBinding;
resultKind = errorType.ResultKind;
if (errorType.CandidateSymbols.Length == 1 && errorType.CandidateSymbols[0] is NamedTypeSymbol)
{
attributeTypeForBinding = (NamedTypeSymbol)errorType.CandidateSymbols[0];
}
}
// Bind constructor and named attribute arguments using the attribute binder
var argumentListOpt = node.ArgumentList;
AnalyzedAttributeArguments analyzedArguments = binder.BindAttributeArguments(argumentListOpt, attributeTypeForBinding, diagnostics);
ImmutableArray<int> argsToParamsOpt;
bool expanded = false;
BitVector defaultArguments = default;
MethodSymbol? attributeConstructor = null;
ImmutableArray<BoundExpression> boundConstructorArguments;
if (attributeTypeForBinding.IsErrorType())
{
boundConstructorArguments = analyzedArguments.ConstructorArguments.Arguments.SelectAsArray(
static (arg, binder) => binder.BindToTypeForErrorRecovery(arg),
binder);
argsToParamsOpt = default;
}
else
{
bool found = binder.TryPerformConstructorOverloadResolution(
attributeTypeForBinding,
analyzedArguments.ConstructorArguments,
attributeTypeForBinding.Name,
node.Location,
suppressResultDiagnostics: attributeType.IsErrorType(),
diagnostics,
out var memberResolutionResult,
out var candidateConstructors,
allowProtectedConstructorsOfBaseType: true,
out CompoundUseSiteInfo<AssemblySymbol> overloadResolutionUseSiteInfo);
ReportConstructorUseSiteDiagnostics(node.Location, diagnostics, suppressUnsupportedRequiredMembersError: false, in overloadResolutionUseSiteInfo);
if (memberResolutionResult.IsNotNull)
{
binder.CheckAndCoerceArguments<MethodSymbol>(node, memberResolutionResult, analyzedArguments.ConstructorArguments, diagnostics, receiver: null, invokedAsExtensionMethod: false, out argsToParamsOpt);
}
else
{
argsToParamsOpt = memberResolutionResult.Result.ArgsToParamsOpt;
}
attributeConstructor = memberResolutionResult.Member;
expanded = memberResolutionResult.Resolution == MemberResolutionKind.ApplicableInExpandedForm;
if (!found)
{
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = binder.GetNewCompoundUseSiteInfo(diagnostics);
resultKind = resultKind.WorseResultKind(
memberResolutionResult.IsValid && !binder.IsConstructorAccessible(memberResolutionResult.Member, ref useSiteInfo) ?
LookupResultKind.Inaccessible :
LookupResultKind.OverloadResolutionFailure);
boundConstructorArguments = binder.BuildArgumentsForErrorRecovery(analyzedArguments.ConstructorArguments, candidateConstructors);
diagnostics.Add(node, useSiteInfo);
}
else
{
binder.BindDefaultArguments(
node,
attributeConstructor.Parameters,
analyzedArguments.ConstructorArguments.Arguments,
argumentRefKindsBuilder: null,
analyzedArguments.ConstructorArguments.Names,
ref argsToParamsOpt,
out defaultArguments,
expanded,
enableCallerInfo: !binder.IsEarlyAttributeBinder,
diagnostics,
attributedMember: attributedMember);
boundConstructorArguments = analyzedArguments.ConstructorArguments.Arguments.ToImmutable();
if (attributeConstructor.Parameters.Any(static p => p.RefKind is RefKind.In or RefKind.RefReadOnlyParameter))
{
Error(diagnostics, ErrorCode.ERR_AttributeCtorInParameter, node, attributeConstructor.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat));
}
}
}
Debug.Assert(boundConstructorArguments.All(a => !a.NeedsToBeConverted()));
ImmutableArray<string?> boundConstructorArgumentNamesOpt = analyzedArguments.ConstructorArguments.GetNames();
// We delay reporting required member errors because:
// 1. If we didn't do it lazily during early attribute binding, we'd cause a cycle.
// 2. If we do it lazily during early attribute binding, we force every early-bound attribute to rebind later because the lazy diagnostic can't be computed yet.
// So instead, we report the error in `LoadAndValidateAttributes` after all attributes have been bound.
ImmutableArray<BoundAssignmentOperator> boundNamedArguments = analyzedArguments.NamedArguments?.ToImmutableAndFree() ?? ImmutableArray<BoundAssignmentOperator>.Empty;
Debug.Assert(boundNamedArguments.All(arg => !arg.Right.NeedsToBeConverted()));
analyzedArguments.ConstructorArguments.Free();
return new BoundAttribute(
node,
attributeConstructor,
boundConstructorArguments,
boundConstructorArgumentNamesOpt,
argsToParamsOpt,
expanded,
defaultArguments,
boundNamedArguments,
resultKind,
attributeType,
hasErrors: resultKind != LookupResultKind.Viable);
}
private CSharpAttributeData GetAttribute(BoundAttribute boundAttribute, BindingDiagnosticBag diagnostics)
{
var attributeType = (NamedTypeSymbol)boundAttribute.Type;
var attributeConstructor = boundAttribute.Constructor;
RoslynDebug.Assert((object)attributeType != null);
Debug.Assert(boundAttribute.Syntax.Kind() == SyntaxKind.Attribute);
bool hasErrors = boundAttribute.HasAnyErrors;
if (attributeType.IsErrorType() || attributeType.IsAbstract || attributeConstructor is null)
{
// prevent cascading diagnostics
Debug.Assert(hasErrors);
return new SourceAttributeData(Compilation, (AttributeSyntax)boundAttribute.Syntax, attributeType, attributeConstructor, hasErrors);
}
// Validate attribute constructor parameters have valid attribute parameter type
ValidateTypeForAttributeParameters(attributeConstructor.Parameters, ((AttributeSyntax)boundAttribute.Syntax).Name, diagnostics, ref hasErrors);
// Validate the attribute arguments and generate TypedConstant for argument's BoundExpression.
var visitor = new AttributeExpressionVisitor(this);
var arguments = boundAttribute.ConstructorArguments;
var constructorArgsArray = visitor.VisitArguments(arguments, diagnostics, ref hasErrors);
var namedArguments = visitor.VisitNamedArguments(boundAttribute.NamedArguments, diagnostics, ref hasErrors);
Debug.Assert(!constructorArgsArray.IsDefault, "Property of VisitArguments");
ImmutableArray<int> argsToParamsOpt = boundAttribute.ConstructorArgumentsToParamsOpt;
ImmutableArray<TypedConstant> rewrittenArguments;
if (hasErrors || attributeConstructor.ParameterCount == 0)
{
rewrittenArguments = constructorArgsArray;
}
else
{
rewrittenArguments = GetRewrittenAttributeConstructorArguments(
attributeConstructor,
constructorArgsArray,
(AttributeSyntax)boundAttribute.Syntax,
argsToParamsOpt,
diagnostics,
ref hasErrors);
// Arguments and parameters length are only required to match when the attribute doesn't have errors.
Debug.Assert(rewrittenArguments.Length == attributeConstructor.ParameterCount);
}
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
bool isConditionallyOmitted = IsAttributeConditionallyOmitted(attributeType, boundAttribute.SyntaxTree, ref useSiteInfo);
diagnostics.Add(boundAttribute.Syntax, useSiteInfo);
return new SourceAttributeData(
Compilation,
(AttributeSyntax)boundAttribute.Syntax,
attributeType,
attributeConstructor,
rewrittenArguments,
makeSourceIndices(),
namedArguments,
hasErrors,
isConditionallyOmitted);
ImmutableArray<int> makeSourceIndices()
{
var lengthAfterRewriting = rewrittenArguments.Length;
if (lengthAfterRewriting == 0 || hasErrors)
{
return default;
}
Debug.Assert(arguments.Count(a => a.IsParamsArrayOrCollection) == (boundAttribute.ConstructorExpanded ? 1 : 0));
// make source indices if we have anything that doesn't map 1:1 from arguments to parameters:
// 1. implicit default arguments
// 2. reordered labeled arguments
// 3. expanded params calls
var defaultArguments = boundAttribute.ConstructorDefaultArguments;
if (argsToParamsOpt.IsDefault && !boundAttribute.ConstructorExpanded)
{
var hasDefaultArgument = false;
var lengthBeforeRewriting = arguments.Length;
for (var i = 0; i < lengthBeforeRewriting; i++)
{
if (defaultArguments[i])
{
hasDefaultArgument = true;
break;
}
}
if (!hasDefaultArgument)
{
return default;
}
}
Debug.Assert(argsToParamsOpt.IsDefault
|| argsToParamsOpt.Length == lengthAfterRewriting);
var constructorArgumentSourceIndices = ArrayBuilder<int>.GetInstance(lengthAfterRewriting);
constructorArgumentSourceIndices.Count = lengthAfterRewriting;
for (int argIndex = 0; argIndex < lengthAfterRewriting; argIndex++)
{
Debug.Assert(!arguments[argIndex].IsParamsArrayOrCollection || arguments[argIndex] is BoundArrayCreation);
int paramIndex = argsToParamsOpt.IsDefault ? argIndex : argsToParamsOpt[argIndex];
constructorArgumentSourceIndices[paramIndex] =
defaultArguments[argIndex] ||
(arguments[argIndex].IsParamsArrayOrCollection && arguments[argIndex] is BoundArrayCreation { Bounds: [BoundLiteral { ConstantValueOpt.Value: 0 }] }) ?
-1 : argIndex;
}
return constructorArgumentSourceIndices.ToImmutableAndFree();
}
}
private void ValidateTypeForAttributeParameters(ImmutableArray<ParameterSymbol> parameters, CSharpSyntaxNode syntax, BindingDiagnosticBag diagnostics, ref bool hasErrors)
{
foreach (var parameter in parameters)
{
var paramType = parameter.TypeWithAnnotations;
Debug.Assert(paramType.HasType);
if (!paramType.Type.IsValidAttributeParameterType(Compilation))
{
Error(diagnostics, ErrorCode.ERR_BadAttributeParamType, syntax, parameter.Name, paramType.Type);
hasErrors = true;
}
}
}
protected bool IsAttributeConditionallyOmitted(NamedTypeSymbol attributeType, SyntaxTree? syntaxTree, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
// When early binding attributes, we don't want to determine if the attribute type is conditional and if so, must be emitted or not.
// Invoking IsConditional property on attributeType can lead to a cycle, hence we delay this computation until after early binding.
if (IsEarlyAttributeBinder)
{
return false;
}
Debug.Assert((object)attributeType != null);
Debug.Assert(!attributeType.IsErrorType());
if (attributeType.IsConditional)
{
ImmutableArray<string> conditionalSymbols = attributeType.GetAppliedConditionalSymbols();
Debug.Assert(conditionalSymbols != null);
if (syntaxTree.IsAnyPreprocessorSymbolDefined(conditionalSymbols))
{
return false;
}
var baseType = attributeType.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo);
if ((object)baseType != null && baseType.IsConditional)
{
return IsAttributeConditionallyOmitted(baseType, syntaxTree, ref useSiteInfo);
}
return true;
}
else
{
return false;
}
}
/// <summary>
/// The caller is responsible for freeing <see cref="AnalyzedAttributeArguments.ConstructorArguments"/> and <see cref="AnalyzedAttributeArguments.NamedArguments"/>.
/// </summary>
private AnalyzedAttributeArguments BindAttributeArguments(
AttributeArgumentListSyntax? attributeArgumentList,
NamedTypeSymbol attributeType,
BindingDiagnosticBag diagnostics)
{
var boundConstructorArguments = AnalyzedArguments.GetInstance();
ArrayBuilder<BoundAssignmentOperator>? boundNamedArgumentsBuilder = null;
if (attributeArgumentList != null)
{
HashSet<string>? boundNamedArgumentsSet = null;
// Only report the first "non-trailing named args required C# 7.2" error,
// so as to avoid "cascading" errors.
bool hadLangVersionError = false;
var shouldHaveName = false;
foreach (var argument in attributeArgumentList.Arguments)
{
if (argument.NameEquals == null)
{
if (shouldHaveName)
{
diagnostics.Add(ErrorCode.ERR_NamedArgumentExpected, argument.Expression.GetLocation());
}
// Constructor argument
this.BindArgumentAndName(
boundConstructorArguments,
diagnostics,
ref hadLangVersionError,
argument,
BindArgumentExpression(diagnostics, argument.Expression, RefKind.None, allowArglist: false),
argument.NameColon,
refKind: RefKind.None);
}
else
{
shouldHaveName = true;
// Named argument
// TODO: use fully qualified identifier name for boundNamedArgumentsSet
string argumentName = argument.NameEquals.Name.Identifier.ValueText!;
if (boundNamedArgumentsBuilder == null)
{
boundNamedArgumentsBuilder = ArrayBuilder<BoundAssignmentOperator>.GetInstance();
boundNamedArgumentsSet = new HashSet<string>();
}
else if (boundNamedArgumentsSet!.Contains(argumentName))
{
// Duplicate named argument
Error(diagnostics, ErrorCode.ERR_DuplicateNamedAttributeArgument, argument, argumentName);
}
BoundAssignmentOperator boundNamedArgument = BindNamedAttributeArgument(argument, attributeType, diagnostics);
boundNamedArgumentsBuilder.Add(boundNamedArgument);
boundNamedArgumentsSet.Add(argumentName);
}
}
}
return new AnalyzedAttributeArguments(boundConstructorArguments, boundNamedArgumentsBuilder);
}
private BoundAssignmentOperator BindNamedAttributeArgument(AttributeArgumentSyntax namedArgument, NamedTypeSymbol attributeType, BindingDiagnosticBag diagnostics)
{
Debug.Assert(namedArgument.NameEquals is not null);
IdentifierNameSyntax nameSyntax = namedArgument.NameEquals.Name;
if (attributeType.IsErrorType())
{
var badLHS = BadExpression(nameSyntax, lookupResultKind: LookupResultKind.Empty);
var rhs = BindRValueWithoutTargetType(namedArgument.Expression, diagnostics);
return new BoundAssignmentOperator(namedArgument, badLHS, rhs, CreateErrorType());
}
bool wasError;
LookupResultKind resultKind;
Symbol namedArgumentNameSymbol = BindNamedAttributeArgumentName(namedArgument, attributeType, diagnostics, out wasError, out resultKind);
ReportDiagnosticsIfObsolete(diagnostics, namedArgumentNameSymbol, namedArgument, hasBaseReceiver: false);
if (namedArgumentNameSymbol.Kind == SymbolKind.Property)
{
var propertySymbol = (PropertySymbol)namedArgumentNameSymbol;
var setMethod = propertySymbol.GetOwnOrInheritedSetMethod();
if (setMethod != null)
{
ReportDiagnosticsIfObsolete(diagnostics, setMethod, namedArgument, hasBaseReceiver: false);
if (setMethod.IsInitOnly && setMethod.DeclaringCompilation != this.Compilation)
{
// an error would have already been reported on declaring an init-only setter
CheckFeatureAvailability(namedArgument, MessageID.IDS_FeatureInitOnlySetters, diagnostics);
}
}
}
Debug.Assert(resultKind == LookupResultKind.Viable || wasError);
TypeSymbol namedArgumentType;
if (wasError)
{
namedArgumentType = CreateErrorType(); // don't generate cascaded errors.
}
else
{
namedArgumentType = BindNamedAttributeArgumentType(namedArgument, namedArgumentNameSymbol, attributeType, diagnostics);
}
// BindRValue just binds the expression without doing any validation (if its a valid expression for attribute argument).
// Validation is done later by AttributeExpressionVisitor
BoundExpression namedArgumentValue = this.BindValue(namedArgument.Expression, diagnostics, BindValueKind.RValue);
namedArgumentValue = GenerateConversionForAssignment(namedArgumentType, namedArgumentValue, diagnostics);
// TODO: should we create an entry even if there are binding errors?
var fieldSymbol = namedArgumentNameSymbol as FieldSymbol;
BoundExpression lvalue;
if (fieldSymbol is object)
{
var containingAssembly = fieldSymbol.ContainingAssembly as SourceAssemblySymbol;
// We do not want to generate any unassigned field or unreferenced field diagnostics.
containingAssembly?.NoteFieldAccess(fieldSymbol, read: true, write: true);
lvalue = new BoundFieldAccess(nameSyntax, null, fieldSymbol, ConstantValue.NotAvailable, resultKind, fieldSymbol.Type);
}
else
{
var propertySymbol = namedArgumentNameSymbol as PropertySymbol;
if (propertySymbol is object)
{
lvalue = new BoundPropertyAccess(nameSyntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, propertySymbol, autoPropertyAccessorKind: AccessorKind.Unknown, resultKind, namedArgumentType);
}
else
{
lvalue = BadExpression(nameSyntax, resultKind);
}
}
return new BoundAssignmentOperator(namedArgument, lvalue, namedArgumentValue, namedArgumentType);
}
private Symbol BindNamedAttributeArgumentName(AttributeArgumentSyntax namedArgument, NamedTypeSymbol attributeType, BindingDiagnosticBag diagnostics, out bool wasError, out LookupResultKind resultKind)
{
RoslynDebug.Assert(namedArgument.NameEquals is object);
var identifierName = namedArgument.NameEquals.Name;
var name = identifierName.Identifier.ValueText;
LookupResult result = LookupResult.GetInstance();
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
this.LookupMembersWithFallback(result, attributeType, name, 0, ref useSiteInfo);
diagnostics.Add(identifierName, useSiteInfo);
Symbol resultSymbol = this.ResultSymbol(result, name, 0, identifierName, diagnostics, false, out wasError, qualifierOpt: null);
resultKind = result.Kind;
result.Free();
return resultSymbol;
}
private TypeSymbol BindNamedAttributeArgumentType(AttributeArgumentSyntax namedArgument, Symbol namedArgumentNameSymbol, NamedTypeSymbol attributeType, BindingDiagnosticBag diagnostics)
{
if (namedArgumentNameSymbol.Kind == SymbolKind.ErrorType)
{
return (TypeSymbol)namedArgumentNameSymbol;
}
// SPEC: For each named-argument Arg in named-argument-list N:
// SPEC: Let Name be the identifier of the named-argument Arg.
// SPEC: Name must identify a non-static read-write public field or property on
// SPEC: attribute class T. If T has no such field or property, then a compile-time error occurs.
bool invalidNamedArgument = false;
TypeSymbol? namedArgumentType = null;
invalidNamedArgument |= (namedArgumentNameSymbol.DeclaredAccessibility != Accessibility.Public);
invalidNamedArgument |= namedArgumentNameSymbol.IsStatic;
if (!invalidNamedArgument)
{
switch (namedArgumentNameSymbol.Kind)
{
case SymbolKind.Field:
var fieldSymbol = (FieldSymbol)namedArgumentNameSymbol;
namedArgumentType = fieldSymbol.Type;
invalidNamedArgument |= fieldSymbol.IsReadOnly;
invalidNamedArgument |= fieldSymbol.IsConst;
break;
case SymbolKind.Property:
var propertySymbol = ((PropertySymbol)namedArgumentNameSymbol).GetLeastOverriddenProperty(this.ContainingType);
namedArgumentType = propertySymbol.Type;
invalidNamedArgument |= propertySymbol.IsReadOnly;
var getMethod = propertySymbol.GetMethod;
var setMethod = propertySymbol.SetMethod;
invalidNamedArgument = invalidNamedArgument || (object)getMethod == null || (object)setMethod == null;
if (!invalidNamedArgument)
{
invalidNamedArgument =
getMethod!.DeclaredAccessibility != Accessibility.Public ||
setMethod!.DeclaredAccessibility != Accessibility.Public;
}
break;
default:
invalidNamedArgument = true;
break;
}
}
if (invalidNamedArgument)
{
RoslynDebug.Assert(namedArgument.NameEquals is object);
return new ExtendedErrorTypeSymbol(attributeType,
namedArgumentNameSymbol,
LookupResultKind.NotAVariable,
diagnostics.Add(ErrorCode.ERR_BadNamedAttributeArgument,
namedArgument.NameEquals.Name.Location,
namedArgumentNameSymbol.Name));
}
RoslynDebug.Assert(namedArgumentType is object);
if (!namedArgumentType.IsValidAttributeParameterType(Compilation))
{
RoslynDebug.Assert(namedArgument.NameEquals is object);
return new ExtendedErrorTypeSymbol(attributeType,
namedArgumentNameSymbol,
LookupResultKind.NotAVariable,
diagnostics.Add(ErrorCode.ERR_BadNamedAttributeArgumentType,
namedArgument.NameEquals.Name.Location,
namedArgumentNameSymbol.Name));
}
return namedArgumentType;
}
/// <summary>
/// Gets the rewritten attribute constructor arguments, i.e. the arguments
/// are in the order of parameters, which may differ from the source
/// if named constructor arguments are used.
///
/// For example:
/// void Goo(int x, int y, int z, int w = 3);
///
/// Goo(0, z: 2, y: 1);
///
/// Arguments returned: 0, 1, 2, 3
/// </summary>
/// <returns>Rewritten attribute constructor arguments</returns>
private ImmutableArray<TypedConstant> GetRewrittenAttributeConstructorArguments(
MethodSymbol attributeConstructor,
ImmutableArray<TypedConstant> constructorArgsArray,
AttributeSyntax syntax,
ImmutableArray<int> argumentsToParams,
BindingDiagnosticBag diagnostics,
ref bool hasErrors)
{
RoslynDebug.Assert((object)attributeConstructor != null);
Debug.Assert(!constructorArgsArray.IsDefault);
Debug.Assert(!hasErrors);
int argumentsCount = constructorArgsArray.Length;
ImmutableArray<ParameterSymbol> parameters = attributeConstructor.Parameters;
int parameterCount = parameters.Length;
var reorderedArguments = new TypedConstant[parameterCount];
for (int i = 0; i < argumentsCount; i++)
{
var paramIndex = argumentsToParams.IsDefault ? i : argumentsToParams[i];
ParameterSymbol parameter = parameters[paramIndex];
TypedConstant reorderedArgument = constructorArgsArray[i];
if (!hasErrors)
{
if (reorderedArgument.Kind == TypedConstantKind.Error)
{
hasErrors = true;
}
else if (reorderedArgument.Kind == TypedConstantKind.Array &&
parameter.Type.TypeKind == TypeKind.Array &&
!((TypeSymbol)reorderedArgument.TypeInternal!).Equals(parameter.Type, TypeCompareKind.AllIgnoreOptions))
{
// NOTE: As in dev11, we don't allow array covariance conversions (presumably, we don't have a way to
// represent the conversion in metadata).
diagnostics.Add(ErrorCode.ERR_BadAttributeArgument, syntax.Location);
hasErrors = true;
}
}
reorderedArguments[paramIndex] = reorderedArgument;
}
Debug.Assert(hasErrors || reorderedArguments.All(arg => arg.Kind != TypedConstantKind.Error));
return reorderedArguments.AsImmutable();
}
#endregion
#region AttributeExpressionVisitor
/// <summary>
/// Walk a custom attribute argument bound node and return a TypedConstant. Verify that the expression is a constant expression.
/// </summary>
private readonly struct AttributeExpressionVisitor
{
private readonly Binder _binder;
public AttributeExpressionVisitor(Binder binder)
{
_binder = binder;
}
public ImmutableArray<TypedConstant> VisitArguments(ImmutableArray<BoundExpression> arguments, BindingDiagnosticBag diagnostics, ref bool attrHasErrors, bool parentHasErrors = false)
{
var validatedArguments = ImmutableArray<TypedConstant>.Empty;
int numArguments = arguments.Length;
if (numArguments > 0)
{
var builder = ArrayBuilder<TypedConstant>.GetInstance(numArguments);
foreach (var argument in arguments)
{
// current argument has errors if parent had errors OR argument.HasErrors.
bool curArgumentHasErrors = parentHasErrors || argument.HasAnyErrors;
builder.Add(VisitExpression(argument, diagnostics, ref attrHasErrors, curArgumentHasErrors));
}
validatedArguments = builder.ToImmutableAndFree();
}
return validatedArguments;
}
public ImmutableArray<KeyValuePair<string, TypedConstant>> VisitNamedArguments(ImmutableArray<BoundAssignmentOperator> arguments, BindingDiagnosticBag diagnostics, ref bool attrHasErrors)
{
ArrayBuilder<KeyValuePair<string, TypedConstant>>? builder = null;
foreach (var argument in arguments)
{
var kv = VisitNamedArgument(argument, diagnostics, ref attrHasErrors);
if (kv.HasValue)
{
if (builder == null)
{
builder = ArrayBuilder<KeyValuePair<string, TypedConstant>>.GetInstance();
}
builder.Add(kv.Value);
}
}
if (builder == null)
{
return ImmutableArray<KeyValuePair<string, TypedConstant>>.Empty;
}
return builder.ToImmutableAndFree();
}
private KeyValuePair<String, TypedConstant>? VisitNamedArgument(BoundAssignmentOperator assignment, BindingDiagnosticBag diagnostics, ref bool attrHasErrors)
{
KeyValuePair<String, TypedConstant>? visitedArgument = null;
switch (assignment.Left.Kind)
{
case BoundKind.FieldAccess:
var fa = (BoundFieldAccess)assignment.Left;
visitedArgument = new KeyValuePair<String, TypedConstant>(fa.FieldSymbol.Name, VisitExpression(assignment.Right, diagnostics, ref attrHasErrors, assignment.HasAnyErrors));
break;
case BoundKind.PropertyAccess:
var pa = (BoundPropertyAccess)assignment.Left;
visitedArgument = new KeyValuePair<String, TypedConstant>(pa.PropertySymbol.Name, VisitExpression(assignment.Right, diagnostics, ref attrHasErrors, assignment.HasAnyErrors));
break;
}
return visitedArgument;
}
// SPEC: An expression E is an attribute-argument-expression if all of the following statements are true:
// SPEC: 1) The type of E is an attribute parameter type (§17.1.3).
// SPEC: 2) At compile-time, the value of Expression can be resolved to one of the following:
// SPEC: a) A constant value.
// SPEC: b) A System.Type object.
// SPEC: c) A one-dimensional array of attribute-argument-expressions
private TypedConstant VisitExpression(BoundExpression node, BindingDiagnosticBag diagnostics, ref bool attrHasErrors, bool curArgumentHasErrors)
{
// Validate Statement 1) of the spec comment above.
RoslynDebug.Assert(node.Type is object);
var typedConstantKind = node.Type.GetAttributeParameterTypedConstantKind(_binder.Compilation);
return VisitExpression(node, typedConstantKind, diagnostics, ref attrHasErrors, curArgumentHasErrors || typedConstantKind == TypedConstantKind.Error);
}
private TypedConstant VisitExpression(BoundExpression node, TypedConstantKind typedConstantKind, BindingDiagnosticBag diagnostics, ref bool attrHasErrors, bool curArgumentHasErrors)
{
// Validate Statement 2) of the spec comment above.
ConstantValue? constantValue = node.ConstantValueOpt;
if (constantValue != null)
{
if (constantValue.IsBad)
{
typedConstantKind = TypedConstantKind.Error;
}
ConstantValueUtils.CheckLangVersionForConstantValue(node, diagnostics);
return CreateTypedConstant(node, typedConstantKind, diagnostics, ref attrHasErrors, curArgumentHasErrors, simpleValue: constantValue.Value);
}
switch (node.Kind)
{
case BoundKind.Conversion:
return VisitConversion((BoundConversion)node, diagnostics, ref attrHasErrors, curArgumentHasErrors);
case BoundKind.TypeOfOperator:
return VisitTypeOfExpression((BoundTypeOfOperator)node, diagnostics, ref attrHasErrors, curArgumentHasErrors);
case BoundKind.ArrayCreation:
return VisitArrayCreation((BoundArrayCreation)node, diagnostics, ref attrHasErrors, curArgumentHasErrors);
default:
return CreateTypedConstant(node, TypedConstantKind.Error, diagnostics, ref attrHasErrors, curArgumentHasErrors);
}
}
private TypedConstant VisitArrayCollectionExpression(TypeSymbol type, BoundCollectionExpression collection, BindingDiagnosticBag diagnostics, ref bool attrHasErrors, bool curArgumentHasErrors)
{
var typedConstantKind = type.GetAttributeParameterTypedConstantKind(_binder.Compilation);
var elements = collection.Elements;
var builder = ArrayBuilder<TypedConstant>.GetInstance(elements.Length);
foreach (var element in elements)
{
builder.Add(VisitCollectionExpressionElement(element, diagnostics, ref attrHasErrors, curArgumentHasErrors || element.HasAnyErrors));
}
return CreateTypedConstant(collection, typedConstantKind, diagnostics, ref attrHasErrors, curArgumentHasErrors, arrayValue: builder.ToImmutableAndFree());
}
private TypedConstant VisitCollectionExpressionElement(BoundNode node, BindingDiagnosticBag diagnostics, ref bool attrHasErrors, bool curArgumentHasErrors)
{
if (node is BoundCollectionExpressionSpreadElement spread)
{
Binder.Error(diagnostics, ErrorCode.ERR_BadAttributeArgument, node.Syntax);
attrHasErrors = true;
return new TypedConstant(spread.Expression.Type, TypedConstantKind.Error, value: null);
}
return VisitExpression((BoundExpression)node, diagnostics, ref attrHasErrors, curArgumentHasErrors);
}
private TypedConstant VisitConversion(BoundConversion node, BindingDiagnosticBag diagnostics, ref bool attrHasErrors, bool curArgumentHasErrors)
{
Debug.Assert(node.ConstantValueOpt == null);
// We have a bound conversion with a non-constant value.
// According to statement 2) of the spec comment, this is not a valid attribute argument.
// However, native compiler allows conversions to object type if the conversion operand is a valid attribute argument.
// See method AttributeHelper::VerifyAttrArg(EXPR *arg).
// We will match native compiler's behavior here.
// Devdiv Bug #8763: Additionally we allow conversions from array type to object[], provided a conversion exists and each array element is a valid attribute argument.
var type = node.Type;
var operand = node.Operand;
var operandType = operand.Type;
if (node.Conversion.IsCollectionExpression
&& node.Conversion.GetCollectionExpressionTypeKind(out _, out _, out _) == CollectionExpressionTypeKind.Array)
{
Debug.Assert(type.IsSZArray());
return VisitArrayCollectionExpression(type, (BoundCollectionExpression)operand, diagnostics, ref attrHasErrors, curArgumentHasErrors);
}
if ((object)type != null && operandType is object)
{
if (type.SpecialType == SpecialType.System_Object ||
operandType.IsArray() && type.IsArray() &&
((ArrayTypeSymbol)type).ElementType.SpecialType == SpecialType.System_Object)
{
var typedConstantKind = operandType.GetAttributeParameterTypedConstantKind(_binder.Compilation);
return VisitExpression(operand, typedConstantKind, diagnostics, ref attrHasErrors, curArgumentHasErrors);
}
}
return CreateTypedConstant(node, TypedConstantKind.Error, diagnostics, ref attrHasErrors, curArgumentHasErrors);
}
private static TypedConstant VisitTypeOfExpression(BoundTypeOfOperator node, BindingDiagnosticBag diagnostics, ref bool attrHasErrors, bool curArgumentHasErrors)
{
var typeOfArgument = (TypeSymbol?)node.SourceType.Type;
// typeof argument is allowed to be:
// (a) an unbound type
// (b) closed constructed type
// typeof argument cannot be an open type
if (typeOfArgument is object) // skip this if the argument was an alias symbol
{
var isValidArgument = true;
switch (typeOfArgument.Kind)
{
case SymbolKind.TypeParameter:
// type parameter represents an open type
isValidArgument = false;
break;
default:
isValidArgument = typeOfArgument.IsUnboundGenericType() || !typeOfArgument.ContainsTypeParameter();
break;
}
if (!isValidArgument && !curArgumentHasErrors)
{
// attribute argument type cannot be an open type
Binder.Error(diagnostics, ErrorCode.ERR_AttrArgWithTypeVars, node.Syntax, typeOfArgument.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat));
curArgumentHasErrors = true;
attrHasErrors = true;
}
}
return CreateTypedConstant(node, TypedConstantKind.Type, diagnostics, ref attrHasErrors, curArgumentHasErrors, simpleValue: node.SourceType.Type);
}
private TypedConstant VisitArrayCreation(BoundArrayCreation node, BindingDiagnosticBag diagnostics, ref bool attrHasErrors, bool curArgumentHasErrors)
{
ImmutableArray<BoundExpression> bounds = node.Bounds;
int boundsCount = bounds.Length;
if (boundsCount > 1)
{
return CreateTypedConstant(node, TypedConstantKind.Error, diagnostics, ref attrHasErrors, curArgumentHasErrors);
}
var type = (ArrayTypeSymbol)node.Type;
var typedConstantKind = type.GetAttributeParameterTypedConstantKind(_binder.Compilation);
ImmutableArray<TypedConstant> initializer;
if (node.InitializerOpt == null)
{
if (boundsCount == 0)
{
initializer = ImmutableArray<TypedConstant>.Empty;
}
else
{
if (bounds[0].IsDefaultValue())
{
initializer = ImmutableArray<TypedConstant>.Empty;
}
else
{
// error: non-constant array creation
initializer = ImmutableArray.Create(CreateTypedConstant(node, TypedConstantKind.Error, diagnostics, ref attrHasErrors, curArgumentHasErrors));
}
}
}
else
{
initializer = VisitArguments(node.InitializerOpt.Initializers, diagnostics, ref attrHasErrors, curArgumentHasErrors);
}
return CreateTypedConstant(node, typedConstantKind, diagnostics, ref attrHasErrors, curArgumentHasErrors, arrayValue: initializer);
}
private static TypedConstant CreateTypedConstant(BoundExpression node, TypedConstantKind typedConstantKind, BindingDiagnosticBag diagnostics, ref bool attrHasErrors, bool curArgumentHasErrors,
object? simpleValue = null, ImmutableArray<TypedConstant> arrayValue = default(ImmutableArray<TypedConstant>))
{
var type = node.Type;
RoslynDebug.Assert(type is object);
if (typedConstantKind != TypedConstantKind.Error && type.ContainsTypeParameter())
{
// Devdiv Bug #12636: Constant values of open types should not be allowed in attributes
// SPEC ERROR: C# language specification does not explicitly disallow constant values of open types. For e.g.
// public class C<T>
// {
// public enum E { V }
// }
//
// [SomeAttr(C<T>.E.V)] // case (a): Constant value of open type.
// [SomeAttr(C<int>.E.V)] // case (b): Constant value of constructed type.
// Both expressions 'C<T>.E.V' and 'C<int>.E.V' satisfy the requirements for a valid attribute-argument-expression:
// (a) Its type is a valid attribute parameter type as per section 17.1.3 of the specification.
// (b) It has a compile time constant value.
// However, native compiler disallows both the above cases.
// We disallow case (a) as it cannot be serialized correctly, but allow case (b) to compile.
typedConstantKind = TypedConstantKind.Error;
}
if (typedConstantKind == TypedConstantKind.Error)
{
if (!curArgumentHasErrors)
{
Binder.Error(diagnostics, ErrorCode.ERR_BadAttributeArgument, node.Syntax);
attrHasErrors = true;
}
return new TypedConstant(type, TypedConstantKind.Error, null);
}
else if (typedConstantKind == TypedConstantKind.Array)
{
return new TypedConstant(type, arrayValue);
}
else
{
return new TypedConstant(type, typedConstantKind, simpleValue);
}
}
}
#endregion
#region AnalyzedAttributeArguments
private readonly struct AnalyzedAttributeArguments
{
internal readonly AnalyzedArguments ConstructorArguments;
internal readonly ArrayBuilder<BoundAssignmentOperator>? NamedArguments;
internal AnalyzedAttributeArguments(AnalyzedArguments constructorArguments, ArrayBuilder<BoundAssignmentOperator>? namedArguments)
{
this.ConstructorArguments = constructorArguments;
this.NamedArguments = namedArguments;
}
}
#endregion
}
}
|