-
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
Lambda expression explicit return type: binding #54075
Changes from 4 commits
1102544
9b2d9ed
f8f6c7a
08d21a9
a752010
6c05d62
ed9af13
20a262b
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 |
---|---|---|
|
@@ -1809,6 +1809,7 @@ internal BoundExpression GenerateConversionForAssignment(TypeSymbol targetType, | |
return CreateConversion(expression.Syntax, expression, conversion, isCast: false, conversionGroupOpt: null, targetType, diagnostics); | ||
} | ||
|
||
#nullable enable | ||
internal void GenerateAnonymousFunctionConversionError(BindingDiagnosticBag diagnostics, SyntaxNode syntax, | ||
UnboundLambda anonymousFunction, TypeSymbol targetType) | ||
{ | ||
|
@@ -1868,9 +1869,13 @@ internal void GenerateAnonymousFunctionConversionError(BindingDiagnosticBag diag | |
return; | ||
} | ||
|
||
// At this point we know that we have either a delegate type or an expression type for the target. | ||
if (reason == LambdaConversionResult.MismatchedReturnType) | ||
{ | ||
Error(diagnostics, ErrorCode.ERR_CantConvAnonMethReturnType, syntax, id, targetType); | ||
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. nit: The name of the error code suggests that conversions have something to do, but the logic and message are that the types have to match. Consider renaming the error code and diagnostic. #Closed 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. Never mind. I see that we're using same naming convention for parameter types ( |
||
return; | ||
} | ||
|
||
var delegateType = targetType.GetDelegateType(); | ||
// At this point we know that we have either a delegate type or an expression type for the target. | ||
|
||
// The target type is a valid delegate or expression tree type. Is there something wrong with the | ||
// parameter list? | ||
|
@@ -1896,6 +1901,9 @@ internal void GenerateAnonymousFunctionConversionError(BindingDiagnosticBag diag | |
return; | ||
} | ||
|
||
var delegateType = targetType.GetDelegateType(); | ||
Debug.Assert(delegateType is not null); | ||
|
||
// There is a parameter list. Does it have the right number of elements? | ||
|
||
if (reason == LambdaConversionResult.BadParameterCount) | ||
|
@@ -2013,6 +2021,7 @@ internal void GenerateAnonymousFunctionConversionError(BindingDiagnosticBag diag | |
Debug.Assert(false, "Missing case in lambda conversion error reporting"); | ||
diagnostics.Add(ErrorCode.ERR_InternalError, syntax.Location); | ||
} | ||
#nullable disable | ||
|
||
protected static void GenerateImplicitConversionError(BindingDiagnosticBag diagnostics, CSharpCompilation compilation, SyntaxNode syntax, | ||
Conversion conversion, TypeSymbol sourceType, TypeSymbol targetType, ConstantValue sourceConstantValueOpt = null) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1246,7 +1246,6 @@ source.Type is object && | |
IsNumericType(source.Type) && | ||
IsConstantNumericZero(sourceConstantValue); | ||
} | ||
#nullable disable | ||
|
||
private static LambdaConversionResult IsAnonymousFunctionCompatibleWithDelegate(UnboundLambda anonymousFunction, TypeSymbol type) | ||
{ | ||
|
@@ -1266,6 +1265,15 @@ private static LambdaConversionResult IsAnonymousFunctionCompatibleWithDelegate( | |
return LambdaConversionResult.BadTargetType; | ||
} | ||
|
||
if (anonymousFunction.HasExplicitReturnType(out var refKind, out var returnType)) | ||
{ | ||
if (invokeMethod.RefKind != refKind || | ||
!invokeMethod.ReturnType.Equals(returnType.Type, TypeCompareKind.AllIgnoreOptions)) | ||
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. Are we sure this is the rule we want? Does this fall out of existing spec rules, or needs to be spec'ed? On one hand, 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. Also, is Update: I see 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 should be confirmed with LDM, but I believe it corresponds to the behavior for parameters. For instance,
There are tests for 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. You're correct, this is the behavior for parameters: https://github.com/dotnet/csharplang/blob/main/spec/conversions.md#anonymous-function-conversions We probably need to add a similar rule to the spec for the return type. 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. These things should just fall out. Variance only allows for implicit reference conversions, nothing else, because they need to be representation-preserving. 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.
Added tests for custom modifiers. |
||
{ | ||
return LambdaConversionResult.MismatchedReturnType; | ||
} | ||
} | ||
|
||
var delegateParameters = invokeMethod.Parameters; | ||
|
||
// SPEC: If F contains an anonymous-function-signature, then D and F have the same number of parameters. | ||
|
@@ -1426,6 +1434,7 @@ private static bool HasAnonymousFunctionConversion(BoundExpression source, TypeS | |
|
||
return IsAnonymousFunctionCompatibleWithType((UnboundLambda)source, destination) == LambdaConversionResult.Success; | ||
} | ||
#nullable disable | ||
|
||
internal Conversion ClassifyImplicitUserDefinedConversionForV6SwitchGoverningType(TypeSymbol sourceType, out TypeSymbol switchGoverningType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ | |
#nullable disable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Diagnostics; | ||
using Microsoft.CodeAnalysis.CSharp.Symbols; | ||
|
@@ -18,7 +17,7 @@ internal class WithLambdaParametersBinder : LocalScopeBinder | |
{ | ||
protected readonly LambdaSymbol lambdaSymbol; | ||
protected readonly MultiDictionary<string, ParameterSymbol> parameterMap; | ||
private SmallDictionary<string, ParameterSymbol> _definitionMap; | ||
private readonly SmallDictionary<string, ParameterSymbol> _definitionMap; | ||
|
||
public WithLambdaParametersBinder(LambdaSymbol lambdaSymbol, Binder enclosing) | ||
: base(enclosing) | ||
|
@@ -29,24 +28,17 @@ public WithLambdaParametersBinder(LambdaSymbol lambdaSymbol, Binder enclosing) | |
var parameters = lambdaSymbol.Parameters; | ||
if (!parameters.IsDefaultOrEmpty) | ||
{ | ||
recordDefinitions(parameters); | ||
foreach (var parameter in lambdaSymbol.Parameters) | ||
_definitionMap = new SmallDictionary<string, ParameterSymbol>(); | ||
foreach (var parameter in parameters) | ||
{ | ||
if (!parameter.IsDiscard) | ||
{ | ||
this.parameterMap.Add(parameter.Name, parameter); | ||
} | ||
} | ||
} | ||
|
||
void recordDefinitions(ImmutableArray<ParameterSymbol> definitions) | ||
{ | ||
var declarationMap = _definitionMap ??= new SmallDictionary<string, ParameterSymbol>(); | ||
foreach (var s in definitions) | ||
{ | ||
if (!s.IsDiscard && !declarationMap.ContainsKey(s.Name)) | ||
{ | ||
declarationMap.Add(s.Name, s); | ||
var name = parameter.Name; | ||
this.parameterMap.Add(name, parameter); | ||
if (!_definitionMap.ContainsKey(name)) | ||
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. We don't need the 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. Oops, missed it above ;-) |
||
{ | ||
_definitionMap.Add(name, parameter); | ||
} | ||
} | ||
} | ||
} | ||
|
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.
Theoretically, when we start inferring delegate types, we could encounter an async function with an
__arglist
parameter. Do we need to check that here? Or perhaps assert that we'll never encounter such a scenario? SourceOrdinaryMethodSymbolBase checks this case in its equivalent method, so I think we should do something about it, even if it's just to assert we'll never hit it. #ClosedThere 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.
Is this an issue introduced by explicit return types or an existing issue with lambda parameters?
It looks like the parser rejects
__arglist
inIsPossibleLambdaParameter()
. I've added some parsing tests.