Skip to content

Migrate to the Swift 6 language mode#8

Merged
gtokman merged 1 commit into
mainfrom
swift6-concurrency
Jul 7, 2026
Merged

Migrate to the Swift 6 language mode#8
gtokman merged 1 commit into
mainfrom
swift6-concurrency

Conversation

@gtokman

@gtokman gtokman commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

What

Migrates all first-party targets off the Swift 5 language mode (and consumers off @preconcurrency import MarkdownView) to full Swift 6 strict concurrency. Builds green with zero concurrency errors/warnings on both AppKit (macOS) and UIKit (iOS).

Why

A consuming app had to mark the library @preconcurrency import MarkdownView because, in Swift 5 mode, the library's public API carried no isolation/Sendable information. This adds proper isolation so downstream Swift 6 consumers can drop @preconcurrency.

Approach (per-target, swift-tools-version: 6.2)

  • MarkdownParser.v6; unchanged (already clean Sendable data).
  • Litext & MarkdownView.v6 + .defaultIsolation(MainActor.self) (Swift 6.2 "single-threaded by default" for the UI layers). This cleared ~80% of diagnostics on its own.
  • isolated deinit for the two deinits that touch main-actor state (LTXLabel, MathPreviewDataSource).
  • Small mechanical fixes: MainActor.assumeIsolated on a main-runloop timer, nonisolated override on NSObject hash/isEqual, @Sendable completion + nonisolated service classes.

The important subtlety

defaultIsolation(MainActor) compiled clean but crashed at runtime (SIGTRAP in dispatch_assert_queue) because it forced MarkdownView's genuine background-processing layer — the com.markdownview.preprocessing Combine pipeline — onto the main actor. The processing subsystem is therefore kept explicitly nonisolated: CodeHighlighter and ImageLoader (@unchecked Sendable), PreprocessedContent (background init nonisolated; math members @MainActor), the diff/highlight helpers, and the pipeline's background stage extracted into a nonisolated static func. (Note: nonisolated on a class does not propagate to its extensions — those are annotated individually.)

Public-API changes (reviewer sign-off appreciated)

  1. Removed the unused theme: parameter from CodeHighlighter.highlight(...) — it was vestigial (colors come from tree-sitter; no caller passed it).
  2. PreprocessedContent.init(...backgroundSafe:) now always defers math rendering; the backgroundSafe: false full-synchronous-render path (no callers) is not expressible off the main actor. Use @MainActor init(parserResult:theme:) when a fully-rendered value is needed up front.

If you'd rather preserve exact source compatibility on either, they can become deprecated no-ops instead.

Testing

  • swift build and xcodebuild -destination 'generic/platform=iOS Simulator' both succeed with no concurrency diagnostics.
  • Full suite: 132 tests run, at parity with main — the only 2 failures (HighlighterTests/testHighlightReturnsMap, DiffViewTests/testSingleTokenFenceAutoDetectsUnifiedDiffAndUsesInnerLanguage) are pre-existing and fail identically on the base branch under swift test (Python isn't a default-enabled trait, so highlighting returns empty). Zero regressions.

Out of scope

16 pre-existing #no-usage (unused NSMenuItem) lint warnings in LTXLabel+Interaction@AppKit.swift are unrelated to concurrency and left untouched.

🤖 Generated with Claude Code

Move all first-party targets off the Swift 5 language mode (and consumers
off `@preconcurrency import`) to full Swift 6 strict concurrency, on both
AppKit and UIKit.

Approach (swift-tools-version 6.2, per-target):
- MarkdownParser: `.v6`, remains nonisolated Sendable data.
- Litext + MarkdownView: `.v6` + `.defaultIsolation(MainActor.self)` for the
  UI layers.
- `isolated deinit` for the two deinits that touch main-actor state
  (LTXLabel, MathPreviewDataSource).

The main subtlety: `defaultIsolation(MainActor)` would force MarkdownView's
genuine background-processing layer (the `com.markdownview.preprocessing`
Combine pipeline) onto the main actor — compiling clean but trapping at
runtime in `dispatch_assert_queue`. The processing subsystem is therefore
kept explicitly `nonisolated`: CodeHighlighter and ImageLoader
(`@unchecked Sendable`), PreprocessedContent (background init nonisolated,
math members `@MainActor`), the diff/highlight helpers, and the pipeline's
background stage extracted into a `nonisolated static` function.

Minor public-API changes:
- Removed the unused `theme:` parameter from `CodeHighlighter.highlight`.
- `PreprocessedContent.init(...backgroundSafe:)` now always defers math
  rendering (the `backgroundSafe: false` full-render path is not expressible
  off the main actor; use `@MainActor init(parserResult:theme:)` instead).

Tests that exercise the main-actor UI API are annotated `@MainActor`. Full
suite is at parity with the base branch (the two Python-trait failures are
pre-existing under `swift test`, which excludes the Python trait).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@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.

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
markdown-view Ready Ready Preview, Comment, Open in v0 Jul 7, 2026 1:24pm

Request Review

@gtokman gtokman merged commit 70d7367 into main Jul 7, 2026
4 of 6 checks passed
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