diff options
author | 2023-08-24 06:56:46 +0800 | |
---|---|---|
committer | 2023-08-23 15:56:46 -0700 | |
commit | 5e07fd4fbcf59550668406d1e52044fa3649446a (patch) | |
tree | 717a0e309bf32d2884d304ff696e374b4e93afb9 | |
parent | c60385716b7a7ac9f788cdf7dfe37250321e0670 (diff) | |
download | bun-5e07fd4fbcf59550668406d1e52044fa3649446a.tar.gz bun-5e07fd4fbcf59550668406d1e52044fa3649446a.tar.zst bun-5e07fd4fbcf59550668406d1e52044fa3649446a.zip |
Fix ffi type (#4265)
* add readonly so it works with as const
* split ffi type infer to args and returns
* add JSCallback to FFITypeToArgsType
* add read functions
* simplify FFITypeOrString
* fix cstring type
* fix pointer type test
* fix readonly
* add unknown handling
* trigger action
* use Parameters
* add read type test
* add other read function to tests
-rw-r--r-- | packages/bun-types/ffi.d.ts | 262 | ||||
-rw-r--r-- | packages/bun-types/tests/ffi.test-d.ts | 59 |
2 files changed, 272 insertions, 49 deletions
diff --git a/packages/bun-types/ffi.d.ts b/packages/bun-types/ffi.d.ts index ea4eb3b5b..4d5f8d983 100644 --- a/packages/bun-types/ffi.d.ts +++ b/packages/bun-types/ffi.d.ts @@ -350,7 +350,7 @@ declare module "bun:ffi" { type UNTYPED = never; export type Pointer = number & {}; - interface FFITypeToType { + interface FFITypeToArgsType { [FFIType.char]: number; [FFIType.int8_t]: number; [FFIType.i8]: number; @@ -365,22 +365,54 @@ declare module "bun:ffi" { [FFIType.int]: number; [FFIType.uint32_t]: number; [FFIType.u32]: number; - [FFIType.int64_t]: number; - [FFIType.i64]: number; - [FFIType.uint64_t]: number; - [FFIType.u64]: number; + [FFIType.int64_t]: number | bigint; + [FFIType.i64]: number | bigint; + [FFIType.uint64_t]: number | bigint; + [FFIType.u64]: number | bigint; [FFIType.double]: number; [FFIType.f64]: number; [FFIType.float]: number; [FFIType.f32]: number; [FFIType.bool]: boolean; - [FFIType.ptr]: Pointer; - [FFIType.pointer]: Pointer; + [FFIType.ptr]: TypedArray | Pointer | CString | null; + [FFIType.pointer]: TypedArray | Pointer | CString | null; + [FFIType.void]: void; + [FFIType.cstring]: TypedArray | Pointer | CString | null; + [FFIType.i64_fast]: number | bigint; + [FFIType.u64_fast]: number | bigint; + [FFIType.function]: Pointer | JSCallback; // cannot be null + } + interface FFITypeToReturnsType { + [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]: bigint; + [FFIType.i64]: bigint; + [FFIType.uint64_t]: bigint; + [FFIType.u64]: bigint; + [FFIType.double]: number; + [FFIType.f64]: number; + [FFIType.float]: number; + [FFIType.f32]: number; + [FFIType.bool]: boolean; + [FFIType.ptr]: Pointer | null; + [FFIType.pointer]: Pointer | null; [FFIType.void]: void; [FFIType.cstring]: CString; [FFIType.i64_fast]: number | bigint; [FFIType.u64_fast]: number | bigint; - [FFIType.function]: (...args: any[]) => any; + [FFIType.function]: Pointer | null; } interface FFITypeStringToType { ["char"]: FFIType.char; @@ -417,36 +449,7 @@ declare module "bun:ffi" { export type FFITypeOrString = | FFIType - | "char" - | "int8_t" - | "i8" - | "uint8_t" - | "u8" - | "int16_t" - | "i16" - | "uint16_t" - | "u16" - | "int32_t" - | "i32" - | "int" - | "uint32_t" - | "u32" - | "int64_t" - | "i64" - | "uint64_t" - | "u64" - | "double" - | "f64" - | "float" - | "f32" - | "bool" - | "ptr" - | "pointer" - | "void" - | "cstring" - | "function" - | "usize" - | "callback"; + | keyof FFITypeStringToType; interface FFIFunction { /** @@ -477,7 +480,7 @@ declare module "bun:ffi" { * } * ``` */ - args?: FFITypeOrString[]; + readonly args?: readonly FFITypeOrString[]; /** * Return type to a FFI function (C ABI) * @@ -505,7 +508,7 @@ declare module "bun:ffi" { * } * ``` */ - returns?: FFITypeOrString; + readonly returns?: FFITypeOrString; /** * Function pointer to the native function @@ -516,7 +519,7 @@ declare module "bun:ffi" { * This is useful if the library has already been loaded * or if the module is also using Node-API. */ - ptr?: number | bigint; + readonly ptr?: number | bigint; /** * Can C/FFI code call this function from a separate thread? @@ -533,10 +536,10 @@ declare module "bun:ffi" { * * @default false */ - threadsafe?: boolean; + readonly threadsafe?: boolean; } - type Symbols = Record<string, FFIFunction>; + type Symbols = Readonly<Record<string, FFIFunction>>; // /** // * Compile a callback function @@ -546,7 +549,7 @@ declare module "bun:ffi" { // */ // export function callback(ffi: FFIFunction, cb: Function): number; - export interface Library<Fns extends Record<string, Narrow<FFIFunction>>> { + export interface Library<Fns extends Readonly<Record<string, Narrow<FFIFunction>>>> { symbols: ConvertFns<Fns>; /** @@ -577,12 +580,14 @@ declare module "bun:ffi" { | (T extends object ? { [K in keyof T]: Narrow<T[K]> } : never) | Extract<{} | null | undefined, T>; - type ConvertFns<Fns extends Record<string, FFIFunction>> = { + type ConvertFns<Fns extends Readonly<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"]>>]; + ...args: Fns[K]["args"] extends infer A extends readonly FFITypeOrString[] + ? { [L in keyof A]: FFITypeToArgsType[ToFFIType<A[L]>] } + : [unknown] extends [Fns[K]["args"]] ? [] : never + ) => [unknown] extends [Fns[K]["returns"]] + ? void + : FFITypeToReturnsType[ToFFIType<NonNullable<Fns[K]["returns"]>>]; }; /** @@ -750,6 +755,165 @@ declare module "bun:ffi" { byteLength?: number, ): ArrayBuffer; + export namespace read { + /** + * The read function behaves similarly to DataView, + * but it's usually faster because it doesn't need to create a DataView or ArrayBuffer. + * + * @param ptr The memory address to read + * @param byteOffset bytes to skip before reading + * + * While there are some checks to catch invalid pointers, this is a difficult + * thing to do safely. Passing an invalid pointer can crash the program and + * reading beyond the bounds of the pointer will crash the program or cause + * undefined behavior. Use with care! + */ + export function u8(ptr: Pointer, byteOffset?: number): number; + /** + * The read function behaves similarly to DataView, + * but it's usually faster because it doesn't need to create a DataView or ArrayBuffer. + * + * @param ptr The memory address to read + * @param byteOffset bytes to skip before reading + * + * While there are some checks to catch invalid pointers, this is a difficult + * thing to do safely. Passing an invalid pointer can crash the program and + * reading beyond the bounds of the pointer will crash the program or cause + * undefined behavior. Use with care! + */ + export function i8(ptr: Pointer, byteOffset?: number): number; + /** + * The read function behaves similarly to DataView, + * but it's usually faster because it doesn't need to create a DataView or ArrayBuffer. + * + * @param ptr The memory address to read + * @param byteOffset bytes to skip before reading + * + * While there are some checks to catch invalid pointers, this is a difficult + * thing to do safely. Passing an invalid pointer can crash the program and + * reading beyond the bounds of the pointer will crash the program or cause + * undefined behavior. Use with care! + */ + export function u16(ptr: Pointer, byteOffset?: number): number; + /** + * The read function behaves similarly to DataView, + * but it's usually faster because it doesn't need to create a DataView or ArrayBuffer. + * + * @param ptr The memory address to read + * @param byteOffset bytes to skip before reading + * + * While there are some checks to catch invalid pointers, this is a difficult + * thing to do safely. Passing an invalid pointer can crash the program and + * reading beyond the bounds of the pointer will crash the program or cause + * undefined behavior. Use with care! + */ + export function i16(ptr: Pointer, byteOffset?: number): number; + /** + * The read function behaves similarly to DataView, + * but it's usually faster because it doesn't need to create a DataView or ArrayBuffer. + * + * @param ptr The memory address to read + * @param byteOffset bytes to skip before reading + * + * While there are some checks to catch invalid pointers, this is a difficult + * thing to do safely. Passing an invalid pointer can crash the program and + * reading beyond the bounds of the pointer will crash the program or cause + * undefined behavior. Use with care! + */ + export function u32(ptr: Pointer, byteOffset?: number): number; + /** + * The read function behaves similarly to DataView, + * but it's usually faster because it doesn't need to create a DataView or ArrayBuffer. + * + * @param ptr The memory address to read + * @param byteOffset bytes to skip before reading + * + * While there are some checks to catch invalid pointers, this is a difficult + * thing to do safely. Passing an invalid pointer can crash the program and + * reading beyond the bounds of the pointer will crash the program or cause + * undefined behavior. Use with care! + */ + export function i32(ptr: Pointer, byteOffset?: number): number; + /** + * The read function behaves similarly to DataView, + * but it's usually faster because it doesn't need to create a DataView or ArrayBuffer. + * + * @param ptr The memory address to read + * @param byteOffset bytes to skip before reading + * + * While there are some checks to catch invalid pointers, this is a difficult + * thing to do safely. Passing an invalid pointer can crash the program and + * reading beyond the bounds of the pointer will crash the program or cause + * undefined behavior. Use with care! + */ + export function f32(ptr: Pointer, byteOffset?: number): number; + /** + * The read function behaves similarly to DataView, + * but it's usually faster because it doesn't need to create a DataView or ArrayBuffer. + * + * @param ptr The memory address to read + * @param byteOffset bytes to skip before reading + * + * While there are some checks to catch invalid pointers, this is a difficult + * thing to do safely. Passing an invalid pointer can crash the program and + * reading beyond the bounds of the pointer will crash the program or cause + * undefined behavior. Use with care! + */ + export function u64(ptr: Pointer, byteOffset?: number): bigint; + /** + * The read function behaves similarly to DataView, + * but it's usually faster because it doesn't need to create a DataView or ArrayBuffer. + * + * @param ptr The memory address to read + * @param byteOffset bytes to skip before reading + * + * While there are some checks to catch invalid pointers, this is a difficult + * thing to do safely. Passing an invalid pointer can crash the program and + * reading beyond the bounds of the pointer will crash the program or cause + * undefined behavior. Use with care! + */ + export function i64(ptr: Pointer, byteOffset?: number): bigint; + /** + * The read function behaves similarly to DataView, + * but it's usually faster because it doesn't need to create a DataView or ArrayBuffer. + * + * @param ptr The memory address to read + * @param byteOffset bytes to skip before reading + * + * While there are some checks to catch invalid pointers, this is a difficult + * thing to do safely. Passing an invalid pointer can crash the program and + * reading beyond the bounds of the pointer will crash the program or cause + * undefined behavior. Use with care! + */ + export function f64(ptr: Pointer, byteOffset?: number): number; + /** + * The read function behaves similarly to DataView, + * but it's usually faster because it doesn't need to create a DataView or ArrayBuffer. + * + * @param ptr The memory address to read + * @param byteOffset bytes to skip before reading + * + * While there are some checks to catch invalid pointers, this is a difficult + * thing to do safely. Passing an invalid pointer can crash the program and + * reading beyond the bounds of the pointer will crash the program or cause + * undefined behavior. Use with care! + */ + export function ptr(ptr: Pointer, byteOffset?: number): number; + /** + * The read function behaves similarly to DataView, + * but it's usually faster because it doesn't need to create a DataView or ArrayBuffer. + * + * @param ptr The memory address to read + * @param byteOffset bytes to skip before reading + * + * While there are some checks to catch invalid pointers, this is a difficult + * thing to do safely. Passing an invalid pointer can crash the program and + * reading beyond the bounds of the pointer will crash the program or cause + * undefined behavior. Use with care! + */ + export function intptr(ptr: Pointer, byteOffset?: number): number; + } + /** * Get the pointer backing a {@link TypedArray} or {@link ArrayBuffer} * diff --git a/packages/bun-types/tests/ffi.test-d.ts b/packages/bun-types/tests/ffi.test-d.ts index 6b1174c50..36136d287 100644 --- a/packages/bun-types/tests/ffi.test-d.ts +++ b/packages/bun-types/tests/ffi.test-d.ts @@ -4,6 +4,8 @@ import { suffix, CString, Pointer, + JSCallback, + read, // FFIFunction, // ConvertFns, // Narrow, @@ -27,6 +29,14 @@ const lib = dlopen( args: [FFIType.i32, FFIType.i32], returns: FFIType.i32, }, + ptr_type: { + args: [FFIType.pointer], + returns: FFIType.pointer, + }, + fn_type: { + args: [FFIType.function], + returns: FFIType.function, + }, allArgs: { args: [ FFIType.char, // string @@ -67,6 +77,17 @@ const lib = dlopen( tsd.expectType<CString>(lib.symbols.sqlite3_libversion()); tsd.expectType<number>(lib.symbols.add(1, 2)); +tsd.expectType<Pointer | null>(lib.symbols.ptr_type(0)); +tc.assert< + tc.IsExact< + (typeof lib)["symbols"]["ptr_type"], + TypedArray | Pointer | CString + > +>; + +tsd.expectType<Pointer | null>(lib.symbols.fn_type(0)); +tc.assert<tc.IsExact<(typeof lib)["symbols"]["fn_type"], Pointer | JSCallback>>; + tc.assert< tc.IsExact< (typeof lib)["symbols"]["allArgs"], @@ -103,3 +124,41 @@ tc.assert< ] > >; + +const as_const_test = { + sqlite3_libversion: { + args: [], + returns: FFIType.cstring, + }, + multi_args: { + args: [FFIType.i32, FFIType.f32], + returns: FFIType.void, + }, + no_returns: { + args: [FFIType.i32], + }, + no_args: { + returns: FFIType.i32, + }, +} as const; + +const lib2 = dlopen(path, as_const_test); + +tsd.expectType<CString>(lib2.symbols.sqlite3_libversion()); +tsd.expectType<void>(lib2.symbols.multi_args(1, 2)); +tc.assert<tc.IsExact<ReturnType<(typeof lib2)["symbols"]["no_returns"]>, void>>; +tc.assert<tc.IsExact<Parameters<(typeof lib2)["symbols"]["no_args"]>, []>>; + +tsd.expectType<number>(read.u8(0)); +tsd.expectType<number>(read.u8(0, 0)); +tsd.expectType<number>(read.i8(0, 0)); +tsd.expectType<number>(read.u16(0, 0)); +tsd.expectType<number>(read.i16(0, 0)); +tsd.expectType<number>(read.u32(0, 0)); +tsd.expectType<number>(read.i32(0, 0)); +tsd.expectType<bigint>(read.u64(0, 0)); +tsd.expectType<bigint>(read.i64(0, 0)); +tsd.expectType<number>(read.f32(0, 0)); +tsd.expectType<number>(read.f64(0, 0)); +tsd.expectType<number>(read.ptr(0, 0)); +tsd.expectType<number>(read.intptr(0, 0)); |