Skip to content

Commit

Permalink
JS Backend: Struct Sharing to avoid generating (#620)
Browse files Browse the repository at this point in the history
* Remove &mut functions to avoid borrowing issues

* Detaching methods from typescript/js file loop

* Adjust method typescript generation in loop

* Accounting for special method presence

* Generation + adjusting whitespace

* Fields can be generated separately

* Formatting, removing unused variables

* Combine methods into one variable to clean up method calls

* Removing extra variable to clean up gen_struct signature
  • Loading branch information
ambiguousname authored Aug 8, 2024
1 parent 690031e commit ccb95d4
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 163 deletions.
1 change: 1 addition & 0 deletions feature_tests/js/api/MyStruct.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions feature_tests/js/api/ResultOpaque.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 56 additions & 20 deletions tool/src/js/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::{borrow::Cow, cell::RefCell};

use crate::{ErrorStore, FileMap};
use diplomat_core::hir::{BackendAttrSupport, DocsUrlGenerator, TypeContext, TypeDef};
Expand All @@ -10,7 +10,7 @@ pub(crate) mod formatter;
use formatter::JSFormatter;

mod type_generation;
use type_generation::TyGenContext;
use type_generation::{MethodsInfo, TyGenContext};

mod layout;

Expand Down Expand Up @@ -88,34 +88,70 @@ pub(crate) fn run<'tcx>(

let name = formatter.fmt_type_name(id);

let context = TyGenContext {
tcx,
formatter: &formatter,
errors: &errors,
imports: RefCell::new(BTreeSet::new()),
};

let (m, special_method_presence, fields, fields_out) = match type_def {
TypeDef::Enum(e) => (&e.methods, &e.special_method_presence, None, None),
TypeDef::Opaque(o) => (&o.methods, &o.special_method_presence, None, None),
TypeDef::Struct(s) => (
&s.methods,
&s.special_method_presence,
Some(context.generate_fields(s)),
None,
),
TypeDef::OutStruct(s) => (
&s.methods,
&s.special_method_presence,
None,
Some(context.generate_fields(s)),
),
_ => unreachable!("HIR/AST variant {:?} is unknown.", type_def),
};

let mut methods_info = MethodsInfo {
methods: m
.iter()
.flat_map(|method| context.generate_method(id, method))
.collect::<Vec<_>>(),
special_methods: context.generate_special_method(special_method_presence),
};

for file_type in [FileType::Module, FileType::Typescript] {
let mut context = TyGenContext {
tcx,
formatter: &formatter,
errors: &errors,
typescript: file_type.is_typescript(),
imports: BTreeSet::new(),
};
let ts = file_type.is_typescript();

for m in &mut methods_info.methods {
m.typescript = ts;
}
methods_info.special_methods.typescript = ts;

// TODO: A lot of this could go faster if we cached info for typescript, instead of re-generating it.
let contents = match type_def {
TypeDef::Enum(e) => context.gen_enum(e, id, &name),
TypeDef::Opaque(o) => context.gen_opaque(o, id, &name),
TypeDef::Struct(s) => context.gen_struct(s, id, false, &name, true),
TypeDef::OutStruct(s) => context.gen_struct(s, id, true, &name, false),
TypeDef::Enum(e) => context.gen_enum(ts, &name, e, &methods_info),
TypeDef::Opaque(o) => context.gen_opaque(ts, &name, o, &methods_info),
TypeDef::Struct(s) => {
context.gen_struct(ts, &name, s, &fields.clone().unwrap(), &methods_info, false)
}
TypeDef::OutStruct(s) => context.gen_struct(
ts,
&name,
s,
&fields_out.clone().unwrap(),
&methods_info,
true,
),
_ => unreachable!("HIR/AST variant {:?} is unknown.", type_def),
};

let file_name = formatter.fmt_file_name(&name, &file_type);

// Remove our self reference:
context.imports.remove(&formatter.fmt_import_statement(
&name,
context.typescript,
"./".into(),
));
context.remove_import(name.clone().into());

files.add_file(file_name, context.generate_base(contents));
files.add_file(file_name, context.generate_base(ts, contents));
}

exports.push(
Expand Down
53 changes: 17 additions & 36 deletions tool/src/js/type_generation/converter.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::borrow::Cow;

use diplomat_core::hir::{
self, borrowing_param::StructBorrowInfo, LifetimeEnv, OpaqueOwner, PrimitiveType, ReturnType,
ReturnableStructDef, SelfType, StructPathLike, SuccessType, TyPosition, Type,
self, borrowing_param::StructBorrowInfo, LifetimeEnv, Method, OpaqueOwner, PrimitiveType,
ReturnType, ReturnableStructDef, SelfType, StructPathLike, SuccessType, TyPosition, Type,
};
use std::fmt::Write;

Expand Down Expand Up @@ -34,19 +34,15 @@ impl<'jsctx, 'tcx> TyGenContext<'jsctx, 'tcx> {
// #region C to JS
/// Given a type from Rust, convert it into something Typescript will understand.
/// We use this to double-check our Javascript work as well.
pub(super) fn gen_js_type_str<P: hir::TyPosition>(&mut self, ty: &Type<P>) -> Cow<'tcx, str> {
pub(super) fn gen_js_type_str<P: hir::TyPosition>(&self, ty: &Type<P>) -> Cow<'tcx, str> {
match *ty {
Type::Primitive(primitive) => self.formatter.fmt_primitive_as_ffi(primitive).into(),
Type::Opaque(ref op) => {
let opaque_id = op.tcx_id.into();
let type_name = self.formatter.fmt_type_name(opaque_id);

// Add to the import list:
self.imports.insert(self.formatter.fmt_import_statement(
&type_name,
self.typescript,
"./".into(),
));
self.add_import(type_name.clone().into());

if self.tcx.resolve_type(opaque_id).attrs().disable {
self.errors
Expand All @@ -64,11 +60,7 @@ impl<'jsctx, 'tcx> TyGenContext<'jsctx, 'tcx> {
let type_name = self.formatter.fmt_type_name(id);

// Add to the import list:
self.imports.insert(self.formatter.fmt_import_statement(
&type_name,
self.typescript,
"./".into(),
));
self.add_import(type_name.clone().into());

if self.tcx.resolve_type(id).attrs().disable {
self.errors
Expand All @@ -81,11 +73,7 @@ impl<'jsctx, 'tcx> TyGenContext<'jsctx, 'tcx> {
let type_name = self.formatter.fmt_type_name(enum_id);

// Add to the import list:
self.imports.insert(self.formatter.fmt_import_statement(
&type_name,
self.typescript,
"./".into(),
));
self.add_import(type_name.clone().into());

if self.tcx.resolve_type(enum_id).attrs().disable {
self.errors
Expand All @@ -102,7 +90,7 @@ impl<'jsctx, 'tcx> TyGenContext<'jsctx, 'tcx> {
}
}

pub(super) fn gen_success_ty(&mut self, out_ty: &SuccessType) -> Cow<'tcx, str> {
pub(super) fn gen_success_ty(&self, out_ty: &SuccessType) -> Cow<'tcx, str> {
match out_ty {
SuccessType::Write => self.formatter.fmt_string().into(),
SuccessType::OutType(o) => self.gen_js_type_str(o),
Expand Down Expand Up @@ -273,7 +261,7 @@ impl<'jsctx, 'tcx> TyGenContext<'jsctx, 'tcx> {
// #region Return Types

/// Give us a Typescript return type from [`ReturnType`]
pub(super) fn gen_js_return_type_str(&mut self, return_type: &ReturnType) -> Cow<'tcx, str> {
pub(super) fn gen_js_return_type_str(&self, return_type: &ReturnType) -> Cow<'tcx, str> {
match *return_type {
// -> () or a -> Result<(), Error>.
ReturnType::Infallible(SuccessType::Unit)
Expand Down Expand Up @@ -317,17 +305,17 @@ impl<'jsctx, 'tcx> TyGenContext<'jsctx, 'tcx> {
/// This basically handles the conversions from whatever the WASM gives us to a JS-friendly type.
/// We access [`super::MethodInfo`] to handle allocation and cleanup.
pub(super) fn gen_c_to_js_for_return_type(
&mut self,
&self,
method_info: &mut super::MethodInfo,
lifetime_environment: &LifetimeEnv,
method: &Method,
) -> Option<Cow<'tcx, str>> {
let return_type = &method_info.method.unwrap().output;
let return_type = &method.output;

// Conditions for allocating a diplomat buffer:
// 1. Function returns an Option<> or Result<>.
// 2. Infallible function returns a slice.
// 3. Infallible function returns a struct.
match *return_type {
match return_type {
// -> ()
ReturnType::Infallible(SuccessType::Unit) => None,

Expand Down Expand Up @@ -367,7 +355,7 @@ impl<'jsctx, 'tcx> TyGenContext<'jsctx, 'tcx> {
Some(
format!(
"return {};",
self.gen_c_to_js_for_type(o, result.into(), lifetime_environment)
self.gen_c_to_js_for_type(o, result.into(), &method.lifetime_env)
)
.into(),
)
Expand Down Expand Up @@ -438,15 +426,8 @@ impl<'jsctx, 'tcx> TyGenContext<'jsctx, 'tcx> {
"if (!diplomatReceive.resultFlag) {{\n {};\n}}\n",
match return_type {
ReturnType::Fallible(_, Some(e)) => {
// Because we don't add Result<_, Error> types to imports, we do that here:
if !self.typescript {
let type_name = self.formatter.fmt_type_name(e.id().unwrap());
self.imports.insert(self.formatter.fmt_import_statement(
&type_name,
false,
"./".into(),
));
}
let type_name = self.formatter.fmt_type_name(e.id().unwrap());
self.add_import(type_name.into());

let receive_deref = self.gen_c_to_js_deref_for_type(
e,
Expand All @@ -455,7 +436,7 @@ impl<'jsctx, 'tcx> TyGenContext<'jsctx, 'tcx> {
);
let type_name = self.formatter.fmt_type_name(e.id().unwrap());
let cause =
self.gen_c_to_js_for_type(e, receive_deref, lifetime_environment);
self.gen_c_to_js_for_type(e, receive_deref, &method.lifetime_env);
format!(
"const cause = {cause};\n throw new Error({message}, {{ cause }})",
message = match e {
Expand Down Expand Up @@ -506,7 +487,7 @@ impl<'jsctx, 'tcx> TyGenContext<'jsctx, 'tcx> {
);
format!(
"{err_check}return {};",
self.gen_c_to_js_for_type(o, ptr_deref, lifetime_environment)
self.gen_c_to_js_for_type(o, ptr_deref, &method.lifetime_env)
)
}
_ => unreachable!("AST/HIR variant {:?} unknown.", return_type),
Expand Down
Loading

0 comments on commit ccb95d4

Please sign in to comment.