-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Support modifiers with simple lambda parameters. #75400
Changes from 101 commits
5ef8b62
377fd39
71258cb
fc3889c
df0fcd3
9be9229
7b8a22a
5ac1ae3
da298d8
fcced9f
1ff139d
a5ac0b2
1381610
30dde1f
138cc1b
4e3d787
06a65d4
2bf5192
ec0f272
480eacb
987928e
08cdb3b
86b9cda
6c07d0c
fbc0b05
ec51315
b7edb45
66ed2a0
223641a
7995a7e
c60680d
922ec4f
b0e61fb
3d5bd38
0fa7e52
4d888f6
5b7ac1a
b1c0415
0c62f86
02376ed
8d112ba
72cc5f2
b6cd56c
5624428
e93792e
955d4fa
b5f28b7
bb74038
47ed0e4
314d807
163a667
c0c55ce
e7b4200
5654594
a6caa89
e0f855f
a690f4a
08b804f
55f8a48
7e7cd11
980ff5e
6975e28
ee34ba8
e4e33aa
111ec5f
06a8fca
d9766a7
47ab140
dee332d
d2cdfc3
e4464e0
b058429
f150417
729c639
ccf7bbc
f65ff60
bd64fc2
f4dc8f5
72b419d
ea65299
acb0f81
c65d771
da06993
d45d1af
79db98b
6c7b931
11a9e71
63f688a
fde87ac
99482d3
1596d26
ef569dc
005511f
622efc9
e6ba439
09c0d2f
48a8d96
5a517e8
1f0d95a
62fed3a
819ac94
d1f8026
1e5d00e
87d005d
f43f02b
38b7c98
7851db3
2093c05
1aa702c
cf775df
7c9ed31
2b89528
0b34018
34fc6e6
a6f45d5
44ed91b
41bd895
2906eb6
84bfaa8
b7aebc9
34de5f2
d3a4b0b
e6d9d31
e51345c
eef4e94
952fc5a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,9 +3,9 @@ | |
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using Microsoft.CodeAnalysis.CSharp.Symbols; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.PooledObjects; | ||
|
@@ -20,6 +20,7 @@ internal partial class Binder | |
// delegate (int x) { } (typed parameter list) | ||
// x => ... (type-inferred parameter list) | ||
// (x) => ... (type-inferred parameter list) | ||
// (ref x) => ... (type-inferred parameter list with modifiers) | ||
// (x, y) => ... (type-inferred parameter list) | ||
// ( ) => ... (typed parameter list) | ||
// (ref int x) => ... (typed parameter list) | ||
|
@@ -52,7 +53,7 @@ private UnboundLambda AnalyzeAnonymousFunction( | |
|
||
var namesBuilder = ArrayBuilder<string>.GetInstance(); | ||
ImmutableArray<bool> discardsOpt = default; | ||
SeparatedSyntaxList<ParameterSyntax>? parameterSyntaxList = null; | ||
SeparatedSyntaxList<ParameterSyntax>? parameterSyntaxListOpt = null; | ||
bool hasSignature; | ||
|
||
if (syntax is LambdaExpressionSyntax lambdaSyntax) | ||
|
@@ -80,8 +81,9 @@ private UnboundLambda AnalyzeAnonymousFunction( | |
{ | ||
(returnRefKind, returnType) = BindExplicitLambdaReturnType(returnTypeSyntax, diagnostics); | ||
} | ||
parameterSyntaxList = paren.ParameterList.Parameters; | ||
CheckParenthesizedLambdaParameters(parameterSyntaxList.Value, diagnostics); | ||
|
||
parameterSyntaxListOpt = paren.ParameterList.Parameters; | ||
CheckParenthesizedLambdaParameters(parameterSyntaxListOpt.Value, diagnostics); | ||
break; | ||
case SyntaxKind.AnonymousMethodExpression: | ||
// delegate (int x) { } | ||
|
@@ -92,7 +94,7 @@ private UnboundLambda AnalyzeAnonymousFunction( | |
hasSignature = anon.ParameterList != null; | ||
if (hasSignature) | ||
{ | ||
parameterSyntaxList = anon.ParameterList!.Parameters; | ||
parameterSyntaxListOpt = anon.ParameterList!.Parameters; | ||
} | ||
|
||
break; | ||
|
@@ -115,9 +117,10 @@ private UnboundLambda AnalyzeAnonymousFunction( | |
} | ||
} | ||
|
||
if (parameterSyntaxList != null) | ||
if (parameterSyntaxListOpt is { } parameterSyntaxList) | ||
{ | ||
var hasExplicitlyTypedParameterList = true; | ||
var isAnonymousMethod = syntax.IsKind(SyntaxKind.AnonymousMethodExpression); | ||
var hasExplicitlyTypedParameterList = parameterSyntaxList.All(static p => p.Type != null); | ||
|
||
var typesBuilder = ArrayBuilder<TypeWithAnnotations>.GetInstance(); | ||
var refKindsBuilder = ArrayBuilder<RefKind>.GetInstance(); | ||
|
@@ -135,20 +138,26 @@ private UnboundLambda AnalyzeAnonymousFunction( | |
|
||
int parameterCount = 0; | ||
int underscoresCount = 0; | ||
foreach (var p in parameterSyntaxList.Value) | ||
int firstDefault = -1; | ||
for (int i = 0, n = parameterSyntaxList.Count; i < n; i++) | ||
{ | ||
parameterCount++; | ||
|
||
var p = parameterSyntaxList[i]; | ||
if (p.Identifier.IsUnderscoreToken()) | ||
{ | ||
underscoresCount++; | ||
} | ||
|
||
checkAttributes(syntax, p.AttributeLists, diagnostics); | ||
|
||
var isAnonymousMethod = syntax.IsKind(SyntaxKind.AnonymousMethodExpression); | ||
if (p.Default != null) | ||
{ | ||
if (firstDefault == -1) | ||
{ | ||
firstDefault = i; | ||
} | ||
|
||
if (isAnonymousMethod) | ||
{ | ||
Error(diagnostics, ErrorCode.ERR_DefaultValueNotAllowed, p.Default.EqualsToken); | ||
|
@@ -165,43 +174,43 @@ private UnboundLambda AnalyzeAnonymousFunction( | |
continue; | ||
} | ||
|
||
var typeSyntax = p.Type; | ||
TypeWithAnnotations type = default; | ||
var refKind = RefKind.None; | ||
var scope = ScopedKind.None; | ||
var typeOpt = p.Type is not null ? BindType(p.Type, diagnostics) : default; | ||
|
||
if (typeSyntax == null) | ||
{ | ||
hasExplicitlyTypedParameterList = false; | ||
} | ||
else | ||
var refKind = ParameterHelpers.GetModifiers(p.Modifiers, out _, out var paramsKeyword, out _, out var scope); | ||
var isParams = paramsKeyword.Kind() != SyntaxKind.None; | ||
|
||
ParameterHelpers.CheckParameterModifiers(p, diagnostics, parsingFunctionPointerParams: false, | ||
parsingLambdaParams: !isAnonymousMethod, | ||
parsingAnonymousMethodParams: isAnonymousMethod); | ||
|
||
ParameterHelpers.ReportParameterErrors( | ||
owner: null, p, ordinal: i, lastParameterIndex: n - 1, isParams: isParams, typeOpt, | ||
refKind, containingSymbol: null, thisKeyword: default, paramsKeyword: paramsKeyword, firstDefault, diagnostics); | ||
|
||
if (parameterCount == parameterSyntaxList.Count && | ||
paramsKeyword.Kind() != SyntaxKind.None && | ||
!typeOpt.IsDefault && | ||
typeOpt.IsSZArray()) | ||
{ | ||
type = BindType(typeSyntax, diagnostics); | ||
ParameterHelpers.CheckParameterModifiers(p, diagnostics, parsingFunctionPointerParams: false, | ||
parsingLambdaParams: !isAnonymousMethod, | ||
parsingAnonymousMethodParams: isAnonymousMethod); | ||
refKind = ParameterHelpers.GetModifiers(p.Modifiers, out _, out var paramsKeyword, out _, out scope); | ||
|
||
var isLastParameter = parameterCount == parameterSyntaxList.Value.Count; | ||
if (isLastParameter && paramsKeyword.Kind() != SyntaxKind.None && type.IsSZArray()) | ||
{ | ||
ReportUseSiteDiagnosticForSynthesizedAttribute(Compilation, | ||
WellKnownMember.System_ParamArrayAttribute__ctor, | ||
diagnostics, | ||
paramsKeyword.GetLocation()); | ||
} | ||
ReportUseSiteDiagnosticForSynthesizedAttribute(Compilation, | ||
WellKnownMember.System_ParamArrayAttribute__ctor, | ||
diagnostics, | ||
paramsKeyword.GetLocation()); | ||
} | ||
|
||
namesBuilder.Add(p.Identifier.ValueText); | ||
typesBuilder.Add(type); | ||
typesBuilder.Add(typeOpt); | ||
refKindsBuilder.Add(refKind); | ||
scopesBuilder.Add(scope); | ||
attributesBuilder.Add(syntax.Kind() == SyntaxKind.ParenthesizedLambdaExpression ? p.AttributeLists : default); | ||
defaultValueBuilder.Add(p.Default); | ||
} | ||
|
||
discardsOpt = computeDiscards(parameterSyntaxList.Value, underscoresCount); | ||
discardsOpt = computeDiscards(parameterSyntaxListOpt.Value, underscoresCount); | ||
|
||
// Only include the types if *all* the parameters had types. Otherwise, if there were no parameter | ||
// types (or a mix of typed and untyped parameters) include no types. Note, in the latter case we will | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// have already reported an error about this issue. | ||
if (hasExplicitlyTypedParameterList) | ||
{ | ||
types = typesBuilder.ToImmutable(); | ||
|
@@ -241,7 +250,7 @@ private UnboundLambda AnalyzeAnonymousFunction( | |
|
||
namesBuilder.Free(); | ||
|
||
return UnboundLambda.Create(syntax, this, diagnostics.AccumulatesDependencies, returnRefKind, returnType, parameterAttributes, refKinds, scopes, types, names, discardsOpt, parameterSyntaxList, defaultValues, isAsync: isAsync, isStatic: isStatic); | ||
return UnboundLambda.Create(syntax, this, diagnostics.AccumulatesDependencies, returnRefKind, returnType, parameterAttributes, refKinds, scopes, types, names, discardsOpt, parameterSyntaxListOpt, defaultValues, isAsync: isAsync, isStatic: isStatic); | ||
|
||
static ImmutableArray<bool> computeDiscards(SeparatedSyntaxList<ParameterSyntax> parameters, int underscoresCount) | ||
{ | ||
|
@@ -308,39 +317,40 @@ private static void CheckParenthesizedLambdaParameters( | |
{ | ||
if (parameterSyntaxList.Count > 0) | ||
{ | ||
var hasTypes = parameterSyntaxList[0].Type != null; | ||
// If one parameter has a type, then all parameters must have a type. | ||
var requiresTypes = parameterSyntaxList.Any(static p => p.Type != null); | ||
|
||
checkForImplicitDefault(hasTypes, parameterSyntaxList[0], diagnostics); | ||
|
||
for (int i = 1, n = parameterSyntaxList.Count; i < n; i++) | ||
foreach (var parameter in parameterSyntaxList) | ||
{ | ||
var parameter = parameterSyntaxList[i]; | ||
|
||
// Ignore parameters with missing names. We'll have already reported an error | ||
// about them in the parser. | ||
if (!parameter.Identifier.IsMissing) | ||
{ | ||
var thisParameterHasType = parameter.Type != null; | ||
|
||
if (hasTypes != thisParameterHasType) | ||
if (requiresTypes) | ||
{ | ||
diagnostics.Add(ErrorCode.ERR_InconsistentLambdaParameterUsage, | ||
parameter.Type?.GetLocation() ?? parameter.Identifier.GetLocation()); | ||
if (parameter.Type is null) | ||
{ | ||
diagnostics.Add(ErrorCode.ERR_InconsistentLambdaParameterUsage, | ||
parameter.Identifier.GetLocation()); | ||
} | ||
} | ||
else | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
if (parameter.Default != null) | ||
{ | ||
diagnostics.Add(ErrorCode.ERR_ImplicitlyTypedDefaultParameter, | ||
parameter.Identifier.GetLocation(), parameter.Identifier.Text); | ||
} | ||
} | ||
|
||
checkForImplicitDefault(thisParameterHasType, parameter, diagnostics); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. inlined. it's an easy check. when we're in the case where we're not requiring types (an implicit lambda) we cannot have default parameter values. |
||
// Check if `(ref i) => ...` is supported by this language version. | ||
if (parameter.Modifiers.Count > 0 && parameter.Type is null) | ||
{ | ||
CheckFeatureAvailability(parameter, MessageID.IDS_FeatureSimpleLambdaParameterModifiers, diagnostics); | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} | ||
} | ||
|
||
static void checkForImplicitDefault(bool hasType, ParameterSyntax param, BindingDiagnosticBag diagnostics) | ||
{ | ||
if (!hasType && param.Default != null) | ||
{ | ||
diagnostics.Add(ErrorCode.ERR_ImplicitlyTypedDefaultParameter, | ||
param.Identifier.GetLocation(), param.Identifier.Text); | ||
} | ||
} | ||
} | ||
|
||
private UnboundLambda BindAnonymousFunction(AnonymousFunctionExpressionSyntax syntax, BindingDiagnosticBag diagnostics) | ||
|
@@ -350,27 +360,6 @@ private UnboundLambda BindAnonymousFunction(AnonymousFunctionExpressionSyntax sy | |
|
||
var lambda = AnalyzeAnonymousFunction(syntax, diagnostics); | ||
var data = lambda.Data; | ||
if (data.HasExplicitlyTypedParameterList) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this logic moved into AnalyzeAnonymousFunction. In particular, we want check parameters unilaterally even if there are types or no types (since we want to check modifier usage in the latter case). so the HasExplicitlyTypedParameterList had to go. |
||
{ | ||
int firstDefault = -1; | ||
for (int i = 0; i < lambda.ParameterCount; i++) | ||
{ | ||
// paramSyntax should not be null here; we should always be operating on an anonymous function which will have parameter information | ||
var paramSyntax = lambda.ParameterSyntax(i); | ||
Debug.Assert(paramSyntax is { }); | ||
if (paramSyntax.Default != null && firstDefault == -1) | ||
{ | ||
firstDefault = i; | ||
} | ||
|
||
ParameterHelpers.GetModifiers(paramSyntax.Modifiers, refnessKeyword: out _, out var paramsKeyword, thisKeyword: out _, scope: out _); | ||
var isParams = paramsKeyword.Kind() != SyntaxKind.None; | ||
|
||
// UNDONE: Where do we report improper use of pointer types? | ||
ParameterHelpers.ReportParameterErrors(owner: null, paramSyntax, ordinal: i, lastParameterIndex: lambda.ParameterCount - 1, isParams: isParams, lambda.ParameterTypeWithAnnotations(i), | ||
lambda.RefKind(i), containingSymbol: null, thisKeyword: default, paramsKeyword: paramsKeyword, firstDefault, diagnostics); | ||
} | ||
} | ||
|
||
// Parser will only have accepted static/async as allowed modifiers on this construct. | ||
// However, it may have accepted duplicates of those modifiers. Ensure that any dupes | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2136,18 +2136,33 @@ internal void GenerateAnonymousFunctionConversionError(BindingDiagnosticBag diag | |
// The simplest possible case is (x, y, z)=>whatever where the target type has a ref or out parameter. | ||
|
||
var delegateParameters = delegateType.DelegateParameters(); | ||
if (reason == LambdaConversionResult.RefInImplicitlyTypedLambda) | ||
if (reason == LambdaConversionResult.MismatchedParameterRefKind) | ||
{ | ||
for (int i = 0; i < anonymousFunction.ParameterCount; ++i) | ||
{ | ||
var delegateRefKind = delegateParameters[i].RefKind; | ||
if (delegateRefKind != RefKind.None) | ||
var lambdaRefKind = anonymousFunction.RefKind(i); | ||
|
||
if (!OverloadResolution.AreRefsCompatibleForMethodConversion( | ||
candidateMethodParameterRefKind: lambdaRefKind, | ||
delegateParameterRefKind: delegateRefKind, | ||
this.Compilation)) | ||
{ | ||
// Parameter {0} must be declared with the '{1}' keyword | ||
Error(diagnostics, ErrorCode.ERR_BadParamRef, anonymousFunction.ParameterLocation(i), | ||
i + 1, delegateRefKind.ToParameterDisplayString()); | ||
var lambdaParameterLocation = anonymousFunction.ParameterLocation(i); | ||
if (delegateRefKind == RefKind.None) | ||
{ | ||
// Parameter {0} should not be declared with the '{1}' keyword | ||
Error(diagnostics, ErrorCode.ERR_BadParamExtraRef, lambdaParameterLocation, i + 1, lambdaRefKind.ToParameterDisplayString()); | ||
} | ||
else | ||
{ | ||
// Parameter {0} must be declared with the '{1}' keyword | ||
Error(diagnostics, ErrorCode.ERR_BadParamRef, lambdaParameterLocation, i + 1, delegateRefKind.ToParameterDisplayString()); | ||
} | ||
} | ||
} | ||
|
||
Debug.Assert(diagnostics.DiagnosticBag?.Count is not 0); | ||
return; | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
|
@@ -2169,16 +2184,18 @@ internal void GenerateAnonymousFunctionConversionError(BindingDiagnosticBag diag | |
|
||
if (reason == LambdaConversionResult.MismatchedParameterType) | ||
{ | ||
// This is checked in the code that returns LambdaConversionResult.MismatchedParameterType | ||
Debug.Assert(anonymousFunction.HasExplicitlyTypedParameterList); | ||
|
||
// Cannot convert {0} to type '{1}' because the parameter types do not match the delegate parameter types | ||
conversionError(diagnostics, ErrorCode.ERR_CantConvAnonMethParams, id, targetType); | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Debug.Assert(anonymousFunction.ParameterCount == delegateParameters.Length); | ||
for (int i = 0; i < anonymousFunction.ParameterCount; ++i) | ||
{ | ||
var lambdaParameterType = anonymousFunction.ParameterType(i); | ||
if (lambdaParameterType.IsErrorType()) | ||
{ | ||
continue; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this waas already checked above on line 2107. no need to check again. |
||
|
||
// Can't be an error type. This was already checked in a loop above this one. | ||
Debug.Assert(!lambdaParameterType.IsErrorType()); | ||
|
||
var lambdaParameterLocation = anonymousFunction.ParameterLocation(i); | ||
var lambdaRefKind = anonymousFunction.RefKind(i); | ||
|
@@ -2193,19 +2210,6 @@ internal void GenerateAnonymousFunctionConversionError(BindingDiagnosticBag diag | |
Error(diagnostics, ErrorCode.ERR_BadParamType, lambdaParameterLocation, | ||
i + 1, lambdaRefKind.ToParameterPrefix(), distinguisher.First, delegateRefKind.ToParameterPrefix(), distinguisher.Second); | ||
jjonescz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
else if (lambdaRefKind != delegateRefKind) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this moved upwards to an earlier error location. |
||
{ | ||
if (delegateRefKind == RefKind.None) | ||
{ | ||
// Parameter {0} should not be declared with the '{1}' keyword | ||
Error(diagnostics, ErrorCode.ERR_BadParamExtraRef, lambdaParameterLocation, i + 1, lambdaRefKind.ToParameterDisplayString()); | ||
} | ||
else | ||
{ | ||
// Parameter {0} must be declared with the '{1}' keyword | ||
Error(diagnostics, ErrorCode.ERR_BadParamRef, lambdaParameterLocation, i + 1, delegateRefKind.ToParameterDisplayString()); | ||
} | ||
} | ||
} | ||
return; | ||
} | ||
|
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.
this logic moved from teh caller. no need for it to do a secondary pass to report errors when it can just do that here.