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:
dualfroz committed 2026-10-03 02:29:11 +02:00
1 parent 2dc58c945d
commit c61379b8b9
3 files changed
+76 -1

No files matched your search

@@ -184,6 +184,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
int tupleTypeIndex = 0;
int nullabilityTypeIndex = 0;
int nativeIntTypeIndex = 0;
int anonymousTypeArgumentDepth = 0;
private ApplyAttributeTypeVisitor(ICompilation compilation,
bool hasDynamicAttribute, bool[] dynamicAttributeData,
@@ -228,7 +229,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
Nullability GetNullability()
{
if (nullabilityTypeIndex < nullableAttributeData?.Length)
if (anonymousTypeArgumentDepth == 0 && nullabilityTypeIndex < nullableAttributeData?.Length)
return nullableAttributeData[nullabilityTypeIndex++];
else
return defaultNullability;
@@ -322,6 +323,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
ExpectDummyNullabilityForGenericValueType();
}
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];
for (int i = 0; i < type.TypeArguments.Count; i++)
{
@@ -329,6 +335,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
arguments[i] = type.TypeArguments[i].AcceptVisitor(this);
changed = changed || arguments[i] != type.TypeArguments[i];
}
if (isAnonymousType)
anonymousTypeArgumentDepth--;
if (!changed)
return type;
return new ParameterizedType(genericType, arguments);