@@ -247,6 +247,10 @@ Conversion StandardImplicitConversion(IType fromType, IType toType, bool allowTu
247247 if ( IdentityConversion ( elementType , spanElementType ) )
248248 return Conversion . InlineArrayConversion ;
249249 }
250+ if ( IsImplicitSpanConversion ( fromType , toType ) )
251+ {
252+ return Conversion . ImplicitSpanConversion ;
253+ }
250254 return Conversion . None ;
251255 }
252256
@@ -1229,6 +1233,49 @@ List<OperatorInfo> GetApplicableConversionOperators(ResolveResult fromResult, IT
12291233 }
12301234 #endregion
12311235
1236+ #region Implicit Span Conversion
1237+
1238+ bool IsImplicitSpanConversion ( IType fromType , IType toType )
1239+ {
1240+ if ( ! compilation . TypeSystemOptions . HasFlag ( TypeSystemOptions . FirstClassSpanTypes ) )
1241+ {
1242+ return false ;
1243+ }
1244+
1245+ // An implicit span conversion permits array_types, System.Span<T>, System.ReadOnlySpan<T>,
1246+ // and string to be converted between each other
1247+ // see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-14.0/first-class-span-types#span-conversions
1248+
1249+ switch ( fromType )
1250+ {
1251+ case ArrayType { Dimensions : 1 , ElementType : var elementType } :
1252+ if ( toType . IsKnownType ( KnownTypeCode . SpanOfT ) )
1253+ {
1254+ return IdentityConversion ( elementType , toType . TypeArguments [ 0 ] ) ;
1255+ }
1256+ if ( toType . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) )
1257+ {
1258+ return IdentityConversion ( elementType , toType . TypeArguments [ 0 ] )
1259+ || IsImplicitReferenceConversion ( elementType , toType . TypeArguments [ 0 ] ) ;
1260+ }
1261+ break ;
1262+ case ParameterizedType pt when pt . IsKnownType ( KnownTypeCode . SpanOfT ) || pt . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) :
1263+ if ( toType . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) )
1264+ {
1265+ return IdentityConversion ( pt . TypeArguments [ 0 ] , toType . TypeArguments [ 0 ] )
1266+ || IsImplicitReferenceConversion ( pt . TypeArguments [ 0 ] , toType . TypeArguments [ 0 ] ) ;
1267+ }
1268+ break ;
1269+ case var s when s . IsKnownType ( KnownTypeCode . String ) :
1270+ return toType . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT )
1271+ && toType . TypeArguments [ 0 ] . IsKnownType ( KnownTypeCode . Char ) ;
1272+ }
1273+
1274+ return false ;
1275+ }
1276+
1277+ #endregion
1278+
12321279 #region AnonymousFunctionConversion
12331280 Conversion AnonymousFunctionConversion ( ResolveResult resolveResult , IType toType )
12341281 {
@@ -1487,11 +1534,32 @@ Conversion TupleConversion(IType fromType, IType toType, bool isExplicit)
14871534
14881535 #region BetterConversion
14891536 /// <summary>
1490- /// Gets the better conversion (C# 4 .0 spec, §7.5.3.3 )
1537+ /// Gets the better conversion (from expression) ( C# 8 .0 spec, §12.6.4.5 )
14911538 /// </summary>
14921539 /// <returns>0 = neither is better; 1 = t1 is better; 2 = t2 is better</returns>
14931540 public int BetterConversion ( ResolveResult resolveResult , IType t1 , IType t2 )
14941541 {
1542+ bool t1Exact = IsExactlyMatching ( resolveResult , t1 ) ;
1543+ bool t2Exact = IsExactlyMatching ( resolveResult , t2 ) ;
1544+ if ( t1Exact && ! t2Exact )
1545+ return 1 ;
1546+ if ( t2Exact && ! t1Exact )
1547+ return 2 ;
1548+ if ( ! t1Exact && ! t2Exact )
1549+ {
1550+ bool c1ImplicitSpanConversion = IsImplicitSpanConversion ( resolveResult . Type , t1 ) ;
1551+ bool c2ImplicitSpanConversion = IsImplicitSpanConversion ( resolveResult . Type , t2 ) ;
1552+ if ( c1ImplicitSpanConversion && ! c2ImplicitSpanConversion )
1553+ return 1 ;
1554+ if ( c2ImplicitSpanConversion && ! c1ImplicitSpanConversion )
1555+ return 2 ;
1556+ }
1557+ if ( t1Exact == t2Exact )
1558+ {
1559+ int r = BetterConversionTarget ( t1 , t2 ) ;
1560+ if ( r != 0 )
1561+ return r ;
1562+ }
14951563 LambdaResolveResult lambda = resolveResult as LambdaResolveResult ;
14961564 if ( lambda != null )
14971565 {
@@ -1542,20 +1610,56 @@ public int BetterConversion(ResolveResult resolveResult, IType t1, IType t2)
15421610 }
15431611
15441612 /// <summary>
1545- /// Unpacks the generic Task[T]. Returns null if the input is not Task[T].
1613+ /// Gets whether an expression E exactly matches a type T (C# 8.0 spec, §12.6.4.6)
15461614 /// </summary>
1547- static IType UnpackTask ( IType type )
1615+ bool IsExactlyMatching ( ResolveResult e , IType t )
15481616 {
1549- ParameterizedType pt = type as ParameterizedType ;
1550- if ( pt != null && pt . TypeParameterCount == 1 && pt . Name == "Task" && pt . Namespace == "System.Threading.Tasks" )
1617+ var s = e . Type ;
1618+ if ( IdentityConversion ( s , t ) )
1619+ return true ;
1620+ if ( e is LambdaResolveResult lambda )
15511621 {
1552- return pt . GetTypeArgument ( 0 ) ;
1622+ if ( ! lambda . IsAnonymousMethod )
1623+ {
1624+ t = UnpackExpressionTreeType ( t ) ;
1625+ }
1626+ IMethod m = t . GetDelegateInvokeMethod ( ) ;
1627+ if ( m == null )
1628+ return false ;
1629+ IType [ ] parameterTypes = new IType [ m . Parameters . Count ] ;
1630+ for ( int i = 0 ; i < parameterTypes . Length ; i ++ )
1631+ parameterTypes [ i ] = m . Parameters [ i ] . Type ;
1632+ var x = lambda . GetInferredReturnType ( parameterTypes ) ;
1633+ var y = m . ReturnType ;
1634+ if ( IdentityConversion ( x , y ) )
1635+ return true ;
1636+ if ( lambda . IsAsync )
1637+ {
1638+ x = UnpackTask ( x ) ;
1639+ y = UnpackTask ( y ) ;
1640+ }
1641+ if ( x != null && y != null )
1642+ return IsExactlyMatching ( new ResolveResult ( x ) , y ) ;
1643+ return false ;
1644+ }
1645+ else
1646+ {
1647+ return false ;
15531648 }
1554- return null ;
15551649 }
15561650
15571651 /// <summary>
1558- /// Gets the better conversion (C# 4.0 spec, §7.5.3.4)
1652+ /// Unpacks the generic TaskType[T]. Returns null if the input is not TaskType[T].
1653+ /// </summary>
1654+ static IType UnpackTask ( IType type )
1655+ {
1656+ return ( TaskType . IsTask ( type ) || TaskType . IsCustomTask ( type , out _ ) ) && type . TypeParameterCount == 1
1657+ ? type . TypeArguments [ 0 ]
1658+ : null ;
1659+ }
1660+
1661+ /// <summary>
1662+ /// Gets the better conversion (from type) (C# 4.0 spec, §7.5.3.4)
15591663 /// </summary>
15601664 /// <returns>0 = neither is better; 1 = t1 is better; 2 = t2 is better</returns>
15611665 public int BetterConversion ( IType s , IType t1 , IType t2 )
@@ -1570,17 +1674,57 @@ public int BetterConversion(IType s, IType t1, IType t2)
15701674 }
15711675
15721676 /// <summary>
1573- /// Gets the better conversion target (C# 4 .0 spec, §7.5.3.5 )
1677+ /// Gets the better conversion target (C# 9 .0 spec, §12.6.4.7 )
15741678 /// </summary>
15751679 /// <returns>0 = neither is better; 1 = t1 is better; 2 = t2 is better</returns>
15761680 int BetterConversionTarget ( IType t1 , IType t2 )
15771681 {
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 ;
1682+ if ( t1 . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) )
1683+ {
1684+ if ( t2 . IsKnownType ( KnownTypeCode . SpanOfT ) )
1685+ {
1686+ if ( IdentityConversion ( t1 . TypeArguments [ 0 ] , t2 . TypeArguments [ 0 ] ) )
1687+ return 1 ;
1688+ }
1689+ if ( t2 . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) )
1690+ {
1691+ bool t1To2 = ImplicitConversion ( t1 . TypeArguments [ 0 ] , t2 . TypeArguments [ 0 ] ) . IsValid ;
1692+ bool t2To1 = ImplicitConversion ( t2 . TypeArguments [ 0 ] , t1 . TypeArguments [ 0 ] ) . IsValid ;
1693+ if ( t1To2 && ! t2To1 )
1694+ return 1 ;
1695+ }
1696+ }
1697+
1698+ if ( t2 . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) )
1699+ {
1700+ if ( t1 . IsKnownType ( KnownTypeCode . SpanOfT ) )
1701+ {
1702+ if ( IdentityConversion ( t2 . TypeArguments [ 0 ] , t1 . TypeArguments [ 0 ] ) )
1703+ return 2 ;
1704+ }
1705+ if ( t1 . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) )
1706+ {
1707+ bool t1To2 = ImplicitConversion ( t1 . TypeArguments [ 0 ] , t2 . TypeArguments [ 0 ] ) . IsValid ;
1708+ bool t2To1 = ImplicitConversion ( t2 . TypeArguments [ 0 ] , t1 . TypeArguments [ 0 ] ) . IsValid ;
1709+ if ( t2To1 && ! t1To2 )
1710+ return 2 ;
1711+ }
1712+ }
1713+
1714+ {
1715+ bool t1To2 = ImplicitConversion ( t1 , t2 ) . IsValid ;
1716+ bool t2To1 = ImplicitConversion ( t2 , t1 ) . IsValid ;
1717+ if ( t1To2 && ! t2To1 )
1718+ return 1 ;
1719+ if ( t2To1 && ! t1To2 )
1720+ return 2 ;
1721+ }
1722+
1723+ var s1 = UnpackTask ( t1 ) ;
1724+ var s2 = UnpackTask ( t2 ) ;
1725+ if ( s1 != null && s2 != null )
1726+ return BetterConversionTarget ( s1 , s2 ) ;
1727+
15841728 TypeCode t1Code = ReflectionHelper . GetTypeCode ( t1 ) ;
15851729 TypeCode t2Code = ReflectionHelper . GetTypeCode ( t2 ) ;
15861730 if ( IsBetterIntegralType ( t1Code , t2Code ) )
0 commit comments