mirror of
https://github.com/denoland/deno.git
synced 2025-09-28 05:04:48 +00: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";
|
| "pointer";
|
||||||
|
|
||||||
/** A foreign function as defined by its parameter and result types */
|
/** A foreign function as defined by its parameter and result types */
|
||||||
export interface ForeignFunction {
|
export interface ForeignFunction<
|
||||||
parameters: NativeType[];
|
Parameters extends readonly NativeType[] = readonly NativeType[],
|
||||||
result: 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`. */
|
/** 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 =
|
type TypedArray =
|
||||||
| Int8Array
|
| Int8Array
|
||||||
| Uint8Array
|
| Uint8Array
|
||||||
|
@ -202,10 +249,9 @@ declare namespace Deno {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A dynamic library resource */
|
/** 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 */
|
/** All of the registered symbols along with functions for calling them */
|
||||||
symbols: { [K in keyof S]: (...args: unknown[]) => unknown };
|
symbols: StaticForeignFunctionInterface<S>;
|
||||||
|
|
||||||
close(): void;
|
close(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +259,7 @@ declare namespace Deno {
|
||||||
*
|
*
|
||||||
* Opens a dynamic library and registers symbols
|
* Opens a dynamic library and registers symbols
|
||||||
*/
|
*/
|
||||||
export function dlopen<S extends Record<string, ForeignFunction>>(
|
export function dlopen<S extends ForeignFunctionInterface>(
|
||||||
filename: string | URL,
|
filename: string | URL,
|
||||||
symbols: S,
|
symbols: S,
|
||||||
): DynamicLibrary<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))]
|
#[cfg(not(debug_assertions))]
|
||||||
const BUILD_VARIANT: &str = "release";
|
const BUILD_VARIANT: &str = "release";
|
||||||
|
|
||||||
#[test]
|
fn build() {
|
||||||
fn basic() {
|
|
||||||
let mut build_plugin_base = Command::new("cargo");
|
let mut build_plugin_base = Command::new("cargo");
|
||||||
let mut build_plugin =
|
let mut build_plugin =
|
||||||
build_plugin_base.arg("build").arg("-p").arg("test_ffi");
|
build_plugin_base.arg("build").arg("-p").arg("test_ffi");
|
||||||
|
@ -19,6 +18,12 @@ fn basic() {
|
||||||
}
|
}
|
||||||
let build_plugin_output = build_plugin.output().unwrap();
|
let build_plugin_output = build_plugin.output().unwrap();
|
||||||
assert!(build_plugin_output.status.success());
|
assert!(build_plugin_output.status.success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic() {
|
||||||
|
build();
|
||||||
|
|
||||||
let output = deno_cmd()
|
let output = deno_cmd()
|
||||||
.arg("run")
|
.arg("run")
|
||||||
.arg("--allow-ffi")
|
.arg("--allow-ffi")
|
||||||
|
@ -66,3 +71,26 @@ fn basic() {
|
||||||
assert_eq!(stdout, expected);
|
assert_eq!(stdout, expected);
|
||||||
assert_eq!(stderr, "");
|
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…
Add table
Add a link
Reference in a new issue