1
0
Fork 0
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:
Divy Srivastava 2022-01-10 21:03:25 +05:30 committed by GitHub
parent 994ac6d49b
commit d8e96d2742
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 195 additions and 10 deletions

View file

@ -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
View 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) => {});

View file

@ -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, "");
}