Skip to content

[Fusion] Build the introspection schema programmatically - #10174

Merged
glen-84 merged 1 commit into
mainfrom
gai/programmatic-introspection-schema
Jul 31, 2026
Merged

glen-84 merged 1 commit into
mainfrom
gai/programmatic-introspection-schema

Conversation

@glen-84

@glen-84 glen-84 commented Jul 31, 2026 •

Copy link
Copy Markdown
Member

Summary

The introspection schema was maintained as two near-identical SDL documents: a base copy, and a full copy with the opt-in feature fields and arguments interleaved. Adding one conditional field or argument meant editing every copy, and further options would multiply them, because SDL type extensions can add fields but cannot add arguments to an existing field.

IntrospectionSchema now builds the document from syntax nodes, one method per introspection type, with each conditional field and argument declared once beside the definition it belongs to. SemanticIntrospectionSchema folds into the same builder and is removed. Documents are cached and shared by shape, and the shape is a key type naming every option the document depends on, so the document cannot be built from an option that is not part of its cache key.

One behavior change: __Directive.requiresOptIn is now the last field of __Directive instead of preceding isDeprecated, matching where the Core introspection types append it. All other output is unchanged.

Performance

Measured against main in Release on .NET 10, 20k iterations per case.

The first build of a given shape replaces lexing and parsing the SDL with direct node
construction, so it gets cheaper on both axes:

first build of a shape: time (lower is better)

  base            main    10.7 µs          █████████████████████
                branch     6.9 µs   −35%   ██████████████

  opt-in          main    12.8 µs          ██████████████████████████
                branch     7.6 µs   −40%   ███████████████

  base+semantic   main    11.5 µs          ███████████████████████
                branch     8.1 µs   −29%   ████████████████


first build of a shape: allocations (lower is better)

  base            main    26.7 KB          ████████████████████
                branch    19.1 KB   −28%   ██████████████

  opt-in          main    33.5 KB          █████████████████████████
                branch    20.1 KB   −40%   ███████████████

  base+semantic   main    30.5 KB          ███████████████████████
                branch    21.8 KB   −29%   ████████████████

Every later build of that shape is served from the cache, as it was on main:

  main      0.03–0.23 µs      136 B   (LINQ Concat at the call site)
  branch    0.00–0.13 µs        0 B

Those timings are at noise level and should be read as "free" on both sides rather than
compared to each other. The allocation difference is real: the call site no longer builds
a Concat iterator to splice the semantic definitions on.

For scale, a full FusionSchemaDefinition.Create of the fusion1 fixture is ~544 µs and
~286 KB, so none of the above is on a hot path.

Test plan

  • Printed the generated document for all four option combinations and diffed against the SDL on main, parsed and re-printed on both sides: identical apart from the __Directive.requiresOptIn position noted above.
  • Confirmed that shapes requested interleaved and repeated return stable, correct documents.
  • Full Fusion.Execution.Tests and the Fusion.AspNetCore.Tests introspection tests pass.

Copilot AI review requested due to automatic review settings July 31, 2026 12:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 replaces the Fusion introspection schema’s SDL-text parsing approach with a programmatic builder that constructs DocumentNode definitions from syntax nodes, caching immutable documents by an explicit “shape” key derived from IFusionSchemaOptions. It also folds the former semantic introspection SDL into the same builder and removes the redundant SemanticIntrospectionSchema class.

Changes:

  • Build the introspection schema programmatically (one method per introspection type) and cache per option “shape”.
  • Integrate semantic introspection type definitions into the same builder and remove SemanticIntrospectionSchema.
  • Update CompositeSchemaBuilder to consume the unified IntrospectionSchema.GetDocument(options) output.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/HotChocolate/Fusion/src/Fusion.Execution.Types/Completion/SemanticIntrospectionSchema.cs Removed redundant SDL-based semantic introspection document source (now built by IntrospectionSchema).
src/HotChocolate/Fusion/src/Fusion.Execution.Types/Completion/IntrospectionSchema.cs Introduces the programmatic introspection schema builder with per-shape caching and conditional fields/args.
src/HotChocolate/Fusion/src/Fusion.Execution.Types/Completion/CompositeSchemaBuilder.cs Switches introspection definition sourcing to the new unified cached document.
Suppressed comments (4)

src/HotChocolate/Fusion/src/Fusion.Execution.Types/Completion/IntrospectionSchema.cs:166

  • When(optIn, Field(...)) still constructs the Field(...) node and allocates the params array even when optIn is false. Prefer a conditional collection expression so the field is only created for the opt-in shape.
                .. When(optIn, Field("requiresOptIn", "[String!]"))

src/HotChocolate/Fusion/src/Fusion.Execution.Types/Completion/IntrospectionSchema.cs:225

  • When(optIn, s_includeOptIn) allocates a params array even when optIn is false. A simple conditional avoids that extra allocation and keeps the opt-in argument out of the base shape path.
    private static InputValueDefinitionNode[] FilterArguments(bool optIn)
        => [s_includeDeprecated, .. When(optIn, s_includeOptIn)];

src/HotChocolate/Fusion/src/Fusion.Execution.Types/Completion/IntrospectionSchema.cs:141

  • When(optIn, Field(...)) still constructs the Field(...) node and allocates the params array even when optIn is false. Prefer a conditional collection expression so the field is only created for the opt-in shape.
                .. When(optIn, Field("requiresOptIn", "[String!]"))

src/HotChocolate/Fusion/src/Fusion.Execution.Types/Completion/IntrospectionSchema.cs:152

  • When(optIn, Field(...)) still constructs the Field(...) node and allocates the params array even when optIn is false. Prefer a conditional collection expression so the field is only created for the opt-in shape.
                .. When(optIn, Field("requiresOptIn", "[String!]"))

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

Copy link
Copy Markdown
Contributor

Patch coverage

100.0% of changed lines covered (201/201)

File Covered Changed Patch %
…/Fusion.Execution.Types/Completion/CompositeSchemaBuilder.cs 1 1 100.0% 🟢
…/src/Fusion.Execution.Types/Completion/IntrospectionSchema.cs 200 200 100.0% 🟢

Project coverage: 54.2% (236232/435832 lines)

@glen-84
glen-84 merged commit 3c40323 into main Jul 31, 2026
290 of 292 checks passed
@glen-84
glen-84 deleted the gai/programmatic-introspection-schema branch July 31, 2026 13:17
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