Repository navigation
feat(swift-interop): returning closures, generic functions, tuples, Result and C structs (5/7) - #522
Draft
NathanWalker wants to merge 9 commits into
Draft
NathanWalker wants to merge 9 commits into
NathanWalker wants to merge 9 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
NathanWalker
marked this pull request as ready for review
October 21, 2026 13:51
NathanWalker
force-pushed
the
swift-interop/4-app-builds
branch
from
October 21, 2026 14:47
0cb1e1b to
91839dc
Compare
NathanWalker
force-pushed
the
swift-interop/5-values
branch
from
October 21, 2026 14:47
8c4d970 to
7a3876e
Compare
NathanWalker
marked this pull request as draft
October 21, 2026 16:33
A closure parameter can now return a value, throw, or both. The JS function's result converts through the value table. Called from another thread, the closure waits for the function to run on the isolate's thread, since Swift needs the value before it continues. When the function throws or returns a value that does not convert, the exception surfaces from the Swift call in progress. A `throws` closure throws into Swift. Any other closure returns its result type's empty value (nil, false, 0, "", an empty collection). Thunk ABI 12: signature codes gain `(params>result)`, and API slot 51 (callbackCallEnd) ends an invocation and returns the result. Generated TypeScript types the function's result.
A generic function, method or initializer is now instantiated once for each concrete stand-in of its type parameters. Each instantiation is an overload of its JS name, and TypeScript declares each one. Stand-ins by constraint: - none: Double, String and Bool; - standard library protocols: those of Double (Int for integer protocols), String and Bool that conform; - one protocol of the unit: its existential, which Swift opens at the call; - a superclass: the superclass. At most one number type stands in, since a JS number converts to every one. Compiler verification drops any instantiation that does not type-check. Functions are left generic when: - a parameter is used as a metatype or through an associated type; - a parameter is constrained by another module's protocol; - the arguments could not tell instantiations apart; - they would need more than six instantiations; - they are nested in a generic context. When several overloads accept a call, the runtime now tells array arguments apart by their first elements. The TypeScript writer and the classifier's ambiguity check match this. Coverage counts a generic function once, as generated when any of its instantiations is. Against the iOS 18 SDK this adds, for example, JSONEncoder.encode(_:), URL.appending(path:directoryHint:) and NavigationPath.append(_:). Every module's thunks still type-check.
…neric types A type parameter that a generic function passes to one of the unit's generic types (`WeatherQuery<T>`, `Box<T>`) now stands in with that type's instantiated arguments. The unit's own instantiations bound these combinations, so up to sixteen are allowed instead of six. WeatherKit's `weather(for:including:)` is now callable, with one overload per WeatherQuery instantiation: `service.weather(location, WeatherQuery.daily)` resolves to a `Forecast<DayWeather>`. Combinations that name no existing instantiation are rejected as unbridged.
`[T?]` and `[String: T?]` now cross as JS arrays and objects holding nulls, in both directions. In thunks, the element readers return `T??`: nil still means an element did not convert, and `.some(nil)` is a JS null. Async sequences keep rejecting optional elements, since a nil there ends the sequence.
Tuples of two or more values cross as JS arrays, or as plain objects
when every element is labeled: `(quotient: Int, remainder: Int)` becomes
`{ quotient, remainder }`. TypeScript types them as `[A, B]` or
`{ x: A; y: B }`. Elements may be optional.
`Set<T>` crosses as a JS Set copy, and arrays are also accepted as
input. TypeScript types it as `Set<T>` (input: `ReadonlySet<T>` or an
array).
New signature codes: `S[<code>]` and `*(<code>,...)`, with labels as
`*(<label>:<code>,...)`. Both travel as array slots and array reports,
so the API table is unchanged. Thunks report tuples through a new
`returnTuple` reporter method built on the array entries.
Against the iOS 18 SDK this adds 46 Foundation and 5 SwiftData
callables. Every module's thunks still type-check.
`any Error` now bridges as a value. In JS it is an Error named
SwiftError, with the Swift type in `swiftType`. From JS, any value is
accepted, and the Swift error describes itself by the JS error's
message. This covers `(Error?) -> Void` completion handlers.
`Result<Success, Failure>` crosses as the same tagged object enums with
associated values use: `{ case: "success", value }` or
`{ case: "failure", value }`, with no value for `Result<Void, F>`.
Failure is `any Error` or one of the unit's types. Completion handlers
taking a Result now bridge too, such as
`(Result<String, ShapeError>) -> Void`.
Thunk ABI 13:
- the signature codes `!` and `R(<success>,<failure>)`;
- API slots 52 (returnErrorValue) and 53 (completeErrorValue), which
report an error as a value instead of throwing it.
The runtime gives a Result code its two cases, so case reporting and
conversion share the tagged-enum paths.
CGPoint, CGSize, CGRect, CGVector, CGAffineTransform, UIEdgeInsets,
NSDirectionalEdgeInsets, UIOffset, NSRange, CLLocationCoordinate2D,
MKCoordinateSpan and MKCoordinateRegion now cross as plain JS object
copies of their fields, for example `{ x, y }`. Inputs also accept
NativeScript's own struct values, such as `CGPointMake(1, 2)`.
Symbol graphs don't describe C struct fields, so a built-in table lists
them. Each struct travels as its fields' labeled-tuple code, so the
runtime is unchanged. Thunks build the struct through its memberwise
initializer and import its framework.
Against the iOS 18 SDK this adds 26 SwiftUI and 5 Charts callables.
Every module's thunks still type-check.
…classes A Swift class that subclasses NSObject (a UIView or UIViewController subclass, for example) stays NativeScript's Objective-C class. When the module loads, its Swift-only members, the ones Objective-C cannot see, now install on that class: - instance members on its prototype; - static members and Swift-only initializers (as `init`) on its constructor. They never replace a member NativeScript already defines. The receiver is NativeScript's wrapper of the object, checked with isKindOfClass. Initializers return the object as NativeScript's wrapper. The generator registers these classes under a new type kind (NSSwiftTypeObjCClass, 6) with their Objective-C class name. The runtime installs members only when NativeScript metadata describes that exact class, since installing on the nearest described superclass would be wrong. An app gets that metadata from its -Swift.h header. TypeScript merges the members into the class's global declaration. Members of UIResponder subclasses are isolated to the main actor, as UIKit declares them. TestRunner's metadata step now includes the SwiftDemo fixture's -Swift.h, the way nsld.sh includes an app's.
A parameter typed `some P` is now an implicit type parameter. It is instantiated the way named generic parameters are, and the bound picks the stand-ins. For example, `describe(_ value: some CustomStringConvertible)` gets overloads for a number, a string and a boolean, `some Greeting` takes `any Greeting`, and `some UIView` takes UIView. Each copy retypes the parameter to its stand-in, so opaque results such as `some View` stay untouched. Against the iOS 18 SDK this adds one Charts callable. SDK `some` parameters are mostly SwiftUI modifiers in generic contexts. The fixture's four new functions all bridge.
NathanWalker
force-pushed
the
swift-interop/4-app-builds
branch
from
October 21, 2026 16:44
91839dc to
e76a9ec
Compare
NathanWalker
force-pushed
the
swift-interop/5-values
branch
from
October 21, 2026 16:44
7a3876e to
ae7d342
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:swiftcoreMore Swift value shapes cross.
Also: optional elements in arrays and dictionaries, and
some Pparameters. The thunk ABI moves to 13.Evidence
Resultand C structs did not cross.After: 117
ns:swiftTestRunner specs (23 new) and 69 generator tests.Merge Danger
Door: two-way
Blast Radius: ns:swift
Swift-only members install on NativeScript's own class for Objective-C-visible Swift classes and never replace NativeScript's members.