Skip to content

build: adopt the Swift 6 language mode across the app, plugins and packages - #2311

Merged
datlechin merged 1 commit into
mainfrom
build/swift-6-language-mode
Aug 20, 2026
Merged

build: adopt the Swift 6 language mode across the app, plugins and packages#2311
datlechin merged 1 commit into
mainfrom
build/swift-6-language-mode

Conversation

@datlechin

Copy link
Copy Markdown
Member

Moves the whole project to the Swift 6 language mode, which is the newest the toolchain accepts (-swift-version 6.1, 6.2 and 7 are all rejected; Swift 6.4 is the compiler, 6 is the language mode).

Before After
Configs/Base.xcconfig SWIFT_VERSION = 5.0 6.0
Packages/TableProCore, Packages/TableProOracle swift-tools-version: 5.9 6.0
.swiftformat --swiftversion 5.9 6.0

LocalPackages/ (the vendored CodeEdit forks) stays on 5.9 so it can still take upstream changes. An xcconfig does not reach SwiftPM targets, which is why the two first-party packages need their own manifest bump; that trap is now written down in CLAUDE.md along with the reason never to test a language-mode change by passing SWIFT_VERSION= on the xcodebuild command line.

What the compiler actually made us fix

  • Non-Sendable Foundation statics. ISO8601DateFormatter, ByteCountFormatter and RelativeDateTimeFormatter are not Sendable, so shared statics now sit behind OSAllocatedUnfairLock. DateFormatter, NumberFormatter and DateComponentsFormatter already carry NS_SWIFT_SENDABLE in the macOS 26 SDK and were left alone.
  • NSLock.lock()/unlock() inside async bodies is a hard error in Swift 6. Converted to scoped withLock across 12 plugins and app areas. Every critical section keeps the exact statements it had, including the multi-exit shapes where unlock() sat before an early return.
  • AppKit observer tokens read from deinit. NSEvent/NotificationCenter tokens typed Any? or NSObjectProtocol? cannot be touched from a nonisolated deinit, so they moved behind a lock.
  • Cross-module enum switches over PluginPrivilegeScope, PluginImportFieldType, PluginBooleanSynonym, PathFieldRole, AutoLimitStyle and SQLDialectDescriptor.BooleanLiteralStyle gained @unknown default, which is what CLAUDE.md already asks for on an open domain.
  • Sendable conformances added to protocols and value types that were already thread-safe: SupervisedProcessRunner, KeyValueStore, ForeignAppImporter, KeyCode, the Exportable* and ConnectionImport* transfer types, and several storage singletons.
  • nonisolated on loggers inside @MainActor types, on pure statics, and across the iOS driver, platform, security and intent layers, which run under SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor and are background code by nature.
  • Three SQLite actors (MCPAuditLogStorage, SQLFavoriteStorage, QueryHistoryStorage, plus LinkedSQLIndex) moved from an init-time bootstrap, which Swift 6 rejects, to a handle that prepares itself on first use. static let shared was already lazy, so first touch is still first use.
  • C pointers crossing a task boundary (libssh2 channels, libduckdb connections, libmongoc documents, BSON handles) travel in small named transfer types instead of implicitly.

A regression this caught in the migration itself

The first attempt replaced CSVTypeInferrer's ISO8601DateFormatter with Date.ISO8601FormatStyle. A 329-input probe showed " 2026-08-20", "2026/08/20" and "-0001-01-01" would have stopped being inferred as dates, so a CSV column of slash-separated dates would have imported as text. Reverted to the original formatters behind a lock. The new CSVTypeInferrerTests pins those three cases plus the lenient forms the formatter accepts, and runs in CI through the TableProCore package job.

A second, subtler one: a pure static func on a SwiftUI View becomes main-actor isolated in Swift 6, and calling it from a nonisolated test traps at runtime rather than failing to compile. That crash took down 2,410 tests in one run. Fixed by marking those helpers nonisolated; a scan found and fixed all three occurrences.

Verification

  • macOS app, all 31 plugin bundles, both test bundles, the iOS app and widget, and both SPM packages build.
  • macOS suite: 12,812 passed, 27 failed, all 27 pre-existing and outside this diff (see below).
  • iOS suite: 221 passed, 0 failed.
  • TableProCore package: all pass, including 13 new CSVTypeInferrerTests.
  • swiftlint lint --strict clean over the app, Plugins/, Packages/, TableProMobile/ and the test targets.

The 27 macOS failures: six suites are the known local environment failures (network, pasteboard, timers), four are timing-flaky and pass in isolation, and the rest fail on code this branch never touches. PreferenceKeysGuardTests trips on removeValue(forKey: "extensions") in CLI/BridgeProxy.swift; URLClassifierTests on the new DuckDB file classification; ThirdPartyLicenseInventoryTests on DUCKDB_VERSION missing from a build script; TabExecutionSettleGuardTests on MCPProtocolDispatcher+Requests.swift; StringCatalogIntegrityTests and KoreanLocalizationSourceTests on .xcstrings content. The two .xcstrings files were already uncommitted before this work started and are deliberately left out of this branch.

Reviewer notes

  • PluginKit surface. SettablePluginDiscoverable.settingsView() gained @MainActor and ExportFormatPlugin/ImportFormatPlugin gained Sendable. Isolation and marker-protocol conformances do not change mangled symbols or witness-table layout, so already-shipped plugins keep loading and no currentPluginKitVersion bump is needed. It is a source-level change for a third-party plugin author, whose settingsView() now has to be @MainActor. Please run scripts/check-pluginkit-abi.sh <merge-base> before merging; nothing in CI does it and I did not run it.
  • Two pre-existing thread-safety gaps are now written down rather than fixed. CopilotChatProvider and the eight export/import plugins carry @unchecked Sendable over genuinely unsynchronised mutable state. Swift 5 was not checking them; making them safe is a redesign of how plugin settings are read during an export, not part of a language-mode bump.
  • XLSXWriter.write(to:) is now async and does the zip build and file write inside its detached task, taking only [ZipFileEntry] across the boundary. Same work, same thread, nothing non-Sendable crossing.

@mintlify

mintlify Bot commented Aug 20, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Aug 20, 2026, 2:59 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

1 similar comment
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin
datlechin merged commit 3368af9 into main Aug 20, 2026
6 of 7 checks passed
@datlechin
datlechin deleted the build/swift-6-language-mode branch August 20, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant