// 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;
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
{
// assign short names to the fake reflection types
using C = Conversion;
using dynamic = ConversionTest.Dynamic;
using nint = ConversionTest.NInt;
using nuint = ConversionTest.NUInt;
[TestFixture, Parallelizable(ParallelScope.All)]
public unsafe class ConversionTest
{
///
/// A reflection class used to represent null .
///
public sealed class Null { }
///
/// A reflection class used to represent dynamic .
///
public sealed class Dynamic { }
///
/// A reflection class used to represent nint .
///
public sealed class NInt { }
///
/// A reflection class used to represent nuint .
///
public sealed class NUInt { }
CSharpConversions conversions;
ICompilation compilation;
[OneTimeSetUp]
public void SetUp()
{
compilation = new SimpleCompilation(TypeSystemLoaderTests.TestAssembly,
TypeSystemLoaderTests.Mscorlib,
TypeSystemLoaderTests.SystemCore);
conversions = new CSharpConversions(compilation);
}
public class ReplaceSpecialTypesVisitor : TypeVisitor
{
public override IType VisitTypeDefinition(ITypeDefinition type)
{
switch (type.FullName)
{
case "ICSharpCode.Decompiler.Tests.Semantics.ConversionTest.Dynamic":
return SpecialType.Dynamic;
case "ICSharpCode.Decompiler.Tests.Semantics.ConversionTest.Null":
return SpecialType.NullType;
case "ICSharpCode.Decompiler.Tests.Semantics.ConversionTest.NInt":
return SpecialType.NInt;
case "ICSharpCode.Decompiler.Tests.Semantics.ConversionTest.NUInt":
return SpecialType.NUInt;
default:
return base.VisitTypeDefinition(type);
}
}
}
Conversion ImplicitConversion(Type from, Type to)
{
IType from2 = compilation.FindType(from).AcceptVisitor(new ReplaceSpecialTypesVisitor());
IType to2 = compilation.FindType(to).AcceptVisitor(new ReplaceSpecialTypesVisitor());
return conversions.ImplicitConversion(from2, to2);
}
Conversion ExplicitConversion(Type from, Type to)
{
IType from2 = compilation.FindType(from).AcceptVisitor(new ReplaceSpecialTypesVisitor());
IType to2 = compilation.FindType(to).AcceptVisitor(new ReplaceSpecialTypesVisitor());
return conversions.ExplicitConversion(from2, to2);
}
///
/// Converts a constant expression (e.g. an integer literal) to the target type.
///
Conversion ConstantConversion(object value, Type to)
{
IType fromType = compilation.FindType(value.GetType());
IType to2 = compilation.FindType(to).AcceptVisitor(new ReplaceSpecialTypesVisitor());
return conversions.ImplicitConversion(new ConstantResolveResult(fromType, value), to2);
}
///
/// Builds a MethodGroupResolveResult from all methods named
/// in (as if the expression were "instance.M") and
/// converts it to .
///
Conversion MethodGroupConversion(Type declaringType, string methodName, Type delegateType,
ResolveResult targetResult = null, IMethod[] extensionMethods = null, IReadOnlyList typeArguments = null)
{
IType declaring = compilation.FindType(declaringType);
var mgrr = new MethodGroupResolveResult(
targetResult ?? new ResolveResult(declaring), methodName,
new[] { new MethodListWithDeclaringType(declaring, declaring.GetMethods(m => m.Name == methodName)) },
typeArguments);
if (extensionMethods != null)
{
mgrr.extensionMethods = new List> { new List(extensionMethods) };
}
IType dt = compilation.FindType(delegateType).AcceptVisitor(new ReplaceSpecialTypesVisitor());
return conversions.ImplicitConversion(mgrr, dt);
}
[Test]
public void IdentityConversions()
{
Assert.That(ImplicitConversion(typeof(char), typeof(char)), Is.EqualTo(C.IdentityConversion));
Assert.That(ImplicitConversion(typeof(string), typeof(string)), Is.EqualTo(C.IdentityConversion));
Assert.That(ImplicitConversion(typeof(object), typeof(object)), Is.EqualTo(C.IdentityConversion));
Assert.That(ImplicitConversion(typeof(bool), typeof(char)), Is.EqualTo(C.None));
Assert.That(conversions.ImplicitConversion(SpecialType.Dynamic, SpecialType.Dynamic), Is.EqualTo(C.IdentityConversion));
Assert.That(conversions.ImplicitConversion(SpecialType.UnknownType, SpecialType.UnknownType), Is.EqualTo(C.IdentityConversion));
Assert.That(conversions.ImplicitConversion(SpecialType.NullType, SpecialType.NullType), Is.EqualTo(C.IdentityConversion));
}
[Test]
public void DynamicIdentityConversions()
{
Assert.That(ImplicitConversion(typeof(object), typeof(dynamic)), Is.EqualTo(C.IdentityConversion));
Assert.That(ImplicitConversion(typeof(dynamic), typeof(object)), Is.EqualTo(C.IdentityConversion));
}
[Test]
public void ComplexDynamicIdentityConversions()
{
Assert.That(ImplicitConversion(typeof(List), typeof(List)), Is.EqualTo(C.IdentityConversion));
Assert.That(ImplicitConversion(typeof(List), typeof(List)), Is.EqualTo(C.IdentityConversion));
Assert.That(ImplicitConversion(typeof(List), typeof(List)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(List), typeof(List)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(List[]>), typeof(List[]>)), Is.EqualTo(C.IdentityConversion));
Assert.That(ImplicitConversion(typeof(List[]>), typeof(List[]>)), Is.EqualTo(C.IdentityConversion));
Assert.That(ImplicitConversion(typeof(List[,]>), typeof(List[]>)), Is.EqualTo(C.None));
}
[Test]
public void TupleIdentityConversions()
{
var intType = compilation.FindType(typeof(int));
var stringType = compilation.FindType(typeof(string));
Assert.That(conversions.ImplicitConversion(
new TupleType(compilation, ImmutableArray.Create(intType, stringType), ImmutableArray.Create("a", "b")),
new TupleType(compilation, ImmutableArray.Create(intType, stringType), ImmutableArray.Create("a", "c"))), Is.EqualTo(C.IdentityConversion));
Assert.That(conversions.ImplicitConversion(
new TupleType(compilation, ImmutableArray.Create(intType, stringType), ImmutableArray.Create("a", "b")),
new TupleType(compilation, ImmutableArray.Create(stringType, intType), ImmutableArray.Create("a", "b"))), Is.EqualTo(C.None));
}
[Test]
public void TupleConversions()
{
Assert.That(
ImplicitConversion(typeof((int, string)), typeof((long, object))), Is.EqualTo(C.TupleConversion(ImmutableArray.Create(C.ImplicitNumericConversion, C.ImplicitReferenceConversion))));
Assert.That(
ImplicitConversion(typeof(ValueTuple), typeof(ValueTuple)), Is.EqualTo(C.TupleConversion(ImmutableArray.Create(C.ImplicitNumericConversion))));
}
[Test]
public void ExplicitTupleConversionClassifiesElementsAsCasts()
{
// Every element of this tuple also converts implicitly, so an implicit tuple conversion
// exists...
var implicitConversion = ImplicitConversion(typeof((DateTime, int)), typeof((DateTimeOffset, long)));
Assert.That(implicitConversion.IsTupleConversion);
Assert.That(implicitConversion.IsImplicit);
Assert.That(implicitConversion.ElementConversions[0].IsUserDefined);
Assert.That(implicitConversion.ElementConversions[0].IsImplicit);
// ...but a cast must not use it: Roslyn's ClassifyConversionFromTypeForCast discards an
// ImplicitTuple result (ExplicitConversionMayDifferFromImplicit lists that kind) and
// re-classifies the elements as cast conversions, so the DateTime element becomes a
// user-defined explicit conversion.
var explicitConversion = ExplicitConversion(typeof((DateTime, int)), typeof((DateTimeOffset, long)));
Assert.That(explicitConversion.IsTupleConversion);
Assert.That(explicitConversion.IsExplicit);
Assert.That(explicitConversion.ElementConversions[0].IsUserDefined);
Assert.That(explicitConversion.ElementConversions[0].IsExplicit);
Assert.That(explicitConversion.ElementConversions[1], Is.EqualTo(C.ImplicitNumericConversion));
}
[Test]
public void PrimitiveConversions()
{
Assert.That(ImplicitConversion(typeof(char), typeof(ushort)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ImplicitConversion(typeof(byte), typeof(char)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(int), typeof(long)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ImplicitConversion(typeof(long), typeof(int)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(int), typeof(float)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ImplicitConversion(typeof(bool), typeof(float)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(float), typeof(double)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ImplicitConversion(typeof(float), typeof(decimal)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(char), typeof(long)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ImplicitConversion(typeof(uint), typeof(long)), Is.EqualTo(C.ImplicitNumericConversion));
}
[Test]
public void EnumerationConversion()
{
ResolveResult zero = new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), 0);
ResolveResult one = new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), 1);
C implicitEnumerationConversion = C.EnumerationConversion(true, false);
Assert.That(conversions.ImplicitConversion(zero, compilation.FindType(typeof(StringComparison))), Is.EqualTo(implicitEnumerationConversion));
Assert.That(conversions.ImplicitConversion(one, compilation.FindType(typeof(StringComparison))), Is.EqualTo(C.None));
}
[Test]
public void NullableConversions()
{
Assert.That(ImplicitConversion(typeof(char), typeof(ushort?)), Is.EqualTo(C.ImplicitLiftedNumericConversion));
Assert.That(ImplicitConversion(typeof(byte), typeof(char?)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(int), typeof(long?)), Is.EqualTo(C.ImplicitLiftedNumericConversion));
Assert.That(ImplicitConversion(typeof(long), typeof(int?)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(int), typeof(float?)), Is.EqualTo(C.ImplicitLiftedNumericConversion));
Assert.That(ImplicitConversion(typeof(bool), typeof(float?)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(float), typeof(double?)), Is.EqualTo(C.ImplicitLiftedNumericConversion));
Assert.That(ImplicitConversion(typeof(float), typeof(decimal?)), Is.EqualTo(C.None));
}
[Test]
public void NullableConversions2()
{
Assert.That(ImplicitConversion(typeof(char?), typeof(ushort?)), Is.EqualTo(C.ImplicitLiftedNumericConversion));
Assert.That(ImplicitConversion(typeof(byte?), typeof(char?)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(int?), typeof(long?)), Is.EqualTo(C.ImplicitLiftedNumericConversion));
Assert.That(ImplicitConversion(typeof(long?), typeof(int?)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(int?), typeof(float?)), Is.EqualTo(C.ImplicitLiftedNumericConversion));
Assert.That(ImplicitConversion(typeof(bool?), typeof(float?)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(float?), typeof(double?)), Is.EqualTo(C.ImplicitLiftedNumericConversion));
Assert.That(ImplicitConversion(typeof(float?), typeof(decimal?)), Is.EqualTo(C.None));
}
[Test]
public void NullableEnumerationConversion()
{
ResolveResult zero = new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), 0);
ResolveResult one = new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), 1);
C implicitEnumerationConversion = C.EnumerationConversion(true, true);
Assert.That(conversions.ImplicitConversion(zero, compilation.FindType(typeof(StringComparison?))), Is.EqualTo(implicitEnumerationConversion));
Assert.That(conversions.ImplicitConversion(one, compilation.FindType(typeof(StringComparison?))), Is.EqualTo(C.None));
}
[Test]
public void NullLiteralConversions()
{
Assert.That(ImplicitConversion(typeof(Null), typeof(int?)), Is.EqualTo(C.NullLiteralConversion));
Assert.That(ImplicitConversion(typeof(Null), typeof(char?)), Is.EqualTo(C.NullLiteralConversion));
Assert.That(ImplicitConversion(typeof(Null), typeof(int)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(Null), typeof(object)), Is.EqualTo(C.NullLiteralConversion));
Assert.That(ImplicitConversion(typeof(Null), typeof(dynamic)), Is.EqualTo(C.NullLiteralConversion));
Assert.That(ImplicitConversion(typeof(Null), typeof(string)), Is.EqualTo(C.NullLiteralConversion));
Assert.That(ImplicitConversion(typeof(Null), typeof(int[])), Is.EqualTo(C.NullLiteralConversion));
}
[Test]
public void SimpleReferenceConversions()
{
Assert.That(ImplicitConversion(typeof(string), typeof(object)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(BitArray), typeof(ICollection)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(IList), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(object), typeof(string)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(ICollection), typeof(BitArray)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(IEnumerable), typeof(IList)), Is.EqualTo(C.None));
}
[Test]
public void ConversionToDynamic()
{
Assert.That(ImplicitConversion(typeof(string), typeof(dynamic)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(int), typeof(dynamic)), Is.EqualTo(C.BoxingConversion));
}
[Test]
public void ConversionFromDynamic()
{
// There is no conversion from the type 'dynamic' to other types (except the identity conversion to object).
// Such conversions only exists from dynamic expression.
// This is an important distinction for type inference (see TypeInferenceTests.IEnumerableCovarianceWithDynamic)
Assert.That(ImplicitConversion(typeof(dynamic), typeof(string)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(dynamic), typeof(int)), Is.EqualTo(C.None));
var dynamicRR = new ResolveResult(SpecialType.Dynamic);
Assert.That(conversions.ImplicitConversion(dynamicRR, compilation.FindType(typeof(string))), Is.EqualTo(C.ImplicitDynamicConversion));
Assert.That(conversions.ImplicitConversion(dynamicRR, compilation.FindType(typeof(int))), Is.EqualTo(C.ImplicitDynamicConversion));
}
[Test]
public void ParameterizedTypeConversions()
{
Assert.That(ImplicitConversion(typeof(List), typeof(ICollection)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(IList), typeof(ICollection)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(List), typeof(ICollection)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(IList), typeof(ICollection)), Is.EqualTo(C.None));
}
[Test]
public void ArrayConversions()
{
Assert.That(ImplicitConversion(typeof(string[]), typeof(object[])), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(string[,]), typeof(object[,])), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(string[]), typeof(object[,])), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(object[]), typeof(string[])), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(string[]), typeof(IList)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(string[,]), typeof(IList)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(string[]), typeof(IList)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(string[]), typeof(Array)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(string[]), typeof(ICloneable)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(Array), typeof(string[])), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(object), typeof(object[])), Is.EqualTo(C.None));
}
[Test]
public void VarianceConversions()
{
Assert.That(ImplicitConversion(typeof(List), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(List), typeof(IEnumerable)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(IEnumerable), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(ICollection), typeof(ICollection)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(Comparer), typeof(IComparer)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(Comparer), typeof(IComparer)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(Comparer), typeof(Comparer)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(List), typeof(IEnumerable)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(IEnumerable), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(Func), typeof(Func)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(Func), typeof(Func)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(Func), typeof(Func)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(Func), typeof(Func)), Is.EqualTo(C.None));
}
[Test]
public void ImplicitPointerConversion()
{
Assert.That(ImplicitConversion(typeof(Null), typeof(int*)), Is.EqualTo(C.ImplicitPointerConversion));
Assert.That(ImplicitConversion(typeof(int*), typeof(void*)), Is.EqualTo(C.ImplicitPointerConversion));
}
[Test]
public void NoConversionFromPointerTypeToObject()
{
Assert.That(ImplicitConversion(typeof(int*), typeof(object)), Is.EqualTo(C.None));
Assert.That(ImplicitConversion(typeof(int*), typeof(dynamic)), Is.EqualTo(C.None));
}
[Test]
public void ConversionToNInt()
{
// Test based on the table in https://github.com/dotnet/csharplang/blob/master/proposals/native-integers.md
Assert.That(ExplicitConversion(typeof(object), typeof(nint)), Is.EqualTo(C.UnboxingConversion));
Assert.That(ExplicitConversion(typeof(void*), typeof(nint)), Is.EqualTo(C.ExplicitPointerConversion));
Assert.That(ExplicitConversion(typeof(sbyte), typeof(nint)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(byte), typeof(nint)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(short), typeof(nint)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(ushort), typeof(nint)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(int), typeof(nint)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(uint), typeof(nint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(long), typeof(nint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(ulong), typeof(nint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(char), typeof(nint)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(float), typeof(nint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(double), typeof(nint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(decimal), typeof(nint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(IntPtr), typeof(nint)), Is.EqualTo(C.IdentityConversion));
Assert.That(ExplicitConversion(typeof(UIntPtr), typeof(nint)), Is.EqualTo(C.None));
}
[Test]
public void ConversionToNUInt()
{
// Test based on the table in https://github.com/dotnet/csharplang/blob/master/proposals/native-integers.md
Assert.That(ExplicitConversion(typeof(object), typeof(nuint)), Is.EqualTo(C.UnboxingConversion));
Assert.That(ExplicitConversion(typeof(void*), typeof(nuint)), Is.EqualTo(C.ExplicitPointerConversion));
Assert.That(ExplicitConversion(typeof(sbyte), typeof(nuint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(byte), typeof(nuint)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(short), typeof(nuint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(ushort), typeof(nuint)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(int), typeof(nuint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(uint), typeof(nuint)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(long), typeof(nuint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(ulong), typeof(nuint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(char), typeof(nuint)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(float), typeof(nuint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(double), typeof(nuint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(decimal), typeof(nuint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(IntPtr), typeof(nuint)), Is.EqualTo(C.None));
Assert.That(ExplicitConversion(typeof(UIntPtr), typeof(nuint)), Is.EqualTo(C.IdentityConversion));
}
[Test]
public void ConversionFromNInt()
{
// Test based on the table in https://github.com/dotnet/csharplang/blob/master/proposals/native-integers.md
Assert.That(ExplicitConversion(typeof(nint), typeof(object)), Is.EqualTo(C.BoxingConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(void*)), Is.EqualTo(C.ExplicitPointerConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(nuint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(sbyte)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(byte)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(short)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(ushort)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(int)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(uint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(long)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(ulong)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(char)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(float)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(double)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(decimal)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(IntPtr)), Is.EqualTo(C.IdentityConversion));
Assert.That(ExplicitConversion(typeof(nint), typeof(UIntPtr)), Is.EqualTo(C.None));
}
[Test]
public void ConversionFromNUInt()
{
// Test based on the table in https://github.com/dotnet/csharplang/blob/master/proposals/native-integers.md
Assert.That(ExplicitConversion(typeof(nuint), typeof(object)), Is.EqualTo(C.BoxingConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(void*)), Is.EqualTo(C.ExplicitPointerConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(nint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(sbyte)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(byte)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(short)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(ushort)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(int)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(uint)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(long)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(ulong)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(char)), Is.EqualTo(C.ExplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(float)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(double)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(decimal)), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(IntPtr)), Is.EqualTo(C.None));
Assert.That(ExplicitConversion(typeof(nuint), typeof(UIntPtr)), Is.EqualTo(C.IdentityConversion));
}
[Test]
public void NIntEnumConversion()
{
var explicitEnumConversion = C.EnumerationConversion(isImplicit: false, isLifted: false);
Assert.That(ExplicitConversion(typeof(nint), typeof(StringComparison)), Is.EqualTo(explicitEnumConversion));
Assert.That(ExplicitConversion(typeof(nuint), typeof(StringComparison)), Is.EqualTo(explicitEnumConversion));
Assert.That(ExplicitConversion(typeof(StringComparison), typeof(nint)), Is.EqualTo(explicitEnumConversion));
Assert.That(ExplicitConversion(typeof(StringComparison), typeof(nuint)), Is.EqualTo(explicitEnumConversion));
}
[Test]
public void IntegerLiteralToNIntConversions()
{
Assert.That(IntegerLiteralConversion(0, typeof(nint)));
Assert.That(IntegerLiteralConversion(-1, typeof(nint)));
Assert.That(!IntegerLiteralConversion(uint.MaxValue, typeof(nint)));
Assert.That(!IntegerLiteralConversion(long.MaxValue, typeof(nint)));
}
[Test]
public void IntegerLiteralToNUIntConversions()
{
Assert.That(IntegerLiteralConversion(0, typeof(nuint)));
Assert.That(!IntegerLiteralConversion(-1, typeof(nuint)));
Assert.That(IntegerLiteralConversion(uint.MaxValue, typeof(nuint)));
Assert.That(!IntegerLiteralConversion(long.MaxValue, typeof(nuint)));
}
[Test]
public void UnconstrainedTypeParameter()
{
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.TypeDefinition, 0, "T");
ITypeParameter t2 = new DefaultTypeParameter(compilation, SymbolKind.TypeDefinition, 1, "T2");
ITypeParameter tm = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "TM");
Assert.That(conversions.ImplicitConversion(SpecialType.NullType, t), Is.EqualTo(C.None));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(KnownTypeCode.Object)), Is.EqualTo(C.BoxingConversion));
Assert.That(conversions.ImplicitConversion(t, SpecialType.Dynamic), Is.EqualTo(C.BoxingConversion));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(typeof(ValueType))), Is.EqualTo(C.None));
Assert.That(conversions.ImplicitConversion(t, t), Is.EqualTo(C.IdentityConversion));
Assert.That(conversions.ImplicitConversion(t2, t), Is.EqualTo(C.None));
Assert.That(conversions.ImplicitConversion(t, t2), Is.EqualTo(C.None));
Assert.That(conversions.ImplicitConversion(t, tm), Is.EqualTo(C.None));
Assert.That(conversions.ImplicitConversion(tm, t), Is.EqualTo(C.None));
}
[Test]
public void TypeParameterWithReferenceTypeConstraint()
{
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.TypeDefinition, 0, "T", hasReferenceTypeConstraint: true);
Assert.That(conversions.ImplicitConversion(SpecialType.NullType, t), Is.EqualTo(C.NullLiteralConversion));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(KnownTypeCode.Object)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(conversions.ImplicitConversion(t, SpecialType.Dynamic), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(typeof(ValueType))), Is.EqualTo(C.None));
}
[Test]
public void TypeParameterWithValueTypeConstraint()
{
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.TypeDefinition, 0, "T", hasValueTypeConstraint: true);
Assert.That(conversions.ImplicitConversion(SpecialType.NullType, t), Is.EqualTo(C.None));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(KnownTypeCode.Object)), Is.EqualTo(C.BoxingConversion));
Assert.That(conversions.ImplicitConversion(t, SpecialType.Dynamic), Is.EqualTo(C.BoxingConversion));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(typeof(ValueType))), Is.EqualTo(C.BoxingConversion));
}
[Test]
public void TypeParameterWithClassConstraint()
{
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.TypeDefinition, 0, "T",
constraints: new[] { compilation.FindType(typeof(StringComparer)) });
Assert.That(conversions.ImplicitConversion(SpecialType.NullType, t), Is.EqualTo(C.NullLiteralConversion));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(KnownTypeCode.Object)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(conversions.ImplicitConversion(t, SpecialType.Dynamic), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(typeof(ValueType))), Is.EqualTo(C.None));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(typeof(StringComparer))), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(typeof(IComparer))), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(typeof(IComparer))), Is.EqualTo(C.None));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(typeof(IComparer))), Is.EqualTo(C.ImplicitReferenceConversion));
}
[Test]
public void TypeParameterWithInterfaceConstraint()
{
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.TypeDefinition, 0, "T",
constraints: new[] { compilation.FindType(typeof(IList)) });
Assert.That(conversions.ImplicitConversion(SpecialType.NullType, t), Is.EqualTo(C.None));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(KnownTypeCode.Object)), Is.EqualTo(C.BoxingConversion));
Assert.That(conversions.ImplicitConversion(t, SpecialType.Dynamic), Is.EqualTo(C.BoxingConversion));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(typeof(ValueType))), Is.EqualTo(C.None));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(typeof(IList))), Is.EqualTo(C.BoxingConversion));
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(typeof(IEnumerable))), Is.EqualTo(C.BoxingConversion));
}
[Test]
public void UserDefinedImplicitConversion()
{
Conversion c = ImplicitConversion(typeof(DateTime), typeof(DateTimeOffset));
Assert.That(c.IsImplicit && c.IsUserDefined);
Assert.That(c.Method.FullName, Is.EqualTo("System.DateTimeOffset.op_Implicit"));
Assert.That(ImplicitConversion(typeof(DateTimeOffset), typeof(DateTime)), Is.EqualTo(C.None));
ITypeDefinition classImplementingIDisposable = compilation.FindType(typeof(ClassImplementingIDisposable)).GetDefinition();
ITypeDefinition genericStructWithIDisposableConstraintAndImplicitConversion = compilation.FindType(typeof(GenericStructWithIDisposableConstraintAndImplicitConversion<>)).GetDefinition();
IType genericStructIDisposableInstance = new ParameterizedType(genericStructWithIDisposableConstraintAndImplicitConversion, ImmutableArray.Create(compilation.FindType(typeof(IDisposable))));
// C => S
Conversion c2 = conversions.ImplicitConversion(classImplementingIDisposable, genericStructIDisposableInstance);
Assert.That(c2.IsImplicit && c2.IsUserDefined);
Assert.That(c2.Method.FullName, Is.EqualTo("ICSharpCode.Decompiler.Tests.TypeSystem.GenericStructWithIDisposableConstraintAndImplicitConversion.op_Implicit"));
Assert.That(conversions.ImplicitConversion(genericStructIDisposableInstance, classImplementingIDisposable), Is.EqualTo(C.None));
}
[Test]
public void UserDefinedImplicitNullableConversion()
{
// User-defined conversion followed by nullable conversion
Conversion c = ImplicitConversion(typeof(DateTime), typeof(DateTimeOffset?));
Assert.That(c.IsValid && c.IsUserDefined);
Assert.That(!c.IsLifted);
// Lifted user-defined conversion
c = ImplicitConversion(typeof(DateTime?), typeof(DateTimeOffset?));
Assert.That(c.IsValid && c.IsUserDefined && c.IsLifted);
// User-defined conversion doesn't drop the nullability
c = ImplicitConversion(typeof(DateTime?), typeof(DateTimeOffset));
Assert.That(!c.IsValid);
}
bool IntegerLiteralConversion(object value, Type to)
{
IType fromType = compilation.FindType(value.GetType()).AcceptVisitor(new ReplaceSpecialTypesVisitor());
ConstantResolveResult crr = new ConstantResolveResult(fromType, value);
IType to2 = compilation.FindType(to).AcceptVisitor(new ReplaceSpecialTypesVisitor());
return conversions.ImplicitConversion(crr, to2).IsValid;
}
[Test]
public void IntegerLiteralToEnumConversions()
{
Assert.That(IntegerLiteralConversion(0, typeof(LoaderOptimization)));
Assert.That(IntegerLiteralConversion(0L, typeof(LoaderOptimization)));
Assert.That(IntegerLiteralConversion(0, typeof(LoaderOptimization?)));
Assert.That(!IntegerLiteralConversion(0, typeof(string)));
Assert.That(!IntegerLiteralConversion(1, typeof(LoaderOptimization)));
}
[Test]
public void ImplicitConstantExpressionConversion()
{
Assert.That(IntegerLiteralConversion(0, typeof(int)));
Assert.That(IntegerLiteralConversion(0, typeof(ushort)));
Assert.That(IntegerLiteralConversion(0, typeof(sbyte)));
Assert.That(IntegerLiteralConversion(-1, typeof(int)));
Assert.That(!IntegerLiteralConversion(-1, typeof(ushort)));
Assert.That(IntegerLiteralConversion(-1, typeof(sbyte)));
Assert.That(IntegerLiteralConversion(200, typeof(int)));
Assert.That(IntegerLiteralConversion(200, typeof(ushort)));
Assert.That(!IntegerLiteralConversion(200, typeof(sbyte)));
}
[Test]
public void ImplicitLongConstantExpressionConversion()
{
Assert.That(!IntegerLiteralConversion(0L, typeof(int)));
Assert.That(!IntegerLiteralConversion(0L, typeof(short)));
Assert.That(IntegerLiteralConversion(0L, typeof(long)));
Assert.That(IntegerLiteralConversion(0L, typeof(ulong)));
Assert.That(IntegerLiteralConversion(-1L, typeof(long)));
Assert.That(!IntegerLiteralConversion(-1L, typeof(ulong)));
}
[Test]
public void ImplicitConstantExpressionConversionToNullable()
{
Assert.That(IntegerLiteralConversion(0, typeof(uint?)));
Assert.That(IntegerLiteralConversion(0, typeof(short?)));
Assert.That(IntegerLiteralConversion(0, typeof(byte?)));
Assert.That(!IntegerLiteralConversion(-1, typeof(uint?)));
Assert.That(IntegerLiteralConversion(-1, typeof(short?)));
Assert.That(!IntegerLiteralConversion(-1, typeof(byte?)));
Assert.That(IntegerLiteralConversion(200, typeof(uint?)));
Assert.That(IntegerLiteralConversion(200, typeof(short?)));
Assert.That(IntegerLiteralConversion(200, typeof(byte?)));
Assert.That(!IntegerLiteralConversion(0L, typeof(uint?)));
Assert.That(IntegerLiteralConversion(0L, typeof(long?)));
Assert.That(IntegerLiteralConversion(0L, typeof(ulong?)));
Assert.That(IntegerLiteralConversion(-1L, typeof(long?)));
Assert.That(!IntegerLiteralConversion(-1L, typeof(ulong?)));
}
[Test]
public void ImplicitConstantExpressionConversionNumberInterfaces()
{
Assert.That(IntegerLiteralConversion(0, typeof(IFormattable)));
Assert.That(IntegerLiteralConversion(0, typeof(IComparable)));
Assert.That(!IntegerLiteralConversion(0, typeof(IComparable)));
Assert.That(!IntegerLiteralConversion(0, typeof(IComparable)));
}
int BetterConversion(Type s, Type t1, Type t2)
{
IType sType = compilation.FindType(s).AcceptVisitor(new ReplaceSpecialTypesVisitor());
IType t1Type = compilation.FindType(t1).AcceptVisitor(new ReplaceSpecialTypesVisitor());
IType t2Type = compilation.FindType(t2).AcceptVisitor(new ReplaceSpecialTypesVisitor());
return conversions.BetterConversion(sType, t1Type, t2Type);
}
int BetterConversion(object value, Type t1, Type t2)
{
IType fromType = compilation.FindType(value.GetType()).AcceptVisitor(new ReplaceSpecialTypesVisitor());
ConstantResolveResult crr = new ConstantResolveResult(fromType, value);
IType t1Type = compilation.FindType(t1).AcceptVisitor(new ReplaceSpecialTypesVisitor());
IType t2Type = compilation.FindType(t2).AcceptVisitor(new ReplaceSpecialTypesVisitor());
return conversions.BetterConversion(crr, t1Type, t2Type);
}
[Test]
public void BetterConversion()
{
Assert.That(BetterConversion(typeof(string), typeof(string), typeof(object)), Is.EqualTo(1));
Assert.That(BetterConversion(typeof(string), typeof(object), typeof(IComparable)), Is.EqualTo(2));
Assert.That(BetterConversion(typeof(string), typeof(IEnumerable), typeof(IComparable)), Is.EqualTo(0));
}
[Test]
public void BetterPrimitiveConversion()
{
Assert.That(BetterConversion(typeof(short), typeof(int), typeof(long)), Is.EqualTo(1));
Assert.That(BetterConversion(typeof(short), typeof(int), typeof(uint)), Is.EqualTo(1));
Assert.That(BetterConversion(typeof(ushort), typeof(uint), typeof(int)), Is.EqualTo(2));
Assert.That(BetterConversion(typeof(char), typeof(short), typeof(int)), Is.EqualTo(1));
Assert.That(BetterConversion(typeof(char), typeof(ushort), typeof(int)), Is.EqualTo(1));
Assert.That(BetterConversion(typeof(sbyte), typeof(long), typeof(ulong)), Is.EqualTo(1));
Assert.That(BetterConversion(typeof(byte), typeof(ushort), typeof(short)), Is.EqualTo(2));
Assert.That(BetterConversion(1, typeof(sbyte), typeof(byte)), Is.EqualTo(1));
Assert.That(BetterConversion(1, typeof(ushort), typeof(sbyte)), Is.EqualTo(2));
}
[Test]
public void BetterNullableConversion()
{
Assert.That(BetterConversion(typeof(byte), typeof(int), typeof(uint?)), Is.EqualTo(0));
Assert.That(BetterConversion(typeof(byte?), typeof(int?), typeof(uint?)), Is.EqualTo(0));
Assert.That(BetterConversion(typeof(byte), typeof(ushort?), typeof(uint?)), Is.EqualTo(1));
Assert.That(BetterConversion(typeof(byte?), typeof(ulong?), typeof(uint?)), Is.EqualTo(2));
Assert.That(BetterConversion(typeof(byte), typeof(ushort?), typeof(uint)), Is.EqualTo(0));
Assert.That(BetterConversion(typeof(byte), typeof(ushort?), typeof(int)), Is.EqualTo(0));
Assert.That(BetterConversion(typeof(byte), typeof(ulong?), typeof(uint)), Is.EqualTo(2));
Assert.That(BetterConversion(typeof(byte), typeof(ulong?), typeof(int)), Is.EqualTo(0));
Assert.That(BetterConversion(typeof(ushort?), typeof(long?), typeof(int?)), Is.EqualTo(2));
Assert.That(BetterConversion(typeof(sbyte), typeof(int?), typeof(uint?)), Is.EqualTo(0));
}
[Test]
public void ExpansiveInheritance()
{
// interface A { }
// interface B : A>> { }
// Finding a conversion B -> A> must terminate even though
// the base-type substitution keeps producing ever-larger types.
ITypeDefinition a = compilation.FindType(typeof(ExpansiveInheritanceTestCases.A<>)).GetDefinition();
ITypeDefinition b = compilation.FindType(typeof(ExpansiveInheritanceTestCases.B<>)).GetDefinition();
IType type1 = new ParameterizedType(b, ImmutableArray.Create(compilation.FindType(KnownTypeCode.Double)));
IType type2 = new ParameterizedType(a, ImmutableArray.Create(
new ParameterizedType(b, ImmutableArray.Create(compilation.FindType(KnownTypeCode.String)))));
Assert.That(!conversions.ImplicitConversion(type1, type2).IsValid);
}
[Test]
public void ImplicitTypeParameterConversion()
{
// void M(T t) where T : U => "U u = t;" is a boxing conversion
// (e.g. T = int, U = object)
ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U");
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T", constraints: new[] { u });
Assert.That(conversions.ImplicitConversion(t, u), Is.EqualTo(C.BoxingConversion));
}
[Test]
public void InvalidImplicitTypeParameterConversion()
{
// void M(T t) where U : T => "U u = t;" is invalid
// (the constraint points the wrong way)
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T");
ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U", constraints: new[] { t });
Assert.That(conversions.ImplicitConversion(t, u), Is.EqualTo(C.None));
}
[Test]
public void ImplicitTypeParameterArrayConversion()
{
// void M(T[] t) where T : U => "U[] u = t;" is invalid
// (e.g. T = int, U = object: int[] is not convertible to object[])
ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U");
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T", constraints: new[] { u });
Assert.That(conversions.ImplicitConversion(new ArrayType(compilation, t), new ArrayType(compilation, u)), Is.EqualTo(C.None));
}
[Test]
public void ImplicitTypeParameterConversionWithClassConstraint()
{
// void M(T t) where T : class, U where U : class => "U u = t;" is a reference conversion
ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U", hasReferenceTypeConstraint: true);
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T", hasReferenceTypeConstraint: true, constraints: new[] { u });
Assert.That(conversions.ImplicitConversion(t, u), Is.EqualTo(C.ImplicitReferenceConversion));
}
[Test]
public void ImplicitTypeParameterArrayConversionWithClassConstraint()
{
// void M(T[] t) where T : class, U where U : class => "U[] u = t;" is a
// covariant array conversion (both are known to be reference types)
ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U", hasReferenceTypeConstraint: true);
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T", hasReferenceTypeConstraint: true, constraints: new[] { u });
Assert.That(conversions.ImplicitConversion(new ArrayType(compilation, t), new ArrayType(compilation, u)), Is.EqualTo(C.ImplicitReferenceConversion));
}
[Test]
public void ImplicitTypeParameterConversionWithClassConstraintOnlyOnT()
{
// void M(T t) where T : class, U => "U u = t;" is a reference conversion
// (T is known to be a reference type, so no boxing is involved)
ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U");
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T", hasReferenceTypeConstraint: true, constraints: new[] { u });
Assert.That(conversions.ImplicitConversion(t, u), Is.EqualTo(C.ImplicitReferenceConversion));
}
[Test]
public void ImplicitTypeParameterArrayConversionWithClassConstraintOnlyOnT()
{
// void M(T[] t) where T : class, U => "U[] u = t;" is a covariant array conversion
ITypeParameter u = new DefaultTypeParameter(compilation, SymbolKind.Method, 1, "U");
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T", hasReferenceTypeConstraint: true, constraints: new[] { u });
Assert.That(conversions.ImplicitConversion(new ArrayType(compilation, t), new ArrayType(compilation, u)), Is.EqualTo(C.ImplicitReferenceConversion));
}
[Test]
public void MethodGroupConversion_Void()
{
// delegate void D();
// D d = M; with public static void M() {}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.VoidStatic), "M",
typeof(MethodGroupConversionTestCases.DVoid));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
Assert.That(!c.DelegateCapturesFirstArgument);
Assert.That(c.Method, Is.Not.Null);
}
[Test]
public void MethodGroupConversion_Void_InstanceMethod()
{
// delegate void D();
// D d = M; with public void M() {} (instance method)
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.VoidInstance), "M",
typeof(MethodGroupConversionTestCases.DVoid));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
Assert.That(c.DelegateCapturesFirstArgument);
Assert.That(c.Method, Is.Not.Null);
}
[Test]
public void MethodGroupConversion_MatchingSignature()
{
// delegate object D(int argument);
// D d = M; with public static object M(int argument) {...}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.ReturnObjectFromInt), "M",
typeof(MethodGroupConversionTestCases.DObjInt));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
}
[Test]
public void MethodGroupConversion_InvalidReturnType()
{
// delegate object D(int argument);
// D d = M; with public static int M(int argument) {...}
// int -> object is a boxing conversion, not an identity/reference conversion,
// so the method is not delegate-compatible.
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.ReturnIntFromInt), "M",
typeof(MethodGroupConversionTestCases.DObjInt));
Assert.That(!c.IsValid);
Assert.That(c.IsMethodGroupConversion);
}
[Test]
public void MethodGroupConversion_CovariantReturnType()
{
// delegate object D(int argument);
// D d = M; with public static string M(int argument) {...}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.ReturnStringFromInt), "M",
typeof(MethodGroupConversionTestCases.DObjInt));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
}
[Test]
public void MethodGroupConversion_RefArgumentTypesEqual()
{
// delegate void D(ref object o);
// D d = M; with public static void M(ref object o) {}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.RefObjParam), "M",
typeof(MethodGroupConversionTestCases.DRefObj));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
}
[Test]
public void MethodGroupConversion_RefArgumentObjectVsDynamic()
{
// delegate void D(ref object o);
// D d = M; with public static void M(ref dynamic o) {}
// ref parameters require an identity conversion, and object <-> dynamic IS an
// identity conversion, so this is valid (Roslyn accepts it; the original
// NRefactory test expected invalid, which matched pre-Roslyn csc behavior).
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.RefDynamicParam), "M",
typeof(MethodGroupConversionTestCases.DRefObj));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
}
[Test]
public void MethodGroupConversion_RefVsOut()
{
// delegate void D(ref object o);
// D d = M; with public static void M(out object o) {...}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.OutObjParam), "M",
typeof(MethodGroupConversionTestCases.DRefObj));
Assert.That(!c.IsValid);
}
[Test]
public void MethodGroupConversion_RefVsNormal()
{
// delegate void D(ref object o);
// D d = M; with public static void M(object o) {}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.ObjParam), "M",
typeof(MethodGroupConversionTestCases.DRefObj));
Assert.That(!c.IsValid);
}
[Test]
public void MethodGroupConversion_NormalVsOut()
{
// delegate void D(object o);
// D d = M; with public static void M(out object o) {...}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.OutObjParam), "M",
typeof(MethodGroupConversionTestCases.DObj));
Assert.That(!c.IsValid);
}
[Test]
public void MethodGroupConversion_MatchingNormalParameter()
{
// delegate void D(object o);
// D d = M; with public static void M(object o) {}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.ObjParam), "M",
typeof(MethodGroupConversionTestCases.DObj));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
}
[Test]
public void MethodGroupConversion_IdentityConversion()
{
// delegate void D(object o);
// D d = M; with public static void M(dynamic o) {}
// object -> dynamic is an identity conversion, so M is delegate-compatible.
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.DynamicParam), "M",
typeof(MethodGroupConversionTestCases.DObj));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
}
[Test]
public void MethodGroupConversion_Contravariance()
{
// delegate void D(string o);
// D d = M; with public static void M(object o) {}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.ObjParam), "M",
typeof(MethodGroupConversionTestCases.DStr));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
}
[Test, Ignore("Not sure if this conversion should be valid or not... NR and mcs both accept it as valid, csc treats it as invalid")]
public void MethodGroupConversion_NoContravarianceDynamic()
{
// delegate void D(string o);
// D d = M; with public static void M(dynamic o) {}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.DynamicParam), "M",
typeof(MethodGroupConversionTestCases.DStr));
//Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
}
[Test]
public void MethodGroupConversion_ExactMatchIsBetter()
{
// delegate void D(string a);
// D d = M; with static void M(object x) {} and static void M(string x = null) {}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.OverloadObjectOrOptionalString), "M",
typeof(MethodGroupConversionTestCases.DStr));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
Assert.That(c.Method.Parameters.Single().Type.FullName, Is.EqualTo("System.String"));
}
[Test]
public void MethodGroupConversion_CannotLeaveOutOptionalParameters()
{
// delegate void D(string a);
// D d = M; with static void M(object x) {} and static void M(string x, string y = null) {}
// The two-parameter overload cannot bind to a one-parameter delegate.
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.OverloadObjectOrStringPlusOptional), "M",
typeof(MethodGroupConversionTestCases.DStr));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
Assert.That(c.Method.Parameters.Single().Type.FullName, Is.EqualTo("System.Object"));
}
[Test]
public void MethodGroupConversion_CannotUseExpandedParams()
{
// delegate void D(string a);
// D d = M; with static void M(object x) {} and static void M(params string[] x) {}
// The params overload only matches in expanded form, which method group
// conversions do not use.
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.OverloadObjectOrParamsString), "M",
typeof(MethodGroupConversionTestCases.DStr));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
Assert.That(c.Method.Parameters.Single().Type.FullName, Is.EqualTo("System.Object"));
}
[Test]
public void MethodGroupConversion_ExtensionMethod()
{
// static class Ext { public static void M(this string s, int x) {} }
// delegate void D(int a);
// string s = ""; D d = s.M;
IType stringType = compilation.FindType(KnownTypeCode.String);
IMethod extensionMethod = compilation.FindType(typeof(MethodGroupConversionExt))
.GetMethods(m => m.Name == "M").Single();
var c = MethodGroupConversion(typeof(string), "M",
typeof(MethodGroupConversionTestCases.DInt),
targetResult: new ResolveResult(stringType),
extensionMethods: new[] { extensionMethod });
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
Assert.That(c.DelegateCapturesFirstArgument);
}
[Test]
public void MethodGroupConversion_ExtensionMethodUsedAsStaticMethod()
{
// static class Ext { public static void M(this string s, int x) {} }
// delegate void D(string s, int a);
// D d = Ext.M;
var c = MethodGroupConversion(typeof(MethodGroupConversionExt), "M",
typeof(MethodGroupConversionTestCases.DStrInt));
Assert.That(c.IsValid);
Assert.That(c.IsMethodGroupConversion);
Assert.That(!c.DelegateCapturesFirstArgument);
}
[Test]
public void MethodGroupConversion_ObjectToDynamic()
{
// Action x = F; with public void F(object o) {}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.ObjParamInstance), "F",
typeof(Action));
Assert.That(c.IsValid);
}
[Test]
public void MethodGroupConversion_ObjectToDynamicGenericArgument()
{
// Action> x = F; with public void F(List l) {}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.ObjListParamInstance), "F",
typeof(Action>));
Assert.That(c.IsValid);
}
[Test]
public void MethodGroupConversion_ObjectToDynamicReturnValue()
{
// Func x = F; with public object F() {...}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.ObjReturnInstance), "F",
typeof(Func));
Assert.That(c.IsValid);
}
[Test]
public void MethodGroupConversion_DynamicToObject()
{
// Action x = F; with public void F(dynamic o) {}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.DynamicParamInstance), "F",
typeof(Action));
Assert.That(c.IsValid);
}
[Test]
public void MethodGroupConversion_DynamicToObjectGenericArgument()
{
// Action> x = F; with public void F(List l) {}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.DynamicListParamInstance), "F",
typeof(Action>));
Assert.That(c.IsValid);
}
[Test]
public void MethodGroupConversion_DynamicToObjectReturnValue()
{
// Func x = F; with public dynamic F() {...}
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.DynamicReturnInstance), "F",
typeof(Func));
Assert.That(c.IsValid);
}
[Test]
public void UserDefined_IntLiteral_ViaUInt_ToCustomStruct()
{
// struct T { public static implicit operator T(uint a) {...} }
// T t = 1;
var c = ConstantConversion(1, typeof(UserDefinedConversionTestCases.TFromUInt));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
}
[Test]
public void UserDefined_NullLiteral_ViaString_ToCustomStruct()
{
// struct T { public static implicit operator T(string a) {...} }
// T t = null;
var c = ImplicitConversion(typeof(Null), typeof(UserDefinedConversionTestCases.TFromString));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
}
[Test]
public void UserDefined_CanUseLiftedEvenIfReturnTypeAlreadyNullable()
{
// struct S { public static implicit operator short?(S s) {...} }
// int? i = s; with s of type S?
var c = ImplicitConversion(typeof(UserDefinedConversionTestCases.SToNullableShort?), typeof(int?));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
Assert.That(c.IsLifted);
}
[Test]
public void UserDefinedImplicitConversion_PicksExactSourceTypeIfPossible()
{
// Convertible has operators from int ("i") and short ("s");
// Convertible a = 33;
var c = ConstantConversion(33, typeof(UserDefinedConversionTestCases.ConvertibleFromIntOrShort));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("i"));
}
[Test]
public void UserDefinedImplicitConversion_PicksMostEncompassedSourceType()
{
// Convertible has operators from long ("l") and uint ("ui");
// Convertible a = (ushort)33;
var c = ConstantConversion((ushort)33, typeof(UserDefinedConversionTestCases.ConvertibleFromLongOrUInt));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("ui"));
}
[Test]
public void UserDefinedImplicitConversion_NoMostEncompassedSourceTypeIsInvalid()
{
// Convertible has operators from ulong and int; neither source type encompasses
// the other, so the conversion from ushort is ambiguous.
var c = ConstantConversion((ushort)33, typeof(UserDefinedConversionTestCases.ConvertibleFromULongOrInt));
Assert.That(!c.IsValid);
}
[Test]
public void UserDefinedImplicitConversion_PicksExactTargetTypeIfPossible()
{
// Convertible has operators to int ("i") and short ("s");
// int a = new Convertible();
var c = ImplicitConversion(typeof(UserDefinedConversionTestCases.ConvertibleToIntOrShort), typeof(int));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("i"));
}
[Test]
public void UserDefinedImplicitConversion_PicksMostEncompassingTargetType()
{
// Convertible has operators to int ("i") and ushort ("us");
// ulong a = new Convertible(); -- only ushort converts implicitly to ulong
var c = ImplicitConversion(typeof(UserDefinedConversionTestCases.ConvertibleToIntOrUShort), typeof(ulong));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("us"));
}
[Test]
public void UserDefinedImplicitConversion_NoMostEncompassingTargetTypeIsInvalid()
{
// Convertible has operators to uint and short; neither target type encompasses
// the other, so the conversion to long is ambiguous.
var c = ImplicitConversion(typeof(UserDefinedConversionTestCases.ConvertibleToUIntOrShort), typeof(long));
Assert.That(!c.IsValid);
}
[Test]
public void UserDefinedImplicitConversion_AmbiguousIsInvalid()
{
// Both AmbiguousA and AmbiguousB declare implicit operator AmbiguousB(AmbiguousA).
var c = ImplicitConversion(typeof(UserDefinedConversionTestCases.AmbiguousA), typeof(UserDefinedConversionTestCases.AmbiguousB));
Assert.That(!c.IsValid);
}
[Test]
public void UserDefinedImplicitConversion_DefinedNullableTakesPrecedenceOverLifted()
{
// struct Convertible declares operators from int ("i") and from int? ("ni");
// Convertible? a = (int?)33; -- the user-defined nullable operator wins over
// the lifted form of the int operator.
var c = ImplicitConversion(typeof(int?), typeof(UserDefinedConversionTestCases.NullableConvertible?));
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 UserDefinedImplicitConversion_UIntConstant()
{
// Convertible has operators from long ("l") and uint ("ui");
// Convertible a = 33; -- the constant 33 converts to uint, which is more specific
var c = ConstantConversion(33, typeof(UserDefinedConversionTestCases.ConvertibleFromLongOrUInt));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("ui"));
}
[Test]
public void UserDefinedImplicitConversion_NullableUIntConstant()
{
// Convertible has operators from long? ("l") and uint? ("ui");
// Convertible a = 33;
var c = ConstantConversion(33, typeof(UserDefinedConversionTestCases.ConvertibleFromNullableLongOrNullableUInt));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("ui"));
}
[Test]
public void UserDefinedImplicitConversion_UseShortResult_BecauseNullableCannotBeUnpacked()
{
// operators to int? ("i") and short ("s");
// int x = new Test(); -- int? cannot be unpacked to int, so short is used
var c = ImplicitConversion(typeof(UserDefinedConversionTestCases.ToNullableIntOrShort), typeof(int));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
Assert.That(c.Method.ReturnType.FullName, Is.EqualTo("System.Int16"));
}
[Test]
public void UserDefinedImplicitConversion_Short_Or_NullableByte_Target()
{
// operators to short ("s") and byte? ("b");
// int? x = new Test();
var c = ImplicitConversion(typeof(UserDefinedConversionTestCases.ToShortOrNullableByte), typeof(int?));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
Assert.That(c.Method.ReturnType.FullName, Is.EqualTo("System.Int16"));
}
[Test]
public void UserDefinedImplicitConversion_Byte_Or_NullableShort_Target()
{
// operators to byte ("b") and short? ("s");
// int? x = new Test();
var c = ImplicitConversion(typeof(UserDefinedConversionTestCases.ToByteOrNullableShort), typeof(int?));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("s"));
}
[Test]
public void UserDefinedImplicitConversion_Int_Or_NullableLong_Source()
{
// operators from int ("i") and long? ("l"); source is short
var c = ImplicitConversion(typeof(short), typeof(UserDefinedConversionTestCases.FromIntOrNullableLong));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
Assert.That(c.Method.Parameters[0].Name, Is.EqualTo("i"));
}
[Test]
public void UserDefinedImplicitConversion_NullableInt_Or_Long_Source()
{
// operators from int? ("i") and long ("l"); source is short:
// neither int? nor long is more specific, so the conversion is ambiguous
var c = ImplicitConversion(typeof(short), typeof(UserDefinedConversionTestCases.FromNullableIntOrLong));
Assert.That(!c.IsValid);
Assert.That(c.IsUserDefined);
}
[Test]
public void UserDefinedImplicitConversion_NullableInt_Or_Long_Constant_Source()
{
// operators from int? ("i") and long ("l"); source is the constant 1
var c = ConstantConversion(1, typeof(UserDefinedConversionTestCases.FromNullableIntOrLong));
Assert.That(!c.IsValid);
Assert.That(c.IsUserDefined);
}
[Test]
public void UserDefinedImplicitConversion_NullableInt_Or_NullableLong_Source()
{
// operators from int? ("i") and long? ("l"); source is short
var c = ImplicitConversion(typeof(short), typeof(UserDefinedConversionTestCases.FromNullableIntOrNullableLong));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
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.
var or = new OverloadResolution(compilation, new[] {
new ResolveResult(compilation.FindType(typeof(UserDefinedConversionTestCases.ConvertibleToString)))
});
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);
Assert.That(or.BestCandidate, Is.SameAs(mString));
}
[Test]
public void PreferAmbiguousConversionOverReferenceConversion()
{
// Ambiguous conversions are a compiler error; but they are not
// preventing the overload from being chosen.
// 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]
public void UserDefinedImplicitConversion_ConversionBeforeUserDefinedOperatorIsCorrect()
{
// Convertible has an operator from long; converting an int goes int -> long -> Convertible.
var c = ImplicitConversion(typeof(int), typeof(UserDefinedConversionTestCases.ConvertibleFromLong));
Assert.That(c.IsValid);
Assert.That(c.ConversionBeforeUserDefinedOperator.IsImplicit);
Assert.That(c.ConversionBeforeUserDefinedOperator.IsNumericConversion);
Assert.That(c.ConversionBeforeUserDefinedOperator.IsValid);
Assert.That(c.ConversionAfterUserDefinedOperator.IsIdentityConversion);
}
[Test]
public void UserDefinedImplicitConversion_ConversionAfterUserDefinedOperatorIsCorrect()
{
// Convertible has an operator to int; converting to long goes Convertible -> int -> long.
var c = ImplicitConversion(typeof(UserDefinedConversionTestCases.ConvertibleToInt), typeof(long));
Assert.That(c.IsValid);
Assert.That(c.ConversionBeforeUserDefinedOperator.IsIdentityConversion);
Assert.That(c.ConversionAfterUserDefinedOperator.IsImplicit);
Assert.That(c.ConversionAfterUserDefinedOperator.IsNumericConversion);
Assert.That(c.ConversionAfterUserDefinedOperator.IsValid);
}
[Test]
public void UserDefinedImplicitConversion_IsImplicit()
{
// Bug icsharpcode/NRefactory#183: conversions from constant expressions were incorrectly marked as explicit
var c = ConstantConversion(7, typeof(UserDefinedConversionTestCases.JsNumber));
Assert.That(c.IsValid);
Assert.That(c.IsImplicit);
Assert.That(!c.IsExplicit);
Assert.That(c.ConversionBeforeUserDefinedOperator, Is.EqualTo(C.IdentityConversion));
Assert.That(c.ConversionAfterUserDefinedOperator, Is.EqualTo(C.IdentityConversion));
}
[Test]
public void TupleIdentityConversionWithUnderlyingValueTuple()
{
// C# standard 10.2.2: identity conversion between a tuple type and the
// corresponding constructed ValueTuple<...> type
var intType = compilation.FindType(typeof(int));
var stringType = compilation.FindType(typeof(string));
IType tupleType = new TupleType(compilation, ImmutableArray.Create(intType, stringType), ImmutableArray.Create("a", "b"));
IType valueTupleType = compilation.FindType(typeof(ValueTuple));
Assert.That(conversions.ImplicitConversion(tupleType, valueTupleType), Is.EqualTo(C.IdentityConversion));
Assert.That(conversions.ImplicitConversion(valueTupleType, tupleType), Is.EqualTo(C.IdentityConversion));
}
[Test]
public void IdentityConversionNullableReferenceType()
{
// C# standard 10.2.2: identity conversion between T and T? for any reference type T
IType stringType = compilation.FindType(KnownTypeCode.String);
IType nullableStringType = stringType.ChangeNullability(Nullability.Nullable);
Assert.That(conversions.ImplicitConversion(stringType, nullableStringType), Is.EqualTo(C.IdentityConversion));
Assert.That(conversions.ImplicitConversion(nullableStringType, stringType), Is.EqualTo(C.IdentityConversion));
}
[Test]
public void NumericConversionMatrix()
{
// C# standard 10.2.3 (implicit numeric conversions) and 10.3.2 (explicit numeric
// conversions). The implicit table below is transcribed from 10.2.3; every other
// pair of distinct numeric types must be an explicit numeric conversion.
var allNumericTypes = new[] {
typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int),
typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float),
typeof(double), typeof(decimal)
};
var implicitConversions = new Dictionary {
[typeof(sbyte)] = new[] { typeof(short), typeof(int), typeof(long), typeof(float), typeof(double), typeof(decimal) },
[typeof(byte)] = new[] { typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) },
[typeof(short)] = new[] { typeof(int), typeof(long), typeof(float), typeof(double), typeof(decimal) },
[typeof(ushort)] = new[] { typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) },
[typeof(int)] = new[] { typeof(long), typeof(float), typeof(double), typeof(decimal) },
[typeof(uint)] = new[] { typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) },
[typeof(long)] = new[] { typeof(float), typeof(double), typeof(decimal) },
[typeof(ulong)] = new[] { typeof(float), typeof(double), typeof(decimal) },
[typeof(char)] = new[] { typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) },
[typeof(float)] = new[] { typeof(double) },
[typeof(double)] = new Type[0],
[typeof(decimal)] = new Type[0],
};
foreach (Type from in allNumericTypes)
{
foreach (Type to in allNumericTypes)
{
string pair = from.Name + " -> " + to.Name;
if (from == to)
{
Assert.That(ImplicitConversion(from, to), Is.EqualTo(C.IdentityConversion), pair);
}
else if (Array.IndexOf(implicitConversions[from], to) >= 0)
{
Assert.That(ImplicitConversion(from, to), Is.EqualTo(C.ImplicitNumericConversion), pair);
Assert.That(ExplicitConversion(from, to), Is.EqualTo(C.ImplicitNumericConversion), pair);
}
else
{
Assert.That(ImplicitConversion(from, to), Is.EqualTo(C.None), pair);
Assert.That(ExplicitConversion(from, to), Is.EqualTo(C.ExplicitNumericConversion), pair);
}
}
}
}
[Test]
public void InterpolatedStringConversion()
{
// C# standard 10.2.5: an interpolated string expression converts to
// System.IFormattable and System.FormattableString
var interpolated = new InterpolatedStringResolveResult(compilation.FindType(KnownTypeCode.String),
"{0}", new ResolveResult(compilation.FindType(KnownTypeCode.Int32)));
Assert.That(conversions.ImplicitConversion(interpolated, compilation.FindType(KnownTypeCode.IFormattable)), Is.EqualTo(C.ImplicitInterpolatedStringConversion));
Assert.That(conversions.ImplicitConversion(interpolated, compilation.FindType(KnownTypeCode.FormattableString)), Is.EqualTo(C.ImplicitInterpolatedStringConversion));
Assert.That(conversions.ImplicitConversion(interpolated, compilation.FindType(KnownTypeCode.String)), Is.EqualTo(C.IdentityConversion));
}
[Test]
public void IdentityDerivedNullableConversion()
{
// C# standard 10.2.6/10.6.1: nullable conversion derived from the identity conversion
Assert.That(ImplicitConversion(typeof(int), typeof(int?)), Is.EqualTo(C.ImplicitNullableConversion));
}
[Test]
public void DelegateToSystemDelegateConversions()
{
// C# standard 10.2.8: from any delegate_type to System.Delegate and the
// interfaces it implements
Assert.That(ImplicitConversion(typeof(Action), typeof(Delegate)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(Action), typeof(MulticastDelegate)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(Action), typeof(ICloneable)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(Delegate), typeof(Action)), Is.EqualTo(C.None));
}
[Test]
public void ClassToBaseClassConversion()
{
// C# standard 10.2.8: from any class_type S to any class_type T, provided S is derived from T
Assert.That(ImplicitConversion(typeof(UserDefinedExplicitConversionTestCases.DerivedClass), typeof(UserDefinedExplicitConversionTestCases.BaseClass)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(UserDefinedExplicitConversionTestCases.BaseClass), typeof(UserDefinedExplicitConversionTestCases.DerivedClass)), Is.EqualTo(C.None));
}
[Test]
public void ArrayToIReadOnlyListConversion()
{
// C# standard 10.2.8: from S[] to IReadOnlyList and its base interfaces
Assert.That(ImplicitConversion(typeof(string[]), typeof(IReadOnlyList)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(string[]), typeof(IReadOnlyList)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(string[]), typeof(IReadOnlyCollection)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(string[,]), typeof(IReadOnlyList)), Is.EqualTo(C.None));
}
[Test]
public void VarianceConversionWithDynamicTypeArgument()
{
// C# standard 10.2.8: implicit reference conversion via a variance-convertible
// type where the type arguments differ by the object/dynamic identity conversion
Assert.That(ImplicitConversion(typeof(IEnumerable), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(ImplicitConversion(typeof(List), typeof(IEnumerable)), Is.EqualTo(C.ImplicitReferenceConversion));
}
[Test]
public void BoxingConversions()
{
// C# standard 10.2.9
Assert.That(ImplicitConversion(typeof(int), typeof(object)), Is.EqualTo(C.BoxingConversion));
Assert.That(ImplicitConversion(typeof(int), typeof(ValueType)), Is.EqualTo(C.BoxingConversion));
Assert.That(ImplicitConversion(typeof(StringComparison), typeof(Enum)), Is.EqualTo(C.BoxingConversion));
Assert.That(ImplicitConversion(typeof(int), typeof(IFormattable)), Is.EqualTo(C.BoxingConversion));
Assert.That(ImplicitConversion(typeof(int), typeof(IComparable)), Is.EqualTo(C.BoxingConversion));
Assert.That(ImplicitConversion(typeof(int), typeof(IComparable)), Is.EqualTo(C.None));
// nullable value types box to the reference types their underlying type boxes to
Assert.That(ImplicitConversion(typeof(int?), typeof(object)), Is.EqualTo(C.BoxingConversion));
Assert.That(ImplicitConversion(typeof(int?), typeof(IFormattable)), Is.EqualTo(C.BoxingConversion));
Assert.That(ImplicitConversion(typeof(StringComparison?), typeof(Enum)), Is.EqualTo(C.BoxingConversion));
}
[Test]
public void BoxingConversionViaVariance()
{
// C# standard 10.2.9: boxing to an interface that the implemented interface is
// variance-convertible to
Assert.That(ImplicitConversion(typeof(StructImplementingIEnumerableOfString), typeof(IEnumerable)), Is.EqualTo(C.BoxingConversion));
Assert.That(ImplicitConversion(typeof(StructImplementingIEnumerableOfString), typeof(IEnumerable)), Is.EqualTo(C.BoxingConversion));
Assert.That(ImplicitConversion(typeof(StructImplementingIEnumerableOfString), typeof(IEnumerable)), Is.EqualTo(C.BoxingConversion));
Assert.That(ImplicitConversion(typeof(StructImplementingIEnumerableOfString), typeof(IEnumerable)), Is.EqualTo(C.None));
}
[Test]
public void ImplicitConstantExpressionConversionToUInt64()
{
// C# standard 10.2.11: an int constant expression converts to ulong
// provided its value is non-negative
Assert.That(IntegerLiteralConversion(0, typeof(ulong)));
Assert.That(IntegerLiteralConversion(200, typeof(ulong)));
Assert.That(!IntegerLiteralConversion(-1, typeof(ulong)));
}
[Test]
public void TypeParameterConversionViaVariance()
{
// C# standard 10.2.12 (last bullet group): conversions from T via a
// variance-convertible interface of its effective base class / interface set
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T",
constraints: new[] { compilation.FindType(typeof(List)) });
Assert.That(conversions.ImplicitConversion(t, compilation.FindType(typeof(IEnumerable))), Is.EqualTo(C.ImplicitReferenceConversion));
ITypeParameter t2 = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T",
constraints: new[] { compilation.FindType(typeof(IEnumerable)) });
Assert.That(conversions.ImplicitConversion(t2, compilation.FindType(typeof(IEnumerable))), Is.EqualTo(C.BoxingConversion));
}
[Test]
public void TupleLiteralConversions()
{
// C# standard 10.2.13: implicit conversion from a tuple literal, using the
// implicit conversions of the element expressions
var intType = compilation.FindType(KnownTypeCode.Int32);
var stringType = compilation.FindType(KnownTypeCode.String);
var byteType = compilation.FindType(KnownTypeCode.Byte);
// (2, null) -> (byte, string)
var literal = new TupleResolveResult(compilation, ImmutableArray.Create(
new ConstantResolveResult(intType, 2),
new ConstantResolveResult(SpecialType.NullType, null)));
Assert.That(conversions.ImplicitConversion(literal, new TupleType(compilation, ImmutableArray.Create(byteType, stringType))),
Is.EqualTo(C.TupleConversion(ImmutableArray.Create(C.ImplicitConstantExpressionConversion, C.NullLiteralConversion))));
// arity mismatch
Assert.That(conversions.ImplicitConversion(literal, new TupleType(compilation, ImmutableArray.Create(byteType, stringType, intType))),
Is.EqualTo(C.None));
// (300, null) -> (byte, string): 300 is out of range for byte
var literal2 = new TupleResolveResult(compilation, ImmutableArray.Create(
new ConstantResolveResult(intType, 300),
new ConstantResolveResult(SpecialType.NullType, null)));
Assert.That(conversions.ImplicitConversion(literal2, new TupleType(compilation, ImmutableArray.Create(byteType, stringType))),
Is.EqualTo(C.None));
}
[Test, Ignore("Known bug: CSharpConversions does not support nullable conversions derived from tuple conversions, but csc accepts them")]
public void LiftedTupleConversions()
{
// csc accepts the nullable conversions derived from tuple conversions:
// (int, string) t = (1, "one");
// (long, object)? a = t;
// (int, string)? tn = t;
// (long, object)? b = tn;
Assert.That(ImplicitConversion(typeof((int, string)), typeof((long, object)?)).IsValid, "(int, string) -> (long, object)?");
Assert.That(ImplicitConversion(typeof((int, string)?), typeof((long, object)?)).IsValid, "(int, string)? -> (long, object)?");
}
[Test]
public void UserDefinedImplicitConversion_OperatorDeclaredInBaseClassOfSource()
{
// C# standard 10.5.4: the set of considered operator declarations includes the
// base classes of the source type
var c = ImplicitConversion(typeof(UserDefinedConversionTestCases.DerivedFromOperatorInBaseClass), typeof(string));
Assert.That(c.IsValid);
Assert.That(c.IsUserDefined);
Assert.That(c.Method.DeclaringType.Name, Is.EqualTo("OperatorInBaseClass"));
}
[Test]
public void DefaultLiteralConversions()
{
// C# standard 10.2.16: an implicit conversion exists from a default_literal to any type
var defaultLiteral = new DefaultLiteralResolveResult();
// a default_value_expression is a constant expression (C# standard 12.8.21)
Assert.That(defaultLiteral.IsCompileTimeConstant);
Assert.That(conversions.ImplicitConversion(defaultLiteral, compilation.FindType(KnownTypeCode.Int32)), Is.EqualTo(C.DefaultLiteralConversion));
Assert.That(conversions.ImplicitConversion(defaultLiteral, compilation.FindType(KnownTypeCode.String)), Is.EqualTo(C.DefaultLiteralConversion));
Assert.That(conversions.ImplicitConversion(defaultLiteral, compilation.FindType(typeof(int?))), Is.EqualTo(C.DefaultLiteralConversion));
ITypeParameter t = new DefaultTypeParameter(compilation, SymbolKind.Method, 0, "T");
Assert.That(conversions.ImplicitConversion(defaultLiteral, t), Is.EqualTo(C.DefaultLiteralConversion));
// explicit conversions include all implicit conversions, so (T)default is also valid
Assert.That(conversions.ExplicitConversion(defaultLiteral, compilation.FindType(KnownTypeCode.Int32)), Is.EqualTo(C.DefaultLiteralConversion));
}
[Test, Ignore("C# standard 10.2.18 is not implemented: no ResolveResult represents a switch expression; the decompiler converts each arm separately in ILAst")]
public void SwitchExpressionConversion()
{
// C# standard 10.2.18: an implicit conversion exists from a switch_expression to
// every type T to which all arm expressions implicitly convert. Once the semantic
// model gains a switch-expression ResolveResult, this test should assert that the
// conversion exists iff every arm converts to the target type.
Assert.Fail("Switch expression conversions are not implemented.");
}
[Test]
public void ThrowExpressionConversion()
{
// C# standard 10.2.17: throw expressions convert to any type
Assert.That(conversions.ImplicitConversion(new ThrowResolveResult(), compilation.FindType(KnownTypeCode.String)), Is.EqualTo(C.ThrowExpressionConversion));
Assert.That(conversions.ImplicitConversion(new ThrowResolveResult(), compilation.FindType(KnownTypeCode.Int32)), Is.EqualTo(C.ThrowExpressionConversion));
}
[Test]
public void StandardImplicitConversions()
{
// C# standard 10.4.2: standard implicit conversions exclude user-defined conversions
Assert.That(conversions.StandardImplicitConversion(compilation.FindType(typeof(int)), compilation.FindType(typeof(long))), Is.EqualTo(C.ImplicitNumericConversion));
Assert.That(conversions.StandardImplicitConversion(compilation.FindType(typeof(string)), compilation.FindType(typeof(object))), Is.EqualTo(C.ImplicitReferenceConversion));
Assert.That(conversions.StandardImplicitConversion(compilation.FindType(typeof(DateTime)), compilation.FindType(typeof(DateTimeOffset))), Is.EqualTo(C.None));
}
[Test]
public void MethodGroupConversion_GenericMethodTypeInference()
{
// C# standard 10.8: delegate parameter types are used to infer the type
// arguments of a generic method group
// delegate int D(string s, int i);
// D d = F; with static T F(string s, T t) -- T=int is inferred
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.GenericMethods), "F",
typeof(MethodGroupConversionTestCases.DStrIntRetInt));
Assert.That(c.IsValid);
Assert.That(c.Method.TypeArguments.Single().IsKnownType(KnownTypeCode.Int32));
}
[Test]
public void MethodGroupConversion_GenericMethodExplicitTypeArguments()
{
// C# standard 10.8:
// delegate int E();
// E e = G; with static T G()
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.GenericMethods), "G",
typeof(MethodGroupConversionTestCases.DRetInt),
typeArguments: new[] { compilation.FindType(KnownTypeCode.Int32) });
Assert.That(c.IsValid);
Assert.That(c.Method.TypeArguments.Single().IsKnownType(KnownTypeCode.Int32));
}
[Test]
public void MethodGroupConversion_CannotInferFromReturnType()
{
// C# standard 10.8: the return type of the delegate is not used for inference
// E e = G; with static T G()
var c = MethodGroupConversion(typeof(MethodGroupConversionTestCases.GenericMethods), "G",
typeof(MethodGroupConversionTestCases.DRetInt));
Assert.That(!c.IsValid);
}
[Test]
public void AnonymousFunctionConversions()
{
// C# standard 10.7.1: compatibility of an anonymous function with a delegate type.
// TestLambda stands in for a lambda whose body is an expression of the given type;
// the signature checks under test here are performed by CSharpConversions itself.
IType intType = compilation.FindType(KnownTypeCode.Int32);
var intParam = new IParameter[] { new DefaultParameter(intType, "x") };
// (int x) => intExpr is compatible with Func and Func
Assert.That(conversions.ImplicitConversion(new TestLambda(intType, intParam), compilation.FindType(typeof(Func))).IsValid);
Assert.That(conversions.ImplicitConversion(new TestLambda(intType, intParam), compilation.FindType(typeof(Func))).IsValid);
// parameter count mismatch
Assert.That(conversions.ImplicitConversion(new TestLambda(intType, intParam), compilation.FindType(typeof(Func))), Is.EqualTo(C.None));
// an explicitly typed parameter must have the delegate's parameter type
Assert.That(conversions.ImplicitConversion(new TestLambda(intType, intParam), compilation.FindType(typeof(Func))), Is.EqualTo(C.None));
// an implicitly typed parameter list is incompatible with ref/out parameters
Assert.That(conversions.ImplicitConversion(
new TestLambda(intType, new IParameter[] { new DefaultParameter(SpecialType.UnknownType, "x") }, isImplicitlyTyped: true),
compilation.FindType(typeof(MethodGroupConversionTestCases.DRefObj))), Is.EqualTo(C.None));
// an anonymous method without a signature accepts any parameter list without out parameters
Assert.That(conversions.ImplicitConversion(
new TestLambda(intType, hasParameterList: false, isAnonymousMethod: true),
compilation.FindType(typeof(Func))).IsValid);
}
[Test]
public void LambdaToExpressionTreeConversion()
{
// C# standard 10.7.1: a lambda expression compatible with D is compatible with
// Expression; anonymous methods are not.
IType intType = compilation.FindType(KnownTypeCode.Int32);
var intParam = new IParameter[] { new DefaultParameter(intType, "x") };
Assert.That(conversions.ImplicitConversion(new TestLambda(intType, intParam),
compilation.FindType(typeof(System.Linq.Expressions.Expression>))).IsValid);
Assert.That(conversions.ImplicitConversion(
new TestLambda(intType, hasParameterList: false, isAnonymousMethod: true),
compilation.FindType(typeof(System.Linq.Expressions.Expression>))), Is.EqualTo(C.None));
}
///
/// Stands in for a lambda or anonymous method whose body is an expression of a fixed type.
///
sealed class TestLambda : LambdaResolveResult
{
readonly IType bodyReturnType;
readonly IParameter[] parameters;
public TestLambda(IType bodyReturnType, IParameter[] parameters = null,
bool hasParameterList = true, bool isAnonymousMethod = false, bool isImplicitlyTyped = false)
{
this.bodyReturnType = bodyReturnType;
this.parameters = parameters ?? new IParameter[0];
this.HasParameterList = hasParameterList;
this.IsAnonymousMethod = isAnonymousMethod;
this.IsImplicitlyTyped = isImplicitlyTyped;
}
public override bool HasParameterList { get; }
public override bool IsAnonymousMethod { get; }
public override bool IsImplicitlyTyped { get; }
public override bool IsAsync => false;
public override IReadOnlyList Parameters => parameters;
public override IType ReturnType => bodyReturnType;
public override ResolveResult Body => null;
public override IType GetInferredReturnType(IType[] parameterTypes)
{
return bodyReturnType;
}
public override Conversion IsValid(IType[] parameterTypes, IType returnType, CSharpConversions conversions)
{
return conversions.ImplicitConversion(bodyReturnType, returnType);
}
}
#region First-class span conversions
Conversion SpanConversion(Type from, Type to)
{
var c = RefAssemblyCompilation.Instance;
return CSharpConversions.Get(c).ImplicitConversion(c.FindType(from), c.FindType(to));
}
[Test]
public void ImplicitSpanConversions()
{
Assert.That(SpanConversion(typeof(string), typeof(ReadOnlySpan)), Is.EqualTo(C.ImplicitSpanConversion),
"the span conversion must win over String's own op_Implicit: user-defined conversions are not considered between span-convertible types");
Assert.That(SpanConversion(typeof(string[]), typeof(Span)), Is.EqualTo(C.ImplicitSpanConversion));
Assert.That(SpanConversion(typeof(string[]), typeof(ReadOnlySpan)), Is.EqualTo(C.ImplicitSpanConversion));
Assert.That(SpanConversion(typeof(Span), typeof(ReadOnlySpan)), Is.EqualTo(C.ImplicitSpanConversion));
Assert.That(SpanConversion(typeof(ReadOnlySpan), typeof(ReadOnlySpan)), Is.EqualTo(C.ImplicitSpanConversion));
}
[Test]
public void NoImplicitSpanConversionWithoutElementCovariance()
{
// Roslyn: CS0029 - no conversion at all relates these.
Assert.That(SpanConversion(typeof(int[]), typeof(ReadOnlySpan)), Is.EqualTo(C.None));
// Roslyn: CS0266 - only an EXPLICIT (span) conversion exists. In particular the
// user-defined route via Span.op_Implicit(object[]) plus array covariance
// must not be considered, because a span conversion exists for the pair.
Assert.That(SpanConversion(typeof(string[]), typeof(Span)), Is.EqualTo(C.None));
}
Conversion SpanMethodGroupConversion(ResolveResult target, Type delegateType)
{
var c = RefAssemblyCompilation.Instance;
var extensionMethod = c.FindType(typeof(SpanReceiverExtensionTestCase))
.GetMethods(m => m.Name == "M").Single();
var mgrr = new MethodGroupResolveResult(
target, "M",
new[] { new MethodListWithDeclaringType(target.Type, target.Type.GetMethods(m => m.Name == "M")) },
typeArguments: null);
mgrr.extensionMethods = new List> { new List { extensionMethod } };
return CSharpConversions.Get(c).ImplicitConversion(mgrr, c.FindType(delegateType));
}
[Test]
public void MethodGroupConversion_SpanConversionOnTheReceiverIsNotConsidered()
{
// C# 14 first-class spans: "span conversion is not considered when overload
// resolution is performed for a method group conversion". For 'str.M' with M being
// an extension on ReadOnlySpan, Roslyn reports CS0123, even though the
// invocation 'str.M()' is legal.
var c = RefAssemblyCompilation.Instance;
var conversion = SpanMethodGroupConversion(
new ResolveResult(c.FindType(KnownTypeCode.String)), typeof(Action));
Assert.That(conversion, Is.EqualTo(C.None));
}
[Test]
public void MethodGroupConversion_IdentityReceiverOnSpanExtensionStillConverts()
{
// Guard for the rule above: with an identity-typed receiver the method group
// conversion stays legal.
var c = RefAssemblyCompilation.Instance;
var conversion = SpanMethodGroupConversion(
new ResolveResult(c.FindType(typeof(ReadOnlySpan))), typeof(Action));
Assert.That(conversion.IsMethodGroupConversion);
Assert.That(conversion.IsValid);
}
#endregion
}
}