Skip to content

fix(strawberryshake): dedupe duplicate fragment spreads in interface list (CS0528)#7

Open
cliedeman wants to merge 1 commit into
fork-ci-basefrom
fix/ss-duplicate-fragment-spread-cs0528
Open

fix(strawberryshake): dedupe duplicate fragment spreads in interface list (CS0528)#7
cliedeman wants to merge 1 commit into
fork-ci-basefrom
fix/ss-duplicate-fragment-spread-cs0528

Conversation

@cliedeman

@cliedeman cliedeman commented Jun 27, 2026

Copy link
Copy Markdown

Problem

Spreading the same named fragment two or more times in one selection set makes Strawberry Shake emit the fragment's generated interface twice in the operation result type's base-interface list. Code generation reports no error, but the generated C# fails to compile:

error CS0528: 'IUF' is already listed in interface list

Minimal reproductions (valid GraphQL, fail to compile before this change)

query Q { me { ...UF ...UF } }            fragment UF on User { id name }
query Q { node(id: "1") { ...NF ...NF } }  fragment NF on Node { id }

These generated, respectively:

public partial interface IQ_Me : IUF, IUF      // CS0528
public partial interface IQ_Node : INF, INF    // CS0528

Root cause

  • FieldCollector.ResolveFragmentSpread adds one FragmentNode per spread, so two ...UF spreads produce two nodes for the same fragment.
  • FragmentHelper.CreateImplements iterates those nodes and, for each, adds the interface OutputTypeModel returned by CreateInterface. Because models are cached one-per-name, the same instance is added once per spread.
  • The existing dedup in CreateInterface (IsImplementedBy) only removes a model that is implemented by another model in the list, not a literal duplicate, so the duplicate base interface flows through to TypeDescriptorMapper.ExtractImplementsBy and into the generated C#.

Fix

In FragmentHelper.CreateImplements, skip an interface model that is already present in the implements list. Models are cached one-per-name, so reference identity is sufficient and the change is minimal.

src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FragmentHelper.cs

Output is unchanged for single spreads, two distinct fragments, the same fragment at different selection levels, and union members (verified by control tests and the full existing suite).

Tests

New DuplicateFragmentSpreadGeneratorTests covers both repros plus four control/regression guards (single spread, two different fragments, same fragment at different levels, same fragment twice on a union member). GeneratorTestHelper.AssertResult Roslyn-compiles the generated output, so a CS0528 fails the test.

  • Before fix: the two repro tests fail with CS0528 ... already listed in interface list; the four controls pass.
  • After fix: all 6 pass; full StrawberryShake.CodeGeneration.CSharp.Tests project green (195 passed, 1 pre-existing defer skip, 0 failed).

— Claude
🤖 Generated with Claude Code

…list

Spreading the same named fragment two or more times in a single selection
set caused the generated operation result type to list the fragment's
generated interface more than once in its base-interface list, producing
C# that fails to compile with CS0528 ('IUF' is already listed in interface
list). Generation reported no error; only the downstream compile failed.

Repros (valid GraphQL, fail to compile before this change):
  query Q { me { ...UF ...UF } } fragment UF on User { id name }
  query Q { node(id: "1") { ...NF ...NF } } fragment NF on Node { id }

Root cause: FieldCollector.ResolveFragmentSpread adds one FragmentNode per
spread, so duplicate spreads yield duplicate nodes. FragmentHelper.CreateImplements
then iterates those nodes and adds the same cached interface OutputTypeModel
once per spread. The existing IsImplementedBy dedup in CreateInterface only
removes a model that is implemented by another model in the list, not a literal
duplicate, so the duplicate base interface reached the generated C#.

Fix: in CreateImplements, skip an interface model that is already present in
the implements list. Models are cached one-per-name, so reference identity is
sufficient. Output for single spreads, distinct fragments, same fragment at
different levels, and union members is unchanged.

Adds DuplicateFragmentSpreadGeneratorTests covering both repros plus four
control cases; AssertResult Roslyn-compiles the output, so a CS0528 fails the
test.
@github-actions

Copy link
Copy Markdown

Patch coverage

100.0% of changed lines covered (6/6)

File Covered Changed Patch %
…/src/CodeGeneration/Analyzers/FragmentHelper.cs 6 6 100.0% 🟢

Project coverage: 52.4% (215301/411033 lines)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants