Skip to content

feat(csharp): read union base-property dedupe facts from the IR#17043

Draft
fern-support wants to merge 1 commit into
will/union-base-props-irfrom
will/union-base-props-csharp
Draft

feat(csharp): read union base-property dedupe facts from the IR#17043
fern-support wants to merge 1 commit into
will/union-base-props-irfrom
will/union-base-props-csharp

Conversation

@fern-support

Copy link
Copy Markdown
Collaborator

Description

Linear ticket: Refs

PR 3 of 3 (stacked on the IR PR #17041). Adds the opt-in C# discriminated-union base-property dedupe, reading the decision from the IR (ObjectTypeDeclaration.deferredUnionBaseProperties) instead of computing it in the generator.

Written fresh (not stacked on #16975, which will be closed), but incorporating #16975's review feedback — see below.

DRAFT — blocked. Base is will/union-base-props-ir (#17041), not main. Does not compile until (1) @fern-fern/ir-sdk publishes 67.12 with deferredUnionBaseProperties and (2) csharp moves to irVersion: 67 (declared in the versions.yml entries here; today csharp is on 66 and the CLI strips v67 fields).

Changes Made

  • base/src/context/GeneratorContext.ts: new getBasePropertyWireNamesToOmitForType(typeId) that returns ir.types[typeId].deferredUnionBaseProperties wire names, gated on the flag. No computation, no reference-graph analysis, no caches.
  • codegen/.../CsharpConfigSchema.ts + generation-info.ts: opt-in dedupe-union-base-properties flag (kebab-case, default off).
  • model/src/generateFields.ts: extracted shared getGeneratedPropertyName helper (single source of truth for property naming).
  • model/src/object/ObjectGenerator.ts: variant-leaf classes + their example initializers omit the deferred base properties.
  • model/src/union/UnionGenerator.ts: generated example sets base properties on the envelope; the union JSON reader strips union-owned properties per-variant (never aggregated) before deserializing a variant.
  • Unit test for getGeneratedPropertyName; versions.yml entries (csharp-sdk + csharp-model, irVersion: 67); seed fixture csharp-union-base-properties.
  • Updated README.md generator (if applicable) — N/A

Learnings applied from #16975's review

  • "compare value type, not just wire name" (data-corruption footgun) → solved by construction: the IR's deferredUnionBaseProperties is computed with structural type equality, so a same-named-but-differently-typed field is never dropped.
  • "omission accumulates across unions / breaks standalone use" → solved by construction: the IR only marks objects used exclusively as a variant, and only props deferred by every owning union.
  • "shared naming helper" → base-property naming goes through getGeneratedPropertyName.
  • "use kebab-case config key" (@Swimburger) → dedupe-union-base-properties, matching every other C# config key.
  • "missing versions.yml entry" → added for both csharp-sdk and csharp-model.

Verification

  • Type-checked the entire change by temporarily stubbing the single deferredUnionBaseProperties access (the only line that needs the unpublished SDK field): all 17 csharp packages compile. The lone remaining error is that documented field access.

Remaining before this can merge

Testing

  • getGeneratedPropertyName unit test added (compiles; runs in CI once unblocked).

Generated with Claude Code

Add an opt-in `dedupe-union-base-properties` C# config flag that stops duplicating
a discriminated union's base properties on its `samePropertiesAsObject` variant
leaves. The decision is read from the IR fact
`ObjectTypeDeclaration.deferredUnionBaseProperties` (computed once in the IR with
structural type equality and an exclusive-variant guard) via a new
`GeneratorContext.getBasePropertyWireNamesToOmitForType` — the generator never
re-derives it.

Model policy (envelope owns the base props; variant leaves + their example
initializers omit them; the union JSON reader strips union-owned properties
per-variant before deserializing) lives in ObjectGenerator/UnionGenerator.

Written fresh rather than stacking on #16975 (which will be closed). Incorporates
that PR's review feedback: reading the structural IR fact removes the
differently-typed-field data-corruption footgun and the cross-union accumulation
concern by construction; base-property naming goes through the shared
`getGeneratedPropertyName` helper; config key is kebab-case per convention.

BLOCKED / DRAFT: stacks on the IR PR (#17041). Does not compile until
`@fern-fern/ir-sdk` publishes 67.12 with `deferredUnionBaseProperties` and csharp
moves to irVersion 67 (declared in versions.yml). Verified the rest type-checks by
temporarily stubbing the one field access (all 17 csharp packages compiled). Not
included: the C# dynamic-snippets leaf omission (needs @fern-api/dynamic-ir-sdk
republished; snippet tests stay disabled on the fixture until then) and the
regenerated seed output for the new fixture.

Co-Authored-By: Claude <noreply@anthropic.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.

2 participants