diff --git a/cli.ts b/cli.ts deleted file mode 100644 index 9becb3d..0000000 --- a/cli.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -import { ensureDir } from "https://deno.land/std@0.132.0/fs/ensure_dir.ts"; -import { parse } from "https://deno.land/std@0.132.0/flags/mod.ts"; -import { join } from "https://deno.land/std@0.132.0/path/mod.ts"; -import { relative } from "https://deno.land/std@0.132.0/path/mod.ts"; -import { codegen } from "./codegen.ts"; - -const flags = parse(Deno.args, { "--": true }); -const release = !!flags.release; - -const metafile = join( - Deno.env.get("OUT_DIR") || await findRelativeTarget(), - "bindings.json", -); - -function build() { - const args = ["build"]; - if (release) args.push("--release"); - args.push(...flags["--"]); - const proc = new Deno.Command("cargo", { args }); - return proc.output(); -} - -async function findRelativeTarget() { - const p = new Deno.Command("cargo", { - args: ["metadata", "--format-version", "1"], - stdout: "piped", - }); - const output = await p.output(); - const metadata = JSON.parse(new TextDecoder().decode(output.stdout)); - return relative(Deno.cwd(), metadata.workspace_root); -} - -let source = null; -async function generate() { - let conf; - try { - conf = JSON.parse(await Deno.readTextFile(metafile)); - } catch (_) { - // Nothing to update. - return; - } - - let cargoTarget = Deno.env.get("CARGO_TARGET_DIR"); - if (!cargoTarget) cargoTarget = "../target"; - - const pkgName = conf.name; - const fetchPrefix = typeof flags.release == "string" - ? flags.release - : await findRelativeTarget() + [cargoTarget, release ? "release" : "debug"] - .join("/"); - - source = "// Auto-generated with deno_bindgen\n"; - source += codegen( - fetchPrefix, - pkgName, - conf.typeDefs, - conf.tsTypes, - conf.symbols, - { - le: conf.littleEndian, - release, - releaseURL: flags.release, - }, - ); - - await Deno.remove(metafile); -} - -try { - await Deno.remove(metafile); -} catch { - // no op -} - -const status = await build().catch((_) => Deno.removeSync(metafile)); -if (status?.success || typeof flags.release == "string") { - await generate(); - if (source) { - await ensureDir("bindings"); - await Deno.writeTextFile("bindings/bindings.ts", source); - } -} - -Deno.exit(status?.code || 0); diff --git a/cli2.ts b/cli2.ts deleted file mode 100644 index 878fbe8..0000000 --- a/cli2.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { parse } from "https://deno.land/std@0.132.0/flags/mod.ts"; - -const flags = parse(Deno.args, { "--": true }); -const release = !!flags.release; - -function build() { - const args = ["build"]; - if (release) args.push("--release"); - args.push(...flags["--"]); - const proc = new Deno.Command("cargo", { args, stderr: "inherit" }); - return proc.outputSync(); -} - -function nm() { - // Run `nm` to get the symbols from the compiled library. - const args = [ - "nm", - "--format=bsd", - "--defined-only", - "target/debug/libdeno_bindgen_test.dylib", - ]; - if (release) args.push("--demangle"); - const proc = new Deno.Command("nm", { args, stdout: "piped" }); - const output = proc.outputSync(); - const stdout = new TextDecoder().decode(output.stdout); - const symbols = stdout.split("\n").filter((s) => s.length > 0).slice(1); - - const symbols2 = symbols.map((s) => { - const [addr, ty, name] = s.split(" "); - return { addr, ty, name }; - }).filter((s) => s.name.startsWith("___de90_")); - return symbols2; -} - -function run_init(symbols) { - const symbols_obj = {}; - symbols.forEach(({ name }) => { - symbols_obj[name.slice(1)] = { - parameters: ["buffer", "buffer"], - result: "pointer", - }; - }); - - const lib = Deno.dlopen("./target/debug/libdeno_bindgen_test.dylib", symbols_obj); - const params = new Uint8Array(20); - const result = new Uint8Array(1); - const processed = []; - for (const fn in lib.symbols) { - const name_ptr = lib.symbols[fn](params, result); - const name = Deno.UnsafePointerView.getCString(name_ptr); - processed.push({ name, params: [...params].map(p => C_TYPE[p]), result: C_TYPE[result[0]] }) - } - return processed; -} - -const C_TYPE = ["A", "B"]; - -function codegen(symbols) { - let code = ''; - for (let i = 0; i < symbols.length; i++) { - const { name, params, result } = symbols[i]; - const params_str = params.map((p, j) => `p${j}: ${p}`).join(', '); - const params_idents = params.map((p, j) => `p${j}`).join(', '); - code += `export function ${name}(${params_str}): ${result} { return lib.${name}(${params_idents}); }\n`; - } - - console.log(code) -} - -build(); -const symbols = nm(); -const processed = run_init(symbols); -codegen(processed); - diff --git a/codegen.ts b/codegen.ts deleted file mode 100644 index f7a9b96..0000000 --- a/codegen.ts +++ /dev/null @@ -1,300 +0,0 @@ -// deno-lint-ignore-file no-explicit-any -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -import { - createFromBuffer, - GlobalConfiguration, -} from "https://deno.land/x/dprint@0.2.0/mod.ts"; -import * as Cache from "https://deno.land/x/cache@0.2.13/mod.ts"; - -Cache.configure({ directory: Cache.options.directory }); -const cache = Cache.namespace("deno_bindgen_cli"); - -const globalConfig: GlobalConfiguration = { - indentWidth: 2, - lineWidth: 80, -}; - -const file = await cache.cache( - "https://plugins.dprint.dev/typescript-0.57.0.wasm", -); - -const tsFormatter = createFromBuffer(Deno.readFileSync(file.path)); - -tsFormatter.setConfig(globalConfig, { - semiColons: "asi", -}); - -const Type: Record = { - void: "null", - i8: "number", - u8: "number", - i16: "number", - u16: "number", - i32: "number", - u32: "number", - i64: "bigint", - u64: "bigint", - usize: "bigint", - isize: "bigint", - f32: "number", - f64: "number", -}; - -const BufferTypes: Record = { - str: "string", - buffer: "Uint8Array", - buffermut: "Uint8Array", - ptr: "Uint8Array", -}; - -enum Encoder { - JsonStringify = "JSON.stringify", - None = "", -} - -const BufferTypeEncoders: Record = { - str: Encoder.None, - buffer: Encoder.None, - buffermut: Encoder.None, - ptr: Encoder.None, -}; - -type TypeDef = Record>; - -function resolveType(typeDefs: TypeDef, type: any): string { - const t = typeof type == "string" ? type : type.structenum.ident; - if (Type[t] !== undefined) return Type[t]; - if (BufferTypes[t] !== undefined) return BufferTypes[t]; - if (Object.keys(typeDefs).find((f) => f == t) !== undefined) { - return t; - } - return "any"; -} - -function resolveDlopenParameter(typeDefs: TypeDef, type: any): string { - const t = typeof type == "string" ? type : type.structenum.ident; - if (Type[t] !== undefined) return t; - if (BufferTypes[t] !== undefined) { - return "buffer"; - } - if ( - Object.keys(typeDefs).find((f) => f == t) !== undefined - ) { - return "buffer"; - } else { - return "pointer"; - } - // deno-lint-ignore no-unreachable - throw new TypeError(`Type not supported: ${t}`); -} - -type Sig = Record< - string, - { - parameters: any[]; - result: string; - nonBlocking?: boolean; - } ->; - -type Options = { - le?: boolean; - release?: boolean; - releaseURL: string | undefined; -}; - -function isTypeDef(p: any) { - return typeof p !== "string"; -} - -function isBufferType(p: any) { - return isTypeDef(p) || BufferTypes[p] !== undefined; -} - -function needsPointer(p: any) { - return isBufferType(p) && p !== "buffer" && p !== "buffermut"; -} - -// TODO(@littledivy): factor out options in an interface -export function codegen( - fetchPrefix: string, - name: string, - decl: TypeDef, - typescript: Record, - signature: Sig, - options?: Options, -) { - signature = Object.keys(signature) - .sort() - .reduce( - (acc, key) => ({ - ...acc, - [key]: signature[key], - }), - {}, - ); - - return tsFormatter.formatText( - "bindings.ts", - ` -function encode(v: string | Uint8Array): Uint8Array { - if (typeof v !== "string") return v; - return new TextEncoder().encode(v); -} - -function decode(v: Uint8Array): string { - return new TextDecoder().decode(v); -} - -// deno-lint-ignore no-explicit-any -function readPointer(v: any): Uint8Array { - const ptr = new Deno.UnsafePointerView(v); - const lengthBe = new Uint8Array(4); - const view = new DataView(lengthBe.buffer); - ptr.copyInto(lengthBe, 0); - const buf = new Uint8Array(view.getUint32(0)); - ptr.copyInto(buf, 4); - return buf; -} - -const url = new URL("${fetchPrefix}", import.meta.url); -${ - typeof options?.releaseURL === "string" - ? ` -import { dlopen, FetchOptions } from "https://deno.land/x/plug@1.0.1/mod.ts"; -let uri = url.toString(); -if (!uri.endsWith("/")) uri += "/"; - -let darwin: string | { aarch64: string; x86_64: string } = uri; - -const opts: FetchOptions = { - name: "${name}", - url: { - darwin, - windows: uri, - linux: uri, - }, - suffixes: { - darwin: { - aarch64: "_arm64", - }, - }, - cache: ${!!options?.release ? '"use"' : '"reloadAll"'}, -}; -const { symbols } = await dlopen(opts, { - ` - : ` -let uri = url.pathname; -if (!uri.endsWith("/")) uri += "/"; - -// https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya#parameters -if (Deno.build.os === "windows") { - uri = uri.replace(/\\//g, "\\\\"); - // Remove leading slash - if (uri.startsWith("\\\\")) { - uri = uri.slice(1); - } -} - -const { symbols } = Deno.dlopen({ - darwin: uri + "lib${name}.dylib", - windows: uri + "${name}.dll", - linux: uri + "lib${name}.so", - freebsd: uri + "lib${name}.so", - netbsd: uri + "lib${name}.so", - aix: uri + "lib${name}.so", - solaris: uri + "lib${name}.so", - illumos: uri + "lib${name}.so", -}[Deno.build.os], {` - } - ${ - Object.keys(signature) - .map( - (sig) => - `${sig}: { parameters: [ ${ - signature[sig].parameters - .map((p) => { - const ffiParam = resolveDlopenParameter(decl, p); - // FIXME: Dupe logic here. - return `"${ffiParam}"${isBufferType(p) ? `, "usize"` : ""}`; - }) - .join(", ") - } ], result: "${ - resolveDlopenParameter( - decl, - signature[sig].result, - ) - }", nonblocking: ${String(!!signature[sig].nonBlocking)} }`, - ) - .join(", ") - } }); -${ - Object.keys(decl) - .sort() - .map((def) => typescript[def]) - .join("\n") - } -${ - Object.keys(signature) - .map((sig) => { - const { parameters, result, nonBlocking } = signature[sig]; - - return `export function ${sig}(${ - parameters - .map((p, i) => `a${i}: ${resolveType(decl, p)}`) - .join(",") - }) { - ${ - parameters - .map((p, i) => - isBufferType(p) - ? `const a${i}_buf = encode(${ - BufferTypeEncoders[p] ?? Encoder.JsonStringify - }(a${i}));` - : null - ) - .filter((c) => c !== null) - .join("\n") - } - - const rawResult = symbols.${sig}(${ - parameters - .map((p, i) => (isBufferType(p) - ? `a${i}_buf, a${i}_buf.byteLength` - : `a${i}`) - ) - .join(", ") - }); - ${ - isBufferType(result) - ? nonBlocking - ? `const result = rawResult.then(readPointer);` - : `const result = readPointer(rawResult);` - : "const result = rawResult;" - }; - ${ - isTypeDef(result) - ? nonBlocking - ? `return result.then(r => JSON.parse(decode(r))) as Promise<${ - resolveType( - decl, - result, - ) - }>;` - : `return JSON.parse(decode(result)) as ${ - resolveType(decl, result) - };` - : result == "str" - ? nonBlocking - ? "return result.then(decode);" - : "return decode(result);" - : "return result;" - }; -}`; - }) - .join("\n") - } - `, - ); -} diff --git a/deno_bindgen_ir/codegen/deno.rs b/deno_bindgen_ir/codegen/deno.rs index a1e89a0..1b5715c 100644 --- a/deno_bindgen_ir/codegen/deno.rs +++ b/deno_bindgen_ir/codegen/deno.rs @@ -9,7 +9,8 @@ use crate::{ Symbol, Type, }; -struct TypeScriptType<'a>(&'a str); +// (ident, is_custom_type) +struct TypeScriptType<'a>(&'a str, bool); impl std::fmt::Display for TypeScriptType<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -21,13 +22,20 @@ impl std::fmt::Display for TypeScriptType<'_> { impl TypeScriptType<'_> { fn into_raw<'a>(&self, ident: &'a str) -> Cow<'a, str> { match self { - Self("Uint8Array") => { + Self("Uint8Array", false) => { Cow::Owned(format!("{ident},\n {ident}.byteLength")) } _ => Cow::Borrowed(ident), } } + fn from_raw<'a>(&self, ident: &'a str) -> Option { + match self { + Self(ty_str, true) => Some(format!("{ty_str}.__constructor({ident})")), + _ => None, + } + } + fn apply_promise(&self, non_blocking: bool) -> Cow<'_, str> { if non_blocking { Cow::Owned(format!("Promise<{}>", self.0)) @@ -39,21 +47,25 @@ impl TypeScriptType<'_> { impl From for TypeScriptType<'_> { fn from(value: Type) -> Self { - Self(match value { - Type::Void => "void", - Type::Uint8 - | Type::Uint16 - | Type::Uint32 - | Type::Uint64 - | Type::Int8 - | Type::Int16 - | Type::Int32 - | Type::Int64 - | Type::Float32 - | Type::Float64 => "number", - Type::Pointer => "Deno.PointerObject | null", - Type::Buffer => "Uint8Array", - }) + Self( + (match value { + Type::Void => "void", + Type::Uint8 + | Type::Uint16 + | Type::Uint32 + | Type::Uint64 + | Type::Int8 + | Type::Int16 + | Type::Int32 + | Type::Int64 + | Type::Float32 + | Type::Float64 => "number", + Type::Pointer => "Deno.PointerObject | null", + Type::Buffer => "Uint8Array", + Type::CustomType(name) => name, + }), + matches!(value, Type::CustomType(_)), + ) } } @@ -80,7 +92,7 @@ impl From for DenoFfiType { Type::Int64 => "i64", Type::Float32 => "f32", Type::Float64 => "f64", - Type::Pointer => "pointer", + Type::CustomType(..) | Type::Pointer => "pointer", Type::Buffer => "buffer", }; @@ -207,13 +219,19 @@ impl<'a> Codegen<'a> { 0, ('(', ')'), )?; + let ret_ty = TypeScriptType::from(symbol.return_type); writeln!( writer, ": {} {{", - TypeScriptType::from(symbol.return_type) - .apply_promise(symbol.non_blocking) + ret_ty.apply_promise(symbol.non_blocking) )?; - write!(writer, " return symbols.{}", symbol.name)?; + let maybe_ret_transform = ret_ty.from_raw("ret"); + if maybe_ret_transform.is_some() { + write!(writer, " const ret = ")?; + } else { + write!(writer, " return ")?; + } + write!(writer, "symbols.{}", symbol.name)?; format_paren( writer, symbol.parameters, @@ -233,6 +251,9 @@ impl<'a> Codegen<'a> { ('(', ')'), )?; + if let Some(ret_transform) = maybe_ret_transform { + write!(writer, "\n return {ret_transform};")?; + } writeln!(writer, "\n}}\n")?; } Inventory::Struct(Struct { @@ -251,7 +272,7 @@ impl<'a> Codegen<'a> { writeln!( writer, - " static __constructor(ptr: Deno.PointerObject) {{" + " static __constructor(ptr: Deno.PointerObject | null) {{" )?; writeln!( writer, @@ -262,12 +283,23 @@ impl<'a> Codegen<'a> { writeln!(writer, " }}")?; for method in methods { - // Skip the first argument, which is always the pointer to the struct. - let parameters = &method.parameters[1..]; - writeln!( + let mut parameters = method.parameters; + + if !method.is_constructor { + // Skip the self ptr argument. + parameters = &method.parameters[1..]; + } + + let method_name = if method.is_constructor { + "constructor" + } else { + &method.name + }; + + write!( writer, - "\n {name}({parameters}): {return_type} {{", - name = method.name, + "\n {name}({parameters})", + name = method_name, parameters = parameters .iter() .enumerate() @@ -276,16 +308,27 @@ impl<'a> Codegen<'a> { }) .collect::>() .join(", "), - return_type = TypeScriptType::from(method.return_type) )?; - write!(writer, " return {}", method.name)?; + if !method.is_constructor { + let return_type = TypeScriptType::from(method.return_type); + writeln!(writer, ": {return_type} {{")?; + } else { + // Typescript doesn't allow constructors to have a return type. + writeln!(writer, " {{")?; + } + + // Apply name mangling. + write!(writer, " return __{}_{}", name, method.name)?; format_paren( writer, parameters, - true, + !method.is_constructor, |writer, parameters| { - writeln!(writer, " this.ptr,",)?; + if !method.is_constructor { + writeln!(writer, " this.ptr,",)?; + } + for (i, parameter) in parameters.iter().enumerate() { let ident = format!("arg{}", i); writeln!( @@ -294,6 +337,7 @@ impl<'a> Codegen<'a> { TypeScriptType::from(*parameter).into_raw(&ident) )?; } + Ok(()) }, 4, diff --git a/deno_bindgen_ir/lib.rs b/deno_bindgen_ir/lib.rs index bcc9567..c813a88 100644 --- a/deno_bindgen_ir/lib.rs +++ b/deno_bindgen_ir/lib.rs @@ -23,6 +23,8 @@ pub enum Type { Float64, Pointer, Buffer, + + CustomType(&'static str), } pub type RawTypes = &'static [Type]; @@ -31,16 +33,19 @@ impl Type { pub fn raw(&self) -> RawTypes { match self { Self::Buffer => &[Self::Pointer, Self::Uint32], - Self::Pointer => &[Self::Pointer], + Self::Pointer | Self::CustomType(..) => &[Self::Pointer], _ => &[], } } pub fn is_number(&self) -> bool { - !matches!(self, Self::Void | Self::Pointer | Self::Buffer) + !matches!( + self, + Self::Void | Self::Pointer | Self::Buffer | Self::CustomType(_) + ) } - pub fn apply_transform( + pub fn apply_arg_transform( &self, name: &mut Box, args: &[Ident], @@ -55,6 +60,12 @@ impl Type { }; }) } + Self::CustomType(_) => { + let pointer = &args[0]; + Some(quote! { + let #name = unsafe { &mut *(#pointer as *mut _) }; + }) + } Self::Pointer => { let pointer = &args[0]; Some(quote! { @@ -65,7 +76,23 @@ impl Type { } } - pub fn to_ident(&self) -> syn::Type { + pub fn apply_ret_transform( + &self, + name: &mut Box, + arg: Ident, + ) -> Option { + match self { + Self::Pointer => Some(quote! { + let #name = #arg as _; + }), + Self::CustomType(_) => Some(quote! { + let #name = Box::into_raw(Box::new(#arg)) as *mut _; + }), + _ => None, + } + } + + pub fn to_ident(&self) -> syn::Expr { match self { Self::Void => parse_quote!(deno_bindgen::Type::Void), Self::Uint8 => parse_quote!(deno_bindgen::Type::Uint8), @@ -80,6 +107,7 @@ impl Type { Self::Float64 => parse_quote!(deno_bindgen::Type::Float64), Self::Pointer => parse_quote!(deno_bindgen::Type::Pointer), Self::Buffer => parse_quote!(deno_bindgen::Type::Buffer), + Self::CustomType(s) => parse_quote!(deno_bindgen::Type::CustomType(#s)), } } } @@ -98,7 +126,7 @@ impl ToTokens for Type { Self::Int64 => quote! { i64 }, Self::Float32 => quote! { f32 }, Self::Float64 => quote! { f64 }, - Self::Pointer => quote! { *const () }, + Self::CustomType(_) | Self::Pointer => quote! { *const () }, Self::Buffer => quote! { *mut u8 }, }; @@ -113,6 +141,7 @@ pub struct Symbol { pub return_type: Type, pub non_blocking: bool, pub internal: bool, + pub is_constructor: bool, } pub struct SymbolBuilder { @@ -121,6 +150,7 @@ pub struct SymbolBuilder { return_type: Type, non_blocking: bool, internal: bool, + is_constructor: bool, } impl SymbolBuilder { @@ -131,9 +161,14 @@ impl SymbolBuilder { return_type: Default::default(), non_blocking: false, internal: false, + is_constructor: false, } } + pub fn set_name(&mut self, name: Ident) { + self.name = name; + } + pub fn push(&mut self, ty: Type) { self.parameters.push(ty); } @@ -149,6 +184,10 @@ impl SymbolBuilder { pub fn internal(&mut self, internal: bool) { self.internal = internal; } + + pub fn is_constructor(&mut self, is_constructor: bool) { + self.is_constructor = is_constructor; + } } impl ToTokens for SymbolBuilder { @@ -162,6 +201,7 @@ impl ToTokens for SymbolBuilder { let non_blocking = &self.non_blocking; let name = &self.name; let internal = &self.internal; + let is_constructor = &self.is_constructor; tokens.extend(quote! { deno_bindgen::Symbol { @@ -170,6 +210,7 @@ impl ToTokens for SymbolBuilder { return_type: #return_type, non_blocking: #non_blocking, internal: #internal, + is_constructor: #is_constructor, } }); } diff --git a/deno_bindgen_macro/src/fn_.rs b/deno_bindgen_macro/src/fn_.rs index 3e1d850..d019db4 100644 --- a/deno_bindgen_macro/src/fn_.rs +++ b/deno_bindgen_macro/src/fn_.rs @@ -30,7 +30,12 @@ fn parse_type(ty: &Box) -> Result { "f64" => return Ok(Type::Float64), "usize" => return Ok(Type::Uint64), "isize" => return Ok(Type::Int64), - _ => return Err(Error::UnsupportedType), + ty_str => { + return Ok(Type::CustomType( + // yeah, don't worry about it. + Box::leak(ty_str.to_string().into_boxed_str()), + )) + } } } @@ -78,6 +83,7 @@ pub fn handle_inner( let mut symbol = SymbolBuilder::new(fn_.sig.ident.clone()); symbol.non_blocking(attrs.non_blocking); symbol.internal(attrs.internal); + symbol.is_constructor(attrs.constructor); // Cannot use enumerate here, there can be multiple raw args per type. let mut i = 0; @@ -109,7 +115,7 @@ pub fn handle_inner( } // Apply the transform. - if let Some(transform) = ty.apply_transform(pat, &idents) { + if let Some(transform) = ty.apply_arg_transform(pat, &idents) { transforms.push(transform); } @@ -136,7 +142,8 @@ pub fn handle_inner( ReturnType::Type(_, ref mut ty) => { let t = parse_type(ty)?; - if let Some(transform) = t.apply_transform(&mut ret, &[ret_ident.clone()]) + if let Some(transform) = + t.apply_ret_transform(&mut ret, ret_ident.clone()) { ret_transform = transform; } diff --git a/deno_bindgen_macro/src/impl_.rs b/deno_bindgen_macro/src/impl_.rs index 27e6f6d..1d58efb 100644 --- a/deno_bindgen_macro/src/impl_.rs +++ b/deno_bindgen_macro/src/impl_.rs @@ -1,9 +1,10 @@ use proc_macro2::TokenStream as TokenStream2; -use syn::{parse_quote, ImplItemFn, ItemImpl, parse, punctuated::Punctuated}; +use quote::format_ident; +use syn::{parse, parse_quote, punctuated::Punctuated, ImplItemFn, ItemImpl}; use crate::util::{self, Result}; -pub fn handle(impl_: ItemImpl) -> Result { +pub fn handle(mut impl_: ItemImpl) -> Result { if impl_.generics.params.first().is_some() { return Err(util::Error::Generics); } @@ -21,37 +22,79 @@ pub fn handle(impl_: ItemImpl) -> Result { let mut methods = Vec::new(); let mut syms = Punctuated::::new(); - for item in impl_.items.iter() { + for item in impl_.items.iter_mut() { match item { - syn::ImplItem::Fn(ImplItemFn { sig, .. }) => { - if sig.receiver().is_some() { - let ref method_name = sig.ident; - let ref out = sig.output; - let inputs = sig.inputs.iter().skip(1).collect::>(); - let idents = inputs - .iter() + syn::ImplItem::Fn(ImplItemFn { sig, attrs, .. }) => { + let mut is_constructor = false; + if let Some(attr) = attrs.first() { + let path = attr.path(); + is_constructor = path.is_ident("constructor"); + + attrs.clear(); + } + + // TODO: Add common name magling util. + let method_name = sig.ident.clone(); + let mangled_name = format_ident!("__{}_{}", ty_str, method_name); + // ... + let ref out = sig.output; + let inputs = sig.inputs.iter(); + + fn idents_with_skip<'a>( + arg: syn::punctuated::Iter<'a, syn::FnArg>, + skip: usize, + ) -> Vec<&'a syn::Ident> { + arg + .skip(skip) .map(|arg| match arg { syn::FnArg::Receiver(_) => unreachable!(), syn::FnArg::Typed(pat_type) => match &*pat_type.pat { - syn::Pat::Ident(ident) => ident.ident.clone(), + syn::Pat::Ident(ident) => &ident.ident, _ => unreachable!(), }, }) - .collect::>(); - let method = parse_quote! { - fn #method_name (self_: *mut #ty_str, #(#inputs),*) #out { + .collect::>() + } + + let method = if sig.receiver().is_some() { + let idents = idents_with_skip(inputs.clone(), 1); + // First argument is the receiver, we skip it. + let inputs = inputs.skip(1); + + parse_quote! { + #[allow(non_snake_case)] + fn #mangled_name (self_: *mut #ty_str, #(#inputs),*) #out { let self_ = unsafe { &mut *self_ }; self_. #method_name (#(#idents),*) } - }; - - let (generated, sym) = crate::fn_::handle_inner(method, crate::FnAttributes { + } + } else if is_constructor { + let idents = idents_with_skip(inputs.clone(), 1); + parse_quote!( + #[allow(non_snake_case)] + fn #mangled_name (#(#inputs),*) #out { + #ty_str:: #method_name (#(#idents),*) + } + ) + } else { + return Err(util::Error::MissingReceiver); + }; + + let (generated, mut sym) = crate::fn_::handle_inner( + method, + crate::FnAttributes { internal: true, + constructor: is_constructor, ..Default::default() - })?; - methods.push(generated); - syms.push(quote::quote! { #sym }); - } + }, + )?; + + // Set method name to the original name as the + // managed name is used for the internal symbol. + sym.set_name(method_name); + + methods.push(generated); + syms.push(quote::quote! { #sym }); } _ => {} } diff --git a/deno_bindgen_macro/src/lib.rs b/deno_bindgen_macro/src/lib.rs index 9f4a39b..800d6dd 100644 --- a/deno_bindgen_macro/src/lib.rs +++ b/deno_bindgen_macro/src/lib.rs @@ -15,6 +15,8 @@ mod util; #[derive(Default)] pub(crate) struct FnAttributes { pub(crate) non_blocking: bool, + pub(crate) constructor: bool, + pub(crate) internal: bool, } diff --git a/deno_bindgen_macro/src/util.rs b/deno_bindgen_macro/src/util.rs index 4ed673e..811047f 100644 --- a/deno_bindgen_macro/src/util.rs +++ b/deno_bindgen_macro/src/util.rs @@ -5,6 +5,7 @@ pub enum Error { UnsupportedType, Generics, WhereClause, + MissingReceiver, } impl std::fmt::Display for Error { @@ -15,6 +16,7 @@ impl std::fmt::Display for Error { Error::UnsupportedType => write!(f, "unsupported type"), Error::Generics => write!(f, "generics are not supported"), Error::WhereClause => write!(f, "where clauses are not supported"), + Error::MissingReceiver => write!(f, "missing receiver"), } } } diff --git a/example/bindings/bindings.ts b/example/bindings/bindings.ts index 8a39b96..d09c84d 100644 --- a/example/bindings/bindings.ts +++ b/example/bindings/bindings.ts @@ -55,14 +55,24 @@ const { symbols } = dlopen('./target/debug/libdeno_bindgen_test.dylib', { result: 'i32', nonblocking: true }, - foo: { + make_foo: { + parameters: [], + result: 'pointer', + nonblocking: false + }, + __Foo_new: { + parameters: [], + result: 'pointer', + nonblocking: false + }, + __Foo_foo: { parameters: [ 'pointer', ], result: 'void', nonblocking: false }, - bar: { + __Foo_bar: { parameters: [ 'pointer', 'u32', @@ -126,19 +136,29 @@ export function non_blocking(): Promise { return symbols.non_blocking() } -function foo( +export function make_foo(): Foo { + const ret = symbols.make_foo() + return Foo.__constructor(ret); +} + +function __Foo_new(): Foo { + const ret = symbols.__Foo_new() + return Foo.__constructor(ret); +} + +function __Foo_foo( arg0: Deno.PointerObject | null, ): void { - return symbols.foo( + return symbols.__Foo_foo( arg0, ) } -function bar( +function __Foo_bar( arg0: Deno.PointerObject | null, arg1: number, ): number { - return symbols.bar( + return symbols.__Foo_bar( arg0, arg1, ) @@ -147,20 +167,24 @@ function bar( export class Foo { ptr: Deno.PointerObject | null = null; - static __constructor(ptr: Deno.PointerObject) { + static __constructor(ptr: Deno.PointerObject | null) { const self = Object.create(Foo.prototype); self.ptr = ptr; return self; } + constructor() { + return __Foo_new() + } + foo(): void { - return foo( + return __Foo_foo( this.ptr, ) } bar(arg0: number): number { - return bar( + return __Foo_bar( this.ptr, arg0, ) diff --git a/example/bindings_test.ts b/example/bindings_test.ts index 92107ab..e6cd06b 100644 --- a/example/bindings_test.ts +++ b/example/bindings_test.ts @@ -6,6 +6,8 @@ import { cstr, strlen, non_blocking, + make_foo, + Foo, } from "./bindings/bindings.ts"; import { assert, assertEquals } from "https://deno.land/std/testing/asserts.ts"; @@ -58,5 +60,19 @@ Deno.test({ }, }); -// struct (glorified pointers) -// impl on struct \ No newline at end of file +Deno.test({ + name: "make_foo#test", + fn: () => { + const foo = make_foo(); + assert(foo instanceof Foo); + assertEquals(foo.bar(1), 43); + }, +}) + +Deno.test({ + name: "Foo#constructor", + fn() { + const foo = new Foo(); + assertEquals(foo.bar(1), 43); + } +}) \ No newline at end of file diff --git a/example/src/lib.rs b/example/src/lib.rs index 0b09951..1f83345 100644 --- a/example/src/lib.rs +++ b/example/src/lib.rs @@ -42,6 +42,11 @@ fn non_blocking() -> i32 { 42 } +#[deno_bindgen] +fn make_foo() -> Foo { + Foo { internal: 42 } +} + #[deno_bindgen] pub struct Foo { internal: u32, @@ -49,6 +54,11 @@ pub struct Foo { #[deno_bindgen] impl Foo { + #[constructor] + fn new() -> Foo { + Foo { internal: 42 } + } + fn foo(&self) {} fn bar(&self, a: u32) -> u32 {