From 729fe03c2976ef466a0b6f2093a99694a2593e4c Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 24 Jul 2026 17:37:47 +0200 Subject: [PATCH] Revive NRefactory explicit-conversion tests without resolver/AST Convert the commented-out block in ExplicitConversionsTest the same way as the implicit ConversionTest block: type-parameter casts via DefaultTypeParameter with cross-referencing constraints, user-defined operators as fixture types in the test assembly, constant sources via ConstantResolveResult. The rr.Input asserts of the originals were resolver artifacts and are dropped; UseDefinedExplicitConversion_Lifted instead exercises the ResolveResult-based ExplicitConversion entry point. Also extend PreferAmbiguousConversionOverReferenceConversion with the overload-resolution half of the original NRefactory test (the ambiguous conversion must not prevent M(BB) from being chosen over M(object)), which the first revival pass had reduced to the conversion classification alone. Assisted-by: Claude:claude-fable-5:Claude Code --- .../Semantics/ConversionTests.cs | 40 +- .../Semantics/ExplicitConversionTest.cs | 669 ++++++------------ .../TypeSystem/TypeSystemTestCase.cs | 246 +++++++ 3 files changed, 487 insertions(+), 468 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs index 4f9edfc30..614893487 100644 --- a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs +++ b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs @@ -1297,25 +1297,28 @@ namespace ICSharpCode.Decompiler.Tests.Semantics Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("i")); } + /// + /// Creates a fake method named M with a single parameter of the given type. + /// + IMethod MakeUnaryMethod(Type parameterType) + { + var m = new FakeMethod(compilation, SymbolKind.Method); + m.Name = "M"; + m.Parameters = new[] { new DefaultParameter(compilation.FindType(parameterType), "x", owner: m) }; + return m; + } + [Test] public void PreferUserDefinedConversionOverReferenceConversion() { // M(new AA()) with overloads M(object) and M(string), where AA has an implicit // conversion to string, picks M(string) -- not because user-defined conversions // are better, but because string is a better conversion target. - IMethod MakeM(Type parameterType) - { - var m = new FakeMethod(compilation, SymbolKind.Method); - m.Name = "M"; - m.Parameters = new[] { new DefaultParameter(compilation.FindType(parameterType), "x", owner: m) }; - return m; - } - var or = new OverloadResolution(compilation, new[] { new ResolveResult(compilation.FindType(typeof(UserDefinedConversionTestCases.ConvertibleToString))) }); - IMethod mObject = MakeM(typeof(object)); - IMethod mString = MakeM(typeof(string)); + IMethod mObject = MakeUnaryMethod(typeof(object)); + IMethod mString = MakeUnaryMethod(typeof(string)); Assert.That(or.AddCandidate(mObject), Is.EqualTo(OverloadResolutionErrors.None)); Assert.That(or.AddCandidate(mString), Is.EqualTo(OverloadResolutionErrors.None)); Assert.That(!or.IsAmbiguous); @@ -1328,13 +1331,22 @@ namespace ICSharpCode.Decompiler.Tests.Semantics // Ambiguous conversions are a compiler error; but they are not // preventing the overload from being chosen. - // M(new AmbiguousA()) with overloads M(AmbiguousB) and M(object) picks - // M(AmbiguousB) because AmbiguousB is a better conversion target than object, - // even though the user-defined conversion itself is ambiguous (declared both - // in AmbiguousA and AmbiguousB) and therefore invalid. + // The user-defined conversion AmbiguousA -> AmbiguousB is ambiguous (declared + // in both classes) and therefore invalid... var c = ImplicitConversion(typeof(UserDefinedConversionTestCases.AmbiguousA), typeof(UserDefinedConversionTestCases.AmbiguousB)); Assert.That(c.IsUserDefined); Assert.That(!c.IsValid); + + // ...but M(new AmbiguousA()) with overloads M(AmbiguousB) and M(object) still + // picks M(AmbiguousB), because AmbiguousB is a better conversion target than object. + var or = new OverloadResolution(compilation, new[] { + new ResolveResult(compilation.FindType(typeof(UserDefinedConversionTestCases.AmbiguousA))) + }); + IMethod mAmbiguousB = MakeUnaryMethod(typeof(UserDefinedConversionTestCases.AmbiguousB)); + IMethod mObject = MakeUnaryMethod(typeof(object)); + or.AddCandidate(mAmbiguousB); + or.AddCandidate(mObject); + Assert.That(or.BestCandidate, Is.SameAs(mAmbiguousB)); } [Test] diff --git a/ICSharpCode.Decompiler.Tests/Semantics/ExplicitConversionTest.cs b/ICSharpCode.Decompiler.Tests/Semantics/ExplicitConversionTest.cs index 15266b783..85b735fe5 100644 --- a/ICSharpCode.Decompiler.Tests/Semantics/ExplicitConversionTest.cs +++ b/ICSharpCode.Decompiler.Tests/Semantics/ExplicitConversionTest.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Linq; using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.Semantics; @@ -286,656 +287,416 @@ namespace ICSharpCode.Decompiler.Tests.Semantics Assert.That(ExplicitConversion(typeof(Enum), typeof(int?)), Is.EqualTo(C.None)); } - /* TODO: we should probably revive these tests somehow - Conversion ResolveCast(string program) + /// + /// Converts a constant expression (e.g. an integer literal) to the target type. + /// + Conversion ExplicitConstantConversion(object value, Type to) { - return Resolve(program).Conversion; + IType fromType = compilation.FindType(value.GetType()); + IType to2 = compilation.FindType(to).AcceptVisitor(new ConversionTest.ReplaceSpecialTypesVisitor()); + return conversions.ExplicitConversion(new ConstantResolveResult(fromType, value), to2); } [Test] public void ObjectToTypeParameter() { - string program = @"using System; -class Test { - public void M(object o) { - T t = $(T)o$; - } -}"; - Assert.AreEqual(C.UnboxingConversion, ResolveCast(program)); + // void M(object o) { T t = (T)o; } + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T"); + Assert.That(conversions.ExplicitConversion(compilation.FindType(KnownTypeCode.Object), t), Is.EqualTo(C.UnboxingConversion)); } [Test] public void UnrelatedClassToTypeParameter() { - string program = @"using System; -class Test { - public void M(string o) { - T t = $(T)o$; - } -}"; - Assert.AreEqual(C.None, ResolveCast(program)); + // void M(string o) { T t = (T)o; } + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T"); + Assert.That(conversions.ExplicitConversion(compilation.FindType(KnownTypeCode.String), t), Is.EqualTo(C.None)); } [Test] public void IntefaceToTypeParameter() { - string program = @"using System; -class Test { - public void M(IDisposable o) { - T t = $(T)o$; - } -}"; - Assert.AreEqual(C.UnboxingConversion, ResolveCast(program)); + // void M(IDisposable o) { T t = (T)o; } + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T"); + Assert.That(conversions.ExplicitConversion(compilation.FindType(typeof(IDisposable)), t), Is.EqualTo(C.UnboxingConversion)); } [Test] public void TypeParameterToInterface() { - string program = @"using System; -class Test { - public void M(T t) { - IDisposable d = $(IDisposable)t$; - } -}"; - Assert.AreEqual(C.BoxingConversion, ResolveCast(program)); + // void M(T t) { IDisposable d = (IDisposable)t; } + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T"); + Assert.That(conversions.ExplicitConversion(t, compilation.FindType(typeof(IDisposable))), Is.EqualTo(C.BoxingConversion)); } [Test] public void ValueTypeToTypeParameter() { - string program = @"using System; -class Test { - public void M(ValueType o) where T : struct { - T t = $(T)o$; - } -}"; - Assert.AreEqual(C.UnboxingConversion, ResolveCast(program)); + // void M(ValueType o) where T : struct { T t = (T)o; } + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T", hasValueTypeConstraint: true); + Assert.That(conversions.ExplicitConversion(compilation.FindType(typeof(ValueType)), t), Is.EqualTo(C.UnboxingConversion)); } [Test] public void InvalidTypeParameterConversion() { - string program = @"using System; -class Test { - public void M(T t) { - U u = $(U)t$; - } -}"; - Assert.AreEqual(C.None, ResolveCast(program)); + // void M(T t) { U u = (U)t; } + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T"); + ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U"); + Assert.That(conversions.ExplicitConversion(t, u), Is.EqualTo(C.None)); } [Test] public void TypeParameterConversion1() { - string program = @"using System; -class Test { - public void M(T t) where T : U { - U u = $(U)t$; - } -}"; - Assert.AreEqual(C.BoxingConversion, ResolveCast(program)); + // void M(T t) where T : U { U u = (U)t; } + ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U"); + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T", constraints: new[] { u }); + Assert.That(conversions.ExplicitConversion(t, u), Is.EqualTo(C.BoxingConversion)); } [Test] public void TypeParameterConversion1Array() { - string program = @"using System; -class Test { - public void M(T[] t) where T : U { - U[] u = $(U[])t$; - } -}"; - Assert.AreEqual(C.None, ResolveCast(program)); + // void M(T[] t) where T : U { U[] u = (U[])t; } + ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U"); + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T", constraints: new[] { u }); + Assert.That(conversions.ExplicitConversion(new ArrayType(compilation, t), new ArrayType(compilation, u)), Is.EqualTo(C.None)); } [Test] public void TypeParameterConversion2() { - string program = @"using System; -class Test { - public void M(T t) where U : T { - U u = $(U)t$; - } -}"; - Assert.AreEqual(C.UnboxingConversion, ResolveCast(program)); + // void M(T t) where U : T { U u = (U)t; } + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T"); + ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U", constraints: new[] { t }); + Assert.That(conversions.ExplicitConversion(t, u), Is.EqualTo(C.UnboxingConversion)); } [Test] public void TypeParameterConversion2Array() { - string program = @"using System; -class Test { - public void M(T[] t) where U : T { - U[] u = $(U[])t$; - } -}"; - Assert.AreEqual(C.None, ResolveCast(program)); + // void M(T[] t) where U : T { U[] u = (U[])t; } + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T"); + ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U", constraints: new[] { t }); + Assert.That(conversions.ExplicitConversion(new ArrayType(compilation, t), new ArrayType(compilation, u)), Is.EqualTo(C.None)); } [Test] public void ImplicitTypeParameterConversionWithClassConstraint() { - string program = @"using System; -class Test { - public void M(T t) where T : class where U : class, T { - U u = $(U)t$; - } -}"; - Assert.AreEqual(C.ExplicitReferenceConversion, ResolveCast(program)); + // void M(T t) where T : class where U : class, T { U u = (U)t; } + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T", hasReferenceTypeConstraint: true); + ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U", hasReferenceTypeConstraint: true, constraints: new[] { t }); + Assert.That(conversions.ExplicitConversion(t, u), Is.EqualTo(C.ExplicitReferenceConversion)); } [Test] public void ImplicitTypeParameterArrayConversionWithClassConstraint() { - string program = @"using System; -class Test { - public void M(T[] t) where T : class where U : class, T { - U[] u = $(U[])t$; - } -}"; - Assert.AreEqual(C.ExplicitReferenceConversion, ResolveCast(program)); + // void M(T[] t) where T : class where U : class, T { U[] u = (U[])t; } + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T", hasReferenceTypeConstraint: true); + ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U", hasReferenceTypeConstraint: true, constraints: new[] { t }); + Assert.That(conversions.ExplicitConversion(new ArrayType(compilation, t), new ArrayType(compilation, u)), Is.EqualTo(C.ExplicitReferenceConversion)); } [Test] public void ImplicitTypeParameterConversionWithClassConstraintOnlyOnT() { - string program = @"using System; -class Test { - public void M(T t) where U : class, T { - U u = $(U)t$; - } -}"; - Assert.AreEqual(C.ExplicitReferenceConversion, ResolveCast(program)); + // void M(T t) where U : class, T { U u = (U)t; } + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T"); + ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U", hasReferenceTypeConstraint: true, constraints: new[] { t }); + Assert.That(conversions.ExplicitConversion(t, u), Is.EqualTo(C.ExplicitReferenceConversion)); } [Test] public void ImplicitTypeParameterArrayConversionWithClassConstraintOnlyOnT() { - string program = @"using System; -class Test { - public void M(T[] t) where U : class, T { - U[] u = $(U[])t$; - } -}"; - Assert.AreEqual(C.ExplicitReferenceConversion, ResolveCast(program)); + // void M(T[] t) where U : class, T { U[] u = (U[])t; } + ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T"); + ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U", hasReferenceTypeConstraint: true, constraints: new[] { t }); + Assert.That(conversions.ExplicitConversion(new ArrayType(compilation, t), new ArrayType(compilation, u)), Is.EqualTo(C.ExplicitReferenceConversion)); } [Test] public void SimpleUserDefinedConversion() { - var rr = Resolve(@" -class C1 {} -class C2 { - public static explicit operator C1(C2 c2) { - return null; - } -} -class C { - public void M() { - var c2 = new C2(); - C1 c1 = $(C1)c2$; - } -}"); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("op_Explicit", rr.Conversion.Method.Name); + // C1 c1 = (C1)c2; with explicit operator C1(C2 c2) + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.SimpleSource), + typeof(UserDefinedExplicitConversionTestCases.SimpleTarget)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.Name, Is.EqualTo("op_Explicit")); } [Test] public void ExplicitReferenceConversionFollowedByUserDefinedConversion() { - var rr = Resolve(@" - class B {} - class S : B {} - class T { - public static explicit operator T(S s) { return null; } - } - class Test { - void Run(B b) { - T t = $(T)b$; - } - }"); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("B", rr.Input.Type.Name); + // class S : B, explicit operator T(S s); + // T t = (T)b; with b of type B needs the explicit reference conversion B -> S first + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.BaseClass), + typeof(UserDefinedExplicitConversionTestCases.TFromDerived)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); } [Test] public void ImplicitUserDefinedConversionFollowedByExplicitNumericConversion() { - var rr = Resolve(@" - struct T { - public static implicit operator float(T t) { return 0; } - } - class Test { - void Run(T t) { - int x = $(int)t$; - } - }"); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); + // struct T { implicit operator float(T t) } + // int x = (int)t; + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.TImplicitToFloat), typeof(int)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); // even though the user-defined conversion is implicit, the combined conversion is explicit - Assert.That(rr.Conversion.IsExplicit); + Assert.That(c.IsExplicit); } [Test] public void BothDirectConversionAndBaseClassConversionAvailable() { - var rr = Resolve(@" - class B {} - class S : B {} - class T { - public static explicit operator T(S s) { return null; } - public static explicit operator T(B b) { return null; } - } - class Test { - void Run(B b) { - T t = $(T)b$; - } - }"); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("b", rr.Conversion.Method.Parameters.Single().Name); + // class S : B, T with explicit operators from S ("s") and from B ("b"); + // T t = (T)b; picks the operator from B + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.BaseClass), + typeof(UserDefinedExplicitConversionTestCases.TFromDerivedOrBase)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.Parameters.Single().Name, Is.EqualTo("b")); } [Test] public void UserDefinedExplicitConversion_PicksExactSourceTypeIfPossible() { - string program = @"using System; -class Convertible { - public static explicit operator Convertible(int i) {return new Convertible(); } - public static explicit operator Convertible(short s) {return new Convertible(); } -} -class Test { - public void M() { - var a = $(Convertible)33$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("i", rr.Conversion.Method.Parameters[0].Name); + // explicit operators from int ("i") and short ("s"); + // (Convertible)33 + var c = ExplicitConstantConversion(33, typeof(UserDefinedExplicitConversionTestCases.ExplicitFromIntOrShort)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("i")); } [Test] public void UserDefinedExplicitConversion_PicksMostEncompassedSourceTypeIfPossible() { - string program = @"using System; -class Convertible { - public static explicit operator Convertible(long l) {return new Convertible(); } - public static explicit operator Convertible(uint ui) {return new Convertible(); } -} -class Test { - public void M() { - var a = $(Convertible)(ushort)33$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("ui", rr.Conversion.Method.Parameters[0].Name); + // explicit operators from long ("l") and uint ("ui"); + // (Convertible)(ushort)33 + var c = ExplicitConstantConversion((ushort)33, typeof(UserDefinedExplicitConversionTestCases.ExplicitFromLongOrUInt)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("ui")); } [Test] public void UserDefinedExplicitConversion_PicksMostEncompassingSourceType() { - string program = @"using System; -class Convertible { - public static explicit operator Convertible(int i) {return new Convertible(); } - public static explicit operator Convertible(ushort us) {return new Convertible(); } -} -class Test { - public void M() { - var a = $(Convertible)(long)33$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("i", rr.Conversion.Method.Parameters[0].Name); + // explicit operators from int ("i") and ushort ("us"); + // (Convertible)(long)33 + var c = ExplicitConstantConversion((long)33, typeof(UserDefinedExplicitConversionTestCases.ExplicitFromIntOrUShort)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("i")); } [Test] public void UserDefinedExplicitConversion_NoMostEncompassingSourceTypeIsInvalid() { - string program = @"using System; -class Convertible { - public static explicit operator Convertible(uint i) {return new Convertible(); } - public static explicit operator Convertible(short us) {return new Convertible(); } -} -class Test { - public void M() { - var a = $(Convertible)(long)33$; - } -}"; - var rr = Resolve(program); - Assert.That(!rr.Conversion.IsValid); + // explicit operators from uint and short; neither source type encompasses the + // other, so the conversion from long is ambiguous. + var c = ExplicitConstantConversion((long)33, typeof(UserDefinedExplicitConversionTestCases.ExplicitFromUIntOrShort)); + Assert.That(!c.IsValid); } [Test] public void UserDefinedExplicitConversion_PicksExactTargetTypeIfPossible() { - string program = @"using System; -class Convertible { - public static explicit operator int(Convertible i) {return 0; } - public static explicit operator short(Convertible s) {return 0; } -} -class Test { - public void M() { - var a = $(int)new Convertible()$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("i", rr.Conversion.Method.Parameters[0].Name); + // explicit operators to int ("i") and short ("s"); + // (int)new Convertible() + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ExplicitToIntOrShort), typeof(int)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("i")); } [Test] public void UserDefinedExplicitConversion_PicksMostEncompassingTargetTypeIfPossible() { - string program = @"using System; -class Convertible { - public static explicit operator int(Convertible i) {return 0; } - public static explicit operator ushort(Convertible us) {return 0; } -} -class Test { - public void M() { - var a = $(ulong)new Convertible()$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("us", rr.Conversion.Method.Parameters[0].Name); + // explicit operators to int ("i") and ushort ("us"); + // (ulong)new Convertible() + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ExplicitToIntOrUShort), typeof(ulong)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("us")); } [Test] public void UserDefinedExplicitConversion_PicksMostEncompassedTargetType() { - string program = @"using System; -class Convertible { - public static explicit operator long(Convertible l) { return 0; } - public static explicit operator uint(Convertible ui) { return 0; } -} -class Test { - public void M() { - var a = $(ushort)new Convertible()$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("ui", rr.Conversion.Method.Parameters[0].Name); + // explicit operators to long ("l") and uint ("ui"); + // (ushort)new Convertible() + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ExplicitToLongOrUInt), typeof(ushort)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("ui")); } [Test] public void UserDefinedExplicitConversion_NoMostEncompassedTargetTypeIsInvalid() { - string program = @"using System; -class Convertible { - public static explicit operator ulong(Convertible l) { return 0; } - public static explicit operator int(Convertible ui) { return 0; } -} -class Test { - public void M() { - var a = $(ushort)new Convertible()$; - } -}"; - var rr = Resolve(program); - Assert.That(!rr.Conversion.IsValid); + // explicit operators to ulong and int; neither target type encompasses the + // other, so the conversion to ushort is ambiguous. + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ExplicitToULongOrInt), typeof(ushort)); + Assert.That(!c.IsValid); } [Test] public void UserDefinedExplicitConversion_AmbiguousIsInvalid() { - string program = @"using System; -class Convertible1 { - public static explicit operator Convertible2(Convertible1 c) {return 0; } -} -class Convertible2 { - public static explicit operator Convertible2(Convertible1 c) {return 0; } -} -class Test { - public void M() { - var a = $(Convertible2)new Convertible1()$; - } -}"; - var rr = Resolve(program); - Assert.That(!rr.Conversion.IsValid); + // Both ExplicitAmbiguousA and ExplicitAmbiguousB declare + // explicit operator ExplicitAmbiguousB(ExplicitAmbiguousA). + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ExplicitAmbiguousA), + typeof(UserDefinedExplicitConversionTestCases.ExplicitAmbiguousB)); + Assert.That(!c.IsValid); } [Test] public void UserDefinedExplicitConversion_Lifted() { - string program = @"using System; -struct Convertible { - public static explicit operator Convertible(int i) {return new Convertible(); } -} -class Test { - public void M(int? i) { - a = $(Convertible?)i$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.That(rr.Conversion.IsLifted); + // struct Convertible { explicit operator Convertible(int i) } + // (Convertible?)i with i of type int? + var c = ExplicitConversion(typeof(int?), typeof(UserDefinedExplicitConversionTestCases.ExplicitFromInt?)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.IsLifted); } [Test] public void UserDefinedExplicitConversionFollowedByImplicitNullableConversion() { - string program = @"using System; -struct Convertible { - public static explicit operator Convertible(int i) {return new Convertible(); } -} -class Test { - public void M(int i) { - a = $(Convertible?)i$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.That(!rr.Conversion.IsLifted); + // struct Convertible { explicit operator Convertible(int i) } + // (Convertible?)i with i of type int + var c = ExplicitConversion(typeof(int), typeof(UserDefinedExplicitConversionTestCases.ExplicitFromInt?)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(!c.IsLifted); } [Test] public void UserDefinedExplicitConversion_ExplicitNullable_ThenUserDefined() { - string program = @"using System; -struct Convertible { - public static explicit operator Convertible(int i) {return new Convertible(); } - public static explicit operator Convertible?(int? ni) {return new Convertible(); } -} -class Test { - public void M(int? i) { - a = $(Convertible)i$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.That(!rr.Conversion.IsLifted); - Assert.AreEqual("i", rr.Conversion.Method.Parameters[0].Name); + // struct Convertible with explicit operators from int ("i") and from int? ("ni"); + // (Convertible)i with i of type int? unwraps the nullable and uses the int operator + var c = ExplicitConversion(typeof(int?), typeof(UserDefinedExplicitConversionTestCases.ExplicitNullableConvertible)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(!c.IsLifted); + Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("i")); } [Test] public void UserDefinedExplicitConversion_DefinedNullableTakesPrecedenceOverLifted() { - string program = @"using System; -struct Convertible { - public static explicit operator Convertible(int i) {return new Convertible(); } - public static explicit operator Convertible?(int? ni) {return new Convertible(); } -} -class Test { - public void M() { - a = $(Convertible?)(int?)33$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.That(!rr.Conversion.IsLifted); - Assert.AreEqual("ni", rr.Conversion.Method.Parameters[0].Name); + // struct Convertible with explicit operators from int ("i") and from int? ("ni"); + // (Convertible?)(int?)33 -- the user-defined nullable operator wins over the + // lifted form of the int operator. + var c = ExplicitConversion(typeof(int?), typeof(UserDefinedExplicitConversionTestCases.ExplicitNullableConvertible?)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(!c.IsLifted); + Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("ni")); } [Test] public void UserDefinedExplicitConversion_UIntConstant() { - string program = @"using System; -class Convertible { - public static explicit operator Convertible(long l) {return new Convertible(); } - public static explicit operator Convertible(uint ui) {return new Convertible(); } -} -class Test { - public void M() { - var a = $(Convertible)33$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("ui", rr.Conversion.Method.Parameters[0].Name); + // explicit operators from long ("l") and uint ("ui"); + // (Convertible)33 -- the constant 33 converts to uint, which is more specific + var c = ExplicitConstantConversion(33, typeof(UserDefinedExplicitConversionTestCases.ExplicitFromLongOrUInt)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("ui")); } [Test] public void UserDefinedExplicitConversion_NullableUIntConstant() { - string program = @"using System; -class Convertible { - public static explicit operator Convertible(long? l) {return new Convertible(); } - public static explicit operator Convertible(uint? ui) {return new Convertible(); } -} -class Test { - public void M() { - Convertible a = $(Convertible)33$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("ui", rr.Conversion.Method.Parameters[0].Name); + // explicit operators from long? ("l") and uint? ("ui"); + // (Convertible)33 + var c = ExplicitConstantConversion(33, typeof(UserDefinedExplicitConversionTestCases.ExplicitFromNullableLongOrNullableUInt)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("ui")); } [Test] public void UseDefinedExplicitConversion_Lifted() { - string program = @" -struct Convertible { - public static explicit operator Convertible(int i) { return new Convertible(); } -} -class Test { - public void M(int? i) { - a = $(Convertible?)i$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.That(rr.Conversion.IsLifted); - Assert.That(rr.Input is LocalResolveResult); + // Same conversion as UserDefinedExplicitConversion_Lifted, but through the + // ResolveResult-based entry point (the original test cast a local variable). + var c = conversions.ExplicitConversion( + new ResolveResult(compilation.FindType(typeof(int?))), + compilation.FindType(typeof(UserDefinedExplicitConversionTestCases.ExplicitFromInt?))); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.IsLifted); } [Test] public void UserDefinedExplicitConversion_Short_Or_NullableByte_Target() { - string program = @"using System; -class Test { - public static explicit operator short(Test s) { return 0; } - public static explicit operator byte?(Test b) { return 0; } -} -class Program { - public static void Main(string[] args) - { - int? x = $(int?)new Test()$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("System.Int16", rr.Conversion.Method.ReturnType.FullName); + // explicit operators to short ("s") and byte? ("b"); + // (int?)new Test() + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ExplicitToShortOrNullableByte), typeof(int?)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.ReturnType.FullName, Is.EqualTo("System.Int16")); } [Test] public void UserDefinedExplicitConversion_Byte_Or_NullableShort_Target() { - string program = @"using System; -class Test { - public static explicit operator byte(Test b) { return 0; } - public static explicit operator short?(Test s) { return 0; } -} -class Program { - public static void Main(string[] args) - { - int? x = $(int?)new Test()$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("s", rr.Conversion.Method.Parameters[0].Name); + // explicit operators to byte ("b") and short? ("s"); + // (int?)new Test() + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ExplicitToByteOrNullableShort), typeof(int?)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("s")); } [Test] public void ExplicitConversionOperatorsCanOverrideApplicableImplicitOnes() { - string program = @" -struct Convertible { - public static explicit operator int(Convertible ci) {return 0; } - public static implicit operator short(Convertible cs) {return 0; } -} -class Test { - static void Main() { - int i = $(int)new Convertible()$; // csc uses the explicit conversion operator - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.IsUserDefined); - Assert.AreEqual("ci", rr.Conversion.Method.Parameters[0].Name); + // struct Convertible { explicit operator int(Convertible ci); implicit operator short(Convertible cs); } + // int i = (int)new Convertible(); -- csc uses the explicit conversion operator + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ExplicitIntImplicitShort), typeof(int)); + Assert.That(c.IsValid); + Assert.That(c.IsUserDefined); + Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("ci")); } [Test] public void UserDefinedExplicitConversion_ConversionBeforeUserDefinedOperatorIsCorrect() { - string program = @"using System; -class Convertible { - public static implicit operator Convertible(int l) {return new Convertible(); } -} -class Test { - public void M() { - long i = 33; - Convertible a = $(Convertible)i$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.ConversionBeforeUserDefinedOperator.IsValid); - Assert.That(rr.Conversion.ConversionBeforeUserDefinedOperator.IsExplicit); - Assert.That(rr.Conversion.ConversionBeforeUserDefinedOperator.IsNumericConversion); - Assert.That(rr.Conversion.ConversionAfterUserDefinedOperator.IsIdentityConversion); + // implicit operator Convertible(int l); casting a long goes long -> int -> Convertible, + // with an explicit numeric conversion before the operator. + var c = ExplicitConversion(typeof(long), typeof(UserDefinedExplicitConversionTestCases.ImplicitFromInt)); + Assert.That(c.IsValid); + Assert.That(c.ConversionBeforeUserDefinedOperator.IsValid); + Assert.That(c.ConversionBeforeUserDefinedOperator.IsExplicit); + Assert.That(c.ConversionBeforeUserDefinedOperator.IsNumericConversion); + Assert.That(c.ConversionAfterUserDefinedOperator.IsIdentityConversion); } [Test] public void UserDefinedExplicitConversion_ConversionAfterUserDefinedOperatorIsCorrect() { - string program = @"using System; -class Convertible { - public static implicit operator long(Convertible i) {return 0; } -} -class Test { - public void M() { - int a = $(int)new Convertible()$; - } -}"; - var rr = Resolve(program); - Assert.That(rr.Conversion.IsValid); - Assert.That(rr.Conversion.ConversionBeforeUserDefinedOperator.IsIdentityConversion); - Assert.That(rr.Conversion.ConversionAfterUserDefinedOperator.IsValid); - Assert.That(rr.Conversion.ConversionAfterUserDefinedOperator.IsExplicit); - Assert.That(rr.Conversion.ConversionAfterUserDefinedOperator.IsNumericConversion); - }*/ + // implicit operator long(Convertible i); casting to int goes Convertible -> long -> int, + // with an explicit numeric conversion after the operator. + var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ImplicitToLong), typeof(int)); + Assert.That(c.IsValid); + Assert.That(c.ConversionBeforeUserDefinedOperator.IsIdentityConversion); + Assert.That(c.ConversionAfterUserDefinedOperator.IsValid); + Assert.That(c.ConversionAfterUserDefinedOperator.IsExplicit); + Assert.That(c.ConversionAfterUserDefinedOperator.IsNumericConversion); + } } } diff --git a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs index 92bf4fd83..faf96bdb1 100644 --- a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs +++ b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs @@ -965,6 +965,252 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem } } + /// + /// Fixtures for the ExplicitConversionsTest.UserDefined* tests: types with user-defined + /// (mostly explicit) conversion operators. Operator parameter names are significant, + /// several tests assert which operator was chosen by its parameter name. + /// + public static class UserDefinedExplicitConversionTestCases + { + public class SimpleTarget { } + + public class SimpleSource + { + public static explicit operator SimpleTarget(SimpleSource c2) + { + return null; + } + } + + public class BaseClass { } + + public class DerivedClass : BaseClass { } + + public class TFromDerived + { + public static explicit operator TFromDerived(DerivedClass s) + { + return null; + } + } + + public struct TImplicitToFloat + { + public static implicit operator float(TImplicitToFloat t) + { + return 0; + } + } + + public class TFromDerivedOrBase + { + public static explicit operator TFromDerivedOrBase(DerivedClass s) + { + return null; + } + public static explicit operator TFromDerivedOrBase(BaseClass b) + { + return null; + } + } + + public class ExplicitFromIntOrShort + { + public static explicit operator ExplicitFromIntOrShort(int i) + { + return new ExplicitFromIntOrShort(); + } + public static explicit operator ExplicitFromIntOrShort(short s) + { + return new ExplicitFromIntOrShort(); + } + } + + public class ExplicitFromLongOrUInt + { + public static explicit operator ExplicitFromLongOrUInt(long l) + { + return new ExplicitFromLongOrUInt(); + } + public static explicit operator ExplicitFromLongOrUInt(uint ui) + { + return new ExplicitFromLongOrUInt(); + } + } + + public class ExplicitFromIntOrUShort + { + public static explicit operator ExplicitFromIntOrUShort(int i) + { + return new ExplicitFromIntOrUShort(); + } + public static explicit operator ExplicitFromIntOrUShort(ushort us) + { + return new ExplicitFromIntOrUShort(); + } + } + + public class ExplicitFromUIntOrShort + { + public static explicit operator ExplicitFromUIntOrShort(uint i) + { + return new ExplicitFromUIntOrShort(); + } + public static explicit operator ExplicitFromUIntOrShort(short us) + { + return new ExplicitFromUIntOrShort(); + } + } + + public class ExplicitToIntOrShort + { + public static explicit operator int(ExplicitToIntOrShort i) + { + return 0; + } + public static explicit operator short(ExplicitToIntOrShort s) + { + return 0; + } + } + + public class ExplicitToIntOrUShort + { + public static explicit operator int(ExplicitToIntOrUShort i) + { + return 0; + } + public static explicit operator ushort(ExplicitToIntOrUShort us) + { + return 0; + } + } + + public class ExplicitToLongOrUInt + { + public static explicit operator long(ExplicitToLongOrUInt l) + { + return 0; + } + public static explicit operator uint(ExplicitToLongOrUInt ui) + { + return 0; + } + } + + public class ExplicitToULongOrInt + { + public static explicit operator ulong(ExplicitToULongOrInt l) + { + return 0; + } + public static explicit operator int(ExplicitToULongOrInt ui) + { + return 0; + } + } + + public class ExplicitAmbiguousA + { + public static explicit operator ExplicitAmbiguousB(ExplicitAmbiguousA c) + { + return null; + } + } + + public class ExplicitAmbiguousB + { + public static explicit operator ExplicitAmbiguousB(ExplicitAmbiguousA c) + { + return null; + } + } + + public struct ExplicitFromInt + { + public static explicit operator ExplicitFromInt(int i) + { + return default(ExplicitFromInt); + } + } + + public struct ExplicitNullableConvertible + { + public static explicit operator ExplicitNullableConvertible(int i) + { + return default(ExplicitNullableConvertible); + } + public static explicit operator ExplicitNullableConvertible?(int? ni) + { + return default(ExplicitNullableConvertible); + } + } + + public class ExplicitFromNullableLongOrNullableUInt + { + public static explicit operator ExplicitFromNullableLongOrNullableUInt(long? l) + { + return new ExplicitFromNullableLongOrNullableUInt(); + } + public static explicit operator ExplicitFromNullableLongOrNullableUInt(uint? ui) + { + return new ExplicitFromNullableLongOrNullableUInt(); + } + } + + public class ExplicitToShortOrNullableByte + { + public static explicit operator short(ExplicitToShortOrNullableByte s) + { + return 0; + } + public static explicit operator byte?(ExplicitToShortOrNullableByte b) + { + return 0; + } + } + + public class ExplicitToByteOrNullableShort + { + public static explicit operator byte(ExplicitToByteOrNullableShort b) + { + return 0; + } + public static explicit operator short?(ExplicitToByteOrNullableShort s) + { + return 0; + } + } + + public struct ExplicitIntImplicitShort + { + public static explicit operator int(ExplicitIntImplicitShort ci) + { + return 0; + } + public static implicit operator short(ExplicitIntImplicitShort cs) + { + return 0; + } + } + + public class ImplicitFromInt + { + public static implicit operator ImplicitFromInt(int l) + { + return new ImplicitFromInt(); + } + } + + public class ImplicitToLong + { + public static implicit operator long(ImplicitToLong i) + { + return 0; + } + } + } + /// /// Fixture for ConversionTest.ExpansiveInheritance: a contravariant interface whose /// implementing interface expands the type argument recursively.