/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Test/Semantic/Semantics/UserDefinedConversionTests.cs
2 324 строки
75 KB
Stephan Bauer
Avoid duplicate error with ambiguous user-defined conversion (#82395)
23 фев 2026, 19:45
Не верифицирован
23 фев 2026, 19:45
1331de9
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // 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.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { public partial class UserDefinedConversionTests : CompilingTestBase { #region "Source" private readonly string _userDefinedConversionTestTemplate = @" class C1 { } class C2 { } class D { public static XXX operator C1(D d) { return null; } public static XXX operator C2(D d) { return null; } public static XXX operator D(C1 c1) { return null; } public static XXX operator D(C2 c2) { return null; } } struct E1 { } struct E2 { } struct F { public static XXX operator E1(F f) { return default(E1); } public static XXX operator E2(F f) { return default(E2); } public static XXX operator F(E1 e1) { return default(F); } public static XXX operator F(E2 e2) { return default(F); } } struct G {} struct H { public static XXX operator G(H h) { return default(G); } } struct I { public static XXX operator G(I? i) { return default(G); } } struct J { public static XXX operator G?(J j) { return default(G?); } } struct K { public static XXX operator G?(K? k) { return default(G?); } } struct L { public static XXX operator G(L l) { return default(G); } public static XXX operator G(L? l) { return default(G); } } struct M { public static XXX operator G(M m) { return default(G); } public static XXX operator G?(M? m) { return default(G?); } } struct N { public static XXX operator G(N? n) { return default(G); } public static XXX operator G?(N n) { return default(G?); } } struct O { public static XXX operator G(O? o) { return default(G); } public static XXX operator G?(O? o) { return default(G?); } } struct P { public static XXX operator G(P p) { return default(G); } public static XXX operator G(P? p) { return default(G); } public static XXX operator G?(P p) { return default(G?); } } struct Q { public static XXX operator G(Q q) { return default(G); } public static XXX operator G(Q? q) { return default(G); } public static XXX operator G?(Q? q) { return default(G?); } } struct R { public static XXX operator G(R r) { return default(G); } public static XXX operator G?(R r) { return default(G?); } public static XXX operator G?(R? r) { return default(G?); } } struct S { public static XXX operator G(S? s) { return default(G); } public static XXX operator G?(S s) { return default(G?); } public static XXX operator G?(S? s) { return default(G?); } } struct T { public static XXX operator G(T t) { return default(G); } public static XXX operator G(T? t) { return default(G); } public static XXX operator G?(T t) { return default(G?); } public static XXX operator G?(T? t) { return default(G?); } } "; #endregion [Fact] public void TestUserDefinedImplicitConversionOverloadResolution() { string source1 = _userDefinedConversionTestTemplate.Replace("XXX", "implicit"); string source2 = @" class Z { static void MC1(C1 c1) { } static void MC2(C2 c2) { } static void MD(D d) { } static void ME1(E1 e1) { } static void MNE1(E1? e1) { } static void ME2(E2 e2) { } static void MNE2(E2? e2) { } static void MF(F f) { } static void MNF(F? f) { } static void MG(G g) { } static void MNG(G? g) { } static void Main() { MC1(default(D)); MC2(default(D)); MD(default(C1)); MD(default(C2)); ME1(default(F)); MNE1(default(F)); MNE1(default(F?)); ME2(default(F)); MNE2(default(F)); MNE2(default(F?)); MF(default(E1)); MF(default(E2)); MNF(default(E1)); MNF(default(E1?)); MNF(default(E2)); MNF(default(E2?)); MG(default(H)); // MG(default(H?)); Not implicit MNG(default(H)); MNG(default(H?)); MG(default(I)); MG(default(I?)); MNG(default(I)); MNG(default(I?)); //MG(default(J)); Not implicit //MG(default(J?)); Not implicit MNG(default(J)); MNG(default(J?)); // Invalid according to specification. Native compiler and Roslyn allow it // by improperly 'half lifting' the conversion. //MG(default(K)); Not implicit //MG(default(K?)); Not implicit MNG(default(K)); MNG(default(K?)); MG(default(L)); MG(default(L?)); MNG(default(L)); // Ambiguous according to specification; Roslyn and native compiler allow it MNG(default(L?)); MG(default(M)); //MG(default(M?)); Not implicit MNG(default(M)); // Ambiguous according to specification; Roslyn and native compiler allow it. MNG(default(M?)); MG(default(N)); MG(default(N?)); MNG(default(N)); // MNG(default(N?)); // Valid according to specification. Native compiler and Roslyn claim this is ambiguous // even though the conversion from N-->G? is not applicable because it cannot be lifted. // Native compiler and Roslyn choose improperly 'half lift' the operator to N?-->G?. MG(default(O)); MG(default(O?)); MNG(default(O)); MNG(default(O?)); MG(default(P)); MG(default(P?)); MNG(default(P)); //MNG(default(P?)); // Similarly valid according to specification, but ambiguous according to native compiler and Roslyn. MG(default(Q)); MG(default(Q?)); MNG(default(Q)); // Ambiguous according to specification; Roslyn and native compiler allow it. MNG(default(Q?)); MG(default(R)); //MG(default(R?)); Not implicit MNG(default(R)); MNG(default(R?)); MG(default(S)); MG(default(S?)); MNG(default(S)); MNG(default(S?)); MG(default(T)); MG(default(T?)); MNG(default(T)); MNG(default(T?)); } } "; string source3 = @" class Z { static void MC1(C1 c1) { } static void MC2(C2 c2) { } static void MD(D d) { } static void ME1(E1 e1) { } static void MNE1(E1? e1) { } static void ME2(E2 e2) { } static void MNE2(E2? e2) { } static void MF(F f) { } static void MNF(F? f) { } static void MG(G g) { } static void MNG(G? g) { } static void Main() { // None of these are implicit conversions. MG(default(H?)); MG(default(J)); MG(default(J?)); MG(default(K)); MG(default(K?)); MG(default(M?)); MG(default(R?)); } } "; var comp = CreateCompilation(source1 + source2); comp.VerifyDiagnostics(); comp = CreateCompilation(source1 + source3); comp.VerifyDiagnostics( // (115,8): error CS1503: Argument 1: cannot convert from 'H?' to 'G' // MG(default(H?)); Diagnostic(ErrorCode.ERR_BadArgType, "default(H?)").WithArguments("1", "H?", "G"), // (116,8): error CS1503: Argument 1: cannot convert from 'J' to 'G' // MG(default(J)); Diagnostic(ErrorCode.ERR_BadArgType, "default(J)").WithArguments("1", "J", "G"), // (117,8): error CS1503: Argument 1: cannot convert from 'J?' to 'G' // MG(default(J?)); Diagnostic(ErrorCode.ERR_BadArgType, "default(J?)").WithArguments("1", "J?", "G"), // (119,8): error CS1503: Argument 1: cannot convert from 'K' to 'G' // MG(default(K)); Diagnostic(ErrorCode.ERR_BadArgType, "default(K)").WithArguments("1", "K", "G"), // (120,8): error CS1503: Argument 1: cannot convert from 'K?' to 'G' // MG(default(K?)); Diagnostic(ErrorCode.ERR_BadArgType, "default(K?)").WithArguments("1", "K?", "G"), // (121,8): error CS1503: Argument 1: cannot convert from 'M?' to 'G' // MG(default(M?)); Diagnostic(ErrorCode.ERR_BadArgType, "default(M?)").WithArguments("1", "M?", "G"), // (122,8): error CS1503: Argument 1: cannot convert from 'R?' to 'G' // MG(default(R?)); Diagnostic(ErrorCode.ERR_BadArgType, "default(R?)").WithArguments("1", "R?", "G")); } [Fact, WorkItem(543716, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543716")] public void TestUserDefinedConversionOverloadResolution_SpecViolations() { // These are all cases where the specification says the conversion should either not exist // or be ambiguous, but the native compiler allows the conversion. Roslyn emulates the // native compiler's behavior to avoid the breaking change. string implicitConversions = _userDefinedConversionTestTemplate.Replace("XXX", "implicit"); string implicitConversionBadSuccess = @" class Z { static void MNG(G? g) { } static void MG(G g) { } static void Main() { // Implicit conversions MNG(default(J?)); MNG(default(L)); MNG(default(M)); MNG(default(Q)); // Explicit conversions MNG((G?)default(L)); MG((G)default(M?)); MNG((G?)default(M)); MNG((G?)default(Q)); MG((G)default(R?)); } }"; var comp = CreateCompilation(implicitConversions + implicitConversionBadSuccess); comp.VerifyDiagnostics(); // More cases where the specification says that the conversion should be bad, but // the native compiler and Roslyn allow it. string explicitConversions = _userDefinedConversionTestTemplate.Replace("XXX", "explicit"); string explicitConversionsBadSuccess = @" class Z { static void MG(G g) { } static void MNG(G? g) { } static void Main() { MNG((G?)default(L)); MG((G)default(M?)); MNG((G?)default(M)); MNG((G?)default(Q)); MG((G)default(R?)); } }"; comp = CreateCompilation(explicitConversions + explicitConversionsBadSuccess); comp.VerifyDiagnostics(); // These are cases where the specification indicates that a conversion should be legal, // but the native compiler disallows it. Roslyn follows the native compiler in these cases. string implicitConversionsBadFailures = @" class Z { static void MNG(G? g) { } static void Main() { MNG(default(N?)); MNG(default(P?)); } };"; comp = CreateCompilation(implicitConversions + implicitConversionsBadFailures); comp.VerifyDiagnostics( // (103,9): error CS0457: Ambiguous user defined conversions 'N.implicit operator G(N?)' and 'N.implicit operator G?(N)' when converting from 'N?' to 'G?' // MNG(default(N?)); Diagnostic(ErrorCode.ERR_AmbigUDConv, "default(N?)").WithArguments("N.implicit operator G(N?)", "N.implicit operator G?(N)", "N?", "G?"), // (104,9): error CS0457: Ambiguous user defined conversions 'P.implicit operator G(P)' and 'P.implicit operator G(P?)' when converting from 'P?' to 'G?' // MNG(default(P?)); Diagnostic(ErrorCode.ERR_AmbigUDConv, "default(P?)").WithArguments("P.implicit operator G(P)", "P.implicit operator G(P?)", "P?", "G?") ); // More cases where the specification indicates that a conversion should be legal, // but the native compiler disallows it. Roslyn follows the native compiler in these cases. string explicitConversionsBadFailures = @" class Z { static void MNG(G? g) { } static void Main() { MNG((G?)default(N?)); MNG((G?)default(P?)); } };"; comp = CreateCompilation(explicitConversions + explicitConversionsBadFailures); comp.VerifyDiagnostics( // (103,9): error CS0457: Ambiguous user defined conversions 'N.explicit operator G(N?)' and 'N.explicit operator G?(N)' when converting from 'N?' to 'G?' // MNG((G?)default(N?)); Diagnostic(ErrorCode.ERR_AmbigUDConv, "(G?)default(N?)").WithArguments("N.explicit operator G(N?)", "N.explicit operator G?(N)", "N?", "G?"), // (104,9): error CS0457: Ambiguous user defined conversions 'P.explicit operator G(P)' and 'P.explicit operator G(P?)' when converting from 'P?' to 'G?' // MNG((G?)default(P?)); Diagnostic(ErrorCode.ERR_AmbigUDConv, "(G?)default(P?)").WithArguments("P.explicit operator G(P)", "P.explicit operator G(P?)", "P?", "G?") ); } [Fact] public void TestUserDefinedConversionOverloadResolution_BreakingChanges() { // Roslyn emulates most of the native compiler's spec violations for user-defined conversions. // It is possible for a user-defined implicit conversion to be ambiguous when processed // as an explicit conversion but unambiguous when processed as an implicit conversion. // In that circumstance, the native compiler reports the ambiguity, oddly enough. // One might expect that if "B b = 1;" succeeds then "B b = (B)1;" ought to as well. // Roslyn matches the native compiler. // This is bug 11202. string source1 = @" class A { public static implicit operator A(int d) { return null; } } class B : A { public static implicit operator B(long d) { return null; } } class C { static void Main() { B b = 1; // Only the operator in B is applicable because we must // convert implicitly from the return type to B. b = (B)1; // Both the operators are applicable because we must // convert from the return type to B via an explicit conversion, // and A to B is therefore legal. Therefore, were we to treat // this solely as an explicit conversion, we'd have an ambiguity // because the return type exactly matches on the operator in B, // and the parameter type exactly matches on the operator in A. // The native compiler produces an error here. Roslyn reasons // that if the implicit conversion succeeds then the explicit conversion // ought to also succeed. } }"; var comp = CreateCompilation(source1); comp.VerifyDiagnostics( // (18,13): error CS0457: Ambiguous user defined conversions 'B.implicit operator B(long)' and 'A.implicit operator A(int)' when converting from 'int' to 'B' // b = (B)1; Diagnostic(ErrorCode.ERR_AmbigUDConv, "(B)1").WithArguments("B.implicit operator B(long)", "A.implicit operator A(int)", "int", "B")); // The native compiler incorrectly treats the explicit user-defined // conversion operator as inapplicable here. Roslyn allows this. // Contrast this with the genuinely incorrect code in the method // TestUserDefinedConversionsTypeParameterEdgeCase below. string source2 = @" class Animal {} class Mammal : Animal {} class X<T> where T : Mammal { public static explicit operator X<T>(Animal a) { return null; } public static void M(T t) { X<T> xt = (X<T>)t; } } "; comp = CreateCompilation(source2); comp.VerifyDiagnostics(); } [Fact] public void TestUserDefinedConversionsTypeParameterEdgeCase() { // In contrast with the code above, this code should genuinely produce // an error. The cast operator means that a standard explicit conversion can // be inserted on both sides of the implicit conversion. Though there is an // explicit conversion from T to Giraffe, this is not a *standard* implicit // conversion because there is no implicit conversion from Giraffe to T. string source = @" class Animal {} class Mammal : Animal {} class Giraffe : Mammal {} class X<T> where T : Mammal { public static implicit operator X<T>(Giraffe g) { return null; } public static void M(T t) { X<T> xt = (X<T>)t; } } "; var comp = CreateCompilation(source); comp.VerifyDiagnostics( // (10,15): error CS0030: Cannot convert type 'T' to 'X<T>' // X<T> xt = (X<T>)t; Diagnostic(ErrorCode.ERR_NoExplicitConv, "(X<T>)t").WithArguments("T", "X<T>")); } [Fact, WorkItem(605100, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/605100")] public void TestUserDefinedConversions_DynamicIdentityBetweenBaseTypes() { string source = @" public class A<U> { public static explicit operator T(A<U> a) { return null; } } public class S : A<dynamic> { } public class T : A<object> { } public class X { static T F(S s) { return (T)s; } } "; // Dev11 doesn't use identity conversion and reports an error, which is wrong: // error CS0457: Ambiguous user defined conversions 'A<dynamic>.explicit operator T(A<dynamic>)' and 'A<object>.explicit operator T(A<object>)' when converting from 'S' to 'T' CreateCompilationWithMscorlib40AndSystemCore(source).VerifyDiagnostics(); } [Fact, WorkItem(605326, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/605326")] public void TestUserDefinedConversions_DynamicIdentityBetweenBaseTypeAndTargetType() { string source = @" public class A<T> { } public class B : A<dynamic> { public static explicit operator A<object>(B x) { return null; } } "; // TODO (tomat): This should report ERR_ConversionWithBase CreateCompilationWithMscorlib40AndSystemCore(source).VerifyDiagnostics(); } [Fact] public void TestUserDefinedExplicitConversionOverloadResolution() { // Explicit conversions should use implicit conversions. string source1 = _userDefinedConversionTestTemplate.Replace("XXX", "implicit"); string source2 = _userDefinedConversionTestTemplate.Replace("XXX", "explicit"); string source3 = @" class Z { static void MC1(C1 c1) { } static void MC2(C2 c2) { } static void MD(D d) { } static void ME1(E1 e1) { } static void MNE1(E1? e1) { } static void ME2(E2 e2) { } static void MNE2(E2? e2) { } static void MF(F f) { } static void MNF(F? f) { } static void MG(G g) { } static void MNG(G? g) { } static void Main() { MC1((C1)default(D)); MC2((C2)default(D)); MD((D)default(C1)); MD((D)default(C2)); ME1((E1)default(F)); MNE1((E1?)default(F)); MNE1((E1?)default(F?)); ME2((E2)default(F)); MNE2((E2?)default(F)); MNE2((E2?)default(F?)); MF((F)default(E1)); MF((F)default(E2)); MNF((F?)default(E1)); MNF((F?)default(E1?)); MNF((F?)default(E2)); MNF((F?)default(E2?)); MG((G)default(H)); MG((G)default(H?)); MNG((G?)default(H)); MNG((G?)default(H?)); MG((G)default(I)); MG((G)default(I?)); MNG((G?)default(I)); MNG((G?)default(I?)); MG((G)default(J)); MG((G)default(J?)); MNG((G?)default(J)); MNG((G?)default(J?)); MG((G)default(K)); MG((G)default(K?)); MNG((G?)default(K)); MNG((G?)default(K?)); MG((G)default(L)); MG((G)default(L?)); MNG((G?)default(L)); // Spec says this should be ambiguous; native compiler and Roslyn allow it. MNG((G?)default(L?)); MG((G)default(M)); MG((G)default(M?)); // Spec says this should be ambiguous; native compiler and Roslyn allow it. MNG((G?)default(M)); // Spec says this should be ambiguous; native compiler and Roslyn allow it. MNG((G?)default(M?)); // MG((G)default(N)); This is an interesting one. The conversion is ambiguous when declared as explicit, unambiguous when implicit. See below. MG((G)default(N?)); MNG((G?)default(N)); // MNG((G?)default(N?)); // Spec says this should be unambiguous; native compiler and Roslyn make it ambiguous. MG((G)default(O)); MG((G)default(O?)); MNG((G?)default(O)); MNG((G?)default(O?)); MG((G)default(P)); MG((G)default(P?)); MNG((G?)default(P)); // MNG((G?)default(P?)); // Spec says this should be unambiguous; native compiler and Roslyn make it ambiguous. MG((G)default(Q)); MG((G)default(Q?)); MNG((G?)default(Q)); // Spec says this should be ambiguous; native compiler and Roslyn allow it. MNG((G?)default(Q?)); MG((G)default(R)); MG((G)default(R?)); // Spec says this should be ambiguous; native compiler and Roslyn allow it. MNG((G?)default(R)); MNG((G?)default(R?)); // MG((G)default(S)); Another one that is ambiguous when declared as explicit, unambiguous when implicit. See below. MG((G)default(S?)); MNG((G?)default(S)); MNG((G?)default(S?)); MG((G)default(T)); MG((G)default(T?)); MNG((G?)default(T)); MNG((G?)default(T?)); } } "; string source4 = @" class Z { static void MG(G g) { } static void MNG(G? g) { } static void Main() { MG((G)default(N)); MG((G)default(S)); } } "; var comp = CreateCompilation(source1 + source3); comp.VerifyDiagnostics(); comp = CreateCompilation(source2 + source3); comp.VerifyDiagnostics(); // The native compiler produces errors for all of these because // it does not consider whether or not an *implicit* conversion // would have succeeded; it only considers whether an *explicit* // conversion is unambiguous. // Roslyn originally had a breaking change here, but now matches dev11. comp = CreateCompilation(source1 + source4); comp.VerifyDiagnostics( // (105,8): error CS0457: Ambiguous user defined conversions 'N.implicit operator G(N?)' and 'N.implicit operator G?(N)' when converting from 'N' to 'G' // MG((G)default(N)); Diagnostic(ErrorCode.ERR_AmbigUDConv, "(G)default(N)").WithArguments("N.implicit operator G(N?)", "N.implicit operator G?(N)", "N", "G"), // (106,8): error CS0457: Ambiguous user defined conversions 'S.implicit operator G(S?)' and 'S.implicit operator G?(S)' when converting from 'S' to 'G' // MG((G)default(S)); Diagnostic(ErrorCode.ERR_AmbigUDConv, "(G)default(S)").WithArguments("S.implicit operator G(S?)", "S.implicit operator G?(S)", "S", "G")); // When restricted to only use explicit conversions, the conversions // truly are ambiguous; the native and Roslyn compilers agree on these cases: comp = CreateCompilation(source2 + source4); comp.VerifyDiagnostics( // (105,8): error CS0457: Ambiguous user defined conversions 'N.explicit operator G(N?)' and 'N.explicit operator G?(N)' when converting from 'N' to 'G' Diagnostic(ErrorCode.ERR_AmbigUDConv, "(G)default(N)").WithArguments("N.explicit operator G(N?)", "N.explicit operator G?(N)", "N", "G"), // (106,9): error CS0457: Ambiguous user defined conversions 'N.explicit operator G(N?)' and 'N.explicit operator G?(N)' when converting from 'N?' to 'G?' Diagnostic(ErrorCode.ERR_AmbigUDConv, "(G)default(S)").WithArguments("S.explicit operator G(S?)", "S.explicit operator G?(S)", "S", "G")); } [Fact] public void TestUserDefinedConversions() { string source = @" using System; class C { public static void Main() { S sx = new S('x'); D dy = new D('y'); E ez = new F('z'); T tx = sx; F fg = (F)dy; B dz = (D)ez; Console.Write(tx); Console.Write(fg); Console.Write(dz); } } struct S { public string str; public S(string str) { this.str = str; } public S(char chr) { this.str = chr.ToString(); } public override string ToString() { return 'S' + this.str; } public static implicit operator T(S s) { return new T(s.str); } } struct T { public string str; public T(string str) { this.str = str; } public T(char chr) { this.str = chr.ToString(); } public override string ToString() { return 'T' + this.str; } } class B { public string str; public B(string str) { this.str = str; } public B(char chr) { this.str = chr.ToString(); } public override string ToString() { return 'B' + this.str; } public static explicit operator E(B b) { return new F(b.str); } } class D : B { public D(char chr) : base(chr) {} public D(string str) : base(str) {} public static explicit operator D(F f) { return new D(f.str); } public override string ToString() { return 'D' + this.str; } } class E { public string str; public E(string str) { this.str = str; } public E(char chr) { this.str = chr.ToString(); } public override string ToString() { return 'E' + this.str; } } class F : E { public F(char chr) : base(chr) {} public F(string str) : base(str) {} public override string ToString() { return 'F' + this.str; } } "; string output = "TxFyDz"; CompileAndVerify(source: source, expectedOutput: output); } [Fact(Skip = "https://github.com/dotnet/roslyn/issues/39959")] [WorkItem(39959, "https://github.com/dotnet/roslyn/issues/39959")] public void TestIntPtrUserDefinedConversions() { // IntPtr and UIntPtr violate the rules of user-defined conversions for // backwards-compatibility reasons. All of these should compile without error. string source1 = @" using System; unsafe class P { public static void Main() { byte uint8 = 0; sbyte int8 = 0; short int16 = 0; ushort uint16 = 0; int int32 = 0; long int64 = 0; uint uint32 = 0; ulong uint64 = 0; double real64 = 0; float real32 = 0; char chr = '\0'; DayOfWeek e = default(DayOfWeek); IntPtr intPtr = default(IntPtr); UIntPtr uintPtr = default(UIntPtr); byte? nuint8 = 0; sbyte? nint8 = 0; short? nint16 = 0; ushort? nuint16 = 0; int? nint32 = 0; long? nint64 = 0; uint? nuint32 = 0; ulong? nuint64 = 0; double? nreal64 = 0; float? nreal32 = 0; char? nchr = '\0'; DayOfWeek? ne = default(DayOfWeek); IntPtr? nintPtr = default(IntPtr); UIntPtr? nuintPtr = default(UIntPtr); "; string source2 = @" decimal dec = 0; decimal? ndec = 0; void* pvoid = default(void*); int* pint = default(int*); "; string source3 = @" // The spec requires that there be either an implicit conversion // from the argument type to the parameter type, or from the // parameter type to the argument type. It does *not* require // that there be an *explicit* conversion from the argument type // to the parameter type. // // This means that if the argument type is short and the parameter // type is uint, the conversion is not a candidate. There is an // implicit conversion from short to uint, but there is no implicit // conversion from short to uint or from uint to short. // // The spec also requires that the operator chosen be unambiguous. // A conversion from byte --> IntPtr?, for example, is ambiguous because // it could be: // byte --> int --> IntPtr --> IntPtr?, or // byte --> int? --> IntPtr? (using the lifted conversion), or // byte --> long --> IntPtr --> IntPtr? or // byte --> long? --> IntPtr? (using the lifted conversion) // // And that set is ambiguous; there is no best operator. // // The native compiler, and hence Roslyn, implements none of these rules. // It allows conversions from IntPtr to any numeric type and vice versa. // All the cases below should compile without error. // // numeric to IntPtr intPtr = (IntPtr) uint8; // i32 imp intPtr = (IntPtr) int8; // i32 imp intPtr = (IntPtr) uint16; // i32 imp intPtr = (IntPtr) int16; // i32 imp intPtr = (IntPtr) chr; // i32 exp intPtr = (IntPtr) uint32; // i64 imp intPtr = (IntPtr) int32; // i32 id intPtr = (IntPtr) uint64; // i64 exp * intPtr = (IntPtr) int64; // i64 id intPtr = (IntPtr) real32; // i64 exp intPtr = (IntPtr) real64; // i64 exp intPtr = (IntPtr) e; // numeric to UIntPtr uintPtr = (UIntPtr) uint8; // u32 imp uintPtr = (UIntPtr) int8; // u64 exp (ulong is bigger type than uint) * uintPtr = (UIntPtr) uint16; // u32 imp uintPtr = (UIntPtr) int16; // u64 exp (ulong bigger) * uintPtr = (UIntPtr) chr; // u32 exp uintPtr = (UIntPtr) uint32; // u32 id uintPtr = (UIntPtr) int32; // u64 exp (ulong bigger) * uintPtr = (UIntPtr) uint64; // u64 id uintPtr = (UIntPtr) int64; // u64 exp uintPtr = (UIntPtr) real32; // u64 exp uintPtr = (UIntPtr) real64; // u64 exp uintPtr = (UIntPtr) e; // IntPtr to numeric uint8 = (byte) intPtr; // i32 uint16 = (ushort) intPtr; // i32 chr = (char) intPtr; // i32 uint32 = (uint) intPtr; // i32 uint64 = (ulong) intPtr; // i64 int8 = (sbyte) intPtr; // i32 int16 = (short) intPtr; // i32 int32 = (int) intPtr; // i32 int64 = (long) intPtr; // i64 real32 = (float) intPtr; // i64 real64 = (double) intPtr; // i64 e = (DayOfWeek) intPtr; // UIntPtr to numeric uint8 = (byte) uintPtr; // u32 uint16 = (ushort) uintPtr; // u32 chr = (char) uintPtr; // u32 uint32 = (uint) uintPtr; // u32 uint64 = (ulong) uintPtr; // u64 int8 = (sbyte) uintPtr; // u32 int16 = (short) uintPtr; // u32 int32 = (int) uintPtr; // u32 int64 = (long) uintPtr; // u64 real32 = (float) uintPtr; // u64 real64 = (double) uintPtr; // u64 e = (DayOfWeek) uintPtr; // numeric to IntPtr? nintPtr = (IntPtr?) uint8; // i32 imp nintPtr = (IntPtr?) int8; // i32 imp nintPtr = (IntPtr?) uint16; // i32 imp nintPtr = (IntPtr?) int16; // i32 imp nintPtr = (IntPtr?) chr; // i32 exp nintPtr = (IntPtr?) uint32; // i64 imp nintPtr = (IntPtr?) int32; // i32 id nintPtr = (IntPtr?) uint64; // i64 exp nintPtr = (IntPtr?) int64; // i64 id nintPtr = (IntPtr?) real32; // i64 exp nintPtr = (IntPtr?) real64; // i64 exp nintPtr = (IntPtr?) e; // numeric to UIntPtr? nuintPtr = (UIntPtr?) uint8; // u32 imp nuintPtr = (UIntPtr?) int8; // u64 exp (ulong is bigger type than uint) nuintPtr = (UIntPtr?) uint16; // u32 imp nuintPtr = (UIntPtr?) int16; // u64 exp (ulong bigger) nuintPtr = (UIntPtr?) chr; // u32 exp nuintPtr = (UIntPtr?) uint32; // u32 id nuintPtr = (UIntPtr?) int32; // u64 exp (ulong bigger) nuintPtr = (UIntPtr?) uint64; // u64 id nuintPtr = (UIntPtr?) int64; // u64 exp nuintPtr = (UIntPtr?) real32; // u64 exp nuintPtr = (UIntPtr?) real64; // u64 exp nuintPtr = (UIntPtr?) e; // From nullable numeric to IntPtr intPtr = (IntPtr) nuint8; // i32 imp intPtr = (IntPtr) nint8; // i32 imp intPtr = (IntPtr) nuint16; // i32 imp intPtr = (IntPtr) nint16; // i32 imp intPtr = (IntPtr) nchr; // i32 exp intPtr = (IntPtr) nuint32; // i64 imp intPtr = (IntPtr) nint32; // i32 id intPtr = (IntPtr) nuint64; // i64 exp intPtr = (IntPtr) nint64; // i64 id intPtr = (IntPtr) nreal32; // i64 exp intPtr = (IntPtr) nreal64; // i64 exp intPtr = (IntPtr) ne; // From nullable numeric to UIntPtr uintPtr = (UIntPtr) nuint8; // u32 imp uintPtr = (UIntPtr) nint8; // u64 exp (ulong is bigger type than uint) uintPtr = (UIntPtr) nuint16; // u32 imp uintPtr = (UIntPtr) nint16; // u64 exp (ulong bigger) uintPtr = (UIntPtr) nchr; // u32 exp uintPtr = (UIntPtr) nuint32; // u32 id uintPtr = (UIntPtr) nint32; // u64 exp (ulong bigger) uintPtr = (UIntPtr) nuint64; // u64 id uintPtr = (UIntPtr) nint64; // u64 exp uintPtr = (UIntPtr) nreal32; // u64 exp uintPtr = (UIntPtr) nreal64; // u64 exp uintPtr = (UIntPtr) ne; // From IntPtr? to numeric uint8 = (byte) nintPtr; // i32 uint16 = (ushort) nintPtr; // i32 chr = (char) nintPtr; // i32 uint32 = (uint) nintPtr; // i32 uint64 = (ulong) nintPtr; // i64 int8 = (sbyte) nintPtr; // i32 int16 = (short) nintPtr; // i32 int32 = (int) nintPtr; // i32 int64 = (long) nintPtr; // i64 real32 = (float) nintPtr; // i64 real64 = (double) nintPtr; // i64 e = (DayOfWeek) nintPtr; // From UIntPtr? to numeric uint8 = (byte) nuintPtr; // u32 uint16 = (ushort) nuintPtr; // u32 chr = (char) nuintPtr; // u32 uint32 = (uint) nuintPtr; // u32 uint64 = (ulong) nuintPtr; // u64 int8 = (sbyte) nuintPtr; // u32 int16 = (short) nuintPtr; // u32 int32 = (int) nuintPtr; // u32 int64 = (long) nuintPtr; // u64 real32 = (float) nuintPtr; // u64 real64 = (double) nuintPtr; // u64 e = (DayOfWeek) nuintPtr; // From IntPtr to nullable numeric nuint8 = (byte?) intPtr; nuint16 = (ushort?) intPtr; nchr = (char?) intPtr; nuint32 = (uint?) intPtr; nuint64 = (ulong?) intPtr; nint8 = (sbyte?) intPtr; nint16 = (short?) intPtr; nint32 = (int?) intPtr; nint64 = (long?) intPtr; nreal32 = (float?) intPtr; nreal64 = (double?) intPtr; ne = (DayOfWeek?) intPtr; // From UIntPtr to nullable numeric nuint8 = (byte?) uintPtr; nuint16 = (ushort?) uintPtr; nchr = (char?) uintPtr; nuint32 = (uint?) uintPtr; nuint64 = (ulong?) uintPtr; nint8 = (sbyte?) uintPtr; nint16 = (short?) uintPtr; nint32 = (int?) uintPtr; nint64 = (long?) uintPtr; nreal32 = (float?) uintPtr; nreal64 = (double?) uintPtr; ne = (DayOfWeek?) uintPtr; "; string source4 = @" // Decimal conversion lowering is not yet implemented: uintPtr = (UIntPtr) dec; // u64 exp intPtr = (IntPtr) dec; // i64 exp dec = (decimal) intPtr; // i64 dec = (decimal) uintPtr; // u64 nintPtr = (IntPtr?) dec; // i64 exp nuintPtr = (UIntPtr?) dec; // u64 exp intPtr = (IntPtr) ndec; // i64 exp uintPtr = (UIntPtr) ndec; // u64 exp dec = (decimal) nintPtr; // i64 dec = (decimal) nuintPtr; // u64 ndec = (decimal?) intPtr; ndec = (decimal?) uintPtr; ndec = (decimal?) nintPtr; ndec = (decimal?) nuintPtr; // Lifted numeric To IntPtr nintPtr = (IntPtr?) nuint8; // i32 imp nintPtr = (IntPtr?) nint8; // i32 imp nintPtr = (IntPtr?) nuint16; // i32 imp nintPtr = (IntPtr?) nint16; // i32 imp nintPtr = (IntPtr?) nchr; // i32 exp nintPtr = (IntPtr?) nuint32; // i64 imp nintPtr = (IntPtr?) nint32; // i32 id nintPtr = (IntPtr?) nuint64; // i64 exp nintPtr = (IntPtr?) nint64; // i64 id nintPtr = (IntPtr?) ndec; // i64 exp nintPtr = (IntPtr?) nreal32; // i64 exp nintPtr = (IntPtr?) nreal64; // i64 exp nintPtr = (IntPtr?) ne; // Lifted numeric to UIntPtr nuintPtr = (UIntPtr?) nuint8; // u32 imp nuintPtr = (UIntPtr?) nint8; // u64 exp (ulong is bigger type than uint) nuintPtr = (UIntPtr?) nuint16; // u32 imp nuintPtr = (UIntPtr?) nint16; // u64 exp (ulong bigger) nuintPtr = (UIntPtr?) nchr; // u32 exp nuintPtr = (UIntPtr?) nuint32; // u32 imp nuintPtr = (UIntPtr?) nint32; // u64 exp (ulong bigger) nuintPtr = (UIntPtr?) nuint64; // u64 id nuintPtr = (UIntPtr?) nint64; // u64 exp nuintPtr = (UIntPtr?) ndec; // u64 exp nuintPtr = (UIntPtr?) nreal32; // u64 exp nuintPtr = (UIntPtr?) nreal64; // u64 exp nuintPtr = (UIntPtr?) ne; // Lifted IntPtr to numeric: nuint8 = (byte?) nintPtr; nuint16 = (ushort?) nintPtr; nchr = (char?) nintPtr; nuint32 = (uint?) nintPtr; nuint64 = (ulong?) nintPtr; nuint32 = (uint?) nuintPtr; nint8 = (sbyte?) nintPtr; nint16 = (short?) nintPtr; nint32 = (int?) nintPtr; nint64 = (long?) nintPtr; nreal32 = (float?) nintPtr; nreal64 = (double?) nintPtr; ne = (DayOfWeek?) nintPtr; // Lifted UIntPtr to numeric: nuint8 = (byte?) nuintPtr; nuint16 = (ushort?) nuintPtr; nchr = (char?) nuintPtr; nuint64 = (ulong?) nuintPtr; nint8 = (sbyte?) nuintPtr; nint16 = (short?) nuintPtr; nint32 = (int?) nuintPtr; nint64 = (long?) nuintPtr; nreal32 = (float?) nuintPtr; nreal64 = (double?) nuintPtr; ne = (DayOfWeek?) nuintPtr; // From pointer intPtr = (IntPtr) pvoid; // pv id uintPtr = (UIntPtr) pvoid; // pv id nintPtr = (IntPtr?) pvoid; // pv id nuintPtr = (UIntPtr?) pvoid; // pv id intPtr = (IntPtr) pint; // pv ptr uintPtr = (UIntPtr) pint; // pv ptr nintPtr = (IntPtr?) pint; // pv ptr nuintPtr = (UIntPtr?) pint; // pv ptr // To pointer pvoid = (void*) intPtr; // pv pvoid = (void*) uintPtr; // pv pvoid = (void*) nintPtr; // pv pvoid = (void*) nuintPtr; // pv pint = (int*) intPtr; // pv pint = (int*) uintPtr; // pv pint = (int*) nintPtr; // pv pint = (int*) nuintPtr; // pv "; string source5 = "}}"; // All of the cases above should pass semantic analysis: var comp = CreateCompilation(source1 + source2 + source3 + source4 + source5, options: TestOptions.UnsafeReleaseDll); comp.VerifyDiagnostics(); comp.VerifyEmitDiagnostics(); // However, we have not yet implemented lowering and code generation for decimal, // lifted operators, nullable conversions and unsafe code so only generate code for // the straightforward intptr/number conversions: var verifier = CompileAndVerify(source: source1 + source3 + source5, options: TestOptions.UnsafeReleaseExe, expectedOutput: ""); } [Fact, WorkItem(543427, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543427")] public void Bug11203() { string source = @" class Program { static void Main() { A x = 1; } } class A { public static implicit operator A(ulong x) { return null; } public static implicit operator A(long x) { return null; } }"; var comp = CreateCompilation(source); comp.VerifyDiagnostics( // (6,15): error CS0457: Ambiguous user defined conversions 'A.implicit operator A(ulong)' and 'A.implicit operator A(long)' when converting from 'int' to 'A' Diagnostic(ErrorCode.ERR_AmbigUDConv, "1").WithArguments("A.implicit operator A(ulong)", "A.implicit operator A(long)", "int", "A")); } [Fact, WorkItem(543430, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543430")] public void Bug11205() { string source1 = @" using System; class A { public A(short i) : this(i.ToString()) {} public A(string str) { this.str = str; } public readonly string str; public static implicit operator short(A x) { return (short)int.Parse(x.str); } public static implicit operator A(short x) { return new A(x.ToString()); } public override string ToString() { return 'A' + str; } static void Main() { var a = new A(5); Console.Write((object)(a)); Console.Write((object)(a++)); Console.Write((object)(a)); Console.Write((object)(++a)); Console.Write((object)(a)); Console.Write((object)(a--)); Console.Write((object)(a)); Console.Write((object)(--a)); Console.Write((object)(a)); } } "; string source2 = @" class A { public static implicit operator int(A x) { return 1; } static void Main() { A a = new A(); a++; } } "; CompileAndVerify(source1, expectedOutput: "A5A5A6A7A7A7A6A5A5"); var comp = CreateCompilation(source2); comp.VerifyDiagnostics( // (13,9): error CS0029: Cannot implicitly convert type 'int' to 'A' // a++; Diagnostic(ErrorCode.ERR_NoImplicitConv, "a++").WithArguments("int", "A") ); } [Fact, WorkItem(543435, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543435")] public void TestBug11210() { // If we have both a user-defined implicit conversion and a built-in explicit conversion // then the built-in explicit conversion "wins" when cast, and the user-defined implicit // conversion "wins" when there is no cast. string source = @" using System; class B { static void Main() { C<int> x = new D<int>(); D<int> y = (D<int>)x; Console.Write(y == null ? 1 : 2); y = x; Console.Write(y == null ? 3 : 4); } } class C<T> { } class D<T> : C<T> { public static implicit operator D<T>(C<int> x) { return null; } }"; var verifier = CompileAndVerify(source, expectedOutput: "23"); } [Fact, WorkItem(543436, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543436")] public void TestBug11211() { string source = @" using System; class C { string str; public static explicit operator C(Func<string> x) { C c = new C(); c.str = x(); return c; } static void Main() { string x = 'A'.ToString(); var c = (C)(() => x.ToLower()); Console.WriteLine(c.str); } }"; var verifier = CompileAndVerify(source, expectedOutput: "a"); } [Fact, WorkItem(543439, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543439")] public void TestBug11214() { // The specification describes analysis of user-defined conversions only in // terms of the types of the source and destination; it strongly implies that // analysis should only consider the type of the source expression and not the // source expression itself. The dev10 compiler certainly does not do this; // it allows user-defined conversions from null literals, lambdas, method groups, // and so on. // // We should update the specification to say that the algorithm takes the // expression being converted into account, not just its type. string source = @" struct C { string str; public static implicit operator C?(string x) { C c = new C(); c.str = x.ToLower(); return c; } static void Main() { C c = (C)'B'.ToString(); System.Console.WriteLine(c.str); } }"; var verifier = CompileAndVerify(source, expectedOutput: "b"); } [Fact, WorkItem(543440, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543440")] public void TestBug11215() { string source = @" using System; static class A { static void Main() { C x = 1; Console.WriteLine(x.str); } } class C { public string str; public static implicit operator C(byte s) { C c = new C(); c.str = s.ToString(); return c; } }"; var verifier = CompileAndVerify(source, expectedOutput: "1"); } [Fact, WorkItem(543441, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543441")] public void TestBug11216() { // An ambiguous user-defined conversion should be considered a valid conversion // for the purposes of overload resolution; that is, this program should say that // Goo(B) and Goo(C) are both applicable candidates, and that neither is better, // even though the conversion from A to B is ambiguous. string source = @" class Program { static void Main() { A x = null; Goo(x); } static void Goo(B x) { } static void Goo(C x) { } } class A { public static implicit operator B(A x) { return null; } } class B { public static implicit operator B(A x) { return null; } } class C { public static implicit operator C(A x) { return null; } } "; var comp = CreateCompilation(source); comp.VerifyDiagnostics(// (7,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.Goo(B)' and 'Program.Goo(C)' // Goo(x); Diagnostic(ErrorCode.ERR_AmbigCall, "Goo").WithArguments("Program.Goo(B)", "Program.Goo(C)")); } [Fact, WorkItem(543446, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543446")] public void TestBug11223() { string source = @" using System; class C { string str; public static explicit operator C(Func<string, string> x) { C c = new C(); c.str = x('A'.ToString()); return c; } static string M(string s) { return s.ToLower(); } static void Main() { var c = (C)M; Console.WriteLine(c.str); } }"; var verifier = CompileAndVerify(source, expectedOutput: "a"); } [Fact, WorkItem(543595, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543595")] public void CompoundAssignment() { string source1 = @" class A { public static implicit operator A(int a) { return new A(); } public static int operator +(A a, int b) { return 1; } } class Program { static void Main() { A a = new A(); a += 5; } } "; var comp = CreateCompilation(source1); comp.VerifyDiagnostics(); var verifier = CompileAndVerify(source: source1, expectedOutput: ""); } [Fact, WorkItem(543598, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543598")] public void ConvertByteLiteralToUserDefinedType() { var source = @" public class Conv { static public implicit operator byte(Conv test) { return 1; } static public implicit operator Conv(byte val) { return null; } } class Test { public static void Main() { Conv cl = new Conv(); cl = 1; } }"; CompileAndVerify(source).VerifyDiagnostics(); } [Fact, WorkItem(543789, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543789")] public void UseImplicitConversionInBase() { string source = @" using System; class Program { static void Main() { if (-(new A())) { Console.Write(""Hello""); } } } class B { public static implicit operator bool(B p) { return true; } } class A : B { public static A operator -(A p) { return null; } }"; CompileAndVerify(source, expectedOutput: "Hello"); } [Fact, WorkItem(682456, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/682456")] public void GenericUDConversionVersusPredefinedConversion() { string source = @" class InArgument<T> { public static implicit operator InArgument<T>(T t) { return default(InArgument<T>); } } public struct @start { static public void Main() { object bar = null; InArgument<object> outArgument2 = bar as InArgument<object>; } }"; CreateCompilation(source).VerifyDiagnostics(); } [Fact, WorkItem(1063555, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1063555")] public void UserDefinedImplicitConversionsOnBuiltinTypes() { string source = @" namespace System { public class Object { } public class ValueType { } public struct Void { } public struct Byte { public static implicit operator MyClass(Byte v) { return null; } } public struct Int16 { public static implicit operator MyClass(Int16 v) { return null; } } public struct Int32 { public static implicit operator MyClass(Int32 v) { return null; } } public struct Int64 { public static implicit operator MyClass(Int64 v) { return null; } } public struct Single { public static implicit operator MyClass(Single v) { return null; } } public struct Double { public static implicit operator MyClass(Double v) { return null; } } public struct Char { public static implicit operator MyClass(Char v) { return null; } } public struct Boolean { public static implicit operator MyClass(Boolean v) { return null; } } public struct SByte { public static implicit operator MyClass(SByte v) { return null; } } public struct UInt16 { public static implicit operator MyClass(UInt16 v) { return null; } } public struct UInt32 { public static implicit operator MyClass(UInt32 v) { return null; } } public struct UInt64 { public static implicit operator MyClass(UInt64 v) { return null; } } public struct IntPtr { public static implicit operator MyClass(IntPtr v) { return null; } } public struct UIntPtr { public static implicit operator MyClass(UIntPtr v) { return null; } } public struct Decimal { public static implicit operator MyClass(Decimal v) { return null; } } public class String { public static implicit operator MyClass(String v) { return null; } } public class MyClass { static void Test(MyClass v) { } static void Main() { Test(new Byte()); Test(new Int16()); Test(new Int32()); Test(new Int64()); Test(new Single()); Test(new Double()); Test(new Char()); Test(new Boolean()); Test(new SByte()); Test(new UInt16()); Test(new UInt32()); Test(new UInt64()); Test(new IntPtr()); Test(new String()); } } } "; CreateEmptyCompilation(source).VerifyDiagnostics(); } [Fact, WorkItem(34876, "https://github.com/dotnet/roslyn/pull/34876")] public void GenericOperatorVoidConversion() { var source = @" class C<T> { public static implicit operator C<T>(T t) => new C<T>(); private static void M1() { } private static C<object> M2() { return M1(); } } "; var comp = CreateCompilation(source); comp.VerifyDiagnostics( // (9,16): error CS0029: Cannot implicitly convert type 'void' to 'C<object>' // return M1(); Diagnostic(ErrorCode.ERR_NoImplicitConv, "M1()").WithArguments("void", "C<object>").WithLocation(9, 16)); } [Fact, WorkItem(34876, "https://github.com/dotnet/roslyn/pull/34876")] public void GenericOperatorVoidConversion_Cast() { var source = @" class C<T> { public static explicit operator C<T>(T t) => new C<T>(); private static void M1() { } private static C<object> M2() { return (C<object>) M1(); } } "; var comp = CreateCompilation(source); comp.VerifyDiagnostics( // (9,16): error CS0030: Cannot convert type 'void' to 'C<object>' // return (C<object>) M1(); Diagnostic(ErrorCode.ERR_NoExplicitConv, "(C<object>) M1()").WithArguments("void", "C<object>").WithLocation(9, 16)); } [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] public void LiftedConversion_InvalidTypeArgument01() { var code = @" int? i = null; C c = i; class C { public static implicit operator C(long l) => throw null; } namespace System { public class Object {} public class String {} public class Exception {} public class ValueType : Object {} public struct Void {} public struct Int32 {} public struct Nullable<T> where T : struct {} public ref struct Int64 {} } "; var comp = CreateEmptyCompilation(code); comp.VerifyDiagnostics( // (3,7): error CS0266: Cannot implicitly convert type 'int?' to 'C'. An explicit conversion exists (are you missing a cast?) // C c = i; Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "i").WithArguments("int?", "C").WithLocation(3, 7) ); } [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] public void LiftedConversion_InvalidTypeArgument02() { var code = @" C c = null; M(c); void M(long? i) => throw null; class C { public static implicit operator int(C c) => throw null; } namespace System { public class Object {} public class String {} public class Exception {} public class ValueType : Object {} public struct Void {} public ref struct Int32 {} public struct Nullable<T> where T : struct { public Nullable(T value) {} } public struct Int64 {} public struct Boolean { } public class Attribute { } public class AttributeUsageAttribute : Attribute { public AttributeUsageAttribute(AttributeTargets t) { } public bool AllowMultiple { get; set; } public bool Inherited { get; set; } } public struct Enum { } public enum AttributeTargets { } } "; var comp = CreateEmptyCompilation(code); var verifier = CompileAndVerify(comp, verify: Verification.Skipped); // Note that no int? is being created. verifier.VerifyIL("<top-level-statements-entry-point>", @" { // Code size 18 (0x12) .maxstack 1 IL_0000: ldnull IL_0001: call ""int C.op_Implicit(C)"" IL_0006: conv.i8 IL_0007: newobj ""long?..ctor(long)"" IL_000c: call ""void Program.<<Main>$>g__M|0_0(long?)"" IL_0011: ret } "); } [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] public void LiftedConversion_InvalidTypeArgument03() { var code = @" int? i = null; C c = (C)i; class C { public static explicit operator C(long l) => throw null; } namespace System { public class Object {} public class String {} public class Exception {} public class ValueType : Object {} public struct Void {} public struct Int32 {} public struct Nullable<T> where T : struct { public T Value { get => throw null; } } public ref struct Int64 {} public struct Boolean { } public class Attribute { } public class AttributeUsageAttribute : Attribute { public AttributeUsageAttribute(AttributeTargets t) { } public bool AllowMultiple { get; set; } public bool Inherited { get; set; } } public struct Enum { } public enum AttributeTargets { } } "; var comp = CreateEmptyCompilation(code); var verifier = CompileAndVerify(comp, verify: Verification.Skipped); verifier.VerifyIL("<top-level-statements-entry-point>", @" { // Code size 23 (0x17) .maxstack 1 .locals init (int? V_0) //i IL_0000: ldloca.s V_0 IL_0002: initobj ""int?"" IL_0008: ldloca.s V_0 IL_000a: call ""int int?.Value.get"" IL_000f: conv.i8 IL_0010: call ""C C.op_Explicit(long)"" IL_0015: pop IL_0016: ret } "); } [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] public void LiftedConversion_InvalidTypeArgument04() { var code = @" C c = null; M((long?)c); void M(long? i) => throw null; class C { public static explicit operator int(C c) => throw null; } namespace System { public class Object {} public class String {} public class Exception {} public class ValueType : Object {} public struct Void {} public ref struct Int32 {} public struct Nullable<T> where T : struct { public Nullable(T value) {} } public struct Int64 {} public struct Boolean { } public class Attribute { } public class AttributeUsageAttribute : Attribute { public AttributeUsageAttribute(AttributeTargets t) { } public bool AllowMultiple { get; set; } public bool Inherited { get; set; } } public struct Enum { } public enum AttributeTargets { } } "; var comp = CreateEmptyCompilation(code); var verifier = CompileAndVerify(comp, verify: Verification.Skipped); // Note that no int? is being created. verifier.VerifyIL("<top-level-statements-entry-point>", @" { // Code size 18 (0x12) .maxstack 1 IL_0000: ldnull IL_0001: call ""int C.op_Explicit(C)"" IL_0006: conv.i8 IL_0007: newobj ""long?..ctor(long)"" IL_000c: call ""void Program.<<Main>$>g__M|0_0(long?)"" IL_0011: ret } "); } [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] public void LiftedConversion_InvalidTypeArgument05() { var code = @" unsafe { S? s = null; void* f = s; System.Console.WriteLine((int)f); } public struct S { public static unsafe implicit operator void*(S v) => throw null; } "; var comp = CreateCompilation(code, options: TestOptions.UnsafeReleaseExe); var verifier = CompileAndVerify(comp, expectedOutput: "0", verify: Verification.Skipped); verifier.VerifyIL("<top-level-statements-entry-point>", @" { // Code size 42 (0x2a) .maxstack 1 .locals init (S? V_0) IL_0000: ldloca.s V_0 IL_0002: initobj ""S?"" IL_0008: ldloc.0 IL_0009: stloc.0 IL_000a: ldloca.s V_0 IL_000c: call ""bool S?.HasValue.get"" IL_0011: brtrue.s IL_0017 IL_0013: ldc.i4.0 IL_0014: conv.u IL_0015: br.s IL_0023 IL_0017: ldloca.s V_0 IL_0019: call ""S S?.GetValueOrDefault()"" IL_001e: call ""void* S.op_Implicit(S)"" IL_0023: conv.i4 IL_0024: call ""void System.Console.WriteLine(int)"" IL_0029: ret } "); } [Fact, WorkItem(56646, "https://github.com/dotnet/roslyn/issues/56646")] public void LiftedConversion_InvalidTypeArgument06() { var code = @" unsafe { S? s = null; void* f = (void*)s; System.Console.WriteLine((int)f); } public struct S { public static unsafe explicit operator void*(S v) => throw null; } "; var comp = CreateCompilation(code, options: TestOptions.UnsafeReleaseExe); var verifier = CompileAndVerify(comp, expectedOutput: "0", verify: Verification.Skipped); verifier.VerifyIL("<top-level-statements-entry-point>", @" { // Code size 42 (0x2a) .maxstack 1 .locals init (S? V_0) IL_0000: ldloca.s V_0 IL_0002: initobj ""S?"" IL_0008: ldloc.0 IL_0009: stloc.0 IL_000a: ldloca.s V_0 IL_000c: call ""bool S?.HasValue.get"" IL_0011: brtrue.s IL_0017 IL_0013: ldc.i4.0 IL_0014: conv.u IL_0015: br.s IL_0023 IL_0017: ldloca.s V_0 IL_0019: call ""S S?.GetValueOrDefault()"" IL_001e: call ""void* S.op_Explicit(S)"" IL_0023: conv.i4 IL_0024: call ""void System.Console.WriteLine(int)"" IL_0029: ret } "); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_ImplicitObjectCreation_Ambiguous() { var source = """ C c = ("a", new()); class C { public static implicit operator C((string, int) pair) => new C(); public static implicit operator C((string, string) pair) => new C(); } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( // (1,7): error CS8135: Tuple with 2 elements cannot be converted to type 'C'. // C c = ("a", new()); Diagnostic(ErrorCode.ERR_ConversionNotTupleCompatible, @"(""a"", new())").WithArguments("2", "C").WithLocation(1, 7)); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_ImplicitObjectCreation_NotAmbiguous() { var source = """ using System; C c = ("a", new()); class C { public static implicit operator C((string, int) pair) { Console.WriteLine("int"); return new C(); } } """; var verifier = CompileAndVerify(source, expectedOutput: "int"); verifier.VerifyDiagnostics(); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_DefaultLiteral_Ambiguous() { var source = """ C c = ("a", default); class C { public static implicit operator C((string, int) pair) => new C(); public static implicit operator C((string, string) pair) => new C(); } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( // (1,7): error CS8135: Tuple with 2 elements cannot be converted to type 'C'. // C c = ("a", default); Diagnostic(ErrorCode.ERR_ConversionNotTupleCompatible, @"(""a"", default)").WithArguments("2", "C").WithLocation(1, 7)); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_NullLiteral_NotAmbiguous() { var source = """ using System; C c = ("a", null); class C { public static implicit operator C((string, int) pair) => new C(); public static implicit operator C((string, string) pair) { Console.WriteLine("string"); return new C(); } } """; var verifier = CompileAndVerify(source, expectedOutput: "string"); verifier.VerifyDiagnostics(); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_NullLiteral_NoConversion() { var source = """ C c = ("a", null); class C { public static implicit operator C((string, int) pair) => new C(); } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( // (1,7): error CS8135: Tuple with 2 elements cannot be converted to type 'C'. // C c = ("a", null); Diagnostic(ErrorCode.ERR_ConversionNotTupleCompatible, @"(""a"", null)").WithArguments("2", "C").WithLocation(1, 7)); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_SwitchExpression_Ambiguous() { var source = """ C c = ("a", "b" switch { _ => default }); class C { public static implicit operator C((string, int) pair) => new C(); public static implicit operator C((string, string) pair) => new C(); } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( // (1,7): error CS8135: Tuple with 2 elements cannot be converted to type 'C'. // C c = ("a", "b" switch { _ => default }); Diagnostic(ErrorCode.ERR_ConversionNotTupleCompatible, @"(""a"", ""b"" switch { _ => default })").WithArguments("2", "C").WithLocation(1, 7)); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_SwitchExpression_NotAmbiguous() { var source = """ using System; C c = ("a", "b" switch { _ => null }); class C { public static implicit operator C((string, int) pair) => new C(); public static implicit operator C((string, string) pair) { Console.WriteLine("string"); return new C(); } } """; var verifier = CompileAndVerify(source, expectedOutput: "string"); verifier.VerifyDiagnostics(); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_SwitchExpression_NoConversion() { var source = """ C c = ("a", "b" switch { _ => null }); class C { public static implicit operator C((string, int) pair) => new C(); } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( // (1,7): error CS8135: Tuple with 2 elements cannot be converted to type 'C'. // C c = ("a", "b" switch { _ => null }); Diagnostic(ErrorCode.ERR_ConversionNotTupleCompatible, @"(""a"", ""b"" switch { _ => null })").WithArguments("2", "C").WithLocation(1, 7)); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_CollectionExpression_Ambiguous() { var source = """ C c = ("a", [default]); class C { public static implicit operator C((string, int[]) pair) => new C(); public static implicit operator C((string, string[]) pair) => new C(); } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( // (1,7): error CS8135: Tuple with 2 elements cannot be converted to type 'C'. // C c = ("a", [default]); Diagnostic(ErrorCode.ERR_ConversionNotTupleCompatible, @"(""a"", [default])").WithArguments("2", "C").WithLocation(1, 7)); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_CollectionExpression_NotAmbiguous() { var source = """ using System; C c = ("a", [null]); class C { public static implicit operator C((string, int[]) pair) => new C(); public static implicit operator C((string, string[]) pair) { Console.WriteLine("string[]"); return new C(); } } """; var verifier = CompileAndVerify(source, expectedOutput: "string[]"); verifier.VerifyDiagnostics(); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_CollectionExpression_NoConversion() { var source = """ C c = ("a", [null]); class C { public static implicit operator C((string, int[]) pair) => new C(); } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( // (1,7): error CS8135: Tuple with 2 elements cannot be converted to type 'C'. // C c = ("a", [null]); Diagnostic(ErrorCode.ERR_ConversionNotTupleCompatible, @"(""a"", [null])").WithArguments("2", "C").WithLocation(1, 7)); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_ConditionalExpression_Ambiguous() { var source = """ bool b = true; C c = ("a", b ? default : throw null!); class C { public static implicit operator C((string, int) pair) => new C(); public static implicit operator C((string, string) pair) => new C(); } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( // (2,7): error CS8135: Tuple with 2 elements cannot be converted to type 'C'. // C c = ("a", b ? default : throw null!); Diagnostic(ErrorCode.ERR_ConversionNotTupleCompatible, @"(""a"", b ? default : throw null!)").WithArguments("2", "C").WithLocation(2, 7)); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_ConditionalExpression_NotAmbiguous() { var source = """ using System; bool b = true; C c = ("a", b ? null : throw null!); class C { public static implicit operator C((string, int) pair) => new C(); public static implicit operator C((string, string) pair) { Console.WriteLine("string"); return new C(); } } """; var verifier = CompileAndVerify(source, expectedOutput: "string"); verifier.VerifyDiagnostics(); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Tuple_UserDefinedConversion_ConditionalExpression_NoConversion() { var source = """ bool b = true; C c = ("a", b ? null : throw null!); class C { public static implicit operator C((string, int) pair) => new C(); } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( // (2,7): error CS8135: Tuple with 2 elements cannot be converted to type 'C'. // C c = ("a", b ? null : throw null!); Diagnostic(ErrorCode.ERR_ConversionNotTupleCompatible, @"(""a"", b ? null : throw null!)").WithArguments("2", "C").WithLocation(2, 7)); } [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2088611")] public void Repro_VsFeedback_2088611() { var source = """ using System; using System.Collections.Generic; public struct SpecialStruct { public readonly string Value; public readonly bool Flag; public readonly Dictionary<string, string> Map; public SpecialStruct(string value) { this.Value = value; } public SpecialStruct(string value, bool flag) { this.Value = value; this.Flag = flag; } public SpecialStruct(string value, Dictionary<string, string> map) { Value = value; Map = map; } public static implicit operator SpecialStruct(string s) => new(s); public static implicit operator SpecialStruct((string, Dictionary<string, string>) tuple) => new(tuple.Item1, tuple.Item2); public static implicit operator SpecialStruct((string, bool) tuple) => new(tuple.Item1, tuple.Item2); } public class Class1 { Dictionary<string, SpecialStruct> specialMap = new() { { "key1", "value1" }, { "key2", ("value2", true) }, { "key3", ("value3", new /*specific type is omitted*/ (StringComparer.OrdinalIgnoreCase) { { "subkey", "subvalue" }, }) }, }; } """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( // (35,23): error CS8135: Tuple with 2 elements cannot be converted to type 'SpecialStruct'. // { "key3", ("value3", new /*specific type is omitted*/ (StringComparer.OrdinalIgnoreCase) Diagnostic(ErrorCode.ERR_ConversionNotTupleCompatible, @"(""value3"", new /*specific type is omitted*/ (StringComparer.OrdinalIgnoreCase) { { ""subkey"", ""subvalue"" }, })").WithArguments("2", "SpecialStruct").WithLocation(35, 23), // (35,34): error CS8754: There is no target type for 'new(System.StringComparer)' // { "key3", ("value3", new /*specific type is omitted*/ (StringComparer.OrdinalIgnoreCase) Diagnostic(ErrorCode.ERR_ImplicitObjectCreationNoTargetType, @"new /*specific type is omitted*/ (StringComparer.OrdinalIgnoreCase) { { ""subkey"", ""subvalue"" }, }").WithArguments("new(System.StringComparer)").WithLocation(35, 34)); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81984")] public void TestAmbiguousUserDefinedConversion_DuplicateErrorMessage() { var source = """ public struct S1 { public static implicit operator S1(S2 x) => throw null; } public struct S2 { public static implicit operator S1(S2 x) => throw null; } class Program { static S1 Test(S2 x) { return x; } } """; CreateCompilation(source).VerifyDiagnostics( // (15,16): error CS0457: Ambiguous user defined conversions 'S2.implicit operator S1(S2)' and 'S1.implicit operator S1(S2)' when converting from 'S2' to 'S1' // return x; Diagnostic(ErrorCode.ERR_AmbigUDConv, "x").WithArguments("S2.implicit operator S1(S2)", "S1.implicit operator S1(S2)", "S2", "S1").WithLocation(15, 16) ); } } }