mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 07:14:47 -05:00
feat(ext/ffi): infer symbol types (#13221)
Co-authored-by: sinclairzx81 <sinclairzx81@users.noreply.github.com>
This commit is contained in:
parent
994ac6d49b
commit
d8e96d2742
3 changed files with 195 additions and 10 deletions
62
cli/dts/lib.deno.unstable.d.ts
vendored
62
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -121,13 +121,60 @@ declare namespace Deno {
|
|||
| "pointer";
|
||||
|
||||
/** A foreign function as defined by its parameter and result types */
|
||||
export interface ForeignFunction {
|
||||
parameters: NativeType[];
|
||||
result: NativeType;
|
||||
export interface ForeignFunction<
|
||||
Parameters extends readonly NativeType[] = readonly NativeType[],
|
||||
Result extends NativeType = NativeType,
|
||||
NonBlocking extends boolean = boolean,
|
||||
> {
|
||||
parameters: Parameters;
|
||||
result: Result;
|
||||
/** When true, function calls will run on a dedicated blocking thread and will return a Promise resolving to the `result`. */
|
||||
nonblocking?: boolean;
|
||||
nonblocking?: NonBlocking;
|
||||
}
|
||||
|
||||
/** A foreign function interface descriptor */
|
||||
export interface ForeignFunctionInterface {
|
||||
[name: string]: ForeignFunction;
|
||||
}
|
||||
|
||||
/** All possible number types interfacing with foreign functions */
|
||||
type StaticNativeNumberType = Exclude<NativeType, "void" | "pointer">;
|
||||
|
||||
/** Infers a foreign function return type */
|
||||
type StaticForeignFunctionResult<T extends NativeType> = T extends "void"
|
||||
? void
|
||||
: T extends StaticNativeNumberType ? number
|
||||
: T extends "pointer" ? UnsafePointer
|
||||
: never;
|
||||
|
||||
type StaticForeignFunctionParameter<T> = T extends "void" ? void
|
||||
: T extends StaticNativeNumberType ? number
|
||||
: T extends "pointer" ? Deno.UnsafePointer | Deno.TypedArray
|
||||
: unknown;
|
||||
|
||||
/** Infers a foreign function parameter list. */
|
||||
type StaticForeignFunctionParameters<T extends readonly NativeType[]> = [
|
||||
...{
|
||||
[K in keyof T]: StaticForeignFunctionParameter<T[K]>;
|
||||
},
|
||||
];
|
||||
|
||||
/** Infers a foreign function */
|
||||
type StaticForeignFunction<T extends ForeignFunction> = (
|
||||
...args: StaticForeignFunctionParameters<T["parameters"]>
|
||||
) => ConditionalAsync<
|
||||
T["nonblocking"],
|
||||
StaticForeignFunctionResult<T["result"]>
|
||||
>;
|
||||
|
||||
type ConditionalAsync<IsAsync extends boolean | undefined, T> =
|
||||
IsAsync extends true ? Promise<T> : T;
|
||||
|
||||
/** Infers a foreign function interface */
|
||||
type StaticForeignFunctionInterface<T extends ForeignFunctionInterface> = {
|
||||
[K in keyof T]: StaticForeignFunction<T[K]>;
|
||||
};
|
||||
|
||||
type TypedArray =
|
||||
| Int8Array
|
||||
| Uint8Array
|
||||
|
@ -202,10 +249,9 @@ declare namespace Deno {
|
|||
}
|
||||
|
||||
/** A dynamic library resource */
|
||||
export interface DynamicLibrary<S extends Record<string, ForeignFunction>> {
|
||||
export interface DynamicLibrary<S extends ForeignFunctionInterface> {
|
||||
/** All of the registered symbols along with functions for calling them */
|
||||
symbols: { [K in keyof S]: (...args: unknown[]) => unknown };
|
||||
|
||||
symbols: StaticForeignFunctionInterface<S>;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
|
@ -213,7 +259,7 @@ declare namespace Deno {
|
|||
*
|
||||
* Opens a dynamic library and registers symbols
|
||||
*/
|
||||
export function dlopen<S extends Record<string, ForeignFunction>>(
|
||||
export function dlopen<S extends ForeignFunctionInterface>(
|
||||
filename: string | URL,
|
||||
symbols: S,
|
||||
): DynamicLibrary<S>;
|
||||
|
|
111
test_ffi/tests/ffi_types.ts
Normal file
111
test_ffi/tests/ffi_types.ts
Normal file
|
@ -0,0 +1,111 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
// deno-lint-ignore-file
|
||||
// Only for testing types. Invoke with `deno cache`
|
||||
|
||||
const remote = Deno.dlopen(
|
||||
"dummy_lib.so",
|
||||
{
|
||||
method1: { parameters: ["usize", "usize"], result: "void" },
|
||||
method2: { parameters: ["void"], result: "void" },
|
||||
method3: { parameters: ["usize"], result: "void" },
|
||||
method4: { parameters: ["isize"], result: "void" },
|
||||
method5: { parameters: ["u8"], result: "void" },
|
||||
method6: { parameters: ["u16"], result: "void" },
|
||||
method7: { parameters: ["u32"], result: "void" },
|
||||
method8: { parameters: ["u64"], result: "void" },
|
||||
method9: { parameters: ["i8"], result: "void" },
|
||||
method10: { parameters: ["i16"], result: "void" },
|
||||
method11: { parameters: ["i32"], result: "void" },
|
||||
method12: { parameters: ["i64"], result: "void" },
|
||||
method13: { parameters: ["f32"], result: "void" },
|
||||
method14: { parameters: ["f64"], result: "void" },
|
||||
method15: { parameters: ["pointer"], result: "void" },
|
||||
method16: { parameters: [], result: "usize" },
|
||||
method17: { parameters: [], result: "usize", nonblocking: true },
|
||||
method18: { parameters: [], result: "pointer" },
|
||||
method19: { parameters: [], result: "pointer", nonblocking: true },
|
||||
} as const,
|
||||
);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method1(0);
|
||||
// @ts-expect-error: Invalid return type
|
||||
<number> remote.symbols.method1(0, 0);
|
||||
<void> remote.symbols.method1(0, 0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method2(null);
|
||||
remote.symbols.method2(void 0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method3(null);
|
||||
remote.symbols.method3(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method4(null);
|
||||
remote.symbols.method4(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method5(null);
|
||||
remote.symbols.method5(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method6(null);
|
||||
remote.symbols.method6(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method7(null);
|
||||
remote.symbols.method7(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method8(null);
|
||||
remote.symbols.method8(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method9(null);
|
||||
remote.symbols.method9(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method10(null);
|
||||
remote.symbols.method10(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method11(null);
|
||||
remote.symbols.method11(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method12(null);
|
||||
remote.symbols.method12(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method13(null);
|
||||
remote.symbols.method13(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method14(null);
|
||||
remote.symbols.method14(0);
|
||||
|
||||
// @ts-expect-error: Invalid argument
|
||||
remote.symbols.method15(null);
|
||||
remote.symbols.method15(new Uint16Array(1));
|
||||
remote.symbols.method15({} as Deno.UnsafePointer);
|
||||
|
||||
const result = remote.symbols.method16();
|
||||
// @ts-expect-error: Invalid argument
|
||||
let r_0: string = result;
|
||||
let r_1: number = result;
|
||||
|
||||
const result2 = remote.symbols.method17();
|
||||
// @ts-expect-error: Invalid argument
|
||||
result2.then((_0: string) => {});
|
||||
result2.then((_1: number) => {});
|
||||
|
||||
const result3 = remote.symbols.method18();
|
||||
// @ts-expect-error: Invalid argument
|
||||
let r3_0: Deno.TypedArray = result3;
|
||||
let r3_1: Deno.UnsafePointer = result3;
|
||||
|
||||
const result4 = remote.symbols.method19();
|
||||
// @ts-expect-error: Invalid argument
|
||||
result4.then((_0: Deno.TypedArray) => {});
|
||||
result4.then((_1: Deno.UnsafePointer) => {});
|
|
@ -9,8 +9,7 @@ const BUILD_VARIANT: &str = "debug";
|
|||
#[cfg(not(debug_assertions))]
|
||||
const BUILD_VARIANT: &str = "release";
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
fn build() {
|
||||
let mut build_plugin_base = Command::new("cargo");
|
||||
let mut build_plugin =
|
||||
build_plugin_base.arg("build").arg("-p").arg("test_ffi");
|
||||
|
@ -19,6 +18,12 @@ fn basic() {
|
|||
}
|
||||
let build_plugin_output = build_plugin.output().unwrap();
|
||||
assert!(build_plugin_output.status.success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
build();
|
||||
|
||||
let output = deno_cmd()
|
||||
.arg("run")
|
||||
.arg("--allow-ffi")
|
||||
|
@ -66,3 +71,26 @@ fn basic() {
|
|||
assert_eq!(stdout, expected);
|
||||
assert_eq!(stderr, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_types() {
|
||||
build();
|
||||
|
||||
let output = deno_cmd()
|
||||
.arg("cache")
|
||||
.arg("--unstable")
|
||||
.arg("--quiet")
|
||||
.arg("tests/ffi_types.ts")
|
||||
.env("NO_COLOR", "1")
|
||||
.output()
|
||||
.unwrap();
|
||||
let stdout = std::str::from_utf8(&output.stdout).unwrap();
|
||||
let stderr = std::str::from_utf8(&output.stderr).unwrap();
|
||||
if !output.status.success() {
|
||||
println!("stdout {}", stdout);
|
||||
println!("stderr {}", stderr);
|
||||
}
|
||||
println!("{:?}", output.status);
|
||||
assert!(output.status.success());
|
||||
assert_eq!(stderr, "");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue