Skip to content

Avoid several breaking changes in overload resolution from inferred types of lambda expressions and method groups #56341

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

Merged
merged 23 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 101 additions & 1 deletion docs/compilers/CSharp/Compiler Breaking Changes - DotNet 6.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,104 @@
{
if (o is null!) {} // error
}
```
```

2. In C# 10, lambda expressions and method groups with inferred type are implicitly convertible to `System.MulticastDelegate`, and bases classes and interfaces of `System.MulticastDelegate` including `object`,
and lambda expressions and method groups are implicitly convertible to `System.Linq.Expressions.Expression` and `System.Linq.Expressions.LambdaExpression`.
These are _function_type_conversions_.

Copy link
Contributor

@AlekseyTs AlekseyTs Sep 17, 2021

Choose a reason for hiding this comment

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

Consider breaking long lines #Closed

The new implicit conversions may change overload resolution in cases where the compiler searches iteratively for overloads and stops at the first type or namespace scope containing any applicable overloads.

a. Instance and extension methods

```csharp
class C
{
static void Main()
{
var c = new C();
c.M(Main); // C#9: E.M(); C#10: C.M()
c.M(() => { }); // C#9: E.M(); C#10: C.M()
}

void M(System.Delegate d) { }
}

static class E
{
public static void M(this object x, System.Action y) { }
}
```

b. Base and derived methods

```csharp
using System;
using System.Linq.Expressions;

class A
{
public void M(Func<int> f) { }
public object this[Func<int> f] => null;
public static A operator+(A a, Func<int> f) => a;
}

class B : A
{
public void M(Expression e) { }
public object this[Delegate d] => null;
public static B operator+(B b, Delegate d) => b;
}

class Program
{
static int F() => 1;

static void Main()
{
var b = new B();
b.M(() => 1); // C#9: A.M(); C#10: B.M()
_ = b[() => 2]; // C#9: A.this[]; C#10: B.this[]
_ = b + F; // C#9: A.operator+(); C#10: B.operator+()
}
}
```

c. Method group conversion to `Expression` or `LambdaExpression`

```csharp
using System;
using System.Linq.Expressions;

var c = new C();
c.M(F); // error CS0428: Cannot convert method group 'F' to non-delegate type 'Expression'

static int F() => 0;

class C
{
public void M(Expression e) { }
}

static class E
{
public static void M(this object o, Func<int> a) { }
}
```

3. In C#10, a lambda expression with inferred type may contribute an argument type that affects overload resolution.

```csharp
using System;

class Program
{
static void F(Func<Func<object>> f, int i) { }
static void F(Func<Func<int>> f, object o) { }

static void Main()
{
F(() => () => 1, 2); // C#9: F(Func<Func<object>>, int); C#10: ambiguous
Copy link
Contributor

@AlekseyTs AlekseyTs Sep 22, 2021

Choose a reason for hiding this comment

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

// C#9: F(Func<Func>, int); C#10: ambiguous

This behavior change feels unexpected to me, perhaps we should follow up on it. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -48,30 +48,7 @@ record Derived(int I) // The positional member 'Base.I' found corresponding to t
}
```

4. In C# 10, lambda expressions and method groups are implicitly convertible to `System.MulticastDelegate`, or any base classes or interfaces of `System.MulticastDelegate` including `object`, and lambda expressions are implicitly convertible to `System.Linq.Expressions.Expression`.

This is a breaking change to overload resolution if there exists an applicable overload with a parameter of type `System.MulticastDelegate`, or a parameter of a type in the base types or interfaces of `System.MulticastDelegate`, or a parameter of type `System.Linq.Expressions.Expression`, and the closest applicable extension method overload with a strongly-typed delegate parameter is in an enclosing namespace.

```C#
class C
{
static void Main()
{
var c = new C();
c.M(Main); // C#9: E.M(), C#10: C.M()
c.M(() => { }); // C#9: E.M(), C#10: C.M()
}

void M(System.Delegate d) { }
}

static class E
{
public static void M(this object x, System.Action y) { }
}
```

5. In .NET 5 and Visual Studio 16.9 (and earlier), top-level statements could be used in a program containing a type named `Program`. In .NET 6 and Visual Studio 17.0, top-level statements generate a partial declaration of a `Program` class, so any user-defined `Program` type must also be a partial class.
4. In .NET 5 and Visual Studio 16.9 (and earlier), top-level statements could be used in a program containing a type named `Program`. In .NET 6 and Visual Studio 17.0, top-level statements generate a partial declaration of a `Program` class, so any user-defined `Program` type must also be a partial class.

```csharp
System.Console.Write("top-level");
Expand All @@ -85,7 +62,7 @@ partial class Program
}
```

6. https://github.com/dotnet/roslyn/issues/53021 C# will now report an error for a misplaced ```::``` token in explicit interface implementation. In this example code:
5. https://github.com/dotnet/roslyn/issues/53021 C# will now report an error for a misplaced ```::``` token in explicit interface implementation. In this example code:

``` C#
void N::I::M()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.Diagnostics.CodeAnalysis;

Expand All @@ -13,17 +11,23 @@ namespace Microsoft.CodeAnalysis.CSharp
/// Represents the results of overload resolution for a single member.
/// </summary>
[SuppressMessage("Performance", "CA1067", Justification = "Equality not actually implemented")]
internal struct MemberResolutionResult<TMember> where TMember : Symbol
internal readonly struct MemberResolutionResult<TMember> where TMember : Symbol
{
private readonly TMember _member;
private readonly TMember _leastOverriddenMember;
private readonly MemberAnalysisResult _result;

internal MemberResolutionResult(TMember member, TMember leastOverriddenMember, MemberAnalysisResult result)
/// <summary>
/// At least one type argument was inferred from a function type.
/// </summary>
internal readonly bool HasTypeArgumentInferredFromFunctionType;

internal MemberResolutionResult(TMember member, TMember leastOverriddenMember, MemberAnalysisResult result, bool hasTypeArgumentInferredFromFunctionType = false)
{
_member = member;
_leastOverriddenMember = leastOverriddenMember;
_result = result;
HasTypeArgumentInferredFromFunctionType = hasTypeArgumentInferredFromFunctionType;
}

internal bool IsNull
Expand Down Expand Up @@ -112,7 +116,7 @@ internal MemberAnalysisResult Result
get { return _result; }
}

public override bool Equals(object obj)
public override bool Equals(object? obj)
{
throw new NotSupportedException();
}
Expand Down
Loading