// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.Tests.TypeSystem; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; using NUnit.Framework; namespace ICSharpCode.Decompiler.Tests.Semantics { using C = Conversion; using dynamic = ConversionTest.Dynamic; [TestFixture, Parallelizable(ParallelScope.All)] public class ExplicitConversionsTest { CSharpConversions conversions; ICompilation compilation; [OneTimeSetUp] public void SetUp() { compilation = new SimpleCompilation(TypeSystemLoaderTests.TestAssembly, TypeSystemLoaderTests.Mscorlib, TypeSystemLoaderTests.SystemCore); conversions = new CSharpConversions(compilation); } Conversion ExplicitConversion(Type from, Type to) { IType from2 = compilation.FindType(from).AcceptVisitor(new ConversionTest.ReplaceSpecialTypesVisitor()); IType to2 = compilation.FindType(to).AcceptVisitor(new ConversionTest.ReplaceSpecialTypesVisitor()); return conversions.ExplicitConversion(from2, to2); } [Test] public void PointerConversion() { Assert.That(ExplicitConversion(typeof(int*), typeof(short)), Is.EqualTo(C.ExplicitPointerConversion)); Assert.That(ExplicitConversion(typeof(short), typeof(void*)), Is.EqualTo(C.ExplicitPointerConversion)); Assert.That(ExplicitConversion(typeof(void*), typeof(int*)), Is.EqualTo(C.ExplicitPointerConversion)); Assert.That(ExplicitConversion(typeof(long*), typeof(byte*)), Is.EqualTo(C.ExplicitPointerConversion)); } [Test] public void ConversionFromDynamic() { // Explicit dynamic conversion is for resolve results only; // otherwise it's an explicit reference / unboxing conversion Assert.That(ExplicitConversion(typeof(dynamic), typeof(string)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(dynamic), typeof(int)), Is.EqualTo(C.UnboxingConversion)); var dynamicRR = new ResolveResult(SpecialType.Dynamic); Assert.That(conversions.ExplicitConversion(dynamicRR, compilation.FindType(typeof(string))), Is.EqualTo(C.ExplicitDynamicConversion)); Assert.That(conversions.ExplicitConversion(dynamicRR, compilation.FindType(typeof(int))), Is.EqualTo(C.ExplicitDynamicConversion)); } [Test] public void NumericConversions() { Assert.That(ExplicitConversion(typeof(sbyte), typeof(uint)), Is.EqualTo(C.ExplicitNumericConversion)); Assert.That(ExplicitConversion(typeof(sbyte), typeof(char)), Is.EqualTo(C.ExplicitNumericConversion)); Assert.That(ExplicitConversion(typeof(byte), typeof(char)), Is.EqualTo(C.ExplicitNumericConversion)); Assert.That(ExplicitConversion(typeof(byte), typeof(sbyte)), Is.EqualTo(C.ExplicitNumericConversion)); // if an implicit conversion exists, ExplicitConversion() should return that Assert.That(ExplicitConversion(typeof(byte), typeof(int)), Is.EqualTo(C.ImplicitNumericConversion)); Assert.That(ExplicitConversion(typeof(double), typeof(float)), Is.EqualTo(C.ExplicitNumericConversion)); Assert.That(ExplicitConversion(typeof(double), typeof(decimal)), Is.EqualTo(C.ExplicitNumericConversion)); Assert.That(ExplicitConversion(typeof(decimal), typeof(double)), Is.EqualTo(C.ExplicitNumericConversion)); Assert.That(ExplicitConversion(typeof(int), typeof(decimal)), Is.EqualTo(C.ImplicitNumericConversion)); Assert.That(ExplicitConversion(typeof(bool), typeof(int)), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(int), typeof(bool)), Is.EqualTo(C.None)); } [Test] public void EnumerationConversions() { var explicitEnumerationConversion = C.EnumerationConversion(false, false); Assert.That(ExplicitConversion(typeof(sbyte), typeof(StringComparison)), Is.EqualTo(explicitEnumerationConversion)); Assert.That(ExplicitConversion(typeof(char), typeof(StringComparison)), Is.EqualTo(explicitEnumerationConversion)); Assert.That(ExplicitConversion(typeof(int), typeof(StringComparison)), Is.EqualTo(explicitEnumerationConversion)); Assert.That(ExplicitConversion(typeof(decimal), typeof(StringComparison)), Is.EqualTo(explicitEnumerationConversion)); Assert.That(ExplicitConversion(typeof(StringComparison), typeof(char)), Is.EqualTo(explicitEnumerationConversion)); Assert.That(ExplicitConversion(typeof(StringComparison), typeof(int)), Is.EqualTo(explicitEnumerationConversion)); Assert.That(ExplicitConversion(typeof(StringComparison), typeof(decimal)), Is.EqualTo(explicitEnumerationConversion)); Assert.That(ExplicitConversion(typeof(StringComparison), typeof(StringSplitOptions)), Is.EqualTo(explicitEnumerationConversion)); } [Test] public void NullableConversion_BasedOnIdentityConversion() { Assert.That(ExplicitConversion(typeof(ArraySegment?), typeof(ArraySegment?)), Is.EqualTo(C.IdentityConversion)); Assert.That(ExplicitConversion(typeof(ArraySegment), typeof(ArraySegment?)), Is.EqualTo(C.ImplicitNullableConversion)); Assert.That(ExplicitConversion(typeof(ArraySegment?), typeof(ArraySegment)), Is.EqualTo(C.ExplicitNullableConversion)); } [Test] public void NullableConversion_BasedOnImplicitNumericConversion() { Assert.That(ExplicitConversion(typeof(int?), typeof(long?)), Is.EqualTo(C.ImplicitLiftedNumericConversion)); Assert.That(ExplicitConversion(typeof(int), typeof(long?)), Is.EqualTo(C.ImplicitLiftedNumericConversion)); Assert.That(ExplicitConversion(typeof(int?), typeof(long)), Is.EqualTo(C.ExplicitLiftedNumericConversion)); } [Test] public void NullableConversion_BasedOnImplicitEnumerationConversion() { ResolveResult zero = new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), 0); ResolveResult one = new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), 1); Assert.That(conversions.ExplicitConversion(zero, compilation.FindType(typeof(StringComparison?))), Is.EqualTo(C.EnumerationConversion(true, true))); Assert.That(conversions.ExplicitConversion(one, compilation.FindType(typeof(StringComparison?))), Is.EqualTo(C.EnumerationConversion(false, true))); } [Test] public void NullableConversion_BasedOnExplicitNumericConversion() { Assert.That(ExplicitConversion(typeof(int?), typeof(short?)), Is.EqualTo(C.ExplicitLiftedNumericConversion)); Assert.That(ExplicitConversion(typeof(int), typeof(short?)), Is.EqualTo(C.ExplicitLiftedNumericConversion)); Assert.That(ExplicitConversion(typeof(int?), typeof(short)), Is.EqualTo(C.ExplicitLiftedNumericConversion)); } [Test] public void NullableConversion_BasedOnExplicitEnumerationConversion() { C c = C.EnumerationConversion(false, true); // c = explicit lifted enumeration conversion Assert.That(ExplicitConversion(typeof(int?), typeof(StringComparison?)), Is.EqualTo(c)); Assert.That(ExplicitConversion(typeof(int), typeof(StringComparison?)), Is.EqualTo(c)); Assert.That(ExplicitConversion(typeof(int?), typeof(StringComparison)), Is.EqualTo(c)); Assert.That(ExplicitConversion(typeof(StringComparison?), typeof(int?)), Is.EqualTo(c)); Assert.That(ExplicitConversion(typeof(StringComparison), typeof(int?)), Is.EqualTo(c)); Assert.That(ExplicitConversion(typeof(StringComparison?), typeof(int)), Is.EqualTo(c)); Assert.That(ExplicitConversion(typeof(StringComparison?), typeof(StringSplitOptions?)), Is.EqualTo(c)); Assert.That(ExplicitConversion(typeof(StringComparison), typeof(StringSplitOptions?)), Is.EqualTo(c)); Assert.That(ExplicitConversion(typeof(StringComparison?), typeof(StringSplitOptions)), Is.EqualTo(c)); } [Test] public void ExplicitReferenceConversion_SealedClass() { Assert.That(ExplicitConversion(typeof(object), typeof(string)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(string)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(string)), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(string)), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(string), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(string), typeof(IEnumerable)), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(string), typeof(IEnumerable)), Is.EqualTo(C.None)); } [Test] public void ExplicitReferenceConversion_NonSealedClass() { Assert.That(ExplicitConversion(typeof(object), typeof(List)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(List)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(List)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(List)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(List), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(List), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(List), typeof(IEnumerable)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(List), typeof(List)), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(List), typeof(List)), Is.EqualTo(C.None)); } [Test] public void ExplicitReferenceConversion_Interfaces() { Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(IEnumerable)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(IEnumerable)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(IEnumerable)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(IConvertible)), Is.EqualTo(C.ExplicitReferenceConversion)); } [Test] public void ExplicitReferenceConversion_Arrays() { Assert.That(ExplicitConversion(typeof(object[]), typeof(string[])), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(dynamic[]), typeof(string[])), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(object[]), typeof(object[,])), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(object[]), typeof(int[])), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(short[]), typeof(int[])), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(Array), typeof(int[])), Is.EqualTo(C.ExplicitReferenceConversion)); } [Test] public void ExplicitReferenceConversion_InterfaceToArray() { Assert.That(ExplicitConversion(typeof(ICloneable), typeof(int[])), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(string[])), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(string[])), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(object[])), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(dynamic[])), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(int[])), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(object[,])), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(object[])), Is.EqualTo(C.None)); } [Test] public void ExplicitReferenceConversion_ArrayToInterface() { Assert.That(ExplicitConversion(typeof(int[]), typeof(ICloneable)), Is.EqualTo(C.ImplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(string[]), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(string[]), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(object[]), typeof(IEnumerable)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(dynamic[]), typeof(IEnumerable)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(int[]), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(object[,]), typeof(IEnumerable)), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(object[]), typeof(IEnumerable)), Is.EqualTo(C.None)); } [Test] public void ExplicitReferenceConversion_Delegates() { Assert.That(ExplicitConversion(typeof(MulticastDelegate), typeof(Action)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(Delegate), typeof(Action)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(ICloneable), typeof(Action)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(System.Threading.ThreadStart), typeof(Action)), Is.EqualTo(C.None)); } [Test] public void ExplicitReferenceConversion_GenericDelegates() { Assert.That(ExplicitConversion(typeof(Action), typeof(Action)), Is.EqualTo(C.ImplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(Action), typeof(Action)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(Func), typeof(Func)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(Func), typeof(Func)), Is.EqualTo(C.ImplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(Action), typeof(Action)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(Action), typeof(Action)), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(Action), typeof(Action>)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(Func), typeof(Func)), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(ExplicitConversion(typeof(Func), typeof(Func)), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(Func), typeof(Func>)), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(Func), typeof(Func>)), Is.EqualTo(C.None)); } [Test] public void UnboxingConversion() { Assert.That(ExplicitConversion(typeof(object), typeof(int)), Is.EqualTo(C.UnboxingConversion)); Assert.That(ExplicitConversion(typeof(object), typeof(decimal)), Is.EqualTo(C.UnboxingConversion)); Assert.That(ExplicitConversion(typeof(ValueType), typeof(int)), Is.EqualTo(C.UnboxingConversion)); Assert.That(ExplicitConversion(typeof(IFormattable), typeof(int)), Is.EqualTo(C.UnboxingConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(int)), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(Enum), typeof(StringComparison)), Is.EqualTo(C.UnboxingConversion)); Assert.That(ExplicitConversion(typeof(Enum), typeof(int)), Is.EqualTo(C.None)); } [Test] public void LiftedUnboxingConversion() { Assert.That(ExplicitConversion(typeof(object), typeof(int?)), Is.EqualTo(C.UnboxingConversion)); Assert.That(ExplicitConversion(typeof(object), typeof(decimal?)), Is.EqualTo(C.UnboxingConversion)); Assert.That(ExplicitConversion(typeof(ValueType), typeof(int?)), Is.EqualTo(C.UnboxingConversion)); Assert.That(ExplicitConversion(typeof(IFormattable), typeof(int?)), Is.EqualTo(C.UnboxingConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(int?)), Is.EqualTo(C.None)); Assert.That(ExplicitConversion(typeof(Enum), typeof(StringComparison?)), Is.EqualTo(C.UnboxingConversion)); Assert.That(ExplicitConversion(typeof(Enum), typeof(int?)), Is.EqualTo(C.None)); } /// /// Converts a constant expression (e.g. an integer literal) to the target type. /// Conversion ExplicitConstantConversion(object value, Type to) { 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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(c.IsExplicit); } [Test] public void BothDirectConversionAndBaseClassConversionAvailable() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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); } [Test] public void EnumerationConversionsWithFloatingPointTypes() { // C# standard 10.3.3: explicit enumeration conversions include float and double var explicitEnumerationConversion = C.EnumerationConversion(false, false); Assert.That(ExplicitConversion(typeof(float), typeof(StringComparison)), Is.EqualTo(explicitEnumerationConversion)); Assert.That(ExplicitConversion(typeof(double), typeof(StringComparison)), Is.EqualTo(explicitEnumerationConversion)); Assert.That(ExplicitConversion(typeof(StringComparison), typeof(float)), Is.EqualTo(explicitEnumerationConversion)); Assert.That(ExplicitConversion(typeof(StringComparison), typeof(double)), Is.EqualTo(explicitEnumerationConversion)); } [Test] public void ExplicitTupleConversions() { // C# standard 10.3.6: explicit tuple conversion with explicit element conversions Assert.That(ExplicitConversion(typeof((long, object)), typeof((int, string))), Is.EqualTo(C.TupleConversion(ImmutableArray.Create(C.ExplicitNumericConversion, C.ExplicitReferenceConversion)))); // arity mismatch Assert.That(ExplicitConversion(typeof((long, object)), typeof(ValueTuple)), Is.EqualTo(C.None)); } [Test] public void UnboxingConversionViaVariance() { // C# standard 10.3.7: unboxing from an interface that is variance-convertible // to/from an interface implemented by the value type Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(StructImplementingIEnumerableOfString)), Is.EqualTo(C.UnboxingConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(StructImplementingIEnumerableOfString)), Is.EqualTo(C.UnboxingConversion)); Assert.That(ExplicitConversion(typeof(IEnumerable), typeof(StructImplementingIEnumerableOfString)), Is.EqualTo(C.None)); } [Test, Ignore("Known bug: CSharpConversions does not support nullable conversions derived from tuple conversions, but csc accepts them")] public void ExplicitLiftedTupleConversions() { // csc accepts the explicit nullable conversions derived from tuple conversions: // (int, string)? tn = ...; // (long, object) c = ((long, object))tn; // (int, string)? d = ((int, string)?)lo; with lo of type (long, object)? Assert.That(ExplicitConversion(typeof((int, string)?), typeof((long, object))).IsValid, "(int, string)? -> (long, object)"); Assert.That(ExplicitConversion(typeof((long, object)?), typeof((int, string)?)).IsValid, "(long, object)? -> (int, string)?"); } [Test, Ignore("Known bug: CSharpConversions rejects explicit user-defined conversions to a nullable target when the operator result needs an explicit numeric conversion, but csc accepts them")] public void UserDefinedExplicitConversion_ExplicitNumericConversionToNullableTarget() { // csc accepts: // int? d = (int?)new ImplicitToLong(); -- UD op to long, then explicit long -> int? // int? e = (int?)new ExplicitToLongOrUInt(); -- UD ops to long/uint, then explicit -> int? var c = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ImplicitToLong), typeof(int?)); Assert.That(c.IsValid, "(int?)ImplicitToLong"); var c2 = ExplicitConversion(typeof(UserDefinedExplicitConversionTestCases.ExplicitToLongOrUInt), typeof(int?)); Assert.That(c2.IsValid, "(int?)ExplicitToLongOrUInt"); } [Test] public void ExplicitTypeParameterConversionFromEffectiveBaseClass() { // C# standard 10.3.8: for T known to be a reference type, explicit reference // conversions exist from the effective base class (and its base classes) to T, // and from T to interfaces it does not implement ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T", constraints: new[] { compilation.FindType(typeof(StringComparer)) }); Assert.That(conversions.ExplicitConversion(compilation.FindType(typeof(StringComparer)), t), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(conversions.ExplicitConversion(compilation.FindType(KnownTypeCode.Object), t), Is.EqualTo(C.ExplicitReferenceConversion)); Assert.That(conversions.ExplicitConversion(t, compilation.FindType(typeof(IConvertible))), Is.EqualTo(C.ExplicitReferenceConversion)); } #region First-class span conversions Conversion SpanExplicitConversion(Type from, Type to) { var c = RefAssemblyCompilation.Instance; return CSharpConversions.Get(c).ExplicitConversion(c.FindType(from), c.FindType(to)); } [Test] public void ExplicitSpanConversion_CovariantArrayToSpan() { // C# 14 first-class spans: an explicit span conversion exists from an array to // Span/ReadOnlySpan when an explicit reference conversion relates the element // types, and user-defined operators are not considered for such pairs. Roslyn // compiles '(Span)objectArray', and reports CS0266 (explicit conversion // exists) for 'Span s = stringArray;'. var downcast = SpanExplicitConversion(typeof(object[]), typeof(Span)); Assert.That(downcast.IsValid); Assert.That(!downcast.IsUserDefined); var downcastRos = SpanExplicitConversion(typeof(object[]), typeof(ReadOnlySpan)); Assert.That(downcastRos.IsValid); Assert.That(!downcastRos.IsUserDefined); var upcast = SpanExplicitConversion(typeof(string[]), typeof(Span)); Assert.That(upcast.IsValid); Assert.That(!upcast.IsUserDefined); } [Test] public void NoExplicitSpanConversionWithoutElementReferenceConversion() { // Roslyn: CS0030 - int[] and Span/ReadOnlySpan are unrelated; the // user-defined operator route (op_Implicit(long[])) must not resurrect the cast. Assert.That(!SpanExplicitConversion(typeof(int[]), typeof(Span)).IsValid); Assert.That(!SpanExplicitConversion(typeof(int[]), typeof(ReadOnlySpan)).IsValid); } #endregion } }