Generate metadata name lookups as Swift source instead of loading JSON at runtime - #232
Merged
Merged
Conversation
Emits a switch statement mapping raw company identifier values to their SIG-assigned names, mirroring the existing numeric-constant generator instead of loading Resources/CompanyIdentifier.json at runtime.
Merges the six UUID category files (service, characteristic, declaration, descriptor, member, unit) into a single switch keyed by raw value, returning name and type, in a fixed category precedence order. Collisions across categories are reported at build time and resolved by keeping the first category in that order.
Also fixes a pre-existing arguments[3]/arguments[2] typo in the .unitIdentifierTests case that crashed with an out-of-range index whenever this command path was actually invoked.
Replaces thousands of individually unrolled #expect() calls with a single @test(arguments:) call site over a generated entries array, built with sequential append() calls rather than one large array literal. Both unrolled #expect() chains and large array literals are pathologically slow for the type checker at this data's scale (~4000 entries); a single parameterized test site with straight-line data construction compiles in seconds instead of minutes.
Same rewrite as CompanyIdentifierTests. Also fixes the generated description expectation, which composed a "0x... (name)" string that never matched UnitIdentifier.description's actual output (just the name, consistent with CompanyIdentifier.description) — previously undetected because this generated test was never actually invoked due to the arguments[3] bug and the Package.swift target-index bug.
Invokes GenerateBluetooth companyIdentifierMetadata to emit CompanyIdentifierNames.swift into the Bluetooth target.
Invokes GenerateBluetooth uuidMetadata to emit BluetoothUUIDMetadataNames.swift into the Bluetooth target.
BluetoothUUID.metadata now reads from the build-time-generated generatedMetadata(for:) switch instead of loading and merging all six UUID category JSON files (and their BluetoothMetadata module) at runtime. Introduces a Foundation-free BluetoothUUID.Metadata struct in place of the old BluetoothMetadata.BluetoothUUID return type — this is an intentional breaking API change, since the old type's home module pulls in Foundation unconditionally.
CompanyIdentifier.name now reads from the build-time-generated generatedName(for:) switch instead of loading and parsing CompanyIdentifier.json (via the BluetoothMetadata module) at runtime.
UnitIdentifier.name/type already delegate to BluetoothUUID.metadata, which no longer needs Foundation or the BluetoothMetadata module now that it reads from generated Swift code.
description and init?(_:) only reach through self.metadata, which no longer touches Foundation, WASI, or Embedded Swift concerns now that it's backed by generated Swift code rather than a JSON-loading runtime dependency.
package.targets[4] no longer pointed at BluetoothTests once BluetoothSDP was inserted ahead of it, so GenerateBluetoothDefinitions was never actually attached to the test target and its generated test files were never compiled or run. Look targets up by name instead of a hardcoded index.
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.
Summary
Replaces
CompanyIdentifier.name,BluetoothUUID.metadata, andUnitIdentifier.name/type's runtime JSON loading (Bundle.module+Data(contentsOf:)+JSONDecoder, via theBluetoothMetadatamodule) with build-time-generated Swift switch statements, extending the existingGenerateBluetoothDefinitionsplugin/GenerateBluetoothtool that already generates the numeric identifier constants (CompanyIdentifier.appleand friends) the same way.Sources/GenerateBluetooth/CompanyIdentifierMetadata.swift/Plugins/.../CompanyIdentifierMetadata.swift: generatesCompanyIdentifier.generatedName(for:), a switch over all 4012 company identifiers.Sources/GenerateBluetooth/BluetoothUUIDMetadata.swift/Plugins/.../BluetoothUUIDMetadata.swift: merges the six UUID category JSON files (service, characteristic, declaration, descriptor, member, unit — in that fixed precedence order) intoBluetoothUUID.generatedMetadata(for:). Collisions across categories are reported at build time; none currently exist.Sources/Bluetooth/{CompanyIdentifierMetadata,BluetoothUUIDMetadata,UnitIdentifierMetadata}.swiftnow call the generated switch functions instead of loading JSON, and no longer import Foundation orBluetoothMetadata.The
BluetoothMetadatamodule/product itself is untouched — it's still available for anyone who wants raw programmatic access to the JSON, and the build-time generator tool still depends on it to read the source JSON (a build-time-only dependency, never shipped).Breaking API change
BluetoothUUID.metadata's return type changes fromBluetoothMetadata.BluetoothUUIDto a newBluetoothUUID.Metadatastruct (name: String,type: String?) declared inSources/Bluetooth. This is necessary because the old type's home module transitively pulls in Foundation, which would have defeated the point of this change.#if Metadataconditions that previously also gated oncanImport(Foundation) && !os(WASI) && !hasFeature(Embedded)are simplified to just#if Metadata, since the lookup no longer touches any of those.Incidental fixes
While wiring this up, found three pre-existing, previously-latent bugs (all masked each other, so none had ever actually been exercised):
Package.swiftusedpackage.targets[4].plugins = [...]to attachGenerateBluetoothDefinitionstoBluetoothTests, but that index silently pointed atBluetoothHCIafterBluetoothSDPwas inserted ahead of it — the plugin was never actually attached to the test target. Now looked up by name..unitIdentifierTestscrashed with an out-of-range index:arguments[3]should have beenarguments[2].UnitIdentifierTests.swiftfailed — itsdescriptionexpectation was built as"0x... (name)", which never matchedUnitIdentifier.description's actual output (just the name, consistent withCompanyIdentifier.description).Test generation redesign
The original generated test files unrolled one
#expect()call per data entry (~4012 × 3 for company identifiers alone). That's pathologically slow for the type checker — many minutes, not seconds — since swift-testing's macro expansion cost scales with the number of source-level#expect()call sites, not how many times each executes. Chunking into many smaller@Test funcbodies didn't help either, and neither did building the test data as one large array literal (a similar type-checker scaling problem, independent of macros).Both
CompanyIdentifierTestsandUnitIdentifierTestsare now generated as a single@Test(arguments:)parameterized test over an entries array built with sequential.append()calls (straight-line statements, same shape as the metadata switch generators, which already compile fast at this scale).BluetoothTestsnow builds in under a minute instead of hanging indefinitely.Test plan
swift build --traits Metadata --target BluetoothTests— 38sswift test --traits Metadata— 497 tests, 34 suites, passswift test(default, no traits) — 495 tests, 32 suites, passswift build(default, no traits) — builds clean