gum-runtime-syntax-version
The integer version stamped on Gum runtime assemblies via GumSyntaxVersionAttribute, used by the tool's codegen to gate emitted code. Triggers when bumping the runtime syntax version, touching Assembl
SKILL.md.claude/skills/gum-runtime-syntax-version/SKILL.md
GitHubGum Runtime Syntax Version
Not the same thing as .gumx file format versioning (see gum-project-versioning). This is an assembly-level integer stamped on each runtime DLL that tells the Gum tool's codegen which conventions / namespaces / role interfaces the consumer's runtime supports.
Where it lives
- Attribute type:
GumDataTypes/GumSyntaxVersionAttribute.cs. - Stamped via
AssemblyAttributes.csin each runtime project:GumCommon/AssemblyAttributes.csMonoGameGum/AssemblyAttributes.cs(KniGum and FnaGum csprojs glob..\**\*.cs, so they inherit this stamp automatically)Runtimes/<Name>/AssemblyAttributes.csfor RaylibGum, SkiaGum and every Skia host (KniSkiaGum, SilkNetGum, StrideGum, UnityGum, SkiaGum.Standalone/.Maui/.ShadowDusk/.Wpf)
- Detection (tool side):
Tools/Gum.ProjectServices/CodeGeneration/SyntaxVersionDetectionService.cs. - Public docs / version table (the version history lives here, not in this skill):
docs/gum-tool/upgrading/syntax-versions.md— published at https://docs.flatredball.com/gum/gum-tool/upgrading/syntax-versions
How detection works
A manual SyntaxVersion in .codsj wins. Otherwise reads the consumer's .csproj (CodeProjectCsprojLocator prefers Assembly-CSharp.csproj, then the shortest name):
- If
ProjectReferenceto a runtime → opensAssemblyAttributes.csin that project's folder, or the folder above it (KniGum/FnaGum), and regex-parses the version. - Else if
PackageReference→ locates the DLL in the NuGet cache (NUGET_PACKAGESif set, else~/.nuget/packages), reads the attribute viaMetadataLoadContext. A floatingVersion="*"/"2026.*"resolves to the highest restored stable version in the cache, so the package must be restored before detection works. - Else if a
<Reference>has a<HintPath>to a runtime DLL (how Unity references Gum) → reads that DLL's attribute.
A runtime project or dll is recognized by name: <Engine>Gum or <Engine>Gum.<Host> (IsRuntimeName), so a new runtime or host named that way needs no list entry for ProjectReference or HintPath. Its NuGet package ID must still be added to GumPackageNames, because add-on packages (Gum.Expressions, Gum.Shapes.*) share the Gum. prefix. It does not go in the legacy-era lists.
When none of the three finds a version, the fallback depends on which runtimes the csproj references. Only the runtimes that existed before unification (LegacyEraRuntimeNames / LegacyEraPackageNames: MonoGame, KNI, FNA, Skia and the SkiaGum.Maui/.Wpf hosts) fall back to 0, because an old build lacks the attribute. Anything else (Stride, Silk.NET, raylib, a new runtime, or no Gum reference) resolves to SyntaxVersionDetectionService.LatestSyntaxVersion, the stamp on GumCommon.
GumCommon is not on the detection scan list — stamping it is for assembly-metadata consistency, not for codegen detection.
A detection or stamp change can change the version a Tests/CodeGen_* harness resolves, which makes its checked-in output stale. Detect_CodeGenHarnessProject_MatchesTheVersionItsOutputWasGeneratedAt pins each harness; when it fails, regenerate (see gum-tool-codegen) and update the pin.
When to bump
When the runtime surface that codegen cares about changes in a way that requires the tool to emit different code (renamed/removed role interfaces, new runtime types, namespace changes). Bump every stamped assembly (grep GumSyntaxVersion(Version) in lock step and add a row to the version table.
Gate on a safe floor, not the exact release that added the API
A codegen check like context.ResolvedSyntaxVersion >= N doesn't need to match the version that
introduced the target API — it only needs to guarantee the API exists. If an API shipped mid-cycle
inside an already-stamped version (so some assemblies at that version have it and some don't), gate
on the next bumped version instead. The cost is early adopters keep seeing the old code path a
little longer; the alternative — gating loosely to minimize that — risks emitting code that fails to
compile against older runtimes still reporting that version. See AddFindByNameAssignment in
CodeGenerator.cs, which gates FindFormsControl<T> on ResolvedSyntaxVersion >= 1 even though the
method shipped mid-version-0.
User-supplied usings follow the same gates
CodeGenerator.TranslateLegacyUsingStatements rewrites legacy namespaces in the .codsj CommonUsingStatements and element UsingStatements at the versions where generated code itself switches (MonoGameGum.GueDeriving/SkiaGum.GueDeriving -> Gum.GueDeriving at >= 1, MonoGameGum -> Gum at >= 3). A new namespace-unification bump that changes GetGueDerivingNamespace or GetGumServiceNamespace must extend that translation with the same threshold.
When NOT to bump
Pure renderable / Forms / sample changes that the codegen doesn't pattern-match against. The version is for codegen gates, not a general changelog.
Instance-Member Pattern vs Extension-Method Shims
When migrating a static extension to an instance method on GraphicalUiElement (or another GumCommon type), the instance method entirely eliminates the need for namespace-migration shims:
- Extension methods require a
usingdirective in scope; instance methods need nothing. - Two extensions with identical signatures cause CS0121 ambiguity when both namespaces are imported — instance methods sidestep this entirely (they always win over extensions).
- No
[Obsolete]spam: the old extension is deleted; call sites resolve to the instance method automatically.
This pattern is viable wherever a GumCommon seam (like IGumService.Default) can dispatch the work. Applied to AddToRoot / RemoveFromRoot at syntax version 3: the per-platform extension classes were deleted and the instance methods dispatch via IGumService.Default. See issue #3119.