diff options
-rw-r--r-- | cli/dts/lib.deno.unstable.d.ts | 62 | ||||
-rw-r--r-- | test_ffi/tests/ffi_types.ts | 111 | ||||
-rw-r--r-- | test_ffi/tests/integration_tests.rs | 32 |
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, ""); +} |