Bind and emit lambda attributes#52178
Conversation
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
I'm not certain why we need both GetDeclarationDiagnostics and AddDeclarationDiagnostics? #Closed
There was a problem hiding this comment.
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)
| } | ||
|
|
||
| // 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.
Why are we not using AddDeclarationDiagnostics here, which will ensure diagnostics are actually calculated? #ByDesign
There was a problem hiding this comment.
Why are we not using
AddDeclarationDiagnosticshere, 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.
Consider verifying that these attributes actually show up in the symbol model. #Closed
There was a problem hiding this comment.
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.
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.
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.
UnknownReturnType [](start = 52, length = 17)
It looks like UnknownReturnType is unused #Closed
| get { return _returnType; } | ||
| } | ||
|
|
||
| public override FlowAnalysisAnnotations ReturnTypeFlowAnalysisAnnotations => FlowAnalysisAnnotations.None; |
There was a problem hiding this comment.
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.
Do nullability checks on returned values work properly?
Added tests with PROTOTYPE comment. #Closed
| 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.
parenthesizedParameters appears unused #Closed
| @"using System.Diagnostics; | ||
| class Program | ||
| { | ||
| [Conditional(""A"")] |
There was a problem hiding this comment.
Can Conditional be applied to a lambda? (I'd think it should not)
We should review other attributes listed in SourceMethodSymbolWithAttributes.DecodeWellKnownAttributeAppliedToMethod as well.
, SkipLocalsInitAttributeEnumeratorCancellationAttribute (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.
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.
- 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)
| 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.
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.
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)
| 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.
I'm not very familiar. What's a scenario affected by this change? #Closed
There was a problem hiding this comment.
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.
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.
Added tests adapted from the corresponding attribute tests in LocalFunctionTests. Thanks for the suggestions (and thanks @RikkiGibson for the local function tests). #Closed
jcouv
left a comment
There was a problem hiding this comment.
Done with review pass (iteration 1)
| { | ||
| foreach (var parameter in _parameters) | ||
| { | ||
| parameter.ForceComplete(null, default); |
There was a problem hiding this comment.
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.
Probably UnmanagedCallersOnlyAttribute as well #Closed
There was a problem hiding this comment.
|
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.
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.
Please add a case where the lambda does return (we'll enforce/warn) #Closed
There was a problem hiding this comment.
There was a problem hiding this comment.
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.
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.
jcouv
left a comment
There was a problem hiding this comment.
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.
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.
Do you want to leave a PROTOTYPE marker here too (test isn't behaving as expected) #Closed
jcouv
left a comment
There was a problem hiding this comment.
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.
Here the message doesn't make sense, as the parameter list is already parenthesized :-/ #Resolved
There was a problem hiding this comment.
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.
I've reverted the error message for anonymous methods. #Closed
jcouv
left a comment
There was a problem hiding this comment.
LGTM THanks (iteration 9) with nit on diagnostic message for attribute used in delegate syntax
| <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.
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.
Updated to "Attributes on lambda expressions require a parenthesized parameter list." #Closed
| } | ||
|
|
||
| [Fact] | ||
| public void Lambda_EditAttributeList() |
There was a problem hiding this comment.
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
Bind and emit attributes on lambda expressions and lambda parameters.
Proposal: lambda-improvements.md
Test plan: #52192