diff --git a/ICSharpCode.Decompiler.Tests/Semantics/TypeInferenceTests.cs b/ICSharpCode.Decompiler.Tests/Semantics/TypeInferenceTests.cs
index 84bb4758a..0b40a02a1 100644
--- a/ICSharpCode.Decompiler.Tests/Semantics/TypeInferenceTests.cs
+++ b/ICSharpCode.Decompiler.Tests/Semantics/TypeInferenceTests.cs
@@ -1194,6 +1194,21 @@ namespace ICSharpCode.Decompiler.Tests.Semantics
Assert.That(success);
}
+ [Test]
+ public void BestCommonTypeNullAndInt()
+ {
+ Assert.That(
+ ti.GetBestCommonType(new[] {
+ new ResolveResult(SpecialType.NullType),
+ new ResolveResult(compilation.FindType(KnownTypeCode.Int32))
+ }, out bool success),
+ Is.EqualTo(compilation.FindType(KnownTypeCode.Int32)));
+ // By my read of the C# spec, the best common type is really the non-nullable `int`.
+ // It's only a following step that will report an error if the argument expressions
+ // are not convertible to the common type.
+ Assert.That(success);
+ }
+
[Test]
public void BestCommonTypeStringAndObject()
{
@@ -1206,6 +1221,30 @@ namespace ICSharpCode.Decompiler.Tests.Semantics
Is.EqualTo(compilation.FindType(KnownTypeCode.Object)));
Assert.That(success);
}
+
+ [Test]
+ public void BestCommonTypeStringAndDynamic()
+ {
+ Assert.That(
+ ti.GetBestCommonType(new[] {
+ new ResolveResult(compilation.FindType(KnownTypeCode.String)),
+ new ResolveResult(SpecialType.Dynamic)
+ }, out bool success),
+ Is.EqualTo(SpecialType.Dynamic));
+ Assert.That(success);
+ }
+
+ [Test]
+ public void BestCommonTypeObjectAndDynamic()
+ {
+ Assert.That(
+ ti.GetBestCommonType(new[] {
+ new ResolveResult(compilation.FindType(KnownTypeCode.Object)),
+ new ResolveResult(SpecialType.Dynamic)
+ }, out bool success),
+ Is.EqualTo(SpecialType.Dynamic));
+ Assert.That(success);
+ }
#endregion
#region FindTypeInBounds
diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs b/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs
index 0e07ca7ba..62e80ddc8 100644
--- a/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs
+++ b/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs
@@ -251,7 +251,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
return;
// Two exact bounds that differ only in tuple element names are not conflicting;
// their names are merged instead (kept where both agree, dropped otherwise).
- IType merged = MergeTupleNames(ExactBound, type);
+ IType merged = MergeSimilarTypes(ExactBound, type);
if (merged != null)
ExactBound = merged;
else
@@ -987,7 +987,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
// MergeTupleNames in Roslyn's MethodTypeInference.cs.
IType fixedTo = tp.ExactBound;
foreach (var b in tp.LowerBounds.Concat(tp.UpperBounds))
- fixedTo = MergeTupleNames(fixedTo, b) ?? fixedTo;
+ fixedTo = MergeSimilarTypes(fixedTo, b) ?? fixedTo;
// the exact bound determines the result, up to the merged element names
tp.FixedTo = fixedTo;
// check validity
@@ -1018,11 +1018,12 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
}
///
- /// Merges the tuple element names of two types that are equal apart from those names:
- /// a name is kept where both sides agree and dropped where they conflict. Returns
- /// null if the types differ in anything else.
+ /// Merges similar types that differ only in tuple element names and/or object/dynamic, recursively.
+ /// * for tuple element names, a name is kept where both sides agree and dropped where they conflict.
+ /// * for object/dynamic, dynamic is preferred over object.
+ /// Returns null if the types differ in any other aspects.
///
- static IType MergeTupleNames(IType a, IType b)
+ static IType MergeSimilarTypes(IType a, IType b)
{
if (a.Equals(b))
return a;
@@ -1032,16 +1033,24 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
return null;
if (a is NullabilityAnnotatedType na && b is NullabilityAnnotatedType nb)
{
- return MergeTupleNames(na.TypeWithoutAnnotation, nb.TypeWithoutAnnotation)
+ return MergeSimilarTypes(na.TypeWithoutAnnotation, nb.TypeWithoutAnnotation)
?.ChangeNullability(a.Nullability);
}
+ if (a.Kind == TypeKind.Dynamic && b.IsKnownType(KnownTypeCode.Object))
+ {
+ return a;
+ }
+ if (b.Kind == TypeKind.Dynamic && a.IsKnownType(KnownTypeCode.Object))
+ {
+ return b;
+ }
if (a is TupleType ta && b is TupleType tb
&& ta.ElementTypes.Length == tb.ElementTypes.Length)
{
var mergedElements = ImmutableArray.CreateBuilder(ta.ElementTypes.Length);
for (int i = 0; i < ta.ElementTypes.Length; i++)
{
- var merged = MergeTupleNames(ta.ElementTypes[i], tb.ElementTypes[i]);
+ var merged = MergeSimilarTypes(ta.ElementTypes[i], tb.ElementTypes[i]);
if (merged == null)
return null;
mergedElements.Add(merged);
@@ -1061,7 +1070,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
var mergedArgs = new IType[pa.TypeArguments.Count];
for (int i = 0; i < pa.TypeArguments.Count; i++)
{
- var merged = MergeTupleNames(pa.TypeArguments[i], pb.TypeArguments[i]);
+ var merged = MergeSimilarTypes(pa.TypeArguments[i], pb.TypeArguments[i]);
if (merged == null)
return null;
mergedArgs[i] = merged;
@@ -1070,7 +1079,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
}
if (a is ArrayType arrA && b is ArrayType arrB && arrA.Dimensions == arrB.Dimensions)
{
- var mergedElem = MergeTupleNames(arrA.ElementType, arrB.ElementType);
+ var mergedElem = MergeSimilarTypes(arrA.ElementType, arrB.ElementType);
if (mergedElem == null)
return null;
return new ArrayType(arrA.Compilation, mergedElem, arrA.Dimensions, arrA.Nullability);
@@ -1080,7 +1089,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
///
/// Collapses bounds that are equal modulo tuple element names (possibly nested) into a
- /// single merged type via , across both bound sets. Bounds
+ /// single merged type via , across both bound sets. Bounds
/// without a shape-equivalent partner are returned as-is.
///
static (IReadOnlyList LowerBounds, IReadOnlyList UpperBounds) MergeShapeEquivalentBounds(
@@ -1095,7 +1104,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
bool absorbed = false;
for (int i = 0; i < mergedBounds.Count && !absorbed; i++)
{
- IType merged = MergeTupleNames(mergedBounds[i], bound);
+ IType merged = MergeSimilarTypes(mergedBounds[i], bound);
if (merged != null)
{
// Bounds that are already equal merge to the existing entry itself;
@@ -1120,7 +1129,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
///
static IType[] MapToMergedBounds(IEnumerable bounds, List mergedBounds)
{
- return bounds.Select(b => mergedBounds.First(m => MergeTupleNames(m, b) != null)).Distinct().ToArray();
+ return bounds.Select(b => mergedBounds.First(m => MergeSimilarTypes(m, b) != null)).Distinct().ToArray();
}
#endregion