Skip to content

Commit

Permalink
feat(ext/ffi): better type hints for Deno.dlopen (denoland#16874)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahaoboy committed Dec 3, 2022
1 parent 0169949 commit 8b5b327
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 19 deletions.
17 changes: 14 additions & 3 deletions cli/tsc/dts/lib.deno.unstable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ declare namespace Deno {
/** The definition of the function. */
definition: Fn;

constructor(pointer: PointerValue, definition: Fn);
constructor(pointer: PointerValue, definition: Const<Fn>);

/** Call the foreign function. */
call: FromForeignFunction<Fn>;
Expand Down Expand Up @@ -494,7 +494,7 @@ declare namespace Deno {
Definition extends UnsafeCallbackDefinition = UnsafeCallbackDefinition,
> {
constructor(
definition: Definition,
definition: Const<Definition>,
callback: UnsafeCallbackFunction<
Definition["parameters"],
Definition["result"]
Expand Down Expand Up @@ -562,6 +562,17 @@ declare namespace Deno {
close(): void;
}

/**
* This magic code used to implement better type hints for {@linkcode Deno.dlopen}
*/
type Cast<A, B> = A extends B ? A : B;
type Const<T> = Cast<
T,
| (T extends string | number | bigint | boolean ? T : never)
| { [K in keyof T]: Const<T[K]> }
| []
>;

/** **UNSTABLE**: New API, yet to be vetted.
*
* Opens an external dynamic library and registers symbols, making foreign
Expand Down Expand Up @@ -611,7 +622,7 @@ declare namespace Deno {
*/
export function dlopen<S extends ForeignLibraryInterface>(
filename: string | URL,
symbols: S,
symbols: Const<S>,
): DynamicLibrary<S>;

/** **UNSTABLE**: New API, yet to be vetted.
Expand Down
36 changes: 20 additions & 16 deletions test_ffi/tests/ffi_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
const remote = Deno.dlopen(
"dummy_lib.so",
{
method1: { parameters: ["usize", "usize"], result: "void", callback: true },
method1: { parameters: ["usize", "bool"], result: "void", callback: true },
method2: { parameters: [], result: "void" },
method3: { parameters: ["usize"], result: "void" },
method4: { parameters: ["isize"], result: "void" },
Expand Down Expand Up @@ -61,31 +61,35 @@ const remote = Deno.dlopen(
static13: { type: "f32" },
static14: { type: "f64" },
static15: { type: "bool" },
} as const,
},
);

Deno.dlopen(
"dummy_lib_2.so",
// @ts-expect-error: Returning a function pointer
// is declared using "pointer" or "function" + UnsafeFnPointer
{
wrong_method1: {
parameters: [],
// @ts-expect-error not assignable to type 'NativeResultType'
result: {
function: {
parameters: [],
result: "void",
},
},
},
} as const,
},
);

// @ts-expect-error: Invalid argument
remote.symbols.method1(0);
// @ts-expect-error: Invalid argument
remote.symbols.method1(0, 0);
// @ts-expect-error: Invalid argument
remote.symbols.method1(true, true);
// @ts-expect-error: Invalid return type
<number> remote.symbols.method1(0, 0);
<void> remote.symbols.method1(0n, 0n);
<number> remote.symbols.method1(0, true);
<void> remote.symbols.method1(0n, true);

// @ts-expect-error: Expected 0 arguments, but got 1.
remote.symbols.method2(null);
Expand Down Expand Up @@ -170,7 +174,7 @@ const fnptr = new Deno.UnsafeFnPointer(
{
parameters: ["u32", "pointer"],
result: "void",
} as const,
},
);
// @ts-expect-error: Invalid argument
fnptr.call(null, null);
Expand All @@ -180,68 +184,68 @@ const unsafe_callback_wrong1 = new Deno.UnsafeCallback(
{
parameters: ["i8"],
result: "void",
} as const,
},
// @ts-expect-error: i8 is not a pointer
(_: bigint) => {},
);
const unsafe_callback_wrong2 = new Deno.UnsafeCallback(
{
parameters: ["pointer"],
result: "u64",
} as const,
},
// @ts-expect-error: must return a number or bigint
(_: Deno.UnsafePointer) => {},
);
const unsafe_callback_wrong3 = new Deno.UnsafeCallback(
{
parameters: [],
result: "void",
} as const,
},
// @ts-expect-error: no parameters
(_: Deno.UnsafePointer) => {},
);
const unsafe_callback_wrong4 = new Deno.UnsafeCallback(
{
parameters: ["u64"],
result: "void",
} as const,
},
// @ts-expect-error: Callback's 64bit parameters are either number or bigint
(_: number) => {},
);
const unsafe_callback_right1 = new Deno.UnsafeCallback(
{
parameters: ["u8", "u32", "pointer"],
result: "void",
} as const,
},
(_1: number, _2: number, _3: Deno.PointerValue) => {},
);
const unsafe_callback_right2 = new Deno.UnsafeCallback(
{
parameters: [],
result: "u8",
} as const,
},
() => 3,
);
const unsafe_callback_right3 = new Deno.UnsafeCallback(
{
parameters: [],
result: "function",
} as const,
},
// Callbacks can return other callbacks' pointers, if really wanted.
() => unsafe_callback_right2.pointer,
);
const unsafe_callback_right4 = new Deno.UnsafeCallback(
{
parameters: ["u8", "u32", "pointer"],
result: "u8",
} as const,
},
(_1: number, _2: number, _3: Deno.PointerValue) => 3,
);
const unsafe_callback_right5 = new Deno.UnsafeCallback(
{
parameters: ["u8", "i32", "pointer"],
result: "void",
} as const,
},
(_1: number, _2: number, _3: Deno.PointerValue) => {},
);

Expand Down

0 comments on commit 8b5b327

Please sign in to comment.