@@ -1229,6 +1229,41 @@ List<OperatorInfo> GetApplicableConversionOperators(ResolveResult fromResult, IT
12291229 }
12301230 #endregion
12311231
1232+ #region Implicit Span Conversion
1233+
1234+ public bool IsImplicitSpanConversion ( IType fromType , IType toType )
1235+ {
1236+ // An implicit span conversion permits array_types, System.Span<T>, System.ReadOnlySpan<T>,
1237+ // and string to be converted between each other
1238+ // see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-14.0/first-class-span-types#span-conversions
1239+
1240+ switch ( fromType )
1241+ {
1242+ case ArrayType { Dimensions : 1 , ElementType : var elementType } :
1243+ if ( toType . IsKnownType ( KnownTypeCode . SpanOfT ) ||
1244+ toType . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) )
1245+ {
1246+ return ImplicitReferenceConversion ( elementType , toType . TypeArguments [ 0 ] , 0 )
1247+ || IdentityConversion ( elementType , toType . TypeArguments [ 0 ] ) ;
1248+ }
1249+ break ;
1250+ case ParameterizedType pt when pt . IsKnownType ( KnownTypeCode . SpanOfT ) || pt . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) :
1251+ if ( toType . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) )
1252+ {
1253+ return ImplicitReferenceConversion ( pt . TypeArguments [ 0 ] , toType . TypeArguments [ 0 ] , 0 )
1254+ || IdentityConversion ( pt . TypeArguments [ 0 ] , toType . TypeArguments [ 0 ] ) ;
1255+ }
1256+ break ;
1257+ case var s when s . IsKnownType ( KnownTypeCode . String ) :
1258+ return toType . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT )
1259+ && toType . TypeArguments [ 0 ] . IsKnownType ( KnownTypeCode . Char ) ;
1260+ }
1261+
1262+ return false ;
1263+ }
1264+
1265+ #endregion
1266+
12321267 #region AnonymousFunctionConversion
12331268 Conversion AnonymousFunctionConversion ( ResolveResult resolveResult , IType toType )
12341269 {
@@ -1487,11 +1522,30 @@ Conversion TupleConversion(IType fromType, IType toType, bool isExplicit)
14871522
14881523 #region BetterConversion
14891524 /// <summary>
1490- /// Gets the better conversion (C# 4 .0 spec, §7.5.3.3 )
1525+ /// Gets the better conversion (from expression) ( C# 8 .0 spec, §12.6.4.5 )
14911526 /// </summary>
14921527 /// <returns>0 = neither is better; 1 = t1 is better; 2 = t2 is better</returns>
14931528 public int BetterConversion ( ResolveResult resolveResult , IType t1 , IType t2 )
14941529 {
1530+ bool t1Exact = IsExactlyMatching ( resolveResult , t1 ) ;
1531+ bool t2Exact = IsExactlyMatching ( resolveResult , t2 ) ;
1532+ if ( t1Exact && ! t2Exact )
1533+ return 1 ;
1534+ if ( t2Exact && ! t1Exact )
1535+ return 2 ;
1536+ if ( ! t1Exact && ! t2Exact )
1537+ {
1538+ bool c1ImplicitSpanConversion = IsImplicitSpanConversion ( resolveResult . Type , t1 ) ;
1539+ bool c2ImplicitSpanConversion = IsImplicitSpanConversion ( resolveResult . Type , t2 ) ;
1540+ if ( c1ImplicitSpanConversion && ! c2ImplicitSpanConversion )
1541+ return 1 ;
1542+ if ( c2ImplicitSpanConversion && ! c1ImplicitSpanConversion )
1543+ return 2 ;
1544+ }
1545+ if ( t1Exact == t2Exact )
1546+ {
1547+ return BetterConversionTarget ( t1 , t2 ) ;
1548+ }
14951549 LambdaResolveResult lambda = resolveResult as LambdaResolveResult ;
14961550 if ( lambda != null )
14971551 {
@@ -1542,20 +1596,56 @@ public int BetterConversion(ResolveResult resolveResult, IType t1, IType t2)
15421596 }
15431597
15441598 /// <summary>
1545- /// Unpacks the generic Task[T]. Returns null if the input is not Task[T].
1599+ /// Gets whether an expression E exactly matches a type T (C# 8.0 spec, §12.6.4.6)
15461600 /// </summary>
1547- static IType UnpackTask ( IType type )
1601+ bool IsExactlyMatching ( ResolveResult e , IType t )
15481602 {
1549- ParameterizedType pt = type as ParameterizedType ;
1550- if ( pt != null && pt . TypeParameterCount == 1 && pt . Name == "Task" && pt . Namespace == "System.Threading.Tasks" )
1603+ var s = e . Type ;
1604+ if ( IdentityConversion ( s , t ) )
1605+ return true ;
1606+ if ( e is LambdaResolveResult lambda )
15511607 {
1552- return pt . GetTypeArgument ( 0 ) ;
1608+ if ( ! lambda . IsAnonymousMethod )
1609+ {
1610+ t = UnpackExpressionTreeType ( t ) ;
1611+ }
1612+ IMethod m = t . GetDelegateInvokeMethod ( ) ;
1613+ if ( m == null )
1614+ return false ;
1615+ IType [ ] parameterTypes = new IType [ m . Parameters . Count ] ;
1616+ for ( int i = 0 ; i < parameterTypes . Length ; i ++ )
1617+ parameterTypes [ i ] = m . Parameters [ i ] . Type ;
1618+ var x = lambda . GetInferredReturnType ( parameterTypes ) ;
1619+ var y = m . ReturnType ;
1620+ if ( IdentityConversion ( x , y ) )
1621+ return true ;
1622+ if ( lambda . IsAsync )
1623+ {
1624+ x = UnpackTask ( x ) ;
1625+ y = UnpackTask ( y ) ;
1626+ }
1627+ if ( x != null && y != null )
1628+ return IsExactlyMatching ( new ResolveResult ( x ) , y ) ;
1629+ return false ;
1630+ }
1631+ else
1632+ {
1633+ return false ;
15531634 }
1554- return null ;
15551635 }
15561636
15571637 /// <summary>
1558- /// Gets the better conversion (C# 4.0 spec, §7.5.3.4)
1638+ /// Unpacks the generic TaskType[T]. Returns null if the input is not TaskType[T].
1639+ /// </summary>
1640+ static IType UnpackTask ( IType type )
1641+ {
1642+ return ( TaskType . IsTask ( type ) || TaskType . IsCustomTask ( type , out _ ) ) && type . TypeParameterCount == 1
1643+ ? type . TypeArguments [ 0 ]
1644+ : null ;
1645+ }
1646+
1647+ /// <summary>
1648+ /// Gets the better conversion (from type) (C# 4.0 spec, §7.5.3.4)
15591649 /// </summary>
15601650 /// <returns>0 = neither is better; 1 = t1 is better; 2 = t2 is better</returns>
15611651 public int BetterConversion ( IType s , IType t1 , IType t2 )
@@ -1570,17 +1660,57 @@ public int BetterConversion(IType s, IType t1, IType t2)
15701660 }
15711661
15721662 /// <summary>
1573- /// Gets the better conversion target (C# 4 .0 spec, §7.5.3.5 )
1663+ /// Gets the better conversion target (C# 9 .0 spec, §12.6.4.7 )
15741664 /// </summary>
15751665 /// <returns>0 = neither is better; 1 = t1 is better; 2 = t2 is better</returns>
15761666 int BetterConversionTarget ( IType t1 , IType t2 )
15771667 {
1578- bool t1To2 = ImplicitConversion ( t1 , t2 ) . IsValid ;
1579- bool t2To1 = ImplicitConversion ( t2 , t1 ) . IsValid ;
1580- if ( t1To2 && ! t2To1 )
1581- return 1 ;
1582- if ( t2To1 && ! t1To2 )
1583- return 2 ;
1668+ if ( t1 . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) )
1669+ {
1670+ if ( t2 . IsKnownType ( KnownTypeCode . SpanOfT ) )
1671+ {
1672+ if ( IdentityConversion ( t1 . TypeArguments [ 0 ] , t2 . TypeArguments [ 0 ] ) )
1673+ return 1 ;
1674+ }
1675+ if ( t2 . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) )
1676+ {
1677+ bool t1To2 = ImplicitConversion ( t1 . TypeArguments [ 0 ] , t2 . TypeArguments [ 0 ] ) . IsValid ;
1678+ bool t2To1 = ImplicitConversion ( t2 . TypeArguments [ 0 ] , t1 . TypeArguments [ 0 ] ) . IsValid ;
1679+ if ( t1To2 && ! t2To1 )
1680+ return 1 ;
1681+ }
1682+ }
1683+
1684+ if ( t2 . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) )
1685+ {
1686+ if ( t1 . IsKnownType ( KnownTypeCode . SpanOfT ) )
1687+ {
1688+ if ( IdentityConversion ( t2 . TypeArguments [ 0 ] , t1 . TypeArguments [ 0 ] ) )
1689+ return 2 ;
1690+ }
1691+ if ( t1 . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) )
1692+ {
1693+ bool t1To2 = ImplicitConversion ( t1 . TypeArguments [ 0 ] , t2 . TypeArguments [ 0 ] ) . IsValid ;
1694+ bool t2To1 = ImplicitConversion ( t2 . TypeArguments [ 0 ] , t1 . TypeArguments [ 0 ] ) . IsValid ;
1695+ if ( t2To1 && ! t1To2 )
1696+ return 2 ;
1697+ }
1698+ }
1699+
1700+ {
1701+ bool t1To2 = ImplicitConversion ( t1 , t2 ) . IsValid ;
1702+ bool t2To1 = ImplicitConversion ( t2 , t1 ) . IsValid ;
1703+ if ( t1To2 && ! t2To1 )
1704+ return 1 ;
1705+ if ( t2To1 && ! t1To2 )
1706+ return 2 ;
1707+ }
1708+
1709+ var s1 = UnpackTask ( t1 ) ;
1710+ var s2 = UnpackTask ( t2 ) ;
1711+ if ( s1 != null && s2 != null )
1712+ return BetterConversionTarget ( s1 , s2 ) ;
1713+
15841714 TypeCode t1Code = ReflectionHelper . GetTypeCode ( t1 ) ;
15851715 TypeCode t2Code = ReflectionHelper . GetTypeCode ( t2 ) ;
15861716 if ( IsBetterIntegralType ( t1Code , t2Code ) )
0 commit comments