// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
///
/// A source parameter, potentially with a default value, attributes, etc.
///
internal class SourceComplexParameterSymbol : SourceParameterSymbol, IAttributeTargetSymbol
{
[Flags]
private enum ParameterSyntaxKind : byte
{
Regular = 0,
ParamsParameter = 1,
ExtensionThisParameter = 2,
DefaultParameter = 4,
}
private readonly SyntaxReference _syntaxRef;
private readonly ParameterSyntaxKind _parameterSyntaxKind;
private CustomAttributesBag _lazyCustomAttributesBag;
private ThreeState _lazyHasOptionalAttribute;
protected ConstantValue _lazyDefaultSyntaxValue;
internal SourceComplexParameterSymbol(
Symbol owner,
int ordinal,
TypeSymbolWithAnnotations parameterType,
RefKind refKind,
string name,
ImmutableArray locations,
SyntaxReference syntaxRef,
ConstantValue defaultSyntaxValue,
bool isParams,
bool isExtensionMethodThis)
: base(owner, parameterType, ordinal, refKind, name, locations)
{
Debug.Assert((syntaxRef == null) || (syntaxRef.GetSyntax().IsKind(SyntaxKind.Parameter)));
_lazyHasOptionalAttribute = ThreeState.Unknown;
_syntaxRef = syntaxRef;
if (isParams)
{
_parameterSyntaxKind |= ParameterSyntaxKind.ParamsParameter;
}
if (isExtensionMethodThis)
{
_parameterSyntaxKind |= ParameterSyntaxKind.ExtensionThisParameter;
}
var parameterSyntax = this.CSharpSyntaxNode;
if (parameterSyntax != null && parameterSyntax.Default != null)
{
_parameterSyntaxKind |= ParameterSyntaxKind.DefaultParameter;
}
_lazyDefaultSyntaxValue = defaultSyntaxValue;
}
private Binder ParameterBinderOpt => (ContainingSymbol as LocalFunctionSymbol)?.ParameterBinder;
internal override SyntaxReference SyntaxReference => _syntaxRef;
internal ParameterSyntax CSharpSyntaxNode => (ParameterSyntax)_syntaxRef?.GetSyntax();
internal SyntaxTree SyntaxTree => _syntaxRef == null ? null : _syntaxRef.SyntaxTree;
internal override ConstantValue ExplicitDefaultConstantValue
{
get
{
// Parameter has either default argument syntax or DefaultParameterValue attribute, but not both.
// We separate these since in some scenarios (delegate Invoke methods) we need to suppress syntactic
// default value but use value from pseudo-custom attribute.
//
// For example:
// public delegate void D([Optional, DefaultParameterValue(1)]int a, int b = 2);
//
// Dev11 emits the first parameter as option with default value and the second as regular parameter.
// The syntactic default value is suppressed since additional synthesized parameters are added at the end of the signature.
return DefaultSyntaxValue ?? DefaultValueFromAttributes;
}
}
internal override ConstantValue DefaultValueFromAttributes
{
get
{
ParameterEarlyWellKnownAttributeData data = GetEarlyDecodedWellKnownAttributeData();
return (data != null && data.DefaultParameterValue != ConstantValue.Unset) ? data.DefaultParameterValue : ConstantValue.NotAvailable;
}
}
internal override bool IsIDispatchConstant
=> GetDecodedWellKnownAttributeData()?.HasIDispatchConstantAttribute == true;
internal override bool IsIUnknownConstant
=> GetDecodedWellKnownAttributeData()?.HasIUnknownConstantAttribute == true;
private bool HasCallerLineNumberAttribute
=> GetEarlyDecodedWellKnownAttributeData()?.HasCallerLineNumberAttribute == true;
private bool HasCallerFilePathAttribute
=> GetEarlyDecodedWellKnownAttributeData()?.HasCallerFilePathAttribute == true;
private bool HasCallerMemberNameAttribute
=> GetEarlyDecodedWellKnownAttributeData()?.HasCallerMemberNameAttribute == true;
internal override bool IsCallerLineNumber => HasCallerLineNumberAttribute;
internal override bool IsCallerFilePath => !HasCallerLineNumberAttribute && HasCallerFilePathAttribute;
internal override bool IsCallerMemberName => !HasCallerLineNumberAttribute
&& !HasCallerFilePathAttribute
&& HasCallerMemberNameAttribute;
internal override FlowAnalysisAnnotations FlowAnalysisAnnotations
{
get
{
FlowAnalysisAnnotations? annotations = TryGetExtraAttributeAnnotations();
if (annotations.HasValue)
{
// https://github.com/dotnet/roslyn/issues/30078: Make sure this is covered by test
return annotations.Value;
}
ParameterWellKnownAttributeData attributeData = GetDecodedWellKnownAttributeData();
bool hasEnsuresNotNull = attributeData?.HasEnsuresNotNullAttribute == true;
return FlowAnalysisAnnotationsFacts.Create(
notNullWhenTrue: hasEnsuresNotNull || attributeData?.HasNotNullWhenTrueAttribute == true,
notNullWhenFalse: hasEnsuresNotNull || attributeData?.HasNotNullWhenFalseAttribute == true,
assertsTrue: attributeData?.HasAssertsTrueAttribute == true,
assertsFalse: attributeData?.HasAssertsFalseAttribute == true);
}
}
private ConstantValue DefaultSyntaxValue
{
get
{
if (_lazyDefaultSyntaxValue == ConstantValue.Unset)
{
var diagnostics = DiagnosticBag.GetInstance();
if (Interlocked.CompareExchange(
ref _lazyDefaultSyntaxValue,
MakeDefaultExpression(diagnostics, ParameterBinderOpt),
ConstantValue.Unset) == ConstantValue.Unset)
{
// This is a race condition where the thread that loses
// the compare exchange tries to access the diagnostics
// before the thread which won the race finishes adding
// them. https://github.com/dotnet/roslyn/issues/17243
AddDeclarationDiagnostics(diagnostics);
}
diagnostics.Free();
}
return _lazyDefaultSyntaxValue;
}
}
// If binder is null, then get it from the compilation. Otherwise use the provided binder.
// Don't always get it from the compilation because we might be in a speculative context (local function parameter),
// in which case the declaring compilation is the wrong one.
protected ConstantValue MakeDefaultExpression(DiagnosticBag diagnostics, Binder binder)
{
var parameterSyntax = this.CSharpSyntaxNode;
if (parameterSyntax == null)
{
return ConstantValue.NotAvailable;
}
var defaultSyntax = parameterSyntax.Default;
if (defaultSyntax == null)
{
return ConstantValue.NotAvailable;
}
if (binder == null)
{
var syntaxTree = _syntaxRef.SyntaxTree;
var compilation = this.DeclaringCompilation;
var binderFactory = compilation.GetBinderFactory(syntaxTree);
binder = binderFactory.GetBinder(defaultSyntax);
}
Debug.Assert(binder.GetBinder(defaultSyntax) == null);
Binder binderForDefault = binder.CreateBinderForParameterDefaultValue(this, defaultSyntax);
Debug.Assert(binderForDefault.InParameterDefaultValue);
Debug.Assert(binderForDefault.ContainingMemberOrLambda == ContainingSymbol);
BoundExpression valueBeforeConversion;
BoundExpression convertedExpression = binderForDefault.BindParameterDefaultValue(defaultSyntax, this, diagnostics, out valueBeforeConversion).Value;
if (valueBeforeConversion.HasErrors)
{
return ConstantValue.Bad;
}
bool hasErrors = ParameterHelpers.ReportDefaultParameterErrors(binder, ContainingSymbol, parameterSyntax, this, valueBeforeConversion, diagnostics);
if (hasErrors)
{
return ConstantValue.Bad;
}
// If we have something like M(double? x = 1) then the expression we'll get is (double?)1, which
// does not have a constant value. The constant value we want is (double)1.
// The default literal conversion is an exception: (double)default would give the wrong value for M(double? x = default).
if (convertedExpression.ConstantValue == null && convertedExpression.Kind == BoundKind.Conversion &&
!(valueBeforeConversion.Kind == BoundKind.DefaultExpression && valueBeforeConversion.Type == null))
{
if (parameterType.TypeSymbol.IsNullableType())
{
convertedExpression = binder.GenerateConversionForAssignment(parameterType.TypeSymbol.GetNullableUnderlyingType(),
valueBeforeConversion, diagnostics, isDefaultParameter: true);
}
}
if (parameterType.IsReferenceType &&
convertedExpression.ConstantValue?.IsNull == true &&
!suppressNullableWarning(convertedExpression) &&
DeclaringCompilation.LanguageVersion >= MessageID.IDS_FeatureNullableReferenceTypes.RequiredVersion())
{
// Note: Eagerly calling IsNullable causes a cycle, so we delay the check
diagnostics.Add(new LazyNullAsNonNullableDiagnosticInfo(parameterType), parameterSyntax.Default.Value.Location);
}
// represent default(struct) by a Null constant:
var value = convertedExpression.ConstantValue ?? ConstantValue.Null;
VerifyParamDefaultValueMatchesAttributeIfAny(value, defaultSyntax.Value, diagnostics);
return value;
bool suppressNullableWarning(BoundExpression expr)
{
while (true)
{
switch (expr.Kind)
{
case BoundKind.Conversion:
expr = ((BoundConversion)expr).Operand;
break;
case BoundKind.SuppressNullableWarningExpression:
return true;
default:
return false;
}
}
}
}
public override string MetadataName
{
get
{
// The metadata parameter name should be the name used in the partial definition.
var sourceMethod = this.ContainingSymbol as SourceOrdinaryMethodSymbol;
if ((object)sourceMethod == null)
{
return base.MetadataName;
}
var definition = sourceMethod.SourcePartialDefinition;
if ((object)definition == null)
{
return base.MetadataName;
}
return definition.Parameters[this.Ordinal].MetadataName;
}
}
protected virtual IAttributeTargetSymbol AttributeOwner => this;
IAttributeTargetSymbol IAttributeTargetSymbol.AttributesOwner => AttributeOwner;
AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation => AttributeLocation.Parameter;
AttributeLocation IAttributeTargetSymbol.AllowedAttributeLocations => AttributeLocation.Parameter;
///
/// Symbol to copy bound attributes from, or null if the attributes are not shared among multiple source parameter symbols.
///
///
/// Used for parameters of partial implementation. We bind the attributes only on the definition
/// part and copy them over to the implementation.
///
private SourceParameterSymbol BoundAttributesSource
{
get
{
var sourceMethod = this.ContainingSymbol as SourceOrdinaryMethodSymbol;
if ((object)sourceMethod == null)
{
return null;
}
var impl = sourceMethod.SourcePartialImplementation;
if ((object)impl == null)
{
return null;
}
return (SourceParameterSymbol)impl.Parameters[this.Ordinal];
}
}
internal sealed override SyntaxList AttributeDeclarationList
{
get
{
var syntax = this.CSharpSyntaxNode;
return (syntax != null) ? syntax.AttributeLists : default(SyntaxList);
}
}
///
/// Gets the syntax list of custom attributes that declares attributes for this parameter symbol.
///
internal virtual OneOrMany> GetAttributeDeclarations()
{
// C# spec:
// The attributes on the parameters of the resulting method declaration
// are the combined attributes of the corresponding parameters of the defining
// and the implementing partial method declaration in unspecified order.
// Duplicates are not removed.
SyntaxList attributes = AttributeDeclarationList;
var sourceMethod = this.ContainingSymbol as SourceOrdinaryMethodSymbol;
if ((object)sourceMethod == null)
{
return OneOrMany.Create(attributes);
}
SyntaxList otherAttributes;
// if this is a definition get the implementation and vice versa
SourceOrdinaryMethodSymbol otherPart = sourceMethod.OtherPartOfPartial;
if ((object)otherPart != null)
{
otherAttributes = ((SourceParameterSymbol)otherPart.Parameters[this.Ordinal]).AttributeDeclarationList;
}
else
{
otherAttributes = default(SyntaxList);
}
if (attributes.Equals(default(SyntaxList)))
{
return OneOrMany.Create(otherAttributes);
}
else if (otherAttributes.Equals(default(SyntaxList)))
{
return OneOrMany.Create(attributes);
}
return OneOrMany.Create(ImmutableArray.Create(attributes, otherAttributes));
}
///
/// Returns data decoded from well-known attributes applied to the symbol or null if there are no applied attributes.
///
///
/// Forces binding and decoding of attributes.
///
internal ParameterWellKnownAttributeData GetDecodedWellKnownAttributeData()
{
var attributesBag = _lazyCustomAttributesBag;
if (attributesBag == null || !attributesBag.IsDecodedWellKnownAttributeDataComputed)
{
attributesBag = this.GetAttributesBag();
}
return (ParameterWellKnownAttributeData)attributesBag.DecodedWellKnownAttributeData;
}
///
/// Returns data decoded from special early bound well-known attributes applied to the symbol or null if there are no applied attributes.
///
///
/// Forces binding and decoding of attributes.
///
internal ParameterEarlyWellKnownAttributeData GetEarlyDecodedWellKnownAttributeData()
{
var attributesBag = _lazyCustomAttributesBag;
if (attributesBag == null || !attributesBag.IsEarlyDecodedWellKnownAttributeDataComputed)
{
attributesBag = this.GetAttributesBag();
}
return (ParameterEarlyWellKnownAttributeData)attributesBag.EarlyDecodedWellKnownAttributeData;
}
///
/// Returns a bag of applied custom attributes and data decoded from well-known attributes. Returns null if there are no attributes applied on the symbol.
///
///
/// Forces binding and decoding of attributes.
///
internal sealed override CustomAttributesBag GetAttributesBag()
{
if (_lazyCustomAttributesBag == null || !_lazyCustomAttributesBag.IsSealed)
{
SourceParameterSymbol copyFrom = this.BoundAttributesSource;
// prevent infinite recursion:
Debug.Assert(!ReferenceEquals(copyFrom, this));
bool bagCreatedOnThisThread;
if ((object)copyFrom != null)
{
var attributesBag = copyFrom.GetAttributesBag();
bagCreatedOnThisThread = Interlocked.CompareExchange(ref _lazyCustomAttributesBag, attributesBag, null) == null;
}
else
{
var attributeSyntax = this.GetAttributeDeclarations();
bagCreatedOnThisThread = LoadAndValidateAttributes(attributeSyntax, ref _lazyCustomAttributesBag, binderOpt: ParameterBinderOpt);
}
if (bagCreatedOnThisThread)
{
state.NotePartComplete(CompletionPart.Attributes);
}
}
return _lazyCustomAttributesBag;
}
internal override void EarlyDecodeWellKnownAttributeType(NamedTypeSymbol attributeType, AttributeSyntax attributeSyntax)
{
Debug.Assert(!attributeType.IsErrorType());
// NOTE: OptionalAttribute is decoded specially before any of the other attributes and stored in the parameter
// symbol (rather than in the EarlyWellKnownAttributeData) because it is needed during overload resolution.
if (CSharpAttributeData.IsTargetEarlyAttribute(attributeType, attributeSyntax, AttributeDescription.OptionalAttribute))
{
_lazyHasOptionalAttribute = ThreeState.True;
}
}
internal override void PostEarlyDecodeWellKnownAttributeTypes()
{
if (_lazyHasOptionalAttribute == ThreeState.Unknown)
{
_lazyHasOptionalAttribute = ThreeState.False;
}
base.PostEarlyDecodeWellKnownAttributeTypes();
}
internal override CSharpAttributeData EarlyDecodeWellKnownAttribute(ref EarlyDecodeWellKnownAttributeArguments arguments)
{
if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.DefaultParameterValueAttribute))
{
return EarlyDecodeAttributeForDefaultParameterValue(AttributeDescription.DefaultParameterValueAttribute, ref arguments);
}
else if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.DecimalConstantAttribute))
{
return EarlyDecodeAttributeForDefaultParameterValue(AttributeDescription.DecimalConstantAttribute, ref arguments);
}
else if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.DateTimeConstantAttribute))
{
return EarlyDecodeAttributeForDefaultParameterValue(AttributeDescription.DateTimeConstantAttribute, ref arguments);
}
else if (!IsOnPartialImplementation(arguments.AttributeSyntax))
{
if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.CallerLineNumberAttribute))
{
arguments.GetOrCreateData().HasCallerLineNumberAttribute = true;
}
else if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.CallerFilePathAttribute))
{
arguments.GetOrCreateData().HasCallerFilePathAttribute = true;
}
else if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.CallerMemberNameAttribute))
{
arguments.GetOrCreateData().HasCallerMemberNameAttribute = true;
}
}
return base.EarlyDecodeWellKnownAttribute(ref arguments);
}
private CSharpAttributeData EarlyDecodeAttributeForDefaultParameterValue(AttributeDescription description, ref EarlyDecodeWellKnownAttributeArguments arguments)
{
Debug.Assert(description.Equals(AttributeDescription.DefaultParameterValueAttribute) ||
description.Equals(AttributeDescription.DecimalConstantAttribute) ||
description.Equals(AttributeDescription.DateTimeConstantAttribute));
bool hasAnyDiagnostics;
var attribute = arguments.Binder.GetAttribute(arguments.AttributeSyntax, arguments.AttributeType, out hasAnyDiagnostics);
ConstantValue value;
if (attribute.HasErrors)
{
value = ConstantValue.Bad;
hasAnyDiagnostics = true;
}
else
{
value = DecodeDefaultParameterValueAttribute(description, attribute, arguments.AttributeSyntax, diagnose: false, diagnosticsOpt: null);
}
var paramData = arguments.GetOrCreateData();
if (paramData.DefaultParameterValue == ConstantValue.Unset)
{
paramData.DefaultParameterValue = value;
}
return !hasAnyDiagnostics ? attribute : null;
}
internal override void DecodeWellKnownAttribute(ref DecodeWellKnownAttributeArguments arguments)
{
Debug.Assert((object)arguments.AttributeSyntaxOpt != null);
var attribute = arguments.Attribute;
Debug.Assert(!attribute.HasErrors);
Debug.Assert(arguments.SymbolPart == AttributeLocation.None);
if (attribute.IsTargetAttribute(this, AttributeDescription.DefaultParameterValueAttribute))
{
// Attribute decoded and constant value stored during EarlyDecodeWellKnownAttribute.
DecodeDefaultParameterValueAttribute(AttributeDescription.DefaultParameterValueAttribute, ref arguments);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.DecimalConstantAttribute))
{
// Attribute decoded and constant value stored during EarlyDecodeWellKnownAttribute.
DecodeDefaultParameterValueAttribute(AttributeDescription.DecimalConstantAttribute, ref arguments);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.DateTimeConstantAttribute))
{
// Attribute decoded and constant value stored during EarlyDecodeWellKnownAttribute.
DecodeDefaultParameterValueAttribute(AttributeDescription.DateTimeConstantAttribute, ref arguments);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.OptionalAttribute))
{
Debug.Assert(_lazyHasOptionalAttribute == ThreeState.True);
if (HasDefaultArgumentSyntax)
{
// error CS1745: Cannot specify default parameter value in conjunction with DefaultParameterAttribute or OptionalAttribute
arguments.Diagnostics.Add(ErrorCode.ERR_DefaultValueUsedWithAttributes, arguments.AttributeSyntaxOpt.Name.Location);
}
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.ParamArrayAttribute))
{
// error CS0674: Do not use 'System.ParamArrayAttribute'. Use the 'params' keyword instead.
arguments.Diagnostics.Add(ErrorCode.ERR_ExplicitParamArray, arguments.AttributeSyntaxOpt.Name.Location);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.InAttribute))
{
arguments.GetOrCreateData().HasInAttribute = true;
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.OutAttribute))
{
arguments.GetOrCreateData().HasOutAttribute = true;
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.MarshalAsAttribute))
{
MarshalAsAttributeDecoder.Decode(ref arguments, AttributeTargets.Parameter, MessageProvider.Instance);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.IDispatchConstantAttribute))
{
arguments.GetOrCreateData().HasIDispatchConstantAttribute = true;
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.IUnknownConstantAttribute))
{
arguments.GetOrCreateData().HasIUnknownConstantAttribute = true;
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.CallerLineNumberAttribute))
{
ValidateCallerLineNumberAttribute(arguments.AttributeSyntaxOpt, arguments.Diagnostics);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.CallerFilePathAttribute))
{
ValidateCallerFilePathAttribute(arguments.AttributeSyntaxOpt, arguments.Diagnostics);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.CallerMemberNameAttribute))
{
ValidateCallerMemberNameAttribute(arguments.AttributeSyntaxOpt, arguments.Diagnostics);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.DynamicAttribute))
{
// DynamicAttribute should not be set explicitly.
arguments.Diagnostics.Add(ErrorCode.ERR_ExplicitDynamicAttr, arguments.AttributeSyntaxOpt.Location);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.IsReadOnlyAttribute))
{
// IsReadOnlyAttribute should not be set explicitly.
arguments.Diagnostics.Add(ErrorCode.ERR_ExplicitReservedAttr, arguments.AttributeSyntaxOpt.Location, AttributeDescription.IsReadOnlyAttribute.FullName);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.IsUnmanagedAttribute))
{
// IsUnmanagedAttribute should not be set explicitly.
arguments.Diagnostics.Add(ErrorCode.ERR_ExplicitReservedAttr, arguments.AttributeSyntaxOpt.Location, AttributeDescription.IsUnmanagedAttribute.FullName);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.IsByRefLikeAttribute))
{
// IsByRefLikeAttribute should not be set explicitly.
arguments.Diagnostics.Add(ErrorCode.ERR_ExplicitReservedAttr, arguments.AttributeSyntaxOpt.Location, AttributeDescription.IsByRefLikeAttribute.FullName);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.TupleElementNamesAttribute))
{
arguments.Diagnostics.Add(ErrorCode.ERR_ExplicitTupleElementNamesAttribute, arguments.AttributeSyntaxOpt.Location);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.NullableAttribute))
{
// NullableAttribute should not be set explicitly.
arguments.Diagnostics.Add(ErrorCode.ERR_ExplicitNullableAttribute, arguments.AttributeSyntaxOpt.Location);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.NotNullWhenTrueAttribute))
{
arguments.GetOrCreateData().HasNotNullWhenTrueAttribute = true;
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.NotNullWhenFalseAttribute))
{
arguments.GetOrCreateData().HasNotNullWhenFalseAttribute = true;
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.EnsuresNotNullAttribute))
{
arguments.GetOrCreateData().HasEnsuresNotNullAttribute = true;
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.AssertsTrueAttribute))
{
arguments.GetOrCreateData().HasAssertsTrueAttribute = true;
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.AssertsFalseAttribute))
{
arguments.GetOrCreateData().HasAssertsFalseAttribute = true;
}
}
private void DecodeDefaultParameterValueAttribute(AttributeDescription description, ref DecodeWellKnownAttributeArguments arguments)
{
var attribute = arguments.Attribute;
var syntax = arguments.AttributeSyntaxOpt;
var diagnostics = arguments.Diagnostics;
Debug.Assert(syntax != null);
Debug.Assert(diagnostics != null);
var value = DecodeDefaultParameterValueAttribute(description, attribute, syntax, diagnose: true, diagnosticsOpt: diagnostics);
if (!value.IsBad)
{
VerifyParamDefaultValueMatchesAttributeIfAny(value, syntax, diagnostics);
}
}
///
/// Verify the default value matches the default value from any earlier attribute
/// (DefaultParameterValueAttribute, DateTimeConstantAttribute or DecimalConstantAttribute).
/// If not, report ERR_ParamDefaultValueDiffersFromAttribute.
///
private void VerifyParamDefaultValueMatchesAttributeIfAny(ConstantValue value, CSharpSyntaxNode syntax, DiagnosticBag diagnostics)
{
var data = GetEarlyDecodedWellKnownAttributeData();
if (data != null)
{
var attrValue = data.DefaultParameterValue;
if ((attrValue != ConstantValue.Unset) &&
(value != attrValue))
{
// CS8017: The parameter has multiple distinct default values.
diagnostics.Add(ErrorCode.ERR_ParamDefaultValueDiffersFromAttribute, syntax.Location);
}
}
}
private ConstantValue DecodeDefaultParameterValueAttribute(AttributeDescription description, CSharpAttributeData attribute, AttributeSyntax node, bool diagnose, DiagnosticBag diagnosticsOpt)
{
Debug.Assert(!attribute.HasErrors);
if (description.Equals(AttributeDescription.DefaultParameterValueAttribute))
{
return DecodeDefaultParameterValueAttribute(attribute, node, diagnose, diagnosticsOpt);
}
else if (description.Equals(AttributeDescription.DecimalConstantAttribute))
{
return attribute.DecodeDecimalConstantValue();
}
else
{
Debug.Assert(description.Equals(AttributeDescription.DateTimeConstantAttribute));
return attribute.DecodeDateTimeConstantValue();
}
}
private ConstantValue DecodeDefaultParameterValueAttribute(CSharpAttributeData attribute, AttributeSyntax node, bool diagnose, DiagnosticBag diagnosticsOpt)
{
Debug.Assert(!diagnose || diagnosticsOpt != null);
if (HasDefaultArgumentSyntax)
{
// error CS1745: Cannot specify default parameter value in conjunction with DefaultParameterAttribute or OptionalAttribute
if (diagnose)
{
diagnosticsOpt.Add(ErrorCode.ERR_DefaultValueUsedWithAttributes, node.Name.Location);
}
return ConstantValue.Bad;
}
// BREAK: In dev10, DefaultParameterValueAttribute could not be applied to System.Type or array parameters.
// When this was attempted, dev10 produced CS1909, ERR_DefaultValueBadParamType. Roslyn takes a different
// approach: instead of looking at the parameter type, we look at the argument type. There's nothing wrong
// with providing a default value for a System.Type or array parameter, as long as the default parameter
// is not a System.Type or an array (i.e. null is fine). Since we are no longer interested in the type of
// the parameter, all occurrences of CS1909 have been replaced with CS1910, ERR_DefaultValueBadValueType,
// to indicate that the argument type, rather than the parameter type, is the source of the problem.
Debug.Assert(attribute.CommonConstructorArguments.Length == 1);
// the type of the value is the type of the expression in the attribute:
var arg = attribute.CommonConstructorArguments[0];
SpecialType specialType = arg.Kind == TypedConstantKind.Enum ?
((INamedTypeSymbol)arg.Type).EnumUnderlyingType.SpecialType :
arg.Type.SpecialType;
var compilation = this.DeclaringCompilation;
var constantValueDiscriminator = ConstantValue.GetDiscriminator(specialType);
HashSet useSiteDiagnostics = null;
if (constantValueDiscriminator == ConstantValueTypeDiscriminator.Bad)
{
if (arg.Kind != TypedConstantKind.Array && arg.Value == null)
{
if (this.Type.IsReferenceType)
{
constantValueDiscriminator = ConstantValueTypeDiscriminator.Null;
}
else
{
// error CS1908: The type of the argument to the DefaultParameterValue attribute must match the parameter type
if (diagnose)
{
diagnosticsOpt.Add(ErrorCode.ERR_DefaultValueTypeMustMatch, node.Name.Location);
}
return ConstantValue.Bad;
}
}
else
{
// error CS1910: Argument of type '{0}' is not applicable for the DefaultParameterValue attribute
if (diagnose)
{
diagnosticsOpt.Add(ErrorCode.ERR_DefaultValueBadValueType, node.Name.Location, arg.Type);
}
return ConstantValue.Bad;
}
}
else if (!compilation.Conversions.ClassifyConversionFromType((TypeSymbol)arg.Type, this.Type.TypeSymbol, ref useSiteDiagnostics).Kind.IsImplicitConversion())
{
// error CS1908: The type of the argument to the DefaultParameterValue attribute must match the parameter type
if (diagnose)
{
diagnosticsOpt.Add(ErrorCode.ERR_DefaultValueTypeMustMatch, node.Name.Location);
diagnosticsOpt.Add(node.Name.Location, useSiteDiagnostics);
}
return ConstantValue.Bad;
}
if (diagnose)
{
diagnosticsOpt.Add(node.Name.Location, useSiteDiagnostics);
}
return ConstantValue.Create(arg.Value, constantValueDiscriminator);
}
private bool IsValidCallerInfoContext(AttributeSyntax node) => !ContainingSymbol.IsExplicitInterfaceImplementation()
&& !ContainingSymbol.IsOperator()
&& !IsOnPartialImplementation(node);
///
/// Is the attribute syntax appearing on a parameter of a partial method implementation part?
/// Since attributes are merged between the parts of a partial, we need to look at the syntax where the
/// attribute appeared in the source to see if it corresponds to a partial method implementation part.
///
///
///
private bool IsOnPartialImplementation(AttributeSyntax node)
{
var method = ContainingSymbol as MethodSymbol;
if ((object)method == null) return false;
var impl = method.IsPartialImplementation() ? method : method.PartialImplementationPart;
if ((object)impl == null) return false;
var paramList =
node // AttributeSyntax
.Parent // AttributeListSyntax
.Parent // ParameterSyntax
.Parent as ParameterListSyntax; // ParameterListSyntax
if (paramList == null) return false;
var methDecl = paramList.Parent as MethodDeclarationSyntax;
if (methDecl == null) return false;
foreach (var r in impl.DeclaringSyntaxReferences)
{
if (r.GetSyntax() == methDecl) return true;
}
return false;
}
private void ValidateCallerLineNumberAttribute(AttributeSyntax node, DiagnosticBag diagnostics)
{
CSharpCompilation compilation = this.DeclaringCompilation;
HashSet useSiteDiagnostics = null;
if (!IsValidCallerInfoContext(node))
{
// CS4024: The CallerLineNumberAttribute applied to parameter '{0}' will have no effect because it applies to a
// member that is used in contexts that do not allow optional arguments
diagnostics.Add(ErrorCode.WRN_CallerLineNumberParamForUnconsumedLocation, node.Name.Location, CSharpSyntaxNode.Identifier.ValueText);
}
else if (!compilation.Conversions.HasCallerLineNumberConversion(Type.TypeSymbol, ref useSiteDiagnostics))
{
// CS4017: CallerLineNumberAttribute cannot be applied because there are no standard conversions from type '{0}' to type '{1}'
TypeSymbol intType = compilation.GetSpecialType(SpecialType.System_Int32);
diagnostics.Add(ErrorCode.ERR_NoConversionForCallerLineNumberParam, node.Name.Location, intType, Type.TypeSymbol);
}
else if (!HasExplicitDefaultValue && !ContainingSymbol.IsPartialImplementation()) // attribute applied to parameter without default
{
// Unconsumed location checks happen first, so we require a default value.
// CS4020: The CallerLineNumberAttribute may only be applied to parameters with default values
diagnostics.Add(ErrorCode.ERR_BadCallerLineNumberParamWithoutDefaultValue, node.Name.Location);
}
diagnostics.Add(node.Name.Location, useSiteDiagnostics);
}
private void ValidateCallerFilePathAttribute(AttributeSyntax node, DiagnosticBag diagnostics)
{
CSharpCompilation compilation = this.DeclaringCompilation;
HashSet useSiteDiagnostics = null;
if (!IsValidCallerInfoContext(node))
{
// CS4025: The CallerFilePathAttribute applied to parameter '{0}' will have no effect because it applies to a
// member that is used in contexts that do not allow optional arguments
diagnostics.Add(ErrorCode.WRN_CallerFilePathParamForUnconsumedLocation, node.Name.Location, CSharpSyntaxNode.Identifier.ValueText);
}
else if (!compilation.Conversions.HasCallerInfoStringConversion(Type.TypeSymbol, ref useSiteDiagnostics))
{
// CS4018: CallerFilePathAttribute cannot be applied because there are no standard conversions from type '{0}' to type '{1}'
TypeSymbol stringType = compilation.GetSpecialType(SpecialType.System_String);
diagnostics.Add(ErrorCode.ERR_NoConversionForCallerFilePathParam, node.Name.Location, stringType, Type.TypeSymbol);
}
else if (!HasExplicitDefaultValue && !ContainingSymbol.IsPartialImplementation()) // attribute applied to parameter without default
{
// Unconsumed location checks happen first, so we require a default value.
// CS4021: The CallerFilePathAttribute may only be applied to parameters with default values
diagnostics.Add(ErrorCode.ERR_BadCallerFilePathParamWithoutDefaultValue, node.Name.Location);
}
else if (HasCallerLineNumberAttribute)
{
// CS7082: The CallerFilePathAttribute applied to parameter '{0}' will have no effect. It is overridden by the CallerLineNumberAttribute.
diagnostics.Add(ErrorCode.WRN_CallerLineNumberPreferredOverCallerFilePath, node.Name.Location, CSharpSyntaxNode.Identifier.ValueText);
}
diagnostics.Add(node.Name.Location, useSiteDiagnostics);
}
private void ValidateCallerMemberNameAttribute(AttributeSyntax node, DiagnosticBag diagnostics)
{
CSharpCompilation compilation = this.DeclaringCompilation;
HashSet useSiteDiagnostics = null;
if (!IsValidCallerInfoContext(node))
{
// CS4026: The CallerMemberNameAttribute applied to parameter '{0}' will have no effect because it applies to a
// member that is used in contexts that do not allow optional arguments
diagnostics.Add(ErrorCode.WRN_CallerMemberNameParamForUnconsumedLocation, node.Name.Location, CSharpSyntaxNode.Identifier.ValueText);
}
else if (!compilation.Conversions.HasCallerInfoStringConversion(Type.TypeSymbol, ref useSiteDiagnostics))
{
// CS4019: CallerMemberNameAttribute cannot be applied because there are no standard conversions from type '{0}' to type '{1}'
TypeSymbol stringType = compilation.GetSpecialType(SpecialType.System_String);
diagnostics.Add(ErrorCode.ERR_NoConversionForCallerMemberNameParam, node.Name.Location, stringType, Type.TypeSymbol);
}
else if (!HasExplicitDefaultValue && !ContainingSymbol.IsPartialImplementation()) // attribute applied to parameter without default
{
// Unconsumed location checks happen first, so we require a default value.
// CS4022: The CallerMemberNameAttribute may only be applied to parameters with default values
diagnostics.Add(ErrorCode.ERR_BadCallerMemberNameParamWithoutDefaultValue, node.Name.Location);
}
else if (HasCallerLineNumberAttribute)
{
// CS7081: The CallerMemberNameAttribute applied to parameter '{0}' will have no effect. It is overridden by the CallerLineNumberAttribute.
diagnostics.Add(ErrorCode.WRN_CallerLineNumberPreferredOverCallerMemberName, node.Name.Location, CSharpSyntaxNode.Identifier.ValueText);
}
else if (HasCallerFilePathAttribute)
{
// CS7080: The CallerMemberNameAttribute applied to parameter '{0}' will have no effect. It is overridden by the CallerFilePathAttribute.
diagnostics.Add(ErrorCode.WRN_CallerFilePathPreferredOverCallerMemberName, node.Name.Location, CSharpSyntaxNode.Identifier.ValueText);
}
diagnostics.Add(node.Name.Location, useSiteDiagnostics);
}
internal override void PostDecodeWellKnownAttributes(ImmutableArray boundAttributes, ImmutableArray allAttributeSyntaxNodes, DiagnosticBag diagnostics, AttributeLocation symbolPart, WellKnownAttributeData decodedData)
{
Debug.Assert(!boundAttributes.IsDefault);
Debug.Assert(!allAttributeSyntaxNodes.IsDefault);
Debug.Assert(boundAttributes.Length == allAttributeSyntaxNodes.Length);
Debug.Assert(_lazyCustomAttributesBag != null);
Debug.Assert(_lazyCustomAttributesBag.IsDecodedWellKnownAttributeDataComputed);
Debug.Assert(symbolPart == AttributeLocation.None);
var data = (ParameterWellKnownAttributeData)decodedData;
if (data != null)
{
switch (RefKind)
{
case RefKind.Ref:
if (data.HasOutAttribute && !data.HasInAttribute)
{
// error CS0662: Cannot specify the Out attribute on a ref parameter without also specifying the In attribute.
diagnostics.Add(ErrorCode.ERR_OutAttrOnRefParam, this.Locations[0]);
}
break;
case RefKind.Out:
if (data.HasInAttribute)
{
// error CS0036: An out parameter cannot have the In attribute.
diagnostics.Add(ErrorCode.ERR_InAttrOnOutParam, this.Locations[0]);
}
break;
case RefKind.In:
if (data.HasOutAttribute)
{
// error CS8355: An in parameter cannot have the Out attribute.
diagnostics.Add(ErrorCode.ERR_OutAttrOnInParam, this.Locations[0]);
}
break;
}
}
base.PostDecodeWellKnownAttributes(boundAttributes, allAttributeSyntaxNodes, diagnostics, symbolPart, decodedData);
}
///
/// True if the parameter has default argument syntax.
///
internal override bool HasDefaultArgumentSyntax
{
get
{
return (_parameterSyntaxKind & ParameterSyntaxKind.DefaultParameter) != 0;
}
}
///
/// True if the parameter is marked by .
///
internal sealed override bool HasOptionalAttribute
{
get
{
if (_lazyHasOptionalAttribute == ThreeState.Unknown)
{
SourceParameterSymbol copyFrom = this.BoundAttributesSource;
// prevent infinite recursion:
Debug.Assert(!ReferenceEquals(copyFrom, this));
if ((object)copyFrom != null)
{
// Parameter of partial implementation.
// We bind the attributes only on the definition part and copy them over to the implementation.
_lazyHasOptionalAttribute = copyFrom.HasOptionalAttribute.ToThreeState();
}
else
{
// lazyHasOptionalAttribute is decoded early, hence we cannot reach here when binding attributes for this symbol.
// So it is fine to force complete attributes here.
var attributes = GetAttributes();
if (!attributes.Any())
{
_lazyHasOptionalAttribute = ThreeState.False;
}
}
}
Debug.Assert(_lazyHasOptionalAttribute.HasValue());
return _lazyHasOptionalAttribute.Value();
}
}
internal override bool IsMetadataOptional
{
get
{
// NOTE: IsMetadataOptional property can be invoked during overload resolution.
// NOTE: Optional attribute is decoded very early in attribute binding phase, see method EarlyDecodeOptionalAttribute
// NOTE: If you update the below check to look for any more attributes, make sure that they are decoded early.
return HasDefaultArgumentSyntax || HasOptionalAttribute;
}
}
internal sealed override bool IsMetadataIn
=> base.IsMetadataIn || GetDecodedWellKnownAttributeData()?.HasInAttribute == true;
internal sealed override bool IsMetadataOut
=> base.IsMetadataOut || GetDecodedWellKnownAttributeData()?.HasOutAttribute == true;
internal sealed override MarshalPseudoCustomAttributeData MarshallingInformation
=> GetDecodedWellKnownAttributeData()?.MarshallingInformation;
public override bool IsParams => (_parameterSyntaxKind & ParameterSyntaxKind.ParamsParameter) != 0;
internal override bool IsExtensionMethodThis => (_parameterSyntaxKind & ParameterSyntaxKind.ExtensionThisParameter) != 0;
public override ImmutableArray RefCustomModifiers => ImmutableArray.Empty;
internal override void ForceComplete(SourceLocation locationOpt, CancellationToken cancellationToken)
{
base.ForceComplete(locationOpt, cancellationToken);
// Force binding of default value.
var unused = this.ExplicitDefaultConstantValue;
}
}
internal sealed class SourceComplexParameterSymbolWithCustomModifiersPrecedingByRef : SourceComplexParameterSymbol
{
private readonly ImmutableArray _refCustomModifiers;
internal SourceComplexParameterSymbolWithCustomModifiersPrecedingByRef(
Symbol owner,
int ordinal,
TypeSymbolWithAnnotations parameterType,
RefKind refKind,
ImmutableArray refCustomModifiers,
string name,
ImmutableArray locations,
SyntaxReference syntaxRef,
ConstantValue defaultSyntaxValue,
bool isParams,
bool isExtensionMethodThis)
: base(owner, ordinal, parameterType, refKind, name, locations, syntaxRef, defaultSyntaxValue, isParams, isExtensionMethodThis)
{
Debug.Assert(!refCustomModifiers.IsEmpty);
_refCustomModifiers = refCustomModifiers;
Debug.Assert(refKind != RefKind.None || _refCustomModifiers.IsEmpty);
}
public override ImmutableArray RefCustomModifiers => _refCustomModifiers;
}
}