diff options
-rwxr-xr-x | packages/bun-types/bun.lockb | bin | 37964 -> 38313 bytes | |||
-rw-r--r-- | packages/bun-types/ffi.d.ts | 149 | ||||
-rw-r--r-- | packages/bun-types/package.json | 5 | ||||
-rw-r--r-- | packages/bun-types/tests/ffi.test-d.ts | 96 |
4 files changed, 222 insertions, 28 deletions
diff --git a/packages/bun-types/bun.lockb b/packages/bun-types/bun.lockb Binary files differindex b95880249..e764f6a0c 100755 --- a/packages/bun-types/bun.lockb +++ b/packages/bun-types/bun.lockb diff --git a/packages/bun-types/ffi.d.ts b/packages/bun-types/ffi.d.ts index 19337fab1..3e7e91534 100644 --- a/packages/bun-types/ffi.d.ts +++ b/packages/bun-types/ffi.d.ts @@ -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? diff --git a/packages/bun-types/package.json b/packages/bun-types/package.json index ada826752..2dbf1839d 100644 --- a/packages/bun-types/package.json +++ b/packages/bun-types/package.json @@ -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" diff --git a/packages/bun-types/tests/ffi.test-d.ts b/packages/bun-types/tests/ffi.test-d.ts new file mode 100644 index 000000000..105736134 --- /dev/null +++ b/packages/bun-types/tests/ffi.test-d.ts @@ -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, + ] + > +>; |