Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 21, 2026

Description

STJ source generator fails when JsonSerializerContext is split across multiple partial declarations with [JsonSerializable] attributes on each:

// File1.cs
[JsonSerializable(typeof(MyClass1))]
internal partial class SerializerContext : JsonSerializerContext { }

// File2.cs  
[JsonSerializable(typeof(MyClass2))]
internal partial class SerializerContext { }

Root cause: ForAttributeWithMetadataName triggers per ClassDeclarationSyntax with the attribute, but contextClassSymbol.GetAttributes() returns ALL attributes from ALL partial declarations. This causes duplicate AddSource calls with identical hintNames.

Fix: Added IsCanonicalPartialDeclaration() to select a single partial (first alphabetically by file path) to generate code for ALL types. Non-canonical partials return null from ParseContextGenerationSpec.

Changes

  • JsonSourceGenerator.Parser.cs: Added canonical partial detection via IsCanonicalPartialDeclaration(), integrated check before ParseJsonSerializerContextAttributes
  • JsonSourceGeneratorTests.cs: Added PartialContextClassWithAttributesOnMultipleDeclarations unit test with explicit file paths (File1.cs, File2.cs) to properly test canonical partial selection. The test properly preserves base compilation syntax trees (including netfx polyfill attributes) for cross-platform compatibility.
  • PartialContextTests.Part1.cs / PartialContextTests.Part2.cs: Created separate partial declaration files for runtime testing
  • JsonSerializerContextTests.cs: Added PartialContextWithAttributesOnMultipleDeclarations_RuntimeBehavior test that validates serialization/deserialization works correctly for types from both partial declarations

All source generator unit tests (including Roslyn 3.11 on .NET Framework) and runtime tests pass.

Original prompt

This section details on the original issue you should resolve

<issue_title>STJ source generator fails when defining the serializer context in 2 partial classes</issue_title>
<issue_description>### Description

If you define a serializer context (JsonSerializerContext) in 2 partial classes, the source generator fails with errors and a warning like this:

Warning	CS8785	Generator 'JsonSourceGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'ArgumentException' with message 'The hintName 'SerializerContext.MyClass1.g.cs' of the added source file must be unique within a generator.

We are doing this to support the following scenario:

  • The first partial class is produced by a code generator based on metadata.
  • We let developers add the second partial class to include classes in the serializer context that are not known to the code generator.

Reproduction Steps

Define a set of classes that need to be serialized:

public class MyClass1
{
    // (...)
}

public class MyClass2
{
    // (...)
}

Define a serializer context in a partial class:

[JsonSerializable(typeof(MyClass1))]
internal partial class SerializerContext : JsonSerializerContext
{
}

Define another partial class for the same serializer:

[JsonSerializable(typeof(MyClass2))]
internal partial class SerializerContext
{
}

This is easily reproducible in a .NET 8 console app.

Expected behavior

The source generator should be able to generate without errors.

Actual behavior

The following errors are produced when building:

warning CS8785: Generator 'JsonSourceGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'ArgumentException' with message 'The hintName 'SerializerContext.MyClass1.g.cs' of the added source file must be unique within a generator.

error CS0534: 'SerializerContext' does not implement inherited abstract member 'JsonSerializerContext.GetTypeInfo(Type)'

error CS0534: 'SerializerContext' does not implement inherited abstract member 'JsonSerializerContext.GeneratedSerializerOptions.get'

Regression?

This worked correctly in .NET 7.

Known Workarounds

Can't seam to find one...

Configuration

.NET 8.0.102

Other information

No response</issue_description>

Comments on the Issue (you are @copilot in this section)

@eiriktsarpalis I would inclined to ascribe this to by-design behavior. The source generator is triggered using `JsonSerializableAttribute` annotations, so if the same class declares attributes in multiple locations then generator would be triggered multiple times for the same context type. It wouldn't be easy to reconcile this without compromising the incremental caching properties of the generator. @eiriktsarpalis You're correct that this used to work in earlier versions. The current behaviour is a direct result of https://github.com//pull/86616, which compiles separate `JsonSerializerContext` declarations independently. This is motivated out of IDE performance concerns: the source generator should only emit code for the context you are currently editing. The drawback of that approach is that it's not possible to group incremental values by a given type without forcing re-evaluation of the entire graph, so the source generator will generate code twice for the same type if `JsonSerializable` are present in multiple declarations.

