Skip to content

Add test coverage for untested corners of implemented language features#3859

Merged
siegfriedpammer merged 27 commits into
masterfrom
more-language-feature-tests
Jul 4, 2026
Merged

Add test coverage for untested corners of implemented language features#3859
siegfriedpammer merged 27 commits into
masterfrom
more-language-feature-tests

Conversation

@siegfriedpammer

@siegfriedpammer siegfriedpammer commented Jul 4, 2026

Copy link
Copy Markdown
Member

This PR was written by an AI agent (Claude Code), driven and reviewed by @siegfriedpammer.

An audit of the language features marked as implemented in #829 and on the wiki Language-Feature-Roadmap, compared against the existing Pretty fixtures, found a number of constructs that decompile correctly today but had no test pinning them. This PR adds green tests only, one commit per feature (27 commits, 26 files, +791 lines, no production code changes).

Highlights:

  • Generic co-/contravariance had zero coverage: no out T/in T declaration or variance conversion anywhere in the suite. New Variance fixture + runner method.
  • The XmlDoc subsystem (ICSharpCode.Decompiler/Documentation/) had zero tests: new unit tests pin IdStringProvider round-trips (nested generics, `0/ 0`` references, @/pointer/array forms incl. `[0:,0:]`, conversion-operator `~ReturnType` suffixes, indexers, `#ctor`) and `XmlDocumentationProvider` lookup.
  • First coverage for: [SetsRequiredMembers] and required-member inheritance, [UnscopedRef] members, static abstract checked/>>> operators, generic attributes with constructed/array/enum type arguments, nullable postcondition attributes (MaybeNullWhen etc.), ConfigureAwait(false) in async iterators, await in an interpolated-string hole, pattern-based Dispose of a ref struct, protected/static/generic default interface members, readonly record structs and records with user-declared ToString/PrintMembers, async local functions, checked compound assignment, lifted compound assignment and Nullable<T> boxing, pointer-to-pointer shapes, dictionary index initializers, UTF-8 literal edge cases, select-into continuations, nested anonymous-type members, generic iterators, exception filters with side effects, and continue inside a catch within a loop (pins the fixed behavior from Continue statement unrecognized in try-catch #2829, which can be closed).

All added tests pass on the Linux configuration subset (Roslyn 3.11 / 4.14 / latest, plus/minus Optimize and net40 where the fixture's group includes it): 303/303 including the pre-existing cases of the touched fixtures. Legacy csc/mcs configurations run on Windows CI only.

🤖 Generated with Claude Code

The Documentation namespace (XmlDocumentationProvider, IdStringProvider,
XmlDocLoader) had no test coverage at all, even though XmlDoc is listed
as an implemented C# 1 feature on the wiki Language-Feature-Roadmap.
These tests pin ID-string generation and FindEntity round-trips for the
intricate encodings (nested generic types, `0/``0 type-parameter
references, ref/out @ suffixes, pointer and jagged/multi-dimensional
array forms incl. [0:,0:], conversion operators with the ~ReturnType
suffix, indexers, #ctor) plus XmlDocumentationProvider lookup by ID
string and by entity against a generated doc file. All green.

Assisted-by: Claude:claude-fable-5:Claude Code
The UnsafeCode fixture had no double-indirection coverage at all:
no pointer-to-pointer loads, stores, or indexing, and no fixed
statement over an element of a pointer array.

Assisted-by: Claude:claude-fable-5:Claude Code
Pins the reconstruction of a continue statement in a catch block
inside a foreach loop, which used to decompile to a goto targeting
a label at the end of the loop body. #2829 reported the goto form
and can be closed.

Assisted-by: Claude:claude-fable-5:Claude Code
All existing filter tests only read state; a filter that mutates a
ref local observed by the handler pins the ordering between the
filter expression and the handler body.

Assisted-by: Claude:claude-fable-5:Claude Code
Generic co-/contravariance had no test coverage at all: no out/in
variance modifier on any interface or delegate declaration and no
variant reference conversion appeared anywhere in the test suite.
The new fixture pins declarations (including a constrained covariant
interface and an explicit implementation of a variant interface) and
conversion shapes that must not produce explicit casts.

Assisted-by: Claude:claude-fable-5:Claude Code
All iterator producers in the fixture used concrete element types;
a generic method pins the reconstruction of the generic state
machine type.

Assisted-by: Claude:claude-fable-5:Claude Code
Overrides whose signatures use a substituted type parameter from a
generic base class were not covered.

Assisted-by: Claude:claude-fable-5:Claude Code
The lifted-operator matrix only used pure expressions; compound
assignment on nullable locals and boxing/unboxing conversions of
Nullable<T> were not covered.

Assisted-by: Claude:claude-fable-5:Claude Code
Anonymous types nested as members of other anonymous types (and read
back through the projection), ToString on an anonymous instance, and
explicit member projections were not covered.

Assisted-by: Claude:claude-fable-5:Claude Code
Virtual/override/sealed-override auto-properties, implicit and
explicit interface implementations, asymmetric accessor
accessibility, and auto-properties in structs were not covered.

Assisted-by: Claude:claude-fable-5:Claude Code
Only group-by continuations were covered; a continuation introduced
by select-into exercises a different transparent-identifier reset.

Assisted-by: Claude:claude-fable-5:Claude Code
AddChecked/MultiplyChecked/ConvertChecked nodes were not covered;
they are pretty-printed as a checked block around the tree-building
calls.

Assisted-by: Claude:claude-fable-5:Claude Code
dynamic used as a while-loop condition and the null-conditional
invocation operator on a dynamic receiver were not covered.

Assisted-by: Claude:claude-fable-5:Claude Code
@siegfriedpammer siegfriedpammer force-pushed the more-language-feature-tests branch 2 times, most recently from 427f5d2 to 3ca22f6 Compare July 4, 2026 12:07
await inside a null-coalescing expression, as a do-while condition,
on a ValueTask, and inside an interpolated-string hole (the
DefaultInterpolatedStringHandler lowering) were not covered.

Assisted-by: Claude:claude-fable-5:Claude Code
The fixture had an iterator local function but no async local
function; the async state-machine-in-local-function shape, static
and capturing, was not covered.

Assisted-by: Claude:claude-fable-5:Claude Code
protected and protected internal default interface members, static
properties and field-like events in interfaces, and a generic
interface with a constrained generic default method were not
covered.

Assisted-by: Claude:claude-fable-5:Claude Code
MaybeNullWhen, NotNull, and return-targeted MaybeNull had no
coverage anywhere in the suite.

Assisted-by: Claude:claude-fable-5:Claude Code
Only interface-based disposal was covered; a using declaration over
a ref struct with a pattern-based Dispose exercises the recognition
heuristic without IDisposable.

Assisted-by: Claude:claude-fable-5:Claude Code
ConfigureAwait(false) inside an async iterator and await foreach
over ConfiguredCancelableAsyncEnumerable were not covered.

Assisted-by: Claude:claude-fable-5:Claude Code
The C# 6 index-initializer syntax was only covered through custom
indexers; a real Dictionary<,> initialized with [key] = value pins
the choice between the index form and the Add form.

Assisted-by: Claude:claude-fable-5:Claude Code
Only two plain ASCII u8 literals were covered; the empty literal and
a literal made of escape sequences pin the recovery heuristic
boundaries.

Assisted-by: Claude:claude-fable-5:Claude Code
readonly record struct had no coverage, and no record overrode a
synthesized member; user-declared ToString and PrintMembers pin the
synthesized-vs-user member detection.

Assisted-by: Claude:claude-fable-5:Claude Code
SetsRequiredMembers had no coverage anywhere; required members
across a base/derived pair and in a generic abstract host were also
untested.

Assisted-by: Claude:claude-fable-5:Claude Code
Generic attributes were only closed over simple types; constructed
generic, array, and enum type arguments exercise separate paths in
the custom-attribute blob decoder.

Assisted-by: Claude:claude-fable-5:Claude Code
Compound assignments selecting between op_Addition and
op_CheckedAddition across a checked block boundary were not
covered.

Assisted-by: Claude:claude-fable-5:Claude Code
Static abstract interface members had no checked operator or
unsigned-right-shift coverage, and static abstract property getters
were never read as rvalues through the constraint.

Assisted-by: Claude:claude-fable-5:Claude Code
UnscopedRef only appeared on a params parameter anywhere in the
suite; a ref-returning method and property on a ref struct pin the
attribute round-trip.

Assisted-by: Claude:claude-fable-5:Claude Code
@siegfriedpammer siegfriedpammer force-pushed the more-language-feature-tests branch from 3ca22f6 to 84977e0 Compare July 4, 2026 12:36
@siegfriedpammer siegfriedpammer merged commit 32058da into master Jul 4, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant