Skip to content
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

Merged
merged 11 commits into from
Apr 14, 2021
Merged

Conversation

cston
Copy link
Member

@cston cston commented Mar 26, 2021

Bind and emit attributes on lambda expressions and lambda parameters.

Proposal: lambda-improvements.md
Test plan: #52192

@cston cston changed the base branch from main to features/lambdas March 26, 2021 22:21
@@ -13,11 +11,10 @@

namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
internal sealed class LambdaSymbol : SourceMethodSymbol
internal sealed class LambdaSymbol : SourceMethodSymbolWithAttributes
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 29, 2021

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; }
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 29, 2021

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

@cston cston force-pushed the bind-attributes branch 3 times, most recently from 7cf1d33 to 2cbe75a Compare April 1, 2021 00:17
@cston cston marked this pull request as ready for review April 1, 2021 04:48
@cston cston requested a review from a team as a code owner April 1, 2021 04:48
@cston cston requested review from halter73 and removed request for halter73 April 1, 2021 05:38
return OneOrMany.Create(attributeLists);
}

internal void GetDeclarationDiagnostics(BindingDiagnosticBag addTo)
Copy link
Member

@333fred 333fred Apr 1, 2021

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

Copy link
Member Author

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);
Copy link
Member

@333fred 333fred Apr 1, 2021

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

Copy link
Member Author

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) => { };
Copy link
Member

@333fred 333fred Apr 1, 2021

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

Copy link
Member Author

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(
Copy link
Member

@333fred 333fred Apr 1, 2021

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);
}
Copy link
Member

@333fred 333fred Apr 1, 2021

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

@333fred
Copy link
Member

333fred commented Apr 1, 2021

Done review pass (commit 1) #Closed

@@ -38,25 +35,30 @@ internal sealed class LambdaSymbol : SourceMethodSymbol

private static readonly TypeWithAnnotations UnknownReturnType = TypeWithAnnotations.Create(ErrorTypeSymbol.UnknownResultType);
Copy link
Member

@jcouv jcouv Apr 6, 2021

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;
Copy link
Member

@jcouv jcouv Apr 6, 2021

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

Copy link
Member Author

@cston cston Apr 12, 2021

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;
Copy link
Member

@jcouv jcouv Apr 6, 2021

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"")]
Copy link
Member

@jcouv jcouv Apr 6, 2021

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

Copy link
Member Author

@cston cston Apr 12, 2021

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

Copy link
Member

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)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was incorrect. ConditionalAttribute is allowed on local functions. We should do the same.
Example


In reply to: 612132673 [](ancestors = 612132673,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);
Copy link
Member

@jcouv jcouv Apr 6, 2021

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

Copy link
Member

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;
Copy link
Member

@jcouv jcouv Apr 6, 2021

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

Copy link
Member Author

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>();
}
Copy link
Member

@jcouv jcouv Apr 6, 2021

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

Copy link
Member Author

@cston cston Apr 9, 2021

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

Copy link
Member

@jcouv jcouv left a 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);
Copy link
Member

@jcouv jcouv Apr 6, 2021

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

@jcouv jcouv added this to the C# 10 milestone Apr 6, 2021
@jcouv jcouv self-assigned this Apr 6, 2021
public class IsReadOnlyAttribute : Attribute { }
public class IsUnmanagedAttribute : Attribute { }
public class IsByRefLikeAttribute : Attribute { }
public class NullableContextAttribute : Attribute { public NullableContextAttribute(byte b) { } }
Copy link
Member

@333fred 333fred Apr 9, 2021

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a response to this.


In reply to: 610916427 [](ancestors = 610916427)

@333fred
Copy link
Member

333fred commented Apr 9, 2021

Done review pass (commit 4) #Closed

@333fred
Copy link
Member

333fred commented Apr 12, 2021

Done review pass (commit 5)

}
}";
var comp = CreateCompilation(new[] { source, MaybeNullAttributeDefinition, NotNullAttributeDefinition }, parseOptions: TestOptions.RegularPreview);
// PROTOTYPE: Report error for a2, no error for a1.
Copy link
Member

@jcouv jcouv Apr 13, 2021

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();
Copy link
Member

@jcouv jcouv Apr 13, 2021

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should warn on a1 too


In reply to: 612139330 [](ancestors = 612139330)

Copy link
Member

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(); };
Copy link
Member

@jcouv jcouv Apr 13, 2021

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

Copy link
Member

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?


In reply to: 612140116 [](ancestors = 612140116)

Copy link
Member

@jcouv jcouv left a 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)

@cston
Copy link
Member Author

cston commented Apr 13, 2021

@333fred, @jcouv, thanks for reviewing. All comments have been addressed.

}
else
{
Error(diagnostics, ErrorCode.ERR_AttributesNotAllowed, attributeList);
Copy link
Member

@jcouv jcouv Apr 13, 2021

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();
Copy link
Member

@jcouv jcouv Apr 13, 2021

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

Copy link
Member

@jcouv jcouv left a 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),
Copy link
Member

@jcouv jcouv Apr 13, 2021

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

Copy link
Member Author

@cston cston Apr 13, 2021

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

Copy link
Member Author

@cston cston Apr 13, 2021

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

Copy link
Member

@jcouv jcouv left a 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>
Copy link
Member

@333fred 333fred Apr 13, 2021

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

Copy link
Member Author

@cston cston Apr 13, 2021

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()
Copy link
Member

@333fred 333fred Apr 13, 2021

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

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (commit 11)

@cston cston merged commit 9a800bf into dotnet:features/lambdas Apr 14, 2021
@cston cston deleted the bind-attributes branch April 14, 2021 01:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants