Skip to content

Read, write, parse and compare C# 14 extension blocks - #109

Merged
Mpdreamz merged 1 commit into
mainfrom
extension-members
Nov 15, 2026
Merged

Mpdreamz merged 1 commit into
mainfrom
extension-members

Conversation

@Mpdreamz

Copy link
Copy Markdown
Contributor

C# 14 extension blocks were read as the compiler's lowering: a nested <G>$… type nobody can name and a set of static methods. They are now first-class.

Reading and writing.

  • A new ApiExtension member holds the receiver, type parameters and members of each block.
  • The file writes a block as the source declares it, extension<T>(List<T> list) where T : struct, with its members indented under it.
  • The static implementation methods are left out. A receiver that only static members use has no name, extension(string).
  • The parser reads blocks back to the same model.

Comparing. Each block member is paired with its receiver.

  • A member that moves to another block over the same type is a receiver change on that member, not a removal and an addition.
  • Rewriting static int A(this string s) as a block member, or back, reports no change: callers compile and run against the same method.

An older bug. Reading System.Private.CoreLib (whose Vector128 and others have blocks with no parameter rows) turned up a crash in ReadParameters: a parameter without a row got a default CustomAttributeHandleCollection, which has no reader.

Verified:

  • The Roslyn oracle compares blocks, receivers and their members, and skips the implementation methods. It agrees on CoreLib and the other framework assemblies.
  • The library generator now writes blocks: instance and static members, generic blocks with constraints, ref receivers and unnamed receivers. All properties pass: compile, oracle, round trip, split files, self-comparison, mirror.
  • New writer sample and six comparison scenarios.
  • Mutations caught:
    • block identity ignoring its member, in both equality and hash;
    • no flattening of blocks;
    • keeping the implementation methods.
  • mise run ci is green locally.

🤖 Generated with Claude Code

A static class's extension blocks are read from the compiler's grouping and marker types and written as the
source declares them, `extension<T>(List<T> list) where T : struct`, with their members under them; the static
methods the compiler writes for those members are left out. A receiver only static members use has no name and
is written `extension(string)`.

The comparison pairs each block member with its receiver, so a member that moves to another block over the same
type is a receiver change on that member, and a method that moves from `static int A(this string s)` into a
block, or back, is no change: callers compile and run against the same method.

Reading System.Private.CoreLib found an old bug: a parameter without a metadata row got a default attribute
collection, which has no reader. Extension blocks there have no parameter rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Mpdreamz Mpdreamz added the feature A new capability (minor bump) label Nov 15, 2026
@Mpdreamz
Mpdreamz merged commit e769153 into main Nov 15, 2026
8 checks passed
@Mpdreamz
Mpdreamz deleted the extension-members branch November 15, 2026 07:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature A new capability (minor bump)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant