Skip to content

@lucent-lang/lucent@0.1.0

Choose a tag to compare

@github-actions github-actions released this 30 Sep 17:51
· 528 commits to main since this release
7161111

Minor Changes

  • #13 9031f4e Thanks @Fausto95! - Give Android code the app's Activities. lucent:android adds currentActivity() (the Activity in front, or null; Lucent holds Activities weakly), startActivityForResult(intent, signal?) and requestPermissions(permissions, signal?) (promises settled once with the answer; aborting forgets the request, and a late answer is dropped), and onActivityEvent(event, handler) for the Activities' lifecycle and new intents. Requests go through a translucent Activity of Lucent's, so they work with any app Activity and survive it being recreated meanwhile. The native package's manifest declares it and a provider that starts tracking Activities with the process. Package manifest components may now set an activity's theme and configChanges.

  • #13 9031f4e Thanks @Fausto95! - Support bigint: literals, every operator JavaScript has for it, BigInt(), BigInt.asIntN and asUintN, Number() and String() of a bigint, and bigints in collections, objects, classes, unions and async code. Values of any size cross the JavaScript boundary exactly, and loose == between a bigint and a number or string, which converts in JavaScript, is reported as LUCENT1002.

  • #13 9031f4e Thanks @Fausto95! - Turn callback APIs, such as a native listener, into promises and subscriptions with fromCallback and subscribe from lucent:core: each settles once, whichever callback or abort comes first, and runs the cleanup you return exactly once.

  • #13 9031f4e Thanks @Fausto95! - Say exactly what the app needs after a build. lucent build lists actions (also in --json and the build record): recompile native code, reload JavaScript, repackage resources, relink native dependencies or reinstall the app, each with its platforms and the files behind it. They come from what the build changed: a body edit recompiles, new exports also reload JavaScript, a package's resources are repackaged. lucent dev now watches Lucent packages outside the app (workspace or linked) whole, including their lucent.json and native files, never rebuilds for what builds write, and stops a build a newer change made stale before it writes anything. Generated files are replaced whole, so Metro never reads half a proxy. Package files are hashed once while their size and times hold, and lucent build no longer starts a second Gradle run while another one resolves the classpath.

  • #13 9031f4e Thanks @Fausto95! - Run heavy work on worker threads with compute(task, input, { signal }) from lucent:core. The task is a function declared at the top level of a module, taking one parameter, and runs on a bounded pool of workers without a second JavaScript runtime; the promise settles on the calling thread. The compiler checks everything the task runs (no module state, no main-thread or unknown native code, no function values it cannot follow, nothing asynchronous: LUCENT3011) and that its input and result are data (LUCENT3012), naming the path to what breaks the rule. The input is copied when compute is called, keeping aliases and cycles; aborting the signal rejects the promise at once, and a running task stops at the next iteration of a loop in the task or in a module function it calls (loops in closures, methods and generic functions, and native calls, run to their end first). The JavaScript version in @lucent-lang/lucent/core behaves the same on the calling thread, for tests.

  • #13 9031f4e Thanks @Fausto95! - Recognize components: an exported function of a .lucent.tsx module that returns a platform view (a UIKit UIView, an Android View, or a subclass). React can't render them yet. Lucent now checks them and leaves them out of the module's JavaScript. It checks that the view comes back on every path, from one props object of plain data, with callback props named on… that return nothing, and that setup code can run on the main thread. The new diagnostics are LUCENT3020 to LUCENT3023. compile() lists each component under components, identified by package, module path and export name.

  • #13 9031f4e Thanks @Fausto95! - Check iOS availability as Android's is checked: an Objective-C or Swift API newer than iOS 15.1 must be used under available("ios", major, minor?) (in an if, after an early exit, or in ?: / &&), or the build fails with LUCENT3007 instead of crashing on older devices. Swift shims are marked @available from what they call, so they compile against the deployment target.

  • #13 9031f4e Thanks @Fausto95! - Lucent classes can extend Objective-C classes on iOS, as they extend SDK classes on Android: class Greeting extends UIViewController is made with the base initializer super(…) names, its methods override the base's (UIKit calling viewDidLoad runs the Lucent one), super.viewDidLoad() calls UIKit's, and inherited members such as this.view work on the instance, which passes wherever the base type is taken. A generated Objective-C subclass stands for each instance; it and the Lucent object keep each other alive while either side uses them, without a cycle. Subclasses of main-thread classes are used on the main thread.

  • #13 9031f4e Thanks @Fausto95! - Native 64-bit integers are bigints: Java's long (long[] as bigint[]), int64_t, uint64_t, NSInteger, NSUInteger and Swift's Int, Int64 and UInt64, in arguments, results, properties, struct fields, Outs, callbacks and constants. Every native value is exact, NSNotFound and Long.MAX_VALUE included, and a bigint the native type cannot hold throws RangeError naming the parameter or field. Enums, option sets and @IntDef/@LongDef groups stay numbers. Code that passes numbers to these APIs must pass bigints now (1000n, BigInt(n)), and Number(list.count) turns a count or index back into a number.

  • #13 9031f4e Thanks @Fausto95! - Hand bytes between Lucent, compute tasks and JavaScript without copying them, with NativeBuffer from lucent:core. NativeBuffer.allocate(size) and NativeBuffer.from(bytes) make one; withRead(read) and withWrite(write) lend a callback the bytes for one call as a span with a Uint8Array's element semantics (reads share the buffer, a write needs it to itself, a conflicting borrow throws an InvalidStateError); transfer(), or passing the buffer to compute, moves the bytes to a new owner and leaves every reference to the old buffer refusing them; toUint8Array() and from are the only copies, and NativeBuffer.stats() counts them. The compiler keeps a span inside its callback (LUCENT3030: not returned, stored, captured by a closure that outlives the call, kept by a callee or held across await) and reports a buffer used after it certainly moved (LUCENT3031). JavaScript receives a buffer as an opaque handle with the same methods, whose borrows lend it a copy, so it never holds memory a worker may be writing. Uint8Array keeps its copy semantics.

  • #13 9031f4e Thanks @Fausto95! - Add typed native extensions. A Lucent package declares one in lucent.json (extensions.<name>): a C header in native sources both platforms build, the opaque structs Lucent code holds as handles (their create, destroy, methods and thread), what pointer parameters are (bytes with their length, a UTF-8 string, an error struct) and how results report failure. lucent build reads the header with clang, checks every name and shape against it, and fails naming the package and the field when they disagree. Lucent code, shared code included, imports the handles as classes and the functions by their C names from lucent:ext/<name>: handles close once (close(), using, or their last reference), throw InvalidStateError after, and cannot cross to JavaScript; a reported failure throws an Error with C's message and code; 64-bit integers are bigints; an exception escaping C ends the process at the call instead of unwinding through Lucent. The header must declare its functions in extern "C" for C++; a function taking a handle is bound only when the declaration names it; a close() from another thread waits for the calls using the handle. Editors get the declarations from .lucent/native/types/ext/.

  • #13 9031f4e Thanks @Fausto95! - Bind the prebuilt frameworks and libraries Lucent packages ship. lucent:ios/<Module> now finds the module of a framework listed in ios.vendoredFrameworks (an .xcframework through its iOS simulator slice), and lucent:android/<package> the classes of a jar or AAR listed in android.libraries, before the app's Gradle build has resolved its classpath. lucent sdk show, search, prefetch and coverage see them too.

  • #13 9031f4e Thanks @Fausto95! - Check and merge Lucent packages' lucent.json with clear rules. An unknown field or a value of the wrong type fails the build, naming the package and the field. Every package's requirement on a pod goes to CocoaPods, and every version of a Gradle artifact goes to Gradle, instead of failing when two packages write different versions; requirements no version can meet, and a strict Gradle version another package's excludes, still fail naming both packages. ios.frameworks lists Apple frameworks to link. Info.plist values can be booleans and arrays of strings; arrays from several packages join, and the Expo config plugin adds the values an app's array lacks. The merged result, with the package each need came from, is written to .lucent/native/resolved.json.

  • #13 9031f4e Thanks @Fausto95! - Let Lucent packages ship native files through typed lucent.json fields: ios.nativeSources, resources, resourceBundles and vendoredFrameworks, and android.nativeSources, resources, assets, libraries and nativeLibraries. Paths are relative to the package. lucent build copies the files into .lucent/native/packages/<package>/ and adds them to the podspec, the Android library's build.gradle and, for C and C++ sources, the runtime's CMake target, so apps need no manual changes to their native projects. Two files that would land in the same place in the app fail the build, naming both packages. The resolved manifest and the build record list each input with its package and a content hash.

  • #13 9031f4e Thanks @Fausto95! - Let Lucent packages declare entitlements, Swift packages, Android manifest components and minimum platform versions in lucent.json: ios.entitlements (merged like Info.plist entries and added by the Expo config plugin; bare apps are told what their .entitlements file lacks), ios.swiftPackages (one requirement per package URL, products joined, added with React Native's spm_dependency), ios.deploymentTarget and android.minSdk (the highest any package needs), and android.components (services, receivers, activities and providers, declared once in the native package's manifest; packages that declare one differently fail the build, naming both).

  • #13 9031f4e Thanks @Fausto95! - Judge every SDK call by one set of binding rules: a refused call says why and which native declaration it comes from, declarations document what cannot be used, and lucent sdk coverage counts it. Native 64-bit integers (Java's long, NSInteger, Int64) no longer round: they are bigints, and enums, option sets and @IntDef groups stay numbers that cross exactly or throw RangeError; option sets take 0; Java arrays of any element copy; functions can be assigned to block properties; and a local named like a reserved word (id) no longer breaks Android glue.

  • #13 9031f4e Thanks @Fausto95! - Pin the SDKs and see what an update changes for your code. lucent sdk lock records the SDKs and dependencies each module was read from, and every SDK member the code uses, in lucent-sdk.lock.json; it needs the SDK of every platform the project has code for, unless --platforms leaves one out. lucent build --frozen and lucent check --frozen then fail when an SDK or dependency differs from the lock, when the code uses a member the lock does not record, or when a platform the lock lists has no SDK installed or would be left to the Gradle build, instead of skipping that platform. lucent sdk diff compares the lock with the installed SDKs, member by member, by native identity: removed members, and changed signatures, availability, deprecation and TypeScript names, before you rebuild the app (--all adds the members your code does not use). lucent sdk coverage now shows how far members get: discovered, representable, generated by the last build, and exercised (from an --exercised file), with --members for every member's stage and reason. Each build and check writes what it used to .lucent/sdk-usage.json.

  • #13 9031f4e Thanks @Fausto95! - Swift async methods take an AbortSignal last, as Kotlin suspend functions do: aborting it, or the calling context going away (a reload), rejects the promise at once with AbortError and cancels the Swift task, and a result that arrives afterwards is released instead of delivered.

  • #13 9031f4e Thanks @Fausto95! - Lucent classes implement every kind of Swift protocol requirement an object can: properties (read, and set when the requirement allows it), throwing methods (a Lucent error becomes the NSError Swift catches), async methods (Swift awaits the Lucent promise), and mutating ones. Protocols with associated types are generic in their declarations (Store<Item>), and a class fixes them with implements Store<string>; Self in a requirement is the implementing class. Such protocols' values pass as arguments that name their associated types (some Store<String>, from iOS 16). Initializer and static requirements are refused with a diagnostic.

  • #13 9031f4e Thanks @Fausto95! - Trace why a call or a build was slow: LUCENT_TRACE=trace.json records, off the hot path, how long each call waited for its thread, for the Lucent lock or for a compute worker, how long it ran and how many bytes it copied, each call at its .lucent.ts line; lucent trace --runtime trace.json merges that with the last build's steps into one trace for Perfetto and prints how long each cause took. On devices, LUCENT_TRACE=platform (iOS, Instruments) and debug.lucent.trace (Android, Perfetto) send the same spans to the platform's tools.

