Repository navigation
feat(swift-interop): modules, generics, protocols, Observation and SwiftUI (3/7) - #520
Draft
NathanWalker wants to merge 6 commits into
Draft
NathanWalker wants to merge 6 commits into
NathanWalker wants to merge 6 commits into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This was referenced Oct 21, 2026
Draft
Draft
NathanWalker
marked this pull request as ready for review
October 21, 2026 13:50
Several Swift modules can now be generated as one unit (ABI 8), via --swift-unit with a --swift-symbolgraph-dir per module. The unit is merged into one IR. Every symbol keeps its own module, so declarations can name another module's types, and extensions of those types become their members. A unit has one thunk file, with shared support code and one type table, and one `.d.ts` per module that imports the others' types. At runtime, each ns_swift_register_<Module> symbol registers the whole unit, and every module maps to it. Each isolate builds the unit's templates once and gives every module its own ns:swift/<Module> namespace, so a type has one JS class wherever it appears. Types and members register with their module. Module-level overloads group per module. A module that is an implementation detail of another (SwiftUICore of SwiftUI) can't be imported. With --swift-reexport, which is implied for that pair, its declarations join the module that re-exports it. As a unit, SwiftUI and SwiftUICore verify and type-check with 2547 callables, against 873 for SwiftUI alone. The fixture gains a SwiftDemoKit module, which SwiftDemo uses and extends. It is built as its own static library target for TestRunner.
A generic type is now bridged through every concrete instantiation that the unit's declarations spell: signatures and public typealiases (for example `Measurement<UnitTemperature>` in WeatherKit). Instantiations found inside other instantiations' members are followed two levels deep. Before classification, instantiateGenerics adds a synthetic non-generic type for each instantiation. It copies the generic type's members, each with a substitution map for the generic parameters. Members whose same-type requirements contradict the arguments are left out, such as a `where UnitType == UnitMass` extension or a synthesized `Self == Style<T>` member. The classifier, thunks and probes then treat instantiations like any other type, and the compiler verifies every instantiated member. Typealiases now bridge as the type they name. Instantiations of one generic type share one JS class. They share a module and a JS name, and the runtime groups them into a family: one template and one member group per name. The constructor resolves among every instantiation's initializers, and instance members only consider the receiver's own instantiation. TypeScript declares the generic class once from template capabilities (`class Pair<First, Second>`), names instantiations with their arguments, and declares typealiases. Fix: the compiler log is removed before each verification run. LLVM's redirect doesn't truncate it, so a shorter second run kept the previous run's errors. At SDK scale, Foundation goes from 3038 to 3486 callables (74 instantiations) and WeatherKit from 267 to 379 (23). The WeatherKit and Foundation unit has 3968 callables. All of them type-check.
A parameter typed by a module protocol (`any P`, or a bare `P`) now accepts a Swift value that conforms to it, or a JS object implementing it (ABI 9). A protocol is adoptable when: - its requirements are instance methods and properties that bridge; - it has no associated types or `Self` requirements; - it has no static, async or main-actor requirements; - it refines at most one module protocol. For each adoptable protocol, the thunks get an __NSAdapter class conforming to it. Each requirement calls the JS object's member of the same name through new object-call entries and reads the returned value. A failure makes a throwing requirement throw. Otherwise the requirement returns its type's zero value, or traps when the type has none. Results come back as the JS object they started as, as their own registered class or struct, or boxed as the protocol, whose JS class carries the protocol's members. The runtime checks that a JS implementation provides every requirement, and builds each protocol's requirement table: the refined protocol's requirements, then its own. A requirement called from another thread runs on the isolate's thread while the Swift caller waits. A JS exception raised during a JS call takes precedence over the error a thunk reports. Callback handles generalize to JS object handles. TypeScript declares protocols as interfaces. The fixture covers JS and Swift conformers, throwing requirements, refinement, implementations Swift holds, and calls from a background task.
NathanWalker
force-pushed
the
swift-interop/2-runtime
branch
from
October 21, 2026 14:47
5efedd7 to
63b3a51
Compare
NathanWalker
force-pushed
the
swift-interop/3-modules-protocols
branch
from
October 21, 2026 14:47
b951726 to
39dac9d
Compare
NathanWalker
marked this pull request as draft
October 21, 2026 16:32
`ns:swift` gains track(read, onChange) and observe(read, onChange) (ABI 10). - track is Swift's withObservationTracking. It runs `read`, and the getter thunks `read` reaches run inside the tracking scope, so the observable properties it touches are recorded. `onChange` runs once, when one of them is about to change. - observe re-arms after every change, calls `onChange` after the change is made, and returns a stop function. Every generated unit carries the tracker and registers it. The runtime uses any loaded unit's tracker and throws what `read` throws. A change made on another thread reaches JS on its thread.
Results typed AsyncStream<T>, AsyncThrowingStream<T, E> or `some AsyncSequence<T, E>` now return JS async iterators, so `for await` works over them (ABI 11). The signature code is ~<element>. The thunk wraps the sequence in a type-erased __NSAsyncIterator. Its step runs the iterator's next() in a Task and reports the element as a one-element array, or an empty array at the end, through the existing async collection path. The runtime hands the iterator to a step function. A small factory compiled once per isolate makes that function an async iterator whose next() calls run one at a time. The step function's weak handle releases the Swift iterator when collected. A pending async result now keeps its own result code and name instead of a member pointer, since an iterator step's member can be collected before the step settles. Opaque results bind without naming their type.
A bridged struct that conforms to SwiftUI.View gains a synthesized hostingController() member. It returns a UIHostingController showing a copy of the view, as an ordinary NativeScript UIViewController that can go into a NativeScript layout. The thunk runs on the main actor and refuses other threads. The compiler verifies it like every other thunk, and the unit imports SwiftUI and UIKit when it hosts views.
NathanWalker
force-pushed
the
swift-interop/3-modules-protocols
branch
from
October 21, 2026 16:44
39dac9d to
83c8aff
Compare
This branch has not been deployed
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
Swift interop stack — review bottom-up; each PR shows only its own commits. Merge in order; after a squash merge, rebase the next branch onto
main. Design discussion: RFC NativeScript/rfcs#57..d.tsns:swiftcoreSwift APIs that span modules or are defined by protocols and generics.
SwiftDemoKitfixture).types/ns-swift.d.tsdeclares thens:swiftmodule itself.Evidence
After: 94
ns:swiftTestRunner specs (24 new), covering generics, protocol adapters on background threads, Observation, async iteration and SwiftUI hosting.Merge Danger
Door: two-way
Blast Radius: ns:swift
Changes stay inside the Swift generator,
SwiftInterop.mmand the fixtures.