Skip to content

Support C# 14 extension members in CsSig - #32

Merged
agocke merged 4 commits into
mainfrom
extension-members
Jun 28, 2026
Merged

Support C# 14 extension members in CsSig#32
agocke merged 4 commits into
mainfrom
extension-members

Conversation

@agocke

@agocke agocke commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Summary

Projects using C# 14 extension members (extension(Receiver) { ... }) produced broken .cssig files: each block was emitted as a nameless public class { ... }. Roslyn models each block as a nested type with an unspeakable, compiler-generated name, which the writer rendered as an empty class name.

Changes

  • ExtensionMembers helper — recognises extension marker types and their receivers via INamedTypeSymbol.IsExtension / ExtensionParameter / TypeKind.Extension.
  • Writer — emits proper extension<T>(receiver) { ... } blocks (deterministically ordered), rendering the receiver with ref-kind and nullability.
  • Signature model — keys extension markers by a fixed <extension> discriminator plus the receiver type, so distinct blocks sharing a receiver are not conflated (soundness).
  • API surface — no longer double-counts the implicit implementation methods the compiler synthesises on the host static class; members are tracked through the extension marker types.
  • Roslyn 5.0 — bump the analyzer/code-fix projects from the Microsoft.CodeAnalysis 4.0 baseline to 5.0 (extension members require it), removing the previous reflection/numeric-enum workarounds and the now-stale (RefKind)4 fallback. RS1035/RS1037 (newly enforced) are suppressed via a documented global config.
  • Tests — bump the harness to Roslyn 5.x (Microsoft.CodeAnalysis.Analyzer.Testing 1.1.4, CSharp.Workspaces 5.3.0) and add round-trip + soundness regression tests.

Validation

  • Round-trip (generate → analyze) on extension members: 0 diagnostics.
  • Missing / extra extension members each report exactly one CSSIG001/CSSIG002.
  • Regenerated a real-world PublicAPI.cssig (serde): all extension(...) blocks correct, no nameless classes, nullable receivers preserved.
  • Full suite: 94 passed, 1 skipped.

agocke and others added 4 commits June 28, 2026 10:48
Roslyn models each `extension(Receiver) { ... }` block as a nested type
with an unspeakable, compiler-generated name. The writer emitted these as
nameless `public class { ... }` declarations, producing broken `.cssig`
files for projects that use extension members.

- Add ExtensionMembers helper isolating the Roslyn-version-sensitive logic
  (numeric TypeKind.Extension, reflective ExtensionParameter) so the
  analyzer keeps targeting the older Roslyn baseline.
- CsSigWriter now emits proper `extension<T>(receiver) { ... }` blocks
  (deterministically ordered), rendering the receiver with ref-kind and
  nullability.
- SignatureModel keys extension markers by the fixed `<extension>`
  discriminator plus their receiver type, so distinct blocks with the same
  receiver are not conflated.
- ApiSurface no longer double-counts the implicit implementation methods the
  compiler synthesises on the host static class; the members are tracked
  through the extension marker types instead.
- Bump the test harness to Roslyn 5.x (Microsoft.CodeAnalysis.Analyzer.Testing
  1.1.4, CSharp.Workspaces 5.3.0) and add round-trip + soundness regression
  tests for extension members.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Now that the analyzer requires C# 14 extension support anyway, bump the
analyzer/code-fix projects from the Microsoft.CodeAnalysis 4.0 baseline to
5.0 and drop the version-compatibility workarounds:

- ExtensionMembers uses INamedTypeSymbol.IsExtension / ExtensionParameter and
  TypeKind.Extension directly instead of numeric enum values and reflection.
- SignatureModel uses RefKind.RefReadOnlyParameter directly instead of the
  numeric (RefKind)4 fallback.
- Suppress RS1035/RS1037 (newly enforced by the updated analyzer-rules
  package) via a global config: RS1035 only fires on the third-party
  IndentingBuilder source, and RS1037 is an orthogonal optimisation hint.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Apply consistent multiline formatting across the CsSig projects and refine
the analyzer, recognizer, writer, signature model, and code-fix provider.
Switch the test harness from the obsolete XUnitVerifier-based
Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit package to
Microsoft.CodeAnalysis.CSharp.Analyzer.Testing 1.1.4.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
C# 14 extension-member support is a new feature.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@agocke
agocke merged commit dfc5b54 into main Jun 28, 2026
1 check passed
@agocke
agocke deleted the extension-members branch June 28, 2026 18:22
agocke added a commit that referenced this pull request Jun 28, 2026
…e members (#33)

Follow-up to #32. Fixes several cases where the `.cssig` writer and the
analyzer disagreed about the public API surface, found by running the
analyzer over the real `serde` project (which now builds clean against
the updated analyzer).

## Fixes
- **Positional records & record inheritance** — emit the primary
constructor parameter list and the record base clause in the type
header; skip the synthesized primary constructor and positional
properties (carried by the header).
- **Inaccessible-only constructors** — emit a `private` stub for
records/classes whose only declared constructors are inaccessible (e.g.
an abstract record with an explicit `private` parameterless ctor),
suppressing the parameterless constructor the compiler would otherwise
synthesize from the body-less form.
- **Generic constraints** — carry `where` clauses on types and methods.
Constraints change member semantics (`where T : struct` makes `T?` a
`Nullable<T>`) and distinguish overloads differing only by constraint.
- **Default & static interface members** — normalize away
virtual/abstract/override/sealed for interface members (a body-less
`.cssig` cannot express default-implementation-ness) and re-emit
`static`, which `SymbolDisplay` strips on interface members.
- **Type-parameter nullability** — collapse oblivious to not-annotated
for bare type-parameter references, reconciling the project's lexical
self-reference with the rebound reference the writer emits.

Bumps the package version to **0.3.0**.

## Tests
Adds round-trip tests: `RoundTripPositionalRecords`,
`RoundTripPrivateConstructorClass`, `RoundTripDefaultInterfaceMethods`,
`RoundTripGenericConstraints`. Full suite: 98 passing, 1 skipped.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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