提交 fd61eb2d 编写于 作者: V VSadov

Allow extension methods on tuple receivers when underlying conversions are "upcasts"

Extensions methods declared on bases and implemented interfaces are allowed to be applied to receivers that of derived or implementing type.

This change extends this rule to tuple receivers as long as the rule holds for individual elements, recursively.

I.E. valid "this" conversions are:
- identity conversion
- implicit reference conversion
- boxing conversion
- (new)  implicit tuple[literal] conversions as long as all underlying conversions are valid "this" conversions.
上级 6b88ea63
......@@ -5360,7 +5360,12 @@ private void BindMemberAccessReportError(BoundMethodGroup node, DiagnosticBag di
}
else
{
if (boundLeft.Kind == BoundKind.TypeExpression ||
if ((object)boundLeft.Type == null)
{
Error(diagnostics, ErrorCode.ERR_NoSuchMember, name, boundLeft.Display, plainName);
}
else if (boundLeft.Kind == BoundKind.TypeExpression ||
boundLeft.Kind == BoundKind.BaseReference ||
node.Kind() == SyntaxKind.AwaitExpression && plainName == WellKnownMemberNames.GetResult)
{
......
......@@ -1351,6 +1351,19 @@ public static bool IsValidExtensionMethodThisArgConversion(Conversion conversion
case ConversionKind.Boxing:
case ConversionKind.ImplicitReference:
return true;
case ConversionKind.ImplicitTuple:
case ConversionKind.ImplicitTupleLiteral:
// check if all element conversions satisfy the requirement
foreach (var elementConversion in conversion.UnderlyingConversions)
{
if (!IsValidExtensionMethodThisArgConversion(elementConversion))
{
return false;
}
}
return true;
default:
return false;
}
......
......@@ -21708,5 +21708,163 @@ class C
var tupleType = model.GetTypeInfo(tuple);
Assert.Equal("(System.Int32 Alice, ?)", tupleType.Type.ToTestDisplayString());
}
[Fact]
[WorkItem(16159, "https://github.com/dotnet/roslyn/issues/16159")]
public void ExtensionMethodOnConvertedTuple001()
{
var source = @"
using System;
using System.Linq;
using System.Collections.Generic;
static class C
{
static IEnumerable<(T, U)> Zip<T, U>(this(IEnumerable<T> xs, IEnumerable<U> ys) source) => source.xs.Zip(source.ys, (x, y) => (x, y));
static void Main()
{
foreach (var t in Zip((new int[] { 1, 2 }, new byte[] { 3, 4 })))
{
System.Console.WriteLine(t);
}
foreach (var t in (new int[] { 1, 2 }, new byte[] { 3, 4 }).Zip())
{
System.Console.WriteLine(t);
}
var notALiteral = (new int[] { 1, 2 }, new byte[] { 3, 4 });
foreach (var (x, y) in notALiteral.Zip())
{
System.Console.WriteLine((x, y));
}
}
}
";
var comp = CompileAndVerify(source,
additionalRefs: s_valueTupleRefs.Concat(new[] { LinqAssemblyRef }),
parseOptions: TestOptions.Regular, expectedOutput: @"
(1, 3)
(2, 4)
(1, 3)
(2, 4)
(1, 3)
(2, 4)
");
}
[Fact]
public void ExtensionMethodOnConvertedTuple002()
{
var source = @"
using System;
using System.Collections;
static class C
{
static string M(this(object x, (ValueType, IStructuralComparable) y) self) => self.ToString();
static string M1(this (dynamic x, System.Exception y) self) => self.ToString();
static void Main()
{
System.Console.WriteLine((""qq"", (Alice: 1, (2, 3))).M());
(dynamic Alice, NullReferenceException Bob) arg = (123, null);
System.Console.WriteLine(arg.M1());
}
}
";
var comp = CompileAndVerify(source,
additionalRefs: s_valueTupleRefs.Concat(new[] { LinqAssemblyRef }),
parseOptions: TestOptions.Regular, expectedOutput: @"
(qq, (1, (2, 3)))
(123, )
");
}
[Fact]
public void ExtensionMethodOnConvertedTuple002Err()
{
var source = @"
using System;
using System.Collections;
static class C
{
static string M(this(object x, (ValueType, IStructuralComparable) y) self) => self.ToString();
static string GetAwaiter(this (object x, Func<int> y) self) => self.ToString();
static void Main()
{
System.Console.WriteLine((""qq"", (Alice: 1, (null, 3))).M());
System.Console.WriteLine((""qq"", ()=>1 ).GetAwaiter());
}
}
";
var comp = CreateCompilationWithMscorlib(source, references: s_valueTupleRefs.Concat(new[] { LinqAssemblyRef }));
comp.VerifyDiagnostics(
// (13,64): error CS0117: '(string, (int, (<null>, int)))' does not contain a definition for 'M'
// System.Console.WriteLine(("qq", (Alice: 1, (null, 3))).M());
Diagnostic(ErrorCode.ERR_NoSuchMember, "M").WithArguments("(string, (int, (<null>, int)))", "M").WithLocation(13, 64),
// (15,49): error CS0117: '(string, lambda expression)' does not contain a definition for 'GetAwaiter'
// System.Console.WriteLine(("qq", ()=>1 ).GetAwaiter());
Diagnostic(ErrorCode.ERR_NoSuchMember, "GetAwaiter").WithArguments("(string, lambda expression)", "GetAwaiter").WithLocation(15, 49)
);
}
[Fact]
public void ExtensionMethodOnConvertedTuple003()
{
var source = @"
static class C
{
static string M(this (int x, long y) self) => self.ToString();
static string M1(this (int x, long? y) self) => self.ToString();
static void Main()
{
// implicit constant conversion
System.Console.WriteLine((1, 2).M());
// identity conversion (this one is OK)
System.Console.WriteLine((1, 2L).M());
// implicit nullable conversion
System.Console.WriteLine((First: 1, Second: 2L).M1());
// null literal conversion
System.Console.WriteLine((1, null).M1());
// implicit numeric conversion
var notAliteral = (A: 1, B: 2);
System.Console.WriteLine(notAliteral.M());
}
}
";
var comp = CreateCompilationWithMscorlib(source, references: s_valueTupleRefs.Concat(new[] { LinqAssemblyRef }));
comp.VerifyDiagnostics(
// (10,41): error CS1928: '(int, int)' does not contain a definition for 'M' and the best extension method overload 'C.M((int x, long y))' has some invalid arguments
// System.Console.WriteLine((1, 2).M());
Diagnostic(ErrorCode.ERR_BadExtensionArgTypes, "M").WithArguments("(int, int)", "M", "C.M((int x, long y))").WithLocation(10, 41),
// (16,57): error CS1928: '(int, long)' does not contain a definition for 'M1' and the best extension method overload 'C.M1((int x, long? y))' has some invalid arguments
// System.Console.WriteLine((First: 1, Second: 2L).M1());
Diagnostic(ErrorCode.ERR_BadExtensionArgTypes, "M1").WithArguments("(int, long)", "M1", "C.M1((int x, long? y))").WithLocation(16, 57),
// (19,44): error CS0117: '(int, <null>)' does not contain a definition for 'M1'
// System.Console.WriteLine((1, null).M1());
Diagnostic(ErrorCode.ERR_NoSuchMember, "M1").WithArguments("(int, <null>)", "M1").WithLocation(19, 44),
// (23,46): error CS1928: '(int A, int B)' does not contain a definition for 'M' and the best extension method overload 'C.M((int x, long y))' has some invalid arguments
// System.Console.WriteLine(notAliteral.M());
Diagnostic(ErrorCode.ERR_BadExtensionArgTypes, "M").WithArguments("(int A, int B)", "M", "C.M((int x, long y))").WithLocation(23, 46)
);
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册