mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Remake typings for FFI dlopen/linkSymbols + introduce Pointer type (#2227)
* Give dlopen & linkSymbols typings for exported functions * Fix lookup table * Fully change over to Pointer + fix examples * add back header for typings * Fix tsc errors * Run formatter on ffi.d.ts * Revert args/return type change * Add type tests for ffi --------- Co-authored-by: Colin McDonnell <colinmcd94@gmail.com>
This commit is contained in:
Binary file not shown.
149
packages/bun-types/ffi.d.ts
vendored
149
packages/bun-types/ffi.d.ts
vendored
@@ -345,6 +345,74 @@ declare module "bun:ffi" {
|
||||
*/
|
||||
u64_fast = 16,
|
||||
}
|
||||
|
||||
type UNTYPED = never;
|
||||
export type Pointer = number & {};
|
||||
|
||||
interface FFITypeToType {
|
||||
[FFIType.char]: number;
|
||||
[FFIType.int8_t]: number;
|
||||
[FFIType.i8]: number;
|
||||
[FFIType.uint8_t]: number;
|
||||
[FFIType.u8]: number;
|
||||
[FFIType.int16_t]: number;
|
||||
[FFIType.i16]: number;
|
||||
[FFIType.uint16_t]: number;
|
||||
[FFIType.u16]: number;
|
||||
[FFIType.int32_t]: number;
|
||||
[FFIType.i32]: number;
|
||||
[FFIType.int]: number;
|
||||
[FFIType.uint32_t]: number;
|
||||
[FFIType.u32]: number;
|
||||
[FFIType.int64_t]: UNTYPED;
|
||||
[FFIType.i64]: UNTYPED;
|
||||
[FFIType.uint64_t]: UNTYPED;
|
||||
[FFIType.u64]: UNTYPED;
|
||||
[FFIType.double]: UNTYPED;
|
||||
[FFIType.f64]: UNTYPED;
|
||||
[FFIType.float]: UNTYPED;
|
||||
[FFIType.f32]: UNTYPED;
|
||||
[FFIType.bool]: boolean;
|
||||
[FFIType.ptr]: Pointer;
|
||||
[FFIType.pointer]: Pointer;
|
||||
[FFIType.void]: UNTYPED;
|
||||
[FFIType.cstring]: CString;
|
||||
[FFIType.i64_fast]: number | bigint;
|
||||
[FFIType.u64_fast]: number | bigint;
|
||||
}
|
||||
interface FFITypeStringToType {
|
||||
["char"]: FFIType.char;
|
||||
["int8_t"]: FFIType.int8_t;
|
||||
["i8"]: FFIType.i8;
|
||||
["uint8_t"]: FFIType.uint8_t;
|
||||
["u8"]: FFIType.u8;
|
||||
["int16_t"]: FFIType.int16_t;
|
||||
["i16"]: FFIType.i16;
|
||||
["uint16_t"]: FFIType.uint16_t;
|
||||
["u16"]: FFIType.u16;
|
||||
["int32_t"]: FFIType.int32_t;
|
||||
["i32"]: FFIType.i32;
|
||||
["int"]: FFIType.int;
|
||||
["uint32_t"]: FFIType.uint32_t;
|
||||
["u32"]: FFIType.u32;
|
||||
["int64_t"]: FFIType.int64_t;
|
||||
["i64"]: FFIType.i64;
|
||||
["uint64_t"]: FFIType.uint64_t;
|
||||
["u64"]: FFIType.u64;
|
||||
["double"]: FFIType.double;
|
||||
["f64"]: FFIType.f64;
|
||||
["float"]: FFIType.float;
|
||||
["f32"]: FFIType.f32;
|
||||
["bool"]: FFIType.bool;
|
||||
["ptr"]: FFIType.ptr;
|
||||
["pointer"]: FFIType.pointer;
|
||||
["void"]: FFIType.void;
|
||||
["cstring"]: FFIType.cstring;
|
||||
["function"]: FFIType.pointer; // for now
|
||||
["usize"]: FFIType.uint64_t; // for now
|
||||
["callback"]: FFIType.pointer; // for now
|
||||
}
|
||||
|
||||
export type FFITypeOrString =
|
||||
| FFIType
|
||||
| "char"
|
||||
@@ -388,12 +456,16 @@ declare module "bun:ffi" {
|
||||
*
|
||||
* @example
|
||||
* From JavaScript:
|
||||
* ```js
|
||||
* const lib = dlopen('add', {
|
||||
* // FFIType can be used or you can pass string labels.
|
||||
* args: [FFIType.i32, "i32"],
|
||||
* returns: "i32",
|
||||
* });
|
||||
* ```ts
|
||||
* import { dlopen, FFIType, suffix } from "bun:ffi"
|
||||
*
|
||||
* const lib = dlopen(`adder.${suffix}`, {
|
||||
* add: {
|
||||
* // FFIType can be used or you can pass string labels.
|
||||
* args: [FFIType.i32, "i32"],
|
||||
* returns: "i32",
|
||||
* },
|
||||
* })
|
||||
* lib.symbols.add(1, 2)
|
||||
* ```
|
||||
* In C:
|
||||
@@ -413,7 +485,9 @@ declare module "bun:ffi" {
|
||||
*
|
||||
* @example
|
||||
* From JavaScript:
|
||||
* ```js
|
||||
* ```ts
|
||||
* import { dlopen, CString } from "bun:ffi"
|
||||
*
|
||||
* const lib = dlopen('z', {
|
||||
* version: {
|
||||
* returns: "ptr",
|
||||
@@ -470,16 +544,8 @@ declare module "bun:ffi" {
|
||||
// */
|
||||
// export function callback(ffi: FFIFunction, cb: Function): number;
|
||||
|
||||
export interface Library {
|
||||
symbols: Record<
|
||||
string,
|
||||
CallableFunction & {
|
||||
/**
|
||||
* The function without a wrapper
|
||||
*/
|
||||
native: CallableFunction;
|
||||
}
|
||||
>;
|
||||
export interface Library<Fns extends Record<string, Narrow<FFIFunction>>> {
|
||||
symbols: ConvertFns<Fns>;
|
||||
|
||||
/**
|
||||
* `dlclose` the library, unloading the symbols and freeing allocated memory.
|
||||
@@ -491,6 +557,32 @@ declare module "bun:ffi" {
|
||||
close(): void;
|
||||
}
|
||||
|
||||
type ToFFIType<T extends FFITypeOrString> = T extends FFIType
|
||||
? T
|
||||
: T extends string
|
||||
? FFITypeStringToType[T]
|
||||
: never;
|
||||
|
||||
type _Narrow<T, U> = [U] extends [T] ? U : Extract<T, U>;
|
||||
type Narrow<T = unknown> =
|
||||
| _Narrow<T, 0 | (number & {})>
|
||||
| _Narrow<T, 0n | (bigint & {})>
|
||||
| _Narrow<T, "" | (string & {})>
|
||||
| _Narrow<T, boolean>
|
||||
| _Narrow<T, symbol>
|
||||
| _Narrow<T, []>
|
||||
| _Narrow<T, { [_: PropertyKey]: Narrow }>
|
||||
| (T extends object ? { [K in keyof T]: Narrow<T[K]> } : never)
|
||||
| Extract<{} | null | undefined, T>;
|
||||
|
||||
type ConvertFns<Fns extends Record<string, FFIFunction>> = {
|
||||
[K in keyof Fns]: (
|
||||
...args: Fns[K]["args"] extends infer A extends FFITypeOrString[]
|
||||
? { [L in keyof A]: FFITypeToType[ToFFIType<A[L]>] }
|
||||
: never
|
||||
) => FFITypeToType[ToFFIType<NonNullable<Fns[K]["returns"]>>];
|
||||
};
|
||||
|
||||
/**
|
||||
* Open a library using `"bun:ffi"`
|
||||
*
|
||||
@@ -518,7 +610,10 @@ declare module "bun:ffi" {
|
||||
* goes to Fabrice Bellard and TinyCC maintainers for making this possible.
|
||||
*
|
||||
*/
|
||||
export function dlopen(name: string, symbols: Symbols): Library;
|
||||
export function dlopen<Fns extends Record<string, Narrow<FFIFunction>>>(
|
||||
name: string,
|
||||
symbols: Fns,
|
||||
): Library<Fns>;
|
||||
|
||||
/**
|
||||
* Turn a native library's function pointer into a JavaScript function
|
||||
@@ -548,7 +643,7 @@ declare module "bun:ffi" {
|
||||
*
|
||||
*/
|
||||
export function CFunction(
|
||||
fn: FFIFunction & { ptr: number | bigint },
|
||||
fn: FFIFunction & { ptr: Pointer },
|
||||
): CallableFunction & {
|
||||
/**
|
||||
* Free the memory allocated by the wrapping function
|
||||
@@ -608,7 +703,9 @@ declare module "bun:ffi" {
|
||||
* goes to Fabrice Bellard and TinyCC maintainers for making this possible.
|
||||
*
|
||||
*/
|
||||
export function linkSymbols(symbols: Symbols): Library;
|
||||
export function linkSymbols<Fns extends Record<string, Narrow<FFIFunction>>>(
|
||||
symbols: Fns,
|
||||
): Library<Fns>;
|
||||
|
||||
/**
|
||||
* Read a pointer as a {@link Buffer}
|
||||
@@ -626,7 +723,7 @@ declare module "bun:ffi" {
|
||||
*
|
||||
*/
|
||||
export function toBuffer(
|
||||
ptr: number,
|
||||
ptr: Pointer,
|
||||
byteOffset?: number,
|
||||
byteLength?: number,
|
||||
): Buffer;
|
||||
@@ -646,7 +743,7 @@ declare module "bun:ffi" {
|
||||
* undefined behavior. Use with care!
|
||||
*/
|
||||
export function toArrayBuffer(
|
||||
ptr: number,
|
||||
ptr: Pointer,
|
||||
byteOffset?: number,
|
||||
byteLength?: number,
|
||||
): ArrayBuffer;
|
||||
@@ -681,7 +778,7 @@ declare module "bun:ffi" {
|
||||
export function ptr(
|
||||
view: TypedArray | ArrayBufferLike | DataView,
|
||||
byteOffset?: number,
|
||||
): number;
|
||||
): Pointer;
|
||||
|
||||
/**
|
||||
* Get a string from a UTF-8 encoded C string
|
||||
@@ -734,7 +831,7 @@ declare module "bun:ffi" {
|
||||
* reading beyond the bounds of the pointer will crash the program or cause
|
||||
* undefined behavior. Use with care!
|
||||
*/
|
||||
constructor(ptr: number, byteOffset?: number, byteLength?: number);
|
||||
constructor(ptr: Pointer, byteOffset?: number, byteLength?: number);
|
||||
|
||||
/**
|
||||
* The ptr to the C string
|
||||
@@ -743,7 +840,7 @@ declare module "bun:ffi" {
|
||||
* is safe to continue using this instance after the `ptr` has been
|
||||
* freed.
|
||||
*/
|
||||
ptr: number;
|
||||
ptr: Pointer;
|
||||
byteOffset?: number;
|
||||
byteLength?: number;
|
||||
|
||||
@@ -772,7 +869,7 @@ declare module "bun:ffi" {
|
||||
*
|
||||
* Becomes `null` once {@link JSCallback.prototype.close} is called
|
||||
*/
|
||||
readonly ptr: number | null;
|
||||
readonly ptr: Pointer | null;
|
||||
|
||||
/**
|
||||
* Can the callback be called from a different thread?
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
"fmt": "prettier --write './**/*.{ts,tsx,js,jsx}'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsd": "^0.22.0",
|
||||
"prettier": "^2.4.1"
|
||||
"conditional-type-checks": "^1.0.6",
|
||||
"prettier": "^2.4.1",
|
||||
"tsd": "^0.22.0"
|
||||
},
|
||||
"tsd": {
|
||||
"directory": "tests"
|
||||
|
||||
96
packages/bun-types/tests/ffi.test-d.ts
Normal file
96
packages/bun-types/tests/ffi.test-d.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { dlopen, FFIType, suffix, CString, Pointer } from "bun:ffi";
|
||||
import * as tsd from "tsd";
|
||||
import * as tc from "conditional-type-checks";
|
||||
|
||||
// `suffix` is either "dylib", "so", or "dll" depending on the platform
|
||||
// you don't have to use "suffix", it's just there for convenience
|
||||
const path = `libsqlite3.${suffix}`;
|
||||
|
||||
const lib = dlopen(
|
||||
path, // a library name or file path
|
||||
{
|
||||
sqlite3_libversion: {
|
||||
// no arguments, returns a string
|
||||
args: [],
|
||||
returns: FFIType.cstring,
|
||||
},
|
||||
add: {
|
||||
args: [FFIType.i32, FFIType.i32],
|
||||
returns: FFIType.i32,
|
||||
},
|
||||
allArgs: {
|
||||
args: [
|
||||
FFIType.char, // string
|
||||
FFIType.int8_t,
|
||||
FFIType.i8,
|
||||
FFIType.uint8_t,
|
||||
FFIType.u8,
|
||||
FFIType.int16_t,
|
||||
FFIType.i16,
|
||||
FFIType.uint16_t,
|
||||
FFIType.u16,
|
||||
FFIType.int32_t,
|
||||
FFIType.i32,
|
||||
FFIType.int,
|
||||
FFIType.uint32_t,
|
||||
FFIType.u32,
|
||||
FFIType.int64_t,
|
||||
FFIType.i64,
|
||||
FFIType.uint64_t,
|
||||
FFIType.u64,
|
||||
FFIType.double,
|
||||
FFIType.f64,
|
||||
FFIType.float,
|
||||
FFIType.f32,
|
||||
FFIType.bool,
|
||||
FFIType.ptr,
|
||||
FFIType.pointer,
|
||||
FFIType.void,
|
||||
FFIType.cstring,
|
||||
FFIType.i64_fast,
|
||||
FFIType.u64_fast,
|
||||
],
|
||||
returns: FFIType.void,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
tsd.expectType<CString>(lib.symbols.sqlite3_libversion());
|
||||
tsd.expectType<number>(lib.symbols.add(1, 2));
|
||||
|
||||
tc.assert<
|
||||
tc.IsExact<
|
||||
typeof lib["symbols"]["allArgs"],
|
||||
[
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
never,
|
||||
never,
|
||||
never,
|
||||
never,
|
||||
never,
|
||||
never,
|
||||
never,
|
||||
never,
|
||||
boolean,
|
||||
Pointer,
|
||||
Pointer,
|
||||
never,
|
||||
CString,
|
||||
number | bigint,
|
||||
number | bigint,
|
||||
]
|
||||
>
|
||||
>;
|
||||
Reference in New Issue
Block a user