Given that this is a tooling-side regression impacting a rare use case that has a clear workaround, I don't think we should attempt to service this. At the same time, I'll reopen the issue so that we can try improving the situation in the future. For this though, I believe that we would require grouping functionality for the IncrementalValuesProvider<T> type in Roslyn.</comment_new>


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 3 commits January 21, 2026 04:37
…n multiple declarations

When a JsonSerializerContext is defined across multiple partial class declarations
with [JsonSerializable] attributes on different partials, the source generator now
correctly generates a single combined context containing all types from all partial
declarations, instead of failing with duplicate hintName errors.

The fix introduces IsCanonicalPartialDeclaration() which picks a deterministic
"canonical" partial (first alphabetically by file path) to process all attributes,
while other partials return null to avoid duplicate generation.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Address code review comments by clarifying edge case behavior for
empty file paths and explaining the fallback logic when no attributes
are found.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix source generator failure with serializer context in partial classes Fix STJ source generator for partial class contexts with JsonSerializable attributes on multiple declarations Jan 21, 2026
Copilot AI requested a review from stephentoub January 21, 2026 04:50
Copy link
Member

@eiriktsarpalis eiriktsarpalis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please also add a test that validates the run time behavior of the generated code, i.e. checking that all types across all partials have been included.

@eiriktsarpalis
Copy link
Member

@copilot please continue work on my previous feedback

@eiriktsarpalis
Copy link
Member

@copilot try again please

@stephentoub
Copy link
Member

@copilot, try again

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a source generator failure when JsonSerializerContext is split across multiple partial class declarations with [JsonSerializable] attributes on each. The issue was introduced in PR #86616 when the generator was changed to process each partial declaration independently for IDE performance. The root cause was that ForAttributeWithMetadataName triggers once per partial, but GetAttributes() returns attributes from all partials, causing duplicate code generation with identical hint names.

Changes:

  • Added canonical partial declaration detection to ensure only one partial generates code for all types
  • Implemented deterministic selection based on alphabetical file path ordering
  • Added comprehensive test coverage for the multi-partial scenario

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs Adds IsCanonicalPartialDeclaration() method that selects the first partial alphabetically by file path as canonical, and integrates this check in ParseContextGenerationSpec() to return null for non-canonical partials
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs Adds PartialContextClassWithAttributesOnMultipleDeclarations test that verifies the generator successfully handles partial contexts with attributes on multiple declarations and produces a single combined context

[Fact]
public void PartialContextClassWithAttributesOnMultipleDeclarations()
{
// Test for https://github.com/dotnet/runtime/issues/97460
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue number referenced in this comment is incorrect. The PR description mentions this fixes issue #99669, but the comment references #97460. Please update the comment to reference the correct issue number.

Suggested change
// Test for https://github.com/dotnet/runtime/issues/97460
// Test for https://github.com/dotnet/runtime/issues/99669

Copilot uses AI. Check for mistakes.
@eiriktsarpalis
Copy link
Member

@copilot please address the remaining feedback

@eiriktsarpalis
Copy link
Member

@copilot can you try again?

@stephentoub
Copy link
Member

@copilot try again

@stephentoub
Copy link
Member

@copilot try again

@eiriktsarpalis
Copy link
Member

@copilot you still there?

…e declarations

- Fixed issue number in unit test comment (97460 -> 99669)
- Added explicit file paths to unit test syntax trees to properly test canonical partial selection
- Added runtime test that validates serialization/deserialization works for types from both partial declarations
- Created PartialContextTests.Part1.cs and PartialContextTests.Part2.cs with separate partial declarations
- Removed incomplete test types from ContextClasses.cs (they were not being used)

Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 23, 2026

@copilot you still there?

Yes! I've addressed the remaining feedback in commit b1d515d:

  1. Fixed the issue number reference in the unit test comment (97460 → 99669)
  2. Added explicit file paths (File1.cs, File2.cs) to the unit test syntax trees to properly demonstrate canonical partial selection
  3. Added a runtime test PartialContextWithAttributesOnMultipleDeclarations_RuntimeBehavior that validates:
    • Both types from both partial declarations are accessible via the generated context
    • Serialization and deserialization work correctly for types from each partial
  4. Created PartialContextTests.Part1.cs and PartialContextTests.Part2.cs with proper separate partial declarations for the runtime test
  5. Removed the incomplete/unused test types from ContextClasses.cs

The test was failing on .NET Framework because it was removing
all syntax trees from the base compilation, which also removed
the polyfill attributes needed for netfx. Now the test preserves
those trees while adding the test-specific syntax trees with
explicit file paths.

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

STJ source generator fails when defining the serializer context in 2 partial classes

3 participants