Skip to content

Commit 5842d53

Browse files
WIP
1 parent b45eaca commit 5842d53

File tree

8 files changed

+184
-22
lines changed

8 files changed

+184
-22
lines changed

ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ public async Task Records([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerO
538538
[Test]
539539
public async Task ExtensionProperties([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
540540
{
541-
await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview);
541+
await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview | CompilerOptions.NullableEnable);
542542
}
543543

544544
[Test]

ICSharpCode.Decompiler/CSharp/CallBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1438,7 +1438,7 @@ private ExpressionWithResolveResult HandleImplicitConversion(IMethod method, Tra
14381438
var conversions = CSharpConversions.Get(expressionBuilder.compilation);
14391439
IType targetType = method.ReturnType;
14401440
var conv = conversions.ImplicitConversion(argument.Type, targetType);
1441-
if (!(conv.IsUserDefined && conv.IsValid && conv.Method.Equals(method)))
1441+
if (!(conv.IsUserDefined && conv.IsValid && conv.Method.Equals(method, NormalizeTypeVisitor.IgnoreNullabilityAndTuples)))
14421442
{
14431443
// implicit conversion to targetType isn't directly possible, so first insert a cast to the argument type
14441444
argument = argument.ConvertTo(method.Parameters[0].Type, expressionBuilder);

ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs

Lines changed: 145 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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))

ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2171,7 +2171,8 @@ static bool IsEligibleExtensionMethod(ICompilation compilation, CSharpConversion
21712171
thisParameterType = thisParameterType.AcceptVisitor(substitution);
21722172
}
21732173
Conversion c = conversions.ImplicitConversion(targetType, thisParameterType);
2174-
return c.IsValid && (c.IsIdentityConversion || c.IsReferenceConversion || c.IsBoxingConversion);
2174+
return (c.IsValid && (c.IsIdentityConversion || c.IsReferenceConversion || c.IsBoxingConversion))
2175+
|| conversions.IsImplicitSpanConversion(targetType, thisParameterType);
21752176
}
21762177

21772178
/// <summary>

ICSharpCode.Decompiler/CSharp/Resolver/Log.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
2929
/// </summary>
3030
static class Log
3131
{
32-
const bool logEnabled = false;
32+
const bool logEnabled = true;
3333
#if __MonoCS__
3434
[Conditional("MCS_DEBUG")]
3535
#else

ICSharpCode.Decompiler/CSharp/Resolver/MemberLookup.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,32 @@ public bool IsAccessible(IEntity entity, bool allowProtectedAccess)
103103
{
104104
if (entity == null)
105105
throw new ArgumentNullException(nameof(entity));
106+
107+
var entityModule = entity.ParentModule.MetadataFile;
108+
var sourceModule = currentModule.MetadataFile;
109+
110+
if (entityModule != sourceModule)
111+
{
112+
113+
bool found = false;
114+
115+
foreach (var item in sourceModule.AssemblyReferences)
116+
{
117+
if (item.Name == entityModule.Name)
118+
{
119+
found = true;
120+
break;
121+
}
122+
}
123+
124+
if (!found)
125+
{
126+
// The entity is defined in an assembly that is not referenced by the current assembly.
127+
// Therefore, it is not accessible.
128+
return false;
129+
}
130+
}
131+
106132
// C# 4.0 spec, §3.5.2 Accessiblity domains
107133
switch (entity.Accessibility)
108134
{

ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,11 +377,12 @@ public void AddMethodLists(IReadOnlyList<MethodListWithDeclaringType> methodList
377377
internal void LogCandidateAddingResult(string text, IParameterizedMember method, OverloadResolutionErrors errors)
378378
{
379379
#if DEBUG
380-
Log.WriteLine(string.Format("{0} {1} = {2}{3}",
380+
Log.WriteLine(string.Format("{0} {1} from {4} = {2}{3}",
381381
text, method,
382382
errors == OverloadResolutionErrors.None ? "Success" : errors.ToString(),
383383
this.BestCandidate == method ? " (best candidate so far)" :
384-
this.BestCandidateAmbiguousWith == method ? " (ambiguous)" : ""
384+
this.BestCandidateAmbiguousWith == method ? " (ambiguous)" : "",
385+
method.ParentModule.AssemblyName
385386
));
386387
#endif
387388
}
@@ -711,7 +712,7 @@ void CheckApplicability(Candidate candidate)
711712
if (IsExtensionMethodInvocation && parameterIndex == 0)
712713
{
713714
// First parameter to extension method must be an identity, reference or boxing conversion
714-
if (!(c == Conversion.IdentityConversion || c == Conversion.ImplicitReferenceConversion || c == Conversion.BoxingConversion))
715+
if (!(c == Conversion.IdentityConversion || c == Conversion.ImplicitReferenceConversion || c == Conversion.BoxingConversion || (c.IsUserDefined && conversions.IsImplicitSpanConversion(arguments[i].Type, parameterType))))
715716
candidate.AddError(OverloadResolutionErrors.ArgumentTypeMismatch);
716717
}
717718
else

ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,10 @@ bool CastCanBeMadeImplicit(Resolver.CSharpConversions conversions, Conversion co
650650
return newTargetType.IsKnownType(KnownTypeCode.FormattableString)
651651
|| newTargetType.IsKnownType(KnownTypeCode.IFormattable);
652652
}
653+
if (conversion.IsUserDefined)
654+
{
655+
return conversions.IsImplicitSpanConversion(inputType, newTargetType);
656+
}
653657
return conversions.IdentityConversion(oldTargetType, newTargetType);
654658
}
655659

0 commit comments

Comments
 (0)