build: adopt the zero-cost Swift 7 upcoming features on VRMMetalKit - #392
Conversation
Measured one at a time against the VRMMetalKit target on Swift 6.3.3 from a clean 0-diagnostic baseline, counting `swift build --target VRMMetalKit` output: | setting | diagnostics | sites | adopted | |----------------------------------|-------------|-------|---------| | InferIsolatedConformances | 0 | 0 | yes | | ImmutableWeakCaptures | 0 | 0 | yes | | MemberImportVisibility | 3 errors | 3 | yes | | ExistentialAny | 1255 warn | 210 | no | | NonisolatedNonsendingByDefault | 0 | 0 | no | | InternalImportsByDefault | 2619 errors | 85 | no | MemberImportVisibility needed three explicit imports for members that were previously reaching this module transitively: CoreGraphics in SpriteCacheSystem (CGSize.init(width:height:)), and Metal in VRMMaterialReport (MTLTexture.width/height) and SpringBoneBreastCollider (MTLBuffer.contents()). That is the whole cost. Not adopted, with reasons: - ExistentialAny is warnings-only in Swift 6 language mode, so it does not block, but it is 210 unique sites across 30+ files (44 in VRMRenderer alone) — mostly `MTLBuffer`/`MTLTexture` existentials. Mechanical but large enough to swamp review; belongs in its own PR. - NonisolatedNonsendingByDefault compiles clean, but SE-0461 changes runtime semantics (nonisolated async functions start on the caller's actor). #384/#387 deliberately established a nonisolated `drawOffscreen` driven from a non-main render thread; a 0 here is not evidence that contract is unaffected, and the suite drives rendering synchronously so it would not catch a regression. - InternalImportsByDefault needs `public import` across the API surface, which for a library is the whole point of the module. Full suite passes; the only remaining build diagnostics are two pre-existing `withUnsafeMutableBytes` warnings in GLTFCore/BufferLoader. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…features # Conflicts: # Package.swift
The two packed-accessor fast paths reached for the C `memcpy` symbol, which BufferLoader only sees through Foundation's implicit re-export of Darwin. UnsafeMutableRawPointer.copyMemory is the stdlib equivalent for the same non-overlapping copy, so the call no longer depends on a transitively imported module. These were the only memcpy uses in GLTFCore. GLTFCore does not enable MemberImportVisibility, so this is preparatory rather than required by the target's current settings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FHQ86b5tGQUF2kJKgCZ8mc
|
✅ Claude Code Review ran on Findings (if any) are posted as inline comments; none means the review found nothing to flag. Run log. |
|
Gitar review |
|
Note Automatic reviews are paused because your team has used its included automatic processing for this billing period (headroom scales with your seat count). You can still comment "Gitar review" to run one anytime, and automatic reviews resume on their own by September 1. Add seats for more headroom. Code Review ✅ ApprovedAdopts zero-cost Swift 7 upcoming features (InferIsolatedConformances, ImmutableWeakCaptures, and MemberImportVisibility) on VRMMetalKit. No issues found. Was this helpful? React with 👍 / 👎 | Gitar |
Running the review now — results will be posted to the dashboard shortly. |
Adopts the Swift 7 upcoming features that cost nothing, and documents — with measurements — what adopting each of the rest would buy and cost. The intent is that this PR answers "why not the others?" without anyone having to re-run the experiment.
Draft: the scope question at the end is worth a decision before this lands.
Measured
Each feature enabled one at a time on the
VRMMetalKittarget, Swift 6.3.3, from a clean 0-diagnostic baseline. Mirrors theMuseCoremethodology, so the numbers are comparable.InferIsolatedConformancesImmutableWeakCapturesMemberImportVisibilityExistentialAnyNonisolatedNonsendingByDefaultInternalImportsByDefaultCount sites, not diagnostics.
ExistentialAny's 1,255 warnings are 210 real sites — a raw diagnostic count overstates the work ~6×. Same distinction behind MuseCore's "was 83 sites, not 1,169" note.Adopted — what each buys
InferIsolatedConformances— conformances on actor-isolated types infer that isolation instead of requiring explicit annotation. Removes a class of spurious isolation errors before they appear.ImmutableWeakCaptures—weakcaptures in closures become immutable. Catches the reassign-a-weak-capture bug, where the write silently doesn't do what the author intended.MemberImportVisibility— members require an explicit import of their defining module. This one earned its keep immediately: it found three places relying on transitive imports, which would break silently if a dependency ever changed what it re-exports.Cost of adoption was exactly three imports:
CoreGraphicsinSpriteCacheSystem—CGSize.init(width:height:)MetalinVRMMaterialReport—MTLTexture.width/.heightMetalinSpringBoneBreastCollider—MTLBuffer.contents()Not adopted
ExistentialAny— 210 sites, buys forward-compat onlyWarnings-only in Swift 6 language mode, so it never blocks a build today. It becomes a hard error in Swift 7, so these sites must be fixed eventually regardless.
What adopting buys: exactly one thing — Swift 7 readiness. It converts a forced future migration, arriving on the toolchain's schedule, into a chosen one.
What it does not buy, which matters before spending review budget on a 210-site sweep:
MTLBuffer60,MTLDevice37,MTLRenderPipelineState23,MTLTexture17,MTLCommandBuffer14, …)ErrorAnimationLayerDecoder/Encoderany MTLBufferis identical codegen toMTLBuffer.MTLBufferforsome/generics; Metal hands you existentials by construction. The annotation surfaces nothing a reader didn't know.any PandPdenote the same type and the spelling requirement is per-module, so downstream consumers are unaffected whether we adopt or not. This is not a source break being got ahead of.The one interesting slice is
AnimationLayer(4 sites) — a project protocol, whereanyvssomeis a genuine design question and might reveal an existential that needn't be one. The other ~206 are find-and-replace.If adopted, sequence it after #380. Of the six heaviest files, only
SpringBoneComputeSystem.swift(23 sites) is also touched by that 68-commit branch — one conflict, but a tedious one to resolve by hand across a mechanical sweep.NonisolatedNonsendingByDefault— 0 diagnostics, but not freeSE-0461. Measures 0 here where MuseCore measured 15, so it looks like the obvious next adoption. It isn't.
What it would buy: nonisolated
asyncfunctions begin executing on the caller's executor instead of hopping to the generic executor — fewer actor hops and lessSendablefriction at call boundaries.Why not yet: that is a runtime semantic change, not a compile-time one. A 0 means the compiler found nothing to complain about, not that behavior is unchanged. #384/#387 deliberately established a nonisolated
drawOffscreendriven from a non-main render thread with a documented single-producer contract; this feature changes where such work starts running. The test suite drives rendering synchronously, so neither a clean build nor a green suite is evidence that contract survives. This wants a concurrency review, not a diagnostic count.InternalImportsByDefault— 2,619 diagnostics across 85 filesSE-0409. Imports become
internalby default; anything appearing in the public API needspublic import.What it would buy: an explicit, checked statement of which dependencies are part of the API versus implementation detail — and, in principle, less transitive module loading for consumers. (Build-time impact not measured here; treat it as a claim, not a finding.)
Why not: same story as MuseCore's 8,926, and arguably worse in kind. A library's broad public surface is the module's purpose, so most of the 85 files would need
public importrather than benefiting from the tightening. High churn, low return.Verification
swift package clean+ fullswift build: the only remaining diagnostics are two pre-existing warnings (GLTFCore/Loader/BufferLoader.swift:209and:293,withUnsafeMutableBytesresult unused), unrelated to this change and already visible on the Xcode Cloud visionOS build.Scope
Only the
VRMMetalKittarget is covered — that is what was measured.GLTFCore,GLTFMetalKit, the CLI executables, and the test targets remain unadopted. Extending the three settings package-wide is a reasonable follow-up, but needs the same per-target measurement first; I would rather not assumeGLTFCoreis clean becauseVRMMetalKitis.