summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2022-01-10 21:03:25 +0530
committerGitHub <noreply@github.com>2022-01-10 21:03:25 +0530
commitd8e96d2742bcbbbc6381b908a68067ec46fc320f (patch)
tree2ad2d479df187d229e522879c465fd7c84b0b072
parent994ac6d49b4f0be144acaaebebb81ebed7d1e744 (diff)
feat(ext/ffi): infer symbol types (#13221)
Co-authored-by: sinclairzx81 <sinclairzx81@users.noreply.github.com>
-rw-r--r--cli/dts/lib.deno.unstable.d.ts62
-rw-r--r--test_ffi/tests/ffi_types.ts111
-rw-r--r--test_ffi/tests/integration_tests.rs32
3 files changed, 195 insertions, 10 deletions
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts
index 442f5d7d4..a371f37d1 100644
--- a/cli/dts/lib.deno.unstable.d.ts
+++ b/cli/dts/lib.deno.unstable.d.ts
@@ -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>;
diff --git a/test_ffi/tests/ffi_types.ts b/test_ffi/tests/ffi_types.ts
new file mode 100644
index 000000000..0066e362c
--- /dev/null
+++ b/test_ffi/tests/ffi_types.ts
@@ -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) => {});
diff --git a/test_ffi/tests/integration_tests.rs b/test_ffi/tests/integration_tests.rs
index 9b4028978..39550925f 100644
--- a/test_ffi/tests/integration_tests.rs
+++ b/test_ffi/tests/integration_tests.rs
@@ -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, "");
+}