Patch Changes

  • #13 9031f4e Thanks @Fausto95! - Report errors thrown by an abort listener in the device log, like other uncaught errors, and keep running the remaining listeners whatever was thrown.

  • #13 9031f4e Thanks @Fausto95! - Run calls whose result is undefined or null when that result is passed on, stored or compared, like show(reset()) or reset() ?? fallback, instead of leaving the call out; a function that always throws now throws there too.

  • #13 46e09c1 Thanks @Fausto95! - Check and build an Expo app before expo prebuild when it imports Android libraries from its dependencies: those modules stay untyped, named in a warning, and Android is checked once the app has its Android project, instead of failing with LUCENT3004.

  • #13 46e09c1 Thanks @Fausto95! - Build Android on a machine without Xcode when a platform module's declaration file names iOS types: they are untyped there, as in shared code.

  • #13 9031f4e Thanks @Fausto95! - Android constant groups: annotation types holding constants (Play services' Priority) are enums, results annotated @IntDef or @StringDef (from the SDK's annotations.zip and each AAR's own) are the union of their constants, and passing a literal or constant outside a parameter's group is the new warning LUCENT3008. Warnings don't fail the build: lucent build and lucent check print them, their --json output lists them under warnings, and the editor shows them as warnings. AARs' @RequiresPermission annotations are read too.

  • #13 9031f4e Thanks @Fausto95! - Android declarations TypeScript accepts: an Activity goes where a Context is taken (an override keeps the non-null result of the method it overrides, whatever package declares it), and abstract SDK classes such as BiometricPrompt.AuthenticationCallback keep their constructors, protected ones as protected, so Lucent classes extend them. Every class extends java.lang.Object, imports named like a local type are aliased, and a few members TypeScript cannot relate to what they override are left out with their reason (an Object result narrowed to an interface or a type variable, EditText.getText() under TextView's string, Stack.empty() beside isEmpty()). A class whose superclass and interface give one property two ways (ViewGroup's layoutDirection, typed by View's constants and as a number by ViewParent) declares the superclass's, the one it calls. The Android declaration audit drops from 81 errors to none.

  • #13 9031f4e Thanks @Fausto95! - Members of generic Java classes are bound: List<String>.get(0) reads a string, and a function implements Consumer<Location>. Classes declare their type parameters and references keep their type arguments; values of a type parameter cross as SDK objects, strings, numbers or booleans, as the type argument says.

  • #13 9031f4e Thanks @Fausto95! - Android thread annotations are followed: @UiThread and @MainThread classes and methods (a View, and every member of it but those marked @AnyThread) must be used inside main(() => …) (LUCENT3006), and calling a @WorkerThread member there warns with the new LUCENT3009, since it blocks. The SDK declarations document both on each member. iOS @MainActor properties of classes that aren't main-only are now checked too.

  • #13 9031f4e Thanks @Fausto95! - Call a Java or Kotlin interface's default method on Android as TypeScript declares it, asserted present: manager.getCredential!(context, request) no longer fails with LUCENT1001.

  • #13 9031f4e Thanks @Fausto95! - Async functions whose body only throws, like async function load(): Promise<string> { return fail(); }, return a rejected promise instead of throwing at the call. Generators without a yield run their body on the first next() instead of when called, and no longer give invalid C++ when empty.

  • #13 9031f4e Thanks @Fausto95! - await on a platform SDK object, such as a Play services Task or a Java future, is a compile error (LUCENT1010) instead of giving the object back: it is not a promise. Wait for it through its completion listener with fromCallback from lucent:core, as for any callback API. No library class is recognized by name, so a package's own listener-based types work the same way.

  • #13 9031f4e Thanks @Fausto95! - Convert and compare values that are a bigint or a number (an SDK constant or a 64-bit value), possibly absent, as JavaScript does: Number(x), BigInt(x), < and the other relational operators, unary - and ~, and ++/--. Number() and BigInt() of such unions were refused, and comparisons and unary minus threw at run time.

  • #13 9031f4e Thanks @Fausto95! - Blocks the iOS SDK passes to Lucent code become functions: a completion handler given to a Lucent function (coordinator.prepare(…, (done) => done())) or to a Lucent class's protocol method (URLSessionTaskDelegate's completionHandler) can be called like any function. Blocks taking CoreFoundation values (CFArrayRef, CFErrorRef) are supported too. Protocol requirements are declared in their completion-handler form only, so Lucent classes can implement them; before, the promise form merged in as an overload made that a type error.

  • #13 46e09c1 Thanks @Fausto95! - Keep lucent build from running small machines and CI runners out of memory: it reads the SDK bindings ahead in one background process per spare core, for the platforms it builds, instead of one process per imported module.

  • #13 9031f4e Thanks @Fausto95! - Record each build's steps in .lucent/build-record.json: what every step read and wrote, with content hashes, whether it ran, was cached or failed, and what the app needs next, so a slow or surprising build can be inspected afterwards.

  • #13 9031f4e Thanks @Fausto95! - Stop refusing a function passed to compute because another module's code that the platform calls back (a kept callback, an override of a platform class's method, a protocol method) uses module state: that code runs on the module's thread, holding its lock, not in the task.

  • #13 9031f4e Thanks @Fausto95! - Print a lucent command's whole output when it goes to a pipe: a large --json report (lucent sdk coverage --json) was cut after 64 kB on macOS.

  • #13 9031f4e Thanks @Fausto95! - Assignments follow JavaScript's order: log += next() reads log before next() runs, box.n = next() writes the object box named before next() ran, an element or field target's object and key run once, and xs[i]! += 1 is accepted as a target.

  • #13 9031f4e Thanks @Fausto95! - Compute tasks read module constants safely across a JavaScript reload. A reload runs each module's initialization again, which assigned constants while a task could still read them (a string or function constant could be freed under the task). Code now reads number, string, boolean and bigint literal constants, and static readonly fields holding one, as their literals; a task that reads any other module constant is refused with LUCENT3011, with the path to it: make it a literal or pass its value in the task's input.

  • #13 9031f4e Thanks @Fausto95! - Let compute tasks call BigInt(), BigInt.asIntN, BigInt.asUintN, a bigint's toString and valueOf, a number's or boolean's valueOf, and toString on arrays, byte arrays, objects and functions. They were refused with LUCENT3011, although none of them touches module state.

  • #13 9031f4e Thanks @Fausto95! - Build the object literals in a conditional's branches as the type the conditional is used as, so const o: Options = big ? { name, size } : { name } no longer throws a TypeError at run time.

  • #13 9031f4e Thanks @Fausto95! - Refuse loose == and != where JavaScript converts before comparing, as LUCENT1002: a number, string, boolean or bigint against another of them (1 == "1", true == 1), or an object against a primitive ([1] == 1). Lucent used to compare these like === and answer false where JavaScript answers true. Generics are checked once instantiated; == between values of one kind, or with null or undefined, is unchanged.

  • #13 9031f4e Thanks @Fausto95! - Let expo prebuild accept a react-native.config.js whose lucent entry is written without quotes, as formatters write it, instead of failing and asking for an entry that is already there.

  • #13 9031f4e Thanks @Fausto95! - Let JavaScript call into Lucent while a long async Lucent function yields with await delay(0): threads now take the Lucent lock in the order they asked, so timers, callbacks and aborts no longer wait seconds behind a loop that keeps retaking it.

  • #13 9031f4e Thanks @Fausto95! - Report comparing two functions as LUCENT1002 (LUCENT2002 for a Map key or Set element), where it used to fail with a C++ error: === and !== between functions, indexOf, lastIndexOf and includes on arrays of functions, and generics that compare a type parameter instantiated with a function type. Lucent does not keep a function value's identity, so it cannot compare functions as JavaScript does.

  • #13 9031f4e Thanks @Fausto95! - When lucent build, run by the app's Gradle build, changes the Lucent Android library's build.gradle (its first Kotlin shim, a package's Android dependency), it now fails and asks for another build instead of letting Gradle build the library as it was configured, which left new Kotlin shims out of the app.

  • #13 9031f4e Thanks @Fausto95! - BOOL * parameters are bound: in calls as an Out<boolean>, and where a block or protocol method receives one (stop in enumeration blocks such as calendar.enumerateDates(…, (date, exact, stop) => { stop.value = true; })), as an Out whose value goes back to the platform when the function returns. A class whose only initializer a protocol synthesizes keeps new C() (new NSDateComponents()).

  • #13 9031f4e Thanks @Fausto95! - iOS methods that write through pointers to numbers, enums, structs, strings, dates and objects are bound: pass an Out<T> from lucent:ios and read its value after the call (color.getRed(red, green, blue, alpha), calendar.range(.day, start, interval, date), a PropertyListSerialization format, an NSRange effective range). value is writable, for pointers the method also reads. Throwing methods that return id no longer fail to compile in -Werror builds.

  • #13 9031f4e Thanks @Fausto95! - A Java SDK call whose result is an array other than byte[], int[], long[] or String[] (such as float[]) reports LUCENT2002 instead of crashing the compiler.

  • #13 9031f4e Thanks @Fausto95! - Android release builds with R8 (Expo's default) no longer fail SDK calls with "Java method not found" when a method's parameters name a library class the code never uses directly (Play services' getCurrentLocation(priority, null) and its CancellationToken): the consumer rules now keep every class in the descriptors the glue looks up.

  • #13 9031f4e Thanks @Fausto95! - A Kotlin parameter with a default followed by one without (create(scope = …, produceFile)) now takes undefined on Android, which leaves Kotlin's default in place: PreferenceDataStoreFactory.INSTANCE.create(undefined, undefined, undefined, () => file).

  • #13 9031f4e Thanks @Fausto95! - Collect Kotlin flows on Android: await flow.collect((value) => …, signal) runs the Lucent function for each value, resolves when the flow completes, rejects with the flow's error or with what the function throws, and cancels the flow when the signal aborts. Lucent functions can be passed where a Kotlin library takes a suspend function (DataStore's edit), running while Kotlin waits, and generic Kotlin members (Flow.collect, FlowKt.first) are called through shims unless their type parameters have bounds other than Any.

  • #13 9031f4e Thanks @Fausto95! - Declare Kotlin libraries on Android as Kotlin declares them, from their Kotlin metadata: parameter names, Kotlin's nullability, properties with their accessors, suspend functions as promises (without their Continuation), extensions as functions taking their receiver first, top-level declarations as statics of their facade, and value, sealed, data and object classes. Internal members, $DefaultImpls and other compiler plumbing are no longer declared. Calling a suspend function, or passing a value class the JVM unboxes, is refused with a clear error until Lucent generates Kotlin shims; a class whose metadata comes from a newer Kotlin is declared as Java sees it, and lucent sdk coverage --json lists why among its reasons.

  • #13 9031f4e Thanks @Fausto95! - Kotlin's read-only List<E> is now an array on Android: a list a Kotlin library returns is copied into an E[], and an array passed where it takes a List<E> is copied into a new list, so await client.search(prefix) gives an array with map and length. Numbers Kotlin boxes (List<Int>, Flow<Double>) are numbers, and Longs bigints (List<Long> is bigint[]), and elements may be null where Kotlin's type allows it. A MutableList, a List<*> and lists Java declarations name stay the Java object, whose changes and identity Lucent code shares; code that called size() and get(i) on a read-only Kotlin list uses length and [i] instead.

  • #13 9031f4e Thanks @Fausto95! - A Kotlin suspend function completing with a Long resolves to a bigint, as every native 64-bit integer does, instead of generating C++ that does not compile.

  • #13 9031f4e Thanks @Fausto95! - Call Kotlin libraries' suspend functions, default arguments and value classes on Android through Kotlin shims Lucent generates for what the program uses. A suspend function is a promise, cancelled by the AbortSignal it takes last, with thrown exceptions as errors and late results released; leaving out an argument with a Kotlin default lets Kotlin's default apply, and passing null passes null. Value classes cross boxed, Kotlin properties with setters can be assigned, and instanceof matches Java objects against Android SDK classes, so the cases of a sealed class and their payloads are read as their classes'.

  • #13 9031f4e Thanks @Fausto95! - Link the native module in an Android app's first build after a fresh checkout: lucent build writes .lucent/native before it runs Gradle, and has Gradle read the app's autolinking config again once the package exists. A Gradle build that read that config before the package existed now fails with "build again" instead of shipping an APK without Lucent.

  • #13 9031f4e Thanks @Fausto95! - Metro can bundle a native package that lucent build --out <dir> wrote: set LUCENT_OUT=<dir> (relative to the project, as --out is) and each *.lucent.ts file is bundled as that package's proxy, with its loader and build identity, instead of .lucent/native's.

  • #13 9031f4e Thanks @Fausto95! - Compile ===, !==, ==, switch cases, indexOf, includes and Map and Set keys between values of different types, as TypeScript allows them: an optional against another optional (number | undefined against string | undefined), overlapping unions, and a generic's value against undefined, null or a literal whatever type it is instantiated with. Values that can't be the same JavaScript value are never equal, and unions compare the member they hold. Loose x == null in a generic, and undefined == null between two optionals, are now true as in JavaScript, and includes, Map and Set find NaN inside a union.

  • #13 9031f4e Thanks @Fausto95! - Report two Lucent modules with the same name under their own code, LUCENT3010, whose fix says to rename one of the files. They were reported as LUCENT3002, a top-level statement, with an unrelated fix.

  • #13 9031f4e Thanks @Fausto95! - Compile names that the C library or a platform SDK defines as macros, like HUGE, DOMAIN, MIN or pascal, as the ordinary TypeScript names they are, instead of failing to build the generated C++.

  • #13 9031f4e Thanks @Fausto95! - Read optional values the checker narrows to undefined or null, like s in const s: string | undefined = undefined; show(s) or after if (s === undefined), instead of reporting LUCENT2002.

  • #13 9031f4e Thanks @Fausto95! - The native code lucent build generates now carries a build identity: a hash of each target's program, a hash of each module's API (its exports and their signatures), and the runtime ABI they need, which JavaScript can read as __lucentIdentity on the Lucent module.

  • #13 9031f4e Thanks @Fausto95! - Compile ??, ||, &&, ??= and conditional expressions whose other operand always throws, like value ?? fail("missing"), and run the throwing operand for ??= instead of assigning undefined.

  • #13 9031f4e Thanks @Fausto95! - Compile calls that always throw wherever a value is expected, like take(fail()), s = fail(), { size: fail() }, [fail()], 1 + fail() or if (fail()), instead of reporting LUCENT2002, and exhaustive switches that pass the value they narrowed away to assertNever(value: never). Templates and calls with several parts no longer give invalid C++ when a part is a call that returns nothing.

  • #13 9031f4e Thanks @Fausto95! - Compile return fail(), arrows like (): string => fail(), and declarations like const name: string = fail(), where fail always throws, instead of reporting LUCENT2002.

  • #13 9031f4e Thanks @Fausto95! - Objective-C generic classes are bound: new NSCache<string, UIImage>(), NSHashTable, NSMapTable and NSLayoutAnchor<AnchorType> declare their type parameters, and their members pass and return values as the type arguments say (strings, numbers, booleans, data and dates are bridged; objects stay objects).

  • #13 9031f4e Thanks @Fausto95! - Compile String(x), template literals, + with a string, toString() and console.log on arrays, maps, sets, records, Uint8Arrays and promises, which used to fail with a C++ error. AbortSignal and AbortController now print as [object AbortSignal] and [object AbortController], as in JavaScript.

  • #13 9031f4e Thanks @Fausto95! - Compile optional calls of methods and functions that return nothing, like signal?.addEventListener("abort", …) or callback?.(), instead of generating C++ that does not build.

  • #13 9031f4e Thanks @Fausto95! - Bind the jars and AARs Lucent packages ship in android.libraries: lucent:android/<package> now finds their classes, as it finds the app's Gradle dependencies'.

  • be7d600 Thanks @Fausto95! - Add a README to the npm package: what Lucent does, how to install it, what the package contains and where the docs are.

  • #13 9031f4e Thanks @Fausto95! - A constructor body can read and assign its parameter properties' parameters (constructor(readonly name: string) { log(name); }), which reported "unsupported reference" before.

  • #13 9031f4e Thanks @Fausto95! - Guard clauses are platform branches: after if (PLATFORM === "ios") return …; the rest of the block is Android code, as TypeScript narrows it (#12).

  • #13 9031f4e Thanks @Fausto95! - Build apps that use use_frameworks!: Lucent's pod now depends on the pods whose modules your iOS code imports, so their headers are found.

  • #13 9031f4e Thanks @Fausto95! - expo prebuild of a fresh checkout no longer stops at LUCENT3004 when a module's Android branch imports a library from the app's dependencies (Play services, AndroidX): until the Gradle build resolves them, those imports are untyped in the iOS build, and the Gradle build's lucentBuild task builds and checks Android, reporting an import that is still missing. The first Gradle build after prebuild no longer stops to ask for another build when Android turns out to need Kotlin shims.

  • #13 9031f4e Thanks @Fausto95! - Present view controllers and system sheets from iOS code and await their result with present() from lucent:ios: it shows the view controller from the scene in use, settles once, dismisses it however it ends, and rejects with an AbortError when a signal aborts, the person swipes it away or its scene goes. Follow the app's and its scenes' lifecycle with onAppEvent() and onSceneEvent(), without replacing the app's delegates.

  • #13 9031f4e Thanks @Fausto95! - Stop refusing a function passed to compute because another module keeps a callback that the platform delivers later (a monitor's handler, a delegate method that returns nothing): such a callback never runs during the task's native calls.

  • #13 9031f4e Thanks @Fausto95! - Say in the editor why Lucent cannot use an iOS C function or constant yet: its declaration in lucent:ios/<module> now carries the reason, as methods and properties already did.

  • #13 9031f4e Thanks @Fausto95! - End Lucent's work for a JavaScript runtime when it reloads: async calls that have not started are dropped, Lucent code awaiting a JavaScript promise or callback resumes with an AbortError instead of never, results the old runtime can no longer receive are released, and platform objects are released on the thread they require (the main thread for UIKit objects) whichever thread drops them. A second Lucent host for the same runtime no longer leaves JavaScript calls failing with "Lucent is not initialized for this runtime".

  • #13 9031f4e Thanks @Fausto95! - Extract SDK bindings again only when a library or SDK they were read from changes, not when another dependency is added, the same pods are installed again or the project moves, and bind pods of apps using use_frameworks! before their first Xcode build. The first build after upgrading extracts once more.

  • #13 9031f4e Thanks @Fausto95! - An SDK call, property read or construction whose receiver or argument awaits ((await file()).getName(), Uri.parse(await text())) compiles: the awaiting operand, and those before it, are evaluated first. It used to emit C++ the compiler refused.

  • #13 9031f4e Thanks @Fausto95! - Type SDK classes as their platforms do: a subclass keeps the overloads it inherits (ByteBuffer.limit() beside limit(int)), an override keeps the non-null result and constants of the method it overrides, and iOS classes no longer repeat their superclass's protocols, so the generated declarations type-check with far fewer errors.

  • #13 9031f4e Thanks @Fausto95! - Declare again, in an SDK class, the superclass methods that an interface it inherits through another interface also declares (ArrayBlockingQueue.addAll, from Collection through BlockingQueue): TypeScript rejected the class for merging two different declarations of one method.

  • #13 9031f4e Thanks @Fausto95! - Give each SDK member name one kind per class hierarchy: a method named like an inherited property now takes its Swift labels, as it already did beside the class's own property (NSCountedSet.countFor(_:) instead of count(_:)), and a class is not declared as conforming to a protocol whose requirement it declares as a property.

  • #13 9031f4e Thanks @Fausto95! - Declare Java buffers as their superclass: java.nio.Buffer no longer declares array(), whose Object result ByteBuffer, IntBuffer and the others narrow to typed arrays. TypeScript rejected those overrides, and with them each buffer as a Buffer. Coverage lists the left-out member with its reason.

  • #13 9031f4e Thanks @Fausto95! - An object type with a field holding an SDK object compiles: it stringifies as {} (a host object), and a type only one platform's code uses stays out of the other platform's output (#11).

  • #13 9031f4e Thanks @Fausto95! - Generate the same iOS declarations on every run: the protocols a class adopts are listed in a stable order, not in the varying order Xcode's symbol graph tool writes them.

  • #13 9031f4e Thanks @Fausto95! - Numbers print exactly as JavaScript prints them. String(x), template literals, console.log and toExponential() gave one digit too many for some doubles, such as 2 ** 89 (6.1897001964269014e+26 where JavaScript prints 6.189700196426902e+26); toFixed dropped the sign of a negative number that rounds to zero (-0.000); and toString(radix) of a very large number crashed.

  • #13 9031f4e Thanks @Fausto95! - Spread maps into array literals ([...map], new Map([...a, ...b])), and Uint8Arrays, like the other iterables; spreading a collection into an array of a wider element type (["start", ...numbers]) now keeps its elements.

  • #13 9031f4e Thanks @Fausto95! - Proxies check the app's native code before a module loads. An app built before this check, with another runtime ABI, without the module, or with another API for it, throws an error that names what to do: rebuild and reinstall the app (compile-native), or reload JavaScript built for the installed app (reload-js). When only function bodies differ, a warning says the installed implementation runs, once per reload. JavaScript-only edits change nothing.

  • #13 9031f4e Thanks @Fausto95! - Compile === and !== between arrays, maps, sets, records, Uint8Arrays and promises, which compare references as in JavaScript, and between bigints in generic functions. They used to fail with a C++ error.

  • #13 9031f4e Thanks @Fausto95! - Store a subclass instance in a union that holds its base class (const s: Shape | string = new Circle()), which was rejected with LUCENT2002.

  • #13 9031f4e Thanks @Fausto95! - Keep the parentheses of a comparison compared again in a SwiftUI body (a === b === false): the view's Swift compiles instead of failing on Swift's non-associative comparisons.

  • #13 9031f4e Thanks @Fausto95! - lucent sdk coverage counts Swift-only members (StoreKit 2, CryptoKit, Swift overlays) as unrepresentable, with the reason "Swift-only", instead of leaving them out.

  • #13 9031f4e Thanks @Fausto95! - Swift-only iOS APIs can be called: initializers, methods, static members, properties (read and written) and top-level functions of Swift types, through a generated @_cdecl shim per member the program uses. Swift structs are objects Lucent holds (boxed), changed in place by mutating members and setters; enums without payloads are numbers, and enums with payloads are unions discriminated by kind ({ kind: "circle"; center: Point; radius: number } | …); throws becomes a Lucent error; async members and getters return promises, and async @MainActor members can be awaited from any thread; strings, data, dates, arrays, dictionaries and optionals convert as for Objective-C. Default arguments can be left out, members that protocol extensions give a type are callable (CryptoKit's SHA256.hash(data:), AVFoundation's load(_:)), and C structs cross by value. Swift modules on the include paths and Swift-only SDK frameworks (CryptoKit) bind. Generic members get a shim per combination of type arguments the program uses. Protocol values cross as objects, and Lucent classes can implement Swift protocols (method requirements; not protocols with associated types yet).

  • #13 9031f4e Thanks @Fausto95! - Compile generics whose signatures put a type parameter in a union (A | B, T | null) whatever order the type arguments sort in, where the call used to pass the union's members in another order than the generic expects. A type argument that would merge into such a union (an optional, null or undefined, and in A | B a union or a type the union already holds) is now refused with LUCENT2002 instead of failing to build. null | undefined is accepted as a type.

  • #13 9031f4e Thanks @Fausto95! - Reject converting a union of object types to another object type (LUCENT2003) when compiling, instead of compiling code that always throws a TypeError; each member of a union is now converted on its own, as a subclass to its base class.

  • #13 9031f4e Thanks @Fausto95! - Android AutoCloseable objects (a Closeable, a Cursor, anything that implements either) are disposable: using cursor = resolver.query(…) closes the cursor however the block is left, and cursor[Symbol.dispose]() closes it too.

  • #13 9031f4e Thanks @Fausto95! - using declarations: using file = open(path) disposes the value with its class's [Symbol.dispose]() however the block is left (return, throw, break, continue or the end), in reverse order, skipping null. A disposal that throws while another error is pending throws a SuppressedError. Classes can define [Symbol.dispose](), the one symbol-keyed member they support; JavaScript doesn't see it. await using, and using directly in a case clause, report LUCENT1001. The editor needs esnext.disposable in tsconfig's lib (React Native's base config lacks it).

  • #13 9031f4e Thanks @Fausto95! - Compile values typed void | undefined, like an unannotated () => item?.update() or a function declared to return void | undefined, and let the result of a call that returns nothing be printed, compared or passed to typeof.

  • #13 9031f4e Thanks @Fausto95! - Stop reporting every call to a @WorkerThread method of a @UiThread Android class: the method's own annotation now overrides its class's, so it is callable outside main() without LUCENT3006.