GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — feat(swift-interop): modules, generics, protocols, Observation and SwiftUI (3/7) by NathanWalker · Pull Request #520 · NativeScript/ios

Conversation

@NathanWalker

@NathanWalker NathanWalker commented Oct 21, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

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.

  1. feat(metadata-generator): Swift symbol graphs to thunks and TypeScript declarations (Swift interop 1/7) #518 generator: symbol graphs → IR → thunks + .d.ts
  2. feat(runtime): ns:swift calls Swift through generated thunks (Swift interop 2/7) #519 runtime: ns:swift core
  3. feat(swift-interop): modules, generics, protocols, Observation and SwiftUI (3/7) #520 modules, generics, protocols, Observation, SwiftUI ← this PR
  4. feat(swift-interop): generate and link thunks in app builds (4/7) #521 app builds
  5. feat(swift-interop): returning closures, generic functions, tuples, Result and C structs (5/7) #522 more values: returning closures, generic functions, tuples, Result, C structs
  6. feat(swift-interop): adapter lifetime, cancellation, subscripts, Combine and operators (6/7) #523 adapter lifetime, cancellation, subscripts, Combine, operators
  7. feat(swift-interop): Any, UUID and Decimal, performance, and docs (Swift interop 7/7) #517 Any/UUID/Decimal, performance, docs

Swift APIs that span modules or are defined by protocols and generics.

new Kit.Pair(1, "one").swapped();              // generic types through the instantiations the code spells
SwiftDemo.introduce({ name: "js", greet: (o) => "hi " + o, reset() {} }, "ada"); // JS implements a Swift protocol
swift.observe(() => thermostat.celsius, update); // @Observable tracking
for await (const n of SwiftDemo.countUp(3)) {}   // AsyncStream → async iterator
new SwiftDemo.Badge("Inbox", 3).hostingController(); // SwiftUI view → UIViewController
  • Generation units: one IR across several modules, so types and extensions work across them (SwiftDemoKit fixture).
  • Generics: one JS class per generic family, instantiation chosen by arguments.
  • Protocols: JS objects become Swift conformers through generated adapters.
  • types/ns-swift.d.ts declares the ns:swift module itself.

Evidence

  • Before: only single-module, non-generic declarations cross.
    After: 94 ns:swift TestRunner 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.mm and the fixtures.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share
  • X
  • Mastodon
  • Reddit
  • LinkedIn

Comment @coderabbitai help to get the list of available commands.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

This was referenced Oct 21, 2026
feat(metadata-generator): Swift symbol graphs to thunks and TypeScript declarations (Swift interop 1/7) #518
Draft
feat(runtime): ns:swift calls Swift through generated thunks (Swift interop 2/7) #519
Draft
feat(swift-interop): generate and link thunks in app builds (4/7) #521
Draft
feat(swift-interop): returning closures, generic functions, tuples, Result and C structs (5/7) #522
Draft
feat(swift-interop): adapter lifetime, cancellation, subscripts, Combine and operators (6/7) #523
Draft
feat(swift-interop): Any, UUID and Decimal, performance, and docs (Swift interop 7/7) #517
Draft
@NathanWalker
NathanWalker marked this pull request as ready for review October 21, 2026 13:50
NathanWalker added 3 commits October 21, 2026 07:46
@NathanWalker
feat(swift-interop): generation units across Swift modules
29bbc5e
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.
@NathanWalker
feat(swift-interop): generic types through their instantiations
d1bfc26
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.
@NathanWalker
feat(swift-interop): JS implementations of Swift protocols
e1d2c86
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
NathanWalker force-pushed the swift-interop/2-runtime branch from 5efedd7 to 63b3a51 Compare October 21, 2026 14:47
@NathanWalker
NathanWalker force-pushed the swift-interop/3-modules-protocols branch from b951726 to 39dac9d Compare October 21, 2026 14:47
@NathanWalker
NathanWalker marked this pull request as draft October 21, 2026 16:32
NathanWalker added 3 commits October 21, 2026 09:44
@NathanWalker
feat(swift-interop): track @observable properties from JS
225586e
`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.
@NathanWalker
feat(swift-interop): async sequences as JS async iterators
0d6c43d
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.
@NathanWalker
feat(swift-interop): host SwiftUI views
83c8aff
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
NathanWalker force-pushed the swift-interop/3-modules-protocols branch from 39dac9d to 83c8aff Compare October 21, 2026 16:44
@NathanWalker NathanWalker added this to the 10.0 milestone Oct 21, 2026
@edusperoni edusperoni mentioned this pull request Nov 5, 2026
fix: report __runtimeVersion without quotes, and cut CI time with the Xcode 26 compilation cache #546
Merged

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

10.0

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@NathanWalker
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.