From 3bf1e7094c1efa47aec1715899ec0b3e21b3862a Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Thu, 23 Jan 2025 20:17:48 +0100 Subject: [PATCH 01/36] Scala.js binding generator, work in progress --- Cargo.lock | 12 + Cargo.toml | 4 + README.md | 2 +- crates/scalajs/Cargo.toml | 21 + crates/scalajs/scala/wit.scala | 90 ++++ crates/scalajs/src/lib.rs | 842 +++++++++++++++++++++++++++++++++ src/bin/wit-bindgen.rs | 10 + 7 files changed, 980 insertions(+), 1 deletion(-) create mode 100644 crates/scalajs/Cargo.toml create mode 100644 crates/scalajs/scala/wit.scala create mode 100644 crates/scalajs/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f9f242910..bbb1afea3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2405,6 +2405,7 @@ dependencies = [ "wit-bindgen-markdown", "wit-bindgen-moonbit", "wit-bindgen-rust", + "wit-bindgen-scalajs", "wit-bindgen-teavm-java", "wit-component", "wit-parser 0.223.0", @@ -2513,6 +2514,17 @@ dependencies = [ "wit-bindgen-rust", ] +[[package]] +name = "wit-bindgen-scalajs" +version = "0.37.0" +dependencies = [ + "anyhow", + "clap", + "heck 0.5.0", + "test-helpers", + "wit-bindgen-core", +] + [[package]] name = "wit-bindgen-teavm-java" version = "0.37.0" diff --git a/Cargo.toml b/Cargo.toml index cd2360df4..6ea745413 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ wit-bindgen-go = { path = 'crates/go', version = '0.37.0' } wit-bindgen-csharp = { path = 'crates/csharp', version = '0.37.0' } wit-bindgen-markdown = { path = 'crates/markdown', version = '0.37.0' } wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.37.0' } +wit-bindgen-scalajs = { path = 'crates/scalajs', version = '0.37.0' } wit-bindgen = { path = 'crates/guest-rust', version = '0.37.0', default-features = false } [[bin]] @@ -63,6 +64,7 @@ wit-bindgen-moonbit = { workspace = true, features = ['clap'], optional = true } wit-bindgen-teavm-java = { workspace = true, features = ['clap'], optional = true } wit-bindgen-go = { workspace = true, features = ['clap'], optional = true } wit-bindgen-csharp = { workspace = true, features = ['clap'], optional = true } +wit-bindgen-scalajs = { workspace = true, features = ['clap'], optional = true } wit-component = { workspace = true } wasm-encoder = { workspace = true } @@ -75,6 +77,7 @@ default = [ 'go', 'csharp', 'moonbit', + 'scalajs', 'async', ] c = ['dep:wit-bindgen-c'] @@ -85,6 +88,7 @@ go = ['dep:wit-bindgen-go'] csharp = ['dep:wit-bindgen-csharp'] csharp-mono = ['csharp'] moonbit = ['dep:wit-bindgen-moonbit'] +scalajs = ['dep:wit-bindgen-scalajs'] async = [] [dev-dependencies] diff --git a/README.md b/README.md index 8f003ef11..d82af1a05 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ WIT files are currently added to a `wit/` folder adjacent to your `Cargo.toml` file. Example code using this then looks like: ```rust -// src/lib.rs +// src/lib.rs.rs // Use a procedural macro to generate bindings for the world we specified in // `host.wit` diff --git a/crates/scalajs/Cargo.toml b/crates/scalajs/Cargo.toml new file mode 100644 index 000000000..d08c3ad1d --- /dev/null +++ b/crates/scalajs/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "wit-bindgen-scalajs" +authors = ["Daniel Vigovszky "] +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +license = { workspace = true } +homepage = 'https://github.com/bytecodealliance/wit-bindgen' +description = """ +Scala.JS bindings generator for WIT and the component model, typically used +through the `wit-bindgen-cli` crate. +""" + +[dependencies] +anyhow = { workspace = true } +wit-bindgen-core = { workspace = true } +heck = { workspace = true } +clap = { workspace = true, optional = true } + +[dev-dependencies] +test-helpers = { path = '../test-helpers' } diff --git a/crates/scalajs/scala/wit.scala b/crates/scalajs/scala/wit.scala new file mode 100644 index 000000000..3c315c421 --- /dev/null +++ b/crates/scalajs/scala/wit.scala @@ -0,0 +1,90 @@ +package object wit { + + import scala.scalajs.js + import scala.scalajs.js.JSConverters._ + import scala.scalajs.js.| + + sealed trait Nullable[+A] extends js.Any + + object Nullable { + def some[A](value: A): Nullable[A] = value.asInstanceOf[Nullable[A]] + + val none: Nullable[Nothing] = null.asInstanceOf[Nullable[Nothing]] + + def fromOption[A](option: Option[A]): Nullable[A] = + option match { + case Some(value) => some(value) + case None => none + } + } + + implicit class NullableOps[A](private val self: Nullable[A]) extends AnyVal { + def toOption: Option[A] = Option(self.asInstanceOf[A]) + } + + sealed trait WitOption[A] extends js.Object { + val tag: String + val `val`: js.UndefOr[A] + } + + object WitOption { + def some[A](value: A): WitOption[A] = new WitOption[A] { + val tag: String = "some" + val `val`: js.UndefOr[A] = value + } + + def none[A]: WitOption[A] = new WitOption[A] { + val tag: String = "none" + val `val`: js.UndefOr[A] = js.undefined + } + + def fromOption[A](option: Option[A]): WitOption[A] = + option match { + case Some(value) => some(value) + case None => none + } + } + + implicit class WitOptionOps[A](private val self: WitOption[A]) extends AnyVal { + def toOption: Option[A] = self.tag match { + case "some" => Some(self.`val`.get) + case _ => None + } + } + + sealed trait WitResult[Ok, Err] extends js.Object { + val tag: String + val `val`: js.UndefOr[Ok | Err] + } + + object WitResult { + def ok[Ok, Err](value: Ok): WitResult[Ok, Err] = new WitResult[Ok, Err] { + val tag: String = "ok" + val `val`: js.UndefOr[Ok | Err] = value + } + + def err[Ok, Err](value: Err): WitResult[Ok, Err] = new WitResult[Ok, Err] { + val tag: String = "err" + val `val`: js.UndefOr[Ok | Err] = value + } + + def fromEither[E, A](either: Either[E, A]): WitResult[A, E] = + either match { + case Right(value) => ok(value) + case Left(value) => err(value) + } + } + + implicit class WitResultOps[Ok, Err](private val self: WitResult[Ok, Err]) extends AnyVal { + def toEither: Either[Err, Ok] = self.tag match { + case "ok" => Right(self.`val`.get.asInstanceOf[Ok]) + case _ => Left(self.`val`.get.asInstanceOf[Err]) + } + } + + type WitList[A] = js.Array[A] + + object WitList { + def fromList[A](list: List[A]): WitList[A] = list.toJSArray + } +} \ No newline at end of file diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs new file mode 100644 index 000000000..f82e00be7 --- /dev/null +++ b/crates/scalajs/src/lib.rs @@ -0,0 +1,842 @@ +use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; +use std::collections::HashMap; +use std::fmt::{Display, Write}; +use std::str::FromStr; +use wit_bindgen_core::wit_parser::{ + Docs, Function, FunctionKind, Handle, Interface, InterfaceId, PackageName, Resolve, Results, + Type, TypeDef, TypeDefKind, TypeId, TypeOwner, WorldId, WorldKey, +}; +use wit_bindgen_core::{uwrite, uwriteln, Direction, Files, WorldGenerator}; +use wit_bindgen_core::Direction::{Export, Import}; + +#[derive(Debug, Clone)] +pub enum ScalaDialect { + Scala2, + Scala3, +} + +impl Default for ScalaDialect { + fn default() -> Self { + ScalaDialect::Scala2 + } +} + +impl Display for ScalaDialect { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ScalaDialect::Scala2 => write!(f, "scala2"), + ScalaDialect::Scala3 => write!(f, "scala3"), + } + } +} + +impl FromStr for ScalaDialect { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "scala2" => Ok(ScalaDialect::Scala2), + "scala3" => Ok(ScalaDialect::Scala3), + _ => Err("Invalid Scala dialect".to_string()), + } + } +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "clap", derive(clap::Args))] +pub struct Opts { + #[cfg_attr( + feature = "clap", + clap(long, help = "Base package for generated Scala.js code") + )] + base_package: Option, + + #[cfg_attr( + feature = "clap", + clap( + long, + help = "Scala dialect to generate code for", + default_value = "scala2" + ) + )] + scala_dialect: ScalaDialect, +} + +impl Opts { + pub fn build(&self) -> Box { + Box::new(ScalaJsWorld { + opts: self.clone(), + generated_files: HashMap::new(), + }) + } + + pub fn base_package_segments(&self) -> Vec { + self.base_package + .clone() + .map(|pkg| pkg.split('.').map(|s| s.to_string()).collect::>()) + .unwrap_or_default() + } + + pub fn base_package_prefix(&self) -> String { + match &self.base_package { + Some(pkg) => format!("{pkg}."), + None => "".to_string(), + } + } +} + +struct ScalaJsWorld { + opts: Opts, + generated_files: HashMap, +} + +impl WorldGenerator for ScalaJsWorld { + fn import_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, + iface: InterfaceId, + _files: &mut Files, + ) -> anyhow::Result<()> { + let key = name; + let wit_name = resolve.name_world_key(key); + + println!("import_interface: {wit_name}"); + + let mut scalajs_iface = ScalaJsInterface::new(wit_name.clone(), resolve, iface, &self.opts, Import); + scalajs_iface.generate(); + self.generated_files + .insert(wit_name, scalajs_iface.finalize()); + + Ok(()) + } + + fn export_interface( + &mut self, + resolve: &Resolve, + name: &WorldKey, + iface: InterfaceId, + _files: &mut Files, + ) -> anyhow::Result<()> { + let key = name; + let wit_name = resolve.name_world_key(key); + + println!("export_interface: {:?}", name); + + let mut scalajs_iface = ScalaJsInterface::new(wit_name.clone(), resolve, iface, &self.opts, Export); + scalajs_iface.generate(); + self.generated_files + .insert(wit_name, scalajs_iface.finalize()); + + Ok(()) + } + + fn import_funcs( + &mut self, + _resolve: &Resolve, + _world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) { + // TODO + println!("import_funcs: {:?}", funcs); + } + + fn export_funcs( + &mut self, + _resolve: &Resolve, + _world: WorldId, + funcs: &[(&str, &Function)], + _files: &mut Files, + ) -> anyhow::Result<()> { + // TODO + println!("export_funcs: {:?}", funcs); + Ok(()) + } + + fn import_types( + &mut self, + _resolve: &Resolve, + _world: WorldId, + types: &[(&str, TypeId)], + _files: &mut Files, + ) { + // TODO + println!("import_types: {:?}", types); + } + + fn finish( + &mut self, + _resolve: &Resolve, + _world: WorldId, + files: &mut Files, + ) -> anyhow::Result<()> { + for (name, iface) in &self.generated_files { + println!("--- interface: {:?}", name); + println!("{}", iface.source); + + files.push(&iface.path(), iface.source.as_bytes()); + } + + let rt = render_runtime_module(&self.opts); + files.push(&rt.path(), rt.source.as_bytes()); + + Ok(()) + } +} + +struct ScalaJsFile { + package: Vec, + name: String, + source: String, +} + +impl ScalaJsFile { + fn path(&self) -> String { + format!("{}/{}.scala", self.package.join("/"), self.name) + } +} + +struct ScalaJsInterface<'a> { + wit_name: String, + name: String, + source: String, + package: Vec, + opts: &'a Opts, + resolve: &'a Resolve, + interface: &'a Interface, + interface_id: InterfaceId, + direction: Direction +} + +impl<'a> ScalaJsInterface<'a> { + pub fn new( + wit_name: String, + resolve: &'a Resolve, + interface_id: InterfaceId, + opts: &'a Opts, + direction: Direction + ) -> Self { + let interface = &resolve.interfaces[interface_id]; + let name = interface + .name + .clone() + .expect("Anonymous interfaces not supported yet") + .to_pascal_case(); + + let package_name = resolve.packages + [interface.package.expect("missing package for interface")] + .name + .clone(); + + let mut package = package_name_to_segments(&opts, &package_name); + + Self { + wit_name, + name, + source: "".to_string(), + package, + opts, + resolve, + interface, + interface_id, + direction + } + } + + pub fn generate(&mut self) { + let mut source = String::new(); + uwriteln!(source, "package {}", self.package.join(".")); + uwriteln!(source, ""); + uwriteln!(source, "import scala.scalajs.js"); + uwriteln!(source, "import scala.scalajs.js.annotation._"); + uwriteln!(source, "import {}wit._", self.opts.base_package_prefix()); + uwriteln!(source, ""); + + uwriteln!(source, "package object {} {{", self.name.to_snake_case()); + + let mut types = Vec::new(); + let mut functions = Vec::new(); + let mut resources = HashMap::new(); + + for (type_name, type_id) in &self.interface.types { + let type_def = &self.resolve.types[*type_id]; + if let Some(typ) = self.render_typedef(type_name, type_def) { + types.push(typ); + } + } + + for (func_name, func) in &self.interface.functions { + let scala_func_name = func_name.to_lower_camel_case(); + + match func.kind { + FunctionKind::Freestanding => { + let args = self.render_args(func.params.iter()); + + let ret = match &func.results { + Results::Named(params) if params.len() == 0 => "Unit".to_string(), + Results::Named(_) => panic!("Named results not supported yet"), // TODO + Results::Anon(typ) => self.render_type_reference(typ), + }; + + let mut function = String::new(); + write_doc_comment(&mut function, " ", &func.docs); + + let postfix = match self.direction { + Import => " = js.native", + Export => "", + }; + + uwriteln!(function, " def {scala_func_name}({args}): {ret}{postfix}"); + functions.push(function); + } + FunctionKind::Method(resource_type) + | FunctionKind::Static(resource_type) + | FunctionKind::Constructor(resource_type) => { + let resource = resources + .entry(resource_type) + .or_insert_with(|| ScalaJsResource::new(self, resource_type)); + resource.add_function(func_name, func); + } + } + } + + for typ in types { + uwriteln!(source, "{}", typ); + uwriteln!(source, ""); + } + + write_doc_comment(&mut source, " ", &self.interface.docs); + if self.direction == Import { + uwriteln!(source, " @js.native"); + uwriteln!(source, " trait {} extends js.Object {{", self.name); + } else { + uwriteln!(source, " trait {} {{", self.name); + } + for (_, resource) in resources { + uwriteln!(source, "{}", resource.finalize()); + uwriteln!(source, ""); + } + + for function in functions { + uwriteln!(source, "{function}"); + } + + uwriteln!(source, " }}"); + + if self.direction == Import { + uwriteln!(source, ""); + uwriteln!(source, " @js.native"); + uwriteln!( + source, + " @JSImport(\"{}\", JSImport.Namespace)", + self.wit_name + ); + uwriteln!(source, " object {} extends {}", self.name, self.name); + } + uwriteln!(source, "}}"); + + self.source = source; + } + + pub fn finalize(self) -> ScalaJsFile { + ScalaJsFile { + package: self.package, + name: self.name, + source: self.source, + } + } + + + fn render_args<'b>(&self, params: impl Iterator) -> String { + let mut args = Vec::new(); + for (param_name, param_typ) in params { + let param_typ = self.render_type_reference(param_typ); + let param_name = param_name.to_lower_camel_case(); + args.push(format!("{param_name}: {param_typ}")); + } + args.join(", ") + } + + fn render_return_type(&self, results: &Results) -> String { + match results { + Results::Named(params) if params.len() == 0 => "Unit".to_string(), + Results::Named(_) => panic!("Named results not supported yet"), // TODO + Results::Anon(typ) => self.render_type_reference(typ), + } + } + + fn render_type_reference(&self, typ: &Type) -> String { + match typ { + Type::Bool => "Boolean".to_string(), + Type::U8 => "Byte".to_string(), + Type::U16 => "Short".to_string(), + Type::U32 => "Int".to_string(), + Type::U64 => "Long".to_string(), + Type::S8 => "Byte".to_string(), + Type::S16 => "Short".to_string(), + Type::S32 => "Int".to_string(), + Type::S64 => "Long".to_string(), + Type::F32 => "Float".to_string(), + Type::F64 => "Double".to_string(), + Type::Char => "Char".to_string(), + Type::String => "String".to_string(), + Type::Id(id) => { + let typ = &self.resolve.types[*id]; + self.render_typedef_reference(typ) + } + } + } + + fn render_typedef_reference(&self, typ: &TypeDef) -> String { + match &typ.kind { + TypeDefKind::Record(_) + | TypeDefKind::Resource + | TypeDefKind::Flags(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Type(_) + | TypeDefKind::Variant(_) => { + let prefix = match self.render_owner(&typ.owner) { + Some(owner) => format!("{owner}."), + None => "".to_string(), + }; + format!( + "{}{}", + prefix, + typ.name + .clone() + .expect("Anonymous types are not supported") + .to_pascal_case() + ) + } + TypeDefKind::Handle(handle) => { + let id = match handle { + Handle::Own(id) => id, + Handle::Borrow(id) => id, + }; + let typ = &self.resolve.types[*id]; + self.render_typedef_reference(typ) + } + TypeDefKind::Tuple(tuple) => { + let mut parts = Vec::new(); + for part in &tuple.types { + parts.push(self.render_type_reference(part)); + } + format!("({})", parts.join(", ")) + } + TypeDefKind::Option(option) => { + if !maybe_null(&self.resolve, option) { + format!("Nullable[{}]", self.render_type_reference(option)) + } else { + format!("WitOption[{}]", self.render_type_reference(option)) + } + } + TypeDefKind::Result(result) => { + let ok = result + .ok + .map(|ok| self.render_type_reference(&ok)) + .unwrap_or("Unit".to_string()); + let err = result + .err + .map(|err| self.render_type_reference(&err)) + .unwrap_or("Unit".to_string()); + format!("WitResult[{ok}, {err}]") + } + TypeDefKind::List(list) => { + format!("WitList[{}]", self.render_type_reference(list)) + } + TypeDefKind::Future(_) => panic!("Futures not supported yet"), + TypeDefKind::Stream(_) => panic!("Streams not supported yet"), + TypeDefKind::ErrorContext => panic!("ErrorContext not supported yet"), + TypeDefKind::Unknown => panic!("Unknown type"), + } + } + + fn render_owner(&self, owner: &TypeOwner) -> Option { + match owner { + TypeOwner::World(id) => { + let world = &self.resolve.worlds[*id]; + // TODO: assuming an object or trait is also generated per world? + Some(world.name.clone().to_pascal_case()) + } + TypeOwner::Interface(id) if id == &self.interface_id => None, + TypeOwner::Interface(id) => { + let iface = &self.resolve.interfaces[*id]; + let name = iface.name.clone().expect("Interface must have a name"); + let package_id = iface.package.expect("Interface must have a package"); + + let package = &self.resolve.packages[package_id]; + let mut segments = package_name_to_segments(&self.opts, &package.name); + segments.push(name.to_snake_case()); + Some(segments.join(".")) + } + TypeOwner::None => None, + } + } + + fn render_typedef(&self, name: &str, typ: &TypeDef) -> Option { + let scala_name = name.to_pascal_case(); + + let mut source = String::new(); + match &typ.kind { + TypeDefKind::Record(record) => { + let mut fields = Vec::new(); + for field in &record.fields { + let typ = self.render_type_reference(&field.ty); + let field_name = field.name.to_lower_camel_case(); + fields.push((field_name, typ, &field.docs)); + } + + write_doc_comment(&mut source, " ", &typ.docs); + uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); + for (field_name, typ, docs) in &fields { + write_doc_comment(&mut source, " ", &docs); + uwriteln!(source, " val {field_name}: {typ}"); + } + uwriteln!(source, " }}"); + uwriteln!(source, ""); + uwriteln!(source, " case object {scala_name} {{"); + uwriteln!(source, " def apply("); + for (field_name, typ, _) in &fields { + uwriteln!(source, " {field_name}0: {typ},"); + } + uwriteln!(source, " ): {scala_name} = {{"); + uwriteln!(source, " new {scala_name} {{"); + for (field_name, typ, _) in &fields { + uwriteln!(source, " val {field_name}: {typ} = {field_name}0"); + } + uwriteln!(source, " }}"); + uwriteln!(source, " }}"); + uwriteln!(source, " }}"); + } + TypeDefKind::Resource => { + // resource wrappers are generated separately + } + TypeDefKind::Handle(_) => { + panic!("Unexpected top-level handle type"); + } + TypeDefKind::Flags(flags) => { + let mut fields = Vec::new(); + for flag in &flags.flags { + let typ = "Boolean".to_string(); + let field_name = flag.name.to_lower_camel_case(); + fields.push((field_name, typ, &flag.docs)); + } + + write_doc_comment(&mut source, " ", &typ.docs); + uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); + for (field_name, typ, docs) in &fields { + write_doc_comment(&mut source, " ", docs); + uwriteln!(source, " val {field_name}: {typ},"); + } + uwriteln!(source, " }}"); + uwriteln!(source, ""); + uwriteln!(source, " case object {scala_name} {{"); + uwriteln!(source, " def apply("); + for (field_name, typ, _) in &fields { + uwriteln!(source, " {field_name}0: {typ},"); + } + uwriteln!(source, " ): {scala_name} = {{"); + uwriteln!(source, " new {scala_name} {{"); + for (field_name, typ, _) in &fields { + uwriteln!(source, " val {field_name}: {typ} = {field_name}0"); + } + uwriteln!(source, " }}"); + uwriteln!(source, " }}"); + uwriteln!(source, " }}"); + } + TypeDefKind::Tuple(tuple) => { + let arity = tuple.types.len(); + write_doc_comment(&mut source, " ", &typ.docs); + uwrite!(source, " type {scala_name} = js.Tuple{arity}["); + for (idx, part) in tuple.types.iter().enumerate() { + let part = self.render_type_reference(part); + uwrite!(source, "{part}"); + if idx < tuple.types.len() - 1 { + uwrite!(source, ", "); + } + } + uwriteln!(source, "]"); + } + TypeDefKind::Variant(variant) => { + write_doc_comment(&mut source, " ", &typ.docs); + uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); + uwriteln!(source, " type Type"); + uwriteln!(source, " val tag: String"); + uwriteln!(source, " val `val`: js.UndefOr[Type]"); + uwriteln!(source, " }}"); + uwriteln!(source, ""); + uwriteln!(source, " object {scala_name} {{"); + for case in &variant.cases { + let case_name = &case.name; + match &case.ty { + Some(ty) => { + let typ = self.render_type_reference(ty); + write_doc_comment(&mut source, " ", &case.docs); + uwriteln!(source, " def {case_name}(value: {typ}): {scala_name} = new {scala_name} {{"); + uwriteln!(source, " type Type = {typ}"); + uwriteln!(source, " val tag: String = \"{case_name}\""); + uwriteln!(source, " val `val`: js.UndefOr[Type] = value"); + uwriteln!(source, " }}"); + } + None => { + write_doc_comment(&mut source, " ", &case.docs); + uwriteln!( + source, + " def {case_name}(): {scala_name} = new {scala_name} {{" + ); + uwriteln!(source, " type Type = ()"); + uwriteln!(source, " val tag: String = \"{case_name}\""); + uwriteln!(source, " val `val`: js.UndefOr[Type] = ()"); + uwriteln!(source, " }}"); + } + } + } + uwriteln!(source, " }}"); + } + TypeDefKind::Enum(enm) => { + write_doc_comment(&mut source, " ", &typ.docs); + uwriteln!(source, " sealed trait {scala_name} extends js.Object"); + uwriteln!(source, ""); + uwriteln!(source, " object {scala_name} {{"); + for case in &enm.cases { + let case_name = &case.name; + write_doc_comment(&mut source, " ", &case.docs); + uwriteln!( + source, + " def {case_name}: {scala_name} = \"{case_name}\".asInstanceOf[{scala_name}]", + ); + } + uwriteln!(source, " }}"); + } + TypeDefKind::Option(option) => { + write_doc_comment(&mut source, " ", &typ.docs); + let typ = self.render_type_reference(option); + if !maybe_null(&self.resolve, option) { + uwriteln!(source, " type {scala_name} = Nullable[{typ}]"); + } else { + uwriteln!(source, " type {scala_name} = WitOption[{typ}]"); + } + } + TypeDefKind::Result(result) => { + write_doc_comment(&mut source, " ", &typ.docs); + let ok = result + .ok + .map(|ok| self.render_type_reference(&ok)) + .unwrap_or("Unit".to_string()); + let err = result + .err + .map(|err| self.render_type_reference(&err)) + .unwrap_or("Unit".to_string()); + uwriteln!(source, " type {scala_name} = WitResult[{ok}, {err}]"); + } + TypeDefKind::List(list) => { + write_doc_comment(&mut source, " ", &typ.docs); + let typ = self.render_type_reference(list); + uwriteln!(source, " type {scala_name} = WitList[{typ}]"); + } + TypeDefKind::Future(_) => { + panic!("Futures are not supported yet"); + } + TypeDefKind::Stream(_) => { + panic!("Streams are not supported yet"); + } + TypeDefKind::ErrorContext => { + panic!("ErrorContext is not supported yet"); + } + TypeDefKind::Type(reftyp) => { + write_doc_comment(&mut source, " ", &typ.docs); + let typ = self.render_type_reference(reftyp); + uwriteln!(source, " type {scala_name} = {typ}"); + } + TypeDefKind::Unknown => { + panic!("Unknown type"); + } + } + + if source.len() > 0 { + Some(source) + } else { + None + } + } +} + +struct ScalaJsResource<'a> { + owner: &'a ScalaJsInterface<'a>, + resource_id: TypeId, + resource_name: String, + class_source: String, + object_source: String, +} + +impl<'a> ScalaJsResource<'a> { + pub fn new(owner: &'a ScalaJsInterface<'a>, resource_id: TypeId) -> Self { + let resource = &owner.resolve.types[resource_id]; + let resource_name = resource + .name + .clone() + .expect("Anonymous resources not supported"); + let scala_resource_name = resource_name.to_pascal_case(); + + let mut class_source = String::new(); + write_doc_comment(&mut class_source, " ", &resource.docs); + uwriteln!(class_source, " @js.native"); + uwriteln!( + class_source, + " class {} extends js.Object {{", + scala_resource_name + ); + + let mut object_source = String::new(); + uwriteln!(object_source, " @js.native"); + uwriteln!( + object_source, + " object {} extends js.Object {{", + scala_resource_name + ); + + Self { + owner, + resource_id, + resource_name, + class_source, + object_source, + } + } + + pub fn add_function(&mut self, func_name: &str, func: &Function) { + match &func.kind { + FunctionKind::Freestanding => unreachable!(), + FunctionKind::Method(_) => { + let args = self.owner.render_args(func.params.iter().skip(1)); + let ret = self.owner.render_return_type(&func.results); + + let scala_func_name = self.get_func_name("[method]", func_name); + write_doc_comment(&mut self.class_source, " ", &func.docs); + uwriteln!( + self.class_source, + " def {scala_func_name}({args}): {ret} = js.native" + ); + } + FunctionKind::Static(_) => { + let args = self.owner.render_args(func.params.iter()); + let ret = self.owner.render_return_type(&func.results); + + let scala_func_name = self.get_func_name("[static]", func_name); + write_doc_comment(&mut self.class_source, " ", &func.docs); + uwriteln!( + self.object_source, + " def {scala_func_name}({args}): {ret} = js.native" + ); + } + FunctionKind::Constructor(_) => { + let args = self.owner.render_args(func.params.iter()); + let ret = self.owner.render_return_type(&func.results); + + write_doc_comment(&mut self.class_source, " ", &func.docs); + uwriteln!( + self.class_source, + " def this({args}): {ret} = js.native" + ); + } + } + } + + pub fn finalize(self) -> String { + let mut class_source = self.class_source; + uwriteln!(class_source, " }}"); + let mut object_source = self.object_source; + uwriteln!(object_source, " }}"); + format!("{}\n{}\n", class_source, object_source) + } + + fn get_func_name(&self, prefix: &str, func_name: &str) -> String { + func_name + .strip_prefix(prefix) + .unwrap() + .strip_prefix(&self.resource_name) + .unwrap() + .to_lower_camel_case() + } +} + +fn render_runtime_module(opts: &Opts) -> ScalaJsFile { + let wit_scala = include_str!("../scala/wit.scala"); + + let mut package = opts.base_package_segments(); + package.push("wit".to_string()); + + let mut source = String::new(); + uwriteln!(source, "package {}", opts.base_package_segments().join(".")); + uwriteln!(source, ""); + uwriteln!(source, "{wit_scala}"); + + ScalaJsFile { + package, + name: "package".to_string(), + source + } +} + +fn package_name_to_segments(opts: &Opts, package_name: &PackageName) -> Vec { + let mut segments = opts.base_package_segments(); + segments.push(package_name.namespace.to_snake_case()); + segments.push(package_name.name.to_snake_case()); + if let Some(version) = &package_name.version { + segments.push(format!("v{}", version.to_string().to_snake_case())); + } + segments +} + +fn write_doc_comment(source: &mut impl Write, indent: &str, docs: &Docs) { + // TODO: rewrite types in `` blocks? + if !docs.is_empty() { + uwriteln!(source, "{}/**", indent); + for line in docs.contents.as_ref().unwrap().lines() { + uwriteln!(source, "{} * {}", indent, line); + } + uwriteln!(source, "{} */", indent); + } +} + +/// Tests whether `ty` can be represented with `null`, and if it can then +/// the "other type" is returned. If `Some` is returned that means that `ty` +/// is `null | `. If `None` is returned that means that `null` can't +/// be used to represent `ty`. +pub fn as_nullable<'a>(resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> { + let id = match ty { + Type::Id(id) => *id, + _ => return None, + }; + match &resolve.types[id].kind { + // If `ty` points to an `option`, then `ty` can be represented + // with `null` if `t` itself can't be represented with null. For + // example `option>` can't be represented with `null` + // since that's ambiguous if it's `none` or `some(none)`. + // + // Note, oddly enough, that `option>>` can be + // represented as `null` since: + // + // * `null` => `none` + // * `{ tag: "none" }` => `some(none)` + // * `{ tag: "some", val: null }` => `some(some(none))` + // * `{ tag: "some", val: 1 }` => `some(some(some(1)))` + // + // It's doubtful anyone would actually rely on that though due to + // how confusing it is. + TypeDefKind::Option(t) => { + if !maybe_null(resolve, t) { + Some(t) + } else { + None + } + } + TypeDefKind::Type(t) => as_nullable(resolve, t), + _ => None, + } +} + +pub fn maybe_null(resolve: &Resolve, ty: &Type) -> bool { + as_nullable(resolve, ty).is_some() +} diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index c6e0b589b..df59fa449 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -72,6 +72,14 @@ enum Opt { #[clap(flatten)] args: Common, }, + /// Generates bindings for Scala.JS guest modules. + #[cfg(feature = "scalajs")] + ScalaJS { + #[clap(flatten)] + opts: wit_bindgen_scalajs::Opts, + #[clap(flatten)] + args: Common, + }, } #[derive(Debug, Parser)] @@ -137,6 +145,8 @@ fn main() -> Result<()> { Opt::TinyGo { opts, args } => (opts.build(), args), #[cfg(feature = "csharp")] Opt::CSharp { opts, args } => (opts.build(), args), + #[cfg(feature = "scalajs")] + Opt::ScalaJS { opts, args } => (opts.build(), args), }; gen_world(generator, &opt, &mut files).map_err(attach_with_context)?; From 9d261a8be94595a2e1c5c54b3e260c435a1af44d Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Wed, 29 Jan 2025 21:09:25 +0100 Subject: [PATCH 02/36] Scala.js binding generator, work in progress --- Cargo.toml | 18 +- crates/scalajs/scala/build.properties | 1 + crates/scalajs/scala/build.sbt | 7 + crates/scalajs/scala/plugins.sbt | 1 + crates/scalajs/scala/wit.scala | 597 ++++++++++++++++++++++++++ crates/scalajs/src/lib.rs | 362 ++++++++++++---- crates/scalajs/tests/codegen.rs | 78 ++++ tests/runtime/main.rs | 90 ++++ 8 files changed, 1066 insertions(+), 88 deletions(-) create mode 100644 crates/scalajs/scala/build.properties create mode 100644 crates/scalajs/scala/build.sbt create mode 100644 crates/scalajs/scala/plugins.sbt create mode 100644 crates/scalajs/tests/codegen.rs diff --git a/Cargo.toml b/Cargo.toml index 6ea745413..12cafca97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ homepage = 'https://github.com/bytecodealliance/wit-bindgen' description = """ CLI tool to generate bindings for WIT documents and the component model. """ - [workspace] members = [ "crates/test-rust-wasm", @@ -51,7 +50,8 @@ wit-bindgen-scalajs = { path = 'crates/scalajs', version = '0.37.0' } wit-bindgen = { path = 'crates/guest-rust', version = '0.37.0', default-features = false } [[bin]] -name = "wit-bindgen" +name = "wit-bindgen-scalajs" +path = "src/bin/wit-bindgen.rs" [dependencies] anyhow = { workspace = true } @@ -70,13 +70,13 @@ wasm-encoder = { workspace = true } [features] default = [ - 'c', - 'rust', - 'markdown', - 'teavm-java', - 'go', - 'csharp', - 'moonbit', +# 'c', +# 'rust', +# 'markdown', +# 'teavm-java', +# 'go', +# 'csharp', +# 'moonbit', 'scalajs', 'async', ] diff --git a/crates/scalajs/scala/build.properties b/crates/scalajs/scala/build.properties new file mode 100644 index 000000000..fe69360b7 --- /dev/null +++ b/crates/scalajs/scala/build.properties @@ -0,0 +1 @@ +sbt.version = 1.10.7 diff --git a/crates/scalajs/scala/build.sbt b/crates/scalajs/scala/build.sbt new file mode 100644 index 000000000..43a03c607 --- /dev/null +++ b/crates/scalajs/scala/build.sbt @@ -0,0 +1,7 @@ +lazy val root = project + .in(file(".")) + .settings( + name := "wit-bindgen-scalajs-test", + scalaVersion := "2.13.16", + ) + .enablePlugins(ScalaJSPlugin) diff --git a/crates/scalajs/scala/plugins.sbt b/crates/scalajs/scala/plugins.sbt new file mode 100644 index 000000000..0242aded2 --- /dev/null +++ b/crates/scalajs/scala/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.18.2") \ No newline at end of file diff --git a/crates/scalajs/scala/wit.scala b/crates/scalajs/scala/wit.scala index 3c315c421..faf533396 100644 --- a/crates/scalajs/scala/wit.scala +++ b/crates/scalajs/scala/wit.scala @@ -3,6 +3,7 @@ package object wit { import scala.scalajs.js import scala.scalajs.js.JSConverters._ import scala.scalajs.js.| + import scala.scalajs.js.annotation.JSName sealed trait Nullable[+A] extends js.Any @@ -87,4 +88,600 @@ package object wit { object WitList { def fromList[A](list: List[A]): WitList[A] = list.toJSArray } + + sealed trait WitTuple0 extends js.Object { + } + + object WitTuple0 { + def apply(): WitTuple0 = js.Array().asInstanceOf[WitTuple0] + + def unapply(tuple: WitTuple0): Some[Unit] = Some(()) + + implicit def fromScalaTuple0(tuple: Unit): WitTuple0 = WitTuple0() + + implicit def toScalaTuple0(tuple: WitTuple0): Unit = () + } + + sealed trait WitTuple1[T1] extends js.Object { + @JSName("0") val _1: T1 + } + + object WitTuple1 { + def apply[T1](_1: T1): WitTuple1[T1] = js.Array(_1).asInstanceOf[WitTuple1[T1]] + + def unapply[T1](tuple: WitTuple1[T1]): Some[(T1)] = Some(tuple) + + implicit def fromScalaTuple1[T1](tuple: (T1)): WitTuple1[T1] = WitTuple1(tuple._1) + + implicit def toScalaTuple1[T1](tuple: WitTuple1[T1]): (T1) = (tuple._1) + } + + sealed trait WitTuple2[T1, T2] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + } + + object WitTuple2 { + def apply[T1, T2](_1: T1, _2: T2): WitTuple2[T1, T2] = js.Array(_1, _2).asInstanceOf[WitTuple2[T1, T2]] + + def unapply[T1, T2](tuple: WitTuple2[T1, T2]): Some[(T1, T2)] = Some(tuple) + + implicit def fromScalaTuple2[T1, T2](tuple: (T1, T2)): WitTuple2[T1, T2] = WitTuple2(tuple._1, tuple._2) + + implicit def toScalaTuple2[T1, T2](tuple: WitTuple2[T1, T2]): (T1, T2) = (tuple._1, tuple._2) + } + + sealed trait WitTuple3[T1, T2, T3] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + } + + object WitTuple3 { + def apply[T1, T2, T3](_1: T1, _2: T2, _3: T3): WitTuple3[T1, T2, T3] = js.Array(_1, _2, _3).asInstanceOf[WitTuple3[T1, T2, T3]] + + def unapply[T1, T2, T3](tuple: WitTuple3[T1, T2, T3]): Some[(T1, T2, T3)] = Some(tuple) + + implicit def fromScalaTuple3[T1, T2, T3](tuple: (T1, T2, T3)): WitTuple3[T1, T2, T3] = WitTuple3(tuple._1, tuple._2, tuple._3) + + implicit def toScalaTuple3[T1, T2, T3](tuple: WitTuple3[T1, T2, T3]): (T1, T2, T3) = (tuple._1, tuple._2, tuple._3) + } + + sealed trait WitTuple4[T1, T2, T3, T4] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + } + + object WitTuple4 { + def apply[T1, T2, T3, T4](_1: T1, _2: T2, _3: T3, _4: T4): WitTuple4[T1, T2, T3, T4] = js.Array(_1, _2, _3, _4).asInstanceOf[WitTuple4[T1, T2, T3, T4]] + + def unapply[T1, T2, T3, T4](tuple: WitTuple4[T1, T2, T3, T4]): Some[(T1, T2, T3, T4)] = Some(tuple) + + implicit def fromScalaTuple4[T1, T2, T3, T4](tuple: (T1, T2, T3, T4)): WitTuple4[T1, T2, T3, T4] = WitTuple4(tuple._1, tuple._2, tuple._3, tuple._4) + + implicit def toScalaTuple4[T1, T2, T3, T4](tuple: WitTuple4[T1, T2, T3, T4]): (T1, T2, T3, T4) = (tuple._1, tuple._2, tuple._3, tuple._4) + } + + sealed trait WitTuple5[T1, T2, T3, T4, T5] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + } + + object WitTuple5 { + def apply[T1, T2, T3, T4, T5](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5): WitTuple5[T1, T2, T3, T4, T5] = js.Array(_1, _2, _3, _4, _5).asInstanceOf[WitTuple5[T1, T2, T3, T4, T5]] + + def unapply[T1, T2, T3, T4, T5](tuple: WitTuple5[T1, T2, T3, T4, T5]): Some[(T1, T2, T3, T4, T5)] = Some(tuple) + + implicit def fromScalaTuple5[T1, T2, T3, T4, T5](tuple: (T1, T2, T3, T4, T5)): WitTuple5[T1, T2, T3, T4, T5] = WitTuple5(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5) + + implicit def toScalaTuple5[T1, T2, T3, T4, T5](tuple: WitTuple5[T1, T2, T3, T4, T5]): (T1, T2, T3, T4, T5) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5) + } + + sealed trait WitTuple6[T1, T2, T3, T4, T5, T6] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + } + + object WitTuple6 { + def apply[T1, T2, T3, T4, T5, T6](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6): WitTuple6[T1, T2, T3, T4, T5, T6] = js.Array(_1, _2, _3, _4, _5, _6).asInstanceOf[WitTuple6[T1, T2, T3, T4, T5, T6]] + + def unapply[T1, T2, T3, T4, T5, T6](tuple: WitTuple6[T1, T2, T3, T4, T5, T6]): Some[(T1, T2, T3, T4, T5, T6)] = Some(tuple) + + implicit def fromScalaTuple6[T1, T2, T3, T4, T5, T6](tuple: (T1, T2, T3, T4, T5, T6)): WitTuple6[T1, T2, T3, T4, T5, T6] = WitTuple6(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6) + + implicit def toScalaTuple6[T1, T2, T3, T4, T5, T6](tuple: WitTuple6[T1, T2, T3, T4, T5, T6]): (T1, T2, T3, T4, T5, T6) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6) + } + + sealed trait WitTuple7[T1, T2, T3, T4, T5, T6, T7] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + } + + object WitTuple7 { + def apply[T1, T2, T3, T4, T5, T6, T7](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7): WitTuple7[T1, T2, T3, T4, T5, T6, T7] = js.Array(_1, _2, _3, _4, _5, _6, _7).asInstanceOf[WitTuple7[T1, T2, T3, T4, T5, T6, T7]] + + def unapply[T1, T2, T3, T4, T5, T6, T7](tuple: WitTuple7[T1, T2, T3, T4, T5, T6, T7]): Some[(T1, T2, T3, T4, T5, T6, T7)] = Some(tuple) + + implicit def fromScalaTuple7[T1, T2, T3, T4, T5, T6, T7](tuple: (T1, T2, T3, T4, T5, T6, T7)): WitTuple7[T1, T2, T3, T4, T5, T6, T7] = WitTuple7(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7) + + implicit def toScalaTuple7[T1, T2, T3, T4, T5, T6, T7](tuple: WitTuple7[T1, T2, T3, T4, T5, T6, T7]): (T1, T2, T3, T4, T5, T6, T7) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7) + } + + sealed trait WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + } + + object WitTuple8 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8): WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8] = js.Array(_1, _2, _3, _4, _5, _6, _7, _8).asInstanceOf[WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8](tuple: WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8]): Some[(T1, T2, T3, T4, T5, T6, T7, T8)] = Some(tuple) + + implicit def fromScalaTuple8[T1, T2, T3, T4, T5, T6, T7, T8](tuple: (T1, T2, T3, T4, T5, T6, T7, T8)): WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8] = WitTuple8(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8) + + implicit def toScalaTuple8[T1, T2, T3, T4, T5, T6, T7, T8](tuple: WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8]): (T1, T2, T3, T4, T5, T6, T7, T8) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8) + } + + sealed trait WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + } + + object WitTuple9 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9): WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] = js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9).asInstanceOf[WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9](tuple: WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9)] = Some(tuple) + + implicit def fromScalaTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9)): WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] = WitTuple9(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9) + + implicit def toScalaTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9](tuple: WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]): (T1, T2, T3, T4, T5, T6, T7, T8, T9) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9) + } + + sealed trait WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + } + + object WitTuple10 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10): WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] = js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10).asInstanceOf[WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](tuple: WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)] = Some(tuple) + + implicit def fromScalaTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)): WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] = WitTuple10(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10) + + implicit def toScalaTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](tuple: WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10) + } + + sealed trait WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + @JSName("10") val _11: T11 + } + + object WitTuple11 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11): WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] = js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11).asInstanceOf[WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](tuple: WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)] = Some(tuple) + + implicit def fromScalaTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)): WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] = WitTuple11(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11) + + implicit def toScalaTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](tuple: WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11) + } + + sealed trait WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + @JSName("10") val _11: T11 + @JSName("11") val _12: T12 + } + + object WitTuple12 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12): WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] = + js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12).asInstanceOf[WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](tuple: WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)] = + Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12)) + + implicit def fromScalaTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)): WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] = + WitTuple12(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12) + + implicit def toScalaTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](tuple: WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) = + (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12) + } + + sealed trait WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + @JSName("10") val _11: T11 + @JSName("11") val _12: T12 + @JSName("12") val _13: T13 + } + + object WitTuple13 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13): WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] = + js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13).asInstanceOf[WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](tuple: WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13)] = + Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13)) + + implicit def fromScalaTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13)): WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] = + WitTuple13(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13) + + implicit def toScalaTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](tuple: WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) = + (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13) + } + + sealed trait WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + @JSName("10") val _11: T11 + @JSName("11") val _12: T12 + @JSName("12") val _13: T13 + @JSName("13") val _14: T14 + } + + object WitTuple14 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14): WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] = + js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14).asInstanceOf[WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](tuple: WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14)] = + Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14)) + + implicit def fromScalaTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14)): WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] = + WitTuple14(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14) + + implicit def toScalaTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](tuple: WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) = + (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14) + } + + sealed trait WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + @JSName("10") val _11: T11 + @JSName("11") val _12: T12 + @JSName("12") val _13: T13 + @JSName("13") val _14: T14 + @JSName("14") val _15: T15 + } + + object WitTuple15 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15): WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] = + js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15).asInstanceOf[WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](tuple: WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15)] = + Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15)) + + implicit def fromScalaTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15)): WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] = + WitTuple15(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15) + + implicit def toScalaTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](tuple: WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) = + (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15) + } + + sealed trait WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + @JSName("10") val _11: T11 + @JSName("11") val _12: T12 + @JSName("12") val _13: T13 + @JSName("13") val _14: T14 + @JSName("14") val _15: T15 + @JSName("15") val _16: T16 + } + + object WitTuple16 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16): WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16] = + js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16).asInstanceOf[WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](tuple: WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16)] = + Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16)) + + implicit def fromScalaTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16)): WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16] = + WitTuple16(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16) + + implicit def toScalaTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](tuple: WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) = + (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16) + } + + sealed trait WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + @JSName("10") val _11: T11 + @JSName("11") val _12: T12 + @JSName("12") val _13: T13 + @JSName("13") val _14: T14 + @JSName("14") val _15: T15 + @JSName("15") val _16: T16 + @JSName("16") val _17: T17 + } + + object WitTuple17 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17): WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17] = + js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17).asInstanceOf[WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](tuple: WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17)] = + Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17)) + + implicit def fromScalaTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17)): WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17] = + WitTuple17(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17) + + implicit def toScalaTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](tuple: WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) = + (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17) + } + + sealed trait WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + @JSName("10") val _11: T11 + @JSName("11") val _12: T12 + @JSName("12") val _13: T13 + @JSName("13") val _14: T14 + @JSName("14") val _15: T15 + @JSName("15") val _16: T16 + @JSName("16") val _17: T17 + @JSName("17") val _18: T18 + } + + object WitTuple18 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18): WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18] = + js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18).asInstanceOf[WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](tuple: WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18)] = + Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18)) + + implicit def fromScalaTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18)): WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18] = + WitTuple18(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18) + + implicit def toScalaTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](tuple: WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) = + (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18) + } + + sealed trait WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + @JSName("10") val _11: T11 + @JSName("11") val _12: T12 + @JSName("12") val _13: T13 + @JSName("13") val _14: T14 + @JSName("14") val _15: T15 + @JSName("15") val _16: T16 + @JSName("16") val _17: T17 + @JSName("17") val _18: T18 + @JSName("18") val _19: T19 + } + + object WitTuple19 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19): WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19] = + js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19).asInstanceOf[WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](tuple: WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19)] = + Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19)) + + implicit def fromScalaTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19)): WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19] = + WitTuple19(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19) + + implicit def toScalaTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](tuple: WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) = + (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19) + } + + sealed trait WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + @JSName("10") val _11: T11 + @JSName("11") val _12: T12 + @JSName("12") val _13: T13 + @JSName("13") val _14: T14 + @JSName("14") val _15: T15 + @JSName("15") val _16: T16 + @JSName("16") val _17: T17 + @JSName("17") val _18: T18 + @JSName("18") val _19: T19 + @JSName("19") val _20: T20 + } + + object WitTuple20 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19, _20: T20): WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20] = + js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20).asInstanceOf[WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20](tuple: WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20)] = + Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20)) + + implicit def fromScalaTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20)): WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20] = + WitTuple20(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20) + + implicit def toScalaTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20](tuple: WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20) = + (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20) + } + + sealed trait WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + @JSName("10") val _11: T11 + @JSName("11") val _12: T12 + @JSName("12") val _13: T13 + @JSName("13") val _14: T14 + @JSName("14") val _15: T15 + @JSName("15") val _16: T16 + @JSName("16") val _17: T17 + @JSName("17") val _18: T18 + @JSName("18") val _19: T19 + @JSName("19") val _20: T20 + @JSName("20") val _21: T21 + } + + object WitTuple21 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19, _20: T20, _21: T21): WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] = + js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21).asInstanceOf[WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](tuple: WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21)] = + Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20, tuple._21)) + + implicit def fromScalaTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21)): WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] = + WitTuple21(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20, tuple._21) + + implicit def toScalaTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](tuple: WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21) = + (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20, tuple._21) + } + + sealed trait WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] extends js.Object { + @JSName("0") val _1: T1 + @JSName("1") val _2: T2 + @JSName("2") val _3: T3 + @JSName("3") val _4: T4 + @JSName("4") val _5: T5 + @JSName("5") val _6: T6 + @JSName("6") val _7: T7 + @JSName("7") val _8: T8 + @JSName("8") val _9: T9 + @JSName("9") val _10: T10 + @JSName("10") val _11: T11 + @JSName("11") val _12: T12 + @JSName("12") val _13: T13 + @JSName("13") val _14: T14 + @JSName("14") val _15: T15 + @JSName("15") val _16: T16 + @JSName("16") val _17: T17 + @JSName("17") val _18: T18 + @JSName("18") val _19: T19 + @JSName("19") val _20: T20 + @JSName("20") val _21: T21 + @JSName("21") val _22: T22 + } + + object WitTuple22 { + def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19, _20: T20, _21: T21, _22: T22): WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] = + js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22).asInstanceOf[WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]] + + def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22](tuple: WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22)] = + Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20, tuple._21, tuple._22)) + + implicit def fromScalaTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22)): WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] = + WitTuple22(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20, tuple._21, tuple._22) + + implicit def toScalaTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22](tuple: WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22) = + (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20, tuple._21, tuple._22) + } } \ No newline at end of file diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index f82e00be7..da71d3d75 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -1,13 +1,15 @@ use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Write}; use std::str::FromStr; use wit_bindgen_core::wit_parser::{ Docs, Function, FunctionKind, Handle, Interface, InterfaceId, PackageName, Resolve, Results, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, WorldId, WorldKey, }; -use wit_bindgen_core::{uwrite, uwriteln, Direction, Files, WorldGenerator}; use wit_bindgen_core::Direction::{Export, Import}; +use wit_bindgen_core::{uwrite, uwriteln, Direction, Files, WorldGenerator}; + +// TODO: need to use https://github.com/golemcloud/jco/blob/main/crates/js-component-bindgen/src/names.rs#L81 for every name, and use JSName where it differs from the scala name #[derive(Debug, Clone)] pub enum ScalaDialect { @@ -49,7 +51,7 @@ pub struct Opts { feature = "clap", clap(long, help = "Base package for generated Scala.js code") )] - base_package: Option, + pub base_package: Option, #[cfg_attr( feature = "clap", @@ -59,15 +61,12 @@ pub struct Opts { default_value = "scala2" ) )] - scala_dialect: ScalaDialect, + pub scala_dialect: ScalaDialect, } impl Opts { pub fn build(&self) -> Box { - Box::new(ScalaJsWorld { - opts: self.clone(), - generated_files: HashMap::new(), - }) + Box::new(ScalaJsWorld::new(self.clone())) } pub fn base_package_segments(&self) -> Vec { @@ -85,9 +84,132 @@ impl Opts { } } +struct ScalaKeywords { + keywords: HashSet, + base_methods: HashSet, +} + +impl ScalaKeywords { + pub fn new(dialect: &ScalaDialect) -> Self { + let mut keywords = HashSet::new(); + keywords.insert("abstract".to_string()); + keywords.insert("case".to_string()); + keywords.insert("do".to_string()); + keywords.insert("else".to_string()); + keywords.insert("finally".to_string()); + keywords.insert("for".to_string()); + keywords.insert("import".to_string()); + keywords.insert("lazy".to_string()); + keywords.insert("object".to_string()); + keywords.insert("override".to_string()); + keywords.insert("return".to_string()); + keywords.insert("sealed".to_string()); + keywords.insert("trait".to_string()); + keywords.insert("try".to_string()); + keywords.insert("var".to_string()); + keywords.insert("while".to_string()); + keywords.insert("catch".to_string()); + keywords.insert("class".to_string()); + keywords.insert("extends".to_string()); + keywords.insert("false".to_string()); + keywords.insert("forSome".to_string()); + keywords.insert("if".to_string()); + keywords.insert("macro".to_string()); + keywords.insert("match".to_string()); + keywords.insert("new".to_string()); + keywords.insert("package".to_string()); + keywords.insert("private".to_string()); + keywords.insert("super".to_string()); + keywords.insert("this".to_string()); + keywords.insert("true".to_string()); + keywords.insert("type".to_string()); + keywords.insert("with".to_string()); + keywords.insert("yield".to_string()); + keywords.insert("def".to_string()); + keywords.insert("final".to_string()); + keywords.insert("implicit".to_string()); + keywords.insert("null".to_string()); + keywords.insert("protected".to_string()); + keywords.insert("throw".to_string()); + keywords.insert("val".to_string()); + keywords.insert("_".to_string()); + keywords.insert(":".to_string()); + keywords.insert("=".to_string()); + keywords.insert("=>".to_string()); + keywords.insert("<-".to_string()); + keywords.insert("<:".to_string()); + keywords.insert("<%".to_string()); + keywords.insert("=>>".to_string()); + keywords.insert(">:".to_string()); + keywords.insert("#".to_string()); + keywords.insert("@".to_string()); + keywords.insert("\u{21D2}".to_string()); + keywords.insert("\u{2190}".to_string()); + + let mut base_methods = HashSet::new(); + base_methods.insert("equals".to_string()); + base_methods.insert("hashCode".to_string()); + base_methods.insert("toString".to_string()); + base_methods.insert("clone".to_string()); + base_methods.insert("finalize".to_string()); + base_methods.insert("getClass".to_string()); + base_methods.insert("notify".to_string()); + base_methods.insert("notifyAll".to_string()); + base_methods.insert("wait".to_string()); + base_methods.insert("isInstanceOf".to_string()); + base_methods.insert("asInstanceOf".to_string()); + base_methods.insert("synchronized".to_string()); + base_methods.insert("ne".to_string()); + base_methods.insert("eq".to_string()); + base_methods.insert("hasOwnProperty".to_string()); + base_methods.insert("isPrototypeOf".to_string()); + base_methods.insert("propertyIsEnumerable".to_string()); + base_methods.insert("toLocaleString".to_string()); + base_methods.insert("valueOf".to_string()); + + match dialect { + ScalaDialect::Scala2 => {} + ScalaDialect::Scala3 => { + keywords.insert("enum".to_string()); + keywords.insert("export".to_string()); + keywords.insert("given".to_string()); + keywords.insert("?=>".to_string()); + keywords.insert("then".to_string()); + } + } + + Self { + keywords, + base_methods + } + } + + fn escape(&self, ident: impl AsRef) -> String { + if self.keywords.contains(ident.as_ref()) { + format!("`{}`", ident.as_ref()) + } else { + ident.as_ref().to_string() + } + } +} + struct ScalaJsWorld { opts: Opts, generated_files: HashMap, + keywords: ScalaKeywords, + overrides: HashMap +} + +impl ScalaJsWorld { + fn new(opts: Opts) -> Self { + let keywords = ScalaKeywords::new(&opts.scala_dialect); + Self { + opts, + generated_files: HashMap::new(), + keywords, + overrides: HashMap::new() + } + } } impl WorldGenerator for ScalaJsWorld { @@ -103,7 +225,15 @@ impl WorldGenerator for ScalaJsWorld { println!("import_interface: {wit_name}"); - let mut scalajs_iface = ScalaJsInterface::new(wit_name.clone(), resolve, iface, &self.opts, Import); + let mut scalajs_iface = ScalaJsInterface::new( + wit_name.clone(), + resolve, + iface, + &self.opts, + Import, + &self.keywords, + &mut self.overrides, + ); scalajs_iface.generate(); self.generated_files .insert(wit_name, scalajs_iface.finalize()); @@ -123,7 +253,15 @@ impl WorldGenerator for ScalaJsWorld { println!("export_interface: {:?}", name); - let mut scalajs_iface = ScalaJsInterface::new(wit_name.clone(), resolve, iface, &self.opts, Export); + let mut scalajs_iface = ScalaJsInterface::new( + wit_name.clone(), + resolve, + iface, + &self.opts, + Export, + &self.keywords, + &mut self.overrides, + ); scalajs_iface.generate(); self.generated_files .insert(wit_name, scalajs_iface.finalize()); @@ -206,7 +344,9 @@ struct ScalaJsInterface<'a> { resolve: &'a Resolve, interface: &'a Interface, interface_id: InterfaceId, - direction: Direction + direction: Direction, + scala_keywords: &'a ScalaKeywords, + overrides: &'a mut HashMap } impl<'a> ScalaJsInterface<'a> { @@ -215,13 +355,15 @@ impl<'a> ScalaJsInterface<'a> { resolve: &'a Resolve, interface_id: InterfaceId, opts: &'a Opts, - direction: Direction + direction: Direction, + scala_keywords: &'a ScalaKeywords, + overrides: &'a mut HashMap ) -> Self { let interface = &resolve.interfaces[interface_id]; let name = interface .name .clone() - .expect("Anonymous interfaces not supported yet") + .unwrap_or(wit_name.clone()) .to_pascal_case(); let package_name = resolve.packages @@ -229,7 +371,7 @@ impl<'a> ScalaJsInterface<'a> { .name .clone(); - let mut package = package_name_to_segments(&opts, &package_name); + let package = package_name_to_segments(&opts, &package_name); Self { wit_name, @@ -240,7 +382,9 @@ impl<'a> ScalaJsInterface<'a> { resolve, interface, interface_id, - direction + direction, + scala_keywords, + overrides } } @@ -253,7 +397,11 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, "import {}wit._", self.opts.base_package_prefix()); uwriteln!(source, ""); - uwriteln!(source, "package object {} {{", self.name.to_snake_case()); + uwriteln!( + source, + "package object {} {{", + self.scala_keywords.escape(self.name.to_snake_case()) + ); let mut types = Vec::new(); let mut functions = Vec::new(); @@ -261,13 +409,32 @@ impl<'a> ScalaJsInterface<'a> { for (type_name, type_id) in &self.interface.types { let type_def = &self.resolve.types[*type_id]; - if let Some(typ) = self.render_typedef(type_name, type_def) { + + let type_name = self.overrides.get(type_id).unwrap_or(type_name); + let type_name = if type_name.eq_ignore_ascii_case(&self.name) { + let overridden_type_name = format!("{}Type", type_name); + self.overrides.insert(*type_id, overridden_type_name.clone()); + overridden_type_name + } else { + type_name.clone() + }; + + if let Some(typ) = self.render_typedef(&type_name, type_def) { types.push(typ); } } + for (_, type_id) in &self.interface.types { + let type_def = &self.resolve.types[*type_id]; + if let TypeDefKind::Resource = &type_def.kind { + resources + .entry(*type_id) + .or_insert_with(|| ScalaJsResource::new(self, *type_id)); + } + } + for (func_name, func) in &self.interface.functions { - let scala_func_name = func_name.to_lower_camel_case(); + let scala_func_name = self.scala_keywords.escape(func_name.to_lower_camel_case()); match func.kind { FunctionKind::Freestanding => { @@ -287,7 +454,10 @@ impl<'a> ScalaJsInterface<'a> { Export => "", }; - uwriteln!(function, " def {scala_func_name}({args}): {ret}{postfix}"); + uwriteln!( + function, + " def {scala_func_name}({args}): {ret}{postfix}" + ); functions.push(function); } FunctionKind::Method(resource_type) @@ -347,12 +517,11 @@ impl<'a> ScalaJsInterface<'a> { } } - fn render_args<'b>(&self, params: impl Iterator) -> String { let mut args = Vec::new(); for (param_name, param_typ) in params { let param_typ = self.render_type_reference(param_typ); - let param_name = param_name.to_lower_camel_case(); + let param_name = self.scala_keywords.escape(param_name.to_lower_camel_case()); args.push(format!("{param_name}: {param_typ}")); } args.join(", ") @@ -396,17 +565,19 @@ impl<'a> ScalaJsInterface<'a> { | TypeDefKind::Enum(_) | TypeDefKind::Type(_) | TypeDefKind::Variant(_) => { - let prefix = match self.render_owner(&typ.owner) { + let prefix = match self.render_owner(&typ.owner, &typ.kind == &TypeDefKind::Resource) { Some(owner) => format!("{owner}."), None => "".to_string(), }; format!( "{}{}", prefix, - typ.name - .clone() - .expect("Anonymous types are not supported") - .to_pascal_case() + self.scala_keywords.escape( + typ.name + .clone() + .expect("Anonymous types are not supported") + .to_pascal_case() + ) ) } TypeDefKind::Handle(handle) => { @@ -418,11 +589,13 @@ impl<'a> ScalaJsInterface<'a> { self.render_typedef_reference(typ) } TypeDefKind::Tuple(tuple) => { + let arity = tuple.types.len(); + let mut parts = Vec::new(); for part in &tuple.types { parts.push(self.render_type_reference(part)); } - format!("({})", parts.join(", ")) + format!("WitTuple{arity}[{}]", parts.join(", ")) } TypeDefKind::Option(option) => { if !maybe_null(&self.resolve, option) { @@ -452,14 +625,23 @@ impl<'a> ScalaJsInterface<'a> { } } - fn render_owner(&self, owner: &TypeOwner) -> Option { + fn render_owner(&self, owner: &TypeOwner, is_resource: bool) -> Option { match owner { TypeOwner::World(id) => { let world = &self.resolve.worlds[*id]; // TODO: assuming an object or trait is also generated per world? - Some(world.name.clone().to_pascal_case()) + Some( + self.scala_keywords + .escape(world.name.clone().to_pascal_case()), + ) } - TypeOwner::Interface(id) if id == &self.interface_id => None, + TypeOwner::Interface(id) if id == &self.interface_id => { + if is_resource { + Some(self.name.clone()) + } else { + None + } + }, TypeOwner::Interface(id) => { let iface = &self.resolve.interfaces[*id]; let name = iface.name.clone().expect("Interface must have a name"); @@ -467,7 +649,12 @@ impl<'a> ScalaJsInterface<'a> { let package = &self.resolve.packages[package_id]; let mut segments = package_name_to_segments(&self.opts, &package.name); - segments.push(name.to_snake_case()); + segments.push(self.scala_keywords.escape(name.to_snake_case())); + + if is_resource { + segments.push(self.scala_keywords.escape(name.to_pascal_case())); + } + Some(segments.join(".")) } TypeOwner::None => None, @@ -475,7 +662,7 @@ impl<'a> ScalaJsInterface<'a> { } fn render_typedef(&self, name: &str, typ: &TypeDef) -> Option { - let scala_name = name.to_pascal_case(); + let scala_name = self.scala_keywords.escape(name.to_pascal_case()); let mut source = String::new(); match &typ.kind { @@ -483,13 +670,14 @@ impl<'a> ScalaJsInterface<'a> { let mut fields = Vec::new(); for field in &record.fields { let typ = self.render_type_reference(&field.ty); - let field_name = field.name.to_lower_camel_case(); - fields.push((field_name, typ, &field.docs)); + let field_name = self.scala_keywords.escape(field.name.to_lower_camel_case()); + let field_name0 = self.scala_keywords.escape(format!("{}0", field.name.to_lower_camel_case())); + fields.push((field_name, field_name0, typ, &field.docs)); } write_doc_comment(&mut source, " ", &typ.docs); uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); - for (field_name, typ, docs) in &fields { + for (field_name, _, typ, docs) in &fields { write_doc_comment(&mut source, " ", &docs); uwriteln!(source, " val {field_name}: {typ}"); } @@ -497,13 +685,13 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, ""); uwriteln!(source, " case object {scala_name} {{"); uwriteln!(source, " def apply("); - for (field_name, typ, _) in &fields { - uwriteln!(source, " {field_name}0: {typ},"); + for (_, field_name0, typ, _) in &fields { + uwriteln!(source, " {field_name0}: {typ},"); } uwriteln!(source, " ): {scala_name} = {{"); uwriteln!(source, " new {scala_name} {{"); - for (field_name, typ, _) in &fields { - uwriteln!(source, " val {field_name}: {typ} = {field_name}0"); + for (field_name, field_name0, typ, _) in &fields { + uwriteln!(source, " val {field_name}: {typ} = {field_name0}"); } uwriteln!(source, " }}"); uwriteln!(source, " }}"); @@ -519,27 +707,28 @@ impl<'a> ScalaJsInterface<'a> { let mut fields = Vec::new(); for flag in &flags.flags { let typ = "Boolean".to_string(); - let field_name = flag.name.to_lower_camel_case(); - fields.push((field_name, typ, &flag.docs)); + let field_name = self.scala_keywords.escape(flag.name.to_lower_camel_case()); + let field_name0 = self.scala_keywords.escape(format!("{}0", flag.name.to_lower_camel_case())); + fields.push((field_name, field_name0, typ, &flag.docs)); } write_doc_comment(&mut source, " ", &typ.docs); uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); - for (field_name, typ, docs) in &fields { + for (field_name, _, typ, docs) in &fields { write_doc_comment(&mut source, " ", docs); - uwriteln!(source, " val {field_name}: {typ},"); + uwriteln!(source, " val {field_name}: {typ}"); } uwriteln!(source, " }}"); uwriteln!(source, ""); uwriteln!(source, " case object {scala_name} {{"); uwriteln!(source, " def apply("); - for (field_name, typ, _) in &fields { - uwriteln!(source, " {field_name}0: {typ},"); + for (_, field_name0, typ, _) in &fields { + uwriteln!(source, " {field_name0}: {typ},"); } uwriteln!(source, " ): {scala_name} = {{"); uwriteln!(source, " new {scala_name} {{"); - for (field_name, typ, _) in &fields { - uwriteln!(source, " val {field_name}: {typ} = {field_name}0"); + for (field_name, field_name0, typ, _) in &fields { + uwriteln!(source, " val {field_name}: {typ} = {field_name0}"); } uwriteln!(source, " }}"); uwriteln!(source, " }}"); @@ -548,7 +737,7 @@ impl<'a> ScalaJsInterface<'a> { TypeDefKind::Tuple(tuple) => { let arity = tuple.types.len(); write_doc_comment(&mut source, " ", &typ.docs); - uwrite!(source, " type {scala_name} = js.Tuple{arity}["); + uwrite!(source, " type {scala_name} = WitTuple{arity}["); for (idx, part) in tuple.types.iter().enumerate() { let part = self.render_type_reference(part); uwrite!(source, "{part}"); @@ -569,11 +758,13 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, " object {scala_name} {{"); for case in &variant.cases { let case_name = &case.name; + let scala_case_name = + self.scala_keywords.escape(case_name.to_lower_camel_case()); match &case.ty { Some(ty) => { let typ = self.render_type_reference(ty); write_doc_comment(&mut source, " ", &case.docs); - uwriteln!(source, " def {case_name}(value: {typ}): {scala_name} = new {scala_name} {{"); + uwriteln!(source, " def {scala_case_name}(value: {typ}): {scala_name} = new {scala_name} {{"); uwriteln!(source, " type Type = {typ}"); uwriteln!(source, " val tag: String = \"{case_name}\""); uwriteln!(source, " val `val`: js.UndefOr[Type] = value"); @@ -583,9 +774,9 @@ impl<'a> ScalaJsInterface<'a> { write_doc_comment(&mut source, " ", &case.docs); uwriteln!( source, - " def {case_name}(): {scala_name} = new {scala_name} {{" + " def {scala_case_name}(): {scala_name} = new {scala_name} {{" ); - uwriteln!(source, " type Type = ()"); + uwriteln!(source, " type Type = Unit"); uwriteln!(source, " val tag: String = \"{case_name}\""); uwriteln!(source, " val `val`: js.UndefOr[Type] = ()"); uwriteln!(source, " }}"); @@ -601,10 +792,12 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, " object {scala_name} {{"); for case in &enm.cases { let case_name = &case.name; + let scala_case_name = + self.scala_keywords.escape(case_name.to_lower_camel_case()); write_doc_comment(&mut source, " ", &case.docs); uwriteln!( source, - " def {case_name}: {scala_name} = \"{case_name}\".asInstanceOf[{scala_name}]", + " def {scala_case_name}: {scala_name} = \"{case_name}\".asInstanceOf[{scala_name}]", ); } uwriteln!(source, " }}"); @@ -664,10 +857,12 @@ impl<'a> ScalaJsInterface<'a> { struct ScalaJsResource<'a> { owner: &'a ScalaJsInterface<'a>, - resource_id: TypeId, + _resource_id: TypeId, resource_name: String, + class_header: String, class_source: String, object_source: String, + constructor_args: String } impl<'a> ScalaJsResource<'a> { @@ -677,17 +872,20 @@ impl<'a> ScalaJsResource<'a> { .name .clone() .expect("Anonymous resources not supported"); - let scala_resource_name = resource_name.to_pascal_case(); - - let mut class_source = String::new(); - write_doc_comment(&mut class_source, " ", &resource.docs); - uwriteln!(class_source, " @js.native"); - uwriteln!( - class_source, - " class {} extends js.Object {{", + let scala_resource_name = owner.scala_keywords.escape(resource_name.to_pascal_case()); + + let mut class_header = String::new(); + write_doc_comment(&mut class_header, " ", &resource.docs); + uwriteln!(class_header, " @js.native"); + uwrite!( + class_header, + " class {}(", scala_resource_name ); + let mut class_source = String::new(); + uwriteln!(class_source, ") extends js.Object {{"); + let mut object_source = String::new(); uwriteln!(object_source, " @js.native"); uwriteln!( @@ -698,10 +896,12 @@ impl<'a> ScalaJsResource<'a> { Self { owner, - resource_id, + _resource_id: resource_id, resource_name, + class_header, class_source, object_source, + constructor_args: String::new() } } @@ -711,12 +911,18 @@ impl<'a> ScalaJsResource<'a> { FunctionKind::Method(_) => { let args = self.owner.render_args(func.params.iter().skip(1)); let ret = self.owner.render_return_type(&func.results); - let scala_func_name = self.get_func_name("[method]", func_name); + + let overrd = if self.owner.scala_keywords.base_methods.contains(&scala_func_name) { + "override " + } else { + "" + }; + write_doc_comment(&mut self.class_source, " ", &func.docs); uwriteln!( self.class_source, - " def {scala_func_name}({args}): {ret} = js.native" + " {overrd}def {scala_func_name}({args}): {ret} = js.native" ); } FunctionKind::Static(_) => { @@ -732,19 +938,15 @@ impl<'a> ScalaJsResource<'a> { } FunctionKind::Constructor(_) => { let args = self.owner.render_args(func.params.iter()); - let ret = self.owner.render_return_type(&func.results); - - write_doc_comment(&mut self.class_source, " ", &func.docs); - uwriteln!( - self.class_source, - " def this({args}): {ret} = js.native" - ); + self.constructor_args = args; } } } pub fn finalize(self) -> String { - let mut class_source = self.class_source; + let mut class_source = self.class_header; + uwrite!(class_source, "{}", self.constructor_args); + uwriteln!(class_source, "{}", self.class_source); uwriteln!(class_source, " }}"); let mut object_source = self.object_source; uwriteln!(object_source, " }}"); @@ -752,12 +954,14 @@ impl<'a> ScalaJsResource<'a> { } fn get_func_name(&self, prefix: &str, func_name: &str) -> String { - func_name - .strip_prefix(prefix) - .unwrap() - .strip_prefix(&self.resource_name) - .unwrap() - .to_lower_camel_case() + self.owner.scala_keywords.escape( + func_name + .strip_prefix(prefix) + .unwrap() + .strip_prefix(&self.resource_name) + .unwrap() + .to_lower_camel_case(), + ) } } @@ -775,7 +979,7 @@ fn render_runtime_module(opts: &Opts) -> ScalaJsFile { ScalaJsFile { package, name: "package".to_string(), - source + source, } } diff --git a/crates/scalajs/tests/codegen.rs b/crates/scalajs/tests/codegen.rs new file mode 100644 index 000000000..716e55a6b --- /dev/null +++ b/crates/scalajs/tests/codegen.rs @@ -0,0 +1,78 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use wit_bindgen_scalajs::ScalaDialect::Scala2; + +macro_rules! codegen_test { + // TODO: implement support for stream, future, and error-context, and then + // remove these lines: + (streams $name:tt $test:tt) => {}; + (futures $name:tt $test:tt) => {}; + (resources_with_streams $name:tt $test:tt) => {}; + (resources_with_futures $name:tt $test:tt) => {}; + (error_context $name:tt $test:tt) => {}; + + ($id:ident $name:tt $test:tt) => { + #[test] + fn $id() { + test_helpers::run_world_codegen_test( + "guest-scalajs", + $test.as_ref(), + |resolve, world, files| { + wit_bindgen_scalajs::Opts { + base_package: Some("test".to_string()), + scala_dialect: Scala2 + } + .build() + .generate(resolve, world, files) + .unwrap() + }, + verify, + ) + } + }; +} +test_helpers::codegen_tests!(); + +fn verify(dir: &Path, name: &str) { + println!("name: {name}, dir: {dir:?}"); + let mut files = Vec::new(); + move_scala_files(dir, &dir.join("src/main/scala"), &mut files); + + write_build_sbt(dir); + write_plugins_sbt(dir); + + let mut cmd = Command::new("sbt"); + cmd.current_dir(dir); + cmd.arg("fastLinkJS"); + + test_helpers::run_command(&mut cmd); +} + +fn move_scala_files(src: &Path, dst: &Path, files: &mut Vec) { + if src.is_dir() { + for entry in fs::read_dir(src).unwrap() { + let path = entry.unwrap().path(); + move_scala_files(&path, &dst.join(path.strip_prefix(src).unwrap()), files); + } + } else if let Some("scala") = src.extension().map(|ext| ext.to_str().unwrap()) { + fs::create_dir_all(dst.parent().unwrap()).unwrap(); + fs::rename(src, dst).unwrap(); + files.push(dst.to_owned()); + } +} + +fn write_build_sbt(dir: &Path) { + let build_sbt = include_str!("../scala/build.sbt"); + fs::write(dir.join("build.sbt"), build_sbt).unwrap(); +} + +fn write_plugins_sbt(dir: &Path) { + let plugins_sbt = include_str!("../scala/plugins.sbt"); + let build_properties = include_str!("../scala/build.properties"); + + let project_dir = dir.join("project"); + fs::create_dir_all(&project_dir).unwrap(); + fs::write(project_dir.join("plugins.sbt"), plugins_sbt).unwrap(); + fs::write(project_dir.join("build.properties"), build_properties).unwrap(); +} diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 60c66ce91..59c622f4f 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -123,6 +123,7 @@ fn tests(name: &str, dir_name: &str) -> Result> { let mut java = Vec::new(); let mut go = Vec::new(); let mut c_sharp: Vec = Vec::new(); + let mut scalajs = Vec::new(); for file in dir.read_dir()? { let path = file?.path(); match path.extension().and_then(|s| s.to_str()) { @@ -131,6 +132,7 @@ fn tests(name: &str, dir_name: &str) -> Result> { Some("rs") => rust.push(path), Some("go") => go.push(path), Some("cs") => c_sharp.push(path), + Some("scala") => scalajs.push(path), _ => {} } } @@ -767,6 +769,94 @@ fn tests(name: &str, dir_name: &str) -> Result> { } } + #[cfg(feature = "scalajs")] + if !scalajs.is_empty() { + // let (resolve, world) = resolve_wit_dir(&dir); + // for path in c.iter() { + // let world_name = &resolve.worlds[world].name; + // let out_dir = out_dir.join(format!("c-{}", world_name)); + // drop(fs::remove_dir_all(&out_dir)); + // fs::create_dir_all(&out_dir).unwrap(); + // + // let snake = world_name.replace("-", "_"); + // let mut files = Default::default(); + // let mut opts = wit_bindgen_c::Opts::default(); + // if let Some(path) = path.file_name().and_then(|s| s.to_str()) { + // if path.contains("utf16") { + // opts.string_encoding = wit_component::StringEncoding::UTF16; + // } + // } + // opts.build().generate(&resolve, world, &mut files).unwrap(); + // + // for (file, contents) in files.iter() { + // let dst = out_dir.join(file); + // fs::write(dst, contents).unwrap(); + // } + // + // let sdk = PathBuf::from(std::env::var_os("WASI_SDK_PATH").expect( + // "point the `WASI_SDK_PATH` environment variable to the path of your wasi-sdk", + // )); + // // Test both C mode and C++ mode. + // for compiler in ["bin/clang", "bin/clang++"] { + // let mut cmd = Command::new(sdk.join(compiler)); + // let out_wasm = out_dir.join(format!( + // "c-{}.wasm", + // path.file_stem().and_then(|s| s.to_str()).unwrap() + // )); + // cmd.arg("--sysroot").arg(sdk.join("share/wasi-sysroot")); + // cmd.arg(path) + // .arg(out_dir.join(format!("{snake}.c"))) + // .arg(out_dir.join(format!("{snake}_component_type.o"))) + // .arg("-I") + // .arg(&out_dir) + // .arg("-Wall") + // .arg("-Wextra") + // .arg("-Werror") + // .arg("-Wno-unused-parameter") + // .arg("-mexec-model=reactor") + // .arg("-g") + // .arg("-o") + // .arg(&out_wasm); + // // Disable the warning about compiling a `.c` file in C++ mode. + // if compiler.ends_with("++") { + // cmd.arg("-Wno-deprecated"); + // } + // let command = format!("{cmd:?}"); + // let output = match cmd.output() { + // Ok(output) => output, + // Err(e) => panic!("failed to spawn compiler: {e}; command was `{command}`"), + // }; + // + // if !output.status.success() { + // println!("status: {}", output.status); + // println!("stdout: ------------------------------------------"); + // println!("{}", String::from_utf8_lossy(&output.stdout)); + // println!("stderr: ------------------------------------------"); + // println!("{}", String::from_utf8_lossy(&output.stderr)); + // panic!("failed to compile"); + // } + // + // // Translate the canonical ABI module into a component. + // let module = fs::read(&out_wasm).expect("failed to read wasm file"); + // let component = ComponentEncoder::default() + // .module(module.as_slice()) + // .expect("pull custom sections from module") + // .validate(true) + // .adapter("wasi_snapshot_preview1", &wasi_adapter) + // .expect("adapter failed to get loaded") + // .encode() + // .expect(&format!( + // "module {:?} can be translated to a component", + // out_wasm + // )); + // let component_path = out_wasm.with_extension("component.wasm"); + // fs::write(&component_path, component).expect("write component to disk"); + // + // result.push(component_path); + // } + // } + } + Ok(result) } From 048164363248650bce980847550a1f022f639707 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Fri, 31 Jan 2025 00:22:58 +0100 Subject: [PATCH 03/36] Scala.js binding generator, work in progress --- crates/scalajs/src/lib.rs | 234 ++++++++++++++++++++++++++++++-------- 1 file changed, 186 insertions(+), 48 deletions(-) diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index da71d3d75..9838a6da5 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -180,7 +180,7 @@ impl ScalaKeywords { Self { keywords, - base_methods + base_methods, } } @@ -195,9 +195,9 @@ impl ScalaKeywords { struct ScalaJsWorld { opts: Opts, - generated_files: HashMap, + generated_files: Vec, keywords: ScalaKeywords, - overrides: HashMap + overrides: HashMap, } impl ScalaJsWorld { @@ -205,9 +205,9 @@ impl ScalaJsWorld { let keywords = ScalaKeywords::new(&opts.scala_dialect); Self { opts, - generated_files: HashMap::new(), + generated_files: Vec::new(), keywords, - overrides: HashMap::new() + overrides: HashMap::new(), } } } @@ -235,8 +235,7 @@ impl WorldGenerator for ScalaJsWorld { &mut self.overrides, ); scalajs_iface.generate(); - self.generated_files - .insert(wit_name, scalajs_iface.finalize()); + self.generated_files.push(scalajs_iface.finalize()); Ok(()) } @@ -263,8 +262,7 @@ impl WorldGenerator for ScalaJsWorld { &mut self.overrides, ); scalajs_iface.generate(); - self.generated_files - .insert(wit_name, scalajs_iface.finalize()); + self.generated_files.push(scalajs_iface.finalize()); Ok(()) } @@ -309,11 +307,8 @@ impl WorldGenerator for ScalaJsWorld { _world: WorldId, files: &mut Files, ) -> anyhow::Result<()> { - for (name, iface) in &self.generated_files { - println!("--- interface: {:?}", name); - println!("{}", iface.source); - - files.push(&iface.path(), iface.source.as_bytes()); + for file in &self.generated_files { + files.push(&file.path(), file.source.as_bytes()); } let rt = render_runtime_module(&self.opts); @@ -346,7 +341,7 @@ struct ScalaJsInterface<'a> { interface_id: InterfaceId, direction: Direction, scala_keywords: &'a ScalaKeywords, - overrides: &'a mut HashMap + overrides: &'a mut HashMap, } impl<'a> ScalaJsInterface<'a> { @@ -357,7 +352,7 @@ impl<'a> ScalaJsInterface<'a> { opts: &'a Opts, direction: Direction, scala_keywords: &'a ScalaKeywords, - overrides: &'a mut HashMap + overrides: &'a mut HashMap, ) -> Self { let interface = &resolve.interfaces[interface_id]; let name = interface @@ -371,7 +366,7 @@ impl<'a> ScalaJsInterface<'a> { .name .clone(); - let package = package_name_to_segments(&opts, &package_name); + let package = package_name_to_segments(&opts, &package_name, &direction, &scala_keywords); Self { wit_name, @@ -384,7 +379,7 @@ impl<'a> ScalaJsInterface<'a> { interface_id, direction, scala_keywords, - overrides + overrides, } } @@ -405,7 +400,8 @@ impl<'a> ScalaJsInterface<'a> { let mut types = Vec::new(); let mut functions = Vec::new(); - let mut resources = HashMap::new(); + let mut imported_resources = HashMap::new(); + let mut exported_resources = HashMap::new(); for (type_name, type_id) in &self.interface.types { let type_def = &self.resolve.types[*type_id]; @@ -413,7 +409,8 @@ impl<'a> ScalaJsInterface<'a> { let type_name = self.overrides.get(type_id).unwrap_or(type_name); let type_name = if type_name.eq_ignore_ascii_case(&self.name) { let overridden_type_name = format!("{}Type", type_name); - self.overrides.insert(*type_id, overridden_type_name.clone()); + self.overrides + .insert(*type_id, overridden_type_name.clone()); overridden_type_name } else { type_name.clone() @@ -427,9 +424,18 @@ impl<'a> ScalaJsInterface<'a> { for (_, type_id) in &self.interface.types { let type_def = &self.resolve.types[*type_id]; if let TypeDefKind::Resource = &type_def.kind { - resources - .entry(*type_id) - .or_insert_with(|| ScalaJsResource::new(self, *type_id)); + match &self.direction { + Import => { + imported_resources + .entry(*type_id) + .or_insert_with(|| ScalaJsImportedResource::new(self, *type_id)); + } + Export => { + exported_resources + .entry(*type_id) + .or_insert_with(|| ScalaJsExportedResource::new(self, *type_id)); + } + } } } @@ -463,10 +469,20 @@ impl<'a> ScalaJsInterface<'a> { FunctionKind::Method(resource_type) | FunctionKind::Static(resource_type) | FunctionKind::Constructor(resource_type) => { - let resource = resources - .entry(resource_type) - .or_insert_with(|| ScalaJsResource::new(self, resource_type)); - resource.add_function(func_name, func); + match &self.direction { + Import => { + let resource = imported_resources + .entry(resource_type) + .or_insert_with(|| ScalaJsImportedResource::new(self, resource_type)); + resource.add_function(func_name, func); + } + Export => { + let resource = exported_resources + .entry(resource_type) + .or_insert_with(|| ScalaJsExportedResource::new(self, resource_type)); + resource.add_function(func_name, func); + } + } } } } @@ -483,7 +499,11 @@ impl<'a> ScalaJsInterface<'a> { } else { uwriteln!(source, " trait {} {{", self.name); } - for (_, resource) in resources { + for (_, resource) in imported_resources { + uwriteln!(source, "{}", resource.finalize()); + uwriteln!(source, ""); + } + for (_, resource) in exported_resources { uwriteln!(source, "{}", resource.finalize()); uwriteln!(source, ""); } @@ -565,10 +585,11 @@ impl<'a> ScalaJsInterface<'a> { | TypeDefKind::Enum(_) | TypeDefKind::Type(_) | TypeDefKind::Variant(_) => { - let prefix = match self.render_owner(&typ.owner, &typ.kind == &TypeDefKind::Resource) { - Some(owner) => format!("{owner}."), - None => "".to_string(), - }; + let prefix = + match self.render_owner(&typ.owner, &typ.kind == &TypeDefKind::Resource) { + Some(owner) => format!("{owner}."), + None => "".to_string(), + }; format!( "{}{}", prefix, @@ -641,14 +662,14 @@ impl<'a> ScalaJsInterface<'a> { } else { None } - }, + } TypeOwner::Interface(id) => { let iface = &self.resolve.interfaces[*id]; let name = iface.name.clone().expect("Interface must have a name"); let package_id = iface.package.expect("Interface must have a package"); let package = &self.resolve.packages[package_id]; - let mut segments = package_name_to_segments(&self.opts, &package.name); + let mut segments = package_name_to_segments(&self.opts, &package.name, &Import, self.scala_keywords); segments.push(self.scala_keywords.escape(name.to_snake_case())); if is_resource { @@ -671,7 +692,9 @@ impl<'a> ScalaJsInterface<'a> { for field in &record.fields { let typ = self.render_type_reference(&field.ty); let field_name = self.scala_keywords.escape(field.name.to_lower_camel_case()); - let field_name0 = self.scala_keywords.escape(format!("{}0", field.name.to_lower_camel_case())); + let field_name0 = self + .scala_keywords + .escape(format!("{}0", field.name.to_lower_camel_case())); fields.push((field_name, field_name0, typ, &field.docs)); } @@ -708,7 +731,9 @@ impl<'a> ScalaJsInterface<'a> { for flag in &flags.flags { let typ = "Boolean".to_string(); let field_name = self.scala_keywords.escape(flag.name.to_lower_camel_case()); - let field_name0 = self.scala_keywords.escape(format!("{}0", flag.name.to_lower_camel_case())); + let field_name0 = self + .scala_keywords + .escape(format!("{}0", flag.name.to_lower_camel_case())); fields.push((field_name, field_name0, typ, &flag.docs)); } @@ -855,17 +880,17 @@ impl<'a> ScalaJsInterface<'a> { } } -struct ScalaJsResource<'a> { +struct ScalaJsImportedResource<'a> { owner: &'a ScalaJsInterface<'a>, _resource_id: TypeId, resource_name: String, class_header: String, class_source: String, object_source: String, - constructor_args: String + constructor_args: String, } -impl<'a> ScalaJsResource<'a> { +impl<'a> ScalaJsImportedResource<'a> { pub fn new(owner: &'a ScalaJsInterface<'a>, resource_id: TypeId) -> Self { let resource = &owner.resolve.types[resource_id]; let resource_name = resource @@ -876,12 +901,9 @@ impl<'a> ScalaJsResource<'a> { let mut class_header = String::new(); write_doc_comment(&mut class_header, " ", &resource.docs); + uwriteln!(class_header, " @js.native"); - uwrite!( - class_header, - " class {}(", - scala_resource_name - ); + uwrite!(class_header, " class {}(", scala_resource_name); let mut class_source = String::new(); uwriteln!(class_source, ") extends js.Object {{"); @@ -901,7 +923,7 @@ impl<'a> ScalaJsResource<'a> { class_header, class_source, object_source, - constructor_args: String::new() + constructor_args: String::new(), } } @@ -913,7 +935,12 @@ impl<'a> ScalaJsResource<'a> { let ret = self.owner.render_return_type(&func.results); let scala_func_name = self.get_func_name("[method]", func_name); - let overrd = if self.owner.scala_keywords.base_methods.contains(&scala_func_name) { + let overrd = if self + .owner + .scala_keywords + .base_methods + .contains(&scala_func_name) + { "override " } else { "" @@ -965,6 +992,107 @@ impl<'a> ScalaJsResource<'a> { } } +struct ScalaJsExportedResource<'a> { + owner: &'a ScalaJsInterface<'a>, + _resource_id: TypeId, + resource_name: String, + class_header: String, + class_source: String, + static_methods: String, + constructor_args: String, +} + +impl<'a> ScalaJsExportedResource<'a> { + pub fn new(owner: &'a ScalaJsInterface<'a>, resource_id: TypeId) -> Self { + let resource = &owner.resolve.types[resource_id]; + let resource_name = resource + .name + .clone() + .expect("Anonymous resources not supported"); + let scala_resource_name = owner.scala_keywords.escape(resource_name.to_pascal_case()); + + let mut class_header = String::new(); + write_doc_comment(&mut class_header, " ", &resource.docs); + + uwrite!(class_header, " abstract class {}(", scala_resource_name); + + let mut class_source = String::new(); + uwriteln!(class_source, ") extends js.Object {{"); + + Self { + owner, + _resource_id: resource_id, + resource_name, + class_header, + class_source, + static_methods: String::new(), + constructor_args: String::new(), + } + } + + pub fn add_function(&mut self, func_name: &str, func: &Function) { + match &func.kind { + FunctionKind::Freestanding => unreachable!(), + FunctionKind::Method(_) => { + let args = self.owner.render_args(func.params.iter().skip(1)); + let ret = self.owner.render_return_type(&func.results); + let scala_func_name = self.get_func_name("[method]", func_name); + + let overrd = if self + .owner + .scala_keywords + .base_methods + .contains(&scala_func_name) + { + "override " + } else { + "" + }; + + write_doc_comment(&mut self.class_source, " ", &func.docs); + uwriteln!( + self.class_source, + " {overrd}def {scala_func_name}({args}): {ret}" + ); + } + FunctionKind::Static(_) => { + let args = self.owner.render_args(func.params.iter()); + let ret = self.owner.render_return_type(&func.results); + + let scala_func_name = self.get_func_name("[static]", func_name); + write_doc_comment(&mut self.class_source, " ", &func.docs); + uwriteln!( + self.static_methods, + " def {scala_func_name}({args}): {ret}" + ); + } + FunctionKind::Constructor(_) => { + let args = self.owner.render_args(func.params.iter()); + self.constructor_args = args; + } + } + } + + pub fn finalize(self) -> String { + let mut class_source = self.class_header; + uwrite!(class_source, "{}", self.constructor_args); + uwriteln!(class_source, "{}", self.class_source); + uwriteln!(class_source, " }}"); + format!("{}\n{}\n", class_source, self.static_methods) + } + + fn get_func_name(&self, prefix: &str, func_name: &str) -> String { + self.owner.scala_keywords.escape( + func_name + .strip_prefix(prefix) + .unwrap() + .strip_prefix(&self.resource_name) + .unwrap() + .to_lower_camel_case(), + ) + } +} + fn render_runtime_module(opts: &Opts) -> ScalaJsFile { let wit_scala = include_str!("../scala/wit.scala"); @@ -983,14 +1111,24 @@ fn render_runtime_module(opts: &Opts) -> ScalaJsFile { } } -fn package_name_to_segments(opts: &Opts, package_name: &PackageName) -> Vec { +fn package_name_to_segments( + opts: &Opts, + package_name: &PackageName, + direction: &Direction, + keywords: &ScalaKeywords, +) -> Vec { let mut segments = opts.base_package_segments(); + + if direction == &Export { + segments.push("export".to_string()); + } + segments.push(package_name.namespace.to_snake_case()); segments.push(package_name.name.to_snake_case()); if let Some(version) = &package_name.version { segments.push(format!("v{}", version.to_string().to_snake_case())); } - segments + segments.into_iter().map(|s| keywords.escape(s)).collect() } fn write_doc_comment(source: &mut impl Write, indent: &str, docs: &Docs) { From 996a9b409713e4649d9fcb17f357f12f8cf2f471 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sat, 1 Feb 2025 11:19:52 +0100 Subject: [PATCH 04/36] Scala.js binding generator, work in progress --- crates/scalajs/src/lib.rs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index 9838a6da5..443ad0e52 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -198,6 +198,8 @@ struct ScalaJsWorld { generated_files: Vec, keywords: ScalaKeywords, overrides: HashMap, + imports: HashSet, + exports: HashSet } impl ScalaJsWorld { @@ -208,6 +210,8 @@ impl ScalaJsWorld { generated_files: Vec::new(), keywords, overrides: HashMap::new(), + imports: HashSet::new(), + exports: HashSet::new() } } } @@ -223,8 +227,7 @@ impl WorldGenerator for ScalaJsWorld { let key = name; let wit_name = resolve.name_world_key(key); - println!("import_interface: {wit_name}"); - + self.imports.insert(iface); let mut scalajs_iface = ScalaJsInterface::new( wit_name.clone(), resolve, @@ -233,6 +236,8 @@ impl WorldGenerator for ScalaJsWorld { Import, &self.keywords, &mut self.overrides, + &self.imports, + &self.exports ); scalajs_iface.generate(); self.generated_files.push(scalajs_iface.finalize()); @@ -250,8 +255,7 @@ impl WorldGenerator for ScalaJsWorld { let key = name; let wit_name = resolve.name_world_key(key); - println!("export_interface: {:?}", name); - + self.exports.insert(iface); let mut scalajs_iface = ScalaJsInterface::new( wit_name.clone(), resolve, @@ -260,6 +264,8 @@ impl WorldGenerator for ScalaJsWorld { Export, &self.keywords, &mut self.overrides, + &self.imports, + &self.exports ); scalajs_iface.generate(); self.generated_files.push(scalajs_iface.finalize()); @@ -342,9 +348,12 @@ struct ScalaJsInterface<'a> { direction: Direction, scala_keywords: &'a ScalaKeywords, overrides: &'a mut HashMap, + imports: &'a HashSet, + exports: &'a HashSet, } impl<'a> ScalaJsInterface<'a> { + // TODO: should just get a reference to ScalaJsWorld pub fn new( wit_name: String, resolve: &'a Resolve, @@ -353,6 +362,8 @@ impl<'a> ScalaJsInterface<'a> { direction: Direction, scala_keywords: &'a ScalaKeywords, overrides: &'a mut HashMap, + imports: &'a HashSet, + exports: &'a HashSet, ) -> Self { let interface = &resolve.interfaces[interface_id]; let name = interface @@ -380,6 +391,8 @@ impl<'a> ScalaJsInterface<'a> { direction, scala_keywords, overrides, + imports, + exports } } @@ -669,7 +682,17 @@ impl<'a> ScalaJsInterface<'a> { let package_id = iface.package.expect("Interface must have a package"); let package = &self.resolve.packages[package_id]; - let mut segments = package_name_to_segments(&self.opts, &package.name, &Import, self.scala_keywords); + + let direction = if self.imports.contains(id) { + Import + } else if self.exports.contains(id) { + Export + } else { + // Have not seen it yet, so it must be also an export + Export + }; + + let mut segments = package_name_to_segments(&self.opts, &package.name, &direction, self.scala_keywords); segments.push(self.scala_keywords.escape(name.to_snake_case())); if is_resource { From d74996b4a8a6a663de8c1e2f8c0f8ed479bfcf78 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sat, 1 Feb 2025 12:29:48 +0100 Subject: [PATCH 05/36] Scala.js binding generator, work in progress --- crates/scalajs/src/lib.rs | 260 ++++++++++++++++++++++++-------------- 1 file changed, 167 insertions(+), 93 deletions(-) diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index 443ad0e52..d0eb7d2c7 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -199,7 +199,7 @@ struct ScalaJsWorld { keywords: ScalaKeywords, overrides: HashMap, imports: HashSet, - exports: HashSet + exports: HashSet, } impl ScalaJsWorld { @@ -211,7 +211,7 @@ impl ScalaJsWorld { keywords, overrides: HashMap::new(), imports: HashSet::new(), - exports: HashSet::new() + exports: HashSet::new(), } } } @@ -237,7 +237,7 @@ impl WorldGenerator for ScalaJsWorld { &self.keywords, &mut self.overrides, &self.imports, - &self.exports + &self.exports, ); scalajs_iface.generate(); self.generated_files.push(scalajs_iface.finalize()); @@ -265,7 +265,7 @@ impl WorldGenerator for ScalaJsWorld { &self.keywords, &mut self.overrides, &self.imports, - &self.exports + &self.exports, ); scalajs_iface.generate(); self.generated_files.push(scalajs_iface.finalize()); @@ -392,12 +392,88 @@ impl<'a> ScalaJsInterface<'a> { scala_keywords, overrides, imports, - exports + exports, } } pub fn generate(&mut self) { + match self.direction { + Import => self.generate_import(), + Export => self.generate_export(), + } + } + + pub fn generate_import(&mut self) { let mut source = String::new(); + self.generate_package_header(&mut source); + + let types = self.collect_type_definition_snippets(); + let imported_resources = self.collect_imported_resources(); + let functions = self.collect_function_snippets(); + + for typ in types { + uwriteln!(source, "{}", typ); + uwriteln!(source, ""); + } + + write_doc_comment(&mut source, " ", &self.interface.docs); + uwriteln!(source, " @js.native"); + uwriteln!(source, " trait {} extends js.Object {{", self.name); + + for (_, resource) in imported_resources { + uwriteln!(source, "{}", resource.finalize()); + uwriteln!(source, ""); + } + + for function in functions { + uwriteln!(source, "{function}"); + } + + uwriteln!(source, " }}"); + + uwriteln!(source, ""); + uwriteln!(source, " @js.native"); + uwriteln!( + source, + " @JSImport(\"{}\", JSImport.Namespace)", + self.wit_name + ); + uwriteln!(source, " object {} extends {}", self.name, self.name); + + uwriteln!(source, "}}"); + self.source = source; + } + + pub fn generate_export(&mut self) { + let mut source = String::new(); + self.generate_package_header(&mut source); + + let types = self.collect_type_definition_snippets(); + let exported_resources = self.collect_exported_resources(); + let functions = self.collect_function_snippets(); + + for typ in types { + uwriteln!(source, "{}", typ); + uwriteln!(source, ""); + } + + for (_, resource) in exported_resources { + uwriteln!(source, "{}", resource.finalize()); + uwriteln!(source, ""); + } + + write_doc_comment(&mut source, " ", &self.interface.docs); + uwriteln!(source, " trait {} {{", self.name); + for function in functions { + uwriteln!(source, "{function}"); + } + uwriteln!(source, " }}"); + + uwriteln!(source, "}}"); + self.source = source; + } + + fn generate_package_header(&mut self, source: &mut String) { uwriteln!(source, "package {}", self.package.join(".")); uwriteln!(source, ""); uwriteln!(source, "import scala.scalajs.js"); @@ -410,11 +486,10 @@ impl<'a> ScalaJsInterface<'a> { "package object {} {{", self.scala_keywords.escape(self.name.to_snake_case()) ); + } + fn collect_type_definition_snippets(&mut self) -> Vec { let mut types = Vec::new(); - let mut functions = Vec::new(); - let mut imported_resources = HashMap::new(); - let mut exported_resources = HashMap::new(); for (type_name, type_id) in &self.interface.types { let type_def = &self.resolve.types[*type_id]; @@ -434,24 +509,69 @@ impl<'a> ScalaJsInterface<'a> { } } + types + } + + fn collect_imported_resources(&self) -> HashMap { + let mut imported_resources = HashMap::new(); for (_, type_id) in &self.interface.types { let type_def = &self.resolve.types[*type_id]; if let TypeDefKind::Resource = &type_def.kind { - match &self.direction { - Import => { - imported_resources - .entry(*type_id) - .or_insert_with(|| ScalaJsImportedResource::new(self, *type_id)); - } - Export => { - exported_resources - .entry(*type_id) - .or_insert_with(|| ScalaJsExportedResource::new(self, *type_id)); - } + imported_resources + .entry(*type_id) + .or_insert_with(|| ScalaJsImportedResource::new(self, *type_id)); + } + } + + for (func_name, func) in &self.interface.functions { + match func.kind { + FunctionKind::Method(resource_type) + | FunctionKind::Static(resource_type) + | FunctionKind::Constructor(resource_type) => { + let resource = imported_resources + .entry(resource_type) + .or_insert_with(|| ScalaJsImportedResource::new(self, resource_type)); + resource.add_function(func_name, func); } + FunctionKind::Freestanding => {} + } + } + + imported_resources + } + + fn collect_exported_resources(&self) -> HashMap { + let mut exported_resources = HashMap::new(); + + for (_, type_id) in &self.interface.types { + let type_def = &self.resolve.types[*type_id]; + if let TypeDefKind::Resource = &type_def.kind { + exported_resources + .entry(*type_id) + .or_insert_with(|| ScalaJsExportedResource::new(self, *type_id)); } } + for (func_name, func) in &self.interface.functions { + match func.kind { + FunctionKind::Method(resource_type) + | FunctionKind::Static(resource_type) + | FunctionKind::Constructor(resource_type) => { + let resource = exported_resources + .entry(resource_type) + .or_insert_with(|| ScalaJsExportedResource::new(self, resource_type)); + resource.add_function(func_name, func); + } + FunctionKind::Freestanding => {} + } + } + + exported_resources + } + + fn collect_function_snippets(&self) -> Vec { + let mut functions = Vec::new(); + for (func_name, func) in &self.interface.functions { let scala_func_name = self.scala_keywords.escape(func_name.to_lower_camel_case()); @@ -479,67 +599,13 @@ impl<'a> ScalaJsInterface<'a> { ); functions.push(function); } - FunctionKind::Method(resource_type) - | FunctionKind::Static(resource_type) - | FunctionKind::Constructor(resource_type) => { - match &self.direction { - Import => { - let resource = imported_resources - .entry(resource_type) - .or_insert_with(|| ScalaJsImportedResource::new(self, resource_type)); - resource.add_function(func_name, func); - } - Export => { - let resource = exported_resources - .entry(resource_type) - .or_insert_with(|| ScalaJsExportedResource::new(self, resource_type)); - resource.add_function(func_name, func); - } - } - } + FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::Constructor(_) => {} } } - for typ in types { - uwriteln!(source, "{}", typ); - uwriteln!(source, ""); - } - - write_doc_comment(&mut source, " ", &self.interface.docs); - if self.direction == Import { - uwriteln!(source, " @js.native"); - uwriteln!(source, " trait {} extends js.Object {{", self.name); - } else { - uwriteln!(source, " trait {} {{", self.name); - } - for (_, resource) in imported_resources { - uwriteln!(source, "{}", resource.finalize()); - uwriteln!(source, ""); - } - for (_, resource) in exported_resources { - uwriteln!(source, "{}", resource.finalize()); - uwriteln!(source, ""); - } - - for function in functions { - uwriteln!(source, "{function}"); - } - - uwriteln!(source, " }}"); - - if self.direction == Import { - uwriteln!(source, ""); - uwriteln!(source, " @js.native"); - uwriteln!( - source, - " @JSImport(\"{}\", JSImport.Namespace)", - self.wit_name - ); - uwriteln!(source, " object {} extends {}", self.name, self.name); - } - uwriteln!(source, "}}"); - - self.source = source; + functions } pub fn finalize(self) -> ScalaJsFile { @@ -670,7 +736,7 @@ impl<'a> ScalaJsInterface<'a> { ) } TypeOwner::Interface(id) if id == &self.interface_id => { - if is_resource { + if is_resource && self.direction == Import { Some(self.name.clone()) } else { None @@ -682,20 +748,17 @@ impl<'a> ScalaJsInterface<'a> { let package_id = iface.package.expect("Interface must have a package"); let package = &self.resolve.packages[package_id]; + let direction = self.interface_direction(id); - let direction = if self.imports.contains(id) { - Import - } else if self.exports.contains(id) { - Export - } else { - // Have not seen it yet, so it must be also an export - Export - }; - - let mut segments = package_name_to_segments(&self.opts, &package.name, &direction, self.scala_keywords); + let mut segments = package_name_to_segments( + &self.opts, + &package.name, + &direction, + self.scala_keywords, + ); segments.push(self.scala_keywords.escape(name.to_snake_case())); - if is_resource { + if is_resource && direction == Import { segments.push(self.scala_keywords.escape(name.to_pascal_case())); } @@ -705,6 +768,17 @@ impl<'a> ScalaJsInterface<'a> { } } + fn interface_direction(&self, id: &InterfaceId) -> Direction { + if self.imports.contains(id) { + Import + } else if self.exports.contains(id) { + Export + } else { + // Have not seen it yet, so it must be also an export + Export + } + } + fn render_typedef(&self, name: &str, typ: &TypeDef) -> Option { let scala_name = self.scala_keywords.escape(name.to_pascal_case()); @@ -1037,7 +1111,7 @@ impl<'a> ScalaJsExportedResource<'a> { let mut class_header = String::new(); write_doc_comment(&mut class_header, " ", &resource.docs); - uwrite!(class_header, " abstract class {}(", scala_resource_name); + uwrite!(class_header, " abstract class {}(", scala_resource_name); let mut class_source = String::new(); uwriteln!(class_source, ") extends js.Object {{"); @@ -1075,7 +1149,7 @@ impl<'a> ScalaJsExportedResource<'a> { write_doc_comment(&mut self.class_source, " ", &func.docs); uwriteln!( self.class_source, - " {overrd}def {scala_func_name}({args}): {ret}" + " {overrd}def {scala_func_name}({args}): {ret}" ); } FunctionKind::Static(_) => { @@ -1086,7 +1160,7 @@ impl<'a> ScalaJsExportedResource<'a> { write_doc_comment(&mut self.class_source, " ", &func.docs); uwriteln!( self.static_methods, - " def {scala_func_name}({args}): {ret}" + " def {scala_func_name}({args}): {ret}" ); } FunctionKind::Constructor(_) => { @@ -1100,7 +1174,7 @@ impl<'a> ScalaJsExportedResource<'a> { let mut class_source = self.class_header; uwrite!(class_source, "{}", self.constructor_args); uwriteln!(class_source, "{}", self.class_source); - uwriteln!(class_source, " }}"); + uwriteln!(class_source, " }}"); format!("{}\n{}\n", class_source, self.static_methods) } From e9744a4e880571b84cf1fba42f61fba40847b57a Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sat, 1 Feb 2025 13:05:57 +0100 Subject: [PATCH 06/36] Scala.js binding generator, work in progress --- crates/scalajs/src/lib.rs | 51 ++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index d0eb7d2c7..83eff17f6 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -4,7 +4,7 @@ use std::fmt::{Display, Write}; use std::str::FromStr; use wit_bindgen_core::wit_parser::{ Docs, Function, FunctionKind, Handle, Interface, InterfaceId, PackageName, Resolve, Results, - Type, TypeDef, TypeDefKind, TypeId, TypeOwner, WorldId, WorldKey, + Tuple, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, WorldId, WorldKey, }; use wit_bindgen_core::Direction::{Export, Import}; use wit_bindgen_core::{uwrite, uwriteln, Direction, Files, WorldGenerator}; @@ -579,11 +579,7 @@ impl<'a> ScalaJsInterface<'a> { FunctionKind::Freestanding => { let args = self.render_args(func.params.iter()); - let ret = match &func.results { - Results::Named(params) if params.len() == 0 => "Unit".to_string(), - Results::Named(_) => panic!("Named results not supported yet"), // TODO - Results::Anon(typ) => self.render_type_reference(typ), - }; + let ret = self.render_return_type(&func.results); let mut function = String::new(); write_doc_comment(&mut function, " ", &func.docs); @@ -628,8 +624,13 @@ impl<'a> ScalaJsInterface<'a> { fn render_return_type(&self, results: &Results) -> String { match results { - Results::Named(params) if params.len() == 0 => "Unit".to_string(), - Results::Named(_) => panic!("Named results not supported yet"), // TODO + Results::Named(results) if results.len() == 0 => "Unit".to_string(), + Results::Named(results) if results.len() == 1 => { + self.render_type_reference(&results.iter().next().unwrap().1) + } + Results::Named(results) => self.render_tuple(&Tuple { + types: results.iter().map(|(_, typ)| typ.clone()).collect(), + }), Results::Anon(typ) => self.render_type_reference(typ), } } @@ -688,15 +689,7 @@ impl<'a> ScalaJsInterface<'a> { let typ = &self.resolve.types[*id]; self.render_typedef_reference(typ) } - TypeDefKind::Tuple(tuple) => { - let arity = tuple.types.len(); - - let mut parts = Vec::new(); - for part in &tuple.types { - parts.push(self.render_type_reference(part)); - } - format!("WitTuple{arity}[{}]", parts.join(", ")) - } + TypeDefKind::Tuple(tuple) => self.render_tuple(tuple), TypeDefKind::Option(option) => { if !maybe_null(&self.resolve, option) { format!("Nullable[{}]", self.render_type_reference(option)) @@ -725,6 +718,16 @@ impl<'a> ScalaJsInterface<'a> { } } + fn render_tuple(&self, tuple: &Tuple) -> String { + let arity = tuple.types.len(); + + let mut parts = Vec::new(); + for part in &tuple.types { + parts.push(self.render_type_reference(part)); + } + format!("WitTuple{arity}[{}]", parts.join(", ")) + } + fn render_owner(&self, owner: &TypeOwner, is_resource: bool) -> Option { match owner { TypeOwner::World(id) => { @@ -1158,9 +1161,10 @@ impl<'a> ScalaJsExportedResource<'a> { let scala_func_name = self.get_func_name("[static]", func_name); write_doc_comment(&mut self.class_source, " ", &func.docs); + uwriteln!(self.static_methods, " // @JSExportStatic"); uwriteln!( self.static_methods, - " def {scala_func_name}({args}): {ret}" + " def {scala_func_name}({args}): {ret}" ); } FunctionKind::Constructor(_) => { @@ -1175,7 +1179,16 @@ impl<'a> ScalaJsExportedResource<'a> { uwrite!(class_source, "{}", self.constructor_args); uwriteln!(class_source, "{}", self.class_source); uwriteln!(class_source, " }}"); - format!("{}\n{}\n", class_source, self.static_methods) + uwriteln!(class_source, ""); + + let scala_resource_name = self + .owner + .scala_keywords + .escape(self.resource_name.to_pascal_case()); + uwriteln!(class_source, " trait {}Static {{", scala_resource_name); + uwriteln!(class_source, "{}", self.static_methods); + uwriteln!(class_source, " }}"); + class_source } fn get_func_name(&self, prefix: &str, func_name: &str) -> String { From 10f48c97f72eb176460fd12c9263fac07b56cd34 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sat, 1 Feb 2025 14:12:18 +0100 Subject: [PATCH 07/36] Scala.js binding generator, work in progress --- crates/scalajs/src/jco.rs | 168 ++++++++++++++++++++++++++++++++++ crates/scalajs/src/lib.rs | 187 ++++++++++++++++++++------------------ 2 files changed, 269 insertions(+), 86 deletions(-) create mode 100644 crates/scalajs/src/jco.rs diff --git a/crates/scalajs/src/jco.rs b/crates/scalajs/src/jco.rs new file mode 100644 index 000000000..a934b0d15 --- /dev/null +++ b/crates/scalajs/src/jco.rs @@ -0,0 +1,168 @@ +// Code copied from jco + +use heck::ToLowerCamelCase; +use wit_bindgen_core::wit_parser::{Resolve, Type, TypeDefKind}; + +/// Tests whether `ty` can be represented with `null`, and if it can then +/// the "other type" is returned. If `Some` is returned that means that `ty` +/// is `null | `. If `None` is returned that means that `null` can't +/// be used to represent `ty`. +pub fn as_nullable<'a>(resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> { + let id = match ty { + Type::Id(id) => *id, + _ => return None, + }; + match &resolve.types[id].kind { + // If `ty` points to an `option`, then `ty` can be represented + // with `null` if `t` itself can't be represented with null. For + // example `option>` can't be represented with `null` + // since that's ambiguous if it's `none` or `some(none)`. + // + // Note, oddly enough, that `option>>` can be + // represented as `null` since: + // + // * `null` => `none` + // * `{ tag: "none" }` => `some(none)` + // * `{ tag: "some", val: null }` => `some(some(none))` + // * `{ tag: "some", val: 1 }` => `some(some(some(1)))` + // + // It's doubtful anyone would actually rely on that though due to + // how confusing it is. + TypeDefKind::Option(t) => { + if !maybe_null(resolve, t) { + Some(t) + } else { + None + } + } + TypeDefKind::Type(t) => as_nullable(resolve, t), + _ => None, + } +} + +pub fn maybe_null(resolve: &Resolve, ty: &Type) -> bool { + as_nullable(resolve, ty).is_some() +} + +// Convert an arbitrary string to a similar close js identifier +pub fn to_js_identifier(goal_name: &str) -> String { + if is_js_identifier(goal_name) { + goal_name.to_string() + } else { + let goal = goal_name.to_lower_camel_case(); + let mut identifier = String::new(); + for char in goal.chars() { + let valid_char = if identifier.is_empty() { + is_js_identifier_start(char) + } else { + is_js_identifier_char(char) + }; + if valid_char { + identifier.push(char); + } else { + identifier.push(match char { + '.' => '_', + _ => '$', + }); + } + } + if !is_js_identifier(&identifier) { + identifier = format!("_{identifier}"); + if !is_js_identifier(&identifier) { + panic!("Unable to generate valid identifier {identifier} for '{goal_name}'"); + } + } + identifier + } +} + +pub fn is_js_identifier(s: &str) -> bool { + let mut chars = s.chars(); + if let Some(char) = chars.next() { + if !is_js_identifier_start(char) { + return false; + } + } else { + return false; + } + for char in chars { + if !is_js_identifier_char(char) { + return false; + } + } + !is_js_reserved_word(&s) +} + +pub fn is_js_reserved_word(s: &str) -> bool { + RESERVED_KEYWORDS.binary_search(&s).is_ok() +} + +// https://tc39.es/ecma262/#prod-IdentifierStartChar +// Unicode ID_Start | "$" | "_" +fn is_js_identifier_start(code: char) -> bool { + match code { + 'A'..='Z' | 'a'..='z' | '$' | '_' => true, + // leaving out non-ascii for now... + _ => false, + } +} + +// https://tc39.es/ecma262/#prod-IdentifierPartChar +// Unicode ID_Continue | "$" | U+200C | U+200D +fn is_js_identifier_char(code: char) -> bool { + match code { + '0'..='9' | 'A'..='Z' | 'a'..='z' | '$' | '_' => true, + // leaving out non-ascii for now... + _ => false, + } +} + +pub(crate) const RESERVED_KEYWORDS: &[&str] = &[ + "await", + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "eval", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "implements", + "import", + "in", + "instanceof", + "interface", + "let", + "new", + "null", + "package", + "private", + "protected", + "public", + "return", + "static", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "yield", +]; diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index 83eff17f6..2dc6ff674 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -1,3 +1,6 @@ +mod jco; + +use crate::jco::{maybe_null, to_js_identifier}; use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Write}; @@ -9,8 +12,6 @@ use wit_bindgen_core::wit_parser::{ use wit_bindgen_core::Direction::{Export, Import}; use wit_bindgen_core::{uwrite, uwriteln, Direction, Files, WorldGenerator}; -// TODO: need to use https://github.com/golemcloud/jco/blob/main/crates/js-component-bindgen/src/names.rs#L81 for every name, and use JSName where it differs from the scala name - #[derive(Debug, Clone)] pub enum ScalaDialect { Scala2, @@ -463,7 +464,7 @@ impl<'a> ScalaJsInterface<'a> { } write_doc_comment(&mut source, " ", &self.interface.docs); - uwriteln!(source, " trait {} {{", self.name); + uwriteln!(source, " trait {} extends js.Object {{", self.name); for function in functions { uwriteln!(source, "{function}"); } @@ -573,7 +574,8 @@ impl<'a> ScalaJsInterface<'a> { let mut functions = Vec::new(); for (func_name, func) in &self.interface.functions { - let scala_func_name = self.scala_keywords.escape(func_name.to_lower_camel_case()); + let func_name = + self.encode_name(func_name.to_lower_camel_case()); match func.kind { FunctionKind::Freestanding => { @@ -589,9 +591,11 @@ impl<'a> ScalaJsInterface<'a> { Export => "", }; + func_name.write_rename_attribute(&mut function, " "); uwriteln!( function, - " def {scala_func_name}({args}): {ret}{postfix}" + " def {}({args}): {ret}{postfix}", + func_name.scala ); functions.push(function); } @@ -616,8 +620,9 @@ impl<'a> ScalaJsInterface<'a> { let mut args = Vec::new(); for (param_name, param_typ) in params { let param_typ = self.render_type_reference(param_typ); - let param_name = self.scala_keywords.escape(param_name.to_lower_camel_case()); - args.push(format!("{param_name}: {param_typ}")); + let param_name = + self.encode_name(param_name.to_lower_camel_case()); + args.push(format!("{}: {param_typ}", param_name.scala)); } args.join(", ") } @@ -783,7 +788,8 @@ impl<'a> ScalaJsInterface<'a> { } fn render_typedef(&self, name: &str, typ: &TypeDef) -> Option { - let scala_name = self.scala_keywords.escape(name.to_pascal_case()); + let encoded_name = self.encode_name(name.to_pascal_case()); + let scala_name = encoded_name.scala; let mut source = String::new(); match &typ.kind { @@ -791,7 +797,8 @@ impl<'a> ScalaJsInterface<'a> { let mut fields = Vec::new(); for field in &record.fields { let typ = self.render_type_reference(&field.ty); - let field_name = self.scala_keywords.escape(field.name.to_lower_camel_case()); + let field_name = + self.encode_name(field.name.to_lower_camel_case()); let field_name0 = self .scala_keywords .escape(format!("{}0", field.name.to_lower_camel_case())); @@ -802,7 +809,8 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); for (field_name, _, typ, docs) in &fields { write_doc_comment(&mut source, " ", &docs); - uwriteln!(source, " val {field_name}: {typ}"); + field_name.write_rename_attribute(&mut source, " "); + uwriteln!(source, " val {}: {typ}", field_name.scala); } uwriteln!(source, " }}"); uwriteln!(source, ""); @@ -814,7 +822,8 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, " ): {scala_name} = {{"); uwriteln!(source, " new {scala_name} {{"); for (field_name, field_name0, typ, _) in &fields { - uwriteln!(source, " val {field_name}: {typ} = {field_name0}"); + field_name.write_rename_attribute(&mut source, " "); + uwriteln!(source, " val {}: {typ} = {field_name0}", field_name.scala); } uwriteln!(source, " }}"); uwriteln!(source, " }}"); @@ -830,7 +839,7 @@ impl<'a> ScalaJsInterface<'a> { let mut fields = Vec::new(); for flag in &flags.flags { let typ = "Boolean".to_string(); - let field_name = self.scala_keywords.escape(flag.name.to_lower_camel_case()); + let field_name = self.encode_name(flag.name.to_lower_camel_case()); let field_name0 = self .scala_keywords .escape(format!("{}0", flag.name.to_lower_camel_case())); @@ -841,7 +850,8 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); for (field_name, _, typ, docs) in &fields { write_doc_comment(&mut source, " ", docs); - uwriteln!(source, " val {field_name}: {typ}"); + field_name.write_rename_attribute(&mut source, " "); + uwriteln!(source, " val {}: {typ}", field_name.scala); } uwriteln!(source, " }}"); uwriteln!(source, ""); @@ -853,7 +863,8 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, " ): {scala_name} = {{"); uwriteln!(source, " new {scala_name} {{"); for (field_name, field_name0, typ, _) in &fields { - uwriteln!(source, " val {field_name}: {typ} = {field_name0}"); + field_name.write_rename_attribute(&mut source, " "); + uwriteln!(source, " val {}: {typ} = {field_name0}", field_name.scala); } uwriteln!(source, " }}"); uwriteln!(source, " }}"); @@ -978,6 +989,23 @@ impl<'a> ScalaJsInterface<'a> { None } } + + fn encode_name(&self, name: impl AsRef) -> EncodedName { + let name = name.as_ref(); + let scala_name = self.scala_keywords.escape(name); + let js_name = to_js_identifier(name); + + let rename_attribute = if scala_name != js_name && scala_name != format!("`{js_name}`") { + format!("@JSName(\"{js_name}\")") + } else { + "".to_string() + }; + EncodedName { + scala: scala_name, + js: js_name, + rename_attribute, + } + } } struct ScalaJsImportedResource<'a> { @@ -997,13 +1025,15 @@ impl<'a> ScalaJsImportedResource<'a> { .name .clone() .expect("Anonymous resources not supported"); - let scala_resource_name = owner.scala_keywords.escape(resource_name.to_pascal_case()); + let encoded_resource_name = + owner.encode_name(resource_name.to_pascal_case()); let mut class_header = String::new(); write_doc_comment(&mut class_header, " ", &resource.docs); uwriteln!(class_header, " @js.native"); - uwrite!(class_header, " class {}(", scala_resource_name); + encoded_resource_name.write_rename_attribute(&mut class_header, " "); + uwrite!(class_header, " class {}(", encoded_resource_name.scala); let mut class_source = String::new(); uwriteln!(class_source, ") extends js.Object {{"); @@ -1013,7 +1043,7 @@ impl<'a> ScalaJsImportedResource<'a> { uwriteln!( object_source, " object {} extends js.Object {{", - scala_resource_name + encoded_resource_name.scala ); Self { @@ -1033,13 +1063,14 @@ impl<'a> ScalaJsImportedResource<'a> { FunctionKind::Method(_) => { let args = self.owner.render_args(func.params.iter().skip(1)); let ret = self.owner.render_return_type(&func.results); - let scala_func_name = self.get_func_name("[method]", func_name); + let encoded_func_name = + self.get_func_name("[method]", func_name); let overrd = if self .owner .scala_keywords .base_methods - .contains(&scala_func_name) + .contains(&encoded_func_name.scala) { "override " } else { @@ -1047,20 +1078,24 @@ impl<'a> ScalaJsImportedResource<'a> { }; write_doc_comment(&mut self.class_source, " ", &func.docs); + encoded_func_name.write_rename_attribute(&mut self.class_source, " "); uwriteln!( self.class_source, - " {overrd}def {scala_func_name}({args}): {ret} = js.native" + " {overrd}def {}({args}): {ret} = js.native", + encoded_func_name.scala ); } FunctionKind::Static(_) => { let args = self.owner.render_args(func.params.iter()); let ret = self.owner.render_return_type(&func.results); - let scala_func_name = self.get_func_name("[static]", func_name); - write_doc_comment(&mut self.class_source, " ", &func.docs); + let encoded_func_name = self.get_func_name("[static]", func_name); + write_doc_comment(&mut self.object_source, " ", &func.docs); + encoded_func_name.write_rename_attribute(&mut self.object_source, " "); uwriteln!( self.object_source, - " def {scala_func_name}({args}): {ret} = js.native" + " def {}({args}): {ret} = js.native", + encoded_func_name.scala ); } FunctionKind::Constructor(_) => { @@ -1080,15 +1115,14 @@ impl<'a> ScalaJsImportedResource<'a> { format!("{}\n{}\n", class_source, object_source) } - fn get_func_name(&self, prefix: &str, func_name: &str) -> String { - self.owner.scala_keywords.escape( - func_name - .strip_prefix(prefix) - .unwrap() - .strip_prefix(&self.resource_name) - .unwrap() - .to_lower_camel_case(), - ) + fn get_func_name(&self, prefix: &str, func_name: &str) -> EncodedName { + let name = func_name + .strip_prefix(prefix) + .unwrap() + .strip_prefix(&self.resource_name) + .unwrap() + .to_lower_camel_case(); + self.owner.encode_name(name) } } @@ -1109,12 +1143,14 @@ impl<'a> ScalaJsExportedResource<'a> { .name .clone() .expect("Anonymous resources not supported"); - let scala_resource_name = owner.scala_keywords.escape(resource_name.to_pascal_case()); + let encoded_resource_name = + owner.encode_name(resource_name.to_pascal_case()); let mut class_header = String::new(); write_doc_comment(&mut class_header, " ", &resource.docs); - uwrite!(class_header, " abstract class {}(", scala_resource_name); + encoded_resource_name.write_rename_attribute(&mut class_header, " // "); + uwrite!(class_header, " abstract class {}(", encoded_resource_name.scala); let mut class_source = String::new(); uwriteln!(class_source, ") extends js.Object {{"); @@ -1136,13 +1172,14 @@ impl<'a> ScalaJsExportedResource<'a> { FunctionKind::Method(_) => { let args = self.owner.render_args(func.params.iter().skip(1)); let ret = self.owner.render_return_type(&func.results); - let scala_func_name = self.get_func_name("[method]", func_name); + let encoded_func_name = + self.get_func_name("[method]", func_name); let overrd = if self .owner .scala_keywords .base_methods - .contains(&scala_func_name) + .contains(&encoded_func_name.scala) { "override " } else { @@ -1150,21 +1187,26 @@ impl<'a> ScalaJsExportedResource<'a> { }; write_doc_comment(&mut self.class_source, " ", &func.docs); + encoded_func_name.write_rename_attribute(&mut self.class_source, " "); uwriteln!( self.class_source, - " {overrd}def {scala_func_name}({args}): {ret}" + " {overrd}def {}({args}): {ret}", + encoded_func_name.scala ); } FunctionKind::Static(_) => { let args = self.owner.render_args(func.params.iter()); let ret = self.owner.render_return_type(&func.results); - let scala_func_name = self.get_func_name("[static]", func_name); - write_doc_comment(&mut self.class_source, " ", &func.docs); + let encoded_func_name = + self.get_func_name("[static]", func_name); + write_doc_comment(&mut self.static_methods, " ", &func.docs); uwriteln!(self.static_methods, " // @JSExportStatic"); + encoded_func_name.write_rename_attribute(&mut self.static_methods, " "); uwriteln!( self.static_methods, - " def {scala_func_name}({args}): {ret}" + " def {}({args}): {ret}", + encoded_func_name.scala ); } FunctionKind::Constructor(_) => { @@ -1191,15 +1233,14 @@ impl<'a> ScalaJsExportedResource<'a> { class_source } - fn get_func_name(&self, prefix: &str, func_name: &str) -> String { - self.owner.scala_keywords.escape( - func_name - .strip_prefix(prefix) - .unwrap() - .strip_prefix(&self.resource_name) - .unwrap() - .to_lower_camel_case(), - ) + fn get_func_name(&self, prefix: &str, func_name: &str) -> EncodedName { + let name = func_name + .strip_prefix(prefix) + .unwrap() + .strip_prefix(&self.resource_name) + .unwrap() + .to_lower_camel_case(); + self.owner.encode_name(name) } } @@ -1252,43 +1293,17 @@ fn write_doc_comment(source: &mut impl Write, indent: &str, docs: &Docs) { } } -/// Tests whether `ty` can be represented with `null`, and if it can then -/// the "other type" is returned. If `Some` is returned that means that `ty` -/// is `null | `. If `None` is returned that means that `null` can't -/// be used to represent `ty`. -pub fn as_nullable<'a>(resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> { - let id = match ty { - Type::Id(id) => *id, - _ => return None, - }; - match &resolve.types[id].kind { - // If `ty` points to an `option`, then `ty` can be represented - // with `null` if `t` itself can't be represented with null. For - // example `option>` can't be represented with `null` - // since that's ambiguous if it's `none` or `some(none)`. - // - // Note, oddly enough, that `option>>` can be - // represented as `null` since: - // - // * `null` => `none` - // * `{ tag: "none" }` => `some(none)` - // * `{ tag: "some", val: null }` => `some(some(none))` - // * `{ tag: "some", val: 1 }` => `some(some(some(1)))` - // - // It's doubtful anyone would actually rely on that though due to - // how confusing it is. - TypeDefKind::Option(t) => { - if !maybe_null(resolve, t) { - Some(t) - } else { - None - } - } - TypeDefKind::Type(t) => as_nullable(resolve, t), - _ => None, - } +#[allow(dead_code)] +struct EncodedName { + pub scala: String, + pub js: String, + pub rename_attribute: String, } -pub fn maybe_null(resolve: &Resolve, ty: &Type) -> bool { - as_nullable(resolve, ty).is_some() +impl EncodedName { + pub fn write_rename_attribute(&self, target: &mut impl Write, ident: &str) { + if self.rename_attribute.len() > 0 { + uwriteln!(target, "{}{}", ident, self.rename_attribute); + } + } } From b36297d84b12d8de406f66cc1e3d1aa0ef16503e Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sat, 1 Feb 2025 19:22:57 +0100 Subject: [PATCH 08/36] Scala.js binding generator, work in progress --- crates/scalajs/src/lib.rs | 667 ++++++++++++++++++++++---------------- 1 file changed, 379 insertions(+), 288 deletions(-) diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index 2dc6ff674..990355622 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -63,6 +63,8 @@ pub struct Opts { ) )] pub scala_dialect: ScalaDialect, + + // TODO: generate skeleton mode - single file with the exported things to be implemented - destructive, will be wired to an explicit sbt command } impl Opts { @@ -194,6 +196,31 @@ impl ScalaKeywords { } } +// TODO: refactor to context, include resolve +trait OwnerContext { + fn is_local_import(&self, id: &InterfaceId, is_resource: bool) -> Option>; +} + +impl OwnerContext for ScalaJsWorld { + fn is_local_import(&self, _id: &InterfaceId, _is_resource: bool) -> Option> { + None + } +} + +impl<'a> OwnerContext for ScalaJsInterface<'a> { + fn is_local_import(&self, id: &InterfaceId, is_resource: bool) -> Option> { + if id == &self.interface_id { + if is_resource && self.direction == Import { + Some(Some(self.name.clone())) + } else { + Some(None) + } + } else { + None + } + } +} + struct ScalaJsWorld { opts: Opts, generated_files: Vec, @@ -201,6 +228,7 @@ struct ScalaJsWorld { overrides: HashMap, imports: HashSet, exports: HashSet, + world_defs: HashMap, } impl ScalaJsWorld { @@ -213,6 +241,194 @@ impl ScalaJsWorld { overrides: HashMap::new(), imports: HashSet::new(), exports: HashSet::new(), + world_defs: HashMap::new(), + } + } + + pub fn encode_name(&self, name: impl AsRef) -> EncodedName { + let name = name.as_ref(); + let scala_name = self.keywords.escape(name); + let js_name = to_js_identifier(name); + + let rename_attribute = if scala_name != js_name && scala_name != format!("`{js_name}`") { + format!("@JSName(\"{js_name}\")") + } else { + "".to_string() + }; + EncodedName { + scala: scala_name, + js: js_name, + rename_attribute, + } + } + + fn render_args<'b>(&self, owner_context: &impl OwnerContext, resolve: &Resolve, params: impl Iterator) -> String { + let mut args = Vec::new(); + for (param_name, param_typ) in params { + let param_typ = self.render_type_reference(owner_context, resolve, param_typ); + let param_name = self.encode_name(param_name.to_lower_camel_case()); + args.push(format!("{}: {param_typ}", param_name.scala)); + } + args.join(", ") + } + + fn render_return_type(&self, owner_context: &impl OwnerContext, resolve: &Resolve, results: &Results) -> String { + match results { + Results::Named(results) if results.len() == 0 => "Unit".to_string(), + Results::Named(results) if results.len() == 1 => { + self.render_type_reference(owner_context, resolve, &results.iter().next().unwrap().1) + } + Results::Named(results) => self.render_tuple(owner_context, resolve, &Tuple { + types: results.iter().map(|(_, typ)| typ.clone()).collect(), + }), + Results::Anon(typ) => self.render_type_reference(owner_context, resolve, typ), + } + } + + fn render_type_reference(&self, owner_context: &impl OwnerContext, resolve: &Resolve, typ: &Type) -> String { + match typ { + Type::Bool => "Boolean".to_string(), + Type::U8 => "Byte".to_string(), + Type::U16 => "Short".to_string(), + Type::U32 => "Int".to_string(), + Type::U64 => "Long".to_string(), + Type::S8 => "Byte".to_string(), + Type::S16 => "Short".to_string(), + Type::S32 => "Int".to_string(), + Type::S64 => "Long".to_string(), + Type::F32 => "Float".to_string(), + Type::F64 => "Double".to_string(), + Type::Char => "Char".to_string(), + Type::String => "String".to_string(), + Type::Id(id) => { + let typ = &resolve.types[*id]; + self.render_typedef_reference(owner_context, resolve, typ) + } + } + } + + fn render_typedef_reference(&self, owner_context: &impl OwnerContext, resolve: &Resolve, typ: &TypeDef) -> String { + match &typ.kind { + TypeDefKind::Record(_) + | TypeDefKind::Resource + | TypeDefKind::Flags(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Type(_) + | TypeDefKind::Variant(_) => { + let prefix = + match self.render_owner(owner_context, resolve, &typ.owner, &typ.kind == &TypeDefKind::Resource) { + Some(owner) => format!("{owner}."), + None => "".to_string(), + }; + format!( + "{}{}", + prefix, + self.keywords.escape( + typ.name + .clone() + .expect("Anonymous types are not supported") + .to_pascal_case() + ) + ) + } + TypeDefKind::Handle(handle) => { + let id = match handle { + Handle::Own(id) => id, + Handle::Borrow(id) => id, + }; + let typ = &resolve.types[*id]; + self.render_typedef_reference(owner_context, resolve, typ) + } + TypeDefKind::Tuple(tuple) => self.render_tuple(owner_context, resolve, tuple), + TypeDefKind::Option(option) => { + if !maybe_null(resolve, option) { + format!("Nullable[{}]", self.render_type_reference(owner_context, resolve, option)) + } else { + format!("WitOption[{}]", self.render_type_reference(owner_context, resolve, option)) + } + } + TypeDefKind::Result(result) => { + let ok = result + .ok + .map(|ok| self.render_type_reference(owner_context, resolve, &ok)) + .unwrap_or("Unit".to_string()); + let err = result + .err + .map(|err| self.render_type_reference(owner_context, resolve, &err)) + .unwrap_or("Unit".to_string()); + format!("WitResult[{ok}, {err}]") + } + TypeDefKind::List(list) => { + format!("WitList[{}]", self.render_type_reference(owner_context, resolve, list)) + } + TypeDefKind::Future(_) => panic!("Futures not supported yet"), + TypeDefKind::Stream(_) => panic!("Streams not supported yet"), + TypeDefKind::ErrorContext => panic!("ErrorContext not supported yet"), + TypeDefKind::Unknown => panic!("Unknown type"), + } + } + + fn render_tuple(&self, owner_context: &impl OwnerContext, resolve: &Resolve, tuple: &Tuple) -> String { + let arity = tuple.types.len(); + + let mut parts = Vec::new(); + for part in &tuple.types { + parts.push(self.render_type_reference(owner_context, resolve, part)); + } + format!("WitTuple{arity}[{}]", parts.join(", ")) + } + + fn render_owner(&self, owner_context: &impl OwnerContext, resolve: &Resolve, owner: &TypeOwner, is_resource: bool) -> Option { + match owner { + TypeOwner::World(id) => { + let world = &resolve.worlds[*id]; + // TODO: assuming an object or trait is also generated per world? + Some( + self.keywords.escape(world.name.clone().to_pascal_case()), + ) + } + TypeOwner::Interface(id) => { + match owner_context.is_local_import(id, is_resource) { + Some(Some(name)) => { + Some(name) + } + Some(None) => None, + None => { + let iface = &resolve.interfaces[*id]; + let name = iface.name.clone().expect("Interface must have a name"); + let package_id = iface.package.expect("Interface must have a package"); + + let package = &resolve.packages[package_id]; + let direction = self.interface_direction(id); + + let mut segments = package_name_to_segments( + &self.opts, + &package.name, + &direction, + &self.keywords, + ); + segments.push(self.keywords.escape(name.to_snake_case())); + + if is_resource && direction == Import { + segments.push(self.keywords.escape(name.to_pascal_case())); + } + + Some(segments.join(".")) + } + } + } + TypeOwner::None => None, + } + } + + fn interface_direction(&self, id: &InterfaceId) -> Direction { + if self.imports.contains(id) { + Import + } else if self.exports.contains(id) { + Export + } else { + // Have not seen it yet, so it must be also an export + Export } } } @@ -229,19 +445,12 @@ impl WorldGenerator for ScalaJsWorld { let wit_name = resolve.name_world_key(key); self.imports.insert(iface); - let mut scalajs_iface = ScalaJsInterface::new( - wit_name.clone(), - resolve, - iface, - &self.opts, - Import, - &self.keywords, - &mut self.overrides, - &self.imports, - &self.exports, - ); + let mut scalajs_iface = + ScalaJsInterface::new(wit_name.clone(), resolve, iface, Import, self); scalajs_iface.generate(); - self.generated_files.push(scalajs_iface.finalize()); + + let file = scalajs_iface.finalize(); + self.generated_files.push(file); Ok(()) } @@ -257,32 +466,71 @@ impl WorldGenerator for ScalaJsWorld { let wit_name = resolve.name_world_key(key); self.exports.insert(iface); - let mut scalajs_iface = ScalaJsInterface::new( - wit_name.clone(), - resolve, - iface, - &self.opts, - Export, - &self.keywords, - &mut self.overrides, - &self.imports, - &self.exports, - ); + let mut scalajs_iface = + ScalaJsInterface::new(wit_name.clone(), resolve, iface, Export, self); scalajs_iface.generate(); - self.generated_files.push(scalajs_iface.finalize()); + + let file = scalajs_iface.finalize(); + self.generated_files.push(file); Ok(()) } fn import_funcs( &mut self, - _resolve: &Resolve, - _world: WorldId, + resolve: &Resolve, + world_id: WorldId, funcs: &[(&str, &Function)], _files: &mut Files, ) { - // TODO - println!("import_funcs: {:?}", funcs); + let world = &resolve.worlds[world_id]; + + if !self.world_defs.contains_key(&world_id) { + // TODO: Extract constructor + + let name = world + .name + .clone() + .to_snake_case(); + + let package_name = resolve.packages + [world.package.expect("missing package for world")] + .name + .clone(); + + let package = package_name_to_segments( + &self.opts, + &package_name, + &Import, + &self.keywords, + ); + + let mut source = String::new(); + uwriteln!(source, "package {}", package.join(".")); + + uwriteln!(source, ""); + uwriteln!(source, "import scala.scalajs.js"); + uwriteln!(source, "import scala.scalajs.js.annotation._"); + uwriteln!(source, "import {}wit._", self.opts.base_package_prefix()); + uwriteln!(source, ""); + uwriteln!(source, "package object {} {{", name); + self.world_defs.insert(world_id, source); + } + + let mut func_imports = String::new(); + for (func_name, func) in funcs { + uwriteln!(func_imports, " @js.native"); + uwriteln!(func_imports, " @JSImport(\"{}\", JSImport.Default)", func_name); + let encoded_name = self.encode_name(func_name.to_lower_camel_case()); + let args = self.render_args(self, resolve, func.params.iter()); + let ret = self.render_return_type(self, resolve, &func.results); + + write_doc_comment(&mut func_imports, " ", &func.docs); + uwriteln!(func_imports, " def {}({args}): {ret} = js.native", encoded_name.scala); + } + + let world_source = self.world_defs.get_mut(&world_id).unwrap(); + uwriteln!(world_source, "{}", func_imports); } fn export_funcs( @@ -310,7 +558,7 @@ impl WorldGenerator for ScalaJsWorld { fn finish( &mut self, - _resolve: &Resolve, + resolve: &Resolve, _world: WorldId, files: &mut Files, ) -> anyhow::Result<()> { @@ -318,6 +566,26 @@ impl WorldGenerator for ScalaJsWorld { files.push(&file.path(), file.source.as_bytes()); } + for (world_id, source) in &mut self.world_defs { + uwriteln!(source, "}}"); + + let world = &resolve.worlds[*world_id]; + let package_name = resolve.packages + [world.package.expect("missing package for world")] + .name + .clone(); + + let package = package_name_to_segments( + &self.opts, + &package_name, + &Import, + &self.keywords, + ); + + let path = format!("{}/{}.scala", package.join("/"), world.name.to_snake_case()); + files.push(&path, source.as_bytes()); + } + let rt = render_runtime_module(&self.opts); files.push(&rt.path(), rt.source.as_bytes()); @@ -342,15 +610,11 @@ struct ScalaJsInterface<'a> { name: String, source: String, package: Vec, - opts: &'a Opts, resolve: &'a Resolve, interface: &'a Interface, interface_id: InterfaceId, direction: Direction, - scala_keywords: &'a ScalaKeywords, - overrides: &'a mut HashMap, - imports: &'a HashSet, - exports: &'a HashSet, + generator: &'a mut ScalaJsWorld, } impl<'a> ScalaJsInterface<'a> { @@ -359,12 +623,8 @@ impl<'a> ScalaJsInterface<'a> { wit_name: String, resolve: &'a Resolve, interface_id: InterfaceId, - opts: &'a Opts, direction: Direction, - scala_keywords: &'a ScalaKeywords, - overrides: &'a mut HashMap, - imports: &'a HashSet, - exports: &'a HashSet, + generator: &'a mut ScalaJsWorld, ) -> Self { let interface = &resolve.interfaces[interface_id]; let name = interface @@ -378,22 +638,23 @@ impl<'a> ScalaJsInterface<'a> { .name .clone(); - let package = package_name_to_segments(&opts, &package_name, &direction, &scala_keywords); + let package = package_name_to_segments( + &generator.opts, + &package_name, + &direction, + &generator.keywords, + ); Self { wit_name, name, source: "".to_string(), package, - opts, resolve, interface, interface_id, direction, - scala_keywords, - overrides, - imports, - exports, + generator, } } @@ -479,13 +740,17 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, ""); uwriteln!(source, "import scala.scalajs.js"); uwriteln!(source, "import scala.scalajs.js.annotation._"); - uwriteln!(source, "import {}wit._", self.opts.base_package_prefix()); + uwriteln!( + source, + "import {}wit._", + self.generator.opts.base_package_prefix() + ); uwriteln!(source, ""); uwriteln!( source, "package object {} {{", - self.scala_keywords.escape(self.name.to_snake_case()) + self.generator.keywords.escape(self.name.to_snake_case()) ); } @@ -495,10 +760,11 @@ impl<'a> ScalaJsInterface<'a> { for (type_name, type_id) in &self.interface.types { let type_def = &self.resolve.types[*type_id]; - let type_name = self.overrides.get(type_id).unwrap_or(type_name); + let type_name = self.generator.overrides.get(type_id).unwrap_or(type_name); let type_name = if type_name.eq_ignore_ascii_case(&self.name) { let overridden_type_name = format!("{}Type", type_name); - self.overrides + self.generator + .overrides .insert(*type_id, overridden_type_name.clone()); overridden_type_name } else { @@ -574,14 +840,12 @@ impl<'a> ScalaJsInterface<'a> { let mut functions = Vec::new(); for (func_name, func) in &self.interface.functions { - let func_name = - self.encode_name(func_name.to_lower_camel_case()); + let func_name = self.generator.encode_name(func_name.to_lower_camel_case()); match func.kind { FunctionKind::Freestanding => { - let args = self.render_args(func.params.iter()); - - let ret = self.render_return_type(&func.results); + let args = self.generator.render_args(self, self.resolve, func.params.iter()); + let ret = self.generator.render_return_type(self, self.resolve, &func.results); let mut function = String::new(); write_doc_comment(&mut function, " ", &func.docs); @@ -616,179 +880,8 @@ impl<'a> ScalaJsInterface<'a> { } } - fn render_args<'b>(&self, params: impl Iterator) -> String { - let mut args = Vec::new(); - for (param_name, param_typ) in params { - let param_typ = self.render_type_reference(param_typ); - let param_name = - self.encode_name(param_name.to_lower_camel_case()); - args.push(format!("{}: {param_typ}", param_name.scala)); - } - args.join(", ") - } - - fn render_return_type(&self, results: &Results) -> String { - match results { - Results::Named(results) if results.len() == 0 => "Unit".to_string(), - Results::Named(results) if results.len() == 1 => { - self.render_type_reference(&results.iter().next().unwrap().1) - } - Results::Named(results) => self.render_tuple(&Tuple { - types: results.iter().map(|(_, typ)| typ.clone()).collect(), - }), - Results::Anon(typ) => self.render_type_reference(typ), - } - } - - fn render_type_reference(&self, typ: &Type) -> String { - match typ { - Type::Bool => "Boolean".to_string(), - Type::U8 => "Byte".to_string(), - Type::U16 => "Short".to_string(), - Type::U32 => "Int".to_string(), - Type::U64 => "Long".to_string(), - Type::S8 => "Byte".to_string(), - Type::S16 => "Short".to_string(), - Type::S32 => "Int".to_string(), - Type::S64 => "Long".to_string(), - Type::F32 => "Float".to_string(), - Type::F64 => "Double".to_string(), - Type::Char => "Char".to_string(), - Type::String => "String".to_string(), - Type::Id(id) => { - let typ = &self.resolve.types[*id]; - self.render_typedef_reference(typ) - } - } - } - - fn render_typedef_reference(&self, typ: &TypeDef) -> String { - match &typ.kind { - TypeDefKind::Record(_) - | TypeDefKind::Resource - | TypeDefKind::Flags(_) - | TypeDefKind::Enum(_) - | TypeDefKind::Type(_) - | TypeDefKind::Variant(_) => { - let prefix = - match self.render_owner(&typ.owner, &typ.kind == &TypeDefKind::Resource) { - Some(owner) => format!("{owner}."), - None => "".to_string(), - }; - format!( - "{}{}", - prefix, - self.scala_keywords.escape( - typ.name - .clone() - .expect("Anonymous types are not supported") - .to_pascal_case() - ) - ) - } - TypeDefKind::Handle(handle) => { - let id = match handle { - Handle::Own(id) => id, - Handle::Borrow(id) => id, - }; - let typ = &self.resolve.types[*id]; - self.render_typedef_reference(typ) - } - TypeDefKind::Tuple(tuple) => self.render_tuple(tuple), - TypeDefKind::Option(option) => { - if !maybe_null(&self.resolve, option) { - format!("Nullable[{}]", self.render_type_reference(option)) - } else { - format!("WitOption[{}]", self.render_type_reference(option)) - } - } - TypeDefKind::Result(result) => { - let ok = result - .ok - .map(|ok| self.render_type_reference(&ok)) - .unwrap_or("Unit".to_string()); - let err = result - .err - .map(|err| self.render_type_reference(&err)) - .unwrap_or("Unit".to_string()); - format!("WitResult[{ok}, {err}]") - } - TypeDefKind::List(list) => { - format!("WitList[{}]", self.render_type_reference(list)) - } - TypeDefKind::Future(_) => panic!("Futures not supported yet"), - TypeDefKind::Stream(_) => panic!("Streams not supported yet"), - TypeDefKind::ErrorContext => panic!("ErrorContext not supported yet"), - TypeDefKind::Unknown => panic!("Unknown type"), - } - } - - fn render_tuple(&self, tuple: &Tuple) -> String { - let arity = tuple.types.len(); - - let mut parts = Vec::new(); - for part in &tuple.types { - parts.push(self.render_type_reference(part)); - } - format!("WitTuple{arity}[{}]", parts.join(", ")) - } - - fn render_owner(&self, owner: &TypeOwner, is_resource: bool) -> Option { - match owner { - TypeOwner::World(id) => { - let world = &self.resolve.worlds[*id]; - // TODO: assuming an object or trait is also generated per world? - Some( - self.scala_keywords - .escape(world.name.clone().to_pascal_case()), - ) - } - TypeOwner::Interface(id) if id == &self.interface_id => { - if is_resource && self.direction == Import { - Some(self.name.clone()) - } else { - None - } - } - TypeOwner::Interface(id) => { - let iface = &self.resolve.interfaces[*id]; - let name = iface.name.clone().expect("Interface must have a name"); - let package_id = iface.package.expect("Interface must have a package"); - - let package = &self.resolve.packages[package_id]; - let direction = self.interface_direction(id); - - let mut segments = package_name_to_segments( - &self.opts, - &package.name, - &direction, - self.scala_keywords, - ); - segments.push(self.scala_keywords.escape(name.to_snake_case())); - - if is_resource && direction == Import { - segments.push(self.scala_keywords.escape(name.to_pascal_case())); - } - - Some(segments.join(".")) - } - TypeOwner::None => None, - } - } - - fn interface_direction(&self, id: &InterfaceId) -> Direction { - if self.imports.contains(id) { - Import - } else if self.exports.contains(id) { - Export - } else { - // Have not seen it yet, so it must be also an export - Export - } - } - fn render_typedef(&self, name: &str, typ: &TypeDef) -> Option { - let encoded_name = self.encode_name(name.to_pascal_case()); + let encoded_name = self.generator.encode_name(name.to_pascal_case()); let scala_name = encoded_name.scala; let mut source = String::new(); @@ -796,11 +889,11 @@ impl<'a> ScalaJsInterface<'a> { TypeDefKind::Record(record) => { let mut fields = Vec::new(); for field in &record.fields { - let typ = self.render_type_reference(&field.ty); - let field_name = - self.encode_name(field.name.to_lower_camel_case()); + let typ = self.generator.render_type_reference(self, self.resolve, &field.ty); + let field_name = self.generator.encode_name(field.name.to_lower_camel_case()); let field_name0 = self - .scala_keywords + .generator + .keywords .escape(format!("{}0", field.name.to_lower_camel_case())); fields.push((field_name, field_name0, typ, &field.docs)); } @@ -823,7 +916,11 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, " new {scala_name} {{"); for (field_name, field_name0, typ, _) in &fields { field_name.write_rename_attribute(&mut source, " "); - uwriteln!(source, " val {}: {typ} = {field_name0}", field_name.scala); + uwriteln!( + source, + " val {}: {typ} = {field_name0}", + field_name.scala + ); } uwriteln!(source, " }}"); uwriteln!(source, " }}"); @@ -839,9 +936,10 @@ impl<'a> ScalaJsInterface<'a> { let mut fields = Vec::new(); for flag in &flags.flags { let typ = "Boolean".to_string(); - let field_name = self.encode_name(flag.name.to_lower_camel_case()); + let field_name = self.generator.encode_name(flag.name.to_lower_camel_case()); let field_name0 = self - .scala_keywords + .generator + .keywords .escape(format!("{}0", flag.name.to_lower_camel_case())); fields.push((field_name, field_name0, typ, &flag.docs)); } @@ -864,7 +962,11 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, " new {scala_name} {{"); for (field_name, field_name0, typ, _) in &fields { field_name.write_rename_attribute(&mut source, " "); - uwriteln!(source, " val {}: {typ} = {field_name0}", field_name.scala); + uwriteln!( + source, + " val {}: {typ} = {field_name0}", + field_name.scala + ); } uwriteln!(source, " }}"); uwriteln!(source, " }}"); @@ -875,7 +977,7 @@ impl<'a> ScalaJsInterface<'a> { write_doc_comment(&mut source, " ", &typ.docs); uwrite!(source, " type {scala_name} = WitTuple{arity}["); for (idx, part) in tuple.types.iter().enumerate() { - let part = self.render_type_reference(part); + let part = self.generator.render_type_reference(self, self.resolve, part); uwrite!(source, "{part}"); if idx < tuple.types.len() - 1 { uwrite!(source, ", "); @@ -894,11 +996,13 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, " object {scala_name} {{"); for case in &variant.cases { let case_name = &case.name; - let scala_case_name = - self.scala_keywords.escape(case_name.to_lower_camel_case()); + let scala_case_name = self + .generator + .keywords + .escape(case_name.to_lower_camel_case()); match &case.ty { Some(ty) => { - let typ = self.render_type_reference(ty); + let typ = self.generator.render_type_reference(self, self.resolve, ty); write_doc_comment(&mut source, " ", &case.docs); uwriteln!(source, " def {scala_case_name}(value: {typ}): {scala_name} = new {scala_name} {{"); uwriteln!(source, " type Type = {typ}"); @@ -928,8 +1032,10 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, " object {scala_name} {{"); for case in &enm.cases { let case_name = &case.name; - let scala_case_name = - self.scala_keywords.escape(case_name.to_lower_camel_case()); + let scala_case_name = self + .generator + .keywords + .escape(case_name.to_lower_camel_case()); write_doc_comment(&mut source, " ", &case.docs); uwriteln!( source, @@ -940,7 +1046,7 @@ impl<'a> ScalaJsInterface<'a> { } TypeDefKind::Option(option) => { write_doc_comment(&mut source, " ", &typ.docs); - let typ = self.render_type_reference(option); + let typ = self.generator.render_type_reference(self, self.resolve, option); if !maybe_null(&self.resolve, option) { uwriteln!(source, " type {scala_name} = Nullable[{typ}]"); } else { @@ -951,17 +1057,17 @@ impl<'a> ScalaJsInterface<'a> { write_doc_comment(&mut source, " ", &typ.docs); let ok = result .ok - .map(|ok| self.render_type_reference(&ok)) + .map(|ok| self.generator.render_type_reference(self, self.resolve, &ok)) .unwrap_or("Unit".to_string()); let err = result .err - .map(|err| self.render_type_reference(&err)) + .map(|err| self.generator.render_type_reference(self, self.resolve, &err)) .unwrap_or("Unit".to_string()); uwriteln!(source, " type {scala_name} = WitResult[{ok}, {err}]"); } TypeDefKind::List(list) => { write_doc_comment(&mut source, " ", &typ.docs); - let typ = self.render_type_reference(list); + let typ = self.generator.render_type_reference(self, self.resolve, list); uwriteln!(source, " type {scala_name} = WitList[{typ}]"); } TypeDefKind::Future(_) => { @@ -975,7 +1081,7 @@ impl<'a> ScalaJsInterface<'a> { } TypeDefKind::Type(reftyp) => { write_doc_comment(&mut source, " ", &typ.docs); - let typ = self.render_type_reference(reftyp); + let typ = self.generator.render_type_reference(self, self.resolve, reftyp); uwriteln!(source, " type {scala_name} = {typ}"); } TypeDefKind::Unknown => { @@ -989,23 +1095,6 @@ impl<'a> ScalaJsInterface<'a> { None } } - - fn encode_name(&self, name: impl AsRef) -> EncodedName { - let name = name.as_ref(); - let scala_name = self.scala_keywords.escape(name); - let js_name = to_js_identifier(name); - - let rename_attribute = if scala_name != js_name && scala_name != format!("`{js_name}`") { - format!("@JSName(\"{js_name}\")") - } else { - "".to_string() - }; - EncodedName { - scala: scala_name, - js: js_name, - rename_attribute, - } - } } struct ScalaJsImportedResource<'a> { @@ -1025,8 +1114,7 @@ impl<'a> ScalaJsImportedResource<'a> { .name .clone() .expect("Anonymous resources not supported"); - let encoded_resource_name = - owner.encode_name(resource_name.to_pascal_case()); + let encoded_resource_name = owner.generator.encode_name(resource_name.to_pascal_case()); let mut class_header = String::new(); write_doc_comment(&mut class_header, " ", &resource.docs); @@ -1061,14 +1149,14 @@ impl<'a> ScalaJsImportedResource<'a> { match &func.kind { FunctionKind::Freestanding => unreachable!(), FunctionKind::Method(_) => { - let args = self.owner.render_args(func.params.iter().skip(1)); - let ret = self.owner.render_return_type(&func.results); - let encoded_func_name = - self.get_func_name("[method]", func_name); + let args = self.owner.generator.render_args(self.owner, self.owner.resolve, func.params.iter().skip(1)); + let ret = self.owner.generator.render_return_type(self.owner, self.owner.resolve, &func.results); + let encoded_func_name = self.get_func_name("[method]", func_name); let overrd = if self .owner - .scala_keywords + .generator + .keywords .base_methods .contains(&encoded_func_name.scala) { @@ -1086,8 +1174,8 @@ impl<'a> ScalaJsImportedResource<'a> { ); } FunctionKind::Static(_) => { - let args = self.owner.render_args(func.params.iter()); - let ret = self.owner.render_return_type(&func.results); + let args = self.owner.generator.render_args(self.owner, self.owner.resolve, func.params.iter()); + let ret = self.owner.generator.render_return_type(self.owner, self.owner.resolve, &func.results); let encoded_func_name = self.get_func_name("[static]", func_name); write_doc_comment(&mut self.object_source, " ", &func.docs); @@ -1099,7 +1187,7 @@ impl<'a> ScalaJsImportedResource<'a> { ); } FunctionKind::Constructor(_) => { - let args = self.owner.render_args(func.params.iter()); + let args = self.owner.generator.render_args(self.owner, self.owner.resolve, func.params.iter()); self.constructor_args = args; } } @@ -1122,7 +1210,7 @@ impl<'a> ScalaJsImportedResource<'a> { .strip_prefix(&self.resource_name) .unwrap() .to_lower_camel_case(); - self.owner.encode_name(name) + self.owner.generator.encode_name(name) } } @@ -1143,14 +1231,17 @@ impl<'a> ScalaJsExportedResource<'a> { .name .clone() .expect("Anonymous resources not supported"); - let encoded_resource_name = - owner.encode_name(resource_name.to_pascal_case()); + let encoded_resource_name = owner.generator.encode_name(resource_name.to_pascal_case()); let mut class_header = String::new(); write_doc_comment(&mut class_header, " ", &resource.docs); encoded_resource_name.write_rename_attribute(&mut class_header, " // "); - uwrite!(class_header, " abstract class {}(", encoded_resource_name.scala); + uwrite!( + class_header, + " abstract class {}(", + encoded_resource_name.scala + ); let mut class_source = String::new(); uwriteln!(class_source, ") extends js.Object {{"); @@ -1170,14 +1261,14 @@ impl<'a> ScalaJsExportedResource<'a> { match &func.kind { FunctionKind::Freestanding => unreachable!(), FunctionKind::Method(_) => { - let args = self.owner.render_args(func.params.iter().skip(1)); - let ret = self.owner.render_return_type(&func.results); - let encoded_func_name = - self.get_func_name("[method]", func_name); + let args = self.owner.generator.render_args(self.owner, self.owner.resolve, func.params.iter().skip(1)); + let ret = self.owner.generator.render_return_type(self.owner, self.owner.resolve, &func.results); + let encoded_func_name = self.get_func_name("[method]", func_name); let overrd = if self .owner - .scala_keywords + .generator + .keywords .base_methods .contains(&encoded_func_name.scala) { @@ -1195,11 +1286,10 @@ impl<'a> ScalaJsExportedResource<'a> { ); } FunctionKind::Static(_) => { - let args = self.owner.render_args(func.params.iter()); - let ret = self.owner.render_return_type(&func.results); + let args = self.owner.generator.render_args(self.owner, self.owner.resolve, func.params.iter()); + let ret = self.owner.generator.render_return_type(self.owner, self.owner.resolve, &func.results); - let encoded_func_name = - self.get_func_name("[static]", func_name); + let encoded_func_name = self.get_func_name("[static]", func_name); write_doc_comment(&mut self.static_methods, " ", &func.docs); uwriteln!(self.static_methods, " // @JSExportStatic"); encoded_func_name.write_rename_attribute(&mut self.static_methods, " "); @@ -1210,7 +1300,7 @@ impl<'a> ScalaJsExportedResource<'a> { ); } FunctionKind::Constructor(_) => { - let args = self.owner.render_args(func.params.iter()); + let args = self.owner.generator.render_args(self.owner, self.owner.resolve, func.params.iter()); self.constructor_args = args; } } @@ -1225,7 +1315,8 @@ impl<'a> ScalaJsExportedResource<'a> { let scala_resource_name = self .owner - .scala_keywords + .generator + .keywords .escape(self.resource_name.to_pascal_case()); uwriteln!(class_source, " trait {}Static {{", scala_resource_name); uwriteln!(class_source, "{}", self.static_methods); @@ -1240,7 +1331,7 @@ impl<'a> ScalaJsExportedResource<'a> { .strip_prefix(&self.resource_name) .unwrap() .to_lower_camel_case(); - self.owner.encode_name(name) + self.owner.generator.encode_name(name) } } From 68aada8432a9dc92b9a779fd4ddd9b61e7bbe0dc Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sun, 2 Feb 2025 12:40:25 +0100 Subject: [PATCH 09/36] Scala.js binding generator, work in progress --- crates/scalajs/src/lib.rs | 542 ++++++++++++++++++++------------------ 1 file changed, 285 insertions(+), 257 deletions(-) diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index 990355622..6b3c99884 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -5,10 +5,7 @@ use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Write}; use std::str::FromStr; -use wit_bindgen_core::wit_parser::{ - Docs, Function, FunctionKind, Handle, Interface, InterfaceId, PackageName, Resolve, Results, - Tuple, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, WorldId, WorldKey, -}; +use wit_bindgen_core::wit_parser::{Docs, Function, FunctionKind, Handle, Interface, InterfaceId, PackageName, Resolve, Results, Tuple, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, World, WorldId, WorldKey}; use wit_bindgen_core::Direction::{Export, Import}; use wit_bindgen_core::{uwrite, uwriteln, Direction, Files, WorldGenerator}; @@ -382,10 +379,27 @@ impl ScalaJsWorld { match owner { TypeOwner::World(id) => { let world = &resolve.worlds[*id]; - // TODO: assuming an object or trait is also generated per world? - Some( - self.keywords.escape(world.name.clone().to_pascal_case()), - ) + + let name = world + .name + .clone() + .to_snake_case(); + + let package_name = resolve.packages + [world.package.expect("missing package for world")] + .name + .clone(); + + let mut package = package_name_to_segments( + &self.opts, + &package_name, + &Import, + &self.keywords, + ); + + package.push(self.keywords.escape(name)); + + Some(package.join("."), ) } TypeOwner::Interface(id) => { match owner_context.is_local_import(id, is_resource) { @@ -421,6 +435,218 @@ impl ScalaJsWorld { } } + fn render_typedef(&self, owner_context: &impl OwnerContext, resolve: &Resolve, name: &str, typ: &TypeDef) -> Option { + let encoded_name = self.encode_name(name.to_pascal_case()); + let scala_name = encoded_name.scala; + + let mut source = String::new(); + match &typ.kind { + TypeDefKind::Record(record) => { + let mut fields = Vec::new(); + for field in &record.fields { + let typ = self.render_type_reference(owner_context, resolve, &field.ty); + let field_name = self.encode_name(field.name.to_lower_camel_case()); + let field_name0 = self + .keywords + .escape(format!("{}0", field.name.to_lower_camel_case())); + fields.push((field_name, field_name0, typ, &field.docs)); + } + + write_doc_comment(&mut source, " ", &typ.docs); + uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); + for (field_name, _, typ, docs) in &fields { + write_doc_comment(&mut source, " ", &docs); + field_name.write_rename_attribute(&mut source, " "); + uwriteln!(source, " val {}: {typ}", field_name.scala); + } + uwriteln!(source, " }}"); + uwriteln!(source, ""); + uwriteln!(source, " case object {scala_name} {{"); + uwriteln!(source, " def apply("); + for (_, field_name0, typ, _) in &fields { + uwriteln!(source, " {field_name0}: {typ},"); + } + uwriteln!(source, " ): {scala_name} = {{"); + uwriteln!(source, " new {scala_name} {{"); + for (field_name, field_name0, typ, _) in &fields { + field_name.write_rename_attribute(&mut source, " "); + uwriteln!( + source, + " val {}: {typ} = {field_name0}", + field_name.scala + ); + } + uwriteln!(source, " }}"); + uwriteln!(source, " }}"); + uwriteln!(source, " }}"); + } + TypeDefKind::Resource => { + // resource wrappers are generated separately + } + TypeDefKind::Handle(_) => { + panic!("Unexpected top-level handle type"); + } + TypeDefKind::Flags(flags) => { + let mut fields = Vec::new(); + for flag in &flags.flags { + let typ = "Boolean".to_string(); + let field_name = self.encode_name(flag.name.to_lower_camel_case()); + let field_name0 = self + .keywords + .escape(format!("{}0", flag.name.to_lower_camel_case())); + fields.push((field_name, field_name0, typ, &flag.docs)); + } + + write_doc_comment(&mut source, " ", &typ.docs); + uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); + for (field_name, _, typ, docs) in &fields { + write_doc_comment(&mut source, " ", docs); + field_name.write_rename_attribute(&mut source, " "); + uwriteln!(source, " val {}: {typ}", field_name.scala); + } + uwriteln!(source, " }}"); + uwriteln!(source, ""); + uwriteln!(source, " case object {scala_name} {{"); + uwriteln!(source, " def apply("); + for (_, field_name0, typ, _) in &fields { + uwriteln!(source, " {field_name0}: {typ},"); + } + uwriteln!(source, " ): {scala_name} = {{"); + uwriteln!(source, " new {scala_name} {{"); + for (field_name, field_name0, typ, _) in &fields { + field_name.write_rename_attribute(&mut source, " "); + uwriteln!( + source, + " val {}: {typ} = {field_name0}", + field_name.scala + ); + } + uwriteln!(source, " }}"); + uwriteln!(source, " }}"); + uwriteln!(source, " }}"); + } + TypeDefKind::Tuple(tuple) => { + let arity = tuple.types.len(); + write_doc_comment(&mut source, " ", &typ.docs); + uwrite!(source, " type {scala_name} = WitTuple{arity}["); + for (idx, part) in tuple.types.iter().enumerate() { + let part = self.render_type_reference(owner_context, resolve, part); + uwrite!(source, "{part}"); + if idx < tuple.types.len() - 1 { + uwrite!(source, ", "); + } + } + uwriteln!(source, "]"); + } + TypeDefKind::Variant(variant) => { + write_doc_comment(&mut source, " ", &typ.docs); + uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); + uwriteln!(source, " type Type"); + uwriteln!(source, " val tag: String"); + uwriteln!(source, " val `val`: js.UndefOr[Type]"); + uwriteln!(source, " }}"); + uwriteln!(source, ""); + uwriteln!(source, " object {scala_name} {{"); + for case in &variant.cases { + let case_name = &case.name; + let scala_case_name = self + .keywords + .escape(case_name.to_lower_camel_case()); + match &case.ty { + Some(ty) => { + let typ = self.render_type_reference(owner_context, resolve, ty); + write_doc_comment(&mut source, " ", &case.docs); + uwriteln!(source, " def {scala_case_name}(value: {typ}): {scala_name} = new {scala_name} {{"); + uwriteln!(source, " type Type = {typ}"); + uwriteln!(source, " val tag: String = \"{case_name}\""); + uwriteln!(source, " val `val`: js.UndefOr[Type] = value"); + uwriteln!(source, " }}"); + } + None => { + write_doc_comment(&mut source, " ", &case.docs); + uwriteln!( + source, + " def {scala_case_name}(): {scala_name} = new {scala_name} {{" + ); + uwriteln!(source, " type Type = Unit"); + uwriteln!(source, " val tag: String = \"{case_name}\""); + uwriteln!(source, " val `val`: js.UndefOr[Type] = ()"); + uwriteln!(source, " }}"); + } + } + } + uwriteln!(source, " }}"); + } + TypeDefKind::Enum(enm) => { + write_doc_comment(&mut source, " ", &typ.docs); + uwriteln!(source, " sealed trait {scala_name} extends js.Object"); + uwriteln!(source, ""); + uwriteln!(source, " object {scala_name} {{"); + for case in &enm.cases { + let case_name = &case.name; + let scala_case_name = self + .keywords + .escape(case_name.to_lower_camel_case()); + write_doc_comment(&mut source, " ", &case.docs); + uwriteln!( + source, + " def {scala_case_name}: {scala_name} = \"{case_name}\".asInstanceOf[{scala_name}]", + ); + } + uwriteln!(source, " }}"); + } + TypeDefKind::Option(option) => { + write_doc_comment(&mut source, " ", &typ.docs); + let typ = self.render_type_reference(owner_context, resolve, option); + if !maybe_null(resolve, option) { + uwriteln!(source, " type {scala_name} = Nullable[{typ}]"); + } else { + uwriteln!(source, " type {scala_name} = WitOption[{typ}]"); + } + } + TypeDefKind::Result(result) => { + write_doc_comment(&mut source, " ", &typ.docs); + let ok = result + .ok + .map(|ok| self.render_type_reference(owner_context, resolve, &ok)) + .unwrap_or("Unit".to_string()); + let err = result + .err + .map(|err| self.render_type_reference(owner_context, resolve, &err)) + .unwrap_or("Unit".to_string()); + uwriteln!(source, " type {scala_name} = WitResult[{ok}, {err}]"); + } + TypeDefKind::List(list) => { + write_doc_comment(&mut source, " ", &typ.docs); + let typ = self.render_type_reference(owner_context, resolve, list); + uwriteln!(source, " type {scala_name} = WitList[{typ}]"); + } + TypeDefKind::Future(_) => { + panic!("Futures are not supported yet"); + } + TypeDefKind::Stream(_) => { + panic!("Streams are not supported yet"); + } + TypeDefKind::ErrorContext => { + panic!("ErrorContext is not supported yet"); + } + TypeDefKind::Type(reftyp) => { + write_doc_comment(&mut source, " ", &typ.docs); + let typ = self.render_type_reference(owner_context, resolve, reftyp); + uwriteln!(source, " type {scala_name} = {typ}"); + } + TypeDefKind::Unknown => { + panic!("Unknown type"); + } + } + + if source.len() > 0 { + Some(source) + } else { + None + } + } + fn interface_direction(&self, id: &InterfaceId) -> Direction { if self.imports.contains(id) { Import @@ -431,6 +657,36 @@ impl ScalaJsWorld { Export } } + + fn generate_world_package_header(&self, resolve: &Resolve, world: &World) -> String { + let name = world + .name + .clone() + .to_snake_case(); + + let package_name = resolve.packages + [world.package.expect("missing package for world")] + .name + .clone(); + + let package = package_name_to_segments( + &self.opts, + &package_name, + &Import, + &self.keywords, + ); + + let mut source = String::new(); + uwriteln!(source, "package {}", package.join(".")); + + uwriteln!(source, ""); + uwriteln!(source, "import scala.scalajs.js"); + uwriteln!(source, "import scala.scalajs.js.annotation._"); + uwriteln!(source, "import {}wit._", self.opts.base_package_prefix()); + uwriteln!(source, ""); + uwriteln!(source, "package object {} {{", name); + source + } } impl WorldGenerator for ScalaJsWorld { @@ -486,34 +742,7 @@ impl WorldGenerator for ScalaJsWorld { let world = &resolve.worlds[world_id]; if !self.world_defs.contains_key(&world_id) { - // TODO: Extract constructor - - let name = world - .name - .clone() - .to_snake_case(); - - let package_name = resolve.packages - [world.package.expect("missing package for world")] - .name - .clone(); - - let package = package_name_to_segments( - &self.opts, - &package_name, - &Import, - &self.keywords, - ); - - let mut source = String::new(); - uwriteln!(source, "package {}", package.join(".")); - - uwriteln!(source, ""); - uwriteln!(source, "import scala.scalajs.js"); - uwriteln!(source, "import scala.scalajs.js.annotation._"); - uwriteln!(source, "import {}wit._", self.opts.base_package_prefix()); - uwriteln!(source, ""); - uwriteln!(source, "package object {} {{", name); + let source = self.generate_world_package_header(resolve, world); self.world_defs.insert(world_id, source); } @@ -547,13 +776,28 @@ impl WorldGenerator for ScalaJsWorld { fn import_types( &mut self, - _resolve: &Resolve, - _world: WorldId, + resolve: &Resolve, + world_id: WorldId, types: &[(&str, TypeId)], _files: &mut Files, ) { - // TODO - println!("import_types: {:?}", types); + let world = &resolve.worlds[world_id]; + + if !self.world_defs.contains_key(&world_id) { + let source = self.generate_world_package_header(resolve, world); + self.world_defs.insert(world_id, source); + } + + let mut type_snippets = String::new(); + for (type_name, type_id) in types { + if let Some(type_snippet) = self.render_typedef(self, resolve, type_name, &resolve.types[*type_id]) { + uwriteln!(type_snippets, "{}", type_snippet); + uwriteln!(type_snippets, ""); + } + } + + let world_source = self.world_defs.get_mut(&world_id).unwrap(); + uwriteln!(world_source, "{}", type_snippets); } fn finish( @@ -771,7 +1015,7 @@ impl<'a> ScalaJsInterface<'a> { type_name.clone() }; - if let Some(typ) = self.render_typedef(&type_name, type_def) { + if let Some(typ) = self.generator.render_typedef(self, &self.resolve, &type_name, type_def) { types.push(typ); } } @@ -879,222 +1123,6 @@ impl<'a> ScalaJsInterface<'a> { source: self.source, } } - - fn render_typedef(&self, name: &str, typ: &TypeDef) -> Option { - let encoded_name = self.generator.encode_name(name.to_pascal_case()); - let scala_name = encoded_name.scala; - - let mut source = String::new(); - match &typ.kind { - TypeDefKind::Record(record) => { - let mut fields = Vec::new(); - for field in &record.fields { - let typ = self.generator.render_type_reference(self, self.resolve, &field.ty); - let field_name = self.generator.encode_name(field.name.to_lower_camel_case()); - let field_name0 = self - .generator - .keywords - .escape(format!("{}0", field.name.to_lower_camel_case())); - fields.push((field_name, field_name0, typ, &field.docs)); - } - - write_doc_comment(&mut source, " ", &typ.docs); - uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); - for (field_name, _, typ, docs) in &fields { - write_doc_comment(&mut source, " ", &docs); - field_name.write_rename_attribute(&mut source, " "); - uwriteln!(source, " val {}: {typ}", field_name.scala); - } - uwriteln!(source, " }}"); - uwriteln!(source, ""); - uwriteln!(source, " case object {scala_name} {{"); - uwriteln!(source, " def apply("); - for (_, field_name0, typ, _) in &fields { - uwriteln!(source, " {field_name0}: {typ},"); - } - uwriteln!(source, " ): {scala_name} = {{"); - uwriteln!(source, " new {scala_name} {{"); - for (field_name, field_name0, typ, _) in &fields { - field_name.write_rename_attribute(&mut source, " "); - uwriteln!( - source, - " val {}: {typ} = {field_name0}", - field_name.scala - ); - } - uwriteln!(source, " }}"); - uwriteln!(source, " }}"); - uwriteln!(source, " }}"); - } - TypeDefKind::Resource => { - // resource wrappers are generated separately - } - TypeDefKind::Handle(_) => { - panic!("Unexpected top-level handle type"); - } - TypeDefKind::Flags(flags) => { - let mut fields = Vec::new(); - for flag in &flags.flags { - let typ = "Boolean".to_string(); - let field_name = self.generator.encode_name(flag.name.to_lower_camel_case()); - let field_name0 = self - .generator - .keywords - .escape(format!("{}0", flag.name.to_lower_camel_case())); - fields.push((field_name, field_name0, typ, &flag.docs)); - } - - write_doc_comment(&mut source, " ", &typ.docs); - uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); - for (field_name, _, typ, docs) in &fields { - write_doc_comment(&mut source, " ", docs); - field_name.write_rename_attribute(&mut source, " "); - uwriteln!(source, " val {}: {typ}", field_name.scala); - } - uwriteln!(source, " }}"); - uwriteln!(source, ""); - uwriteln!(source, " case object {scala_name} {{"); - uwriteln!(source, " def apply("); - for (_, field_name0, typ, _) in &fields { - uwriteln!(source, " {field_name0}: {typ},"); - } - uwriteln!(source, " ): {scala_name} = {{"); - uwriteln!(source, " new {scala_name} {{"); - for (field_name, field_name0, typ, _) in &fields { - field_name.write_rename_attribute(&mut source, " "); - uwriteln!( - source, - " val {}: {typ} = {field_name0}", - field_name.scala - ); - } - uwriteln!(source, " }}"); - uwriteln!(source, " }}"); - uwriteln!(source, " }}"); - } - TypeDefKind::Tuple(tuple) => { - let arity = tuple.types.len(); - write_doc_comment(&mut source, " ", &typ.docs); - uwrite!(source, " type {scala_name} = WitTuple{arity}["); - for (idx, part) in tuple.types.iter().enumerate() { - let part = self.generator.render_type_reference(self, self.resolve, part); - uwrite!(source, "{part}"); - if idx < tuple.types.len() - 1 { - uwrite!(source, ", "); - } - } - uwriteln!(source, "]"); - } - TypeDefKind::Variant(variant) => { - write_doc_comment(&mut source, " ", &typ.docs); - uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); - uwriteln!(source, " type Type"); - uwriteln!(source, " val tag: String"); - uwriteln!(source, " val `val`: js.UndefOr[Type]"); - uwriteln!(source, " }}"); - uwriteln!(source, ""); - uwriteln!(source, " object {scala_name} {{"); - for case in &variant.cases { - let case_name = &case.name; - let scala_case_name = self - .generator - .keywords - .escape(case_name.to_lower_camel_case()); - match &case.ty { - Some(ty) => { - let typ = self.generator.render_type_reference(self, self.resolve, ty); - write_doc_comment(&mut source, " ", &case.docs); - uwriteln!(source, " def {scala_case_name}(value: {typ}): {scala_name} = new {scala_name} {{"); - uwriteln!(source, " type Type = {typ}"); - uwriteln!(source, " val tag: String = \"{case_name}\""); - uwriteln!(source, " val `val`: js.UndefOr[Type] = value"); - uwriteln!(source, " }}"); - } - None => { - write_doc_comment(&mut source, " ", &case.docs); - uwriteln!( - source, - " def {scala_case_name}(): {scala_name} = new {scala_name} {{" - ); - uwriteln!(source, " type Type = Unit"); - uwriteln!(source, " val tag: String = \"{case_name}\""); - uwriteln!(source, " val `val`: js.UndefOr[Type] = ()"); - uwriteln!(source, " }}"); - } - } - } - uwriteln!(source, " }}"); - } - TypeDefKind::Enum(enm) => { - write_doc_comment(&mut source, " ", &typ.docs); - uwriteln!(source, " sealed trait {scala_name} extends js.Object"); - uwriteln!(source, ""); - uwriteln!(source, " object {scala_name} {{"); - for case in &enm.cases { - let case_name = &case.name; - let scala_case_name = self - .generator - .keywords - .escape(case_name.to_lower_camel_case()); - write_doc_comment(&mut source, " ", &case.docs); - uwriteln!( - source, - " def {scala_case_name}: {scala_name} = \"{case_name}\".asInstanceOf[{scala_name}]", - ); - } - uwriteln!(source, " }}"); - } - TypeDefKind::Option(option) => { - write_doc_comment(&mut source, " ", &typ.docs); - let typ = self.generator.render_type_reference(self, self.resolve, option); - if !maybe_null(&self.resolve, option) { - uwriteln!(source, " type {scala_name} = Nullable[{typ}]"); - } else { - uwriteln!(source, " type {scala_name} = WitOption[{typ}]"); - } - } - TypeDefKind::Result(result) => { - write_doc_comment(&mut source, " ", &typ.docs); - let ok = result - .ok - .map(|ok| self.generator.render_type_reference(self, self.resolve, &ok)) - .unwrap_or("Unit".to_string()); - let err = result - .err - .map(|err| self.generator.render_type_reference(self, self.resolve, &err)) - .unwrap_or("Unit".to_string()); - uwriteln!(source, " type {scala_name} = WitResult[{ok}, {err}]"); - } - TypeDefKind::List(list) => { - write_doc_comment(&mut source, " ", &typ.docs); - let typ = self.generator.render_type_reference(self, self.resolve, list); - uwriteln!(source, " type {scala_name} = WitList[{typ}]"); - } - TypeDefKind::Future(_) => { - panic!("Futures are not supported yet"); - } - TypeDefKind::Stream(_) => { - panic!("Streams are not supported yet"); - } - TypeDefKind::ErrorContext => { - panic!("ErrorContext is not supported yet"); - } - TypeDefKind::Type(reftyp) => { - write_doc_comment(&mut source, " ", &typ.docs); - let typ = self.generator.render_type_reference(self, self.resolve, reftyp); - uwriteln!(source, " type {scala_name} = {typ}"); - } - TypeDefKind::Unknown => { - panic!("Unknown type"); - } - } - - if source.len() > 0 { - Some(source) - } else { - None - } - } } struct ScalaJsImportedResource<'a> { From 8b7f6fc3583f0218c9974c7976340b63c64f443d Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Tue, 4 Feb 2025 18:48:23 +0100 Subject: [PATCH 10/36] Scala.js binding generator, work in progress --- crates/scalajs/src/lib.rs | 674 +++++++++++++++++++++++++------------- 1 file changed, 453 insertions(+), 221 deletions(-) diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index 6b3c99884..718e4a79c 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -5,7 +5,10 @@ use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Write}; use std::str::FromStr; -use wit_bindgen_core::wit_parser::{Docs, Function, FunctionKind, Handle, Interface, InterfaceId, PackageName, Resolve, Results, Tuple, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, World, WorldId, WorldKey}; +use wit_bindgen_core::wit_parser::{ + Docs, Function, FunctionKind, Handle, Interface, InterfaceId, PackageName, Resolve, Results, + Tuple, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, World, WorldId, WorldKey, +}; use wit_bindgen_core::Direction::{Export, Import}; use wit_bindgen_core::{uwrite, uwriteln, Direction, Files, WorldGenerator}; @@ -60,13 +63,12 @@ pub struct Opts { ) )] pub scala_dialect: ScalaDialect, - // TODO: generate skeleton mode - single file with the exported things to be implemented - destructive, will be wired to an explicit sbt command } impl Opts { pub fn build(&self) -> Box { - Box::new(ScalaJsWorld::new(self.clone())) + Box::new(ScalaJs::new(self.clone())) } pub fn base_package_segments(&self) -> Vec { @@ -198,7 +200,13 @@ trait OwnerContext { fn is_local_import(&self, id: &InterfaceId, is_resource: bool) -> Option>; } -impl OwnerContext for ScalaJsWorld { +impl OwnerContext for ScalaJs { + fn is_local_import(&self, _id: &InterfaceId, _is_resource: bool) -> Option> { + None + } +} + +impl OwnerContext for ScalaJsContext { fn is_local_import(&self, _id: &InterfaceId, _is_resource: bool) -> Option> { None } @@ -218,30 +226,15 @@ impl<'a> OwnerContext for ScalaJsInterface<'a> { } } -struct ScalaJsWorld { +struct ScalaJsContext { opts: Opts, - generated_files: Vec, keywords: ScalaKeywords, overrides: HashMap, imports: HashSet, exports: HashSet, - world_defs: HashMap, } -impl ScalaJsWorld { - fn new(opts: Opts) -> Self { - let keywords = ScalaKeywords::new(&opts.scala_dialect); - Self { - opts, - generated_files: Vec::new(), - keywords, - overrides: HashMap::new(), - imports: HashSet::new(), - exports: HashSet::new(), - world_defs: HashMap::new(), - } - } - +impl ScalaJsContext { pub fn encode_name(&self, name: impl AsRef) -> EncodedName { let name = name.as_ref(); let scala_name = self.keywords.escape(name); @@ -259,7 +252,12 @@ impl ScalaJsWorld { } } - fn render_args<'b>(&self, owner_context: &impl OwnerContext, resolve: &Resolve, params: impl Iterator) -> String { + fn render_args<'b>( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + params: impl Iterator, + ) -> String { let mut args = Vec::new(); for (param_name, param_typ) in params { let param_typ = self.render_type_reference(owner_context, resolve, param_typ); @@ -269,20 +267,36 @@ impl ScalaJsWorld { args.join(", ") } - fn render_return_type(&self, owner_context: &impl OwnerContext, resolve: &Resolve, results: &Results) -> String { + fn render_return_type( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + results: &Results, + ) -> String { match results { Results::Named(results) if results.len() == 0 => "Unit".to_string(), - Results::Named(results) if results.len() == 1 => { - self.render_type_reference(owner_context, resolve, &results.iter().next().unwrap().1) - } - Results::Named(results) => self.render_tuple(owner_context, resolve, &Tuple { - types: results.iter().map(|(_, typ)| typ.clone()).collect(), - }), + Results::Named(results) if results.len() == 1 => self.render_type_reference( + owner_context, + resolve, + &results.iter().next().unwrap().1, + ), + Results::Named(results) => self.render_tuple( + owner_context, + resolve, + &Tuple { + types: results.iter().map(|(_, typ)| typ.clone()).collect(), + }, + ), Results::Anon(typ) => self.render_type_reference(owner_context, resolve, typ), } } - fn render_type_reference(&self, owner_context: &impl OwnerContext, resolve: &Resolve, typ: &Type) -> String { + fn render_type_reference( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + typ: &Type, + ) -> String { match typ { Type::Bool => "Boolean".to_string(), Type::U8 => "Byte".to_string(), @@ -304,7 +318,12 @@ impl ScalaJsWorld { } } - fn render_typedef_reference(&self, owner_context: &impl OwnerContext, resolve: &Resolve, typ: &TypeDef) -> String { + fn render_typedef_reference( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + typ: &TypeDef, + ) -> String { match &typ.kind { TypeDefKind::Record(_) | TypeDefKind::Resource @@ -312,11 +331,15 @@ impl ScalaJsWorld { | TypeDefKind::Enum(_) | TypeDefKind::Type(_) | TypeDefKind::Variant(_) => { - let prefix = - match self.render_owner(owner_context, resolve, &typ.owner, &typ.kind == &TypeDefKind::Resource) { - Some(owner) => format!("{owner}."), - None => "".to_string(), - }; + let prefix = match self.render_owner( + owner_context, + resolve, + &typ.owner, + &typ.kind == &TypeDefKind::Resource, + ) { + Some(owner) => format!("{owner}."), + None => "".to_string(), + }; format!( "{}{}", prefix, @@ -339,9 +362,15 @@ impl ScalaJsWorld { TypeDefKind::Tuple(tuple) => self.render_tuple(owner_context, resolve, tuple), TypeDefKind::Option(option) => { if !maybe_null(resolve, option) { - format!("Nullable[{}]", self.render_type_reference(owner_context, resolve, option)) + format!( + "Nullable[{}]", + self.render_type_reference(owner_context, resolve, option) + ) } else { - format!("WitOption[{}]", self.render_type_reference(owner_context, resolve, option)) + format!( + "WitOption[{}]", + self.render_type_reference(owner_context, resolve, option) + ) } } TypeDefKind::Result(result) => { @@ -356,7 +385,10 @@ impl ScalaJsWorld { format!("WitResult[{ok}, {err}]") } TypeDefKind::List(list) => { - format!("WitList[{}]", self.render_type_reference(owner_context, resolve, list)) + format!( + "WitList[{}]", + self.render_type_reference(owner_context, resolve, list) + ) } TypeDefKind::Future(_) => panic!("Futures not supported yet"), TypeDefKind::Stream(_) => panic!("Streams not supported yet"), @@ -365,7 +397,12 @@ impl ScalaJsWorld { } } - fn render_tuple(&self, owner_context: &impl OwnerContext, resolve: &Resolve, tuple: &Tuple) -> String { + fn render_tuple( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + tuple: &Tuple, + ) -> String { let arity = tuple.types.len(); let mut parts = Vec::new(); @@ -375,67 +412,68 @@ impl ScalaJsWorld { format!("WitTuple{arity}[{}]", parts.join(", ")) } - fn render_owner(&self, owner_context: &impl OwnerContext, resolve: &Resolve, owner: &TypeOwner, is_resource: bool) -> Option { + fn render_owner( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + owner: &TypeOwner, + is_resource: bool, + ) -> Option { match owner { TypeOwner::World(id) => { let world = &resolve.worlds[*id]; - let name = world - .name - .clone() - .to_snake_case(); + let name = world.name.clone().to_snake_case(); let package_name = resolve.packages [world.package.expect("missing package for world")] - .name - .clone(); - - let mut package = package_name_to_segments( - &self.opts, - &package_name, - &Import, - &self.keywords, - ); + .name + .clone(); + + let mut package = + package_name_to_segments(&self.opts, &package_name, &Import, &self.keywords); package.push(self.keywords.escape(name)); - Some(package.join("."), ) + Some(package.join(".")) } - TypeOwner::Interface(id) => { - match owner_context.is_local_import(id, is_resource) { - Some(Some(name)) => { - Some(name) - } - Some(None) => None, - None => { - let iface = &resolve.interfaces[*id]; - let name = iface.name.clone().expect("Interface must have a name"); - let package_id = iface.package.expect("Interface must have a package"); - - let package = &resolve.packages[package_id]; - let direction = self.interface_direction(id); - - let mut segments = package_name_to_segments( - &self.opts, - &package.name, - &direction, - &self.keywords, - ); - segments.push(self.keywords.escape(name.to_snake_case())); - - if is_resource && direction == Import { - segments.push(self.keywords.escape(name.to_pascal_case())); - } + TypeOwner::Interface(id) => match owner_context.is_local_import(id, is_resource) { + Some(Some(name)) => Some(name), + Some(None) => None, + None => { + let iface = &resolve.interfaces[*id]; + let name = iface.name.clone().expect("Interface must have a name"); + let package_id = iface.package.expect("Interface must have a package"); + + let package = &resolve.packages[package_id]; + let direction = self.interface_direction(id); + + let mut segments = package_name_to_segments( + &self.opts, + &package.name, + &direction, + &self.keywords, + ); + segments.push(self.keywords.escape(name.to_snake_case())); - Some(segments.join(".")) + if is_resource && direction == Import { + segments.push(self.keywords.escape(name.to_pascal_case())); } + + Some(segments.join(".")) } - } + }, TypeOwner::None => None, } } - fn render_typedef(&self, owner_context: &impl OwnerContext, resolve: &Resolve, name: &str, typ: &TypeDef) -> Option { + fn render_typedef( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + name: &str, + typ: &TypeDef, + ) -> Option { let encoded_name = self.encode_name(name.to_pascal_case()); let scala_name = encoded_name.scala; @@ -549,9 +587,7 @@ impl ScalaJsWorld { uwriteln!(source, " object {scala_name} {{"); for case in &variant.cases { let case_name = &case.name; - let scala_case_name = self - .keywords - .escape(case_name.to_lower_camel_case()); + let scala_case_name = self.keywords.escape(case_name.to_lower_camel_case()); match &case.ty { Some(ty) => { let typ = self.render_type_reference(owner_context, resolve, ty); @@ -584,9 +620,7 @@ impl ScalaJsWorld { uwriteln!(source, " object {scala_name} {{"); for case in &enm.cases { let case_name = &case.name; - let scala_case_name = self - .keywords - .escape(case_name.to_lower_camel_case()); + let scala_case_name = self.keywords.escape(case_name.to_lower_camel_case()); write_doc_comment(&mut source, " ", &case.docs); uwriteln!( source, @@ -657,39 +691,15 @@ impl ScalaJsWorld { Export } } +} - fn generate_world_package_header(&self, resolve: &Resolve, world: &World) -> String { - let name = world - .name - .clone() - .to_snake_case(); - - let package_name = resolve.packages - [world.package.expect("missing package for world")] - .name - .clone(); - - let package = package_name_to_segments( - &self.opts, - &package_name, - &Import, - &self.keywords, - ); - - let mut source = String::new(); - uwriteln!(source, "package {}", package.join(".")); - - uwriteln!(source, ""); - uwriteln!(source, "import scala.scalajs.js"); - uwriteln!(source, "import scala.scalajs.js.annotation._"); - uwriteln!(source, "import {}wit._", self.opts.base_package_prefix()); - uwriteln!(source, ""); - uwriteln!(source, "package object {} {{", name); - source - } +struct ScalaJs { + context: ScalaJsContext, + generated_files: Vec, + world_defs: HashMap, } -impl WorldGenerator for ScalaJsWorld { +impl WorldGenerator for ScalaJs { fn import_interface( &mut self, resolve: &Resolve, @@ -700,7 +710,7 @@ impl WorldGenerator for ScalaJsWorld { let key = name; let wit_name = resolve.name_world_key(key); - self.imports.insert(iface); + self.context.imports.insert(iface); let mut scalajs_iface = ScalaJsInterface::new(wit_name.clone(), resolve, iface, Import, self); scalajs_iface.generate(); @@ -721,7 +731,7 @@ impl WorldGenerator for ScalaJsWorld { let key = name; let wit_name = resolve.name_world_key(key); - self.exports.insert(iface); + self.context.exports.insert(iface); let mut scalajs_iface = ScalaJsInterface::new(wit_name.clone(), resolve, iface, Export, self); scalajs_iface.generate(); @@ -742,24 +752,16 @@ impl WorldGenerator for ScalaJsWorld { let world = &resolve.worlds[world_id]; if !self.world_defs.contains_key(&world_id) { - let source = self.generate_world_package_header(resolve, world); - self.world_defs.insert(world_id, source); + let world_def = ScalaJsWorld::new(&self.context, resolve, world_id, world); + self.world_defs.insert(world_id, world_def); } - let mut func_imports = String::new(); for (func_name, func) in funcs { - uwriteln!(func_imports, " @js.native"); - uwriteln!(func_imports, " @JSImport(\"{}\", JSImport.Default)", func_name); - let encoded_name = self.encode_name(func_name.to_lower_camel_case()); - let args = self.render_args(self, resolve, func.params.iter()); - let ret = self.render_return_type(self, resolve, &func.results); - - write_doc_comment(&mut func_imports, " ", &func.docs); - uwriteln!(func_imports, " def {}({args}): {ret} = js.native", encoded_name.scala); + self.world_defs + .get_mut(&world_id) + .unwrap() + .add_imported_function(&self.context, resolve, func_name, func); } - - let world_source = self.world_defs.get_mut(&world_id).unwrap(); - uwriteln!(world_source, "{}", func_imports); } fn export_funcs( @@ -784,20 +786,18 @@ impl WorldGenerator for ScalaJsWorld { let world = &resolve.worlds[world_id]; if !self.world_defs.contains_key(&world_id) { - let source = self.generate_world_package_header(resolve, world); - self.world_defs.insert(world_id, source); + let world_def = ScalaJsWorld::new(&self.context, resolve, world_id, world); + self.world_defs.insert(world_id, world_def); } - let mut type_snippets = String::new(); for (type_name, type_id) in types { - if let Some(type_snippet) = self.render_typedef(self, resolve, type_name, &resolve.types[*type_id]) { - uwriteln!(type_snippets, "{}", type_snippet); - uwriteln!(type_snippets, ""); - } + self.world_defs.get_mut(&world_id).unwrap().add_type( + &self.context, + resolve, + type_name, + type_id, + ); } - - let world_source = self.world_defs.get_mut(&world_id).unwrap(); - uwriteln!(world_source, "{}", type_snippets); } fn finish( @@ -810,33 +810,35 @@ impl WorldGenerator for ScalaJsWorld { files.push(&file.path(), file.source.as_bytes()); } - for (world_id, source) in &mut self.world_defs { - uwriteln!(source, "}}"); - - let world = &resolve.worlds[*world_id]; - let package_name = resolve.packages - [world.package.expect("missing package for world")] - .name - .clone(); - - let package = package_name_to_segments( - &self.opts, - &package_name, - &Import, - &self.keywords, - ); - - let path = format!("{}/{}.scala", package.join("/"), world.name.to_snake_case()); - files.push(&path, source.as_bytes()); + for (_, world_def) in self.world_defs.drain() { + let world_file = world_def.finalize(&self.context, resolve); + files.push(&world_file.path(), world_file.source.as_bytes()); } - let rt = render_runtime_module(&self.opts); + let rt = render_runtime_module(&self.context.opts); files.push(&rt.path(), rt.source.as_bytes()); Ok(()) } } +impl ScalaJs { + fn new(opts: Opts) -> Self { + let keywords = ScalaKeywords::new(&opts.scala_dialect); + Self { + context: ScalaJsContext { + opts, + keywords, + overrides: HashMap::new(), + imports: HashSet::new(), + exports: HashSet::new(), + }, + generated_files: Vec::new(), + world_defs: HashMap::new(), + } + } +} + struct ScalaJsFile { package: Vec, name: String, @@ -849,6 +851,130 @@ impl ScalaJsFile { } } +struct ScalaJsWorld { + world_id: WorldId, + header: String, + types: String, + global_imports: String, + imported_resources: HashMap, +} + +impl ScalaJsWorld { + fn new(context: &ScalaJsContext, resolve: &Resolve, world_id: WorldId, world: &World) -> Self { + let name = world.name.clone().to_snake_case(); + + let package_name = resolve.packages[world.package.expect("missing package for world")] + .name + .clone(); + + let package = + package_name_to_segments(&context.opts, &package_name, &Import, &context.keywords); + + let mut header = String::new(); + uwriteln!(header, "package {}", package.join(".")); + + uwriteln!(header, ""); + uwriteln!(header, "import scala.scalajs.js"); + uwriteln!(header, "import scala.scalajs.js.annotation._"); + uwriteln!(header, "import {}wit._", context.opts.base_package_prefix()); + uwriteln!(header, ""); + uwriteln!(header, "package object {} {{", name); + + Self { + world_id, + header, + types: String::new(), + global_imports: String::new(), + imported_resources: HashMap::new(), + } + } + + fn add_imported_function( + &mut self, + context: &ScalaJsContext, + resolve: &Resolve, + func_name: &str, + func: &Function, + ) { + match func.kind { + FunctionKind::Freestanding => { + uwriteln!(self.global_imports, " @js.native"); + uwriteln!( + self.global_imports, + " @JSImport(\"{}\", JSImport.Default)", + func_name + ); + let encoded_name = context.encode_name(func_name.to_lower_camel_case()); + let args = context.render_args(context, resolve, func.params.iter()); + let ret = context.render_return_type(context, resolve, &func.results); + + write_doc_comment(&mut self.global_imports, " ", &func.docs); + uwriteln!( + self.global_imports, + " def {}({args}): {ret} = js.native", + encoded_name.scala + ); + } + FunctionKind::Method(resource_type) + | FunctionKind::Static(resource_type) + | FunctionKind::Constructor(resource_type) => { + let resource = self + .imported_resources + .entry(resource_type) + .or_insert_with(|| { + ScalaJsImportedResource::new(context, resolve, resource_type, " ") + }); + resource.add_function(context, resolve, context, func_name, func); + } + } + } + + fn add_type(&mut self, context: &ScalaJsContext, resolve: &Resolve, name: &str, id: &TypeId) { + let typ = &resolve.types[*id]; + if let Some(typ) = context.render_typedef(context, resolve, name, typ) { + uwriteln!(self.types, "{}", typ); + uwriteln!(self.types, ""); + } + + if let TypeDefKind::Resource = &typ.kind { + self.imported_resources + .entry(*id) + .or_insert_with(|| ScalaJsImportedResource::new(context, resolve, *id, " ")); + } + } + + fn finalize(mut self, context: &ScalaJsContext, resolve: &Resolve) -> ScalaJsFile { + let mut source = String::new(); + uwriteln!(source, "{}", self.header); + uwriteln!(source, "{}", self.types); + uwriteln!(source, "{}", self.global_imports); + + for (_, mut imported_resource) in self.imported_resources.drain() { + imported_resource.annotate(&format!( + "@JSImport(\"{}\", JSImport.Default)", + imported_resource.name.js + )); + uwriteln!(source, "{}", imported_resource.finalize()); + } + + uwriteln!(source, "}}"); + + let world = &resolve.worlds[self.world_id]; + let package_name = resolve.packages[world.package.expect("missing package for world")] + .name + .clone(); + + let package = + package_name_to_segments(&context.opts, &package_name, &Import, &context.keywords); + + ScalaJsFile { + package, + name: world.name.to_snake_case(), + source, + } + } +} + struct ScalaJsInterface<'a> { wit_name: String, name: String, @@ -858,7 +984,7 @@ struct ScalaJsInterface<'a> { interface: &'a Interface, interface_id: InterfaceId, direction: Direction, - generator: &'a mut ScalaJsWorld, + generator: &'a mut ScalaJs, } impl<'a> ScalaJsInterface<'a> { @@ -868,7 +994,7 @@ impl<'a> ScalaJsInterface<'a> { resolve: &'a Resolve, interface_id: InterfaceId, direction: Direction, - generator: &'a mut ScalaJsWorld, + generator: &'a mut ScalaJs, ) -> Self { let interface = &resolve.interfaces[interface_id]; let name = interface @@ -883,10 +1009,10 @@ impl<'a> ScalaJsInterface<'a> { .clone(); let package = package_name_to_segments( - &generator.opts, + &generator.context.opts, &package_name, &direction, - &generator.keywords, + &generator.context.keywords, ); Self { @@ -987,14 +1113,17 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!( source, "import {}wit._", - self.generator.opts.base_package_prefix() + self.generator.context.opts.base_package_prefix() ); uwriteln!(source, ""); uwriteln!( source, "package object {} {{", - self.generator.keywords.escape(self.name.to_snake_case()) + self.generator + .context + .keywords + .escape(self.name.to_snake_case()) ); } @@ -1004,10 +1133,16 @@ impl<'a> ScalaJsInterface<'a> { for (type_name, type_id) in &self.interface.types { let type_def = &self.resolve.types[*type_id]; - let type_name = self.generator.overrides.get(type_id).unwrap_or(type_name); + let type_name = self + .generator + .context + .overrides + .get(type_id) + .unwrap_or(type_name); let type_name = if type_name.eq_ignore_ascii_case(&self.name) { let overridden_type_name = format!("{}Type", type_name); self.generator + .context .overrides .insert(*type_id, overridden_type_name.clone()); overridden_type_name @@ -1015,7 +1150,11 @@ impl<'a> ScalaJsInterface<'a> { type_name.clone() }; - if let Some(typ) = self.generator.render_typedef(self, &self.resolve, &type_name, type_def) { + if let Some(typ) = + self.generator + .context + .render_typedef(self, &self.resolve, &type_name, type_def) + { types.push(typ); } } @@ -1028,9 +1167,14 @@ impl<'a> ScalaJsInterface<'a> { for (_, type_id) in &self.interface.types { let type_def = &self.resolve.types[*type_id]; if let TypeDefKind::Resource = &type_def.kind { - imported_resources - .entry(*type_id) - .or_insert_with(|| ScalaJsImportedResource::new(self, *type_id)); + imported_resources.entry(*type_id).or_insert_with(|| { + ScalaJsImportedResource::new( + &self.generator.context, + self.resolve, + *type_id, + " ", + ) + }); } } @@ -1039,10 +1183,21 @@ impl<'a> ScalaJsInterface<'a> { FunctionKind::Method(resource_type) | FunctionKind::Static(resource_type) | FunctionKind::Constructor(resource_type) => { - let resource = imported_resources - .entry(resource_type) - .or_insert_with(|| ScalaJsImportedResource::new(self, resource_type)); - resource.add_function(func_name, func); + let resource = imported_resources.entry(resource_type).or_insert_with(|| { + ScalaJsImportedResource::new( + &self.generator.context, + self.resolve, + resource_type, + " ", + ) + }); + resource.add_function( + &self.generator.context, + self.resolve, + self, + func_name, + func, + ); } FunctionKind::Freestanding => {} } @@ -1084,12 +1239,22 @@ impl<'a> ScalaJsInterface<'a> { let mut functions = Vec::new(); for (func_name, func) in &self.interface.functions { - let func_name = self.generator.encode_name(func_name.to_lower_camel_case()); + let func_name = self + .generator + .context + .encode_name(func_name.to_lower_camel_case()); match func.kind { FunctionKind::Freestanding => { - let args = self.generator.render_args(self, self.resolve, func.params.iter()); - let ret = self.generator.render_return_type(self, self.resolve, &func.results); + let args = + self.generator + .context + .render_args(self, self.resolve, func.params.iter()); + let ret = self.generator.context.render_return_type( + self, + self.resolve, + &func.results, + ); let mut function = String::new(); write_doc_comment(&mut function, " ", &func.docs); @@ -1125,65 +1290,87 @@ impl<'a> ScalaJsInterface<'a> { } } -struct ScalaJsImportedResource<'a> { - owner: &'a ScalaJsInterface<'a>, +struct ScalaJsImportedResource { _resource_id: TypeId, resource_name: String, class_header: String, class_source: String, object_source: String, constructor_args: String, + name: EncodedName, + indent: String, + annotations: Vec, } -impl<'a> ScalaJsImportedResource<'a> { - pub fn new(owner: &'a ScalaJsInterface<'a>, resource_id: TypeId) -> Self { - let resource = &owner.resolve.types[resource_id]; +impl ScalaJsImportedResource { + pub fn new( + context: &ScalaJsContext, + resolve: &Resolve, + resource_id: TypeId, + indent: &str, + ) -> Self { + let resource = &resolve.types[resource_id]; let resource_name = resource .name .clone() .expect("Anonymous resources not supported"); - let encoded_resource_name = owner.generator.encode_name(resource_name.to_pascal_case()); + let encoded_resource_name = context.encode_name(resource_name.to_pascal_case()); let mut class_header = String::new(); write_doc_comment(&mut class_header, " ", &resource.docs); - uwriteln!(class_header, " @js.native"); + uwriteln!(class_header, "{indent}@js.native"); encoded_resource_name.write_rename_attribute(&mut class_header, " "); - uwrite!(class_header, " class {}(", encoded_resource_name.scala); + uwrite!( + class_header, + "{indent}class {}(", + encoded_resource_name.scala + ); let mut class_source = String::new(); uwriteln!(class_source, ") extends js.Object {{"); let mut object_source = String::new(); - uwriteln!(object_source, " @js.native"); + uwriteln!(object_source, "{indent}@js.native"); uwriteln!( object_source, - " object {} extends js.Object {{", + "{indent}object {} extends js.Object {{", encoded_resource_name.scala ); Self { - owner, _resource_id: resource_id, resource_name, class_header, class_source, object_source, constructor_args: String::new(), + name: encoded_resource_name, + indent: indent.to_string(), + annotations: Vec::new(), } } - pub fn add_function(&mut self, func_name: &str, func: &Function) { + pub fn annotate(&mut self, annotation: &str) { + self.annotations.push(annotation.to_string()); + } + + pub fn add_function( + &mut self, + context: &ScalaJsContext, + resolve: &Resolve, + owner_context: &impl OwnerContext, + func_name: &str, + func: &Function, + ) { match &func.kind { FunctionKind::Freestanding => unreachable!(), FunctionKind::Method(_) => { - let args = self.owner.generator.render_args(self.owner, self.owner.resolve, func.params.iter().skip(1)); - let ret = self.owner.generator.render_return_type(self.owner, self.owner.resolve, &func.results); - let encoded_func_name = self.get_func_name("[method]", func_name); + let args = context.render_args(owner_context, resolve, func.params.iter().skip(1)); + let ret = context.render_return_type(owner_context, resolve, &func.results); + let encoded_func_name = self.get_func_name(context, "[method]", func_name); - let overrd = if self - .owner - .generator + let overrd = if context .keywords .base_methods .contains(&encoded_func_name.scala) @@ -1193,52 +1380,72 @@ impl<'a> ScalaJsImportedResource<'a> { "" }; - write_doc_comment(&mut self.class_source, " ", &func.docs); + write_doc_comment( + &mut self.class_source, + &format!("{} ", self.indent), + &func.docs, + ); encoded_func_name.write_rename_attribute(&mut self.class_source, " "); uwriteln!( self.class_source, - " {overrd}def {}({args}): {ret} = js.native", + "{} {overrd}def {}({args}): {ret} = js.native", + self.indent, encoded_func_name.scala ); } FunctionKind::Static(_) => { - let args = self.owner.generator.render_args(self.owner, self.owner.resolve, func.params.iter()); - let ret = self.owner.generator.render_return_type(self.owner, self.owner.resolve, &func.results); + let args = context.render_args(owner_context, resolve, func.params.iter()); + let ret = context.render_return_type(owner_context, resolve, &func.results); - let encoded_func_name = self.get_func_name("[static]", func_name); + let encoded_func_name = self.get_func_name(context, "[static]", func_name); write_doc_comment(&mut self.object_source, " ", &func.docs); - encoded_func_name.write_rename_attribute(&mut self.object_source, " "); + encoded_func_name + .write_rename_attribute(&mut self.object_source, &format!("{} ", self.indent)); uwriteln!( self.object_source, - " def {}({args}): {ret} = js.native", + "{} def {}({args}): {ret} = js.native", + self.indent, encoded_func_name.scala ); } FunctionKind::Constructor(_) => { - let args = self.owner.generator.render_args(self.owner, self.owner.resolve, func.params.iter()); + let args = context.render_args(owner_context, resolve, func.params.iter()); self.constructor_args = args; } } } pub fn finalize(self) -> String { - let mut class_source = self.class_header; + let mut class_source = String::new(); + for annotation in &self.annotations { + uwriteln!(class_source, "{}{}", self.indent, annotation); + } + uwriteln!(class_source, "{}", self.class_header); uwrite!(class_source, "{}", self.constructor_args); uwriteln!(class_source, "{}", self.class_source); - uwriteln!(class_source, " }}"); - let mut object_source = self.object_source; - uwriteln!(object_source, " }}"); + uwriteln!(class_source, "{}}}", self.indent); + let mut object_source = String::new(); + for annotation in self.annotations { + uwriteln!(object_source, "{}{}", self.indent, annotation); + } + uwriteln!(object_source, "{}", self.object_source); + uwriteln!(object_source, "{}}}", self.indent); format!("{}\n{}\n", class_source, object_source) } - fn get_func_name(&self, prefix: &str, func_name: &str) -> EncodedName { + fn get_func_name( + &self, + context: &ScalaJsContext, + prefix: &str, + func_name: &str, + ) -> EncodedName { let name = func_name .strip_prefix(prefix) .unwrap() .strip_prefix(&self.resource_name) .unwrap() .to_lower_camel_case(); - self.owner.generator.encode_name(name) + context.encode_name(name) } } @@ -1259,7 +1466,10 @@ impl<'a> ScalaJsExportedResource<'a> { .name .clone() .expect("Anonymous resources not supported"); - let encoded_resource_name = owner.generator.encode_name(resource_name.to_pascal_case()); + let encoded_resource_name = owner + .generator + .context + .encode_name(resource_name.to_pascal_case()); let mut class_header = String::new(); write_doc_comment(&mut class_header, " ", &resource.docs); @@ -1289,13 +1499,22 @@ impl<'a> ScalaJsExportedResource<'a> { match &func.kind { FunctionKind::Freestanding => unreachable!(), FunctionKind::Method(_) => { - let args = self.owner.generator.render_args(self.owner, self.owner.resolve, func.params.iter().skip(1)); - let ret = self.owner.generator.render_return_type(self.owner, self.owner.resolve, &func.results); + let args = self.owner.generator.context.render_args( + self.owner, + self.owner.resolve, + func.params.iter().skip(1), + ); + let ret = self.owner.generator.context.render_return_type( + self.owner, + self.owner.resolve, + &func.results, + ); let encoded_func_name = self.get_func_name("[method]", func_name); let overrd = if self .owner .generator + .context .keywords .base_methods .contains(&encoded_func_name.scala) @@ -1314,8 +1533,16 @@ impl<'a> ScalaJsExportedResource<'a> { ); } FunctionKind::Static(_) => { - let args = self.owner.generator.render_args(self.owner, self.owner.resolve, func.params.iter()); - let ret = self.owner.generator.render_return_type(self.owner, self.owner.resolve, &func.results); + let args = self.owner.generator.context.render_args( + self.owner, + self.owner.resolve, + func.params.iter(), + ); + let ret = self.owner.generator.context.render_return_type( + self.owner, + self.owner.resolve, + &func.results, + ); let encoded_func_name = self.get_func_name("[static]", func_name); write_doc_comment(&mut self.static_methods, " ", &func.docs); @@ -1328,7 +1555,11 @@ impl<'a> ScalaJsExportedResource<'a> { ); } FunctionKind::Constructor(_) => { - let args = self.owner.generator.render_args(self.owner, self.owner.resolve, func.params.iter()); + let args = self.owner.generator.context.render_args( + self.owner, + self.owner.resolve, + func.params.iter(), + ); self.constructor_args = args; } } @@ -1344,6 +1575,7 @@ impl<'a> ScalaJsExportedResource<'a> { let scala_resource_name = self .owner .generator + .context .keywords .escape(self.resource_name.to_pascal_case()); uwriteln!(class_source, " trait {}Static {{", scala_resource_name); @@ -1359,7 +1591,7 @@ impl<'a> ScalaJsExportedResource<'a> { .strip_prefix(&self.resource_name) .unwrap() .to_lower_camel_case(); - self.owner.generator.encode_name(name) + self.owner.generator.context.encode_name(name) } } From 170bb196c700a728c273b63ab0825274297d9787 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Thu, 6 Feb 2025 18:18:05 +0100 Subject: [PATCH 11/36] World exports --- crates/scalajs/src/lib.rs | 100 +++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 13 deletions(-) diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index 718e4a79c..8fc64cda1 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -766,13 +766,25 @@ impl WorldGenerator for ScalaJs { fn export_funcs( &mut self, - _resolve: &Resolve, - _world: WorldId, + resolve: &Resolve, + world_id: WorldId, funcs: &[(&str, &Function)], _files: &mut Files, ) -> anyhow::Result<()> { - // TODO - println!("export_funcs: {:?}", funcs); + let world = &resolve.worlds[world_id]; + + if !self.world_defs.contains_key(&world_id) { + let world_def = ScalaJsWorld::new(&self.context, resolve, world_id, world); + self.world_defs.insert(world_id, world_def); + } + + for (func_name, func) in funcs { + self.world_defs + .get_mut(&world_id) + .unwrap() + .add_exported_function(&self.context, resolve, func_name, func); + } + Ok(()) } @@ -811,8 +823,10 @@ impl WorldGenerator for ScalaJs { } for (_, world_def) in self.world_defs.drain() { - let world_file = world_def.finalize(&self.context, resolve); - files.push(&world_file.path(), world_file.source.as_bytes()); + let world_files = world_def.finalize(&self.context, resolve); + for world_file in world_files { + files.push(&world_file.path(), world_file.source.as_bytes()); + } } let rt = render_runtime_module(&self.context.opts); @@ -857,6 +871,8 @@ struct ScalaJsWorld { types: String, global_imports: String, imported_resources: HashMap, + export_header: String, + global_exports: String } impl ScalaJsWorld { @@ -880,12 +896,27 @@ impl ScalaJsWorld { uwriteln!(header, ""); uwriteln!(header, "package object {} {{", name); + let export_package = + package_name_to_segments(&context.opts, &package_name, &Export, &context.keywords); + + let mut export_header = String::new(); + uwriteln!(export_header, "package {}", export_package.join(".")); + uwriteln!(export_header, ""); + uwriteln!(export_header, "import scala.scalajs.js"); + uwriteln!(export_header, "import scala.scalajs.js.annotation._"); + uwriteln!(export_header, "import {}wit._", context.opts.base_package_prefix()); + uwriteln!(export_header, ""); + uwriteln!(export_header, "package object {} {{", name); + uwriteln!(export_header, " trait {} extends js.Object {{", world.name.clone().to_pascal_case()); + Self { world_id, header, types: String::new(), global_imports: String::new(), imported_resources: HashMap::new(), + export_header, + global_exports: String::new() } } @@ -943,7 +974,35 @@ impl ScalaJsWorld { } } - fn finalize(mut self, context: &ScalaJsContext, resolve: &Resolve) -> ScalaJsFile { + fn add_exported_function( + &mut self, + context: &ScalaJsContext, + resolve: &Resolve, + func_name: &str, + func: &Function, + ) { + match func.kind { + FunctionKind::Freestanding => { + let encoded_name = context.encode_name(func_name.to_lower_camel_case()); + let args = context.render_args(context, resolve, func.params.iter()); + let ret = context.render_return_type(context, resolve, &func.results); + + write_doc_comment(&mut self.global_exports, " ", &func.docs); + uwriteln!( + self.global_exports, + " def {}({args}): {ret}", + encoded_name.scala + ); + } + FunctionKind::Method(resource_type) + | FunctionKind::Static(resource_type) + | FunctionKind::Constructor(resource_type) => { + panic!("Exported inline resource functions are not supported") + } + } + } + + fn finalize(mut self, context: &ScalaJsContext, resolve: &Resolve) -> Vec { let mut source = String::new(); uwriteln!(source, "{}", self.header); uwriteln!(source, "{}", self.types); @@ -959,6 +1018,12 @@ impl ScalaJsWorld { uwriteln!(source, "}}"); + let mut export_source = String::new(); + uwriteln!(export_source, "{}", self.export_header); + uwriteln!(export_source, "{}", self.global_exports); + uwriteln!(export_source, " }}"); + uwriteln!(export_source, "}}"); + let world = &resolve.worlds[self.world_id]; let package_name = resolve.packages[world.package.expect("missing package for world")] .name @@ -966,12 +1031,21 @@ impl ScalaJsWorld { let package = package_name_to_segments(&context.opts, &package_name, &Import, &context.keywords); - - ScalaJsFile { - package, - name: world.name.to_snake_case(), - source, - } + let export_package = + package_name_to_segments(&context.opts, &package_name, &Export, &context.keywords); + + vec![ + ScalaJsFile { + package, + name: world.name.to_snake_case(), + source, + }, + ScalaJsFile { + package: export_package, + name: world.name.to_snake_case(), + source: export_source + } + ] } } From e58fca0a2b6d2ceee64849425cd5a663eb126cd3 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Thu, 6 Feb 2025 18:41:57 +0100 Subject: [PATCH 12/36] Restructuring --- crates/scalajs/src/context.rs | 681 ++++++++++++++ crates/scalajs/src/interface.rs | 326 +++++++ crates/scalajs/src/lib.rs | 1562 ++----------------------------- crates/scalajs/src/resource.rs | 311 ++++++ crates/scalajs/src/rt.rs | 22 + crates/scalajs/src/world.rs | 214 +++++ 6 files changed, 1614 insertions(+), 1502 deletions(-) create mode 100644 crates/scalajs/src/context.rs create mode 100644 crates/scalajs/src/interface.rs create mode 100644 crates/scalajs/src/resource.rs create mode 100644 crates/scalajs/src/rt.rs create mode 100644 crates/scalajs/src/world.rs diff --git a/crates/scalajs/src/context.rs b/crates/scalajs/src/context.rs new file mode 100644 index 000000000..a500420c0 --- /dev/null +++ b/crates/scalajs/src/context.rs @@ -0,0 +1,681 @@ +use crate::interface::ScalaJsInterface; +use crate::jco::{maybe_null, to_js_identifier}; +use crate::{Opts, ScalaDialect, ScalaJs}; +use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; +use std::collections::{HashMap, HashSet}; +use std::fmt::Write; +use wit_bindgen_core::wit_parser::{ + Docs, Handle, InterfaceId, PackageName, Resolve, Results, Tuple, Type, TypeDef, TypeDefKind, + TypeId, TypeOwner, +}; +use wit_bindgen_core::Direction::{Export, Import}; +use wit_bindgen_core::{uwrite, uwriteln, Direction}; + +pub struct ScalaKeywords { + keywords: HashSet, + pub base_methods: HashSet, +} + +impl ScalaKeywords { + pub fn new(dialect: &ScalaDialect) -> Self { + let mut keywords = HashSet::new(); + keywords.insert("abstract".to_string()); + keywords.insert("case".to_string()); + keywords.insert("do".to_string()); + keywords.insert("else".to_string()); + keywords.insert("finally".to_string()); + keywords.insert("for".to_string()); + keywords.insert("import".to_string()); + keywords.insert("lazy".to_string()); + keywords.insert("object".to_string()); + keywords.insert("override".to_string()); + keywords.insert("return".to_string()); + keywords.insert("sealed".to_string()); + keywords.insert("trait".to_string()); + keywords.insert("try".to_string()); + keywords.insert("var".to_string()); + keywords.insert("while".to_string()); + keywords.insert("catch".to_string()); + keywords.insert("class".to_string()); + keywords.insert("extends".to_string()); + keywords.insert("false".to_string()); + keywords.insert("forSome".to_string()); + keywords.insert("if".to_string()); + keywords.insert("macro".to_string()); + keywords.insert("match".to_string()); + keywords.insert("new".to_string()); + keywords.insert("package".to_string()); + keywords.insert("private".to_string()); + keywords.insert("super".to_string()); + keywords.insert("this".to_string()); + keywords.insert("true".to_string()); + keywords.insert("type".to_string()); + keywords.insert("with".to_string()); + keywords.insert("yield".to_string()); + keywords.insert("def".to_string()); + keywords.insert("final".to_string()); + keywords.insert("implicit".to_string()); + keywords.insert("null".to_string()); + keywords.insert("protected".to_string()); + keywords.insert("throw".to_string()); + keywords.insert("val".to_string()); + keywords.insert("_".to_string()); + keywords.insert(":".to_string()); + keywords.insert("=".to_string()); + keywords.insert("=>".to_string()); + keywords.insert("<-".to_string()); + keywords.insert("<:".to_string()); + keywords.insert("<%".to_string()); + keywords.insert("=>>".to_string()); + keywords.insert(">:".to_string()); + keywords.insert("#".to_string()); + keywords.insert("@".to_string()); + keywords.insert("\u{21D2}".to_string()); + keywords.insert("\u{2190}".to_string()); + + let mut base_methods = HashSet::new(); + base_methods.insert("equals".to_string()); + base_methods.insert("hashCode".to_string()); + base_methods.insert("toString".to_string()); + base_methods.insert("clone".to_string()); + base_methods.insert("finalize".to_string()); + base_methods.insert("getClass".to_string()); + base_methods.insert("notify".to_string()); + base_methods.insert("notifyAll".to_string()); + base_methods.insert("wait".to_string()); + base_methods.insert("isInstanceOf".to_string()); + base_methods.insert("asInstanceOf".to_string()); + base_methods.insert("synchronized".to_string()); + base_methods.insert("ne".to_string()); + base_methods.insert("eq".to_string()); + base_methods.insert("hasOwnProperty".to_string()); + base_methods.insert("isPrototypeOf".to_string()); + base_methods.insert("propertyIsEnumerable".to_string()); + base_methods.insert("toLocaleString".to_string()); + base_methods.insert("valueOf".to_string()); + + match dialect { + ScalaDialect::Scala2 => {} + ScalaDialect::Scala3 => { + keywords.insert("enum".to_string()); + keywords.insert("export".to_string()); + keywords.insert("given".to_string()); + keywords.insert("?=>".to_string()); + keywords.insert("then".to_string()); + } + } + + Self { + keywords, + base_methods, + } + } + + pub(crate) fn escape(&self, ident: impl AsRef) -> String { + if self.keywords.contains(ident.as_ref()) { + format!("`{}`", ident.as_ref()) + } else { + ident.as_ref().to_string() + } + } +} + +// TODO: refactor to context, include resolve +pub trait OwnerContext { + fn is_local_import(&self, id: &InterfaceId, is_resource: bool) -> Option>; +} + +impl OwnerContext for ScalaJs { + fn is_local_import(&self, _id: &InterfaceId, _is_resource: bool) -> Option> { + None + } +} + +impl OwnerContext for ScalaJsContext { + fn is_local_import(&self, _id: &InterfaceId, _is_resource: bool) -> Option> { + None + } +} + +impl<'a> OwnerContext for ScalaJsInterface<'a> { + fn is_local_import(&self, id: &InterfaceId, is_resource: bool) -> Option> { + if id == &self.interface_id { + if is_resource && self.direction == Import { + Some(Some(self.name.clone())) + } else { + Some(None) + } + } else { + None + } + } +} + +pub struct ScalaJsContext { + pub opts: Opts, + pub keywords: ScalaKeywords, + pub overrides: HashMap, + pub imports: HashSet, + pub exports: HashSet, +} + +impl ScalaJsContext { + pub fn encode_name(&self, name: impl AsRef) -> EncodedName { + let name = name.as_ref(); + let scala_name = self.keywords.escape(name); + let js_name = to_js_identifier(name); + + let rename_attribute = if scala_name != js_name && scala_name != format!("`{js_name}`") { + format!("@JSName(\"{js_name}\")") + } else { + "".to_string() + }; + EncodedName { + scala: scala_name, + js: js_name, + rename_attribute, + } + } + + pub fn render_args<'b>( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + params: impl Iterator, + ) -> String { + let mut args = Vec::new(); + for (param_name, param_typ) in params { + let param_typ = self.render_type_reference(owner_context, resolve, param_typ); + let param_name = self.encode_name(param_name.to_lower_camel_case()); + args.push(format!("{}: {param_typ}", param_name.scala)); + } + args.join(", ") + } + + pub fn render_return_type( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + results: &Results, + ) -> String { + match results { + Results::Named(results) if results.len() == 0 => "Unit".to_string(), + Results::Named(results) if results.len() == 1 => self.render_type_reference( + owner_context, + resolve, + &results.iter().next().unwrap().1, + ), + Results::Named(results) => self.render_tuple( + owner_context, + resolve, + &Tuple { + types: results.iter().map(|(_, typ)| typ.clone()).collect(), + }, + ), + Results::Anon(typ) => self.render_type_reference(owner_context, resolve, typ), + } + } + + fn render_type_reference( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + typ: &Type, + ) -> String { + match typ { + Type::Bool => "Boolean".to_string(), + Type::U8 => "Byte".to_string(), + Type::U16 => "Short".to_string(), + Type::U32 => "Int".to_string(), + Type::U64 => "Long".to_string(), + Type::S8 => "Byte".to_string(), + Type::S16 => "Short".to_string(), + Type::S32 => "Int".to_string(), + Type::S64 => "Long".to_string(), + Type::F32 => "Float".to_string(), + Type::F64 => "Double".to_string(), + Type::Char => "Char".to_string(), + Type::String => "String".to_string(), + Type::Id(id) => { + let typ = &resolve.types[*id]; + self.render_typedef_reference(owner_context, resolve, typ) + } + } + } + + fn render_typedef_reference( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + typ: &TypeDef, + ) -> String { + match &typ.kind { + TypeDefKind::Record(_) + | TypeDefKind::Resource + | TypeDefKind::Flags(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Type(_) + | TypeDefKind::Variant(_) => { + let prefix = match self.render_owner( + owner_context, + resolve, + &typ.owner, + &typ.kind == &TypeDefKind::Resource, + ) { + Some(owner) => format!("{owner}."), + None => "".to_string(), + }; + format!( + "{}{}", + prefix, + self.keywords.escape( + typ.name + .clone() + .expect("Anonymous types are not supported") + .to_pascal_case() + ) + ) + } + TypeDefKind::Handle(handle) => { + let id = match handle { + Handle::Own(id) => id, + Handle::Borrow(id) => id, + }; + let typ = &resolve.types[*id]; + self.render_typedef_reference(owner_context, resolve, typ) + } + TypeDefKind::Tuple(tuple) => self.render_tuple(owner_context, resolve, tuple), + TypeDefKind::Option(option) => { + if !maybe_null(resolve, option) { + format!( + "Nullable[{}]", + self.render_type_reference(owner_context, resolve, option) + ) + } else { + format!( + "WitOption[{}]", + self.render_type_reference(owner_context, resolve, option) + ) + } + } + TypeDefKind::Result(result) => { + let ok = result + .ok + .map(|ok| self.render_type_reference(owner_context, resolve, &ok)) + .unwrap_or("Unit".to_string()); + let err = result + .err + .map(|err| self.render_type_reference(owner_context, resolve, &err)) + .unwrap_or("Unit".to_string()); + format!("WitResult[{ok}, {err}]") + } + TypeDefKind::List(list) => { + format!( + "WitList[{}]", + self.render_type_reference(owner_context, resolve, list) + ) + } + TypeDefKind::Future(_) => panic!("Futures not supported yet"), + TypeDefKind::Stream(_) => panic!("Streams not supported yet"), + TypeDefKind::ErrorContext => panic!("ErrorContext not supported yet"), + TypeDefKind::Unknown => panic!("Unknown type"), + } + } + + fn render_tuple( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + tuple: &Tuple, + ) -> String { + let arity = tuple.types.len(); + + let mut parts = Vec::new(); + for part in &tuple.types { + parts.push(self.render_type_reference(owner_context, resolve, part)); + } + format!("WitTuple{arity}[{}]", parts.join(", ")) + } + + fn render_owner( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + owner: &TypeOwner, + is_resource: bool, + ) -> Option { + match owner { + TypeOwner::World(id) => { + let world = &resolve.worlds[*id]; + + let name = world.name.clone().to_snake_case(); + + let package_name = resolve.packages + [world.package.expect("missing package for world")] + .name + .clone(); + + let mut package = + package_name_to_segments(&self.opts, &package_name, &Import, &self.keywords); + + package.push(self.keywords.escape(name)); + + Some(package.join(".")) + } + TypeOwner::Interface(id) => match owner_context.is_local_import(id, is_resource) { + Some(Some(name)) => Some(name), + Some(None) => None, + None => { + let iface = &resolve.interfaces[*id]; + let name = iface.name.clone().expect("Interface must have a name"); + let package_id = iface.package.expect("Interface must have a package"); + + let package = &resolve.packages[package_id]; + let direction = self.interface_direction(id); + + let mut segments = package_name_to_segments( + &self.opts, + &package.name, + &direction, + &self.keywords, + ); + segments.push(self.keywords.escape(name.to_snake_case())); + + if is_resource && direction == Import { + segments.push(self.keywords.escape(name.to_pascal_case())); + } + + Some(segments.join(".")) + } + }, + TypeOwner::None => None, + } + } + + pub(crate) fn render_typedef( + &self, + owner_context: &impl OwnerContext, + resolve: &Resolve, + name: &str, + typ: &TypeDef, + ) -> Option { + let encoded_name = self.encode_name(name.to_pascal_case()); + let scala_name = encoded_name.scala; + + let mut source = String::new(); + match &typ.kind { + TypeDefKind::Record(record) => { + let mut fields = Vec::new(); + for field in &record.fields { + let typ = self.render_type_reference(owner_context, resolve, &field.ty); + let field_name = self.encode_name(field.name.to_lower_camel_case()); + let field_name0 = self + .keywords + .escape(format!("{}0", field.name.to_lower_camel_case())); + fields.push((field_name, field_name0, typ, &field.docs)); + } + + write_doc_comment(&mut source, " ", &typ.docs); + uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); + for (field_name, _, typ, docs) in &fields { + write_doc_comment(&mut source, " ", &docs); + field_name.write_rename_attribute(&mut source, " "); + uwriteln!(source, " val {}: {typ}", field_name.scala); + } + uwriteln!(source, " }}"); + uwriteln!(source, ""); + uwriteln!(source, " case object {scala_name} {{"); + uwriteln!(source, " def apply("); + for (_, field_name0, typ, _) in &fields { + uwriteln!(source, " {field_name0}: {typ},"); + } + uwriteln!(source, " ): {scala_name} = {{"); + uwriteln!(source, " new {scala_name} {{"); + for (field_name, field_name0, typ, _) in &fields { + field_name.write_rename_attribute(&mut source, " "); + uwriteln!( + source, + " val {}: {typ} = {field_name0}", + field_name.scala + ); + } + uwriteln!(source, " }}"); + uwriteln!(source, " }}"); + uwriteln!(source, " }}"); + } + TypeDefKind::Resource => { + // resource wrappers are generated separately + } + TypeDefKind::Handle(_) => { + panic!("Unexpected top-level handle type"); + } + TypeDefKind::Flags(flags) => { + let mut fields = Vec::new(); + for flag in &flags.flags { + let typ = "Boolean".to_string(); + let field_name = self.encode_name(flag.name.to_lower_camel_case()); + let field_name0 = self + .keywords + .escape(format!("{}0", flag.name.to_lower_camel_case())); + fields.push((field_name, field_name0, typ, &flag.docs)); + } + + write_doc_comment(&mut source, " ", &typ.docs); + uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); + for (field_name, _, typ, docs) in &fields { + write_doc_comment(&mut source, " ", docs); + field_name.write_rename_attribute(&mut source, " "); + uwriteln!(source, " val {}: {typ}", field_name.scala); + } + uwriteln!(source, " }}"); + uwriteln!(source, ""); + uwriteln!(source, " case object {scala_name} {{"); + uwriteln!(source, " def apply("); + for (_, field_name0, typ, _) in &fields { + uwriteln!(source, " {field_name0}: {typ},"); + } + uwriteln!(source, " ): {scala_name} = {{"); + uwriteln!(source, " new {scala_name} {{"); + for (field_name, field_name0, typ, _) in &fields { + field_name.write_rename_attribute(&mut source, " "); + uwriteln!( + source, + " val {}: {typ} = {field_name0}", + field_name.scala + ); + } + uwriteln!(source, " }}"); + uwriteln!(source, " }}"); + uwriteln!(source, " }}"); + } + TypeDefKind::Tuple(tuple) => { + let arity = tuple.types.len(); + write_doc_comment(&mut source, " ", &typ.docs); + uwrite!(source, " type {scala_name} = WitTuple{arity}["); + for (idx, part) in tuple.types.iter().enumerate() { + let part = self.render_type_reference(owner_context, resolve, part); + uwrite!(source, "{part}"); + if idx < tuple.types.len() - 1 { + uwrite!(source, ", "); + } + } + uwriteln!(source, "]"); + } + TypeDefKind::Variant(variant) => { + write_doc_comment(&mut source, " ", &typ.docs); + uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); + uwriteln!(source, " type Type"); + uwriteln!(source, " val tag: String"); + uwriteln!(source, " val `val`: js.UndefOr[Type]"); + uwriteln!(source, " }}"); + uwriteln!(source, ""); + uwriteln!(source, " object {scala_name} {{"); + for case in &variant.cases { + let case_name = &case.name; + let scala_case_name = self.keywords.escape(case_name.to_lower_camel_case()); + match &case.ty { + Some(ty) => { + let typ = self.render_type_reference(owner_context, resolve, ty); + write_doc_comment(&mut source, " ", &case.docs); + uwriteln!(source, " def {scala_case_name}(value: {typ}): {scala_name} = new {scala_name} {{"); + uwriteln!(source, " type Type = {typ}"); + uwriteln!(source, " val tag: String = \"{case_name}\""); + uwriteln!(source, " val `val`: js.UndefOr[Type] = value"); + uwriteln!(source, " }}"); + } + None => { + write_doc_comment(&mut source, " ", &case.docs); + uwriteln!( + source, + " def {scala_case_name}(): {scala_name} = new {scala_name} {{" + ); + uwriteln!(source, " type Type = Unit"); + uwriteln!(source, " val tag: String = \"{case_name}\""); + uwriteln!(source, " val `val`: js.UndefOr[Type] = ()"); + uwriteln!(source, " }}"); + } + } + } + uwriteln!(source, " }}"); + } + TypeDefKind::Enum(enm) => { + write_doc_comment(&mut source, " ", &typ.docs); + uwriteln!(source, " sealed trait {scala_name} extends js.Object"); + uwriteln!(source, ""); + uwriteln!(source, " object {scala_name} {{"); + for case in &enm.cases { + let case_name = &case.name; + let scala_case_name = self.keywords.escape(case_name.to_lower_camel_case()); + write_doc_comment(&mut source, " ", &case.docs); + uwriteln!( + source, + " def {scala_case_name}: {scala_name} = \"{case_name}\".asInstanceOf[{scala_name}]", + ); + } + uwriteln!(source, " }}"); + } + TypeDefKind::Option(option) => { + write_doc_comment(&mut source, " ", &typ.docs); + let typ = self.render_type_reference(owner_context, resolve, option); + if !maybe_null(resolve, option) { + uwriteln!(source, " type {scala_name} = Nullable[{typ}]"); + } else { + uwriteln!(source, " type {scala_name} = WitOption[{typ}]"); + } + } + TypeDefKind::Result(result) => { + write_doc_comment(&mut source, " ", &typ.docs); + let ok = result + .ok + .map(|ok| self.render_type_reference(owner_context, resolve, &ok)) + .unwrap_or("Unit".to_string()); + let err = result + .err + .map(|err| self.render_type_reference(owner_context, resolve, &err)) + .unwrap_or("Unit".to_string()); + uwriteln!(source, " type {scala_name} = WitResult[{ok}, {err}]"); + } + TypeDefKind::List(list) => { + write_doc_comment(&mut source, " ", &typ.docs); + let typ = self.render_type_reference(owner_context, resolve, list); + uwriteln!(source, " type {scala_name} = WitList[{typ}]"); + } + TypeDefKind::Future(_) => { + panic!("Futures are not supported yet"); + } + TypeDefKind::Stream(_) => { + panic!("Streams are not supported yet"); + } + TypeDefKind::ErrorContext => { + panic!("ErrorContext is not supported yet"); + } + TypeDefKind::Type(reftyp) => { + write_doc_comment(&mut source, " ", &typ.docs); + let typ = self.render_type_reference(owner_context, resolve, reftyp); + uwriteln!(source, " type {scala_name} = {typ}"); + } + TypeDefKind::Unknown => { + panic!("Unknown type"); + } + } + + if source.len() > 0 { + Some(source) + } else { + None + } + } + + fn interface_direction(&self, id: &InterfaceId) -> Direction { + if self.imports.contains(id) { + Import + } else if self.exports.contains(id) { + Export + } else { + // Have not seen it yet, so it must be also an export + Export + } + } +} + +pub struct ScalaJsFile { + pub package: Vec, + pub name: String, + pub source: String, +} + +impl ScalaJsFile { + pub fn path(&self, optional_root: &Option) -> String { + // TODO: use PathBuf + match optional_root { + Some(root) => format!("{}/{}/{}.scala", root, self.package.join("/"), self.name), + None => format!("{}/{}.scala", self.package.join("/"), self.name), + } + } +} + +pub fn package_name_to_segments( + opts: &Opts, + package_name: &PackageName, + direction: &Direction, + keywords: &ScalaKeywords, +) -> Vec { + let mut segments = opts.base_package_segments(); + + if direction == &Export { + segments.push("export".to_string()); + } + + segments.push(package_name.namespace.to_snake_case()); + segments.push(package_name.name.to_snake_case()); + if let Some(version) = &package_name.version { + segments.push(format!("v{}", version.to_string().to_snake_case())); + } + segments.into_iter().map(|s| keywords.escape(s)).collect() +} + +pub fn write_doc_comment(source: &mut impl Write, indent: &str, docs: &Docs) { + // TODO: rewrite types in `` blocks? + if !docs.is_empty() { + uwriteln!(source, "{}/**", indent); + for line in docs.contents.as_ref().unwrap().lines() { + uwriteln!(source, "{} * {}", indent, line); + } + uwriteln!(source, "{} */", indent); + } +} + +#[allow(dead_code)] +pub struct EncodedName { + pub scala: String, + pub js: String, + pub rename_attribute: String, +} + +impl EncodedName { + pub fn write_rename_attribute(&self, target: &mut impl Write, ident: &str) { + if self.rename_attribute.len() > 0 { + uwriteln!(target, "{}{}", ident, self.rename_attribute); + } + } +} diff --git a/crates/scalajs/src/interface.rs b/crates/scalajs/src/interface.rs new file mode 100644 index 000000000..d980a34c5 --- /dev/null +++ b/crates/scalajs/src/interface.rs @@ -0,0 +1,326 @@ +use crate::context::{package_name_to_segments, write_doc_comment, ScalaJsFile}; +use crate::resource::{ScalaJsExportedResource, ScalaJsImportedResource}; +use crate::ScalaJs; +use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; +use std::collections::HashMap; +use std::fmt::Write; +use wit_bindgen_core::wit_parser::{ + FunctionKind, Interface, InterfaceId, Resolve, TypeDefKind, TypeId, +}; +use wit_bindgen_core::Direction::{Export, Import}; +use wit_bindgen_core::{uwriteln, Direction}; + +pub struct ScalaJsInterface<'a> { + wit_name: String, + pub(crate) name: String, + source: String, + package: Vec, + pub resolve: &'a Resolve, + interface: &'a Interface, + pub(crate) interface_id: InterfaceId, + pub(crate) direction: Direction, + pub(crate) generator: &'a mut ScalaJs, +} + +impl<'a> ScalaJsInterface<'a> { + // TODO: should just get a reference to ScalaJsWorld + pub fn new( + wit_name: String, + resolve: &'a Resolve, + interface_id: InterfaceId, + direction: Direction, + generator: &'a mut ScalaJs, + ) -> Self { + let interface = &resolve.interfaces[interface_id]; + let name = interface + .name + .clone() + .unwrap_or(wit_name.clone()) + .to_pascal_case(); + + let package_name = resolve.packages + [interface.package.expect("missing package for interface")] + .name + .clone(); + + let package = package_name_to_segments( + &generator.context.opts, + &package_name, + &direction, + &generator.context.keywords, + ); + + Self { + wit_name, + name, + source: "".to_string(), + package, + resolve, + interface, + interface_id, + direction, + generator, + } + } + + pub fn generate(&mut self) { + match self.direction { + Import => self.generate_import(), + Export => self.generate_export(), + } + } + + pub fn generate_import(&mut self) { + let mut source = String::new(); + self.generate_package_header(&mut source); + + let types = self.collect_type_definition_snippets(); + let imported_resources = self.collect_imported_resources(); + let functions = self.collect_function_snippets(); + + for typ in types { + uwriteln!(source, "{}", typ); + uwriteln!(source, ""); + } + + write_doc_comment(&mut source, " ", &self.interface.docs); + uwriteln!(source, " @js.native"); + uwriteln!(source, " trait {} extends js.Object {{", self.name); + + for (_, resource) in imported_resources { + uwriteln!(source, "{}", resource.finalize()); + uwriteln!(source, ""); + } + + for function in functions { + uwriteln!(source, "{function}"); + } + + uwriteln!(source, " }}"); + + uwriteln!(source, ""); + uwriteln!(source, " @js.native"); + uwriteln!( + source, + " @JSImport(\"{}\", JSImport.Namespace)", + self.wit_name + ); + uwriteln!(source, " object {} extends {}", self.name, self.name); + + uwriteln!(source, "}}"); + self.source = source; + } + + pub fn generate_export(&mut self) { + let mut source = String::new(); + self.generate_package_header(&mut source); + + let types = self.collect_type_definition_snippets(); + let exported_resources = self.collect_exported_resources(); + let functions = self.collect_function_snippets(); + + for typ in types { + uwriteln!(source, "{}", typ); + uwriteln!(source, ""); + } + + for (_, resource) in exported_resources { + uwriteln!(source, "{}", resource.finalize()); + uwriteln!(source, ""); + } + + write_doc_comment(&mut source, " ", &self.interface.docs); + uwriteln!(source, " trait {} extends js.Object {{", self.name); + for function in functions { + uwriteln!(source, "{function}"); + } + uwriteln!(source, " }}"); + + uwriteln!(source, "}}"); + self.source = source; + } + + fn generate_package_header(&mut self, source: &mut String) { + uwriteln!(source, "package {}", self.package.join(".")); + uwriteln!(source, ""); + uwriteln!(source, "import scala.scalajs.js"); + uwriteln!(source, "import scala.scalajs.js.annotation._"); + uwriteln!( + source, + "import {}wit._", + self.generator.context.opts.base_package_prefix() + ); + uwriteln!(source, ""); + + uwriteln!( + source, + "package object {} {{", + self.generator + .context + .keywords + .escape(self.name.to_snake_case()) + ); + } + + fn collect_type_definition_snippets(&mut self) -> Vec { + let mut types = Vec::new(); + + for (type_name, type_id) in &self.interface.types { + let type_def = &self.resolve.types[*type_id]; + + let type_name = self + .generator + .context + .overrides + .get(type_id) + .unwrap_or(type_name); + let type_name = if type_name.eq_ignore_ascii_case(&self.name) { + let overridden_type_name = format!("{}Type", type_name); + self.generator + .context + .overrides + .insert(*type_id, overridden_type_name.clone()); + overridden_type_name + } else { + type_name.clone() + }; + + if let Some(typ) = + self.generator + .context + .render_typedef(self, &self.resolve, &type_name, type_def) + { + types.push(typ); + } + } + + types + } + + fn collect_imported_resources(&self) -> HashMap { + let mut imported_resources = HashMap::new(); + for (_, type_id) in &self.interface.types { + let type_def = &self.resolve.types[*type_id]; + if let TypeDefKind::Resource = &type_def.kind { + imported_resources.entry(*type_id).or_insert_with(|| { + ScalaJsImportedResource::new( + &self.generator.context, + self.resolve, + *type_id, + " ", + ) + }); + } + } + + for (func_name, func) in &self.interface.functions { + match func.kind { + FunctionKind::Method(resource_type) + | FunctionKind::Static(resource_type) + | FunctionKind::Constructor(resource_type) => { + let resource = imported_resources.entry(resource_type).or_insert_with(|| { + ScalaJsImportedResource::new( + &self.generator.context, + self.resolve, + resource_type, + " ", + ) + }); + resource.add_function( + &self.generator.context, + self.resolve, + self, + func_name, + func, + ); + } + FunctionKind::Freestanding => {} + } + } + + imported_resources + } + + fn collect_exported_resources(&self) -> HashMap { + let mut exported_resources = HashMap::new(); + + for (_, type_id) in &self.interface.types { + let type_def = &self.resolve.types[*type_id]; + if let TypeDefKind::Resource = &type_def.kind { + exported_resources + .entry(*type_id) + .or_insert_with(|| ScalaJsExportedResource::new(self, *type_id)); + } + } + + for (func_name, func) in &self.interface.functions { + match func.kind { + FunctionKind::Method(resource_type) + | FunctionKind::Static(resource_type) + | FunctionKind::Constructor(resource_type) => { + let resource = exported_resources + .entry(resource_type) + .or_insert_with(|| ScalaJsExportedResource::new(self, resource_type)); + resource.add_function(func_name, func); + } + FunctionKind::Freestanding => {} + } + } + + exported_resources + } + + fn collect_function_snippets(&self) -> Vec { + let mut functions = Vec::new(); + + for (func_name, func) in &self.interface.functions { + let func_name = self + .generator + .context + .encode_name(func_name.to_lower_camel_case()); + + match func.kind { + FunctionKind::Freestanding => { + let args = + self.generator + .context + .render_args(self, self.resolve, func.params.iter()); + let ret = self.generator.context.render_return_type( + self, + self.resolve, + &func.results, + ); + + let mut function = String::new(); + write_doc_comment(&mut function, " ", &func.docs); + + let postfix = match self.direction { + Import => " = js.native", + Export => "", + }; + + func_name.write_rename_attribute(&mut function, " "); + uwriteln!( + function, + " def {}({args}): {ret}{postfix}", + func_name.scala + ); + functions.push(function); + } + FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::Constructor(_) => {} + } + } + + functions + } + + pub fn finalize(self) -> ScalaJsFile { + ScalaJsFile { + package: self.package, + name: self.name, + source: self.source, + } + } +} diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index 8fc64cda1..6cc5f518a 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -1,16 +1,20 @@ -mod jco; - -use crate::jco::{maybe_null, to_js_identifier}; -use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; +pub mod context; +pub mod interface; +pub mod jco; +pub mod resource; +pub mod rt; +pub mod world; + +use crate::context::{ScalaJsContext, ScalaJsFile, ScalaKeywords}; +use crate::interface::ScalaJsInterface; +use crate::rt::render_runtime_module; +use crate::world::ScalaJsWorld; use std::collections::{HashMap, HashSet}; -use std::fmt::{Display, Write}; +use std::fmt::Display; use std::str::FromStr; -use wit_bindgen_core::wit_parser::{ - Docs, Function, FunctionKind, Handle, Interface, InterfaceId, PackageName, Resolve, Results, - Tuple, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, World, WorldId, WorldKey, -}; +use wit_bindgen_core::wit_parser::{Function, InterfaceId, Resolve, TypeId, WorldId, WorldKey}; use wit_bindgen_core::Direction::{Export, Import}; -use wit_bindgen_core::{uwrite, uwriteln, Direction, Files, WorldGenerator}; +use wit_bindgen_core::{Files, WorldGenerator}; #[derive(Debug, Clone)] pub enum ScalaDialect { @@ -54,6 +58,12 @@ pub struct Opts { )] pub base_package: Option, + #[cfg_attr( + feature = "clap", + clap(long, help = "Base package for generated Scala.js skeleton code") + )] + pub skeleton_base_package: Option, + #[cfg_attr( feature = "clap", clap( @@ -63,7 +73,33 @@ pub struct Opts { ) )] pub scala_dialect: ScalaDialect, - // TODO: generate skeleton mode - single file with the exported things to be implemented - destructive, will be wired to an explicit sbt command + #[cfg_attr( + feature = "clap", + clap( + long, + help = "Generate a skeleton for implementing all the exports", + default_value = "scala2" + ) + )] + pub generate_skeleton: bool, + #[cfg_attr( + feature = "clap", + clap( + long, + help = "Relative root directory for placing the skeleton sources", + default_value = "scala2" + ) + )] + pub skeleton_root: Option, + #[cfg_attr( + feature = "clap", + clap( + long, + help = "Relative root directory for placing the binding sources", + default_value = "scala2" + ) + )] + pub binding_root: Option, } impl Opts { @@ -86,614 +122,7 @@ impl Opts { } } -struct ScalaKeywords { - keywords: HashSet, - base_methods: HashSet, -} - -impl ScalaKeywords { - pub fn new(dialect: &ScalaDialect) -> Self { - let mut keywords = HashSet::new(); - keywords.insert("abstract".to_string()); - keywords.insert("case".to_string()); - keywords.insert("do".to_string()); - keywords.insert("else".to_string()); - keywords.insert("finally".to_string()); - keywords.insert("for".to_string()); - keywords.insert("import".to_string()); - keywords.insert("lazy".to_string()); - keywords.insert("object".to_string()); - keywords.insert("override".to_string()); - keywords.insert("return".to_string()); - keywords.insert("sealed".to_string()); - keywords.insert("trait".to_string()); - keywords.insert("try".to_string()); - keywords.insert("var".to_string()); - keywords.insert("while".to_string()); - keywords.insert("catch".to_string()); - keywords.insert("class".to_string()); - keywords.insert("extends".to_string()); - keywords.insert("false".to_string()); - keywords.insert("forSome".to_string()); - keywords.insert("if".to_string()); - keywords.insert("macro".to_string()); - keywords.insert("match".to_string()); - keywords.insert("new".to_string()); - keywords.insert("package".to_string()); - keywords.insert("private".to_string()); - keywords.insert("super".to_string()); - keywords.insert("this".to_string()); - keywords.insert("true".to_string()); - keywords.insert("type".to_string()); - keywords.insert("with".to_string()); - keywords.insert("yield".to_string()); - keywords.insert("def".to_string()); - keywords.insert("final".to_string()); - keywords.insert("implicit".to_string()); - keywords.insert("null".to_string()); - keywords.insert("protected".to_string()); - keywords.insert("throw".to_string()); - keywords.insert("val".to_string()); - keywords.insert("_".to_string()); - keywords.insert(":".to_string()); - keywords.insert("=".to_string()); - keywords.insert("=>".to_string()); - keywords.insert("<-".to_string()); - keywords.insert("<:".to_string()); - keywords.insert("<%".to_string()); - keywords.insert("=>>".to_string()); - keywords.insert(">:".to_string()); - keywords.insert("#".to_string()); - keywords.insert("@".to_string()); - keywords.insert("\u{21D2}".to_string()); - keywords.insert("\u{2190}".to_string()); - - let mut base_methods = HashSet::new(); - base_methods.insert("equals".to_string()); - base_methods.insert("hashCode".to_string()); - base_methods.insert("toString".to_string()); - base_methods.insert("clone".to_string()); - base_methods.insert("finalize".to_string()); - base_methods.insert("getClass".to_string()); - base_methods.insert("notify".to_string()); - base_methods.insert("notifyAll".to_string()); - base_methods.insert("wait".to_string()); - base_methods.insert("isInstanceOf".to_string()); - base_methods.insert("asInstanceOf".to_string()); - base_methods.insert("synchronized".to_string()); - base_methods.insert("ne".to_string()); - base_methods.insert("eq".to_string()); - base_methods.insert("hasOwnProperty".to_string()); - base_methods.insert("isPrototypeOf".to_string()); - base_methods.insert("propertyIsEnumerable".to_string()); - base_methods.insert("toLocaleString".to_string()); - base_methods.insert("valueOf".to_string()); - - match dialect { - ScalaDialect::Scala2 => {} - ScalaDialect::Scala3 => { - keywords.insert("enum".to_string()); - keywords.insert("export".to_string()); - keywords.insert("given".to_string()); - keywords.insert("?=>".to_string()); - keywords.insert("then".to_string()); - } - } - - Self { - keywords, - base_methods, - } - } - - fn escape(&self, ident: impl AsRef) -> String { - if self.keywords.contains(ident.as_ref()) { - format!("`{}`", ident.as_ref()) - } else { - ident.as_ref().to_string() - } - } -} - -// TODO: refactor to context, include resolve -trait OwnerContext { - fn is_local_import(&self, id: &InterfaceId, is_resource: bool) -> Option>; -} - -impl OwnerContext for ScalaJs { - fn is_local_import(&self, _id: &InterfaceId, _is_resource: bool) -> Option> { - None - } -} - -impl OwnerContext for ScalaJsContext { - fn is_local_import(&self, _id: &InterfaceId, _is_resource: bool) -> Option> { - None - } -} - -impl<'a> OwnerContext for ScalaJsInterface<'a> { - fn is_local_import(&self, id: &InterfaceId, is_resource: bool) -> Option> { - if id == &self.interface_id { - if is_resource && self.direction == Import { - Some(Some(self.name.clone())) - } else { - Some(None) - } - } else { - None - } - } -} - -struct ScalaJsContext { - opts: Opts, - keywords: ScalaKeywords, - overrides: HashMap, - imports: HashSet, - exports: HashSet, -} - -impl ScalaJsContext { - pub fn encode_name(&self, name: impl AsRef) -> EncodedName { - let name = name.as_ref(); - let scala_name = self.keywords.escape(name); - let js_name = to_js_identifier(name); - - let rename_attribute = if scala_name != js_name && scala_name != format!("`{js_name}`") { - format!("@JSName(\"{js_name}\")") - } else { - "".to_string() - }; - EncodedName { - scala: scala_name, - js: js_name, - rename_attribute, - } - } - - fn render_args<'b>( - &self, - owner_context: &impl OwnerContext, - resolve: &Resolve, - params: impl Iterator, - ) -> String { - let mut args = Vec::new(); - for (param_name, param_typ) in params { - let param_typ = self.render_type_reference(owner_context, resolve, param_typ); - let param_name = self.encode_name(param_name.to_lower_camel_case()); - args.push(format!("{}: {param_typ}", param_name.scala)); - } - args.join(", ") - } - - fn render_return_type( - &self, - owner_context: &impl OwnerContext, - resolve: &Resolve, - results: &Results, - ) -> String { - match results { - Results::Named(results) if results.len() == 0 => "Unit".to_string(), - Results::Named(results) if results.len() == 1 => self.render_type_reference( - owner_context, - resolve, - &results.iter().next().unwrap().1, - ), - Results::Named(results) => self.render_tuple( - owner_context, - resolve, - &Tuple { - types: results.iter().map(|(_, typ)| typ.clone()).collect(), - }, - ), - Results::Anon(typ) => self.render_type_reference(owner_context, resolve, typ), - } - } - - fn render_type_reference( - &self, - owner_context: &impl OwnerContext, - resolve: &Resolve, - typ: &Type, - ) -> String { - match typ { - Type::Bool => "Boolean".to_string(), - Type::U8 => "Byte".to_string(), - Type::U16 => "Short".to_string(), - Type::U32 => "Int".to_string(), - Type::U64 => "Long".to_string(), - Type::S8 => "Byte".to_string(), - Type::S16 => "Short".to_string(), - Type::S32 => "Int".to_string(), - Type::S64 => "Long".to_string(), - Type::F32 => "Float".to_string(), - Type::F64 => "Double".to_string(), - Type::Char => "Char".to_string(), - Type::String => "String".to_string(), - Type::Id(id) => { - let typ = &resolve.types[*id]; - self.render_typedef_reference(owner_context, resolve, typ) - } - } - } - - fn render_typedef_reference( - &self, - owner_context: &impl OwnerContext, - resolve: &Resolve, - typ: &TypeDef, - ) -> String { - match &typ.kind { - TypeDefKind::Record(_) - | TypeDefKind::Resource - | TypeDefKind::Flags(_) - | TypeDefKind::Enum(_) - | TypeDefKind::Type(_) - | TypeDefKind::Variant(_) => { - let prefix = match self.render_owner( - owner_context, - resolve, - &typ.owner, - &typ.kind == &TypeDefKind::Resource, - ) { - Some(owner) => format!("{owner}."), - None => "".to_string(), - }; - format!( - "{}{}", - prefix, - self.keywords.escape( - typ.name - .clone() - .expect("Anonymous types are not supported") - .to_pascal_case() - ) - ) - } - TypeDefKind::Handle(handle) => { - let id = match handle { - Handle::Own(id) => id, - Handle::Borrow(id) => id, - }; - let typ = &resolve.types[*id]; - self.render_typedef_reference(owner_context, resolve, typ) - } - TypeDefKind::Tuple(tuple) => self.render_tuple(owner_context, resolve, tuple), - TypeDefKind::Option(option) => { - if !maybe_null(resolve, option) { - format!( - "Nullable[{}]", - self.render_type_reference(owner_context, resolve, option) - ) - } else { - format!( - "WitOption[{}]", - self.render_type_reference(owner_context, resolve, option) - ) - } - } - TypeDefKind::Result(result) => { - let ok = result - .ok - .map(|ok| self.render_type_reference(owner_context, resolve, &ok)) - .unwrap_or("Unit".to_string()); - let err = result - .err - .map(|err| self.render_type_reference(owner_context, resolve, &err)) - .unwrap_or("Unit".to_string()); - format!("WitResult[{ok}, {err}]") - } - TypeDefKind::List(list) => { - format!( - "WitList[{}]", - self.render_type_reference(owner_context, resolve, list) - ) - } - TypeDefKind::Future(_) => panic!("Futures not supported yet"), - TypeDefKind::Stream(_) => panic!("Streams not supported yet"), - TypeDefKind::ErrorContext => panic!("ErrorContext not supported yet"), - TypeDefKind::Unknown => panic!("Unknown type"), - } - } - - fn render_tuple( - &self, - owner_context: &impl OwnerContext, - resolve: &Resolve, - tuple: &Tuple, - ) -> String { - let arity = tuple.types.len(); - - let mut parts = Vec::new(); - for part in &tuple.types { - parts.push(self.render_type_reference(owner_context, resolve, part)); - } - format!("WitTuple{arity}[{}]", parts.join(", ")) - } - - fn render_owner( - &self, - owner_context: &impl OwnerContext, - resolve: &Resolve, - owner: &TypeOwner, - is_resource: bool, - ) -> Option { - match owner { - TypeOwner::World(id) => { - let world = &resolve.worlds[*id]; - - let name = world.name.clone().to_snake_case(); - - let package_name = resolve.packages - [world.package.expect("missing package for world")] - .name - .clone(); - - let mut package = - package_name_to_segments(&self.opts, &package_name, &Import, &self.keywords); - - package.push(self.keywords.escape(name)); - - Some(package.join(".")) - } - TypeOwner::Interface(id) => match owner_context.is_local_import(id, is_resource) { - Some(Some(name)) => Some(name), - Some(None) => None, - None => { - let iface = &resolve.interfaces[*id]; - let name = iface.name.clone().expect("Interface must have a name"); - let package_id = iface.package.expect("Interface must have a package"); - - let package = &resolve.packages[package_id]; - let direction = self.interface_direction(id); - - let mut segments = package_name_to_segments( - &self.opts, - &package.name, - &direction, - &self.keywords, - ); - segments.push(self.keywords.escape(name.to_snake_case())); - - if is_resource && direction == Import { - segments.push(self.keywords.escape(name.to_pascal_case())); - } - - Some(segments.join(".")) - } - }, - TypeOwner::None => None, - } - } - - fn render_typedef( - &self, - owner_context: &impl OwnerContext, - resolve: &Resolve, - name: &str, - typ: &TypeDef, - ) -> Option { - let encoded_name = self.encode_name(name.to_pascal_case()); - let scala_name = encoded_name.scala; - - let mut source = String::new(); - match &typ.kind { - TypeDefKind::Record(record) => { - let mut fields = Vec::new(); - for field in &record.fields { - let typ = self.render_type_reference(owner_context, resolve, &field.ty); - let field_name = self.encode_name(field.name.to_lower_camel_case()); - let field_name0 = self - .keywords - .escape(format!("{}0", field.name.to_lower_camel_case())); - fields.push((field_name, field_name0, typ, &field.docs)); - } - - write_doc_comment(&mut source, " ", &typ.docs); - uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); - for (field_name, _, typ, docs) in &fields { - write_doc_comment(&mut source, " ", &docs); - field_name.write_rename_attribute(&mut source, " "); - uwriteln!(source, " val {}: {typ}", field_name.scala); - } - uwriteln!(source, " }}"); - uwriteln!(source, ""); - uwriteln!(source, " case object {scala_name} {{"); - uwriteln!(source, " def apply("); - for (_, field_name0, typ, _) in &fields { - uwriteln!(source, " {field_name0}: {typ},"); - } - uwriteln!(source, " ): {scala_name} = {{"); - uwriteln!(source, " new {scala_name} {{"); - for (field_name, field_name0, typ, _) in &fields { - field_name.write_rename_attribute(&mut source, " "); - uwriteln!( - source, - " val {}: {typ} = {field_name0}", - field_name.scala - ); - } - uwriteln!(source, " }}"); - uwriteln!(source, " }}"); - uwriteln!(source, " }}"); - } - TypeDefKind::Resource => { - // resource wrappers are generated separately - } - TypeDefKind::Handle(_) => { - panic!("Unexpected top-level handle type"); - } - TypeDefKind::Flags(flags) => { - let mut fields = Vec::new(); - for flag in &flags.flags { - let typ = "Boolean".to_string(); - let field_name = self.encode_name(flag.name.to_lower_camel_case()); - let field_name0 = self - .keywords - .escape(format!("{}0", flag.name.to_lower_camel_case())); - fields.push((field_name, field_name0, typ, &flag.docs)); - } - - write_doc_comment(&mut source, " ", &typ.docs); - uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); - for (field_name, _, typ, docs) in &fields { - write_doc_comment(&mut source, " ", docs); - field_name.write_rename_attribute(&mut source, " "); - uwriteln!(source, " val {}: {typ}", field_name.scala); - } - uwriteln!(source, " }}"); - uwriteln!(source, ""); - uwriteln!(source, " case object {scala_name} {{"); - uwriteln!(source, " def apply("); - for (_, field_name0, typ, _) in &fields { - uwriteln!(source, " {field_name0}: {typ},"); - } - uwriteln!(source, " ): {scala_name} = {{"); - uwriteln!(source, " new {scala_name} {{"); - for (field_name, field_name0, typ, _) in &fields { - field_name.write_rename_attribute(&mut source, " "); - uwriteln!( - source, - " val {}: {typ} = {field_name0}", - field_name.scala - ); - } - uwriteln!(source, " }}"); - uwriteln!(source, " }}"); - uwriteln!(source, " }}"); - } - TypeDefKind::Tuple(tuple) => { - let arity = tuple.types.len(); - write_doc_comment(&mut source, " ", &typ.docs); - uwrite!(source, " type {scala_name} = WitTuple{arity}["); - for (idx, part) in tuple.types.iter().enumerate() { - let part = self.render_type_reference(owner_context, resolve, part); - uwrite!(source, "{part}"); - if idx < tuple.types.len() - 1 { - uwrite!(source, ", "); - } - } - uwriteln!(source, "]"); - } - TypeDefKind::Variant(variant) => { - write_doc_comment(&mut source, " ", &typ.docs); - uwriteln!(source, " sealed trait {scala_name} extends js.Object {{"); - uwriteln!(source, " type Type"); - uwriteln!(source, " val tag: String"); - uwriteln!(source, " val `val`: js.UndefOr[Type]"); - uwriteln!(source, " }}"); - uwriteln!(source, ""); - uwriteln!(source, " object {scala_name} {{"); - for case in &variant.cases { - let case_name = &case.name; - let scala_case_name = self.keywords.escape(case_name.to_lower_camel_case()); - match &case.ty { - Some(ty) => { - let typ = self.render_type_reference(owner_context, resolve, ty); - write_doc_comment(&mut source, " ", &case.docs); - uwriteln!(source, " def {scala_case_name}(value: {typ}): {scala_name} = new {scala_name} {{"); - uwriteln!(source, " type Type = {typ}"); - uwriteln!(source, " val tag: String = \"{case_name}\""); - uwriteln!(source, " val `val`: js.UndefOr[Type] = value"); - uwriteln!(source, " }}"); - } - None => { - write_doc_comment(&mut source, " ", &case.docs); - uwriteln!( - source, - " def {scala_case_name}(): {scala_name} = new {scala_name} {{" - ); - uwriteln!(source, " type Type = Unit"); - uwriteln!(source, " val tag: String = \"{case_name}\""); - uwriteln!(source, " val `val`: js.UndefOr[Type] = ()"); - uwriteln!(source, " }}"); - } - } - } - uwriteln!(source, " }}"); - } - TypeDefKind::Enum(enm) => { - write_doc_comment(&mut source, " ", &typ.docs); - uwriteln!(source, " sealed trait {scala_name} extends js.Object"); - uwriteln!(source, ""); - uwriteln!(source, " object {scala_name} {{"); - for case in &enm.cases { - let case_name = &case.name; - let scala_case_name = self.keywords.escape(case_name.to_lower_camel_case()); - write_doc_comment(&mut source, " ", &case.docs); - uwriteln!( - source, - " def {scala_case_name}: {scala_name} = \"{case_name}\".asInstanceOf[{scala_name}]", - ); - } - uwriteln!(source, " }}"); - } - TypeDefKind::Option(option) => { - write_doc_comment(&mut source, " ", &typ.docs); - let typ = self.render_type_reference(owner_context, resolve, option); - if !maybe_null(resolve, option) { - uwriteln!(source, " type {scala_name} = Nullable[{typ}]"); - } else { - uwriteln!(source, " type {scala_name} = WitOption[{typ}]"); - } - } - TypeDefKind::Result(result) => { - write_doc_comment(&mut source, " ", &typ.docs); - let ok = result - .ok - .map(|ok| self.render_type_reference(owner_context, resolve, &ok)) - .unwrap_or("Unit".to_string()); - let err = result - .err - .map(|err| self.render_type_reference(owner_context, resolve, &err)) - .unwrap_or("Unit".to_string()); - uwriteln!(source, " type {scala_name} = WitResult[{ok}, {err}]"); - } - TypeDefKind::List(list) => { - write_doc_comment(&mut source, " ", &typ.docs); - let typ = self.render_type_reference(owner_context, resolve, list); - uwriteln!(source, " type {scala_name} = WitList[{typ}]"); - } - TypeDefKind::Future(_) => { - panic!("Futures are not supported yet"); - } - TypeDefKind::Stream(_) => { - panic!("Streams are not supported yet"); - } - TypeDefKind::ErrorContext => { - panic!("ErrorContext is not supported yet"); - } - TypeDefKind::Type(reftyp) => { - write_doc_comment(&mut source, " ", &typ.docs); - let typ = self.render_type_reference(owner_context, resolve, reftyp); - uwriteln!(source, " type {scala_name} = {typ}"); - } - TypeDefKind::Unknown => { - panic!("Unknown type"); - } - } - - if source.len() > 0 { - Some(source) - } else { - None - } - } - - fn interface_direction(&self, id: &InterfaceId) -> Direction { - if self.imports.contains(id) { - Import - } else if self.exports.contains(id) { - Export - } else { - // Have not seen it yet, so it must be also an export - Export - } - } -} - -struct ScalaJs { +pub struct ScalaJs { context: ScalaJsContext, generated_files: Vec, world_defs: HashMap, @@ -819,18 +248,27 @@ impl WorldGenerator for ScalaJs { files: &mut Files, ) -> anyhow::Result<()> { for file in &self.generated_files { - files.push(&file.path(), file.source.as_bytes()); + files.push( + &file.path(&self.context.opts.binding_root), + file.source.as_bytes(), + ); } for (_, world_def) in self.world_defs.drain() { let world_files = world_def.finalize(&self.context, resolve); for world_file in world_files { - files.push(&world_file.path(), world_file.source.as_bytes()); + files.push( + &world_file.path(&self.context.opts.binding_root), + world_file.source.as_bytes(), + ); } } let rt = render_runtime_module(&self.context.opts); - files.push(&rt.path(), rt.source.as_bytes()); + files.push( + &rt.path(&self.context.opts.binding_root), + rt.source.as_bytes(), + ); Ok(()) } @@ -852,883 +290,3 @@ impl ScalaJs { } } } - -struct ScalaJsFile { - package: Vec, - name: String, - source: String, -} - -impl ScalaJsFile { - fn path(&self) -> String { - format!("{}/{}.scala", self.package.join("/"), self.name) - } -} - -struct ScalaJsWorld { - world_id: WorldId, - header: String, - types: String, - global_imports: String, - imported_resources: HashMap, - export_header: String, - global_exports: String -} - -impl ScalaJsWorld { - fn new(context: &ScalaJsContext, resolve: &Resolve, world_id: WorldId, world: &World) -> Self { - let name = world.name.clone().to_snake_case(); - - let package_name = resolve.packages[world.package.expect("missing package for world")] - .name - .clone(); - - let package = - package_name_to_segments(&context.opts, &package_name, &Import, &context.keywords); - - let mut header = String::new(); - uwriteln!(header, "package {}", package.join(".")); - - uwriteln!(header, ""); - uwriteln!(header, "import scala.scalajs.js"); - uwriteln!(header, "import scala.scalajs.js.annotation._"); - uwriteln!(header, "import {}wit._", context.opts.base_package_prefix()); - uwriteln!(header, ""); - uwriteln!(header, "package object {} {{", name); - - let export_package = - package_name_to_segments(&context.opts, &package_name, &Export, &context.keywords); - - let mut export_header = String::new(); - uwriteln!(export_header, "package {}", export_package.join(".")); - uwriteln!(export_header, ""); - uwriteln!(export_header, "import scala.scalajs.js"); - uwriteln!(export_header, "import scala.scalajs.js.annotation._"); - uwriteln!(export_header, "import {}wit._", context.opts.base_package_prefix()); - uwriteln!(export_header, ""); - uwriteln!(export_header, "package object {} {{", name); - uwriteln!(export_header, " trait {} extends js.Object {{", world.name.clone().to_pascal_case()); - - Self { - world_id, - header, - types: String::new(), - global_imports: String::new(), - imported_resources: HashMap::new(), - export_header, - global_exports: String::new() - } - } - - fn add_imported_function( - &mut self, - context: &ScalaJsContext, - resolve: &Resolve, - func_name: &str, - func: &Function, - ) { - match func.kind { - FunctionKind::Freestanding => { - uwriteln!(self.global_imports, " @js.native"); - uwriteln!( - self.global_imports, - " @JSImport(\"{}\", JSImport.Default)", - func_name - ); - let encoded_name = context.encode_name(func_name.to_lower_camel_case()); - let args = context.render_args(context, resolve, func.params.iter()); - let ret = context.render_return_type(context, resolve, &func.results); - - write_doc_comment(&mut self.global_imports, " ", &func.docs); - uwriteln!( - self.global_imports, - " def {}({args}): {ret} = js.native", - encoded_name.scala - ); - } - FunctionKind::Method(resource_type) - | FunctionKind::Static(resource_type) - | FunctionKind::Constructor(resource_type) => { - let resource = self - .imported_resources - .entry(resource_type) - .or_insert_with(|| { - ScalaJsImportedResource::new(context, resolve, resource_type, " ") - }); - resource.add_function(context, resolve, context, func_name, func); - } - } - } - - fn add_type(&mut self, context: &ScalaJsContext, resolve: &Resolve, name: &str, id: &TypeId) { - let typ = &resolve.types[*id]; - if let Some(typ) = context.render_typedef(context, resolve, name, typ) { - uwriteln!(self.types, "{}", typ); - uwriteln!(self.types, ""); - } - - if let TypeDefKind::Resource = &typ.kind { - self.imported_resources - .entry(*id) - .or_insert_with(|| ScalaJsImportedResource::new(context, resolve, *id, " ")); - } - } - - fn add_exported_function( - &mut self, - context: &ScalaJsContext, - resolve: &Resolve, - func_name: &str, - func: &Function, - ) { - match func.kind { - FunctionKind::Freestanding => { - let encoded_name = context.encode_name(func_name.to_lower_camel_case()); - let args = context.render_args(context, resolve, func.params.iter()); - let ret = context.render_return_type(context, resolve, &func.results); - - write_doc_comment(&mut self.global_exports, " ", &func.docs); - uwriteln!( - self.global_exports, - " def {}({args}): {ret}", - encoded_name.scala - ); - } - FunctionKind::Method(resource_type) - | FunctionKind::Static(resource_type) - | FunctionKind::Constructor(resource_type) => { - panic!("Exported inline resource functions are not supported") - } - } - } - - fn finalize(mut self, context: &ScalaJsContext, resolve: &Resolve) -> Vec { - let mut source = String::new(); - uwriteln!(source, "{}", self.header); - uwriteln!(source, "{}", self.types); - uwriteln!(source, "{}", self.global_imports); - - for (_, mut imported_resource) in self.imported_resources.drain() { - imported_resource.annotate(&format!( - "@JSImport(\"{}\", JSImport.Default)", - imported_resource.name.js - )); - uwriteln!(source, "{}", imported_resource.finalize()); - } - - uwriteln!(source, "}}"); - - let mut export_source = String::new(); - uwriteln!(export_source, "{}", self.export_header); - uwriteln!(export_source, "{}", self.global_exports); - uwriteln!(export_source, " }}"); - uwriteln!(export_source, "}}"); - - let world = &resolve.worlds[self.world_id]; - let package_name = resolve.packages[world.package.expect("missing package for world")] - .name - .clone(); - - let package = - package_name_to_segments(&context.opts, &package_name, &Import, &context.keywords); - let export_package = - package_name_to_segments(&context.opts, &package_name, &Export, &context.keywords); - - vec![ - ScalaJsFile { - package, - name: world.name.to_snake_case(), - source, - }, - ScalaJsFile { - package: export_package, - name: world.name.to_snake_case(), - source: export_source - } - ] - } -} - -struct ScalaJsInterface<'a> { - wit_name: String, - name: String, - source: String, - package: Vec, - resolve: &'a Resolve, - interface: &'a Interface, - interface_id: InterfaceId, - direction: Direction, - generator: &'a mut ScalaJs, -} - -impl<'a> ScalaJsInterface<'a> { - // TODO: should just get a reference to ScalaJsWorld - pub fn new( - wit_name: String, - resolve: &'a Resolve, - interface_id: InterfaceId, - direction: Direction, - generator: &'a mut ScalaJs, - ) -> Self { - let interface = &resolve.interfaces[interface_id]; - let name = interface - .name - .clone() - .unwrap_or(wit_name.clone()) - .to_pascal_case(); - - let package_name = resolve.packages - [interface.package.expect("missing package for interface")] - .name - .clone(); - - let package = package_name_to_segments( - &generator.context.opts, - &package_name, - &direction, - &generator.context.keywords, - ); - - Self { - wit_name, - name, - source: "".to_string(), - package, - resolve, - interface, - interface_id, - direction, - generator, - } - } - - pub fn generate(&mut self) { - match self.direction { - Import => self.generate_import(), - Export => self.generate_export(), - } - } - - pub fn generate_import(&mut self) { - let mut source = String::new(); - self.generate_package_header(&mut source); - - let types = self.collect_type_definition_snippets(); - let imported_resources = self.collect_imported_resources(); - let functions = self.collect_function_snippets(); - - for typ in types { - uwriteln!(source, "{}", typ); - uwriteln!(source, ""); - } - - write_doc_comment(&mut source, " ", &self.interface.docs); - uwriteln!(source, " @js.native"); - uwriteln!(source, " trait {} extends js.Object {{", self.name); - - for (_, resource) in imported_resources { - uwriteln!(source, "{}", resource.finalize()); - uwriteln!(source, ""); - } - - for function in functions { - uwriteln!(source, "{function}"); - } - - uwriteln!(source, " }}"); - - uwriteln!(source, ""); - uwriteln!(source, " @js.native"); - uwriteln!( - source, - " @JSImport(\"{}\", JSImport.Namespace)", - self.wit_name - ); - uwriteln!(source, " object {} extends {}", self.name, self.name); - - uwriteln!(source, "}}"); - self.source = source; - } - - pub fn generate_export(&mut self) { - let mut source = String::new(); - self.generate_package_header(&mut source); - - let types = self.collect_type_definition_snippets(); - let exported_resources = self.collect_exported_resources(); - let functions = self.collect_function_snippets(); - - for typ in types { - uwriteln!(source, "{}", typ); - uwriteln!(source, ""); - } - - for (_, resource) in exported_resources { - uwriteln!(source, "{}", resource.finalize()); - uwriteln!(source, ""); - } - - write_doc_comment(&mut source, " ", &self.interface.docs); - uwriteln!(source, " trait {} extends js.Object {{", self.name); - for function in functions { - uwriteln!(source, "{function}"); - } - uwriteln!(source, " }}"); - - uwriteln!(source, "}}"); - self.source = source; - } - - fn generate_package_header(&mut self, source: &mut String) { - uwriteln!(source, "package {}", self.package.join(".")); - uwriteln!(source, ""); - uwriteln!(source, "import scala.scalajs.js"); - uwriteln!(source, "import scala.scalajs.js.annotation._"); - uwriteln!( - source, - "import {}wit._", - self.generator.context.opts.base_package_prefix() - ); - uwriteln!(source, ""); - - uwriteln!( - source, - "package object {} {{", - self.generator - .context - .keywords - .escape(self.name.to_snake_case()) - ); - } - - fn collect_type_definition_snippets(&mut self) -> Vec { - let mut types = Vec::new(); - - for (type_name, type_id) in &self.interface.types { - let type_def = &self.resolve.types[*type_id]; - - let type_name = self - .generator - .context - .overrides - .get(type_id) - .unwrap_or(type_name); - let type_name = if type_name.eq_ignore_ascii_case(&self.name) { - let overridden_type_name = format!("{}Type", type_name); - self.generator - .context - .overrides - .insert(*type_id, overridden_type_name.clone()); - overridden_type_name - } else { - type_name.clone() - }; - - if let Some(typ) = - self.generator - .context - .render_typedef(self, &self.resolve, &type_name, type_def) - { - types.push(typ); - } - } - - types - } - - fn collect_imported_resources(&self) -> HashMap { - let mut imported_resources = HashMap::new(); - for (_, type_id) in &self.interface.types { - let type_def = &self.resolve.types[*type_id]; - if let TypeDefKind::Resource = &type_def.kind { - imported_resources.entry(*type_id).or_insert_with(|| { - ScalaJsImportedResource::new( - &self.generator.context, - self.resolve, - *type_id, - " ", - ) - }); - } - } - - for (func_name, func) in &self.interface.functions { - match func.kind { - FunctionKind::Method(resource_type) - | FunctionKind::Static(resource_type) - | FunctionKind::Constructor(resource_type) => { - let resource = imported_resources.entry(resource_type).or_insert_with(|| { - ScalaJsImportedResource::new( - &self.generator.context, - self.resolve, - resource_type, - " ", - ) - }); - resource.add_function( - &self.generator.context, - self.resolve, - self, - func_name, - func, - ); - } - FunctionKind::Freestanding => {} - } - } - - imported_resources - } - - fn collect_exported_resources(&self) -> HashMap { - let mut exported_resources = HashMap::new(); - - for (_, type_id) in &self.interface.types { - let type_def = &self.resolve.types[*type_id]; - if let TypeDefKind::Resource = &type_def.kind { - exported_resources - .entry(*type_id) - .or_insert_with(|| ScalaJsExportedResource::new(self, *type_id)); - } - } - - for (func_name, func) in &self.interface.functions { - match func.kind { - FunctionKind::Method(resource_type) - | FunctionKind::Static(resource_type) - | FunctionKind::Constructor(resource_type) => { - let resource = exported_resources - .entry(resource_type) - .or_insert_with(|| ScalaJsExportedResource::new(self, resource_type)); - resource.add_function(func_name, func); - } - FunctionKind::Freestanding => {} - } - } - - exported_resources - } - - fn collect_function_snippets(&self) -> Vec { - let mut functions = Vec::new(); - - for (func_name, func) in &self.interface.functions { - let func_name = self - .generator - .context - .encode_name(func_name.to_lower_camel_case()); - - match func.kind { - FunctionKind::Freestanding => { - let args = - self.generator - .context - .render_args(self, self.resolve, func.params.iter()); - let ret = self.generator.context.render_return_type( - self, - self.resolve, - &func.results, - ); - - let mut function = String::new(); - write_doc_comment(&mut function, " ", &func.docs); - - let postfix = match self.direction { - Import => " = js.native", - Export => "", - }; - - func_name.write_rename_attribute(&mut function, " "); - uwriteln!( - function, - " def {}({args}): {ret}{postfix}", - func_name.scala - ); - functions.push(function); - } - FunctionKind::Method(_) - | FunctionKind::Static(_) - | FunctionKind::Constructor(_) => {} - } - } - - functions - } - - pub fn finalize(self) -> ScalaJsFile { - ScalaJsFile { - package: self.package, - name: self.name, - source: self.source, - } - } -} - -struct ScalaJsImportedResource { - _resource_id: TypeId, - resource_name: String, - class_header: String, - class_source: String, - object_source: String, - constructor_args: String, - name: EncodedName, - indent: String, - annotations: Vec, -} - -impl ScalaJsImportedResource { - pub fn new( - context: &ScalaJsContext, - resolve: &Resolve, - resource_id: TypeId, - indent: &str, - ) -> Self { - let resource = &resolve.types[resource_id]; - let resource_name = resource - .name - .clone() - .expect("Anonymous resources not supported"); - let encoded_resource_name = context.encode_name(resource_name.to_pascal_case()); - - let mut class_header = String::new(); - write_doc_comment(&mut class_header, " ", &resource.docs); - - uwriteln!(class_header, "{indent}@js.native"); - encoded_resource_name.write_rename_attribute(&mut class_header, " "); - uwrite!( - class_header, - "{indent}class {}(", - encoded_resource_name.scala - ); - - let mut class_source = String::new(); - uwriteln!(class_source, ") extends js.Object {{"); - - let mut object_source = String::new(); - uwriteln!(object_source, "{indent}@js.native"); - uwriteln!( - object_source, - "{indent}object {} extends js.Object {{", - encoded_resource_name.scala - ); - - Self { - _resource_id: resource_id, - resource_name, - class_header, - class_source, - object_source, - constructor_args: String::new(), - name: encoded_resource_name, - indent: indent.to_string(), - annotations: Vec::new(), - } - } - - pub fn annotate(&mut self, annotation: &str) { - self.annotations.push(annotation.to_string()); - } - - pub fn add_function( - &mut self, - context: &ScalaJsContext, - resolve: &Resolve, - owner_context: &impl OwnerContext, - func_name: &str, - func: &Function, - ) { - match &func.kind { - FunctionKind::Freestanding => unreachable!(), - FunctionKind::Method(_) => { - let args = context.render_args(owner_context, resolve, func.params.iter().skip(1)); - let ret = context.render_return_type(owner_context, resolve, &func.results); - let encoded_func_name = self.get_func_name(context, "[method]", func_name); - - let overrd = if context - .keywords - .base_methods - .contains(&encoded_func_name.scala) - { - "override " - } else { - "" - }; - - write_doc_comment( - &mut self.class_source, - &format!("{} ", self.indent), - &func.docs, - ); - encoded_func_name.write_rename_attribute(&mut self.class_source, " "); - uwriteln!( - self.class_source, - "{} {overrd}def {}({args}): {ret} = js.native", - self.indent, - encoded_func_name.scala - ); - } - FunctionKind::Static(_) => { - let args = context.render_args(owner_context, resolve, func.params.iter()); - let ret = context.render_return_type(owner_context, resolve, &func.results); - - let encoded_func_name = self.get_func_name(context, "[static]", func_name); - write_doc_comment(&mut self.object_source, " ", &func.docs); - encoded_func_name - .write_rename_attribute(&mut self.object_source, &format!("{} ", self.indent)); - uwriteln!( - self.object_source, - "{} def {}({args}): {ret} = js.native", - self.indent, - encoded_func_name.scala - ); - } - FunctionKind::Constructor(_) => { - let args = context.render_args(owner_context, resolve, func.params.iter()); - self.constructor_args = args; - } - } - } - - pub fn finalize(self) -> String { - let mut class_source = String::new(); - for annotation in &self.annotations { - uwriteln!(class_source, "{}{}", self.indent, annotation); - } - uwriteln!(class_source, "{}", self.class_header); - uwrite!(class_source, "{}", self.constructor_args); - uwriteln!(class_source, "{}", self.class_source); - uwriteln!(class_source, "{}}}", self.indent); - let mut object_source = String::new(); - for annotation in self.annotations { - uwriteln!(object_source, "{}{}", self.indent, annotation); - } - uwriteln!(object_source, "{}", self.object_source); - uwriteln!(object_source, "{}}}", self.indent); - format!("{}\n{}\n", class_source, object_source) - } - - fn get_func_name( - &self, - context: &ScalaJsContext, - prefix: &str, - func_name: &str, - ) -> EncodedName { - let name = func_name - .strip_prefix(prefix) - .unwrap() - .strip_prefix(&self.resource_name) - .unwrap() - .to_lower_camel_case(); - context.encode_name(name) - } -} - -struct ScalaJsExportedResource<'a> { - owner: &'a ScalaJsInterface<'a>, - _resource_id: TypeId, - resource_name: String, - class_header: String, - class_source: String, - static_methods: String, - constructor_args: String, -} - -impl<'a> ScalaJsExportedResource<'a> { - pub fn new(owner: &'a ScalaJsInterface<'a>, resource_id: TypeId) -> Self { - let resource = &owner.resolve.types[resource_id]; - let resource_name = resource - .name - .clone() - .expect("Anonymous resources not supported"); - let encoded_resource_name = owner - .generator - .context - .encode_name(resource_name.to_pascal_case()); - - let mut class_header = String::new(); - write_doc_comment(&mut class_header, " ", &resource.docs); - - encoded_resource_name.write_rename_attribute(&mut class_header, " // "); - uwrite!( - class_header, - " abstract class {}(", - encoded_resource_name.scala - ); - - let mut class_source = String::new(); - uwriteln!(class_source, ") extends js.Object {{"); - - Self { - owner, - _resource_id: resource_id, - resource_name, - class_header, - class_source, - static_methods: String::new(), - constructor_args: String::new(), - } - } - - pub fn add_function(&mut self, func_name: &str, func: &Function) { - match &func.kind { - FunctionKind::Freestanding => unreachable!(), - FunctionKind::Method(_) => { - let args = self.owner.generator.context.render_args( - self.owner, - self.owner.resolve, - func.params.iter().skip(1), - ); - let ret = self.owner.generator.context.render_return_type( - self.owner, - self.owner.resolve, - &func.results, - ); - let encoded_func_name = self.get_func_name("[method]", func_name); - - let overrd = if self - .owner - .generator - .context - .keywords - .base_methods - .contains(&encoded_func_name.scala) - { - "override " - } else { - "" - }; - - write_doc_comment(&mut self.class_source, " ", &func.docs); - encoded_func_name.write_rename_attribute(&mut self.class_source, " "); - uwriteln!( - self.class_source, - " {overrd}def {}({args}): {ret}", - encoded_func_name.scala - ); - } - FunctionKind::Static(_) => { - let args = self.owner.generator.context.render_args( - self.owner, - self.owner.resolve, - func.params.iter(), - ); - let ret = self.owner.generator.context.render_return_type( - self.owner, - self.owner.resolve, - &func.results, - ); - - let encoded_func_name = self.get_func_name("[static]", func_name); - write_doc_comment(&mut self.static_methods, " ", &func.docs); - uwriteln!(self.static_methods, " // @JSExportStatic"); - encoded_func_name.write_rename_attribute(&mut self.static_methods, " "); - uwriteln!( - self.static_methods, - " def {}({args}): {ret}", - encoded_func_name.scala - ); - } - FunctionKind::Constructor(_) => { - let args = self.owner.generator.context.render_args( - self.owner, - self.owner.resolve, - func.params.iter(), - ); - self.constructor_args = args; - } - } - } - - pub fn finalize(self) -> String { - let mut class_source = self.class_header; - uwrite!(class_source, "{}", self.constructor_args); - uwriteln!(class_source, "{}", self.class_source); - uwriteln!(class_source, " }}"); - uwriteln!(class_source, ""); - - let scala_resource_name = self - .owner - .generator - .context - .keywords - .escape(self.resource_name.to_pascal_case()); - uwriteln!(class_source, " trait {}Static {{", scala_resource_name); - uwriteln!(class_source, "{}", self.static_methods); - uwriteln!(class_source, " }}"); - class_source - } - - fn get_func_name(&self, prefix: &str, func_name: &str) -> EncodedName { - let name = func_name - .strip_prefix(prefix) - .unwrap() - .strip_prefix(&self.resource_name) - .unwrap() - .to_lower_camel_case(); - self.owner.generator.context.encode_name(name) - } -} - -fn render_runtime_module(opts: &Opts) -> ScalaJsFile { - let wit_scala = include_str!("../scala/wit.scala"); - - let mut package = opts.base_package_segments(); - package.push("wit".to_string()); - - let mut source = String::new(); - uwriteln!(source, "package {}", opts.base_package_segments().join(".")); - uwriteln!(source, ""); - uwriteln!(source, "{wit_scala}"); - - ScalaJsFile { - package, - name: "package".to_string(), - source, - } -} - -fn package_name_to_segments( - opts: &Opts, - package_name: &PackageName, - direction: &Direction, - keywords: &ScalaKeywords, -) -> Vec { - let mut segments = opts.base_package_segments(); - - if direction == &Export { - segments.push("export".to_string()); - } - - segments.push(package_name.namespace.to_snake_case()); - segments.push(package_name.name.to_snake_case()); - if let Some(version) = &package_name.version { - segments.push(format!("v{}", version.to_string().to_snake_case())); - } - segments.into_iter().map(|s| keywords.escape(s)).collect() -} - -fn write_doc_comment(source: &mut impl Write, indent: &str, docs: &Docs) { - // TODO: rewrite types in `` blocks? - if !docs.is_empty() { - uwriteln!(source, "{}/**", indent); - for line in docs.contents.as_ref().unwrap().lines() { - uwriteln!(source, "{} * {}", indent, line); - } - uwriteln!(source, "{} */", indent); - } -} - -#[allow(dead_code)] -struct EncodedName { - pub scala: String, - pub js: String, - pub rename_attribute: String, -} - -impl EncodedName { - pub fn write_rename_attribute(&self, target: &mut impl Write, ident: &str) { - if self.rename_attribute.len() > 0 { - uwriteln!(target, "{}{}", ident, self.rename_attribute); - } - } -} diff --git a/crates/scalajs/src/resource.rs b/crates/scalajs/src/resource.rs new file mode 100644 index 000000000..dc7d995ec --- /dev/null +++ b/crates/scalajs/src/resource.rs @@ -0,0 +1,311 @@ +use crate::context::{write_doc_comment, EncodedName, OwnerContext, ScalaJsContext}; +use crate::interface::ScalaJsInterface; +use heck::{ToLowerCamelCase, ToPascalCase}; +use std::fmt::Write; +use wit_bindgen_core::wit_parser::{Function, FunctionKind, Resolve, TypeId}; +use wit_bindgen_core::{uwrite, uwriteln}; + +pub struct ScalaJsImportedResource { + _resource_id: TypeId, + resource_name: String, + class_header: String, + class_source: String, + object_source: String, + constructor_args: String, + pub name: EncodedName, + indent: String, + annotations: Vec, +} + +impl ScalaJsImportedResource { + pub fn new( + context: &ScalaJsContext, + resolve: &Resolve, + resource_id: TypeId, + indent: &str, + ) -> Self { + let resource = &resolve.types[resource_id]; + let resource_name = resource + .name + .clone() + .expect("Anonymous resources not supported"); + let encoded_resource_name = context.encode_name(resource_name.to_pascal_case()); + + let mut class_header = String::new(); + write_doc_comment(&mut class_header, " ", &resource.docs); + + uwriteln!(class_header, "{indent}@js.native"); + encoded_resource_name.write_rename_attribute(&mut class_header, " "); + uwrite!( + class_header, + "{indent}class {}(", + encoded_resource_name.scala + ); + + let mut class_source = String::new(); + uwriteln!(class_source, ") extends js.Object {{"); + + let mut object_source = String::new(); + uwriteln!(object_source, "{indent}@js.native"); + uwriteln!( + object_source, + "{indent}object {} extends js.Object {{", + encoded_resource_name.scala + ); + + Self { + _resource_id: resource_id, + resource_name, + class_header, + class_source, + object_source, + constructor_args: String::new(), + name: encoded_resource_name, + indent: indent.to_string(), + annotations: Vec::new(), + } + } + + pub fn annotate(&mut self, annotation: &str) { + self.annotations.push(annotation.to_string()); + } + + pub fn add_function( + &mut self, + context: &ScalaJsContext, + resolve: &Resolve, + owner_context: &impl OwnerContext, + func_name: &str, + func: &Function, + ) { + match &func.kind { + FunctionKind::Freestanding => unreachable!(), + FunctionKind::Method(_) => { + let args = context.render_args(owner_context, resolve, func.params.iter().skip(1)); + let ret = context.render_return_type(owner_context, resolve, &func.results); + let encoded_func_name = self.get_func_name(context, "[method]", func_name); + + let overrd = if context + .keywords + .base_methods + .contains(&encoded_func_name.scala) + { + "override " + } else { + "" + }; + + write_doc_comment( + &mut self.class_source, + &format!("{} ", self.indent), + &func.docs, + ); + encoded_func_name.write_rename_attribute(&mut self.class_source, " "); + uwriteln!( + self.class_source, + "{} {overrd}def {}({args}): {ret} = js.native", + self.indent, + encoded_func_name.scala + ); + } + FunctionKind::Static(_) => { + let args = context.render_args(owner_context, resolve, func.params.iter()); + let ret = context.render_return_type(owner_context, resolve, &func.results); + + let encoded_func_name = self.get_func_name(context, "[static]", func_name); + write_doc_comment(&mut self.object_source, " ", &func.docs); + encoded_func_name + .write_rename_attribute(&mut self.object_source, &format!("{} ", self.indent)); + uwriteln!( + self.object_source, + "{} def {}({args}): {ret} = js.native", + self.indent, + encoded_func_name.scala + ); + } + FunctionKind::Constructor(_) => { + let args = context.render_args(owner_context, resolve, func.params.iter()); + self.constructor_args = args; + } + } + } + + pub fn finalize(self) -> String { + let mut class_source = String::new(); + for annotation in &self.annotations { + uwriteln!(class_source, "{}{}", self.indent, annotation); + } + uwriteln!(class_source, "{}", self.class_header); + uwrite!(class_source, "{}", self.constructor_args); + uwriteln!(class_source, "{}", self.class_source); + uwriteln!(class_source, "{}}}", self.indent); + let mut object_source = String::new(); + for annotation in self.annotations { + uwriteln!(object_source, "{}{}", self.indent, annotation); + } + uwriteln!(object_source, "{}", self.object_source); + uwriteln!(object_source, "{}}}", self.indent); + format!("{}\n{}\n", class_source, object_source) + } + + fn get_func_name( + &self, + context: &ScalaJsContext, + prefix: &str, + func_name: &str, + ) -> EncodedName { + let name = func_name + .strip_prefix(prefix) + .unwrap() + .strip_prefix(&self.resource_name) + .unwrap() + .to_lower_camel_case(); + context.encode_name(name) + } +} + +pub struct ScalaJsExportedResource<'a> { + owner: &'a ScalaJsInterface<'a>, + _resource_id: TypeId, + resource_name: String, + class_header: String, + class_source: String, + static_methods: String, + constructor_args: String, +} + +impl<'a> ScalaJsExportedResource<'a> { + pub fn new(owner: &'a ScalaJsInterface<'a>, resource_id: TypeId) -> Self { + let resource = &owner.resolve.types[resource_id]; + let resource_name = resource + .name + .clone() + .expect("Anonymous resources not supported"); + let encoded_resource_name = owner + .generator + .context + .encode_name(resource_name.to_pascal_case()); + + let mut class_header = String::new(); + write_doc_comment(&mut class_header, " ", &resource.docs); + + encoded_resource_name.write_rename_attribute(&mut class_header, " // "); + uwrite!( + class_header, + " abstract class {}(", + encoded_resource_name.scala + ); + + let mut class_source = String::new(); + uwriteln!(class_source, ") extends js.Object {{"); + + Self { + owner, + _resource_id: resource_id, + resource_name, + class_header, + class_source, + static_methods: String::new(), + constructor_args: String::new(), + } + } + + pub fn add_function(&mut self, func_name: &str, func: &Function) { + match &func.kind { + FunctionKind::Freestanding => unreachable!(), + FunctionKind::Method(_) => { + let args = self.owner.generator.context.render_args( + self.owner, + self.owner.resolve, + func.params.iter().skip(1), + ); + let ret = self.owner.generator.context.render_return_type( + self.owner, + self.owner.resolve, + &func.results, + ); + let encoded_func_name = self.get_func_name("[method]", func_name); + + let overrd = if self + .owner + .generator + .context + .keywords + .base_methods + .contains(&encoded_func_name.scala) + { + "override " + } else { + "" + }; + + write_doc_comment(&mut self.class_source, " ", &func.docs); + encoded_func_name.write_rename_attribute(&mut self.class_source, " "); + uwriteln!( + self.class_source, + " {overrd}def {}({args}): {ret}", + encoded_func_name.scala + ); + } + FunctionKind::Static(_) => { + let args = self.owner.generator.context.render_args( + self.owner, + self.owner.resolve, + func.params.iter(), + ); + let ret = self.owner.generator.context.render_return_type( + self.owner, + self.owner.resolve, + &func.results, + ); + + let encoded_func_name = self.get_func_name("[static]", func_name); + write_doc_comment(&mut self.static_methods, " ", &func.docs); + uwriteln!(self.static_methods, " // @JSExportStatic"); + encoded_func_name.write_rename_attribute(&mut self.static_methods, " "); + uwriteln!( + self.static_methods, + " def {}({args}): {ret}", + encoded_func_name.scala + ); + } + FunctionKind::Constructor(_) => { + let args = self.owner.generator.context.render_args( + self.owner, + self.owner.resolve, + func.params.iter(), + ); + self.constructor_args = args; + } + } + } + + pub fn finalize(self) -> String { + let mut class_source = self.class_header; + uwrite!(class_source, "{}", self.constructor_args); + uwriteln!(class_source, "{}", self.class_source); + uwriteln!(class_source, " }}"); + uwriteln!(class_source, ""); + + let scala_resource_name = self + .owner + .generator + .context + .keywords + .escape(self.resource_name.to_pascal_case()); + uwriteln!(class_source, " trait {}Static {{", scala_resource_name); + uwriteln!(class_source, "{}", self.static_methods); + uwriteln!(class_source, " }}"); + class_source + } + + fn get_func_name(&self, prefix: &str, func_name: &str) -> EncodedName { + let name = func_name + .strip_prefix(prefix) + .unwrap() + .strip_prefix(&self.resource_name) + .unwrap() + .to_lower_camel_case(); + self.owner.generator.context.encode_name(name) + } +} diff --git a/crates/scalajs/src/rt.rs b/crates/scalajs/src/rt.rs new file mode 100644 index 000000000..0a6349ecc --- /dev/null +++ b/crates/scalajs/src/rt.rs @@ -0,0 +1,22 @@ +use wit_bindgen_core::uwriteln; +use crate::context::ScalaJsFile; +use crate::Opts; +use std::fmt::Write; + +pub fn render_runtime_module(opts: &Opts) -> ScalaJsFile { + let wit_scala = include_str!("../scala/wit.scala"); + + let mut package = opts.base_package_segments(); + package.push("wit".to_string()); + + let mut source = String::new(); + uwriteln!(source, "package {}", opts.base_package_segments().join(".")); + uwriteln!(source, ""); + uwriteln!(source, "{wit_scala}"); + + ScalaJsFile { + package, + name: "package".to_string(), + source, + } +} diff --git a/crates/scalajs/src/world.rs b/crates/scalajs/src/world.rs new file mode 100644 index 000000000..3000faf7c --- /dev/null +++ b/crates/scalajs/src/world.rs @@ -0,0 +1,214 @@ +use crate::context::{package_name_to_segments, write_doc_comment, ScalaJsContext}; +use crate::resource::ScalaJsImportedResource; +use crate::ScalaJsFile; +use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; +use std::collections::HashMap; +use std::fmt::Write; +use wit_bindgen_core::uwriteln; +use wit_bindgen_core::wit_parser::{ + Function, FunctionKind, Resolve, TypeDefKind, TypeId, World, WorldId, +}; +use wit_bindgen_core::Direction::{Export, Import}; + +pub struct ScalaJsWorld { + world_id: WorldId, + header: String, + types: String, + global_imports: String, + imported_resources: HashMap, + export_header: String, + global_exports: String, +} + +impl ScalaJsWorld { + pub fn new( + context: &ScalaJsContext, + resolve: &Resolve, + world_id: WorldId, + world: &World, + ) -> Self { + let name = world.name.clone().to_snake_case(); + + let package_name = resolve.packages[world.package.expect("missing package for world")] + .name + .clone(); + + let package = + package_name_to_segments(&context.opts, &package_name, &Import, &context.keywords); + + let mut header = String::new(); + uwriteln!(header, "package {}", package.join(".")); + + uwriteln!(header, ""); + uwriteln!(header, "import scala.scalajs.js"); + uwriteln!(header, "import scala.scalajs.js.annotation._"); + uwriteln!(header, "import {}wit._", context.opts.base_package_prefix()); + uwriteln!(header, ""); + uwriteln!(header, "package object {} {{", name); + + let export_package = + package_name_to_segments(&context.opts, &package_name, &Export, &context.keywords); + + let mut export_header = String::new(); + uwriteln!(export_header, "package {}", export_package.join(".")); + uwriteln!(export_header, ""); + uwriteln!(export_header, "import scala.scalajs.js"); + uwriteln!(export_header, "import scala.scalajs.js.annotation._"); + uwriteln!( + export_header, + "import {}wit._", + context.opts.base_package_prefix() + ); + uwriteln!(export_header, ""); + uwriteln!(export_header, "package object {} {{", name); + uwriteln!( + export_header, + " trait {} extends js.Object {{", + world.name.clone().to_pascal_case() + ); + + Self { + world_id, + header, + types: String::new(), + global_imports: String::new(), + imported_resources: HashMap::new(), + export_header, + global_exports: String::new(), + } + } + + pub fn add_imported_function( + &mut self, + context: &ScalaJsContext, + resolve: &Resolve, + func_name: &str, + func: &Function, + ) { + match func.kind { + FunctionKind::Freestanding => { + uwriteln!(self.global_imports, " @js.native"); + uwriteln!( + self.global_imports, + " @JSImport(\"{}\", JSImport.Default)", + func_name + ); + let encoded_name = context.encode_name(func_name.to_lower_camel_case()); + let args = context.render_args(context, resolve, func.params.iter()); + let ret = context.render_return_type(context, resolve, &func.results); + + write_doc_comment(&mut self.global_imports, " ", &func.docs); + uwriteln!( + self.global_imports, + " def {}({args}): {ret} = js.native", + encoded_name.scala + ); + } + FunctionKind::Method(resource_type) + | FunctionKind::Static(resource_type) + | FunctionKind::Constructor(resource_type) => { + let resource = self + .imported_resources + .entry(resource_type) + .or_insert_with(|| { + ScalaJsImportedResource::new(context, resolve, resource_type, " ") + }); + resource.add_function(context, resolve, context, func_name, func); + } + } + } + + pub fn add_type( + &mut self, + context: &ScalaJsContext, + resolve: &Resolve, + name: &str, + id: &TypeId, + ) { + let typ = &resolve.types[*id]; + if let Some(typ) = context.render_typedef(context, resolve, name, typ) { + uwriteln!(self.types, "{}", typ); + uwriteln!(self.types, ""); + } + + if let TypeDefKind::Resource = &typ.kind { + self.imported_resources + .entry(*id) + .or_insert_with(|| ScalaJsImportedResource::new(context, resolve, *id, " ")); + } + } + + pub fn add_exported_function( + &mut self, + context: &ScalaJsContext, + resolve: &Resolve, + func_name: &str, + func: &Function, + ) { + match func.kind { + FunctionKind::Freestanding => { + let encoded_name = context.encode_name(func_name.to_lower_camel_case()); + let args = context.render_args(context, resolve, func.params.iter()); + let ret = context.render_return_type(context, resolve, &func.results); + + write_doc_comment(&mut self.global_exports, " ", &func.docs); + uwriteln!( + self.global_exports, + " def {}({args}): {ret}", + encoded_name.scala + ); + } + FunctionKind::Method(_resource_type) + | FunctionKind::Static(_resource_type) + | FunctionKind::Constructor(_resource_type) => { + panic!("Exported inline resource functions are not supported") + } + } + } + + pub fn finalize(mut self, context: &ScalaJsContext, resolve: &Resolve) -> Vec { + let mut source = String::new(); + uwriteln!(source, "{}", self.header); + uwriteln!(source, "{}", self.types); + uwriteln!(source, "{}", self.global_imports); + + for (_, mut imported_resource) in self.imported_resources.drain() { + imported_resource.annotate(&format!( + "@JSImport(\"{}\", JSImport.Default)", + imported_resource.name.js + )); + uwriteln!(source, "{}", imported_resource.finalize()); + } + + uwriteln!(source, "}}"); + + let mut export_source = String::new(); + uwriteln!(export_source, "{}", self.export_header); + uwriteln!(export_source, "{}", self.global_exports); + uwriteln!(export_source, " }}"); + uwriteln!(export_source, "}}"); + + let world = &resolve.worlds[self.world_id]; + let package_name = resolve.packages[world.package.expect("missing package for world")] + .name + .clone(); + + let package = + package_name_to_segments(&context.opts, &package_name, &Import, &context.keywords); + let export_package = + package_name_to_segments(&context.opts, &package_name, &Export, &context.keywords); + + vec![ + ScalaJsFile { + package, + name: world.name.to_snake_case(), + source, + }, + ScalaJsFile { + package: export_package, + name: world.name.to_snake_case(), + source: export_source, + }, + ] + } +} From 28977912f0e42e97d79c3624cd31a3e6e5bc95c8 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Thu, 6 Feb 2025 21:14:31 +0100 Subject: [PATCH 13/36] Scala.js binding generator, feature complete --- crates/scalajs/src/context.rs | 170 ++++++++--- crates/scalajs/src/interface.rs | 34 +-- crates/scalajs/src/lib.rs | 60 +++- crates/scalajs/src/resource.rs | 10 +- crates/scalajs/src/skeleton.rs | 494 ++++++++++++++++++++++++++++++++ crates/scalajs/src/world.rs | 45 ++- crates/scalajs/tests/codegen.rs | 6 +- 7 files changed, 744 insertions(+), 75 deletions(-) create mode 100644 crates/scalajs/src/skeleton.rs diff --git a/crates/scalajs/src/context.rs b/crates/scalajs/src/context.rs index a500420c0..2f539c1b2 100644 --- a/crates/scalajs/src/context.rs +++ b/crates/scalajs/src/context.rs @@ -1,12 +1,13 @@ use crate::interface::ScalaJsInterface; use crate::jco::{maybe_null, to_js_identifier}; +use crate::skeleton::ScalaJsInterfaceSkeleton; use crate::{Opts, ScalaDialect, ScalaJs}; use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; use std::collections::{HashMap, HashSet}; use std::fmt::Write; use wit_bindgen_core::wit_parser::{ Docs, Handle, InterfaceId, PackageName, Resolve, Results, Tuple, Type, TypeDef, TypeDefKind, - TypeId, TypeOwner, + TypeId, TypeOwner, WorldItem, }; use wit_bindgen_core::Direction::{Export, Import}; use wit_bindgen_core::{uwrite, uwriteln, Direction}; @@ -120,30 +121,107 @@ impl ScalaKeywords { } } -// TODO: refactor to context, include resolve pub trait OwnerContext { - fn is_local_import(&self, id: &InterfaceId, is_resource: bool) -> Option>; + fn is_local_import( + &self, + id: &InterfaceId, + is_resource: bool, + direction: Direction, + ) -> Option>; } impl OwnerContext for ScalaJs { - fn is_local_import(&self, _id: &InterfaceId, _is_resource: bool) -> Option> { + fn is_local_import( + &self, + _id: &InterfaceId, + _is_resource: bool, + _direction: Direction, + ) -> Option> { None } } impl OwnerContext for ScalaJsContext { - fn is_local_import(&self, _id: &InterfaceId, _is_resource: bool) -> Option> { + fn is_local_import( + &self, + _id: &InterfaceId, + _is_resource: bool, + _direction: Direction, + ) -> Option> { None } } impl<'a> OwnerContext for ScalaJsInterface<'a> { - fn is_local_import(&self, id: &InterfaceId, is_resource: bool) -> Option> { + fn is_local_import( + &self, + id: &InterfaceId, + is_resource: bool, + direction: Direction, + ) -> Option> { if id == &self.interface_id { if is_resource && self.direction == Import { - Some(Some(self.name.clone())) + Some(Some(self.name.clone())) // Using a local type within 'name' + } else { + if self.direction == Export && direction == Import { + None // Using the fully qualified import package + } else { + Some(None) // Using the current export package + } + } + } else { + None // Using the fully qualified package + } + } +} + +impl<'a> OwnerContext for ScalaJsInterfaceSkeleton<'a> { + fn is_local_import( + &self, + id: &InterfaceId, + is_resource: bool, + direction: Direction, + ) -> Option> { + if is_resource && &self.interface_id == id { + let iface = &self.resolve.interfaces[*id]; + if iface.name.is_some() { + None // use the fully qualified name of the interface } else { - Some(None) + // Otherwise we must refer to the supertype directly from the world export here + for (_world_id, world) in self.resolve.worlds.iter() { + let world_name = + world + .exports + .iter() + .find_map(|(world_key, item)| match item { + WorldItem::Interface { id: iface_id, .. } if iface_id == id => { + Some(self.resolve.name_world_key(world_key)) + } + _ => None, + }); + + if let Some(world_name) = world_name { + let package_id = iface.package.expect("Interface must have a package"); + let package = &self.resolve.packages[package_id]; + + let mut segments = package_name_to_segments( + &self.generator.context.opts, + &package.name, + &direction, + &self.generator.context.keywords, + false, + ); + segments.push( + self.generator + .context + .keywords + .escape(world_name.to_snake_case()), + ); + return Some(Some(segments.join("."))); + } + } + + panic!("Anonymous interface was not found in any worlds"); } } else { None @@ -355,39 +433,48 @@ impl ScalaJsContext { .name .clone(); - let mut package = - package_name_to_segments(&self.opts, &package_name, &Import, &self.keywords); + let mut package = package_name_to_segments( + &self.opts, + &package_name, + &Import, + &self.keywords, + false, + ); package.push(self.keywords.escape(name)); Some(package.join(".")) } - TypeOwner::Interface(id) => match owner_context.is_local_import(id, is_resource) { - Some(Some(name)) => Some(name), - Some(None) => None, - None => { - let iface = &resolve.interfaces[*id]; - let name = iface.name.clone().expect("Interface must have a name"); - let package_id = iface.package.expect("Interface must have a package"); - - let package = &resolve.packages[package_id]; - let direction = self.interface_direction(id); - - let mut segments = package_name_to_segments( - &self.opts, - &package.name, - &direction, - &self.keywords, - ); - segments.push(self.keywords.escape(name.to_snake_case())); + TypeOwner::Interface(id) => { + let direction = self.interface_direction(id); + + match owner_context.is_local_import(id, is_resource, direction) { + Some(Some(name)) => Some(name), + Some(None) => None, + None => { + let iface = &resolve.interfaces[*id]; + let name = iface.name.clone().expect("Interface must have a name"); + let package_id = iface.package.expect("Interface must have a package"); + + let package = &resolve.packages[package_id]; + + let mut segments = package_name_to_segments( + &self.opts, + &package.name, + &direction, + &self.keywords, + false, + ); + segments.push(self.keywords.escape(name.to_snake_case())); + + if is_resource && direction == Import { + segments.push(self.keywords.escape(name.to_pascal_case())); + } - if is_resource && direction == Import { - segments.push(self.keywords.escape(name.to_pascal_case())); + Some(segments.join(".")) } - - Some(segments.join(".")) } - }, + } TypeOwner::None => None, } } @@ -606,7 +693,7 @@ impl ScalaJsContext { } } - fn interface_direction(&self, id: &InterfaceId) -> Direction { + pub fn interface_direction(&self, id: &InterfaceId) -> Direction { if self.imports.contains(id) { Import } else if self.exports.contains(id) { @@ -639,8 +726,13 @@ pub fn package_name_to_segments( package_name: &PackageName, direction: &Direction, keywords: &ScalaKeywords, + is_skeleton: bool, ) -> Vec { - let mut segments = opts.base_package_segments(); + let mut segments = if is_skeleton { + opts.base_skeleton_package_segments() + } else { + opts.base_package_segments() + }; if direction == &Export { segments.push("export".to_string()); @@ -678,4 +770,12 @@ impl EncodedName { uwriteln!(target, "{}{}", ident, self.rename_attribute); } } + + pub fn write_export_attribute(&self, target: &mut impl Write, ident: &str) { + uwriteln!(target, "{}@JSExport(\"{}\")", ident, self.js); + } + + pub fn write_static_export_attribute(&self, target: &mut impl Write, ident: &str) { + uwriteln!(target, "{}@JSExportStatic(\"{}\")", ident, self.js); + } } diff --git a/crates/scalajs/src/interface.rs b/crates/scalajs/src/interface.rs index d980a34c5..d7116a50e 100644 --- a/crates/scalajs/src/interface.rs +++ b/crates/scalajs/src/interface.rs @@ -12,14 +12,15 @@ use wit_bindgen_core::{uwriteln, Direction}; pub struct ScalaJsInterface<'a> { wit_name: String, - pub(crate) name: String, + pub name: String, + package_object_name: String, source: String, package: Vec, pub resolve: &'a Resolve, interface: &'a Interface, - pub(crate) interface_id: InterfaceId, - pub(crate) direction: Direction, - pub(crate) generator: &'a mut ScalaJs, + pub interface_id: InterfaceId, + pub direction: Direction, + pub generator: &'a mut ScalaJs, } impl<'a> ScalaJsInterface<'a> { @@ -48,11 +49,15 @@ impl<'a> ScalaJsInterface<'a> { &package_name, &direction, &generator.context.keywords, + false, ); + let package_object_name = generator.context.keywords.escape(name.to_snake_case()); + Self { wit_name, name, + package_object_name, source: "".to_string(), package, resolve, @@ -115,7 +120,9 @@ impl<'a> ScalaJsInterface<'a> { let mut source = String::new(); self.generate_package_header(&mut source); - let types = self.collect_type_definition_snippets(); + let also_imported = self.generator.context.interface_direction(&self.interface_id) == Import; + + let types = if also_imported { Vec::new() } else { self.collect_type_definition_snippets() }; let exported_resources = self.collect_exported_resources(); let functions = self.collect_function_snippets(); @@ -130,7 +137,7 @@ impl<'a> ScalaJsInterface<'a> { } write_doc_comment(&mut source, " ", &self.interface.docs); - uwriteln!(source, " trait {} extends js.Object {{", self.name); + uwriteln!(source, " trait {} extends {{", self.name); for function in functions { uwriteln!(source, "{function}"); } @@ -152,14 +159,7 @@ impl<'a> ScalaJsInterface<'a> { ); uwriteln!(source, ""); - uwriteln!( - source, - "package object {} {{", - self.generator - .context - .keywords - .escape(self.name.to_snake_case()) - ); + uwriteln!(source, "package object {} {{", self.package_object_name); } fn collect_type_definition_snippets(&mut self) -> Vec { @@ -299,7 +299,9 @@ impl<'a> ScalaJsInterface<'a> { Export => "", }; - func_name.write_rename_attribute(&mut function, " "); + if self.direction == Import { + func_name.write_rename_attribute(&mut function, " "); + } uwriteln!( function, " def {}({args}): {ret}{postfix}", @@ -319,7 +321,7 @@ impl<'a> ScalaJsInterface<'a> { pub fn finalize(self) -> ScalaJsFile { ScalaJsFile { package: self.package, - name: self.name, + name: self.package_object_name, source: self.source, } } diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index 6cc5f518a..eb5876865 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -3,11 +3,13 @@ pub mod interface; pub mod jco; pub mod resource; pub mod rt; +mod skeleton; pub mod world; use crate::context::{ScalaJsContext, ScalaJsFile, ScalaKeywords}; use crate::interface::ScalaJsInterface; use crate::rt::render_runtime_module; +use crate::skeleton::{ScalaJsInterfaceSkeleton, ScalaJsWorldSkeleton}; use crate::world::ScalaJsWorld; use std::collections::{HashMap, HashSet}; use std::fmt::Display; @@ -114,6 +116,14 @@ impl Opts { .unwrap_or_default() } + pub fn base_skeleton_package_segments(&self) -> Vec { + self.skeleton_base_package + .clone() + .or(self.base_package.clone()) + .map(|pkg| pkg.split('.').map(|s| s.to_string()).collect::>()) + .unwrap_or_default() + } + pub fn base_package_prefix(&self) -> String { match &self.base_package { Some(pkg) => format!("{pkg}."), @@ -125,7 +135,9 @@ impl Opts { pub struct ScalaJs { context: ScalaJsContext, generated_files: Vec, + generated_skeleton_files: Vec, world_defs: HashMap, + world_skeletons: HashMap, } impl WorldGenerator for ScalaJs { @@ -164,9 +176,16 @@ impl WorldGenerator for ScalaJs { let mut scalajs_iface = ScalaJsInterface::new(wit_name.clone(), resolve, iface, Export, self); scalajs_iface.generate(); - - let file = scalajs_iface.finalize(); - self.generated_files.push(file); + let binding_file = scalajs_iface.finalize(); + self.generated_files.push(binding_file); + + if self.context.opts.generate_skeleton { + let mut interface_skeleton = + ScalaJsInterfaceSkeleton::new(wit_name.clone(), resolve, iface, self); + interface_skeleton.generate(); + let skeleton_file = interface_skeleton.finalize(); + self.generated_skeleton_files.push(skeleton_file); + } Ok(()) } @@ -214,6 +233,21 @@ impl WorldGenerator for ScalaJs { .add_exported_function(&self.context, resolve, func_name, func); } + if self.context.opts.generate_skeleton { + if !self.world_skeletons.contains_key(&world_id) { + let world_skeleton = + ScalaJsWorldSkeleton::new(&self.context, resolve, world_id, world); + self.world_skeletons.insert(world_id, world_skeleton); + } + + for (func_name, func) in funcs { + self.world_skeletons + .get_mut(&world_id) + .unwrap() + .add_exported_function(&self.context, resolve, func_name, func); + } + } + Ok(()) } @@ -247,6 +281,12 @@ impl WorldGenerator for ScalaJs { _world: WorldId, files: &mut Files, ) -> anyhow::Result<()> { + let skeleton_root = &self.context.opts.skeleton_root.clone().or(self + .context + .opts + .binding_root + .clone()); + for file in &self.generated_files { files.push( &file.path(&self.context.opts.binding_root), @@ -254,6 +294,10 @@ impl WorldGenerator for ScalaJs { ); } + for file in &self.generated_skeleton_files { + files.push(&file.path(&skeleton_root), file.source.as_bytes()); + } + for (_, world_def) in self.world_defs.drain() { let world_files = world_def.finalize(&self.context, resolve); for world_file in world_files { @@ -264,6 +308,14 @@ impl WorldGenerator for ScalaJs { } } + for (_, world_skeleton) in self.world_skeletons.drain() { + let world_file = world_skeleton.finalize(resolve); + files.push( + &world_file.path(&skeleton_root), + world_file.source.as_bytes(), + ); + } + let rt = render_runtime_module(&self.context.opts); files.push( &rt.path(&self.context.opts.binding_root), @@ -286,7 +338,9 @@ impl ScalaJs { exports: HashSet::new(), }, generated_files: Vec::new(), + generated_skeleton_files: Vec::new(), world_defs: HashMap::new(), + world_skeletons: HashMap::new(), } } } diff --git a/crates/scalajs/src/resource.rs b/crates/scalajs/src/resource.rs index dc7d995ec..b9eff58d6 100644 --- a/crates/scalajs/src/resource.rs +++ b/crates/scalajs/src/resource.rs @@ -168,6 +168,7 @@ pub struct ScalaJsExportedResource<'a> { owner: &'a ScalaJsInterface<'a>, _resource_id: TypeId, resource_name: String, + encoded_resource_name: EncodedName, class_header: String, class_source: String, static_methods: String, @@ -203,6 +204,7 @@ impl<'a> ScalaJsExportedResource<'a> { owner, _resource_id: resource_id, resource_name, + encoded_resource_name, class_header, class_source, static_methods: String::new(), @@ -287,13 +289,7 @@ impl<'a> ScalaJsExportedResource<'a> { uwriteln!(class_source, " }}"); uwriteln!(class_source, ""); - let scala_resource_name = self - .owner - .generator - .context - .keywords - .escape(self.resource_name.to_pascal_case()); - uwriteln!(class_source, " trait {}Static {{", scala_resource_name); + uwriteln!(class_source, " trait {}Static {{", self.encoded_resource_name.scala); uwriteln!(class_source, "{}", self.static_methods); uwriteln!(class_source, " }}"); class_source diff --git a/crates/scalajs/src/skeleton.rs b/crates/scalajs/src/skeleton.rs new file mode 100644 index 000000000..48d7d85ec --- /dev/null +++ b/crates/scalajs/src/skeleton.rs @@ -0,0 +1,494 @@ +use crate::context::{ + package_name_to_segments, write_doc_comment, EncodedName, ScalaJsContext, ScalaJsFile, +}; +use crate::ScalaJs; +use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; +use std::collections::HashMap; +use std::fmt::Write; +use wit_bindgen_core::wit_parser::{ + Function, FunctionKind, Interface, InterfaceId, Resolve, TypeDefKind, TypeId, World, WorldId, +}; +use wit_bindgen_core::Direction::Export; +use wit_bindgen_core::{uwrite, uwriteln}; + +pub struct ScalaJsInterfaceSkeleton<'a> { + wit_name: String, + name: String, + source: String, + package: Vec, + binding_package: Vec, + pub resolve: &'a Resolve, + interface: &'a Interface, + pub interface_id: InterfaceId, + pub generator: &'a mut ScalaJs, +} + +impl<'a> ScalaJsInterfaceSkeleton<'a> { + pub fn new( + wit_name: String, + resolve: &'a Resolve, + interface_id: InterfaceId, + generator: &'a mut ScalaJs, + ) -> Self { + let interface = &resolve.interfaces[interface_id]; + let name = interface + .name + .clone() + .unwrap_or(wit_name.clone()) + .to_pascal_case(); + + let package_name = resolve.packages + [interface.package.expect("missing package for interface")] + .name + .clone(); + + let package = package_name_to_segments( + &generator.context.opts, + &package_name, + &Export, + &generator.context.keywords, + true, + ); + + let binding_package = package_name_to_segments( + &generator.context.opts, + &package_name, + &Export, + &generator.context.keywords, + false, + ); + + Self { + wit_name, + name, + source: "".to_string(), + package, + binding_package, + resolve, + interface, + interface_id, + generator, + } + } + + pub fn generate(&mut self) { + let mut source = String::new(); + uwriteln!(source, "package {}", self.package.join(".")); + uwriteln!(source, ""); + uwriteln!(source, "import scala.scalajs.js"); + uwriteln!(source, "import scala.scalajs.js.annotation._"); + uwriteln!( + source, + "import {}wit._", + self.generator.context.opts.base_package_prefix() + ); + uwriteln!(source, ""); + + let base_trait_name = format!( + "{}.{}.{}", + self.binding_package.join("."), + self.generator + .context + .keywords + .escape(self.name.to_snake_case()), + self.name + ); + + let encoded_wit_name = self.generator.context.encode_name(&self.wit_name); + + uwriteln!(source, "@JSExportTopLevel(\"{}\")", encoded_wit_name.js); + uwriteln!( + source, + "object {} extends {} {{", + self.name, + base_trait_name + ); + + for function in self.collect_function_snippets() { + uwriteln!(source, "{}", function); + } + + uwriteln!(source, "}}"); + + let exported_resources = self.collect_exported_resources(); + + uwriteln!(source, ""); + for (_, resource) in exported_resources { + uwriteln!(source, "{}", resource.finalize()); + uwriteln!(source, ""); + } + + self.source = source; + } + + pub fn finalize(self) -> ScalaJsFile { + ScalaJsFile { + package: self.package, + name: self.name, + source: self.source, + } + } + + fn collect_function_snippets(&self) -> Vec { + let mut functions = Vec::new(); + + for (func_name, func) in &self.interface.functions { + let func_name = self + .generator + .context + .encode_name(func_name.to_lower_camel_case()); + + match func.kind { + FunctionKind::Freestanding => { + let args = + self.generator + .context + .render_args(self, self.resolve, func.params.iter()); + let ret = self.generator.context.render_return_type( + self, + self.resolve, + &func.results, + ); + + let mut function = String::new(); + + func_name.write_export_attribute(&mut function, " "); + uwriteln!( + function, + " override def {}({args}): {ret} = {{", + func_name.scala + ); + uwriteln!(function, " ???"); + uwriteln!(function, " }}"); + functions.push(function); + } + FunctionKind::Method(_) + | FunctionKind::Static(_) + | FunctionKind::Constructor(_) => {} + } + } + + functions + } + + fn collect_exported_resources(&self) -> HashMap { + let mut exported_resources = HashMap::new(); + + for (_, type_id) in &self.interface.types { + let type_def = &self.resolve.types[*type_id]; + if let TypeDefKind::Resource = &type_def.kind { + exported_resources + .entry(*type_id) + .or_insert_with(|| ScalaJsExportedResourceSkeleton::new(self, *type_id)); + } + } + + for (func_name, func) in &self.interface.functions { + match func.kind { + FunctionKind::Method(resource_type) + | FunctionKind::Static(resource_type) + | FunctionKind::Constructor(resource_type) => { + let resource = exported_resources.entry(resource_type).or_insert_with(|| { + ScalaJsExportedResourceSkeleton::new(self, resource_type) + }); + resource.add_function(func_name, func); + } + FunctionKind::Freestanding => {} + } + } + + exported_resources + } +} + +pub struct ScalaJsWorldSkeleton { + world_id: WorldId, + export_header: String, + global_exports: String, + export_package: Vec, +} + +impl ScalaJsWorldSkeleton { + pub fn new( + context: &ScalaJsContext, + resolve: &Resolve, + world_id: WorldId, + world: &World, + ) -> Self { + let package_name = resolve.packages[world.package.expect("missing package for world")] + .name + .clone(); + + let export_package = package_name_to_segments( + &context.opts, + &package_name, + &Export, + &context.keywords, + true, + ); + + let binding_package = package_name_to_segments( + &context.opts, + &package_name, + &Export, + &context.keywords, + false, + ); + + let encoded_name = context.encode_name(world.name.to_pascal_case()); + + let base_trait_name = format!( + "{}.{}.{}", + binding_package.join("."), + context.keywords.escape(world.name.to_snake_case()), + encoded_name.scala + ); + + let mut export_header = String::new(); + uwriteln!(export_header, "package {}", export_package.join(".")); + uwriteln!(export_header, ""); + uwriteln!(export_header, "import scala.scalajs.js"); + uwriteln!(export_header, "import scala.scalajs.js.annotation._"); + uwriteln!( + export_header, + "import {}wit._", + context.opts.base_package_prefix() + ); + uwriteln!(export_header, ""); + uwriteln!( + export_header, + "object {} extends {base_trait_name} {{", + encoded_name.scala + ); + + Self { + world_id, + export_header, + global_exports: String::new(), + export_package, + } + } + + pub fn add_exported_function( + &mut self, + context: &ScalaJsContext, + resolve: &Resolve, + func_name: &str, + func: &Function, + ) { + match func.kind { + FunctionKind::Freestanding => { + let encoded_name = context.encode_name(func_name.to_lower_camel_case()); + let args = context.render_args(context, resolve, func.params.iter()); + let ret = context.render_return_type(context, resolve, &func.results); + + uwriteln!( + self.global_exports, + " def {}({args}): {ret} = {{", + encoded_name.scala + ); + uwriteln!(self.global_exports, " ???"); + uwriteln!(self.global_exports, " }}"); + } + FunctionKind::Method(_resource_type) + | FunctionKind::Static(_resource_type) + | FunctionKind::Constructor(_resource_type) => { + panic!("Exported inline resource functions are not supported") + } + } + } + + pub fn finalize(self, resolve: &Resolve) -> ScalaJsFile { + let mut export_source = String::new(); + uwriteln!(export_source, "{}", self.export_header); + uwriteln!(export_source, "{}", self.global_exports); + uwriteln!(export_source, "}}"); + + let world = &resolve.worlds[self.world_id]; + + ScalaJsFile { + package: self.export_package, + name: world.name.to_snake_case(), + source: export_source, + } + } +} + +pub struct ScalaJsExportedResourceSkeleton<'a> { + owner: &'a ScalaJsInterfaceSkeleton<'a>, + _resource_id: TypeId, + resource_name: String, + encoded_resource_name: EncodedName, + class_header: String, + base_class_header: String, + class_source: String, + static_methods: String, + constructor_args: String, + base_constructor_args: String, + base_static_trait_name: String, +} + +impl<'a> ScalaJsExportedResourceSkeleton<'a> { + pub fn new(owner: &'a ScalaJsInterfaceSkeleton<'a>, resource_id: TypeId) -> Self { + let resource = &owner.resolve.types[resource_id]; + let resource_name = resource + .name + .clone() + .expect("Anonymous resources not supported"); + let encoded_resource_name = owner + .generator + .context + .encode_name(resource_name.to_pascal_case()); + + let base_class_name = format!( + "{}.{}.{}", + owner.binding_package.join("."), + owner + .generator + .context + .keywords + .escape(owner.name.to_snake_case()), + encoded_resource_name.scala + ); + + let base_static_trait_name = format!( + "{}.{}.{}", + owner.binding_package.join("."), + owner + .generator + .context + .keywords + .escape(owner.name.to_snake_case()), + format!("{}Static", encoded_resource_name.scala) + ); + + let mut class_header = String::new(); + write_doc_comment(&mut class_header, " ", &resource.docs); + + uwriteln!( + class_header, + "@JSExportTopLevel(\"{}\")", + encoded_resource_name.js + ); + uwrite!(class_header, "class {}(", encoded_resource_name.scala); + + let mut base_class_header = String::new(); + uwrite!(base_class_header, ") extends {base_class_name}("); + + let mut class_source = String::new(); + uwriteln!(class_source, ") {{"); + + Self { + owner, + _resource_id: resource_id, + resource_name, + encoded_resource_name, + class_header, + base_class_header, + class_source, + static_methods: String::new(), + constructor_args: String::new(), + base_constructor_args: String::new(), + base_static_trait_name, + } + } + + pub fn add_function(&mut self, func_name: &str, func: &Function) { + match &func.kind { + FunctionKind::Freestanding => unreachable!(), + FunctionKind::Method(_) => { + let args = self.owner.generator.context.render_args( + self.owner, + self.owner.resolve, + func.params.iter().skip(1), + ); + let ret = self.owner.generator.context.render_return_type( + self.owner, + self.owner.resolve, + &func.results, + ); + let encoded_func_name = self.get_func_name("[method]", func_name); + + encoded_func_name.write_rename_attribute(&mut self.class_source, " "); + uwriteln!( + self.class_source, + " override def {}({args}): {ret} = {{", + encoded_func_name.scala + ); + uwriteln!(self.class_source, " ???"); + uwriteln!(self.class_source, " }}"); + } + FunctionKind::Static(_) => { + let args = self.owner.generator.context.render_args( + self.owner, + self.owner.resolve, + func.params.iter(), + ); + let ret = self.owner.generator.context.render_return_type( + self.owner, + self.owner.resolve, + &func.results, + ); + + let encoded_func_name = self.get_func_name("[static]", func_name); + encoded_func_name.write_static_export_attribute(&mut self.static_methods, " "); + uwriteln!( + self.static_methods, + " override def {}({args}): {ret} = {{", + encoded_func_name.scala + ); + uwriteln!(self.static_methods, " ???"); + uwriteln!(self.static_methods, " }}"); + } + FunctionKind::Constructor(_) => { + let args = self.owner.generator.context.render_args( + self.owner, + self.owner.resolve, + func.params.iter(), + ); + self.constructor_args = args; + + let mut param_names = Vec::new(); + for (param_name, _) in func.params.iter() { + let param_name = self + .owner + .generator + .context + .encode_name(param_name.to_lower_camel_case()); + param_names.push(param_name.scala); + } + self.base_constructor_args = param_names.join(", "); + } + } + } + + pub fn finalize(self) -> String { + let mut class_source = self.class_header; + uwrite!(class_source, "{}", self.constructor_args); + uwrite!(class_source, "{}", self.base_class_header); + uwrite!(class_source, "{}", self.base_constructor_args); + uwriteln!(class_source, "{}", self.class_source); + uwriteln!(class_source, "}}"); + uwriteln!(class_source, ""); + + uwriteln!( + class_source, + "object {} extends {} {{", + self.encoded_resource_name.scala, + self.base_static_trait_name + ); + uwriteln!(class_source, "{}", self.static_methods); + uwriteln!(class_source, "}}"); + class_source + } + + fn get_func_name(&self, prefix: &str, func_name: &str) -> EncodedName { + let name = func_name + .strip_prefix(prefix) + .unwrap() + .strip_prefix(&self.resource_name) + .unwrap() + .to_lower_camel_case(); + self.owner.generator.context.encode_name(name) + } +} diff --git a/crates/scalajs/src/world.rs b/crates/scalajs/src/world.rs index 3000faf7c..10fcce338 100644 --- a/crates/scalajs/src/world.rs +++ b/crates/scalajs/src/world.rs @@ -33,8 +33,13 @@ impl ScalaJsWorld { .name .clone(); - let package = - package_name_to_segments(&context.opts, &package_name, &Import, &context.keywords); + let package = package_name_to_segments( + &context.opts, + &package_name, + &Import, + &context.keywords, + false, + ); let mut header = String::new(); uwriteln!(header, "package {}", package.join(".")); @@ -46,8 +51,15 @@ impl ScalaJsWorld { uwriteln!(header, ""); uwriteln!(header, "package object {} {{", name); - let export_package = - package_name_to_segments(&context.opts, &package_name, &Export, &context.keywords); + let export_package = package_name_to_segments( + &context.opts, + &package_name, + &Export, + &context.keywords, + false, + ); + + let encoded_world_name = context.encode_name(world.name.clone().to_pascal_case()); let mut export_header = String::new(); uwriteln!(export_header, "package {}", export_package.join(".")); @@ -61,11 +73,7 @@ impl ScalaJsWorld { ); uwriteln!(export_header, ""); uwriteln!(export_header, "package object {} {{", name); - uwriteln!( - export_header, - " trait {} extends js.Object {{", - world.name.clone().to_pascal_case() - ); + uwriteln!(export_header, " trait {} {{", encoded_world_name.scala); Self { world_id, @@ -98,6 +106,7 @@ impl ScalaJsWorld { let ret = context.render_return_type(context, resolve, &func.results); write_doc_comment(&mut self.global_imports, " ", &func.docs); + encoded_name.write_rename_attribute(&mut self.global_imports, " "); uwriteln!( self.global_imports, " def {}({args}): {ret} = js.native", @@ -193,10 +202,20 @@ impl ScalaJsWorld { .name .clone(); - let package = - package_name_to_segments(&context.opts, &package_name, &Import, &context.keywords); - let export_package = - package_name_to_segments(&context.opts, &package_name, &Export, &context.keywords); + let package = package_name_to_segments( + &context.opts, + &package_name, + &Import, + &context.keywords, + false, + ); + let export_package = package_name_to_segments( + &context.opts, + &package_name, + &Export, + &context.keywords, + false, + ); vec![ ScalaJsFile { diff --git a/crates/scalajs/tests/codegen.rs b/crates/scalajs/tests/codegen.rs index 716e55a6b..0796a5791 100644 --- a/crates/scalajs/tests/codegen.rs +++ b/crates/scalajs/tests/codegen.rs @@ -21,7 +21,11 @@ macro_rules! codegen_test { |resolve, world, files| { wit_bindgen_scalajs::Opts { base_package: Some("test".to_string()), - scala_dialect: Scala2 + skeleton_base_package: Some("skeleton".to_string()), + scala_dialect: Scala2, + generate_skeleton: true, + skeleton_root: None, + binding_root: None, } .build() .generate(resolve, world, files) From 7048c1f17dc266eb2ef3dc0d770a419f612005bd Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Fri, 7 Feb 2025 18:00:57 +0100 Subject: [PATCH 14/36] Fix clap attributes --- crates/scalajs/src/lib.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs/src/lib.rs index eb5876865..d69f762b5 100644 --- a/crates/scalajs/src/lib.rs +++ b/crates/scalajs/src/lib.rs @@ -77,11 +77,7 @@ pub struct Opts { pub scala_dialect: ScalaDialect, #[cfg_attr( feature = "clap", - clap( - long, - help = "Generate a skeleton for implementing all the exports", - default_value = "scala2" - ) + clap(long, help = "Generate a skeleton for implementing all the exports",) )] pub generate_skeleton: bool, #[cfg_attr( @@ -89,17 +85,12 @@ pub struct Opts { clap( long, help = "Relative root directory for placing the skeleton sources", - default_value = "scala2" ) )] pub skeleton_root: Option, #[cfg_attr( feature = "clap", - clap( - long, - help = "Relative root directory for placing the binding sources", - default_value = "scala2" - ) + clap(long, help = "Relative root directory for placing the binding sources",) )] pub binding_root: Option, } From 15940f73ba4dea4d305c049b07f64bd5dac741f1 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Fri, 7 Feb 2025 18:14:15 +0100 Subject: [PATCH 15/36] Fixed export name --- crates/scalajs/src/skeleton.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/scalajs/src/skeleton.rs b/crates/scalajs/src/skeleton.rs index 48d7d85ec..7327f633c 100644 --- a/crates/scalajs/src/skeleton.rs +++ b/crates/scalajs/src/skeleton.rs @@ -2,7 +2,7 @@ use crate::context::{ package_name_to_segments, write_doc_comment, EncodedName, ScalaJsContext, ScalaJsFile, }; use crate::ScalaJs; -use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; +use heck::{ToKebabCase, ToLowerCamelCase, ToPascalCase, ToSnakeCase}; use std::collections::HashMap; use std::fmt::Write; use wit_bindgen_core::wit_parser::{ @@ -94,9 +94,9 @@ impl<'a> ScalaJsInterfaceSkeleton<'a> { self.name ); - let encoded_wit_name = self.generator.context.encode_name(&self.wit_name); + let encoded_name = self.generator.context.encode_name(&self.name.to_kebab_case()); - uwriteln!(source, "@JSExportTopLevel(\"{}\")", encoded_wit_name.js); + uwriteln!(source, "@JSExportTopLevel(\"{}\")", encoded_name.js); uwriteln!( source, "object {} extends {} {{", From c29093b26fcaafc8cd3cc4665a899394a12fb39e Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sat, 8 Feb 2025 17:21:15 +0100 Subject: [PATCH 16/36] Fixes --- crates/scalajs/scala/wit.scala | 2 - crates/scalajs/src/context.rs | 2 +- crates/scalajs/src/interface.rs | 9 +++- crates/scalajs/src/resource.rs | 45 +++++++++++++++++-- crates/scalajs/src/skeleton.rs | 78 ++++++++++++++++++++++++--------- 5 files changed, 109 insertions(+), 27 deletions(-) diff --git a/crates/scalajs/scala/wit.scala b/crates/scalajs/scala/wit.scala index faf533396..5d0f0a2a9 100644 --- a/crates/scalajs/scala/wit.scala +++ b/crates/scalajs/scala/wit.scala @@ -111,8 +111,6 @@ package object wit { def unapply[T1](tuple: WitTuple1[T1]): Some[(T1)] = Some(tuple) - implicit def fromScalaTuple1[T1](tuple: (T1)): WitTuple1[T1] = WitTuple1(tuple._1) - implicit def toScalaTuple1[T1](tuple: WitTuple1[T1]): (T1) = (tuple._1) } diff --git a/crates/scalajs/src/context.rs b/crates/scalajs/src/context.rs index 2f539c1b2..1f400f86a 100644 --- a/crates/scalajs/src/context.rs +++ b/crates/scalajs/src/context.rs @@ -735,7 +735,7 @@ pub fn package_name_to_segments( }; if direction == &Export { - segments.push("export".to_string()); + segments.push("exports".to_string()); } segments.push(package_name.namespace.to_snake_case()); diff --git a/crates/scalajs/src/interface.rs b/crates/scalajs/src/interface.rs index d7116a50e..4568bb3b8 100644 --- a/crates/scalajs/src/interface.rs +++ b/crates/scalajs/src/interface.rs @@ -131,16 +131,23 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, ""); } + let mut constructors = Vec::new(); for (_, resource) in exported_resources { + constructors.push(resource.constructor_function()); uwriteln!(source, "{}", resource.finalize()); uwriteln!(source, ""); } write_doc_comment(&mut source, " ", &self.interface.docs); - uwriteln!(source, " trait {} extends {{", self.name); + uwriteln!(source, " trait {} {{", self.name); for function in functions { uwriteln!(source, "{function}"); } + + for constructor in constructors { + uwriteln!(source, "{constructor}"); + } + uwriteln!(source, " }}"); uwriteln!(source, "}}"); diff --git a/crates/scalajs/src/resource.rs b/crates/scalajs/src/resource.rs index b9eff58d6..1ddfedd42 100644 --- a/crates/scalajs/src/resource.rs +++ b/crates/scalajs/src/resource.rs @@ -124,7 +124,13 @@ impl ScalaJsImportedResource { ); } FunctionKind::Constructor(_) => { - let args = context.render_args(owner_context, resolve, func.params.iter()); + // Renaming constructor parameters because they can collide with the method names + let renamed_func_params: Vec<_> = func + .params + .iter() + .map(|(name, typ)| (format!("{name}0"), typ.clone())) + .collect(); + let args = context.render_args(owner_context, resolve, renamed_func_params.iter()); self.constructor_args = args; } } @@ -272,16 +278,45 @@ impl<'a> ScalaJsExportedResource<'a> { ); } FunctionKind::Constructor(_) => { + // Renaming constructor parameters because they can collide with the method names + let renamed_func_params: Vec<_> = func + .params + .iter() + .map(|(name, typ)| (format!("{name}0"), typ.clone())) + .collect(); + let args = self.owner.generator.context.render_args( self.owner, self.owner.resolve, - func.params.iter(), + renamed_func_params.iter(), ); self.constructor_args = args; } } } + pub fn constructor_function(&self) -> String { + let mut constructor = String::new(); + uwriteln!( + constructor, + " // @JSExport(\"{}\")", + self.encoded_resource_name.js + ); + let encoded_name = self + .owner + .generator + .context + .encode_name(self.resource_name.to_lower_camel_case()); + uwriteln!( + constructor, + " def {}({}): {}", + encoded_name.scala, + self.constructor_args, + self.encoded_resource_name.scala + ); + constructor + } + pub fn finalize(self) -> String { let mut class_source = self.class_header; uwrite!(class_source, "{}", self.constructor_args); @@ -289,7 +324,11 @@ impl<'a> ScalaJsExportedResource<'a> { uwriteln!(class_source, " }}"); uwriteln!(class_source, ""); - uwriteln!(class_source, " trait {}Static {{", self.encoded_resource_name.scala); + uwriteln!( + class_source, + " trait {}Static {{", + self.encoded_resource_name.scala + ); uwriteln!(class_source, "{}", self.static_methods); uwriteln!(class_source, " }}"); class_source diff --git a/crates/scalajs/src/skeleton.rs b/crates/scalajs/src/skeleton.rs index 7327f633c..bcc6e3473 100644 --- a/crates/scalajs/src/skeleton.rs +++ b/crates/scalajs/src/skeleton.rs @@ -12,7 +12,6 @@ use wit_bindgen_core::Direction::Export; use wit_bindgen_core::{uwrite, uwriteln}; pub struct ScalaJsInterfaceSkeleton<'a> { - wit_name: String, name: String, source: String, package: Vec, @@ -59,7 +58,6 @@ impl<'a> ScalaJsInterfaceSkeleton<'a> { ); Self { - wit_name, name, source: "".to_string(), package, @@ -94,8 +92,19 @@ impl<'a> ScalaJsInterfaceSkeleton<'a> { self.name ); - let encoded_name = self.generator.context.encode_name(&self.name.to_kebab_case()); + let encoded_name = self + .generator + .context + .encode_name(&self.name.to_kebab_case()); + let exported_resources = self.collect_exported_resources(); + let mut constructors = Vec::new(); + uwriteln!(source, ""); + for (_, resource) in exported_resources { + constructors.push(resource.constructor_function()); + uwriteln!(source, "{}", resource.finalize()); + uwriteln!(source, ""); + } uwriteln!(source, "@JSExportTopLevel(\"{}\")", encoded_name.js); uwriteln!( source, @@ -108,15 +117,13 @@ impl<'a> ScalaJsInterfaceSkeleton<'a> { uwriteln!(source, "{}", function); } + for constructor in constructors { + uwriteln!(source, "{}", constructor); + } + uwriteln!(source, "}}"); - let exported_resources = self.collect_exported_resources(); - uwriteln!(source, ""); - for (_, resource) in exported_resources { - uwriteln!(source, "{}", resource.finalize()); - uwriteln!(source, ""); - } self.source = source; } @@ -152,14 +159,14 @@ impl<'a> ScalaJsInterfaceSkeleton<'a> { let mut function = String::new(); - func_name.write_export_attribute(&mut function, " "); + func_name.write_export_attribute(&mut function, " "); uwriteln!( function, - " override def {}({args}): {ret} = {{", + " override def {}({args}): {ret} = {{", func_name.scala ); - uwriteln!(function, " ???"); - uwriteln!(function, " }}"); + uwriteln!(function, " ???"); + uwriteln!(function, " }}"); functions.push(function); } FunctionKind::Method(_) @@ -326,6 +333,7 @@ pub struct ScalaJsExportedResourceSkeleton<'a> { constructor_args: String, base_constructor_args: String, base_static_trait_name: String, + base_class_name: String, } impl<'a> ScalaJsExportedResourceSkeleton<'a> { @@ -365,11 +373,6 @@ impl<'a> ScalaJsExportedResourceSkeleton<'a> { let mut class_header = String::new(); write_doc_comment(&mut class_header, " ", &resource.docs); - uwriteln!( - class_header, - "@JSExportTopLevel(\"{}\")", - encoded_resource_name.js - ); uwrite!(class_header, "class {}(", encoded_resource_name.scala); let mut base_class_header = String::new(); @@ -390,6 +393,7 @@ impl<'a> ScalaJsExportedResourceSkeleton<'a> { constructor_args: String::new(), base_constructor_args: String::new(), base_static_trait_name, + base_class_name, } } @@ -441,15 +445,21 @@ impl<'a> ScalaJsExportedResourceSkeleton<'a> { uwriteln!(self.static_methods, " }}"); } FunctionKind::Constructor(_) => { + // Renaming constructor parameters because they can collide with the method names + let renamed_func_params: Vec<_> = func + .params + .iter() + .map(|(name, typ)| (format!("{name}0"), typ.clone())) + .collect(); let args = self.owner.generator.context.render_args( self.owner, self.owner.resolve, - func.params.iter(), + renamed_func_params.iter(), ); self.constructor_args = args; let mut param_names = Vec::new(); - for (param_name, _) in func.params.iter() { + for (param_name, _) in renamed_func_params.iter() { let param_name = self .owner .generator @@ -462,6 +472,34 @@ impl<'a> ScalaJsExportedResourceSkeleton<'a> { } } + pub fn constructor_function(&self) -> String { + let mut constructor = String::new(); + uwriteln!( + constructor, + " @JSExport(\"{}\")", + self.encoded_resource_name.js + ); + let encoded_name = self + .owner + .generator + .context + .encode_name(self.resource_name.to_lower_camel_case()); + uwriteln!( + constructor, + " def {}({}): {} = ", + encoded_name.scala, + self.constructor_args, + self.base_class_name + ); + uwriteln!( + constructor, + " new {}({})", + self.encoded_resource_name.scala, + self.base_constructor_args + ); + constructor + } + pub fn finalize(self) -> String { let mut class_source = self.class_header; uwrite!(class_source, "{}", self.constructor_args); From 9c16e8f21d54cf7b43a1cd7fbc15955fd93d7e7c Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sun, 9 Feb 2025 12:46:45 +0100 Subject: [PATCH 17/36] Uint8Array --- crates/scalajs/src/context.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/scalajs/src/context.rs b/crates/scalajs/src/context.rs index 1f400f86a..77068c4aa 100644 --- a/crates/scalajs/src/context.rs +++ b/crates/scalajs/src/context.rs @@ -388,10 +388,14 @@ impl ScalaJsContext { format!("WitResult[{ok}, {err}]") } TypeDefKind::List(list) => { - format!( - "WitList[{}]", - self.render_type_reference(owner_context, resolve, list) - ) + if matches!(list, Type::U8) { + "js.typedarray.Uint8Array".to_string() + } else { + format!( + "WitList[{}]", + self.render_type_reference(owner_context, resolve, list) + ) + } } TypeDefKind::Future(_) => panic!("Futures not supported yet"), TypeDefKind::Stream(_) => panic!("Streams not supported yet"), @@ -664,8 +668,12 @@ impl ScalaJsContext { } TypeDefKind::List(list) => { write_doc_comment(&mut source, " ", &typ.docs); - let typ = self.render_type_reference(owner_context, resolve, list); - uwriteln!(source, " type {scala_name} = WitList[{typ}]"); + if matches!(list, Type::U8) { + uwriteln!(source, " type {scala_name} = js.typedarray.Uint8Array") + } else { + let typ = self.render_type_reference(owner_context, resolve, list); + uwriteln!(source, " type {scala_name} = WitList[{typ}]"); + } } TypeDefKind::Future(_) => { panic!("Futures are not supported yet"); From e01bdf7258fd0473c89e6b9f406ae5dfdd1e89ce Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Wed, 19 Feb 2025 20:51:36 +0100 Subject: [PATCH 18/36] Result return types are exceptions --- crates/scalajs/src/context.rs | 73 ++++++++++++++++++++++++++------- crates/scalajs/src/interface.rs | 26 ++++++++---- crates/scalajs/src/resource.rs | 42 +++++++++++++++---- crates/scalajs/src/skeleton.rs | 42 ++++++++++++++----- crates/scalajs/src/world.rs | 20 +++++++-- 5 files changed, 160 insertions(+), 43 deletions(-) diff --git a/crates/scalajs/src/context.rs b/crates/scalajs/src/context.rs index 77068c4aa..5a4758501 100644 --- a/crates/scalajs/src/context.rs +++ b/crates/scalajs/src/context.rs @@ -275,22 +275,67 @@ impl ScalaJsContext { owner_context: &impl OwnerContext, resolve: &Resolve, results: &Results, - ) -> String { + ) -> (String, Option) { match results { - Results::Named(results) if results.len() == 0 => "Unit".to_string(), - Results::Named(results) if results.len() == 1 => self.render_type_reference( - owner_context, - resolve, - &results.iter().next().unwrap().1, - ), - Results::Named(results) => self.render_tuple( - owner_context, - resolve, - &Tuple { - types: results.iter().map(|(_, typ)| typ.clone()).collect(), - }, + Results::Named(results) if results.len() == 0 => ("Unit".to_string(), None), + Results::Named(pairs) if pairs.len() == 1 => { + if let Some((ok, err)) = results.throws(resolve) { + let throws = if let Some(err) = err { + Some(self.render_type_reference(owner_context, resolve, err)) + } else { + Some("Unit".to_string()) + }; + if let Some(ok) = ok { + ( + self.render_type_reference(owner_context, resolve, ok), + throws, + ) + } else { + ("Unit".to_string(), throws) + } + } else { + ( + self.render_type_reference( + owner_context, + resolve, + &pairs.iter().next().unwrap().1, + ), + None, + ) + } + } + Results::Named(results) => ( + self.render_tuple( + owner_context, + resolve, + &Tuple { + types: results.iter().map(|(_, typ)| typ.clone()).collect(), + }, + ), + None, ), - Results::Anon(typ) => self.render_type_reference(owner_context, resolve, typ), + Results::Anon(typ) => { + if let Some((ok, err)) = results.throws(resolve) { + let throws = if let Some(err) = err { + Some(self.render_type_reference(owner_context, resolve, err)) + } else { + Some("Unit".to_string()) + }; + if let Some(ok) = ok { + ( + self.render_type_reference(owner_context, resolve, ok), + throws, + ) + } else { + ("Unit".to_string(), throws) + } + } else { + ( + self.render_type_reference(owner_context, resolve, typ), + None, + ) + } + } } } diff --git a/crates/scalajs/src/interface.rs b/crates/scalajs/src/interface.rs index 4568bb3b8..91858f17f 100644 --- a/crates/scalajs/src/interface.rs +++ b/crates/scalajs/src/interface.rs @@ -120,9 +120,17 @@ impl<'a> ScalaJsInterface<'a> { let mut source = String::new(); self.generate_package_header(&mut source); - let also_imported = self.generator.context.interface_direction(&self.interface_id) == Import; - - let types = if also_imported { Vec::new() } else { self.collect_type_definition_snippets() }; + let also_imported = self + .generator + .context + .interface_direction(&self.interface_id) + == Import; + + let types = if also_imported { + Vec::new() + } else { + self.collect_type_definition_snippets() + }; let exported_resources = self.collect_exported_resources(); let functions = self.collect_function_snippets(); @@ -292,7 +300,7 @@ impl<'a> ScalaJsInterface<'a> { self.generator .context .render_args(self, self.resolve, func.params.iter()); - let ret = self.generator.context.render_return_type( + let (ret, throws) = self.generator.context.render_return_type( self, self.resolve, &func.results, @@ -301,11 +309,15 @@ impl<'a> ScalaJsInterface<'a> { let mut function = String::new(); write_doc_comment(&mut function, " ", &func.docs); - let postfix = match self.direction { - Import => " = js.native", - Export => "", + let mut postfix = match self.direction { + Import => " = js.native".to_string(), + Export => "".to_string(), }; + if let Some(throws) = throws { + postfix.push_str(&format!(" // throws {throws}")); + } + if self.direction == Import { func_name.write_rename_attribute(&mut function, " "); } diff --git a/crates/scalajs/src/resource.rs b/crates/scalajs/src/resource.rs index 1ddfedd42..dc27b50fd 100644 --- a/crates/scalajs/src/resource.rs +++ b/crates/scalajs/src/resource.rs @@ -82,7 +82,8 @@ impl ScalaJsImportedResource { FunctionKind::Freestanding => unreachable!(), FunctionKind::Method(_) => { let args = context.render_args(owner_context, resolve, func.params.iter().skip(1)); - let ret = context.render_return_type(owner_context, resolve, &func.results); + let (ret, throws) = + context.render_return_type(owner_context, resolve, &func.results); let encoded_func_name = self.get_func_name(context, "[method]", func_name); let overrd = if context @@ -95,6 +96,12 @@ impl ScalaJsImportedResource { "" }; + let postfix = if let Some(throws) = throws { + format!(" // throws {}", throws) + } else { + "".to_string() + }; + write_doc_comment( &mut self.class_source, &format!("{} ", self.indent), @@ -103,14 +110,21 @@ impl ScalaJsImportedResource { encoded_func_name.write_rename_attribute(&mut self.class_source, " "); uwriteln!( self.class_source, - "{} {overrd}def {}({args}): {ret} = js.native", + "{} {overrd}def {}({args}): {ret} = js.native{postfix}", self.indent, encoded_func_name.scala ); } FunctionKind::Static(_) => { let args = context.render_args(owner_context, resolve, func.params.iter()); - let ret = context.render_return_type(owner_context, resolve, &func.results); + let (ret, throws) = + context.render_return_type(owner_context, resolve, &func.results); + + let postfix = if let Some(throws) = throws { + format!(" // throws {}", throws) + } else { + "".to_string() + }; let encoded_func_name = self.get_func_name(context, "[static]", func_name); write_doc_comment(&mut self.object_source, " ", &func.docs); @@ -118,7 +132,7 @@ impl ScalaJsImportedResource { .write_rename_attribute(&mut self.object_source, &format!("{} ", self.indent)); uwriteln!( self.object_source, - "{} def {}({args}): {ret} = js.native", + "{} def {}({args}): {ret} = js.native{postfix}", self.indent, encoded_func_name.scala ); @@ -227,7 +241,7 @@ impl<'a> ScalaJsExportedResource<'a> { self.owner.resolve, func.params.iter().skip(1), ); - let ret = self.owner.generator.context.render_return_type( + let (ret, throws) = self.owner.generator.context.render_return_type( self.owner, self.owner.resolve, &func.results, @@ -247,11 +261,17 @@ impl<'a> ScalaJsExportedResource<'a> { "" }; + let postfix = if let Some(throws) = throws { + format!(" // throws {}", throws) + } else { + "".to_string() + }; + write_doc_comment(&mut self.class_source, " ", &func.docs); encoded_func_name.write_rename_attribute(&mut self.class_source, " "); uwriteln!( self.class_source, - " {overrd}def {}({args}): {ret}", + " {overrd}def {}({args}): {ret}{postfix}", encoded_func_name.scala ); } @@ -261,19 +281,25 @@ impl<'a> ScalaJsExportedResource<'a> { self.owner.resolve, func.params.iter(), ); - let ret = self.owner.generator.context.render_return_type( + let (ret, throws) = self.owner.generator.context.render_return_type( self.owner, self.owner.resolve, &func.results, ); + let postfix = if let Some(throws) = throws { + format!(" // throws {}", throws) + } else { + "".to_string() + }; + let encoded_func_name = self.get_func_name("[static]", func_name); write_doc_comment(&mut self.static_methods, " ", &func.docs); uwriteln!(self.static_methods, " // @JSExportStatic"); encoded_func_name.write_rename_attribute(&mut self.static_methods, " "); uwriteln!( self.static_methods, - " def {}({args}): {ret}", + " def {}({args}): {ret}{postfix}", encoded_func_name.scala ); } diff --git a/crates/scalajs/src/skeleton.rs b/crates/scalajs/src/skeleton.rs index bcc6e3473..0912e7e21 100644 --- a/crates/scalajs/src/skeleton.rs +++ b/crates/scalajs/src/skeleton.rs @@ -123,8 +123,6 @@ impl<'a> ScalaJsInterfaceSkeleton<'a> { uwriteln!(source, "}}"); - - self.source = source; } @@ -151,18 +149,24 @@ impl<'a> ScalaJsInterfaceSkeleton<'a> { self.generator .context .render_args(self, self.resolve, func.params.iter()); - let ret = self.generator.context.render_return_type( + let (ret, throws) = self.generator.context.render_return_type( self, self.resolve, &func.results, ); + let postfix = if let Some(throws) = throws { + format!(" // throws {}", throws) + } else { + "".to_string() + }; + let mut function = String::new(); func_name.write_export_attribute(&mut function, " "); uwriteln!( function, - " override def {}({args}): {ret} = {{", + " override def {}({args}): {ret} = {{{postfix}", func_name.scala ); uwriteln!(function, " ???"); @@ -287,11 +291,17 @@ impl ScalaJsWorldSkeleton { FunctionKind::Freestanding => { let encoded_name = context.encode_name(func_name.to_lower_camel_case()); let args = context.render_args(context, resolve, func.params.iter()); - let ret = context.render_return_type(context, resolve, &func.results); + let (ret, throws) = context.render_return_type(context, resolve, &func.results); + + let postfix = if let Some(throws) = throws { + format!(" // throws {}", throws) + } else { + "".to_string() + }; uwriteln!( self.global_exports, - " def {}({args}): {ret} = {{", + " def {}({args}): {ret} = {{{postfix}", encoded_name.scala ); uwriteln!(self.global_exports, " ???"); @@ -406,17 +416,23 @@ impl<'a> ScalaJsExportedResourceSkeleton<'a> { self.owner.resolve, func.params.iter().skip(1), ); - let ret = self.owner.generator.context.render_return_type( + let (ret, throws) = self.owner.generator.context.render_return_type( self.owner, self.owner.resolve, &func.results, ); let encoded_func_name = self.get_func_name("[method]", func_name); + let postfix = if let Some(throws) = throws { + format!(" // throws {}", throws) + } else { + "".to_string() + }; + encoded_func_name.write_rename_attribute(&mut self.class_source, " "); uwriteln!( self.class_source, - " override def {}({args}): {ret} = {{", + " override def {}({args}): {ret} = {{{postfix}", encoded_func_name.scala ); uwriteln!(self.class_source, " ???"); @@ -428,17 +444,23 @@ impl<'a> ScalaJsExportedResourceSkeleton<'a> { self.owner.resolve, func.params.iter(), ); - let ret = self.owner.generator.context.render_return_type( + let (ret, throws) = self.owner.generator.context.render_return_type( self.owner, self.owner.resolve, &func.results, ); + let postfix = if let Some(throws) = throws { + format!(" // throws {}", throws) + } else { + "".to_string() + }; + let encoded_func_name = self.get_func_name("[static]", func_name); encoded_func_name.write_static_export_attribute(&mut self.static_methods, " "); uwriteln!( self.static_methods, - " override def {}({args}): {ret} = {{", + " override def {}({args}): {ret} = {{{postfix}", encoded_func_name.scala ); uwriteln!(self.static_methods, " ???"); diff --git a/crates/scalajs/src/world.rs b/crates/scalajs/src/world.rs index 10fcce338..2923e08c9 100644 --- a/crates/scalajs/src/world.rs +++ b/crates/scalajs/src/world.rs @@ -103,13 +103,19 @@ impl ScalaJsWorld { ); let encoded_name = context.encode_name(func_name.to_lower_camel_case()); let args = context.render_args(context, resolve, func.params.iter()); - let ret = context.render_return_type(context, resolve, &func.results); + let (ret, throws) = context.render_return_type(context, resolve, &func.results); + + let postfix = if let Some(throws) = throws { + format!(" // throws {}", throws) + } else { + "".to_string() + }; write_doc_comment(&mut self.global_imports, " ", &func.docs); encoded_name.write_rename_attribute(&mut self.global_imports, " "); uwriteln!( self.global_imports, - " def {}({args}): {ret} = js.native", + " def {}({args}): {ret} = js.native{postfix}", encoded_name.scala ); } @@ -158,12 +164,18 @@ impl ScalaJsWorld { FunctionKind::Freestanding => { let encoded_name = context.encode_name(func_name.to_lower_camel_case()); let args = context.render_args(context, resolve, func.params.iter()); - let ret = context.render_return_type(context, resolve, &func.results); + let (ret, throws) = context.render_return_type(context, resolve, &func.results); + + let postfix = if let Some(throws) = throws { + format!(" // throws {}", throws) + } else { + "".to_string() + }; write_doc_comment(&mut self.global_exports, " ", &func.docs); uwriteln!( self.global_exports, - " def {}({args}): {ret}", + " def {}({args}): {ret}{postfix}", encoded_name.scala ); } From 8e7a2e1dc9ffb30cb6b1fa3edf63e299d891e910 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sun, 23 Feb 2025 12:00:35 +0100 Subject: [PATCH 19/36] Generate top-level class exports for resources, with prefixed names --- crates/scalajs/src/interface.rs | 1 - crates/scalajs/src/skeleton.rs | 57 +++++++++------------------------ 2 files changed, 15 insertions(+), 43 deletions(-) diff --git a/crates/scalajs/src/interface.rs b/crates/scalajs/src/interface.rs index 91858f17f..f6680af61 100644 --- a/crates/scalajs/src/interface.rs +++ b/crates/scalajs/src/interface.rs @@ -24,7 +24,6 @@ pub struct ScalaJsInterface<'a> { } impl<'a> ScalaJsInterface<'a> { - // TODO: should just get a reference to ScalaJsWorld pub fn new( wit_name: String, resolve: &'a Resolve, diff --git a/crates/scalajs/src/skeleton.rs b/crates/scalajs/src/skeleton.rs index 0912e7e21..980639a39 100644 --- a/crates/scalajs/src/skeleton.rs +++ b/crates/scalajs/src/skeleton.rs @@ -13,6 +13,7 @@ use wit_bindgen_core::{uwrite, uwriteln}; pub struct ScalaJsInterfaceSkeleton<'a> { name: String, + encoded_name: EncodedName, source: String, package: Vec, binding_package: Vec, @@ -36,6 +37,8 @@ impl<'a> ScalaJsInterfaceSkeleton<'a> { .unwrap_or(wit_name.clone()) .to_pascal_case(); + let encoded_name = generator.context.encode_name(&name.to_kebab_case()); + let package_name = resolve.packages [interface.package.expect("missing package for interface")] .name @@ -59,6 +62,7 @@ impl<'a> ScalaJsInterfaceSkeleton<'a> { Self { name, + encoded_name, source: "".to_string(), package, binding_package, @@ -92,20 +96,15 @@ impl<'a> ScalaJsInterfaceSkeleton<'a> { self.name ); - let encoded_name = self - .generator - .context - .encode_name(&self.name.to_kebab_case()); - let exported_resources = self.collect_exported_resources(); - let mut constructors = Vec::new(); + uwriteln!(source, ""); for (_, resource) in exported_resources { - constructors.push(resource.constructor_function()); uwriteln!(source, "{}", resource.finalize()); uwriteln!(source, ""); } - uwriteln!(source, "@JSExportTopLevel(\"{}\")", encoded_name.js); + + uwriteln!(source, "@JSExportTopLevel(\"{}\")", self.encoded_name.js); uwriteln!( source, "object {} extends {} {{", @@ -117,10 +116,6 @@ impl<'a> ScalaJsInterfaceSkeleton<'a> { uwriteln!(source, "{}", function); } - for constructor in constructors { - uwriteln!(source, "{}", constructor); - } - uwriteln!(source, "}}"); self.source = source; @@ -343,7 +338,6 @@ pub struct ScalaJsExportedResourceSkeleton<'a> { constructor_args: String, base_constructor_args: String, base_static_trait_name: String, - base_class_name: String, } impl<'a> ScalaJsExportedResourceSkeleton<'a> { @@ -383,6 +377,14 @@ impl<'a> ScalaJsExportedResourceSkeleton<'a> { let mut class_header = String::new(); write_doc_comment(&mut class_header, " ", &resource.docs); + let prefixed_export_name = + format!("{}_{}", owner.encoded_name.js, encoded_resource_name.js); + + uwriteln!( + class_header, + "@JSExportTopLevel(\"{}\")", + prefixed_export_name + ); uwrite!(class_header, "class {}(", encoded_resource_name.scala); let mut base_class_header = String::new(); @@ -403,7 +405,6 @@ impl<'a> ScalaJsExportedResourceSkeleton<'a> { constructor_args: String::new(), base_constructor_args: String::new(), base_static_trait_name, - base_class_name, } } @@ -494,34 +495,6 @@ impl<'a> ScalaJsExportedResourceSkeleton<'a> { } } - pub fn constructor_function(&self) -> String { - let mut constructor = String::new(); - uwriteln!( - constructor, - " @JSExport(\"{}\")", - self.encoded_resource_name.js - ); - let encoded_name = self - .owner - .generator - .context - .encode_name(self.resource_name.to_lower_camel_case()); - uwriteln!( - constructor, - " def {}({}): {} = ", - encoded_name.scala, - self.constructor_args, - self.base_class_name - ); - uwriteln!( - constructor, - " new {}({})", - self.encoded_resource_name.scala, - self.base_constructor_args - ); - constructor - } - pub fn finalize(self) -> String { let mut class_source = self.class_header; uwrite!(class_source, "{}", self.constructor_args); From d9c42f1b99f1227368ef9770d7e5fbf8651db293 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sun, 23 Feb 2025 12:08:46 +0100 Subject: [PATCH 20/36] Remove resource constructors from interface base traits --- crates/scalajs/src/interface.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/scalajs/src/interface.rs b/crates/scalajs/src/interface.rs index f6680af61..8b03cab6f 100644 --- a/crates/scalajs/src/interface.rs +++ b/crates/scalajs/src/interface.rs @@ -138,9 +138,7 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, ""); } - let mut constructors = Vec::new(); for (_, resource) in exported_resources { - constructors.push(resource.constructor_function()); uwriteln!(source, "{}", resource.finalize()); uwriteln!(source, ""); } @@ -151,10 +149,6 @@ impl<'a> ScalaJsInterface<'a> { uwriteln!(source, "{function}"); } - for constructor in constructors { - uwriteln!(source, "{constructor}"); - } - uwriteln!(source, " }}"); uwriteln!(source, "}}"); From 8117ffa02db74a10d78f1db96400917a1a33e182 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sun, 23 Feb 2025 12:14:55 +0100 Subject: [PATCH 21/36] Format and CI fixes --- .github/workflows/main.yml | 6 +++++- ci/build-tarballs.sh | 8 ++++---- crates/scalajs/src/rt.rs | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f7d46bc65..7164b8f14 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -59,7 +59,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] # moonbit removed from language matrix for now - causing CI failures - lang: [c, rust, teavm-java, go, csharp] + lang: [c, rust, teavm-java, go, csharp, scalajs] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -116,6 +116,9 @@ jobs: with: tinygo-version: 0.31.0 + - uses: olafurpg/setup-scala@v11 + if: matrix.lang == 'scalajs' + - run: | cargo test \ -p wit-bindgen-cli \ @@ -165,6 +168,7 @@ jobs: - run: cargo build --no-default-features --features csharp - run: cargo build --no-default-features --features markdown - run: cargo build --no-default-features --features moonbit + - run: cargo build --no-default-features --features scalajs # Feature combos of the `wit-bindgen` crate - run: cargo build --target wasm32-wasip1 -p wit-bindgen --no-default-features diff --git a/ci/build-tarballs.sh b/ci/build-tarballs.sh index 8b6c559db..4b845fbdb 100755 --- a/ci/build-tarballs.sh +++ b/ci/build-tarballs.sh @@ -10,19 +10,19 @@ mkdir tmp mkdir -p dist tag=$(./ci/print-current-version.sh) -bin_pkgname=wit-bindgen-$tag-$platform +bin_pkgname=wit-bindgen-scalajs-$tag-$platform mkdir tmp/$bin_pkgname cp LICENSE-* README.md tmp/$bin_pkgname fmt=tar if [ "$platform" = "x86_64-windows" ]; then - cp target/release/wit-bindgen.exe tmp/$bin_pkgname + cp target/release/wit-bindgen-scalajs.exe tmp/$bin_pkgname fmt=zip elif [ "$target" = "" ]; then - cp target/release/wit-bindgen tmp/$bin_pkgname + cp target/release/wit-bindgen-scalajs tmp/$bin_pkgname else - cp target/$target/release/wit-bindgen tmp/$bin_pkgname + cp target/$target/release/wit-bindgen-sclajs tmp/$bin_pkgname fi diff --git a/crates/scalajs/src/rt.rs b/crates/scalajs/src/rt.rs index 0a6349ecc..9e68b93ed 100644 --- a/crates/scalajs/src/rt.rs +++ b/crates/scalajs/src/rt.rs @@ -1,7 +1,7 @@ -use wit_bindgen_core::uwriteln; use crate::context::ScalaJsFile; use crate::Opts; use std::fmt::Write; +use wit_bindgen_core::uwriteln; pub fn render_runtime_module(opts: &Opts) -> ScalaJsFile { let wit_scala = include_str!("../scala/wit.scala"); From 3253e54c2971eb3dc9ce7e28c69fe0cbf8b8eec7 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sun, 23 Feb 2025 12:26:58 +0100 Subject: [PATCH 22/36] Fix typo --- ci/build-tarballs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/build-tarballs.sh b/ci/build-tarballs.sh index 4b845fbdb..60722553a 100755 --- a/ci/build-tarballs.sh +++ b/ci/build-tarballs.sh @@ -22,7 +22,7 @@ if [ "$platform" = "x86_64-windows" ]; then elif [ "$target" = "" ]; then cp target/release/wit-bindgen-scalajs tmp/$bin_pkgname else - cp target/$target/release/wit-bindgen-sclajs tmp/$bin_pkgname + cp target/$target/release/wit-bindgen-scalajs tmp/$bin_pkgname fi From 86d8ba214bc6974dcaac1c75e2833d72fe8d6f15 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sun, 23 Feb 2025 13:00:16 +0100 Subject: [PATCH 23/36] No need for setup-scala --- .github/workflows/main.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7164b8f14..0d24e04fc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -116,9 +116,6 @@ jobs: with: tinygo-version: 0.31.0 - - uses: olafurpg/setup-scala@v11 - if: matrix.lang == 'scalajs' - - run: | cargo test \ -p wit-bindgen-cli \ From 5b1979ecd1a209ee659a46693e186a956c7f612d Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sun, 23 Feb 2025 13:13:11 +0100 Subject: [PATCH 24/36] setup-scala is needed, but does not work on Windows --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0d24e04fc..462781754 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -116,12 +116,16 @@ jobs: with: tinygo-version: 0.31.0 + - uses: olafurpg/setup-scala@v11 + if: matrix.lang == 'scalajs' + - run: | cargo test \ -p wit-bindgen-cli \ -p wit-bindgen-${{ matrix.lang }} \ --no-default-features \ --features ${{ matrix.lang }} + if: matrix.os != 'windows-latest' || matrix.lang == 'scalajs' # setup-scala does not make sbt available on Windows? test_unit: name: Crate Unit Tests From 01b6e37b9fad61046bf53300656170442ea40d69 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sun, 23 Feb 2025 13:26:48 +0100 Subject: [PATCH 25/36] CI fix --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 462781754..07303c1c3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -125,7 +125,7 @@ jobs: -p wit-bindgen-${{ matrix.lang }} \ --no-default-features \ --features ${{ matrix.lang }} - if: matrix.os != 'windows-latest' || matrix.lang == 'scalajs' # setup-scala does not make sbt available on Windows? + if: ! (matrix.os == 'windows-latest' && matrix.lang == 'scalajs') # setup-scala does not make sbt available on Windows? test_unit: name: Crate Unit Tests From 8fd55a2c088d4f76d85fda82278666cb09666395 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Sun, 23 Feb 2025 13:34:47 +0100 Subject: [PATCH 26/36] CI fix --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 07303c1c3..24c58b7b0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -125,7 +125,7 @@ jobs: -p wit-bindgen-${{ matrix.lang }} \ --no-default-features \ --features ${{ matrix.lang }} - if: ! (matrix.os == 'windows-latest' && matrix.lang == 'scalajs') # setup-scala does not make sbt available on Windows? + if: ${{ ! (matrix.os == 'windows-latest' && matrix.lang == 'scalajs') }} # setup-scala does not make sbt available on Windows? test_unit: name: Crate Unit Tests From cb8ad56471559af982666d3fcf8a0bf75f58c308 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Tue, 25 Feb 2025 16:50:13 +0100 Subject: [PATCH 27/36] Disable ScalaJS tests on MacOS too for CI --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 24c58b7b0..d97067b8f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -125,7 +125,7 @@ jobs: -p wit-bindgen-${{ matrix.lang }} \ --no-default-features \ --features ${{ matrix.lang }} - if: ${{ ! (matrix.os == 'windows-latest' && matrix.lang == 'scalajs') }} # setup-scala does not make sbt available on Windows? + if: ${{ (! (matrix.os == 'windows-latest' && matrix.lang == 'scalajs')) && (! (matrix.os == 'macos-latest' && matrix.lang == 'scalajs')) }} # setup-scala does not make sbt available on Windows, and the scalajs tests are very slow on macos test_unit: name: Crate Unit Tests From 7a923cb30d514e31111ed6640f2cd24231cca52a Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Tue, 25 Feb 2025 16:51:10 +0100 Subject: [PATCH 28/36] Reenabled all features --- Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 12cafca97..c7459b922 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,13 +70,13 @@ wasm-encoder = { workspace = true } [features] default = [ -# 'c', -# 'rust', -# 'markdown', -# 'teavm-java', -# 'go', -# 'csharp', -# 'moonbit', + 'c', + 'rust', + 'markdown', + 'teavm-java', + 'go', + 'csharp', + 'moonbit', 'scalajs', 'async', ] From 8c1aec0acf6934d07e32037860309ee513585079 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Tue, 25 Feb 2025 16:51:47 +0100 Subject: [PATCH 29/36] Undo readme change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d82af1a05..8f003ef11 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ WIT files are currently added to a `wit/` folder adjacent to your `Cargo.toml` file. Example code using this then looks like: ```rust -// src/lib.rs.rs +// src/lib.rs // Use a procedural macro to generate bindings for the world we specified in // `host.wit` From b0c7f84a4de8286443e22a1ac23a87051d01bc59 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Tue, 25 Feb 2025 16:52:58 +0100 Subject: [PATCH 30/36] Undo executable name --- Cargo.toml | 2 +- ci/build-tarballs.sh | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c7459b922..dc6c9cd7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ wit-bindgen-scalajs = { path = 'crates/scalajs', version = '0.37.0' } wit-bindgen = { path = 'crates/guest-rust', version = '0.37.0', default-features = false } [[bin]] -name = "wit-bindgen-scalajs" +name = "wit-bindgen" path = "src/bin/wit-bindgen.rs" [dependencies] diff --git a/ci/build-tarballs.sh b/ci/build-tarballs.sh index 60722553a..8b6c559db 100755 --- a/ci/build-tarballs.sh +++ b/ci/build-tarballs.sh @@ -10,19 +10,19 @@ mkdir tmp mkdir -p dist tag=$(./ci/print-current-version.sh) -bin_pkgname=wit-bindgen-scalajs-$tag-$platform +bin_pkgname=wit-bindgen-$tag-$platform mkdir tmp/$bin_pkgname cp LICENSE-* README.md tmp/$bin_pkgname fmt=tar if [ "$platform" = "x86_64-windows" ]; then - cp target/release/wit-bindgen-scalajs.exe tmp/$bin_pkgname + cp target/release/wit-bindgen.exe tmp/$bin_pkgname fmt=zip elif [ "$target" = "" ]; then - cp target/release/wit-bindgen-scalajs tmp/$bin_pkgname + cp target/release/wit-bindgen tmp/$bin_pkgname else - cp target/$target/release/wit-bindgen-scalajs tmp/$bin_pkgname + cp target/$target/release/wit-bindgen tmp/$bin_pkgname fi From 0488de50ac914e9e08bdbce17a5a6cd7d68b50ab Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Tue, 25 Feb 2025 16:54:32 +0100 Subject: [PATCH 31/36] No runtime scalajs tests yet --- tests/runtime/main.rs | 88 ------------------------------------------- 1 file changed, 88 deletions(-) diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 59c622f4f..0a723700d 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -769,94 +769,6 @@ fn tests(name: &str, dir_name: &str) -> Result> { } } - #[cfg(feature = "scalajs")] - if !scalajs.is_empty() { - // let (resolve, world) = resolve_wit_dir(&dir); - // for path in c.iter() { - // let world_name = &resolve.worlds[world].name; - // let out_dir = out_dir.join(format!("c-{}", world_name)); - // drop(fs::remove_dir_all(&out_dir)); - // fs::create_dir_all(&out_dir).unwrap(); - // - // let snake = world_name.replace("-", "_"); - // let mut files = Default::default(); - // let mut opts = wit_bindgen_c::Opts::default(); - // if let Some(path) = path.file_name().and_then(|s| s.to_str()) { - // if path.contains("utf16") { - // opts.string_encoding = wit_component::StringEncoding::UTF16; - // } - // } - // opts.build().generate(&resolve, world, &mut files).unwrap(); - // - // for (file, contents) in files.iter() { - // let dst = out_dir.join(file); - // fs::write(dst, contents).unwrap(); - // } - // - // let sdk = PathBuf::from(std::env::var_os("WASI_SDK_PATH").expect( - // "point the `WASI_SDK_PATH` environment variable to the path of your wasi-sdk", - // )); - // // Test both C mode and C++ mode. - // for compiler in ["bin/clang", "bin/clang++"] { - // let mut cmd = Command::new(sdk.join(compiler)); - // let out_wasm = out_dir.join(format!( - // "c-{}.wasm", - // path.file_stem().and_then(|s| s.to_str()).unwrap() - // )); - // cmd.arg("--sysroot").arg(sdk.join("share/wasi-sysroot")); - // cmd.arg(path) - // .arg(out_dir.join(format!("{snake}.c"))) - // .arg(out_dir.join(format!("{snake}_component_type.o"))) - // .arg("-I") - // .arg(&out_dir) - // .arg("-Wall") - // .arg("-Wextra") - // .arg("-Werror") - // .arg("-Wno-unused-parameter") - // .arg("-mexec-model=reactor") - // .arg("-g") - // .arg("-o") - // .arg(&out_wasm); - // // Disable the warning about compiling a `.c` file in C++ mode. - // if compiler.ends_with("++") { - // cmd.arg("-Wno-deprecated"); - // } - // let command = format!("{cmd:?}"); - // let output = match cmd.output() { - // Ok(output) => output, - // Err(e) => panic!("failed to spawn compiler: {e}; command was `{command}`"), - // }; - // - // if !output.status.success() { - // println!("status: {}", output.status); - // println!("stdout: ------------------------------------------"); - // println!("{}", String::from_utf8_lossy(&output.stdout)); - // println!("stderr: ------------------------------------------"); - // println!("{}", String::from_utf8_lossy(&output.stderr)); - // panic!("failed to compile"); - // } - // - // // Translate the canonical ABI module into a component. - // let module = fs::read(&out_wasm).expect("failed to read wasm file"); - // let component = ComponentEncoder::default() - // .module(module.as_slice()) - // .expect("pull custom sections from module") - // .validate(true) - // .adapter("wasi_snapshot_preview1", &wasi_adapter) - // .expect("adapter failed to get loaded") - // .encode() - // .expect(&format!( - // "module {:?} can be translated to a component", - // out_wasm - // )); - // let component_path = out_wasm.with_extension("component.wasm"); - // fs::write(&component_path, component).expect("write component to disk"); - // - // result.push(component_path); - // } - // } - } - Ok(result) } From 5998e3a3b12ce2350da8139ae9df5f55da340ac9 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Tue, 25 Feb 2025 16:54:43 +0100 Subject: [PATCH 32/36] Undo Cargo.toml change --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dc6c9cd7f..b3b67a4bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,6 @@ wit-bindgen = { path = 'crates/guest-rust', version = '0.37.0', default-features [[bin]] name = "wit-bindgen" -path = "src/bin/wit-bindgen.rs" [dependencies] anyhow = { workspace = true } From f3bc801fc96a4020564604679d6d0f7dbd57cdab Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Tue, 25 Feb 2025 17:29:26 +0100 Subject: [PATCH 33/36] Fix after updating to latest wit-parser --- Cargo.lock | 2 +- crates/scalajs/src/context.rs | 63 +++++++++++---------------------- crates/scalajs/src/interface.rs | 9 +++-- crates/scalajs/src/resource.rs | 8 ++--- crates/scalajs/src/skeleton.rs | 15 ++++---- crates/scalajs/src/world.rs | 4 +-- 6 files changed, 38 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc7f6fcbd..3062e0d26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2672,7 +2672,7 @@ dependencies = [ [[package]] name = "wit-bindgen-scalajs" -version = "0.37.0" +version = "0.39.0" dependencies = [ "anyhow", "clap", diff --git a/crates/scalajs/src/context.rs b/crates/scalajs/src/context.rs index 5a4758501..6db5b79a3 100644 --- a/crates/scalajs/src/context.rs +++ b/crates/scalajs/src/context.rs @@ -6,8 +6,8 @@ use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase}; use std::collections::{HashMap, HashSet}; use std::fmt::Write; use wit_bindgen_core::wit_parser::{ - Docs, Handle, InterfaceId, PackageName, Resolve, Results, Tuple, Type, TypeDef, TypeDefKind, - TypeId, TypeOwner, WorldItem, + Docs, Handle, InterfaceId, PackageName, Resolve, Tuple, Type, TypeDef, TypeDefKind, TypeId, + TypeOwner, WorldItem, }; use wit_bindgen_core::Direction::{Export, Import}; use wit_bindgen_core::{uwrite, uwriteln, Direction}; @@ -274,12 +274,12 @@ impl ScalaJsContext { &self, owner_context: &impl OwnerContext, resolve: &Resolve, - results: &Results, + results: &Option, ) -> (String, Option) { match results { - Results::Named(results) if results.len() == 0 => ("Unit".to_string(), None), - Results::Named(pairs) if pairs.len() == 1 => { - if let Some((ok, err)) = results.throws(resolve) { + None => ("Unit".to_string(), None), + Some(result) => { + if let Some((ok, err)) = throws(result, resolve) { let throws = if let Some(err) = err { Some(self.render_type_reference(owner_context, resolve, err)) } else { @@ -295,43 +295,7 @@ impl ScalaJsContext { } } else { ( - self.render_type_reference( - owner_context, - resolve, - &pairs.iter().next().unwrap().1, - ), - None, - ) - } - } - Results::Named(results) => ( - self.render_tuple( - owner_context, - resolve, - &Tuple { - types: results.iter().map(|(_, typ)| typ.clone()).collect(), - }, - ), - None, - ), - Results::Anon(typ) => { - if let Some((ok, err)) = results.throws(resolve) { - let throws = if let Some(err) = err { - Some(self.render_type_reference(owner_context, resolve, err)) - } else { - Some("Unit".to_string()) - }; - if let Some(ok) = ok { - ( - self.render_type_reference(owner_context, resolve, ok), - throws, - ) - } else { - ("Unit".to_string(), throws) - } - } else { - ( - self.render_type_reference(owner_context, resolve, typ), + self.render_type_reference(owner_context, resolve, result), None, ) } @@ -810,6 +774,19 @@ pub fn write_doc_comment(source: &mut impl Write, indent: &str, docs: &Docs) { } } +pub fn throws<'a>( + typ: &Type, + resolve: &'a Resolve, +) -> Option<(Option<&'a Type>, Option<&'a Type>)> { + match typ { + Type::Id(id) => match &resolve.types[*id].kind { + TypeDefKind::Result(r) => Some((r.ok.as_ref(), r.err.as_ref())), + _ => None, + }, + _ => None, + } +} + #[allow(dead_code)] pub struct EncodedName { pub scala: String, diff --git a/crates/scalajs/src/interface.rs b/crates/scalajs/src/interface.rs index 8b03cab6f..94b0ce1c5 100644 --- a/crates/scalajs/src/interface.rs +++ b/crates/scalajs/src/interface.rs @@ -293,11 +293,10 @@ impl<'a> ScalaJsInterface<'a> { self.generator .context .render_args(self, self.resolve, func.params.iter()); - let (ret, throws) = self.generator.context.render_return_type( - self, - self.resolve, - &func.results, - ); + let (ret, throws) = + self.generator + .context + .render_return_type(self, self.resolve, &func.result); let mut function = String::new(); write_doc_comment(&mut function, " ", &func.docs); diff --git a/crates/scalajs/src/resource.rs b/crates/scalajs/src/resource.rs index dc27b50fd..cae7c330f 100644 --- a/crates/scalajs/src/resource.rs +++ b/crates/scalajs/src/resource.rs @@ -83,7 +83,7 @@ impl ScalaJsImportedResource { FunctionKind::Method(_) => { let args = context.render_args(owner_context, resolve, func.params.iter().skip(1)); let (ret, throws) = - context.render_return_type(owner_context, resolve, &func.results); + context.render_return_type(owner_context, resolve, &func.result); let encoded_func_name = self.get_func_name(context, "[method]", func_name); let overrd = if context @@ -118,7 +118,7 @@ impl ScalaJsImportedResource { FunctionKind::Static(_) => { let args = context.render_args(owner_context, resolve, func.params.iter()); let (ret, throws) = - context.render_return_type(owner_context, resolve, &func.results); + context.render_return_type(owner_context, resolve, &func.result); let postfix = if let Some(throws) = throws { format!(" // throws {}", throws) @@ -244,7 +244,7 @@ impl<'a> ScalaJsExportedResource<'a> { let (ret, throws) = self.owner.generator.context.render_return_type( self.owner, self.owner.resolve, - &func.results, + &func.result, ); let encoded_func_name = self.get_func_name("[method]", func_name); @@ -284,7 +284,7 @@ impl<'a> ScalaJsExportedResource<'a> { let (ret, throws) = self.owner.generator.context.render_return_type( self.owner, self.owner.resolve, - &func.results, + &func.result, ); let postfix = if let Some(throws) = throws { diff --git a/crates/scalajs/src/skeleton.rs b/crates/scalajs/src/skeleton.rs index 980639a39..4e44783b8 100644 --- a/crates/scalajs/src/skeleton.rs +++ b/crates/scalajs/src/skeleton.rs @@ -144,11 +144,10 @@ impl<'a> ScalaJsInterfaceSkeleton<'a> { self.generator .context .render_args(self, self.resolve, func.params.iter()); - let (ret, throws) = self.generator.context.render_return_type( - self, - self.resolve, - &func.results, - ); + let (ret, throws) = + self.generator + .context + .render_return_type(self, self.resolve, &func.result); let postfix = if let Some(throws) = throws { format!(" // throws {}", throws) @@ -286,7 +285,7 @@ impl ScalaJsWorldSkeleton { FunctionKind::Freestanding => { let encoded_name = context.encode_name(func_name.to_lower_camel_case()); let args = context.render_args(context, resolve, func.params.iter()); - let (ret, throws) = context.render_return_type(context, resolve, &func.results); + let (ret, throws) = context.render_return_type(context, resolve, &func.result); let postfix = if let Some(throws) = throws { format!(" // throws {}", throws) @@ -420,7 +419,7 @@ impl<'a> ScalaJsExportedResourceSkeleton<'a> { let (ret, throws) = self.owner.generator.context.render_return_type( self.owner, self.owner.resolve, - &func.results, + &func.result, ); let encoded_func_name = self.get_func_name("[method]", func_name); @@ -448,7 +447,7 @@ impl<'a> ScalaJsExportedResourceSkeleton<'a> { let (ret, throws) = self.owner.generator.context.render_return_type( self.owner, self.owner.resolve, - &func.results, + &func.result, ); let postfix = if let Some(throws) = throws { diff --git a/crates/scalajs/src/world.rs b/crates/scalajs/src/world.rs index 2923e08c9..411c03711 100644 --- a/crates/scalajs/src/world.rs +++ b/crates/scalajs/src/world.rs @@ -103,7 +103,7 @@ impl ScalaJsWorld { ); let encoded_name = context.encode_name(func_name.to_lower_camel_case()); let args = context.render_args(context, resolve, func.params.iter()); - let (ret, throws) = context.render_return_type(context, resolve, &func.results); + let (ret, throws) = context.render_return_type(context, resolve, &func.result); let postfix = if let Some(throws) = throws { format!(" // throws {}", throws) @@ -164,7 +164,7 @@ impl ScalaJsWorld { FunctionKind::Freestanding => { let encoded_name = context.encode_name(func_name.to_lower_camel_case()); let args = context.render_args(context, resolve, func.params.iter()); - let (ret, throws) = context.render_return_type(context, resolve, &func.results); + let (ret, throws) = context.render_return_type(context, resolve, &func.result); let postfix = if let Some(throws) = throws { format!(" // throws {}", throws) From c82af1ee000bd8eaab63dc94353b77090812fa24 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Wed, 26 Feb 2025 10:00:00 +0000 Subject: [PATCH 34/36] Update crates/scalajs/Cargo.toml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Doeraene --- crates/scalajs/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/scalajs/Cargo.toml b/crates/scalajs/Cargo.toml index d08c3ad1d..8647f8f99 100644 --- a/crates/scalajs/Cargo.toml +++ b/crates/scalajs/Cargo.toml @@ -7,7 +7,7 @@ repository = { workspace = true } license = { workspace = true } homepage = 'https://github.com/bytecodealliance/wit-bindgen' description = """ -Scala.JS bindings generator for WIT and the component model, typically used +Scala.js bindings generator for WIT and the component model, typically used through the `wit-bindgen-cli` crate. """ From 178be86661f75ce5a04998d54c899f3161b27950 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Thu, 27 Feb 2025 13:19:33 +0100 Subject: [PATCH 35/36] Using js.TupleN and up-to-date github action steps --- .github/workflows/main.yml | 8 +- crates/scalajs/scala/wit.scala | 589 ++------------------------------- 2 files changed, 28 insertions(+), 569 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 36202e1ff..a955096d8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -112,7 +112,13 @@ jobs: with: tinygo-version: 0.31.0 - - uses: olafurpg/setup-scala@v11 + - uses: actions/setup-java@v4 + if: matrix.lang == 'scalajs' + with: + distribution: 'temurin' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 if: matrix.lang == 'scalajs' - run: | diff --git a/crates/scalajs/scala/wit.scala b/crates/scalajs/scala/wit.scala index 5d0f0a2a9..385fe6ea5 100644 --- a/crates/scalajs/scala/wit.scala +++ b/crates/scalajs/scala/wit.scala @@ -114,572 +114,25 @@ package object wit { implicit def toScalaTuple1[T1](tuple: WitTuple1[T1]): (T1) = (tuple._1) } - sealed trait WitTuple2[T1, T2] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - } - - object WitTuple2 { - def apply[T1, T2](_1: T1, _2: T2): WitTuple2[T1, T2] = js.Array(_1, _2).asInstanceOf[WitTuple2[T1, T2]] - - def unapply[T1, T2](tuple: WitTuple2[T1, T2]): Some[(T1, T2)] = Some(tuple) - - implicit def fromScalaTuple2[T1, T2](tuple: (T1, T2)): WitTuple2[T1, T2] = WitTuple2(tuple._1, tuple._2) - - implicit def toScalaTuple2[T1, T2](tuple: WitTuple2[T1, T2]): (T1, T2) = (tuple._1, tuple._2) - } - - sealed trait WitTuple3[T1, T2, T3] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - } - - object WitTuple3 { - def apply[T1, T2, T3](_1: T1, _2: T2, _3: T3): WitTuple3[T1, T2, T3] = js.Array(_1, _2, _3).asInstanceOf[WitTuple3[T1, T2, T3]] - - def unapply[T1, T2, T3](tuple: WitTuple3[T1, T2, T3]): Some[(T1, T2, T3)] = Some(tuple) - - implicit def fromScalaTuple3[T1, T2, T3](tuple: (T1, T2, T3)): WitTuple3[T1, T2, T3] = WitTuple3(tuple._1, tuple._2, tuple._3) - - implicit def toScalaTuple3[T1, T2, T3](tuple: WitTuple3[T1, T2, T3]): (T1, T2, T3) = (tuple._1, tuple._2, tuple._3) - } - - sealed trait WitTuple4[T1, T2, T3, T4] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - } - - object WitTuple4 { - def apply[T1, T2, T3, T4](_1: T1, _2: T2, _3: T3, _4: T4): WitTuple4[T1, T2, T3, T4] = js.Array(_1, _2, _3, _4).asInstanceOf[WitTuple4[T1, T2, T3, T4]] - - def unapply[T1, T2, T3, T4](tuple: WitTuple4[T1, T2, T3, T4]): Some[(T1, T2, T3, T4)] = Some(tuple) - - implicit def fromScalaTuple4[T1, T2, T3, T4](tuple: (T1, T2, T3, T4)): WitTuple4[T1, T2, T3, T4] = WitTuple4(tuple._1, tuple._2, tuple._3, tuple._4) - - implicit def toScalaTuple4[T1, T2, T3, T4](tuple: WitTuple4[T1, T2, T3, T4]): (T1, T2, T3, T4) = (tuple._1, tuple._2, tuple._3, tuple._4) - } - - sealed trait WitTuple5[T1, T2, T3, T4, T5] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - } - - object WitTuple5 { - def apply[T1, T2, T3, T4, T5](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5): WitTuple5[T1, T2, T3, T4, T5] = js.Array(_1, _2, _3, _4, _5).asInstanceOf[WitTuple5[T1, T2, T3, T4, T5]] - - def unapply[T1, T2, T3, T4, T5](tuple: WitTuple5[T1, T2, T3, T4, T5]): Some[(T1, T2, T3, T4, T5)] = Some(tuple) - - implicit def fromScalaTuple5[T1, T2, T3, T4, T5](tuple: (T1, T2, T3, T4, T5)): WitTuple5[T1, T2, T3, T4, T5] = WitTuple5(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5) - - implicit def toScalaTuple5[T1, T2, T3, T4, T5](tuple: WitTuple5[T1, T2, T3, T4, T5]): (T1, T2, T3, T4, T5) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5) - } - - sealed trait WitTuple6[T1, T2, T3, T4, T5, T6] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - } - - object WitTuple6 { - def apply[T1, T2, T3, T4, T5, T6](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6): WitTuple6[T1, T2, T3, T4, T5, T6] = js.Array(_1, _2, _3, _4, _5, _6).asInstanceOf[WitTuple6[T1, T2, T3, T4, T5, T6]] - - def unapply[T1, T2, T3, T4, T5, T6](tuple: WitTuple6[T1, T2, T3, T4, T5, T6]): Some[(T1, T2, T3, T4, T5, T6)] = Some(tuple) - - implicit def fromScalaTuple6[T1, T2, T3, T4, T5, T6](tuple: (T1, T2, T3, T4, T5, T6)): WitTuple6[T1, T2, T3, T4, T5, T6] = WitTuple6(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6) - - implicit def toScalaTuple6[T1, T2, T3, T4, T5, T6](tuple: WitTuple6[T1, T2, T3, T4, T5, T6]): (T1, T2, T3, T4, T5, T6) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6) - } - - sealed trait WitTuple7[T1, T2, T3, T4, T5, T6, T7] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - } - - object WitTuple7 { - def apply[T1, T2, T3, T4, T5, T6, T7](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7): WitTuple7[T1, T2, T3, T4, T5, T6, T7] = js.Array(_1, _2, _3, _4, _5, _6, _7).asInstanceOf[WitTuple7[T1, T2, T3, T4, T5, T6, T7]] - - def unapply[T1, T2, T3, T4, T5, T6, T7](tuple: WitTuple7[T1, T2, T3, T4, T5, T6, T7]): Some[(T1, T2, T3, T4, T5, T6, T7)] = Some(tuple) - - implicit def fromScalaTuple7[T1, T2, T3, T4, T5, T6, T7](tuple: (T1, T2, T3, T4, T5, T6, T7)): WitTuple7[T1, T2, T3, T4, T5, T6, T7] = WitTuple7(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7) - - implicit def toScalaTuple7[T1, T2, T3, T4, T5, T6, T7](tuple: WitTuple7[T1, T2, T3, T4, T5, T6, T7]): (T1, T2, T3, T4, T5, T6, T7) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7) - } - - sealed trait WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - } - - object WitTuple8 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8): WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8] = js.Array(_1, _2, _3, _4, _5, _6, _7, _8).asInstanceOf[WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8](tuple: WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8]): Some[(T1, T2, T3, T4, T5, T6, T7, T8)] = Some(tuple) - - implicit def fromScalaTuple8[T1, T2, T3, T4, T5, T6, T7, T8](tuple: (T1, T2, T3, T4, T5, T6, T7, T8)): WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8] = WitTuple8(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8) - - implicit def toScalaTuple8[T1, T2, T3, T4, T5, T6, T7, T8](tuple: WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8]): (T1, T2, T3, T4, T5, T6, T7, T8) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8) - } - - sealed trait WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - } - - object WitTuple9 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9): WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] = js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9).asInstanceOf[WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9](tuple: WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9)] = Some(tuple) - - implicit def fromScalaTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9)): WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] = WitTuple9(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9) - - implicit def toScalaTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9](tuple: WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]): (T1, T2, T3, T4, T5, T6, T7, T8, T9) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9) - } - - sealed trait WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - } - - object WitTuple10 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10): WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] = js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10).asInstanceOf[WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](tuple: WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)] = Some(tuple) - - implicit def fromScalaTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)): WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] = WitTuple10(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10) - - implicit def toScalaTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](tuple: WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10) - } - - sealed trait WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - @JSName("10") val _11: T11 - } - - object WitTuple11 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11): WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] = js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11).asInstanceOf[WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](tuple: WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)] = Some(tuple) - - implicit def fromScalaTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)): WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] = WitTuple11(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11) - - implicit def toScalaTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](tuple: WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) = (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11) - } - - sealed trait WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - @JSName("10") val _11: T11 - @JSName("11") val _12: T12 - } - - object WitTuple12 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12): WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] = - js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12).asInstanceOf[WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](tuple: WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)] = - Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12)) - - implicit def fromScalaTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)): WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] = - WitTuple12(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12) - - implicit def toScalaTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](tuple: WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) = - (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12) - } - - sealed trait WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - @JSName("10") val _11: T11 - @JSName("11") val _12: T12 - @JSName("12") val _13: T13 - } - - object WitTuple13 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13): WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] = - js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13).asInstanceOf[WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](tuple: WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13)] = - Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13)) - - implicit def fromScalaTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13)): WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] = - WitTuple13(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13) - - implicit def toScalaTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](tuple: WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) = - (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13) - } - - sealed trait WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - @JSName("10") val _11: T11 - @JSName("11") val _12: T12 - @JSName("12") val _13: T13 - @JSName("13") val _14: T14 - } - - object WitTuple14 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14): WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] = - js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14).asInstanceOf[WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](tuple: WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14)] = - Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14)) - - implicit def fromScalaTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14)): WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] = - WitTuple14(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14) - - implicit def toScalaTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](tuple: WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) = - (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14) - } - - sealed trait WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - @JSName("10") val _11: T11 - @JSName("11") val _12: T12 - @JSName("12") val _13: T13 - @JSName("13") val _14: T14 - @JSName("14") val _15: T15 - } - - object WitTuple15 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15): WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] = - js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15).asInstanceOf[WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](tuple: WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15)] = - Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15)) - - implicit def fromScalaTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15)): WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] = - WitTuple15(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15) - - implicit def toScalaTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](tuple: WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) = - (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15) - } - - sealed trait WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - @JSName("10") val _11: T11 - @JSName("11") val _12: T12 - @JSName("12") val _13: T13 - @JSName("13") val _14: T14 - @JSName("14") val _15: T15 - @JSName("15") val _16: T16 - } - - object WitTuple16 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16): WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16] = - js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16).asInstanceOf[WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](tuple: WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16)] = - Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16)) - - implicit def fromScalaTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16)): WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16] = - WitTuple16(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16) - - implicit def toScalaTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](tuple: WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) = - (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16) - } - - sealed trait WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - @JSName("10") val _11: T11 - @JSName("11") val _12: T12 - @JSName("12") val _13: T13 - @JSName("13") val _14: T14 - @JSName("14") val _15: T15 - @JSName("15") val _16: T16 - @JSName("16") val _17: T17 - } - - object WitTuple17 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17): WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17] = - js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17).asInstanceOf[WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](tuple: WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17)] = - Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17)) - - implicit def fromScalaTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17)): WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17] = - WitTuple17(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17) - - implicit def toScalaTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](tuple: WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) = - (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17) - } - - sealed trait WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - @JSName("10") val _11: T11 - @JSName("11") val _12: T12 - @JSName("12") val _13: T13 - @JSName("13") val _14: T14 - @JSName("14") val _15: T15 - @JSName("15") val _16: T16 - @JSName("16") val _17: T17 - @JSName("17") val _18: T18 - } - - object WitTuple18 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18): WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18] = - js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18).asInstanceOf[WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](tuple: WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18)] = - Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18)) - - implicit def fromScalaTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18)): WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18] = - WitTuple18(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18) - - implicit def toScalaTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](tuple: WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) = - (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18) - } - - sealed trait WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - @JSName("10") val _11: T11 - @JSName("11") val _12: T12 - @JSName("12") val _13: T13 - @JSName("13") val _14: T14 - @JSName("14") val _15: T15 - @JSName("15") val _16: T16 - @JSName("16") val _17: T17 - @JSName("17") val _18: T18 - @JSName("18") val _19: T19 - } - - object WitTuple19 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19): WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19] = - js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19).asInstanceOf[WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](tuple: WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19)] = - Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19)) - - implicit def fromScalaTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19)): WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19] = - WitTuple19(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19) - - implicit def toScalaTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](tuple: WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) = - (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19) - } - - sealed trait WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - @JSName("10") val _11: T11 - @JSName("11") val _12: T12 - @JSName("12") val _13: T13 - @JSName("13") val _14: T14 - @JSName("14") val _15: T15 - @JSName("15") val _16: T16 - @JSName("16") val _17: T17 - @JSName("17") val _18: T18 - @JSName("18") val _19: T19 - @JSName("19") val _20: T20 - } - - object WitTuple20 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19, _20: T20): WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20] = - js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20).asInstanceOf[WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20](tuple: WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20)] = - Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20)) - - implicit def fromScalaTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20)): WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20] = - WitTuple20(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20) - - implicit def toScalaTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20](tuple: WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20) = - (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20) - } - - sealed trait WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - @JSName("10") val _11: T11 - @JSName("11") val _12: T12 - @JSName("12") val _13: T13 - @JSName("13") val _14: T14 - @JSName("14") val _15: T15 - @JSName("15") val _16: T16 - @JSName("16") val _17: T17 - @JSName("17") val _18: T18 - @JSName("18") val _19: T19 - @JSName("19") val _20: T20 - @JSName("20") val _21: T21 - } - - object WitTuple21 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19, _20: T20, _21: T21): WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] = - js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21).asInstanceOf[WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](tuple: WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21)] = - Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20, tuple._21)) - - implicit def fromScalaTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21)): WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] = - WitTuple21(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20, tuple._21) - - implicit def toScalaTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21](tuple: WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21) = - (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20, tuple._21) - } - - sealed trait WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] extends js.Object { - @JSName("0") val _1: T1 - @JSName("1") val _2: T2 - @JSName("2") val _3: T3 - @JSName("3") val _4: T4 - @JSName("4") val _5: T5 - @JSName("5") val _6: T6 - @JSName("6") val _7: T7 - @JSName("7") val _8: T8 - @JSName("8") val _9: T9 - @JSName("9") val _10: T10 - @JSName("10") val _11: T11 - @JSName("11") val _12: T12 - @JSName("12") val _13: T13 - @JSName("13") val _14: T14 - @JSName("14") val _15: T15 - @JSName("15") val _16: T16 - @JSName("16") val _17: T17 - @JSName("17") val _18: T18 - @JSName("18") val _19: T19 - @JSName("19") val _20: T20 - @JSName("20") val _21: T21 - @JSName("21") val _22: T22 - } - - object WitTuple22 { - def apply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22](_1: T1, _2: T2, _3: T3, _4: T4, _5: T5, _6: T6, _7: T7, _8: T8, _9: T9, _10: T10, _11: T11, _12: T12, _13: T13, _14: T14, _15: T15, _16: T16, _17: T17, _18: T18, _19: T19, _20: T20, _21: T21, _22: T22): WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] = - js.Array(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22).asInstanceOf[WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]] - - def unapply[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22](tuple: WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]): Some[(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22)] = - Some((tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20, tuple._21, tuple._22)) - - implicit def fromScalaTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22](tuple: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22)): WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] = - WitTuple22(tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20, tuple._21, tuple._22) - - implicit def toScalaTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22](tuple: WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22]): (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22) = - (tuple._1, tuple._2, tuple._3, tuple._4, tuple._5, tuple._6, tuple._7, tuple._8, tuple._9, tuple._10, tuple._11, tuple._12, tuple._13, tuple._14, tuple._15, tuple._16, tuple._17, tuple._18, tuple._19, tuple._20, tuple._21, tuple._22) - } + type WitTuple2[T1, T2] = js.Tuple2[T1, T2] + type WitTuple3[T1, T2, T3] = js.Tuple3[T1, T2, T3] + type WitTuple4[T1, T2, T3, T4] = js.Tuple4[T1, T2, T3, T4] + type WitTuple5[T1, T2, T3, T4, T5] = js.Tuple5[T1, T2, T3, T4, T5] + type WitTuple6[T1, T2, T3, T4, T5, T6] = js.Tuple6[T1, T2, T3, T4, T5, T6] + type WitTuple7[T1, T2, T3, T4, T5, T6, T7] = js.Tuple7[T1, T2, T3, T4, T5, T6, T7] + type WitTuple8[T1, T2, T3, T4, T5, T6, T7, T8] = js.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8] + type WitTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] = js.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] + type WitTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] = js.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] + type WitTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] = js.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] + type WitTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] = js.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] + type WitTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] = js.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] + type WitTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] = js.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] + type WitTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] = js.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] + type WitTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16] = js.Tuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16] + type WitTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17] = js.Tuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17] + type WitTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18] = js.Tuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18] + type WitTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19] = js.Tuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19] + type WitTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20] = js.Tuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20] + type WitTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] = js.Tuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21] + type WitTuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] = js.Tuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] } \ No newline at end of file From 809bac79115be1b80adf791e00ab3e97259d8355 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Thu, 27 Feb 2025 13:24:00 +0100 Subject: [PATCH 36/36] Renamed to scalajs-jco --- .github/workflows/main.yml | 10 +++++----- Cargo.lock | 4 ++-- Cargo.toml | 8 ++++---- crates/{scalajs => scalajs-jco}/Cargo.toml | 2 +- crates/{scalajs => scalajs-jco}/scala/build.properties | 0 crates/{scalajs => scalajs-jco}/scala/build.sbt | 0 crates/{scalajs => scalajs-jco}/scala/plugins.sbt | 0 crates/{scalajs => scalajs-jco}/scala/wit.scala | 0 crates/{scalajs => scalajs-jco}/src/context.rs | 0 crates/{scalajs => scalajs-jco}/src/interface.rs | 0 crates/{scalajs => scalajs-jco}/src/jco.rs | 0 crates/{scalajs => scalajs-jco}/src/lib.rs | 0 crates/{scalajs => scalajs-jco}/src/resource.rs | 0 crates/{scalajs => scalajs-jco}/src/rt.rs | 0 crates/{scalajs => scalajs-jco}/src/skeleton.rs | 0 crates/{scalajs => scalajs-jco}/src/world.rs | 0 crates/{scalajs => scalajs-jco}/tests/codegen.rs | 4 ++-- 17 files changed, 14 insertions(+), 14 deletions(-) rename crates/{scalajs => scalajs-jco}/Cargo.toml (94%) rename crates/{scalajs => scalajs-jco}/scala/build.properties (100%) rename crates/{scalajs => scalajs-jco}/scala/build.sbt (100%) rename crates/{scalajs => scalajs-jco}/scala/plugins.sbt (100%) rename crates/{scalajs => scalajs-jco}/scala/wit.scala (100%) rename crates/{scalajs => scalajs-jco}/src/context.rs (100%) rename crates/{scalajs => scalajs-jco}/src/interface.rs (100%) rename crates/{scalajs => scalajs-jco}/src/jco.rs (100%) rename crates/{scalajs => scalajs-jco}/src/lib.rs (100%) rename crates/{scalajs => scalajs-jco}/src/resource.rs (100%) rename crates/{scalajs => scalajs-jco}/src/rt.rs (100%) rename crates/{scalajs => scalajs-jco}/src/skeleton.rs (100%) rename crates/{scalajs => scalajs-jco}/src/world.rs (100%) rename crates/{scalajs => scalajs-jco}/tests/codegen.rs (96%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a955096d8..2c8b940a9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -59,7 +59,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] # moonbit removed from language matrix for now - causing CI failures - lang: [c, rust, teavm-java, go, csharp, scalajs] + lang: [c, rust, teavm-java, go, csharp, scalajs-jco] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -113,13 +113,13 @@ jobs: tinygo-version: 0.31.0 - uses: actions/setup-java@v4 - if: matrix.lang == 'scalajs' + if: matrix.lang == 'scalajs-jco' with: distribution: 'temurin' java-version: 17 cache: 'sbt' - uses: sbt/setup-sbt@v1 - if: matrix.lang == 'scalajs' + if: matrix.lang == 'scalajs-jco' - run: | cargo test \ @@ -127,7 +127,7 @@ jobs: -p wit-bindgen-${{ matrix.lang }} \ --no-default-features \ --features ${{ matrix.lang }} - if: ${{ (! (matrix.os == 'windows-latest' && matrix.lang == 'scalajs')) && (! (matrix.os == 'macos-latest' && matrix.lang == 'scalajs')) }} # setup-scala does not make sbt available on Windows, and the scalajs tests are very slow on macos + if: ${{ (! (matrix.os == 'windows-latest' && matrix.lang == 'scalajs-jco')) && (! (matrix.os == 'macos-latest' && matrix.lang == 'scalajs-jco')) }} # setup-scala does not make sbt available on Windows, and the scalajs tests are very slow on macos test_unit: name: Crate Unit Tests @@ -171,7 +171,7 @@ jobs: - run: cargo build --no-default-features --features csharp - run: cargo build --no-default-features --features markdown - run: cargo build --no-default-features --features moonbit - - run: cargo build --no-default-features --features scalajs + - run: cargo build --no-default-features --features scalajs-jco # Feature combos of the `wit-bindgen` crate - run: cargo build --target wasm32-wasip1 -p wit-bindgen --no-default-features diff --git a/Cargo.lock b/Cargo.lock index 3062e0d26..3630e9321 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2561,7 +2561,7 @@ dependencies = [ "wit-bindgen-markdown", "wit-bindgen-moonbit", "wit-bindgen-rust", - "wit-bindgen-scalajs", + "wit-bindgen-scalajs-jco", "wit-bindgen-teavm-java", "wit-component", "wit-parser 0.226.0", @@ -2671,7 +2671,7 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-scalajs" +name = "wit-bindgen-scalajs-jco" version = "0.39.0" dependencies = [ "anyhow", diff --git a/Cargo.toml b/Cargo.toml index fce2bfff0..c24640484 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ wit-bindgen-go = { path = 'crates/go', version = '0.39.0' } wit-bindgen-csharp = { path = 'crates/csharp', version = '0.39.0' } wit-bindgen-markdown = { path = 'crates/markdown', version = '0.39.0' } wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.39.0' } -wit-bindgen-scalajs = { path = 'crates/scalajs', version = '0.39.0' } +wit-bindgen-scalajs-jco = { path = 'crates/scalajs-jco', version = '0.39.0' } wit-bindgen = { path = 'crates/guest-rust', version = '0.39.0', default-features = false } [[bin]] @@ -64,7 +64,7 @@ wit-bindgen-moonbit = { workspace = true, features = ['clap'], optional = true } wit-bindgen-teavm-java = { workspace = true, features = ['clap'], optional = true } wit-bindgen-go = { workspace = true, features = ['clap'], optional = true } wit-bindgen-csharp = { workspace = true, features = ['clap'], optional = true } -wit-bindgen-scalajs = { workspace = true, features = ['clap'], optional = true } +wit-bindgen-scalajs-jco = { workspace = true, features = ['clap'], optional = true } wit-component = { workspace = true } wasm-encoder = { workspace = true } @@ -77,7 +77,7 @@ default = [ 'go', 'csharp', 'moonbit', - 'scalajs', + 'scalajs-jco', 'async', ] c = ['dep:wit-bindgen-c'] @@ -88,7 +88,7 @@ go = ['dep:wit-bindgen-go'] csharp = ['dep:wit-bindgen-csharp'] csharp-mono = ['csharp'] moonbit = ['dep:wit-bindgen-moonbit'] -scalajs = ['dep:wit-bindgen-scalajs'] +scalajs-jco = ['dep:wit-bindgen-scalajs-jco'] async = [] [dev-dependencies] diff --git a/crates/scalajs/Cargo.toml b/crates/scalajs-jco/Cargo.toml similarity index 94% rename from crates/scalajs/Cargo.toml rename to crates/scalajs-jco/Cargo.toml index 8647f8f99..6d340307e 100644 --- a/crates/scalajs/Cargo.toml +++ b/crates/scalajs-jco/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wit-bindgen-scalajs" +name = "wit-bindgen-scalajs-jco" authors = ["Daniel Vigovszky "] version = { workspace = true } edition = { workspace = true } diff --git a/crates/scalajs/scala/build.properties b/crates/scalajs-jco/scala/build.properties similarity index 100% rename from crates/scalajs/scala/build.properties rename to crates/scalajs-jco/scala/build.properties diff --git a/crates/scalajs/scala/build.sbt b/crates/scalajs-jco/scala/build.sbt similarity index 100% rename from crates/scalajs/scala/build.sbt rename to crates/scalajs-jco/scala/build.sbt diff --git a/crates/scalajs/scala/plugins.sbt b/crates/scalajs-jco/scala/plugins.sbt similarity index 100% rename from crates/scalajs/scala/plugins.sbt rename to crates/scalajs-jco/scala/plugins.sbt diff --git a/crates/scalajs/scala/wit.scala b/crates/scalajs-jco/scala/wit.scala similarity index 100% rename from crates/scalajs/scala/wit.scala rename to crates/scalajs-jco/scala/wit.scala diff --git a/crates/scalajs/src/context.rs b/crates/scalajs-jco/src/context.rs similarity index 100% rename from crates/scalajs/src/context.rs rename to crates/scalajs-jco/src/context.rs diff --git a/crates/scalajs/src/interface.rs b/crates/scalajs-jco/src/interface.rs similarity index 100% rename from crates/scalajs/src/interface.rs rename to crates/scalajs-jco/src/interface.rs diff --git a/crates/scalajs/src/jco.rs b/crates/scalajs-jco/src/jco.rs similarity index 100% rename from crates/scalajs/src/jco.rs rename to crates/scalajs-jco/src/jco.rs diff --git a/crates/scalajs/src/lib.rs b/crates/scalajs-jco/src/lib.rs similarity index 100% rename from crates/scalajs/src/lib.rs rename to crates/scalajs-jco/src/lib.rs diff --git a/crates/scalajs/src/resource.rs b/crates/scalajs-jco/src/resource.rs similarity index 100% rename from crates/scalajs/src/resource.rs rename to crates/scalajs-jco/src/resource.rs diff --git a/crates/scalajs/src/rt.rs b/crates/scalajs-jco/src/rt.rs similarity index 100% rename from crates/scalajs/src/rt.rs rename to crates/scalajs-jco/src/rt.rs diff --git a/crates/scalajs/src/skeleton.rs b/crates/scalajs-jco/src/skeleton.rs similarity index 100% rename from crates/scalajs/src/skeleton.rs rename to crates/scalajs-jco/src/skeleton.rs diff --git a/crates/scalajs/src/world.rs b/crates/scalajs-jco/src/world.rs similarity index 100% rename from crates/scalajs/src/world.rs rename to crates/scalajs-jco/src/world.rs diff --git a/crates/scalajs/tests/codegen.rs b/crates/scalajs-jco/tests/codegen.rs similarity index 96% rename from crates/scalajs/tests/codegen.rs rename to crates/scalajs-jco/tests/codegen.rs index 0796a5791..77f8e8f4e 100644 --- a/crates/scalajs/tests/codegen.rs +++ b/crates/scalajs-jco/tests/codegen.rs @@ -1,7 +1,7 @@ use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; -use wit_bindgen_scalajs::ScalaDialect::Scala2; +use wit_bindgen_scalajs_jco::ScalaDialect::Scala2; macro_rules! codegen_test { // TODO: implement support for stream, future, and error-context, and then @@ -19,7 +19,7 @@ macro_rules! codegen_test { "guest-scalajs", $test.as_ref(), |resolve, world, files| { - wit_bindgen_scalajs::Opts { + wit_bindgen_scalajs_jco::Opts { base_package: Some("test".to_string()), skeleton_base_package: Some("skeleton".to_string()), scala_dialect: Scala2,