aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Code Hz <git@hertz.moe> 2023-08-24 06:56:46 +0800
committerGravatar GitHub <noreply@github.com> 2023-08-23 15:56:46 -0700
commit5e07fd4fbcf59550668406d1e52044fa3649446a (patch)
tree717a0e309bf32d2884d304ff696e374b4e93afb9
parentc60385716b7a7ac9f788cdf7dfe37250321e0670 (diff)
downloadbun-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.ts262
-rw-r--r--packages/bun-types/tests/ffi.test-d.ts59
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));