Skip to content

feat(runtime): ns:interop builtin modules, global interop as compat - #527

Draft
edusperoni wants to merge 3 commits into
mainfrom
feat/ns-interop
Draft

edusperoni wants to merge 3 commits into
mainfrom
feat/ns-interop

Conversation

@edusperoni

@edusperoni edusperoni commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Adoption step 0 of rfcs#58 ("interop and runtime helpers"). interop moves into builtin modules, and the global interop becomes a compat layer built from them. Android counterpart: NativeScript/android#2074.

Modules

Module Exports
ns:interop Pointer, Reference, FunctionReference, sizeof, alloc, free, handleof, stringFromCString, types (the C primitives: void, bool, int8…uint64, ulong, slong, float, double)
ns:interop/ios (also ns:interop/visionos, ns:interop/macos) adopt, bufferFromData, types (id, class, selector, protocol, unichar, noop)
ns:runtime (new members) escapeException, setExternalSize, getExternalSize
  • Exports and their types objects are frozen, like the other ns: modules.
  • The aliases resolve to ns:interop/ios before any cache lookup, so require returns the same exports object and ESM gets the same module (one namespace object).

The global interop

  • Built by runtime/js/interop-global.js, which requires the three modules through the builtin require. The ns: files never refer to it.
  • Same key set as today, and every member (including all 20 types) is === its module export.
  • The object is still mutable, and the interop property is still enumerable.
  • It's now a lazy global (kLazyGlobals), built on first read. No C++ reads it.
  • inline-functions.js (the @ObjCMethod/@ObjCParam decorators) gets void from ns:interop at decorator time, so apps that never touch interop don't build these modules.

Native changes

  • The 20 type objects are created once per isolate, on first use (a StateFor slot). GetInteropType hands them out when marshalling pointer and C-string results, so they have to exist even if no JS ever loads ns:interop. noop still wins the shared void encoding.
  • Workers no longer register interop twice. They used to end up with two sets of type objects, so the marshaller's types and interop.types were different objects. There's now one set per isolate.
  • Pointer, Reference, FunctionReference and Unmanaged already create their constructors lazily, so native marshalling never depends on the module having loaded.

Types

  • types/ns-interop.d.ts and types/ns-interop-ios.d.ts, plus the ns:runtime additions, all shipped with the runtime and referenced from types/index.d.ts.
  • Pointer, Reference<T>, FunctionReference<T>, Type<T>, StructType<T>, Unmanaged<T> and AdoptedPointer are module exports. That means generated declarations can write import type * as interop from "ns:interop" and keep their existing interop.Pointer references unchanged.
  • bufferFromData(data: NSData & object) relies on an empty global interface NSData {}.
    • With @nativescript/types-ios in the program, it merges into that package's NSData class.
    • Without it, any object is accepted.
  • The global interop declaration stays in @nativescript/types-ios. Switching it to alias these modules is a follow-up there.

Tests

  • New NsInteropTests.js, with a worker fixture and an ESM fixture. It covers:
    • export key sets;
    • frozen exports;
    • alias identity under require, static import and dynamic import();
    • that the global's key set is unchanged and every member is === its module export, on the main thread and in a worker.
  • Results:
    • Full suite: 1853 passed, 0 failed. Baseline was 1835, and the 18 new specs account for the difference.
    • ASan: 1853 passed, 0 failed, no reports.

Open points

  • Error messages still say interop.setExternalSize … / interop.escapeException …, including when called through ns:runtime. They were left as-is because existing tests and docs match on them.
  • interop.initializer / interop.Args (rfcs#56) aren't on main yet. They'll join ns:interop when that lands.

ns:interop carries the cross-platform native memory and value API
(Pointer, Reference, FunctionReference, sizeof, alloc, free, handleof,
stringFromCString, the C primitive types); ns:interop/ios carries adopt,
bufferFromData and the Objective-C types, with ns:interop/visionos and
ns:interop/macos as aliases canonicalized before every cache, so each
spelling yields the same exports object and the same ES module.
ns:runtime gains escapeException, setExternalSize and getExternalSize.

The global interop becomes a lazy global (interop-global.js) assembled
from the three modules through the builtin require: same key set, every
member and all twenty types identical to the module exports, still
mutable and enumerable. Workers get it through the same global template
instead of a second registration.

The primitive type objects are created natively on first use, by the
module bindings or by native marshalling (GetInteropType), so pointer and
C-string results keep their types even when no app code reads interop.
inline-functions.js takes the void type from ns:interop when a decorator
runs rather than from the global.
…ditions

Pointer, Reference<T>, FunctionReference<T>, Type<T>, StructType<T>,
Unmanaged<T> and AdoptedPointer are module exports, so declarations can
`import type * as interop from "ns:interop"`. bufferFromData takes
`NSData & object` against an empty global NSData interface, which merges
into @nativescript/types-ios's class when present and accepts any object
otherwise.
@coderabbitai

coderabbitai Bot commented Oct 23, 2026

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

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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant