diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs index 951343dbe27cb..0bb2b7b25c899 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs @@ -121,14 +121,14 @@ internal static void GetAttributes( BindingDiagnosticBag diagnostics) { beforeAttributePartBound?.Invoke(node); - var boundAttribute = new ExecutableCodeBinder(node, this.ContainingMemberOrLambda, this).BindAttribute(node, boundAttributeType, diagnostics); + 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, BindingDiagnosticBag diagnostics) + internal BoundAttribute BindAttribute(AttributeSyntax node, NamedTypeSymbol attributeType, Symbol? attributedMember, BindingDiagnosticBag diagnostics) { - return this.GetRequiredBinder(node).BindAttributeCore(node, attributeType, diagnostics); + return this.GetRequiredBinder(node).BindAttributeCore(node, attributeType, attributedMember, diagnostics); } private Binder SkipSemanticModelBinder() @@ -143,7 +143,7 @@ private Binder SkipSemanticModelBinder() return result; } - private BoundAttribute BindAttributeCore(AttributeSyntax node, NamedTypeSymbol attributeType, BindingDiagnosticBag diagnostics) + private BoundAttribute BindAttributeCore(AttributeSyntax node, NamedTypeSymbol attributeType, Symbol? attributedMember, BindingDiagnosticBag diagnostics) { Debug.Assert(this.SkipSemanticModelBinder() == this.GetRequiredBinder(node).SkipSemanticModelBinder()); @@ -172,53 +172,86 @@ private BoundAttribute BindAttributeCore(AttributeSyntax node, NamedTypeSymbol a Binder attributeArgumentBinder = this.WithAdditionalFlags(BinderFlags.AttributeArgument); AnalyzedAttributeArguments analyzedArguments = attributeArgumentBinder.BindAttributeArguments(argumentListOpt, attributeTypeForBinding, diagnostics); - CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); ImmutableArray argsToParamsOpt = default; bool expanded = false; + BitVector defaultArguments = default; MethodSymbol? attributeConstructor = null; - - // Bind attributeType's constructor based on the bound constructor arguments ImmutableArray boundConstructorArguments; - if (!attributeTypeForBinding.IsErrorType()) - { - attributeConstructor = BindAttributeConstructor(node, - attributeTypeForBinding, - analyzedArguments.ConstructorArguments, - diagnostics, - ref resultKind, - suppressErrors: attributeType.IsErrorType(), - ref argsToParamsOpt, - ref expanded, - ref useSiteInfo, - out boundConstructorArguments); - } - else + if (attributeTypeForBinding.IsErrorType()) { boundConstructorArguments = analyzedArguments.ConstructorArguments.Arguments.SelectAsArray( static (arg, attributeArgumentBinder) => attributeArgumentBinder.BindToTypeForErrorRecovery(arg), attributeArgumentBinder); } - Debug.Assert(boundConstructorArguments.All(a => !a.NeedsToBeConverted())); - diagnostics.Add(node, useSiteInfo); - - if (attributeConstructor is object) + else { - ReportDiagnosticsIfObsolete(diagnostics, attributeConstructor, node, hasBaseReceiver: false); - - if (attributeConstructor.Parameters.Any(p => p.RefKind == RefKind.In)) + bool found = TryPerformConstructorOverloadResolution( + attributeTypeForBinding, + analyzedArguments.ConstructorArguments, + attributeTypeForBinding.Name, + node.Location, + suppressResultDiagnostics: attributeType.IsErrorType(), + diagnostics, + out var memberResolutionResult, + out var candidateConstructors, + allowProtectedConstructorsOfBaseType: true); + attributeConstructor = memberResolutionResult.Member; + expanded = memberResolutionResult.Resolution == MemberResolutionKind.ApplicableInExpandedForm; + argsToParamsOpt = memberResolutionResult.Result.ArgsToParamsOpt; + + if (!found) { - Error(diagnostics, ErrorCode.ERR_AttributeCtorInParameter, node, attributeConstructor.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); + CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); + resultKind = resultKind.WorseResultKind( + memberResolutionResult.IsValid && !IsConstructorAccessible(memberResolutionResult.Member, ref useSiteInfo) ? + LookupResultKind.Inaccessible : + LookupResultKind.OverloadResolutionFailure); + boundConstructorArguments = BuildArgumentsForErrorRecovery(analyzedArguments.ConstructorArguments, candidateConstructors); + diagnostics.Add(node, useSiteInfo); + } + else + { + attributeArgumentBinder.BindDefaultArguments( + node, + attributeConstructor.Parameters, + analyzedArguments.ConstructorArguments.Arguments, + argumentRefKindsBuilder: null, + ref argsToParamsOpt, + out defaultArguments, + expanded, + enableCallerInfo: !IsEarlyAttributeBinder, + diagnostics, + attributedMember: attributedMember); + boundConstructorArguments = analyzedArguments.ConstructorArguments.Arguments.ToImmutable(); + ReportDiagnosticsIfObsolete(diagnostics, attributeConstructor, node, hasBaseReceiver: false); + + if (attributeConstructor.Parameters.Any(p => p.RefKind == RefKind.In)) + { + Error(diagnostics, ErrorCode.ERR_AttributeCtorInParameter, node, attributeConstructor.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); + } } } + Debug.Assert(boundConstructorArguments.All(a => !a.NeedsToBeConverted())); + ImmutableArray boundConstructorArgumentNamesOpt = analyzedArguments.ConstructorArguments.GetNames(); ImmutableArray boundNamedArguments = analyzedArguments.NamedArguments?.ToImmutableAndFree() ?? ImmutableArray.Empty; Debug.Assert(boundNamedArguments.All(arg => !arg.Right.NeedsToBeConverted())); analyzedArguments.ConstructorArguments.Free(); - return new BoundAttribute(node, attributeConstructor, boundConstructorArguments, boundConstructorArgumentNamesOpt, argsToParamsOpt, expanded, - boundNamedArguments, resultKind, attributeType, hasErrors: resultKind != LookupResultKind.Viable); + return new BoundAttribute( + node, + attributeConstructor, + boundConstructorArguments, + boundConstructorArgumentNamesOpt, + argsToParamsOpt, + expanded, + defaultArguments, + boundNamedArguments, + resultKind, + attributeType, + hasErrors: resultKind != LookupResultKind.Viable); } private CSharpAttributeData GetAttribute(BoundAttribute boundAttribute, BindingDiagnosticBag diagnostics) @@ -249,24 +282,81 @@ private CSharpAttributeData GetAttribute(BoundAttribute boundAttribute, BindingD Debug.Assert(!constructorArgsArray.IsDefault, "Property of VisitArguments"); - ImmutableArray constructorArgumentsSourceIndices; - ImmutableArray constructorArguments; + ImmutableArray argsToParamsOpt = boundAttribute.ConstructorArgumentsToParamsOpt; + ImmutableArray rewrittenArguments; if (hasErrors || attributeConstructor.ParameterCount == 0) { - constructorArgumentsSourceIndices = default(ImmutableArray); - constructorArguments = constructorArgsArray; + rewrittenArguments = constructorArgsArray; } else { - constructorArguments = GetRewrittenAttributeConstructorArguments(out constructorArgumentsSourceIndices, attributeConstructor, - constructorArgsArray, boundAttribute.ConstructorArgumentNamesOpt, (AttributeSyntax)boundAttribute.Syntax, boundAttribute.ConstructorArgumentsToParamsOpt, diagnostics, ref hasErrors); + rewrittenArguments = GetRewrittenAttributeConstructorArguments( + attributeConstructor, + constructorArgsArray, + boundAttribute.ConstructorArgumentNamesOpt, + (AttributeSyntax)boundAttribute.Syntax, + argsToParamsOpt, + diagnostics, + boundAttribute.ConstructorExpanded, + 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 useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); bool isConditionallyOmitted = IsAttributeConditionallyOmitted(attributeType, boundAttribute.SyntaxTree, ref useSiteInfo); diagnostics.Add(boundAttribute.Syntax, useSiteInfo); - return new SourceAttributeData(boundAttribute.Syntax.GetReference(), attributeType, attributeConstructor, constructorArguments, constructorArgumentsSourceIndices, namedArguments, hasErrors, isConditionallyOmitted); + return new SourceAttributeData( + boundAttribute.Syntax.GetReference(), + attributeType, + attributeConstructor, + rewrittenArguments, + makeSourceIndices(), + namedArguments, + hasErrors, + isConditionallyOmitted); + + ImmutableArray makeSourceIndices() + { + var lengthAfterRewriting = rewrittenArguments.Length; + if (lengthAfterRewriting == 0 || hasErrors) + { + return default; + } + + // 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; + } + } + + var constructorArgumentSourceIndices = ArrayBuilder.GetInstance(lengthAfterRewriting); + constructorArgumentSourceIndices.Count = lengthAfterRewriting; + for (int argIndex = 0; argIndex < lengthAfterRewriting; argIndex++) + { + int paramIndex = argsToParamsOpt.IsDefault ? argIndex : argsToParamsOpt[argIndex]; + constructorArgumentSourceIndices[paramIndex] = defaultArguments[argIndex] ? -1 : argIndex; + } + return constructorArgumentSourceIndices.ToImmutableAndFree(); + } } private void ValidateTypeForAttributeParameters(ImmutableArray parameters, CSharpSyntaxNode syntax, BindingDiagnosticBag diagnostics, ref bool hasErrors) @@ -549,46 +639,6 @@ private TypeSymbol BindNamedAttributeArgumentType(AttributeArgumentSyntax namedA return namedArgumentType; } - protected MethodSymbol BindAttributeConstructor( - AttributeSyntax node, - NamedTypeSymbol attributeType, - AnalyzedArguments boundConstructorArguments, - BindingDiagnosticBag diagnostics, - ref LookupResultKind resultKind, - bool suppressErrors, - ref ImmutableArray argsToParamsOpt, - ref bool expanded, - ref CompoundUseSiteInfo useSiteInfo, - out ImmutableArray constructorArguments) - { - MemberResolutionResult memberResolutionResult; - ImmutableArray candidateConstructors; - if (!TryPerformConstructorOverloadResolution( - attributeType, - boundConstructorArguments, - attributeType.Name, - node.Location, - suppressErrors, //don't cascade in these cases - diagnostics, - out memberResolutionResult, - out candidateConstructors, - allowProtectedConstructorsOfBaseType: true)) - { - resultKind = resultKind.WorseResultKind( - memberResolutionResult.IsValid && !IsConstructorAccessible(memberResolutionResult.Member, ref useSiteInfo) ? - LookupResultKind.Inaccessible : - LookupResultKind.OverloadResolutionFailure); - constructorArguments = BuildArgumentsForErrorRecovery(boundConstructorArguments, candidateConstructors); - } - else - { - constructorArguments = boundConstructorArguments.Arguments.ToImmutable(); - } - argsToParamsOpt = memberResolutionResult.Result.ArgsToParamsOpt; - expanded = memberResolutionResult.Result.Kind == MemberResolutionKind.ApplicableInExpandedForm; - return memberResolutionResult.Member; - } - /// /// Gets the rewritten attribute constructor arguments, i.e. the arguments /// are in the order of parameters, which may differ from the source @@ -606,13 +656,13 @@ protected MethodSymbol BindAttributeConstructor( /// CONSIDER: Can we share some code will call rewriting in the local rewriter? /// private ImmutableArray GetRewrittenAttributeConstructorArguments( - out ImmutableArray constructorArgumentsSourceIndices, MethodSymbol attributeConstructor, ImmutableArray constructorArgsArray, ImmutableArray constructorArgumentNamesOpt, AttributeSyntax syntax, ImmutableArray argumentsToParams, BindingDiagnosticBag diagnostics, + bool expanded, ref bool hasErrors) { RoslynDebug.Assert((object)attributeConstructor != null); @@ -621,66 +671,30 @@ private ImmutableArray GetRewrittenAttributeConstructorArguments( int argumentsCount = constructorArgsArray.Length; - // argsConsumedCount keeps track of the number of constructor arguments - // consumed from this.ConstructorArguments array - int argsConsumedCount = 0; - - bool hasNamedCtorArguments = !constructorArgumentNamesOpt.IsDefault; - Debug.Assert(!hasNamedCtorArguments || - constructorArgumentNamesOpt.Length == argumentsCount); - ImmutableArray parameters = attributeConstructor.Parameters; int parameterCount = parameters.Length; var reorderedArguments = new TypedConstant[parameterCount]; - int[]? sourceIndices = null; - - for (int i = 0; i < parameterCount; i++) + for (int i = 0; i < argumentsCount; i++) { - Debug.Assert(argsConsumedCount <= argumentsCount); + var paramIndex = argumentsToParams.IsDefault ? i : argumentsToParams[i]; + ParameterSymbol parameter = parameters[paramIndex]; - ParameterSymbol parameter = parameters[i]; TypedConstant reorderedArgument; - - if (parameter.IsParams && parameter.Type.IsSZArray() && i + 1 == parameterCount) + if (parameter.IsParams && parameter.Type.IsSZArray()) { - reorderedArgument = GetParamArrayArgument(parameter, constructorArgsArray, constructorArgumentNamesOpt, argumentsCount, - argsConsumedCount, this.Conversions, out bool foundNamed); - if (!foundNamed) - { - sourceIndices = sourceIndices ?? CreateSourceIndicesArray(i, parameterCount); - } - } - else if (argsConsumedCount < argumentsCount) - { - if (!hasNamedCtorArguments || - constructorArgumentNamesOpt[argsConsumedCount] == null) - { - // positional constructor argument - reorderedArgument = constructorArgsArray[argsConsumedCount]; - if (sourceIndices != null) - { - sourceIndices[i] = argsConsumedCount; - } - argsConsumedCount++; - } - else - { - // Current parameter must either have a matching named argument or a default value - // For the former case, argsConsumedCount must be incremented to note that we have - // consumed a named argument. For the latter case, argsConsumedCount stays same. - int matchingArgumentIndex; - reorderedArgument = getMatchingNamedOrOptionalConstructorArgument(out matchingArgumentIndex, constructorArgsArray, - parameter, argumentsCount, ref argsConsumedCount, syntax, argumentsToParams, diagnostics); - - sourceIndices = sourceIndices ?? CreateSourceIndicesArray(i, parameterCount); - sourceIndices[i] = matchingArgumentIndex; - } + reorderedArgument = GetParamArrayArgument( + parameter, + constructorArgsArray, + constructorArgumentNamesOpt, + argumentsCount, + currentArgumentIndex: i, + this.Conversions, + endOfParamsArrayIndex: out i); } else { - reorderedArgument = GetDefaultValueArgument(parameter, syntax, argumentsToParams, argumentsCount, diagnostics); - sourceIndices = sourceIndices ?? CreateSourceIndicesArray(i, parameterCount); + reorderedArgument = constructorArgsArray[i]; } if (!hasErrors) @@ -700,256 +714,77 @@ private ImmutableArray GetRewrittenAttributeConstructorArguments( } } - reorderedArguments[i] = reorderedArgument; + reorderedArguments[paramIndex] = reorderedArgument; } - constructorArgumentsSourceIndices = sourceIndices != null ? sourceIndices.AsImmutableOrNull() : default(ImmutableArray); - return reorderedArguments.AsImmutableOrNull(); - - // Local functions - TypedConstant getMatchingNamedOrOptionalConstructorArgument( - out int matchingArgumentIndex, - ImmutableArray constructorArgsArray, - ParameterSymbol parameter, - int argumentsCount, - ref int argsConsumedCount, - AttributeSyntax syntax, - ImmutableArray argumentsToParams, - BindingDiagnosticBag diagnostics) + // If we are in expanded form and no explicit argument was provided for the params array, then create the empty params array now. + if (expanded && reorderedArguments[^1].Kind == TypedConstantKind.Error) { - matchingArgumentIndex = argumentsToParams.IsDefaultOrEmpty - ? parameter.Ordinal - : argumentsToParams.IndexOf(parameter.Ordinal); - - if (matchingArgumentIndex > -1) - { - // found a matching named argument - Debug.Assert(matchingArgumentIndex < argumentsCount); - - // increment argsConsumedCount - argsConsumedCount++; - return constructorArgsArray[matchingArgumentIndex]; - } - else - { - return GetDefaultValueArgument(parameter, syntax, argumentsToParams, argumentsCount, diagnostics); - } + var paramArray = parameters[^1]; + Debug.Assert(paramArray.IsParams); + reorderedArguments[^1] = new TypedConstant(paramArray.Type, ImmutableArray.Empty); } - } - - private static int[] CreateSourceIndicesArray(int paramIndex, int parameterCount) - { - Debug.Assert(paramIndex >= 0); - Debug.Assert(paramIndex < parameterCount); - var sourceIndices = new int[parameterCount]; - for (int i = 0; i < paramIndex; i++) - { - sourceIndices[i] = i; - } - - for (int i = paramIndex; i < parameterCount; i++) - { - sourceIndices[i] = -1; - } - - return sourceIndices; + Debug.Assert(hasErrors || reorderedArguments.All(arg => arg.Kind != TypedConstantKind.Error)); + return reorderedArguments.AsImmutable(); } - private static int GetMatchingNamedConstructorArgumentIndex(string parameterName, ImmutableArray argumentNamesOpt, int startIndex, int argumentsCount) - { - RoslynDebug.Assert(parameterName != null); - Debug.Assert(startIndex >= 0 && startIndex < argumentsCount); - - if (parameterName.IsEmpty() || !argumentNamesOpt.Any()) - { - return argumentsCount; - } - - // get the matching named (constructor) argument - int argIndex = startIndex; - while (argIndex < argumentsCount) - { - var name = argumentNamesOpt[argIndex]; - - if (string.Equals(name, parameterName, StringComparison.Ordinal)) - { - break; - } - - argIndex++; - } - - return argIndex; - } - - private TypedConstant GetDefaultValueArgument(ParameterSymbol parameter, AttributeSyntax syntax, ImmutableArray argumentsToParams, int argumentsCount, BindingDiagnosticBag diagnostics) - { - var parameterType = parameter.Type; - ConstantValue? defaultConstantValue = parameter.IsOptional ? parameter.ExplicitDefaultConstantValue : ConstantValue.NotAvailable; - - TypedConstantKind kind; - object? defaultValue = null; - - if (!IsEarlyAttributeBinder && parameter.IsCallerLineNumber) - { - int line = syntax.SyntaxTree.GetDisplayLineNumber(syntax.Name.Span); - kind = TypedConstantKind.Primitive; - - CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); - var conversion = Conversions.GetCallerLineNumberConversion(parameterType, ref useSiteInfo); - diagnostics.Add(syntax, useSiteInfo); - - if (conversion.IsNumeric || conversion.IsConstantExpression) - { - // DoUncheckedConversion() keeps "single" floats as doubles internally to maintain higher - // precision, so make sure they get cast to floats here. - defaultValue = (parameterType.SpecialType == SpecialType.System_Single) - ? (float)line - : Binder.DoUncheckedConversion(parameterType.SpecialType, ConstantValue.Create(line)); - } - else - { - // Boxing or identity conversion: - parameterType = GetSpecialType(SpecialType.System_Int32, diagnostics, syntax); - defaultValue = line; - } - } - else if (!IsEarlyAttributeBinder && parameter.IsCallerFilePath) - { - parameterType = GetSpecialType(SpecialType.System_String, diagnostics, syntax); - kind = TypedConstantKind.Primitive; - defaultValue = syntax.SyntaxTree.GetDisplayPath(syntax.Name.Span, Compilation.Options.SourceReferenceResolver); - } - else if (!IsEarlyAttributeBinder && parameter.IsCallerMemberName && (object)((ContextualAttributeBinder)this).AttributedMember != null) - { - parameterType = GetSpecialType(SpecialType.System_String, diagnostics, syntax); - kind = TypedConstantKind.Primitive; - defaultValue = ((ContextualAttributeBinder)this).AttributedMember.GetMemberCallerName(); - } - // We check IsCallerMemberName here since the above if can be reached with AttributedMember == null, in which case, - // We shouldn't be checking CallerArgumentExpression - else if (!IsEarlyAttributeBinder && syntax.ArgumentList is not null && !parameter.IsCallerMemberName && - getCallerArgumentArgumentIndex(parameter, argumentsToParams) is int argumentIndex && argumentIndex > -1 && argumentIndex < argumentsCount) - { - Debug.Assert(argumentsCount <= syntax.ArgumentList.Arguments.Count); - parameterType = GetSpecialType(SpecialType.System_String, diagnostics, syntax); - kind = TypedConstantKind.Primitive; - defaultValue = syntax.ArgumentList.Arguments[argumentIndex].Expression.ToString(); - } - else if (defaultConstantValue == ConstantValue.NotAvailable) - { - // There is no constant value given for the parameter in source/metadata. - // For example, the attribute constructor with signature: M([Optional] int x), has no default value from syntax or attributes. - // Default value for these cases is "default(parameterType)". - - // Optional parameter of System.Object type is treated specially though. - // Native compiler treats "M([Optional] object x)" equivalent to "M(object x)" for attributes if parameter type is System.Object. - // We generate a better diagnostic for this case by treating "x" in the above case as optional, but generating CS7067 instead. - if (parameterType.SpecialType == SpecialType.System_Object) - { - // CS7067: Attribute constructor parameter '{0}' is optional, but no default parameter value was specified. - diagnostics.Add(ErrorCode.ERR_BadAttributeParamDefaultArgument, syntax.Name.Location, parameter.Name); - kind = TypedConstantKind.Error; - } - else - { - kind = TypedConstant.GetTypedConstantKind(parameterType, this.Compilation); - Debug.Assert(kind != TypedConstantKind.Error); - - defaultConstantValue = parameterType.GetDefaultValue(); - if (defaultConstantValue != null) - { - defaultValue = defaultConstantValue.Value; - } - } - } - else if (defaultConstantValue.IsBad) - { - // Constant value through syntax had errors, don't generate cascading diagnostics. - kind = TypedConstantKind.Error; - } - else if (parameterType.SpecialType == SpecialType.System_Object && !defaultConstantValue.IsNull) - { - // error CS1763: '{0}' is of type '{1}'. A default parameter value of a reference type other than string can only be initialized with null - diagnostics.Add(ErrorCode.ERR_NotNullRefDefaultParameter, syntax.Location, parameter.Name, parameterType); - kind = TypedConstantKind.Error; - } - else - { - kind = TypedConstant.GetTypedConstantKind(parameterType, this.Compilation); - Debug.Assert(kind != TypedConstantKind.Error); - - defaultValue = defaultConstantValue.Value; - } - - if (kind == TypedConstantKind.Array) - { - Debug.Assert(defaultValue == null); - return new TypedConstant(parameterType, default(ImmutableArray)); - } - else - { - return new TypedConstant(parameterType, kind, defaultValue); - } - - static int getCallerArgumentArgumentIndex(ParameterSymbol parameter, ImmutableArray argumentsToParams) - { - return argumentsToParams.IsDefault || parameter.CallerArgumentExpressionParameterIndex == -1 - ? parameter.CallerArgumentExpressionParameterIndex - : argumentsToParams.IndexOf(parameter.CallerArgumentExpressionParameterIndex); - } - } - - private static TypedConstant GetParamArrayArgument(ParameterSymbol parameter, ImmutableArray constructorArgsArray, - ImmutableArray constructorArgumentNamesOpt, int argumentsCount, int argsConsumedCount, Conversions conversions, out bool foundNamed) + // This should eventually be moved to initial binding. + // https://github.com/dotnet/roslyn/issues/49602 + private static TypedConstant GetParamArrayArgument( + ParameterSymbol parameter, + ImmutableArray constructorArgsArray, + ImmutableArray constructorArgumentNamesOpt, + int argumentsCount, + int currentArgumentIndex, + Conversions conversions, + out int endOfParamsArrayIndex) { - Debug.Assert(argsConsumedCount <= argumentsCount); + Debug.Assert(currentArgumentIndex <= argumentsCount); // If there's a named argument, we'll use that - if (!constructorArgumentNamesOpt.IsDefault) + if (!constructorArgumentNamesOpt.IsDefault && constructorArgumentNamesOpt.Contains(parameter.Name)) { - int argIndex = constructorArgumentNamesOpt.IndexOf(parameter.Name); - if (argIndex >= 0) + Debug.Assert(constructorArgumentNamesOpt.IndexOf(parameter.Name) == currentArgumentIndex); + endOfParamsArrayIndex = currentArgumentIndex; + if (TryGetNormalParamValue(parameter, constructorArgsArray, currentArgumentIndex, conversions, out var namedValue)) { - foundNamed = true; - if (TryGetNormalParamValue(parameter, constructorArgsArray, argIndex, conversions, out var namedValue)) - { - return namedValue; - } - - // A named argument for a params parameter is necessarily the only one for that parameter - return new TypedConstant(parameter.Type, ImmutableArray.Create(constructorArgsArray[argIndex])); + return namedValue; } + + // A named argument for a params parameter is necessarily the only one for that parameter + return new TypedConstant(parameter.Type, ImmutableArray.Create(constructorArgsArray[currentArgumentIndex])); } - int paramArrayArgCount = argumentsCount - argsConsumedCount; - foundNamed = false; + int paramArrayArgCount = argumentsCount - currentArgumentIndex; // If there are zero arguments left if (paramArrayArgCount == 0) { + endOfParamsArrayIndex = argumentsCount - 1; return new TypedConstant(parameter.Type, ImmutableArray.Empty); } // If there's exactly one argument left, we'll try to use it in normal form if (paramArrayArgCount == 1 && - TryGetNormalParamValue(parameter, constructorArgsArray, argsConsumedCount, conversions, out var lastValue)) + TryGetNormalParamValue(parameter, constructorArgsArray, currentArgumentIndex, conversions, out var lastValue)) { + endOfParamsArrayIndex = argumentsCount - 1; return lastValue; } Debug.Assert(!constructorArgsArray.IsDefault); - Debug.Assert(argsConsumedCount <= constructorArgsArray.Length); + Debug.Assert(currentArgumentIndex <= constructorArgsArray.Length); // Take the trailing arguments as an array for expanded form var values = new TypedConstant[paramArrayArgCount]; for (int i = 0; i < paramArrayArgCount; i++) { - values[i] = constructorArgsArray[argsConsumedCount++]; + values[i] = constructorArgsArray[currentArgumentIndex++]; } + endOfParamsArrayIndex = currentArgumentIndex + paramArrayArgCount - 1; return new TypedConstant(parameter.Type, values.AsImmutableOrNull()); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 05a1304c219ee..043b4e81549fe 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -1182,6 +1182,7 @@ private BoundExpression GetDefaultParameterSpecialNoConversion(SyntaxNode syntax // We have a call to a method M([Optional] object x) which omits the argument. The value we generate // for the argument depends on the presence or absence of other attributes. The rules are: // + // * If we're generating a default argument for an attribute, it's a compile error. // * If the parameter is marked as [MarshalAs(Interface)], [MarshalAs(IUnknown)] or [MarshalAs(IDispatch)] // then the argument is null. // * Otherwise, if the parameter is marked as [IUnknownConstant] then the argument is @@ -1191,7 +1192,12 @@ private BoundExpression GetDefaultParameterSpecialNoConversion(SyntaxNode syntax // * Otherwise, the argument is Type.Missing. BoundExpression? defaultValue = null; - if (parameter.IsMarshalAsObject) + if (InAttributeArgument) + { + // CS7067: Attribute constructor parameter '{0}' is optional, but no default parameter value was specified. + diagnostics.Add(ErrorCode.ERR_BadAttributeParamDefaultArgument, syntax.Location, parameter.Name); + } + else if (parameter.IsMarshalAsObject) { // default(object) defaultValue = new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true }; @@ -1278,7 +1284,8 @@ internal void BindDefaultArguments( bool expanded, bool enableCallerInfo, BindingDiagnosticBag diagnostics, - bool assertMissingParametersAreOptional = true) + bool assertMissingParametersAreOptional = true, + Symbol? attributedMember = null) { var visitedParameters = BitVector.Create(parameters.Length); @@ -1300,11 +1307,12 @@ internal void BindDefaultArguments( // In a scenario like `string Prop { get; } = M();`, the containing symbol could be the synthesized field. // We want to use the associated user-declared symbol instead where possible. - var containingMember = ContainingMember() switch + var containingMember = InAttributeArgument ? attributedMember : ContainingMember() switch { FieldSymbol { AssociatedSymbol: { } symbol } => symbol, var c => c }; + Debug.Assert(InAttributeArgument || (attributedMember is null && containingMember is not null)); defaultArguments = BitVector.Create(parameters.Length); ArrayBuilder? argsToParamsBuilder = null; @@ -1348,7 +1356,7 @@ internal void BindDefaultArguments( argsToParamsBuilder.Free(); } - BoundExpression bindDefaultArgument(SyntaxNode syntax, ParameterSymbol parameter, Symbol containingMember, bool enableCallerInfo, BindingDiagnosticBag diagnostics, ArrayBuilder argumentsBuilder, int argumentsCount, ImmutableArray argsToParamsOpt) + BoundExpression bindDefaultArgument(SyntaxNode syntax, ParameterSymbol parameter, Symbol? containingMember, bool enableCallerInfo, BindingDiagnosticBag diagnostics, ArrayBuilder argumentsBuilder, int argumentsCount, ImmutableArray argsToParamsOpt) { TypeSymbol parameterType = parameter.Type; if (Flags.Includes(BinderFlags.ParameterDefaultValue)) @@ -1358,7 +1366,14 @@ BoundExpression bindDefaultArgument(SyntaxNode syntax, ParameterSymbol parameter return new BoundDefaultExpression(syntax, parameterType) { WasCompilerGenerated = true }; } - var defaultConstantValue = parameter.ExplicitDefaultConstantValue switch + var parameterDefaultValue = parameter.ExplicitDefaultConstantValue; + if (InAttributeArgument && parameterDefaultValue?.IsBad == true) + { + diagnostics.Add(ErrorCode.ERR_BadAttributeArgument, syntax.Location); + return BadExpression(syntax).MakeCompilerGenerated(); + } + + var defaultConstantValue = parameterDefaultValue switch { // Bad default values are implicitly replaced with default(T) at call sites. { IsBad: true } => ConstantValue.Null, @@ -1366,6 +1381,7 @@ BoundExpression bindDefaultArgument(SyntaxNode syntax, ParameterSymbol parameter }; Debug.Assert((object?)defaultConstantValue != ConstantValue.Unset); + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; var callerSourceLocation = enableCallerInfo ? GetCallerLocation(syntax) : null; BoundExpression defaultValue; if (callerSourceLocation is object && parameter.IsCallerLineNumber) @@ -1378,13 +1394,16 @@ BoundExpression bindDefaultArgument(SyntaxNode syntax, ParameterSymbol parameter string path = callerSourceLocation.SourceTree.GetDisplayPath(callerSourceLocation.SourceSpan, Compilation.Options.SourceReferenceResolver); defaultValue = new BoundLiteral(syntax, ConstantValue.Create(path), Compilation.GetSpecialType(SpecialType.System_String)) { WasCompilerGenerated = true }; } - else if (callerSourceLocation is object && parameter.IsCallerMemberName) + else if (callerSourceLocation is object && parameter.IsCallerMemberName && containingMember is not null) { var memberName = containingMember.GetMemberCallerName(); defaultValue = new BoundLiteral(syntax, ConstantValue.Create(memberName), Compilation.GetSpecialType(SpecialType.System_String)) { WasCompilerGenerated = true }; } - else if (callerSourceLocation is object && getArgumentIndex(parameter.CallerArgumentExpressionParameterIndex, argsToParamsOpt) is int argumentIndex && - argumentIndex > -1 && argumentIndex < argumentsCount) + else if (callerSourceLocation is object + && !parameter.IsCallerMemberName + && Conversions.ClassifyBuiltInConversion(Compilation.GetSpecialType(SpecialType.System_String), parameterType, ref discardedUseSiteInfo).Exists + && getArgumentIndex(parameter.CallerArgumentExpressionParameterIndex, argsToParamsOpt) is int argumentIndex + && argumentIndex > -1 && argumentIndex < argumentsCount) { var argument = argumentsBuilder[argumentIndex]; defaultValue = new BoundLiteral(syntax, ConstantValue.Create(argument.Syntax.ToString()), Compilation.GetSpecialType(SpecialType.System_String)) { WasCompilerGenerated = true }; @@ -1411,6 +1430,12 @@ BoundExpression bindDefaultArgument(SyntaxNode syntax, ParameterSymbol parameter { TypeSymbol constantType = Compilation.GetSpecialType(defaultConstantValue.SpecialType); defaultValue = new BoundLiteral(syntax, defaultConstantValue, constantType) { WasCompilerGenerated = true }; + + if (InAttributeArgument && parameterType.SpecialType == SpecialType.System_Object) + { + // error CS1763: '{0}' is of type '{1}'. A default parameter value of a reference type other than string can only be initialized with null + diagnostics.Add(ErrorCode.ERR_NotNullRefDefaultParameter, syntax.Location, parameter.Name, parameterType); + } } CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 1994cdcd5f617..86443deed2996 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1755,6 +1755,7 @@ + diff --git a/src/Compilers/CSharp/Portable/Compilation/AttributeSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/AttributeSemanticModel.cs index 84b3d9ae7fc69..ccadc076c3bc8 100644 --- a/src/Compilers/CSharp/Portable/Compilation/AttributeSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/AttributeSemanticModel.cs @@ -89,7 +89,8 @@ internal override BoundNode Bind(Binder binder, CSharpSyntaxNode node, BindingDi if (node.Kind() == SyntaxKind.Attribute) { var attribute = (AttributeSyntax)node; - return binder.BindAttribute(attribute, AttributeType, diagnostics); + // note: we should find the attributed member before binding the attribute as part of https://github.com/dotnet/roslyn/issues/53618 + return binder.BindAttribute(attribute, AttributeType, attributedMember: null, diagnostics); } else if (SyntaxFacts.IsAttributeName(node)) { diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index 3f6167f425685..059261beb0559 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -411,7 +411,8 @@ private BoundAttribute GetSpeculativelyBoundAttribute(int position, AttributeSyn AliasSymbol aliasOpt; // not needed. NamedTypeSymbol attributeType = (NamedTypeSymbol)binder.BindType(attribute.Name, BindingDiagnosticBag.Discarded, out aliasOpt).Type; - var boundNode = new ExecutableCodeBinder(attribute, binder.ContainingMemberOrLambda, binder).BindAttribute(attribute, attributeType, BindingDiagnosticBag.Discarded); + // note: we don't need to pass an 'attributedMember' here because we only need symbolInfo from this node + var boundNode = new ExecutableCodeBinder(attribute, binder.ContainingMemberOrLambda, binder).BindAttribute(attribute, attributeType, attributedMember: null, BindingDiagnosticBag.Discarded); return boundNode; } diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 234351a12b658..af98de7773ece 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -6211,7 +6211,7 @@ public BoundEventAssignmentOperator Update(EventSymbol @event, bool isAddition, internal sealed partial class BoundAttribute : BoundExpression { - public BoundAttribute(SyntaxNode syntax, MethodSymbol? constructor, ImmutableArray constructorArguments, ImmutableArray constructorArgumentNamesOpt, ImmutableArray constructorArgumentsToParamsOpt, bool constructorExpanded, ImmutableArray namedArguments, LookupResultKind resultKind, TypeSymbol type, bool hasErrors = false) + public BoundAttribute(SyntaxNode syntax, MethodSymbol? constructor, ImmutableArray constructorArguments, ImmutableArray constructorArgumentNamesOpt, ImmutableArray constructorArgumentsToParamsOpt, bool constructorExpanded, BitVector constructorDefaultArguments, ImmutableArray namedArguments, LookupResultKind resultKind, TypeSymbol type, bool hasErrors = false) : base(BoundKind.Attribute, syntax, type, hasErrors || constructorArguments.HasErrors() || namedArguments.HasErrors()) { @@ -6224,6 +6224,7 @@ public BoundAttribute(SyntaxNode syntax, MethodSymbol? constructor, ImmutableArr this.ConstructorArgumentNamesOpt = constructorArgumentNamesOpt; this.ConstructorArgumentsToParamsOpt = constructorArgumentsToParamsOpt; this.ConstructorExpanded = constructorExpanded; + this.ConstructorDefaultArguments = constructorDefaultArguments; this.NamedArguments = namedArguments; this._ResultKind = resultKind; } @@ -6241,6 +6242,8 @@ public BoundAttribute(SyntaxNode syntax, MethodSymbol? constructor, ImmutableArr public bool ConstructorExpanded { get; } + public BitVector ConstructorDefaultArguments { get; } + public ImmutableArray NamedArguments { get; } private readonly LookupResultKind _ResultKind; @@ -6248,11 +6251,11 @@ public BoundAttribute(SyntaxNode syntax, MethodSymbol? constructor, ImmutableArr [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitAttribute(this); - public BoundAttribute Update(MethodSymbol? constructor, ImmutableArray constructorArguments, ImmutableArray constructorArgumentNamesOpt, ImmutableArray constructorArgumentsToParamsOpt, bool constructorExpanded, ImmutableArray namedArguments, LookupResultKind resultKind, TypeSymbol type) + public BoundAttribute Update(MethodSymbol? constructor, ImmutableArray constructorArguments, ImmutableArray constructorArgumentNamesOpt, ImmutableArray constructorArgumentsToParamsOpt, bool constructorExpanded, BitVector constructorDefaultArguments, ImmutableArray namedArguments, LookupResultKind resultKind, TypeSymbol type) { - if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(constructor, this.Constructor) || constructorArguments != this.ConstructorArguments || constructorArgumentNamesOpt != this.ConstructorArgumentNamesOpt || constructorArgumentsToParamsOpt != this.ConstructorArgumentsToParamsOpt || constructorExpanded != this.ConstructorExpanded || namedArguments != this.NamedArguments || resultKind != this.ResultKind || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(constructor, this.Constructor) || constructorArguments != this.ConstructorArguments || constructorArgumentNamesOpt != this.ConstructorArgumentNamesOpt || constructorArgumentsToParamsOpt != this.ConstructorArgumentsToParamsOpt || constructorExpanded != this.ConstructorExpanded || constructorDefaultArguments != this.ConstructorDefaultArguments || namedArguments != this.NamedArguments || resultKind != this.ResultKind || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundAttribute(this.Syntax, constructor, constructorArguments, constructorArgumentNamesOpt, constructorArgumentsToParamsOpt, constructorExpanded, namedArguments, resultKind, type, this.HasErrors); + var result = new BoundAttribute(this.Syntax, constructor, constructorArguments, constructorArgumentNamesOpt, constructorArgumentsToParamsOpt, constructorExpanded, constructorDefaultArguments, namedArguments, resultKind, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -11546,7 +11549,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor ImmutableArray constructorArguments = this.VisitList(node.ConstructorArguments); ImmutableArray namedArguments = this.VisitList(node.NamedArguments); TypeSymbol? type = this.VisitType(node.Type); - return node.Update(node.Constructor, constructorArguments, node.ConstructorArgumentNamesOpt, node.ConstructorArgumentsToParamsOpt, node.ConstructorExpanded, namedArguments, node.ResultKind, type); + return node.Update(node.Constructor, constructorArguments, node.ConstructorArgumentNamesOpt, node.ConstructorArgumentsToParamsOpt, node.ConstructorExpanded, node.ConstructorDefaultArguments, namedArguments, node.ResultKind, type); } public override BoundNode? VisitUnconvertedObjectCreationExpression(BoundUnconvertedObjectCreationExpression node) { @@ -13610,12 +13613,12 @@ public NullabilityRewriter(ImmutableDictionary arg.Value)); + // `SourceAttributeData.GetAttributeArgumentSyntax` asserts in debug mode when the attributeData has errors, so we don't test it here. + } + + [Fact] + public void TestCallWithOptionalParametersInsideAttribute() + { + var source = @" +using System; + +class Attr : Attribute { public Attr(int x) { } } + +class C +{ + [Attr(M())] + void M0() { } + + public static int M(int x = 0) => x; +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (8,11): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // [Attr(M())] + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "M()").WithLocation(8, 11)); + + var m0 = comp.GetMember("C.M0"); + var attrs = m0.GetAttributes(); + Assert.Equal(1, attrs.Length); + Assert.Equal(TypedConstantKind.Error, attrs[0].ConstructorArguments.Single().Kind); + } + + [Fact] + public void TestAttributeCallerInfoSemanticModel() + { + var source = @" +using System; +using System.Runtime.CompilerServices; + +class Attr : Attribute { public Attr([CallerMemberName] string s = null) { } } + +class C +{ + [Attr()] + void M0() { } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var root = tree.GetRoot(); + var attrSyntax = root.DescendantNodes().OfType().Last(); + + var semanticModel = comp.GetSemanticModel(tree); + var m0 = semanticModel.GetDeclaredSymbol(root.DescendantNodes().OfType().Last()); + var attrs = m0.GetAttributes(); + Assert.Equal("M0", attrs.Single().ConstructorArguments.Single().Value); + + var operation = semanticModel.GetOperation(attrSyntax); + // note: this operation tree should contain a constant string "M0" instead of null. + // this should ideally be fixed as part of https://github.com/dotnet/roslyn/issues/53618. + VerifyOperationTree(comp, operation, @" +IOperation: (OperationKind.None, Type: Attr) (Syntax: 'Attr()') + Children(1): + IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsImplicit) (Syntax: 'Attr()') +"); + } + + [Fact] + public void TestAttributeCallerInfoSemanticModel_Speculative() + { + var source = @" +using System; +using System.Runtime.CompilerServices; + +class Attr : Attribute { public Attr([CallerMemberName] string s = null) { } } + +class C +{ + [Attr(""a"")] + void M0() { } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var root = tree.GetRoot(); + var attrSyntax = root.DescendantNodes().OfType().Last(); + + var semanticModel = comp.GetSemanticModel(tree); + var newRoot = root.ReplaceNode(attrSyntax, attrSyntax.WithArgumentList(SyntaxFactory.ParseAttributeArgumentList("()"))); + var newAttrSyntax = newRoot.DescendantNodes().OfType().Last(); + + Assert.True(semanticModel.TryGetSpeculativeSemanticModel(attrSyntax.ArgumentList.Position, newAttrSyntax, out var speculativeModel)); + + var speculativeOperation = speculativeModel.GetOperation(newAttrSyntax); + // note: this operation tree should contain a constant string "M0" instead of null. + // this should ideally be fixed as part of https://github.com/dotnet/roslyn/issues/53618. + VerifyOperationTree(comp, speculativeOperation, @" +IOperation: (OperationKind.None, Type: Attr) (Syntax: 'Attr()') + Children(1): + IDefaultValueOperation (OperationKind.DefaultValue, Type: System.String, Constant: null, IsImplicit) (Syntax: 'Attr()') +"); + } + + [Fact] + public void NotNullIfNotNullDefinitionUsesCallerMemberName() + { + var definitionSource = @" +using System.Runtime.CompilerServices; +using System.Diagnostics.CodeAnalysis; + +namespace System.Diagnostics.CodeAnalysis +{ + public class NotNullIfNotNullAttribute : Attribute + { + public NotNullIfNotNullAttribute([CallerMemberName] string paramName = """") { } + } +} + +public class C +{ + [return: NotNullIfNotNull] + public static string? M(string? M) + { + return M; + } +} +"; + + var usageSource = @" +class Program +{ + void M0() + { + C.M(""a"").ToString(); + } +}"; + CreateCompilation(new[] { definitionSource, usageSource }, options: WithNullableEnable()) + .VerifyDiagnostics(); + + var definitionComp = CreateCompilation(definitionSource, options: WithNullableEnable()); + + CreateCompilation(usageSource, references: new[] { definitionComp.ToMetadataReference() }, options: WithNullableEnable()) + .VerifyDiagnostics(); + + CreateCompilation(usageSource, references: new[] { definitionComp.EmitToImageReference() }, options: WithNullableEnable()) + .VerifyDiagnostics(); } [Fact] @@ -622,7 +838,11 @@ public static void Main() var program = (NamedTypeSymbol)comp.GetMember("Program"); var attributeData = (SourceAttributeData)program.GetAttributes()[0]; - Assert.Equal(new[] { 1, -1 }, attributeData.ConstructorArgumentsSourceIndices); + Assert.Equal(new[] { 1, 0 }, attributeData.ConstructorArgumentsSourceIndices); + + var attributeSyntax = (AttributeSyntax)attributeData.ApplicationSyntaxReference.GetSyntax(); + Assert.Equal(@"a: true", attributeData.GetAttributeArgumentSyntax(parameterIndex: 0, attributeSyntax).ToString()); + Assert.Equal(@"b: new object[] { ""Hello"", ""World"" }", attributeData.GetAttributeArgumentSyntax(parameterIndex: 1, attributeSyntax).ToString()); } [Fact] @@ -659,7 +879,11 @@ public static void Main() var program = (NamedTypeSymbol)comp.GetMember("Program"); var attributeData = (SourceAttributeData)program.GetAttributes()[0]; - Assert.Equal(new[] { 1, -1 }, attributeData.ConstructorArgumentsSourceIndices); + Assert.Equal(new[] { 1, 0 }, attributeData.ConstructorArgumentsSourceIndices); + + var attributeSyntax = comp.SyntaxTrees[0].GetRoot().DescendantNodes().OfType().First(); + Assert.Equal(@"a: true", attributeData.GetAttributeArgumentSyntax(parameterIndex: 0, attributeSyntax).ToString()); + Assert.Equal(@"b: ""Hello""", attributeData.GetAttributeArgumentSyntax(parameterIndex: 1, attributeSyntax).ToString()); } [Fact] @@ -695,7 +919,11 @@ public static void Main() var program = (NamedTypeSymbol)comp.GetMember("Program"); var attributeData = (SourceAttributeData)program.GetAttributes()[0]; - Assert.Equal(new[] { 0, -1 }, attributeData.ConstructorArgumentsSourceIndices); + Assert.Equal(new[] { 0, 1 }, attributeData.ConstructorArgumentsSourceIndices); + + var attributeSyntax = (AttributeSyntax)attributeData.ApplicationSyntaxReference.GetSyntax(); + Assert.Equal(@"true", attributeData.GetAttributeArgumentSyntax(parameterIndex: 0, attributeSyntax).ToString()); + Assert.Equal(@"new object[] { ""Hello"" }", attributeData.GetAttributeArgumentSyntax(parameterIndex: 1, attributeSyntax).ToString()); } [Fact] @@ -731,7 +959,11 @@ public static void Main() var program = (NamedTypeSymbol)comp.GetMember("Program"); var attributeData = (SourceAttributeData)program.GetAttributes()[0]; - Assert.Equal(new[] { 0, -1 }, attributeData.ConstructorArgumentsSourceIndices); + Assert.Equal(new[] { 0, 1 }, attributeData.ConstructorArgumentsSourceIndices); + + var attributeSyntax = (AttributeSyntax)attributeData.ApplicationSyntaxReference.GetSyntax(); + Assert.Equal(@"a: true", attributeData.GetAttributeArgumentSyntax(parameterIndex: 0, attributeSyntax).ToString()); + Assert.Equal(@"new object[] { ""Hello"" }", attributeData.GetAttributeArgumentSyntax(parameterIndex: 1, attributeSyntax).ToString()); } [Fact] @@ -766,7 +998,11 @@ public static void Main() var program = (NamedTypeSymbol)comp.GetMember("Program"); var attributeData = (SourceAttributeData)program.GetAttributes()[0]; - Assert.Equal(new[] { 1, -1 }, attributeData.ConstructorArgumentsSourceIndices); + Assert.Equal(new[] { 1, 0 }, attributeData.ConstructorArgumentsSourceIndices); + + var attributeSyntax = (AttributeSyntax)attributeData.ApplicationSyntaxReference.GetSyntax(); + Assert.Equal(@"a: true", attributeData.GetAttributeArgumentSyntax(parameterIndex: 0, attributeSyntax).ToString()); + Assert.Equal(@"b: null", attributeData.GetAttributeArgumentSyntax(parameterIndex: 1, attributeSyntax).ToString()); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests_WellKnownAttributes.cs b/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests_WellKnownAttributes.cs index 8715f52c9b56d..d4ab4f3c76e5f 100644 --- a/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests_WellKnownAttributes.cs +++ b/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests_WellKnownAttributes.cs @@ -1794,40 +1794,52 @@ public static void Main() { } CreateCompilation(source).VerifyDiagnostics( // (63,65): error CS1763: 'x' is of type 'object'. A default parameter value of a reference type other than string can only be initialized with null // public MyPermission5Attribute(SecurityAction action, object x = SecurityAction.Demand) : base(SecurityAction.Demand) - Diagnostic(ErrorCode.ERR_NotNullRefDefaultParameter, "x").WithArguments("x", "object"), + Diagnostic(ErrorCode.ERR_NotNullRefDefaultParameter, "x").WithArguments("x", "object").WithLocation(63, 65), // (76,42): error CS1763: 'x' is of type 'object'. A default parameter value of a reference type other than string can only be initialized with null // public MyPermission6Attribute(object x = SecurityAction.Demand) : base(SecurityAction.Demand) - Diagnostic(ErrorCode.ERR_NotNullRefDefaultParameter, "x").WithArguments("x", "object"), + Diagnostic(ErrorCode.ERR_NotNullRefDefaultParameter, "x").WithArguments("x", "object").WithLocation(76, 42), // (101,46): error CS1908: The type of the argument to the DefaultParameterValue attribute must match the parameter type // public MyPermission8Attribute([Optional][DefaultParameterValueAttribute(null)]SecurityAction x) : base(SecurityAction.Demand) - Diagnostic(ErrorCode.ERR_DefaultValueTypeMustMatch, "DefaultParameterValueAttribute"), + Diagnostic(ErrorCode.ERR_DefaultValueTypeMustMatch, "DefaultParameterValueAttribute").WithLocation(101, 46), // (113,46): error CS1908: The type of the argument to the DefaultParameterValue attribute must match the parameter type // public MyPermission9Attribute([Optional][DefaultParameterValueAttribute(-1)]SecurityAction x) : base(SecurityAction.Demand) - Diagnostic(ErrorCode.ERR_DefaultValueTypeMustMatch, "DefaultParameterValueAttribute"), + Diagnostic(ErrorCode.ERR_DefaultValueTypeMustMatch, "DefaultParameterValueAttribute").WithLocation(113, 46), // (137,2): error CS7067: Attribute constructor parameter 'x' is optional, but no default parameter value was specified. // [MyPermission(SecurityAction.Demand)] - Diagnostic(ErrorCode.ERR_BadAttributeParamDefaultArgument, "MyPermission").WithArguments("x"), + Diagnostic(ErrorCode.ERR_BadAttributeParamDefaultArgument, "MyPermission(SecurityAction.Demand)").WithArguments("x").WithLocation(137, 2), // (140,2): error CS7067: Attribute constructor parameter 'x' is optional, but no default parameter value was specified. // [MyPermission2(SecurityAction.Demand)] - Diagnostic(ErrorCode.ERR_BadAttributeParamDefaultArgument, "MyPermission2").WithArguments("x"), + Diagnostic(ErrorCode.ERR_BadAttributeParamDefaultArgument, "MyPermission2(SecurityAction.Demand)").WithArguments("x").WithLocation(140, 2), // (143,2): error CS7067: Attribute constructor parameter 'x' is optional, but no default parameter value was specified. // [MyPermission3()] - Diagnostic(ErrorCode.ERR_BadAttributeParamDefaultArgument, "MyPermission3").WithArguments("x"), - // (149,16): error CS1503: Argument 1: cannot convert from '' to 'System.Security.Permissions.SecurityAction' + Diagnostic(ErrorCode.ERR_BadAttributeParamDefaultArgument, "MyPermission3()").WithArguments("x").WithLocation(143, 2), + // (149,16): error CS1503: Argument 1: cannot convert from '' to 'SecurityAction' // [MyPermission4(null)] - Diagnostic(ErrorCode.ERR_BadArgType, "null").WithArguments("1", "", "System.Security.Permissions.SecurityAction"), + Diagnostic(ErrorCode.ERR_BadArgType, "null").WithArguments("1", "", "System.Security.Permissions.SecurityAction").WithLocation(149, 16), + // (151,2): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // [MyPermission5(SecurityAction.Demand)] + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "MyPermission5(SecurityAction.Demand)").WithLocation(151, 2), + // (154,2): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // [MyPermission6()] + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "MyPermission6()").WithLocation(154, 2), + // (161,2): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // [MyPermission8()] + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "MyPermission8()").WithLocation(161, 2), + // (164,2): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // [MyPermission9()] + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "MyPermission9()").WithLocation(164, 2), // (167,2): error CS1763: 'y' is of type 'object'. A default parameter value of a reference type other than string can only be initialized with null // [MyPermission10(SecurityAction.Demand)] - Diagnostic(ErrorCode.ERR_NotNullRefDefaultParameter, "MyPermission10(SecurityAction.Demand)").WithArguments("y", "object"), + Diagnostic(ErrorCode.ERR_NotNullRefDefaultParameter, "MyPermission10(SecurityAction.Demand)").WithArguments("y", "object").WithLocation(167, 2), // (145,2): error CS7048: First argument to a security attribute must be a valid SecurityAction // [MyPermission3(null)] - Diagnostic(ErrorCode.ERR_SecurityAttributeMissingAction, "MyPermission3"), + Diagnostic(ErrorCode.ERR_SecurityAttributeMissingAction, "MyPermission3").WithLocation(145, 2), // (147,2): error CS7049: Security attribute 'MyPermission4' has an invalid SecurityAction value '0' // [MyPermission4()] - Diagnostic(ErrorCode.ERR_SecurityAttributeInvalidAction, "MyPermission4()").WithArguments("MyPermission4", "0"), + Diagnostic(ErrorCode.ERR_SecurityAttributeInvalidAction, "MyPermission4()").WithArguments("MyPermission4", "0").WithLocation(147, 2), // (156,2): error CS7048: First argument to a security attribute must be a valid SecurityAction // [MyPermission6(null)] - Diagnostic(ErrorCode.ERR_SecurityAttributeMissingAction, "MyPermission6")); + Diagnostic(ErrorCode.ERR_SecurityAttributeMissingAction, "MyPermission6").WithLocation(156, 2)); } [Fact]