Skip to content

Commit

Permalink
Create attribute default arguments during binding (dotnet#59750)
Browse files Browse the repository at this point in the history
  • Loading branch information
RikkiGibson authored Mar 3, 2022
1 parent be10278 commit b1b184a
Show file tree
Hide file tree
Showing 8 changed files with 492 additions and 377 deletions.
517 changes: 176 additions & 341 deletions src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs

Large diffs are not rendered by default.

41 changes: 33 additions & 8 deletions src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 };
Expand Down Expand Up @@ -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);
Expand All @@ -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<int>? argsToParamsBuilder = null;
Expand Down Expand Up @@ -1348,7 +1356,7 @@ internal void BindDefaultArguments(
argsToParamsBuilder.Free();
}

BoundExpression bindDefaultArgument(SyntaxNode syntax, ParameterSymbol parameter, Symbol containingMember, bool enableCallerInfo, BindingDiagnosticBag diagnostics, ArrayBuilder<BoundExpression> argumentsBuilder, int argumentsCount, ImmutableArray<int> argsToParamsOpt)
BoundExpression bindDefaultArgument(SyntaxNode syntax, ParameterSymbol parameter, Symbol? containingMember, bool enableCallerInfo, BindingDiagnosticBag diagnostics, ArrayBuilder<BoundExpression> argumentsBuilder, int argumentsCount, ImmutableArray<int> argsToParamsOpt)
{
TypeSymbol parameterType = parameter.Type;
if (Flags.Includes(BinderFlags.ParameterDefaultValue))
Expand All @@ -1358,14 +1366,22 @@ 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,
var constantValue => constantValue
};
Debug.Assert((object?)defaultConstantValue != ConstantValue.Unset);

var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
var callerSourceLocation = enableCallerInfo ? GetCallerLocation(syntax) : null;
BoundExpression defaultValue;
if (callerSourceLocation is object && parameter.IsCallerLineNumber)
Expand All @@ -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 };
Expand All @@ -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<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1755,6 +1755,7 @@
<Field Name="ConstructorArgumentNamesOpt" Type="ImmutableArray&lt;string?&gt;" Null="allow"/>
<Field Name="ConstructorArgumentsToParamsOpt" Type="ImmutableArray&lt;int&gt;" Null="allow"/>
<Field Name="ConstructorExpanded" Type="bool" />
<Field Name="ConstructorDefaultArguments" Type="BitVector"/>
<Field Name="NamedArguments" Type ="ImmutableArray&lt;BoundAssignmentOperator&gt;"/>
<Field Name="ResultKind" PropertyOverrides="true" Type="LookupResultKind"/>
</Node>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b1b184a

Please sign in to comment.