mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Merge pull request #4077 from icsharpcode/type-inference
Improvements for type inference
This commit is contained in:
@@ -23,6 +23,7 @@ using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
using ICSharpCode.Decompiler.CSharp.Resolver;
|
||||
using ICSharpCode.Decompiler.Metadata;
|
||||
@@ -713,6 +714,15 @@ namespace ICSharpCode.Decompiler.Tests.Semantics
|
||||
ImmutableArray.CreateRange(elementNames));
|
||||
}
|
||||
|
||||
FunctionPointerType MakeFunctionPointerType(ICompilation comp, IType returnType)
|
||||
{
|
||||
return new FunctionPointerType(
|
||||
(MetadataModule)comp.MainModule,
|
||||
SignatureCallingConvention.Default, ImmutableArray<IType>.Empty,
|
||||
returnType, returnIsRefReadOnly: false,
|
||||
ImmutableArray<IType>.Empty, ImmutableArray<ReferenceKind>.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BestCommonTypeMergesTupleElementNames()
|
||||
{
|
||||
@@ -730,6 +740,21 @@ namespace ICSharpCode.Decompiler.Tests.Semantics
|
||||
Assert.That(success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BestCommonTypeMergesFunctionPointerTupleElementNames()
|
||||
{
|
||||
var comp = RefAssemblyCompilation.Instance;
|
||||
var inference = new TypeInference(comp);
|
||||
|
||||
Assert.That(
|
||||
inference.GetBestCommonType(new[] {
|
||||
new ResolveResult(MakeFunctionPointerType(comp, MakeTupleType(comp, "a", "b"))),
|
||||
new ResolveResult(MakeFunctionPointerType(comp, MakeTupleType(comp, "a", "c")))
|
||||
}, out bool success),
|
||||
Is.EqualTo(MakeFunctionPointerType(comp, MakeTupleType(comp, "a", null))));
|
||||
Assert.That(success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FixingMergesTupleElementNamesOfExactAndLowerBounds()
|
||||
{
|
||||
@@ -854,25 +879,24 @@ namespace ICSharpCode.Decompiler.Tests.Semantics
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FixingDoesNotMergeBoundsThatDifferInNullability()
|
||||
public void FixingMergesBoundsThatDifferInNullability()
|
||||
{
|
||||
// Signature: M<T>(T x, T y)
|
||||
// Invocation: M(nullableArrayOfString, arrayOfString);
|
||||
// Merging nullability requires the variance of the position, which this
|
||||
// implementation does not track, so such bounds stay distinct and fixing fails
|
||||
// (csc infers string[]?).
|
||||
// Merging nullability in this covariant position should result in T=string[]? (the nullable array type).
|
||||
var comp = RefAssemblyCompilation.Instance;
|
||||
var T = new DefaultTypeParameter(comp, SymbolKind.Method, 0, "T");
|
||||
IType stringType = comp.FindType(KnownTypeCode.String);
|
||||
|
||||
new TypeInference(comp).InferTypeArguments(new ITypeParameter[] { T },
|
||||
new[] {
|
||||
var result = new TypeInference(comp).InferTypeArguments([T],
|
||||
[
|
||||
new ResolveResult(new ArrayType(comp, stringType, 1, Nullability.Nullable)),
|
||||
new ResolveResult(new ArrayType(comp, stringType))
|
||||
},
|
||||
new IType[] { T, T },
|
||||
],
|
||||
[T, T],
|
||||
out bool success);
|
||||
Assert.That(success, Is.False);
|
||||
Assert.That(success, Is.True);
|
||||
Assert.That(result, Is.EqualTo([new ArrayType(comp, stringType, 1, Nullability.Nullable)]));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -1245,6 +1269,42 @@ namespace ICSharpCode.Decompiler.Tests.Semantics
|
||||
Is.EqualTo(SpecialType.Dynamic));
|
||||
Assert.That(success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BestCommonTypeDynamicAndObject()
|
||||
{
|
||||
Assert.That(
|
||||
ti.GetBestCommonType(new[] {
|
||||
new ResolveResult(SpecialType.Dynamic),
|
||||
new ResolveResult(compilation.FindType(KnownTypeCode.Object))
|
||||
}, out bool success),
|
||||
Is.EqualTo(SpecialType.Dynamic));
|
||||
Assert.That(success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BestCommonTypeObjectAndNullableString()
|
||||
{
|
||||
Assert.That(
|
||||
ti.GetBestCommonType([
|
||||
new ResolveResult(compilation.FindType(KnownTypeCode.Object).ChangeNullability(Nullability.NotNullable)),
|
||||
new ResolveResult(compilation.FindType(KnownTypeCode.String).ChangeNullability(Nullability.Nullable))
|
||||
], out bool success),
|
||||
Is.EqualTo(compilation.FindType(KnownTypeCode.Object).ChangeNullability(Nullability.Nullable)));
|
||||
Assert.That(success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BestCommonTypeObjectAndNullableObject()
|
||||
{
|
||||
Assert.That(
|
||||
ti.GetBestCommonType([
|
||||
new ResolveResult(compilation.FindType(KnownTypeCode.Object).ChangeNullability(Nullability.NotNullable)),
|
||||
new ResolveResult(compilation.FindType(KnownTypeCode.Object).ChangeNullability(Nullability.Nullable))
|
||||
], out bool success),
|
||||
Is.EqualTo(compilation.FindType(KnownTypeCode.Object).ChangeNullability(Nullability.Nullable)));
|
||||
Assert.That(success);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region FindTypeInBounds
|
||||
@@ -1387,8 +1447,12 @@ namespace ICSharpCode.Decompiler.Tests.Semantics
|
||||
// ReadOnlyCollectionBuilder<T> appears because the test compilation includes
|
||||
// System.Core, which declares it as another public implementation of both
|
||||
// IList and IList<T>.
|
||||
var typesInBounds = FindAllTypesInBounds(Resolve(), Resolve(typeof(IEnumerable<ICloneable>), typeof(IEnumerable<IComparable>), typeof(IList)));
|
||||
// As this finds all derived types, the result set contains compiler-generated types like <>z__ReadOnlyArray`1.
|
||||
// We filter those out to make the test more robust against changes.
|
||||
typesInBounds = typesInBounds.Where(t => !t.GetDefinition().IsCompilerGenerated()).ToArray();
|
||||
Assert.That(
|
||||
FindAllTypesInBounds(Resolve(), Resolve(typeof(IEnumerable<ICloneable>), typeof(IEnumerable<IComparable>), typeof(IList))),
|
||||
typesInBounds,
|
||||
Is.EqualTo(Resolve(typeof(List<string>), typeof(List<Version>), typeof(Collection<string>), typeof(Collection<Version>), typeof(ReadOnlyCollection<string>), typeof(ReadOnlyCollection<Version>), typeof(System.Runtime.CompilerServices.ReadOnlyCollectionBuilder<string>), typeof(System.Runtime.CompilerServices.ReadOnlyCollectionBuilder<Version>))));
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
||||
{
|
||||
@@ -207,4 +208,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public class T09_Linq
|
||||
{
|
||||
public IEnumerable<string> QueryWithNonNullableReferenceTypes(IEnumerable<string> strings)
|
||||
{
|
||||
return from s in strings
|
||||
where s.Length > 0
|
||||
select s.ToUpper();
|
||||
}
|
||||
|
||||
public IEnumerable<string> QueryWithNullableReferenceTypes(IEnumerable<string?> strings)
|
||||
{
|
||||
return from s in strings
|
||||
where s != null
|
||||
select s.ToUpper();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = MergeSimilarTypes(ExactBound, type);
|
||||
IType merged = MergeSimilarTypes(ExactBound, type, VarianceModifier.Invariant);
|
||||
if (merged != null)
|
||||
ExactBound = merged;
|
||||
else
|
||||
@@ -986,8 +986,14 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
|
||||
// merged - kept where both sides agree, dropped where they conflict. See
|
||||
// MergeTupleNames in Roslyn's MethodTypeInference.cs.
|
||||
IType fixedTo = tp.ExactBound;
|
||||
foreach (var b in tp.LowerBounds.Concat(tp.UpperBounds))
|
||||
fixedTo = MergeSimilarTypes(fixedTo, b) ?? fixedTo;
|
||||
foreach (var b in tp.LowerBounds)
|
||||
{
|
||||
fixedTo = MergeSimilarTypes(fixedTo, b, VarianceModifier.Covariant) ?? fixedTo;
|
||||
}
|
||||
foreach (var b in tp.UpperBounds)
|
||||
{
|
||||
fixedTo = MergeSimilarTypes(fixedTo, b, VarianceModifier.Contravariant) ?? fixedTo;
|
||||
}
|
||||
// the exact bound determines the result, up to the merged element names
|
||||
tp.FixedTo = fixedTo;
|
||||
// check validity
|
||||
@@ -1000,8 +1006,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
|
||||
// Same Roslyn-style merge, over lower and upper bounds together as Roslyn's Fix does,
|
||||
// so bounds that differ only in tuple element names don't survive into
|
||||
// FindTypesInBounds as distinct candidates.
|
||||
var (lowerBounds, upperBounds) = MergeShapeEquivalentBounds(tp.LowerBounds, tp.UpperBounds);
|
||||
var types = CreateNestedInstance().FindTypesInBounds(lowerBounds, upperBounds);
|
||||
var types = CreateNestedInstance().FindTypesInBounds(tp.LowerBounds, tp.UpperBounds);
|
||||
Log.Unindent();
|
||||
if (algorithm == TypeInferenceAlgorithm.ImprovedReturnAllResults)
|
||||
{
|
||||
@@ -1018,23 +1023,23 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// Merges similar types that differ only in any of these aspects.
|
||||
/// * tuple element names: a name is kept where both sides agree and dropped where they conflict.
|
||||
/// * object/dynamic: dynamic is preferred over object.
|
||||
/// * nullability: depends on the variance of the position.
|
||||
/// Returns <c>null</c> if the types differ in any other aspects.
|
||||
/// </summary>
|
||||
static IType MergeSimilarTypes(IType a, IType b)
|
||||
static IType MergeSimilarTypes(IType a, IType b, VarianceModifier variance)
|
||||
{
|
||||
if (a.Equals(b))
|
||||
return a;
|
||||
// Roslyn merges differing nullability based on the variance of the position; this
|
||||
// implementation does not track that, so differently annotated types are left alone.
|
||||
if (a.Nullability != b.Nullability)
|
||||
return null;
|
||||
if (a is NullabilityAnnotatedType na && b is NullabilityAnnotatedType nb)
|
||||
if (a is NullabilityAnnotatedType || b is NullabilityAnnotatedType)
|
||||
{
|
||||
return MergeSimilarTypes(na.TypeWithoutAnnotation, nb.TypeWithoutAnnotation)
|
||||
?.ChangeNullability(a.Nullability);
|
||||
var nullability = MergeNullability(a.Nullability, b.Nullability, variance);
|
||||
var merged = MergeSimilarTypes(a.WithoutNullability(), b.WithoutNullability(), variance);
|
||||
if (merged == null)
|
||||
return null;
|
||||
return merged.ChangeNullability(nullability);
|
||||
}
|
||||
if (a.Kind == TypeKind.Dynamic && b.IsKnownType(KnownTypeCode.Object))
|
||||
{
|
||||
@@ -1050,7 +1055,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
|
||||
var mergedElements = ImmutableArray.CreateBuilder<IType>(ta.ElementTypes.Length);
|
||||
for (int i = 0; i < ta.ElementTypes.Length; i++)
|
||||
{
|
||||
var merged = MergeSimilarTypes(ta.ElementTypes[i], tb.ElementTypes[i]);
|
||||
// Note: even though ValueTuple has invariant type parameters,
|
||||
// Roslyn merges tuple element types in a covariant manner.
|
||||
var merged = MergeSimilarTypes(
|
||||
ta.ElementTypes[i], tb.ElementTypes[i],
|
||||
variance.Combine(VarianceModifier.Covariant));
|
||||
if (merged == null)
|
||||
return null;
|
||||
mergedElements.Add(merged);
|
||||
@@ -1064,72 +1073,124 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
|
||||
ta.GetDefinition()?.ParentModule);
|
||||
}
|
||||
if (a is ParameterizedType pa && b is ParameterizedType pb
|
||||
&& pa.GenericType.Equals(pb.GenericType)
|
||||
&& pa.TypeArguments.Count == pb.TypeArguments.Count)
|
||||
{
|
||||
var genericType = MergeSimilarTypes(pa.GenericType, pb.GenericType, variance);
|
||||
if (genericType == null)
|
||||
return null;
|
||||
var mergedArgs = new IType[pa.TypeArguments.Count];
|
||||
for (int i = 0; i < pa.TypeArguments.Count; i++)
|
||||
{
|
||||
var merged = MergeSimilarTypes(pa.TypeArguments[i], pb.TypeArguments[i]);
|
||||
var merged = MergeSimilarTypes(
|
||||
pa.TypeArguments[i], pb.TypeArguments[i],
|
||||
variance.Combine(pa.TypeParameters[i].Variance));
|
||||
if (merged == null)
|
||||
return null;
|
||||
mergedArgs[i] = merged;
|
||||
}
|
||||
return new ParameterizedType(pa.GenericType, mergedArgs);
|
||||
return new ParameterizedType(genericType, mergedArgs);
|
||||
}
|
||||
if (a is ArrayType arrA && b is ArrayType arrB && arrA.Dimensions == arrB.Dimensions)
|
||||
{
|
||||
var mergedElem = MergeSimilarTypes(arrA.ElementType, arrB.ElementType);
|
||||
// Roslyn ArrayTypeSymbol merges in a covariant manner.
|
||||
var mergedElem = MergeSimilarTypes(
|
||||
arrA.ElementType, arrB.ElementType,
|
||||
variance.Combine(VarianceModifier.Covariant));
|
||||
if (mergedElem == null)
|
||||
return null;
|
||||
return new ArrayType(arrA.Compilation, mergedElem, arrA.Dimensions, arrA.Nullability);
|
||||
var nullability = MergeNullability(arrA.Nullability, arrB.Nullability, variance);
|
||||
return new ArrayType(arrA.Compilation, mergedElem, arrA.Dimensions, nullability);
|
||||
}
|
||||
if (a is ByReferenceType refA && b is ByReferenceType refB)
|
||||
{
|
||||
var mergedElem = MergeSimilarTypes(
|
||||
refA.ElementType, refB.ElementType,
|
||||
variance.Combine(VarianceModifier.Invariant));
|
||||
if (mergedElem == null)
|
||||
return null;
|
||||
return new ByReferenceType(mergedElem);
|
||||
}
|
||||
if (a is PointerType ptrA && b is PointerType ptrB)
|
||||
{
|
||||
var mergedElem = MergeSimilarTypes(
|
||||
ptrA.ElementType, ptrB.ElementType,
|
||||
variance.Combine(VarianceModifier.Invariant));
|
||||
if (mergedElem == null)
|
||||
return null;
|
||||
return new PointerType(mergedElem);
|
||||
}
|
||||
if (a is FunctionPointerType fnPtrA && b is FunctionPointerType fnPtrB
|
||||
&& fnPtrA.CallingConvention == fnPtrB.CallingConvention
|
||||
&& fnPtrA.CustomCallingConventions.SequenceEqual(fnPtrB.CustomCallingConventions)
|
||||
&& fnPtrA.ReturnIsRefReadOnly == fnPtrB.ReturnIsRefReadOnly
|
||||
&& fnPtrA.ParameterTypes.Length == fnPtrB.ParameterTypes.Length
|
||||
&& fnPtrA.ParameterReferenceKinds.SequenceEqual(fnPtrB.ParameterReferenceKinds))
|
||||
{
|
||||
var mergedReturn = MergeSimilarTypes(
|
||||
fnPtrA.ReturnType, fnPtrB.ReturnType,
|
||||
variance.Combine(VarianceModifier.Covariant));
|
||||
if (mergedReturn == null)
|
||||
return null;
|
||||
var mergedParameters = ImmutableArray.CreateBuilder<IType>(fnPtrA.ParameterTypes.Length);
|
||||
for (int i = 0; i < fnPtrA.ParameterTypes.Length; i++)
|
||||
{
|
||||
var mergedParameter = MergeSimilarTypes(
|
||||
fnPtrA.ParameterTypes[i], fnPtrB.ParameterTypes[i],
|
||||
variance.Combine(VarianceModifier.Contravariant));
|
||||
if (mergedParameter == null)
|
||||
return null;
|
||||
mergedParameters.Add(mergedParameter);
|
||||
}
|
||||
return fnPtrA.WithSignature(mergedReturn, mergedParameters.MoveToImmutable());
|
||||
}
|
||||
if (a is ModifiedType modA && b is ModifiedType modB
|
||||
&& modA.Kind == modB.Kind
|
||||
&& modA.Modifier.Equals(modB.Modifier))
|
||||
{
|
||||
var mergedElem = MergeSimilarTypes(modA.ElementType, modB.ElementType, variance);
|
||||
if (mergedElem == null)
|
||||
return null;
|
||||
return new ModifiedType(modA.Modifier, mergedElem, modA.Kind == TypeKind.ModReq);
|
||||
}
|
||||
if (a is UnknownType unknownTypeA && b is UnknownType unknownTypeB
|
||||
&& unknownTypeA.FullTypeName == unknownTypeB.FullTypeName)
|
||||
{
|
||||
if (unknownTypeA.IsReferenceType == unknownTypeB.IsReferenceType)
|
||||
return unknownTypeA;
|
||||
else
|
||||
return unknownTypeA.WithoutReferenceTypeKnowledge();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collapses bounds that are equal modulo tuple element names (possibly nested) into a
|
||||
/// single merged type via <see cref="MergeSimilarTypes"/>, across both bound sets. Bounds
|
||||
/// without a shape-equivalent partner are returned as-is.
|
||||
/// </summary>
|
||||
static (IReadOnlyList<IType> LowerBounds, IReadOnlyList<IType> UpperBounds) MergeShapeEquivalentBounds(
|
||||
IReadOnlyCollection<IType> lowerBounds, IReadOnlyCollection<IType> upperBounds)
|
||||
static Nullability MergeNullability(Nullability a, Nullability b, VarianceModifier variance)
|
||||
{
|
||||
if (lowerBounds.Count + upperBounds.Count < 2)
|
||||
return (lowerBounds.ToArray(), upperBounds.ToArray());
|
||||
var mergedBounds = new List<IType>();
|
||||
bool anyNamesMerged = false;
|
||||
foreach (var bound in lowerBounds.Concat(upperBounds))
|
||||
{
|
||||
bool absorbed = false;
|
||||
for (int i = 0; i < mergedBounds.Count && !absorbed; i++)
|
||||
{
|
||||
IType merged = MergeSimilarTypes(mergedBounds[i], bound);
|
||||
if (merged != null)
|
||||
{
|
||||
// Bounds that are already equal merge to the existing entry itself;
|
||||
// only a new type means element names were actually merged.
|
||||
anyNamesMerged |= !ReferenceEquals(merged, mergedBounds[i]);
|
||||
mergedBounds[i] = merged;
|
||||
absorbed = true;
|
||||
}
|
||||
}
|
||||
if (!absorbed)
|
||||
mergedBounds.Add(bound);
|
||||
}
|
||||
if (!anyNamesMerged)
|
||||
return (lowerBounds.ToArray(), upperBounds.ToArray());
|
||||
return (MapToMergedBounds(lowerBounds, mergedBounds), MapToMergedBounds(upperBounds, mergedBounds));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces each bound with the entry of <paramref name="mergedBounds"/> it was merged into.
|
||||
/// Merging only changes element names, never the shape, so every bound is still
|
||||
/// shape-equivalent to exactly one of those entries.
|
||||
/// </summary>
|
||||
static IType[] MapToMergedBounds(IEnumerable<IType> bounds, List<IType> mergedBounds)
|
||||
{
|
||||
return bounds.Select(b => mergedBounds.First(m => MergeSimilarTypes(m, b) != null)).Distinct().ToArray();
|
||||
// Like Roslyn's MergeNullableAnnotation()
|
||||
return (variance, a, b) switch {
|
||||
// Covariant merging rules: Nullable wins over Oblivious which wins over NotNullable.
|
||||
(VarianceModifier.Covariant, Nullability.Nullable, _) => Nullability.Nullable,
|
||||
(VarianceModifier.Covariant, _, Nullability.Nullable) => Nullability.Nullable,
|
||||
(VarianceModifier.Covariant, Nullability.Oblivious, _) => Nullability.Oblivious,
|
||||
(VarianceModifier.Covariant, _, Nullability.Oblivious) => Nullability.Oblivious,
|
||||
(VarianceModifier.Covariant, Nullability.NotNullable, Nullability.NotNullable) => Nullability.NotNullable,
|
||||
// Contravariant merging rules: NotNullable wins over Oblivious which wins over Nullable.
|
||||
(VarianceModifier.Contravariant, Nullability.NotNullable, _) => Nullability.NotNullable,
|
||||
(VarianceModifier.Contravariant, _, Nullability.NotNullable) => Nullability.NotNullable,
|
||||
(VarianceModifier.Contravariant, Nullability.Oblivious, _) => Nullability.Oblivious,
|
||||
(VarianceModifier.Contravariant, _, Nullability.Oblivious) => Nullability.Oblivious,
|
||||
(VarianceModifier.Contravariant, Nullability.Nullable, Nullability.Nullable) => Nullability.Nullable,
|
||||
// Invariant merging rules: NotNullable wins over Nullable which wins over Oblivious.
|
||||
// Weird but that's what Roslyn does:
|
||||
// static T M<T>(ref T x, ref T y) => x;
|
||||
// M(ref nullableArray, nonNullableArray);
|
||||
// T is inferred as int[] and then the first argument reports a "possible null reference assignment" warning.
|
||||
(VarianceModifier.Invariant, Nullability.NotNullable, _) => Nullability.NotNullable,
|
||||
(VarianceModifier.Invariant, _, Nullability.NotNullable) => Nullability.NotNullable,
|
||||
(VarianceModifier.Invariant, Nullability.Nullable, _) => Nullability.Nullable,
|
||||
(VarianceModifier.Invariant, _, Nullability.Nullable) => Nullability.Nullable,
|
||||
(VarianceModifier.Invariant, Nullability.Oblivious, Nullability.Oblivious) => Nullability.Oblivious,
|
||||
_ => throw new NotSupportedException("Unexpected nullability combination: " + a + ", " + b + " with variance " + variance)
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -1169,7 +1230,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
|
||||
/// <summary>
|
||||
/// Finds a type that satisfies the given lower and upper bounds.
|
||||
/// </summary>
|
||||
public IType FindTypeInBounds(IReadOnlyList<IType> lowerBounds, IReadOnlyList<IType> upperBounds)
|
||||
public IType FindTypeInBounds(IReadOnlyCollection<IType> lowerBounds, IReadOnlyCollection<IType> upperBounds)
|
||||
{
|
||||
if (lowerBounds == null)
|
||||
throw new ArgumentNullException(nameof(lowerBounds));
|
||||
@@ -1189,13 +1250,13 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
|
||||
}
|
||||
}
|
||||
|
||||
static IType GetFirstTypePreferNonInterfaces(IReadOnlyList<IType> result)
|
||||
static IType GetFirstTypePreferNonInterfaces(IReadOnlyCollection<IType> result)
|
||||
{
|
||||
return result.FirstOrDefault(c => c.Kind != TypeKind.Interface)
|
||||
?? result.FirstOrDefault() ?? SpecialType.UnknownType;
|
||||
}
|
||||
|
||||
IReadOnlyList<IType> FindTypesInBounds(IReadOnlyList<IType> lowerBounds, IReadOnlyList<IType> upperBounds)
|
||||
IReadOnlyCollection<IType> FindTypesInBounds(IReadOnlyCollection<IType> lowerBounds, IReadOnlyCollection<IType> upperBounds)
|
||||
{
|
||||
// If there's only a single type; return that single type.
|
||||
// If both inputs are empty, return the empty list.
|
||||
@@ -1210,8 +1271,57 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
|
||||
Log.WriteCollection("FindTypesInBound, LowerBounds=", lowerBounds);
|
||||
Log.WriteCollection("FindTypesInBound, UpperBounds=", upperBounds);
|
||||
|
||||
// Deduplicate types. This also merges types that differ only in tuple
|
||||
// element names and/or object/dynamic.
|
||||
// The specification doesn't mention this step, but Roslyn does it,
|
||||
// and it's crucial for tuple element names and nested nullabilities in otherwise
|
||||
// equivalent types.
|
||||
Nullability? topLevelNullability = null;
|
||||
var candidateMergeDict = new Dictionary<IType, IType>();
|
||||
void AddCandidates(IReadOnlyCollection<IType> bounds, VarianceModifier variance)
|
||||
{
|
||||
// This helper function works like Roslyn's MethodTypeInference.AddAllCandidates().
|
||||
// It deduplicates similar types and merges them into a single candidate type,
|
||||
// handling differences in nullability, tuple element names, and object/dynamic,
|
||||
// but only if the type is otherwise completely identical.
|
||||
foreach (var bound in bounds)
|
||||
{
|
||||
if (topLevelNullability.HasValue)
|
||||
{
|
||||
topLevelNullability = MergeNullability(topLevelNullability.Value, bound.Nullability, variance);
|
||||
}
|
||||
else
|
||||
{
|
||||
topLevelNullability = bound.Nullability;
|
||||
}
|
||||
var key = bound.AcceptVisitor(NormalizeTypeVisitor.KeyForTypeMerging);
|
||||
if (candidateMergeDict.TryGetValue(key, out var existing))
|
||||
{
|
||||
var merged = MergeSimilarTypes(existing, bound, variance);
|
||||
Log.WriteLine(" Merged similar types " + existing + " and " + bound + " into " + merged);
|
||||
if (merged != null)
|
||||
{
|
||||
candidateMergeDict[key] = merged;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Fail("MergeSimilarTypes should always be able to merge;"
|
||||
+ " is the KeyForTypeMerging visitor misconfigured?");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
candidateMergeDict.Add(key, bound);
|
||||
}
|
||||
}
|
||||
}
|
||||
AddCandidates(lowerBounds, VarianceModifier.Covariant);
|
||||
AddCandidates(upperBounds, VarianceModifier.Contravariant);
|
||||
|
||||
Log.WriteCollection("FindTypesInBound, Merged types from bounds=", candidateMergeDict.Values);
|
||||
|
||||
// First try the Fixing algorithm from the C# spec (§12.6.3.13)
|
||||
List<IType> candidateTypes = lowerBounds.Union(upperBounds)
|
||||
List<IType> candidateTypes = candidateMergeDict.Values
|
||||
.Where(c => lowerBounds.All(b => conversions.ImplicitConversion(b, c).IsValid))
|
||||
.Where(c => upperBounds.All(b => conversions.ImplicitConversion(c, b).IsValid))
|
||||
.ToList(); // evaluate the query only once
|
||||
@@ -1224,6 +1334,18 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
|
||||
c => candidateTypes.All(o => conversions.ImplicitConversion(o, c).IsValid)
|
||||
).ToList();
|
||||
|
||||
// Apply the merged top-level nullability:
|
||||
Debug.Assert(topLevelNullability.HasValue);
|
||||
// Roslyn has a different approach in MergeOrRemoveCandidates, which can
|
||||
// differ in behavior when there's both lower+upper bounds
|
||||
// -- e.g. `static void M<T>(T x, Action<T> a)` called with `M("s", (object? o) => {}))`
|
||||
// is inferred as `T = object?` by Roslyn, but `T = object` by us.
|
||||
// To match Roslyn exactly, we'd need to handle the topLevelNullability per-candidate.
|
||||
for (int i = 0; i < candidateTypes.Count; i++)
|
||||
{
|
||||
candidateTypes[i] = candidateTypes[i].ChangeNullability(topLevelNullability.Value);
|
||||
}
|
||||
|
||||
// If the specified algorithm produces a single candidate, we return
|
||||
// that candidate.
|
||||
// We also return the whole candidate list if we're not using the improved
|
||||
@@ -1240,10 +1362,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
|
||||
if (lowerBounds.Count > 0)
|
||||
{
|
||||
// Find candidates by using the lower bounds:
|
||||
var hashSet = new HashSet<ITypeDefinition>(lowerBounds[0].GetAllBaseTypeDefinitions());
|
||||
for (int i = 1; i < lowerBounds.Count; i++)
|
||||
var lowerBoundsList = lowerBounds.ToList();
|
||||
var hashSet = new HashSet<ITypeDefinition>(lowerBoundsList[0].GetAllBaseTypeDefinitions());
|
||||
for (int i = 1; i < lowerBoundsList.Count; i++)
|
||||
{
|
||||
hashSet.IntersectWith(lowerBounds[i].GetAllBaseTypeDefinitions());
|
||||
hashSet.IntersectWith(lowerBoundsList[i].GetAllBaseTypeDefinitions());
|
||||
}
|
||||
candidateTypeDefinitions = hashSet.ToList();
|
||||
}
|
||||
|
||||
@@ -142,6 +142,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
|
||||
MemberReferenceExpression? mre = invocation.Target as MemberReferenceExpression;
|
||||
if (mre == null || IsNullConditional(mre.Target))
|
||||
return null;
|
||||
if (mre.TypeArguments.Count > 0)
|
||||
return null;
|
||||
switch (mre.MemberName)
|
||||
{
|
||||
case "Select":
|
||||
@@ -346,7 +348,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
|
||||
|
||||
static bool IsComplexQuery(MemberReferenceExpression mre)
|
||||
{
|
||||
return ((mre.Target is InvocationExpression && mre.Parent is InvocationExpression) || mre.Parent?.Parent is QueryClause);
|
||||
return (mre.Target is InvocationExpression && mre.Parent is InvocationExpression) || mre.Parent?.Parent is QueryClause;
|
||||
}
|
||||
|
||||
QueryFromClause MakeFromClause(ParameterDeclaration parameter, Expression body)
|
||||
@@ -412,6 +414,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
|
||||
return false;
|
||||
if (parameter.Name != expectedParameterName)
|
||||
return false;
|
||||
if (mre.TypeArguments.Count > 0)
|
||||
return false;
|
||||
|
||||
if (mre.MemberName == "OrderBy" || mre.MemberName == "OrderByDescending")
|
||||
return !IsNullConditional(mre.Target);
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace ICSharpCode.Decompiler.Semantics
|
||||
return Conversion.None;
|
||||
for (int i = 0; i < parameterTypes.Length; ++i)
|
||||
{
|
||||
if (parameterTypes[i].Equals(this.Parameters[i].Type))
|
||||
if (NormalizeTypeVisitor.IgnoreNullability.EquivalentTypes(parameterTypes[i], this.Parameters[i].Type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -129,19 +129,32 @@ namespace ICSharpCode.Decompiler.TypeSystem
|
||||
/// <summary>
|
||||
/// Represents the variance of a type parameter.
|
||||
/// </summary>
|
||||
public enum VarianceModifier : byte
|
||||
public enum VarianceModifier : sbyte
|
||||
{
|
||||
/// <summary>
|
||||
/// The type parameter is not variant.
|
||||
/// </summary>
|
||||
Invariant,
|
||||
Invariant = 0,
|
||||
/// <summary>
|
||||
/// The type parameter is covariant (used in output position).
|
||||
/// </summary>
|
||||
Covariant,
|
||||
Covariant = 1,
|
||||
/// <summary>
|
||||
/// The type parameter is contravariant (used in input position).
|
||||
/// </summary>
|
||||
Contravariant
|
||||
Contravariant = -1
|
||||
};
|
||||
|
||||
static class VarianceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Combines variance modifiers.
|
||||
/// It's like a multiplication where Invariant is 0, Covariant is +1 and Contravariant is -1.
|
||||
/// </summary>
|
||||
public static VarianceModifier Combine(this VarianceModifier a, VarianceModifier b)
|
||||
{
|
||||
// By picking matching enum values, we can actually implement this as multiplication.
|
||||
return (VarianceModifier)((sbyte)a * (sbyte)b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,12 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
|
||||
|
||||
public abstract IType AcceptVisitor(TypeVisitor visitor);
|
||||
|
||||
public abstract override int GetHashCode();
|
||||
public abstract bool Equals(IType other);
|
||||
public sealed override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as IType);
|
||||
}
|
||||
|
||||
IEnumerable<IMethod> IType.GetAccessors(Predicate<IMethod> filter, GetMemberOptions options)
|
||||
{
|
||||
|
||||
@@ -52,6 +52,11 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
|
||||
return visitor.VisitNullabilityAnnotatedType(this);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return baseType.GetHashCode() ^ nullability.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(IType other)
|
||||
{
|
||||
return other is NullabilityAnnotatedType nat
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System.Linq;
|
||||
|
||||
using ICSharpCode.Decompiler.TypeSystem.Implementation;
|
||||
using ICSharpCode.Decompiler.Util;
|
||||
|
||||
namespace ICSharpCode.Decompiler.TypeSystem
|
||||
{
|
||||
@@ -34,6 +37,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
|
||||
DynamicAndObject = true,
|
||||
IntPtrToNInt = true,
|
||||
TupleToUnderlyingType = true,
|
||||
RemoveTupleElementNames = false,
|
||||
RemoveModOpt = true,
|
||||
RemoveModReq = true,
|
||||
RemoveNullability = true,
|
||||
@@ -45,17 +49,32 @@ namespace ICSharpCode.Decompiler.TypeSystem
|
||||
DynamicAndObject = false,
|
||||
IntPtrToNInt = false,
|
||||
TupleToUnderlyingType = true,
|
||||
RemoveTupleElementNames = false,
|
||||
RemoveModOpt = true,
|
||||
RemoveModReq = true,
|
||||
RemoveNullability = true,
|
||||
};
|
||||
|
||||
// Used in type inference to group together similar types that can be merged.
|
||||
internal static readonly NormalizeTypeVisitor KeyForTypeMerging = new NormalizeTypeVisitor {
|
||||
ReplaceClassTypeParametersWithDummy = false,
|
||||
ReplaceMethodTypeParametersWithDummy = false,
|
||||
DynamicAndObject = true,
|
||||
IntPtrToNInt = false,
|
||||
TupleToUnderlyingType = false,
|
||||
RemoveTupleElementNames = true,
|
||||
RemoveModOpt = false,
|
||||
RemoveModReq = false,
|
||||
RemoveNullability = true,
|
||||
};
|
||||
|
||||
internal static readonly NormalizeTypeVisitor IgnoreNullability = new NormalizeTypeVisitor {
|
||||
ReplaceClassTypeParametersWithDummy = false,
|
||||
ReplaceMethodTypeParametersWithDummy = false,
|
||||
DynamicAndObject = false,
|
||||
IntPtrToNInt = false,
|
||||
TupleToUnderlyingType = false,
|
||||
RemoveTupleElementNames = false,
|
||||
RemoveModOpt = true,
|
||||
RemoveModReq = true,
|
||||
RemoveNullability = true,
|
||||
@@ -75,6 +94,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
|
||||
public bool DynamicAndObject = true;
|
||||
public bool IntPtrToNInt = true;
|
||||
public bool TupleToUnderlyingType = true;
|
||||
public bool RemoveTupleElementNames = true;
|
||||
public bool RemoveNullability = true;
|
||||
|
||||
public override IType VisitTypeParameter(ITypeParameter type)
|
||||
@@ -129,6 +149,14 @@ namespace ICSharpCode.Decompiler.TypeSystem
|
||||
{
|
||||
return type.UnderlyingType.AcceptVisitor(this);
|
||||
}
|
||||
else if (RemoveTupleElementNames && type.ElementNames.Any(name => name != null))
|
||||
{
|
||||
return new TupleType(
|
||||
type.Compilation,
|
||||
type.ElementTypes.SelectImmutableArray(t => t.AcceptVisitor(this)),
|
||||
default,
|
||||
type.GetDefinition()?.ParentModule);
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.VisitTupleType(type);
|
||||
|
||||
Reference in New Issue
Block a user