Skip to content

Commit 6955704

Browse files
1 parent 235ae52 commit 6955704

File tree

2 files changed

+56
-31
lines changed

2 files changed

+56
-31
lines changed

ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,16 @@ public void NullableConversions2()
175175
Assert.That(ImplicitConversion(typeof(float?), typeof(decimal?)), Is.EqualTo(C.None));
176176
}
177177

178+
[Test]
179+
public void NullableEnumerationConversion()
180+
{
181+
ResolveResult zero = new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), 0);
182+
ResolveResult one = new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), 1);
183+
C implicitEnumerationConversion = C.EnumerationConversion(true, true);
184+
Assert.That(conversions.ImplicitConversion(zero, compilation.FindType(typeof(StringComparison?))), Is.EqualTo(implicitEnumerationConversion));
185+
Assert.That(conversions.ImplicitConversion(one, compilation.FindType(typeof(StringComparison?))), Is.EqualTo(C.None));
186+
}
187+
178188
[Test]
179189
public void NullLiteralConversions()
180190
{

ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ private Conversion ImplicitConversion(ResolveResult resolveResult, IType toType,
109109
if (ImplicitConstantExpressionConversion(resolveResult, toType))
110110
return Conversion.ImplicitConstantExpressionConversion;
111111
}
112+
// C# 9.0 spec: §10.2.5
112113
if (resolveResult is InterpolatedStringResolveResult)
113114
{
114115
if (toType.IsKnownType(KnownTypeCode.IFormattable) || toType.IsKnownType(KnownTypeCode.FormattableString))
@@ -122,6 +123,8 @@ private Conversion ImplicitConversion(ResolveResult resolveResult, IType toType,
122123
c = MethodGroupConversion(resolveResult, toType);
123124
if (c != Conversion.None)
124125
return c;
126+
// C# 9.0 spec: §10.2.16 default literal conversions
127+
// TODO
125128
if (resolveResult.IsCompileTimeConstant)
126129
{
127130
c = StandardImplicitConversion(resolveResult.Type, toType, allowTuple);
@@ -142,6 +145,7 @@ private Conversion ImplicitConversion(ResolveResult resolveResult, IType toType,
142145
if (c != Conversion.None)
143146
return c;
144147
}
148+
// C# 9.0 spec: §10.2.17
145149
if (resolveResult is ThrowResolveResult)
146150
{
147151
return Conversion.ThrowExpressionConversion;
@@ -205,7 +209,7 @@ public Conversion StandardImplicitConversion(IType fromType, IType toType)
205209

206210
Conversion StandardImplicitConversion(IType fromType, IType toType, bool allowTupleConversion)
207211
{
208-
// C# 4.0 spec: §6.3.1
212+
// C# 9.0 spec: §10.4.2
209213
if (IdentityConversion(fromType, toType))
210214
return Conversion.IdentityConversion;
211215
if (ImplicitNumericConversion(fromType, toType))
@@ -229,6 +233,8 @@ Conversion StandardImplicitConversion(IType fromType, IType toType, bool allowTu
229233
return Conversion.ImplicitPointerConversion;
230234
if (allowTupleConversion)
231235
{
236+
// TODO are tuple conversions really standard implicit conversions?
237+
// the C# 9.0 spec doesn't list them as standard implicit conversions.
232238
c = TupleConversion(fromType, toType, isExplicit: false);
233239
if (c != Conversion.None)
234240
return c;
@@ -380,7 +386,7 @@ public bool IdentityConversion(IType fromType, IType toType)
380386

381387
bool ImplicitNumericConversion(IType fromType, IType toType)
382388
{
383-
// C# 4.0 spec: §6.1.2
389+
// C# 9.0 spec: §10.2.3
384390

385391
TypeCode from = ReflectionHelper.GetTypeCode(fromType);
386392
if (from == TypeCode.Empty)
@@ -448,7 +454,7 @@ bool AnyNumericConversion(IType fromType, IType toType)
448454
#region Enumeration Conversions
449455
Conversion ImplicitEnumerationConversion(ResolveResult rr, IType toType)
450456
{
451-
// C# 4.0 spec: §6.1.3
457+
// C# 9.0 spec: §10.2.4 + enum part of §10.2.6 (Nullable conversions)
452458
Debug.Assert(rr.IsCompileTimeConstant);
453459
TypeCode constantType = ReflectionHelper.GetTypeCode(rr.Type);
454460
if (constantType >= TypeCode.SByte && constantType <= TypeCode.Decimal && Convert.ToDouble(rr.ConstantValue) == 0)
@@ -479,7 +485,7 @@ bool ExplicitEnumerationConversion(IType fromType, IType toType)
479485
#region Nullable Conversions
480486
Conversion ImplicitNullableConversion(IType fromType, IType toType)
481487
{
482-
// C# 4.0 spec: §6.1.4
488+
// C# 9.0 spec: §10.2.6
483489
if (NullableType.IsNullable(toType))
484490
{
485491
IType t = NullableType.GetUnderlyingType(toType);
@@ -513,7 +519,7 @@ Conversion ExplicitNullableConversion(IType fromType, IType toType)
513519
#region Null Literal Conversion
514520
bool NullLiteralConversion(IType fromType, IType toType)
515521
{
516-
// C# 4.0 spec: §6.1.5
522+
// C# 9.0 spec: §10.2.7
517523
if (fromType.Kind == TypeKind.Null)
518524
{
519525
return NullableType.IsNullable(toType) || toType.IsReferenceType == true;
@@ -533,7 +539,7 @@ public bool IsImplicitReferenceConversion(IType fromType, IType toType)
533539

534540
bool ImplicitReferenceConversion(IType fromType, IType toType, int subtypeCheckNestingDepth)
535541
{
536-
// C# 4.0 spec: §6.1.6
542+
// C# 9.0 spec: §10.2.8
537543

538544
// reference conversions are possible:
539545
// - if both types are known to be reference types
@@ -552,7 +558,7 @@ bool ImplicitReferenceConversion(IType fromType, IType toType, int subtypeCheckN
552558
return fromArray.Dimensions == toArray.Dimensions
553559
&& ImplicitReferenceConversion(fromArray.ElementType, toArray.ElementType, subtypeCheckNestingDepth);
554560
}
555-
// conversion from single-dimensional array S[] to IList<T>:
561+
// conversion from single-dimensional array S[] to IList<T>/IReadOnlyList<T> + base interfaces:
556562
IType toTypeArgument = UnpackGenericArrayInterface(toType);
557563
if (fromArray.Dimensions == 1 && toTypeArgument != null)
558564
{
@@ -570,20 +576,20 @@ bool ImplicitReferenceConversion(IType fromType, IType toType, int subtypeCheckN
570576
}
571577

572578
/// <summary>
573-
/// For IList{T}, ICollection{T}, IEnumerable{T} and IReadOnlyList{T}, returns T.
579+
/// For <see cref="IList{T}"/>, <see cref="ICollection{T}"/>, <see cref="IEnumerable{T}"/> and <see cref="IReadOnlyList{T}"/>, returns T.
574580
/// Otherwise, returns null.
575581
/// </summary>
576582
IType UnpackGenericArrayInterface(IType interfaceType)
577583
{
578-
ParameterizedType pt = interfaceType as ParameterizedType;
579-
if (pt != null)
584+
if (interfaceType is ParameterizedType pt)
580585
{
581586
switch (pt.GetDefinition()?.KnownTypeCode)
582587
{
583588
case KnownTypeCode.IListOfT:
584589
case KnownTypeCode.ICollectionOfT:
585590
case KnownTypeCode.IEnumerableOfT:
586591
case KnownTypeCode.IReadOnlyListOfT:
592+
case KnownTypeCode.IReadOnlyCollectionOfT:
587593
return pt.GetTypeArgument(0);
588594
}
589595
}
@@ -780,7 +786,7 @@ bool IsSealedReferenceType(IType type)
780786
#region Boxing Conversions
781787
bool IsBoxingConversion(IType fromType, IType toType)
782788
{
783-
// C# 4.0 spec: §6.1.7
789+
// C# 9.0 spec: §10.2.9
784790
fromType = NullableType.GetUnderlyingType(fromType);
785791
if (fromType.IsReferenceType == false && !fromType.IsByRefLike && toType.IsReferenceType == true)
786792
return IsSubtypeOf(fromType, toType, 0);
@@ -815,7 +821,7 @@ bool ImplicitConstantExpressionConversion(ResolveResult rr, IType toType)
815821
{
816822
if (rr == null || !rr.IsCompileTimeConstant)
817823
return false;
818-
// C# 4.0 spec: §6.1.9
824+
// C# 9.0 spec: §10.2.11 + part of §10.2.6 (Nullable conversions)
819825
TypeCode fromTypeCode = ReflectionHelper.GetTypeCode(rr.Type);
820826
toType = NullableType.GetUnderlyingType(toType);
821827
TypeCode toTypeCode = ReflectionHelper.GetTypeCode(toType);
@@ -859,10 +865,11 @@ bool ImplicitConstantExpressionConversion(ResolveResult rr, IType toType)
859865
/// </summary>
860866
bool ImplicitTypeParameterConversion(IType fromType, IType toType)
861867
{
868+
// C# 9.0 spec: §10.2.12
862869
if (fromType.Kind != TypeKind.TypeParameter)
863870
return false; // not a type parameter
864-
if (fromType.IsReferenceType == true)
865-
return false; // already handled by ImplicitReferenceConversion
871+
if (fromType.IsReferenceType.HasValue)
872+
return false; // already handled by ImplicitReferenceConversion/BoxingConversion
866873
return IsSubtypeOf(fromType, toType, 0);
867874
}
868875

@@ -1212,7 +1219,7 @@ List<OperatorInfo> GetApplicableConversionOperators(ResolveResult fromResult, IT
12121219
#region AnonymousFunctionConversion
12131220
Conversion AnonymousFunctionConversion(ResolveResult resolveResult, IType toType)
12141221
{
1215-
// C# 5.0 spec §6.5 Anonymous function conversions
1222+
// C# 9.0 spec §10.7 Anonymous function conversions
12161223
LambdaResolveResult f = resolveResult as LambdaResolveResult;
12171224
if (f == null)
12181225
return Conversion.None;
@@ -1242,6 +1249,7 @@ Conversion AnonymousFunctionConversion(ResolveResult resolveResult, IType toType
12421249
if (f.IsImplicitlyTyped)
12431250
{
12441251
// If F has an implicitly typed parameter list, D has no ref or out parameters.
1252+
// TODO: what about in parameters?
12451253
foreach (IParameter p in d.Parameters)
12461254
{
12471255
if (p.ReferenceKind != ReferenceKind.None)
@@ -1294,9 +1302,8 @@ static IType UnpackExpressionTreeType(IType type)
12941302
#region MethodGroupConversion
12951303
Conversion MethodGroupConversion(ResolveResult resolveResult, IType toType)
12961304
{
1297-
// C# 4.0 spec §6.6 Method group conversions
1298-
MethodGroupResolveResult rr = resolveResult as MethodGroupResolveResult;
1299-
if (rr == null)
1305+
// C# 9.0 spec §10.8 Method group conversions
1306+
if (resolveResult is not MethodGroupResolveResult rr)
13001307
return Conversion.None;
13011308
IMethod invoke = toType.GetDelegateInvokeMethod();
13021309
if (invoke == null)
@@ -1312,6 +1319,10 @@ Conversion MethodGroupConversion(ResolveResult resolveResult, IType toType)
13121319
parameterType = ((ByReferenceType)parameterType).ElementType;
13131320
args[i] = new ByReferenceResolveResult(parameterType, param.ReferenceKind);
13141321
}
1322+
else if (param.Type.Kind == TypeKind.Dynamic)
1323+
{
1324+
args[i] = new ResolveResult(compilation.FindType(KnownTypeCode.Object));
1325+
}
13151326
else
13161327
{
13171328
args[i] = new ResolveResult(parameterType);
@@ -1327,8 +1338,7 @@ Conversion MethodGroupConversion(ResolveResult resolveResult, IType toType)
13271338
if (or.FoundApplicableCandidate)
13281339
{
13291340
IMethod method = (IMethod)or.GetBestCandidateWithSubstitutedTypeArguments();
1330-
var thisRR = rr.TargetResult as ThisResolveResult;
1331-
bool isVirtual = method.IsOverridable && !(thisRR != null && thisRR.CausesNonVirtualInvocation);
1341+
bool isVirtual = method.IsOverridable && !(rr.TargetResult is ThisResolveResult { CausesNonVirtualInvocation: true });
13321342
bool isValid = !or.IsAmbiguous && IsDelegateCompatible(method, invoke, or.IsExtensionMethodInvocation);
13331343
bool delegateCapturesFirstArgument = or.IsExtensionMethodInvocation || !method.IsStatic;
13341344
if (isValid)
@@ -1362,32 +1372,33 @@ public bool IsDelegateCompatible(IMethod method, IType delegateType)
13621372

13631373
/// <summary>
13641374
/// Gets whether a method <paramref name="m"/> is compatible with a delegate type.
1365-
/// §15.2 Delegate compatibility
13661375
/// </summary>
13671376
/// <param name="m">The method to test for compatibility</param>
1368-
/// <param name="invoke">The invoke method of the delegate</param>
1377+
/// <param name="d">The invoke method of the delegate</param>
13691378
/// <param name="isExtensionMethodInvocation">Gets whether m is accessed using extension method syntax.
13701379
/// If this parameter is true, the first parameter of <paramref name="m"/> will be ignored.</param>
1371-
bool IsDelegateCompatible(IMethod m, IMethod invoke, bool isExtensionMethodInvocation)
1380+
bool IsDelegateCompatible(IMethod m, IMethod d, bool isExtensionMethodInvocation)
13721381
{
1382+
// C# 9.0 §20.4 Delegate compatibility
13731383
if (m == null)
13741384
throw new ArgumentNullException(nameof(m));
1375-
if (invoke == null)
1376-
throw new ArgumentNullException(nameof(invoke));
1385+
if (d == null)
1386+
throw new ArgumentNullException(nameof(d));
13771387
int firstParameterInM = isExtensionMethodInvocation ? 1 : 0;
1378-
if (m.Parameters.Count - firstParameterInM != invoke.Parameters.Count)
1388+
if (m.Parameters.Count - firstParameterInM != d.Parameters.Count)
13791389
return false;
1380-
for (int i = 0; i < invoke.Parameters.Count; i++)
1390+
for (int i = 0; i < d.Parameters.Count; i++)
13811391
{
13821392
var pm = m.Parameters[firstParameterInM + i];
1383-
var pd = invoke.Parameters[i];
1393+
var pd = d.Parameters[i];
13841394
// ret/out/in must match
13851395
if (pm.ReferenceKind != pd.ReferenceKind)
13861396
return false;
13871397
if (pm.ReferenceKind != ReferenceKind.None)
13881398
{
13891399
// ref/out/in parameters must have same types
1390-
if (!pm.Type.Equals(pd.Type))
1400+
// according to the spec, but Roslyn seems to allow identity conversions
1401+
if (!IdentityConversion(pd.Type, pm.Type))
13911402
return false;
13921403
}
13931404
else
@@ -1397,15 +1408,18 @@ bool IsDelegateCompatible(IMethod m, IMethod invoke, bool isExtensionMethodInvoc
13971408
return false;
13981409
}
13991410
}
1411+
if (m.ReturnTypeIsRefReadOnly != d.ReturnTypeIsRefReadOnly)
1412+
return false;
14001413
// check return type compatibility
1401-
return IdentityConversion(m.ReturnType, invoke.ReturnType)
1402-
|| IsImplicitReferenceConversion(m.ReturnType, invoke.ReturnType);
1414+
return IdentityConversion(m.ReturnType, d.ReturnType)
1415+
|| IsImplicitReferenceConversion(m.ReturnType, d.ReturnType);
14031416
}
14041417
#endregion
14051418

14061419
#region Tuple Conversion
14071420
Conversion TupleConversion(TupleResolveResult fromRR, IType toType, bool isExplicit)
14081421
{
1422+
// C# 9.0 spec: §10.2.13 (implicit tuple conversions) + $10.3.6 (explicit tuple conversions)
14091423
var fromElements = fromRR.Elements;
14101424
var toElements = TupleType.GetTupleElementTypes(toType);
14111425
if (toElements.IsDefault || fromElements.Length != toElements.Length)
@@ -1431,6 +1445,7 @@ Conversion TupleConversion(TupleResolveResult fromRR, IType toType, bool isExpli
14311445

14321446
Conversion TupleConversion(IType fromType, IType toType, bool isExplicit)
14331447
{
1448+
// C# 9.0 spec: §10.2.13 (implicit tuple conversions) + $10.3.6 (explicit tuple conversions)
14341449
var fromElements = TupleType.GetTupleElementTypes(fromType);
14351450
if (fromElements.IsDefaultOrEmpty)
14361451
return Conversion.None;

0 commit comments

Comments
 (0)