Skip to content

Fix .cssig round-trips for records, generic constraints, and interface members - #33

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

Fix .cssig round-trips for records, generic constraints, and interface members#33
agocke merged 9 commits into
mainfrom
extension-members

Conversation

@agocke

@agocke agocke commented Jun 28, 2026

Copy link
Copy Markdown
Owner

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.

agocke and others added 9 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>
…e members

Resolves several cases where the writer and analyzer disagreed about the API
surface, surfaced by running the analyzer over the serde project:

- Positional records: emit the primary constructor parameter list and the
  record base clause in the type header, and skip the synthesized primary
  constructor and positional properties (they are carried by the header).
- Constructors: emit a `private` stub for records/classes whose only declared
  constructors are inaccessible (including an abstract record with an explicit
  private parameterless constructor), to suppress the public/protected
  parameterless constructor the compiler would otherwise synthesize.
- Generic constraints: carry `where` clauses on types and methods. Constraints
  change member semantics (`where T : struct` makes `T?` a Nullable<T>) and
  distinguish overloads that differ only by constraint.
- Default interface members and static interface members: normalize away
  virtual/abstract/override/sealed for interface members (a bodyless .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 lexical self-reference seen on the
  project side with the rebound reference the writer emits.

Bumps the package version to 0.3.0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ivate members

Drop the private-constructor stub. Implicit public/protected parameterless
constructors are emitted explicitly as () primary-constructor syntax, and the
analyzer ignores synthesized parameterless constructors on the .cssig side so a
type with only an inaccessible ctor declares none. No private members appear in
.cssig.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…SIGN.md

The .cssig grammar now rejects private, bare internal, and private protected
on any member, accessor, or type (CSSIG004); only public/protected/protected
internal are expressible. Trim the analyzer README to usage essentials and move
internals to DESIGN.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts:
#	src/CsSig/Analyzer/ApiSurface.cs
#	src/CsSig/Analyzer/CsSigWriter.cs
#	src/CsSig/Analyzer/IsExternalInit.cs
#	src/CsSig/Analyzer/SignatureModel.cs
#	src/CsSig/CodeFixes/StaticCs.CsSig.CodeFixes.csproj
@agocke
agocke merged commit 50b1416 into main Jun 28, 2026
1 check passed
@agocke
agocke deleted the extension-members branch June 28, 2026 23:00
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