Fix #4016: Don't consume nullable flags for anonymous type arguments
Roslyn writes [Nullable] for an anonymous type as if the type was not generic, so its type arguments have no flags. Taking one per argument shifted every annotation after the anonymous type, which showed on the cached lambda fields in <>c, and tripped the dummy-flag assert when the shifted stream landed on a generic struct. [Dynamic] and [TupleElementNames] do have entries for those arguments (checked against what Roslyn emits), so only the nullable index skips them.
This commit is contained in:
1 parent
2dc58c945d
commit
c61379b8b9
3 files changed
+76
-1
No files matched your search
@@ -86,6 +86,40 @@ namespace ICSharpCode.Decompiler.Tests
|
|||||||
await DecompileNestedGetMethod("BlockExtensions").ConfigureAwait(false);
|
await DecompileNestedGetMethod("BlockExtensions").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task AnonymousTypeArgumentsHaveNoNullableFlags()
|
||||||
|
{
|
||||||
|
// #4016: NullableAttribute has no entries for the type arguments of an anonymous type,
|
||||||
|
// so the annotations after it must not shift. The affected fields live in the lambda
|
||||||
|
// cache class, which only shows up when it is decompiled on its own.
|
||||||
|
var csFile = Path.Combine(TestCasePath, "AnonymousTypeNullability.cs");
|
||||||
|
var compilation = await Tester.CompileCSharp(csFile, CompilerOptions.UseRoslynLatest | CompilerOptions.Optimize | CompilerOptions.Library | CompilerOptions.NullableEnable).ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = new DecompilerSettings();
|
||||||
|
using var file = new FileStream(compilation.PathToAssembly, FileMode.Open, FileAccess.Read);
|
||||||
|
var module = new PEFile(compilation.PathToAssembly, file, PEStreamOptions.PrefetchEntireImage);
|
||||||
|
var targetFramework = module.Metadata.DetectTargetFrameworkId();
|
||||||
|
var resolver = new UniversalAssemblyResolver(compilation.PathToAssembly, false, targetFramework, null, PEStreamOptions.PrefetchMetadata);
|
||||||
|
resolver.AddSearchDirectory(Tester.RefAssembliesToolset.GetPath(targetFramework));
|
||||||
|
var typeSystem = new DecompilerTypeSystem(module, resolver, settings);
|
||||||
|
var decompiler = new CSharpDecompiler(typeSystem, settings);
|
||||||
|
|
||||||
|
var lambdaCache = typeSystem.MainModule.TypeDefinitions
|
||||||
|
.Single(t => t.Name == "AnonymousTypeNullability")
|
||||||
|
.NestedTypes.Single(t => t.Name == "<>c");
|
||||||
|
|
||||||
|
var code = decompiler.Decompile(lambdaCache.MetadataToken).ToString();
|
||||||
|
|
||||||
|
Assert.That(code, Does.Contain("Func<<>f__AnonymousType0<string, string>, List<string?>> <>9__0_1;"));
|
||||||
|
Assert.That(code, Does.Contain("Func<<>f__AnonymousType1<string>, KeyValuePair<string, string?>> <>9__1_1;"));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
compilation.DeleteTempFiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async Task DecompileNestedGetMethod(string containerName)
|
static async Task DecompileNestedGetMethod(string containerName)
|
||||||
{
|
{
|
||||||
var csFile = Path.Combine(TestCasePath, "ExtensionContainerNestedType.cs");
|
var csFile = Path.Combine(TestCasePath, "ExtensionContainerNestedType.cs");
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace ICSharpCode.Decompiler.Tests.TestCases.IsolatedDecompilation
|
||||||
|
{
|
||||||
|
public class AnonymousTypeNullability
|
||||||
|
{
|
||||||
|
public void ReferenceTypeAfterAnonymousType(IEnumerable<string> xs)
|
||||||
|
{
|
||||||
|
xs.Select(x => new { A = x, B = x }).Pick(a => new List<string?>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ValueTypeAfterAnonymousType(IEnumerable<string> xs)
|
||||||
|
{
|
||||||
|
xs.Select(x => new { A = x }).PickPair(a => new KeyValuePair<string, string?>(a.A, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AnonymousTypeNullabilityExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<T> Pick<TSource, T>(this IEnumerable<TSource> source, Func<TSource, List<T?>> selector) where T : class
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<T> PickPair<TSource, T>(this IEnumerable<TSource> source, Func<TSource, KeyValuePair<string, T?>> selector) where T : class
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -184,6 +184,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
|
|||||||
int tupleTypeIndex = 0;
|
int tupleTypeIndex = 0;
|
||||||
int nullabilityTypeIndex = 0;
|
int nullabilityTypeIndex = 0;
|
||||||
int nativeIntTypeIndex = 0;
|
int nativeIntTypeIndex = 0;
|
||||||
|
int anonymousTypeArgumentDepth = 0;
|
||||||
|
|
||||||
private ApplyAttributeTypeVisitor(ICompilation compilation,
|
private ApplyAttributeTypeVisitor(ICompilation compilation,
|
||||||
bool hasDynamicAttribute, bool[] dynamicAttributeData,
|
bool hasDynamicAttribute, bool[] dynamicAttributeData,
|
||||||
@@ -228,7 +229,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
|
|||||||
|
|
||||||
Nullability GetNullability()
|
Nullability GetNullability()
|
||||||
{
|
{
|
||||||
if (nullabilityTypeIndex < nullableAttributeData?.Length)
|
if (anonymousTypeArgumentDepth == 0 && nullabilityTypeIndex < nullableAttributeData?.Length)
|
||||||
return nullableAttributeData[nullabilityTypeIndex++];
|
return nullableAttributeData[nullabilityTypeIndex++];
|
||||||
else
|
else
|
||||||
return defaultNullability;
|
return defaultNullability;
|
||||||
@@ -322,6 +323,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
|
|||||||
ExpectDummyNullabilityForGenericValueType();
|
ExpectDummyNullabilityForGenericValueType();
|
||||||
}
|
}
|
||||||
bool changed = type.GenericType != genericType;
|
bool changed = type.GenericType != genericType;
|
||||||
|
// Roslyn writes the [Nullable] flags of an anonymous type as if it was not generic, so its
|
||||||
|
// type arguments have no flags of their own. [Dynamic] and [TupleElementNames] do count them.
|
||||||
|
bool isAnonymousType = nullableAttributeData != null && type.IsAnonymousType();
|
||||||
|
if (isAnonymousType)
|
||||||
|
anonymousTypeArgumentDepth++;
|
||||||
var arguments = new IType[type.TypeArguments.Count];
|
var arguments = new IType[type.TypeArguments.Count];
|
||||||
for (int i = 0; i < type.TypeArguments.Count; i++)
|
for (int i = 0; i < type.TypeArguments.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -329,6 +335,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
|
|||||||
arguments[i] = type.TypeArguments[i].AcceptVisitor(this);
|
arguments[i] = type.TypeArguments[i].AcceptVisitor(this);
|
||||||
changed = changed || arguments[i] != type.TypeArguments[i];
|
changed = changed || arguments[i] != type.TypeArguments[i];
|
||||||
}
|
}
|
||||||
|
if (isAnonymousType)
|
||||||
|
anonymousTypeArgumentDepth--;
|
||||||
if (!changed)
|
if (!changed)
|
||||||
return type;
|
return type;
|
||||||
return new ParameterizedType(genericType, arguments);
|
return new ParameterizedType(genericType, arguments);
|
||||||
|
|||||||
Reference in new issue
Block a user