fix(strawberryshake): dedupe duplicate fragment spreads in interface list (CS0528)#7
Open
cliedeman wants to merge 1 commit into
Open
fix(strawberryshake): dedupe duplicate fragment spreads in interface list (CS0528)#7cliedeman wants to merge 1 commit into
cliedeman wants to merge 1 commit into
Conversation
…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.
10 tasks
cliedeman
force-pushed
the
fix/ss-duplicate-fragment-spread-cs0528
branch
from
June 27, 2026 14:34
7b1008f to
423fd9f
Compare
Patch coverage100.0% of changed lines covered (6/6)
Project coverage: 52.4% (215301/411033 lines) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
Minimal reproductions (valid GraphQL, fail to compile before this change)
These generated, respectively:
Root cause
FieldCollector.ResolveFragmentSpreadadds oneFragmentNodeper spread, so two...UFspreads produce two nodes for the same fragment.FragmentHelper.CreateImplementsiterates those nodes and, for each, adds the interfaceOutputTypeModelreturned byCreateInterface. Because models are cached one-per-name, the same instance is added once per spread.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 toTypeDescriptorMapper.ExtractImplementsByand into the generated C#.Fix
In
FragmentHelper.CreateImplements, skip an interface model that is already present in theimplementslist. Models are cached one-per-name, so reference identity is sufficient and the change is minimal.src/StrawberryShake/CodeGeneration/src/CodeGeneration/Analyzers/FragmentHelper.csOutput 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
DuplicateFragmentSpreadGeneratorTestscovers 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.AssertResultRoslyn-compiles the generated output, so a CS0528 fails the test.CS0528 ... already listed in interface list; the four controls pass.StrawberryShake.CodeGeneration.CSharp.Testsproject green (195 passed, 1 pre-existing defer skip, 0 failed).— Claude
🤖 Generated with Claude Code