-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bind and emit lambda attributes #52178
Conversation
@@ -13,11 +11,10 @@ | |||
|
|||
namespace Microsoft.CodeAnalysis.CSharp.Symbols | |||
{ | |||
internal sealed class LambdaSymbol : SourceMethodSymbol | |||
internal sealed class LambdaSymbol : SourceMethodSymbolWithAttributes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LambdaSymbol [](start = 26, length = 12)
It looks like this type should override AddDeclarationDiagnostics so that it could collect errors that are reported during attribute binding and other similar errors that are normally treated as declaratio errors. #Resolved
|
||
internal override bool IsIDispatchConstant | ||
{ | ||
get { return false; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return false; [](start = 17, length = 14)
Isn't this based on attributes? Same for other similar properties. #Resolved
7cf1d33
to
2cbe75a
Compare
return OneOrMany.Create(attributeLists); | ||
} | ||
|
||
internal void GetDeclarationDiagnostics(BindingDiagnosticBag addTo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not certain why we need both GetDeclarationDiagnostics
and AddDeclarationDiagnostics
? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AddDeclarationDiagnostics()
is an override of a method called in several places (such as from SourceParameterSymbol
) where declaration diagnostics may be calculated. GetDeclarationDiagnostics()
provides access to the declaration diagnostics which is necessary from UnboundLambdaState.ReallyBind()
to ensure the diagnostics that are calculated are actually reported.
In reply to: 605833109 [](ancestors = 605833109)
@@ -602,6 +605,9 @@ private BoundLambda ReallyBind(NamedTypeSymbol delegateType) | |||
block = BindLambdaBody(lambdaSymbol, lambdaBodyBinder, diagnostics); | |||
} | |||
|
|||
// PROTOTYPE: Test that we're reporting diagnostics even in the case where we re-used the lambda above. | |||
lambdaSymbol.GetDeclarationDiagnostics(diagnostics); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we not using AddDeclarationDiagnostics
here, which will ensure diagnostics are actually calculated? #ByDesign
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we not using
AddDeclarationDiagnostics
here, which will ensure diagnostics are actually calculated?
AddDeclarationDiagnostics()
only adds diagnostics that have already been calculated. GetDeclarationDiagnostics()
will ensure diagnostics are added to _declarationDiagnostics
.
In reply to: 605833428 [](ancestors = 605833428)
static void Main() | ||
{ | ||
Action<object, object> a; | ||
a = ([A] _, y) => { }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider verifying that these attributes actually show up in the symbol model. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added verification here and in LambdaTests.LambdaAttributes_01
.
In reply to: 605841651 [](ancestors = 605841651)
Func<bool> a4 = [MemberNotNull(""x"")][MemberNotNullWhen(false, ""y"")][MemberNotNullWhen(true, ""z"")] () => true; | ||
} | ||
}"; | ||
var comp = CreateCompilation( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prototype comment to make sure these actually do the things they're supposed to? #Closed
var lambdas = exprs.SelectAsArray(e => GetLambdaSymbol(model, e)); | ||
Assert.Equal(FlowAnalysisAnnotations.AllowNull | FlowAnalysisAnnotations.MaybeNullWhenFalse, lambdas[0].Parameters[0].FlowAnalysisAnnotations); | ||
Assert.Equal(new[] { "x" }, lambdas[1].Parameters[1].NotNullIfParameterNotNull); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider an error test that uses an attribute recursively, something like
[OtherAttr([Attr] () => {})]
class Attr : Attribute {}
class OtherAttr : Attribute { public OtherAttr(object o) {} }
``` #Closed
Done review pass (commit 1) #Closed |
@@ -38,25 +35,30 @@ internal sealed class LambdaSymbol : SourceMethodSymbol | |||
|
|||
private static readonly TypeWithAnnotations UnknownReturnType = TypeWithAnnotations.Create(ErrorTypeSymbol.UnknownResultType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UnknownReturnType [](start = 52, length = 17)
It looks like UnknownReturnType
is unused #Closed
@@ -188,12 +148,6 @@ public override TypeWithAnnotations ReturnTypeWithAnnotations | |||
get { return _returnType; } | |||
} | |||
|
|||
public override FlowAnalysisAnnotations ReturnTypeFlowAnalysisAnnotations => FlowAnalysisAnnotations.None; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please include a test for nullable return type and nullability attributes (Maybe/NotNull/NotNullIfNotNull). Do nullability checks on returned values work properly? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do nullability checks on returned values work properly?
Added tests with PROTOTYPE comment. #Closed
@@ -339,6 +314,7 @@ public override bool HidesBaseMethodsByName | |||
var builder = ArrayBuilder<ParameterSymbol>.GetInstance(unboundLambda.ParameterCount); | |||
var hasExplicitlyTypedParameterList = unboundLambda.HasExplicitlyTypedParameterList; | |||
var numDelegateParameters = parameterTypes.Length; | |||
var parenthesizedParameters = (Syntax as ParenthesizedLambdaExpressionSyntax)?.ParameterList; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parenthesizedParameters
appears unused #Closed
@"using System.Diagnostics; | ||
class Program | ||
{ | ||
[Conditional(""A"")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can Conditional be applied to a lambda? (I'd think it should not)
We should review other attributes listed in SourceMethodSymbolWithAttributes.DecodeWellKnownAttributeAppliedToMethod
as well.
, SkipLocalsInitAttribute
EnumeratorCancellationAttribute
(probably requires explicit return types on lambda first), ExcludeFromCodeCoverageAttribute
, OptionalAttribute
should be observable.
There's also MarshalAsAttribute
and PrincipalPermission
Let's test disallowed attributes on lambda and on lambda parameters.
I don't know if we care, but Action a = [Obsolete]() => {};
should probably produce a diagnostic. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConditionalAttribute
and ObsoleteAttribute
are not handled. Let me know if you think there is a scenario with either that needs to be addressed.
The other attributes should be handled. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- ObsoleteAttribute: I agree (ie. don't care). But we should have a test
- ConditionalAttribute: I think that should be disallowed (as it is on local functions)
In reply to: 612020197 [](ancestors = 612020197)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (syntax.Kind() == SyntaxKind.ParenthesizedLambdaExpression) | ||
{ | ||
// PROTOTYPE: Report error if parameter syntax is missing the type (using implicit parameter type syntax). | ||
MessageID.IDS_FeatureLambdaAttributes.CheckFeatureAvailability(diagnostics, attributeList); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we do a similar check on attribute on the lambda too?
I see this is checked during parsing. Is there a reason both shouldn't be semantic checks? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may have missed this question (should we only use semantic checks for LangVer checks?)
Could you also check if IDS_FeatureLambdaAttributes has the appropriate comment (syntax vs. semantic check)?
In reply to: 607473773 [](ancestors = 607473773)
@@ -168,7 +168,7 @@ protected override ImmutableArray<TypeSymbol> ExtraSynthesizedRefParameters | |||
=> ImmutableArray<TypeSymbol>.CastUp(_structEnvironments); | |||
internal int ExtraSynthesizedParameterCount => this._structEnvironments.IsDefault ? 0 : this._structEnvironments.Length; | |||
|
|||
internal override bool InheritsBaseMethodAttributes => BaseMethod is LocalFunctionSymbol; | |||
internal override bool InheritsBaseMethodAttributes => true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not very familiar. What's a scenario affected by this change? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's a scenario affected by this change?
For rewritten lambdas or local functions (rewritten during lowering by ClosureConversion
), this property ensures any parameters in the rewritten method refer back to the original lambda or local function property for attributes. That is required to emit the attributes.
In reply to: 607479101 [](ancestors = 607479101)
private static LambdaSymbol GetLambdaSymbol(SemanticModel model, LambdaExpressionSyntax syntax) | ||
{ | ||
return model.GetSymbolInfo(syntax).Symbol.GetSymbol<LambdaSymbol>(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some test ideas, if not covered already:
- Test semantic model on attributes (like LocalFunctionAttribute_Argument_SemanticModel)
- Enforcement of attribute targets (like LocalFunctionAttribute_BadAttributeLocation)
- Disallowed attributes (like LocalFunctionDisallowedAttributes and LocalFunctionDisallowedSecurityAttributes)
- Scoping rules on attributes (lookup tests like LookupInsideLocalFunctionAttribute)
- Incremental parsing tests (like Statement_EditAttributeList_01)
#Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added tests adapted from the corresponding attribute tests in LocalFunctionTests
. Thanks for the suggestions (and thanks @RikkiGibson for the local function tests). #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (iteration 1)
{ | ||
foreach (var parameter in _parameters) | ||
{ | ||
parameter.ForceComplete(null, default); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: consider argument names #Closed
public class IsReadOnlyAttribute : Attribute { } | ||
public class IsUnmanagedAttribute : Attribute { } | ||
public class IsByRefLikeAttribute : Attribute { } | ||
public class NullableContextAttribute : Attribute { public NullableContextAttribute(byte b) { } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably UnmanagedCallersOnlyAttribute
as well #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done review pass (commit 4) #Closed |
Done review pass (commit 5) |
} | ||
}"; | ||
var comp = CreateCompilation(new[] { source, MaybeNullAttributeDefinition, NotNullAttributeDefinition }, parseOptions: TestOptions.RegularPreview); | ||
// PROTOTYPE: Report error for a2, no error for a1. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think both declarations will end up having a warning. The first one in the conversion/assignment, and the other in the return value of the lambda.
Confirmed with similar scenario with local functions:
sharplab #Closed
static void Main() | ||
{ | ||
Action a1 = [DoesNotReturn] () => { }; | ||
Action a2 = [DoesNotReturn] () => throw new Exception(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a case where the lambda does return (we'll enforce/warn) #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From offline discussion, let's test NotNullWhen enforcement too (should likely fall out when DoesNotReturn works)
In reply to: 612140847 [](ancestors = 612140847,612139330)
static void Main() | ||
{ | ||
Action<object> a1 = ([AllowNull] x) => { x.ToString(); }; | ||
Action<object?> a2 = ([DisallowNull] x) => { x.ToString(); }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto here. We should have two warnings, just like with local functions. A dereference warning inside the first lambda, and a warning on conversion of the second lambda.
sharplab #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (iteration 5) with comments and some test suggestions on nullability attributes (can be PROTOTYPED)
} | ||
else | ||
{ | ||
Error(diagnostics, ErrorCode.ERR_AttributesNotAllowed, attributeList); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: consider using a dedicated error message suggesting to parenthesize the lambda's parameter list #Closed
static void Main() | ||
{ | ||
Action a1 = [DoesNotReturn] () => { }; | ||
Action a2 = [DoesNotReturn] () => throw new Exception(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to leave a PROTOTYPE marker here too (test isn't behaving as expected) #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (iteration 8). Just missing a couple PROTOTYPE markers. One nit to consider
{ | ||
// (8,56): error CS8916: Attributes require a lambda expression with a parenthesized parameter list. | ||
// Action<object, object> a = delegate (object x, [A][B] object y) { }; | ||
Diagnostic(ErrorCode.ERR_AttributesRequireParenthesizedLambdaExpression, "[A]").WithLocation(8, 56), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here the message doesn't make sense, as the parameter list is already parenthesized :-/ #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the parameter list is parenthesized, but the message also indicates the expression should be a lambda expression. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've reverted the error message for anonymous methods. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM THanks (iteration 9) with nit on diagnostic message for attribute used in delegate syntax
@@ -3941,6 +3941,9 @@ You should consider suppressing the warning only if you're sure that you don't w | |||
<data name="ERR_AttributesNotAllowed" xml:space="preserve"> | |||
<value>Attributes are not valid in this context.</value> | |||
</data> | |||
<data name="ERR_AttributesRequireParenthesizedLambdaExpression" xml:space="preserve"> | |||
<value>Attributes require a lambda expression with a parenthesized parameter list.</value> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wording of this is a bit awkward. Consider "Using attributes on a lambda expression requires a parenthesized parameter list", to avoid implying that all attributes require parenthesized lambda expressions. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to "Attributes on lambda expressions require a parenthesized parameter list." #Closed
@@ -3019,6 +3019,50 @@ void M() | |||
WalkTreeAndVerify(tree.GetCompilationUnitRoot(), fullTree.GetCompilationUnitRoot()); | |||
} | |||
|
|||
[Fact] | |||
public void Lambda_EditAttributeList() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider tests where the parameter list isn't parenthesized, where there wasn't an attribute to start with, and where the attribute is applied to a parameter, not the lambda. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (commit 11)
Bind and emit attributes on lambda expressions and lambda parameters.
Proposal: lambda-improvements.md
Test plan: #52192