Skip to content

Commit

Permalink
Add support for symbols in js_name
Browse files Browse the repository at this point in the history
  • Loading branch information
RunDevelopment committed Oct 28, 2024
1 parent 76776ef commit 87597e5
Show file tree
Hide file tree
Showing 12 changed files with 804 additions and 211 deletions.
83 changes: 61 additions & 22 deletions crates/backend/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,32 @@ impl Program {
}
}

/// The name of a JS method, field, or property.
///
/// JavaScript, broadly, has 2 ways to access properties on objects:
///
/// 1. String properties. E.g. `obj.foo` or `obj['foo']`.
/// 2. Symbol properties. E.g. `obj[Symbol.iterator]`.
///
/// String properties are the most common, and are represented by the
/// `Identifier` variant. This makes code gen easier, because we allowed to
/// use the `.` operator in JS to access the property.
///
/// Symbol properties are less common but no less important. Many JS protocols
/// (like iterators) are defined by well-known symbols. Furthermore, this also
/// supports custom symbols created by `Symbol.for(key)`.
///
/// Note that symbols are only allowed for properties, fields, and methods.
/// Free functions, enums, types, and classes cannot be named with symbols.
#[cfg_attr(feature = "extra-traits", derive(Debug))]
#[derive(Clone)]
pub enum Name {
/// A valid JS identifier.
Identifier(String),
/// The name of a well-known symbol. E.g. `iterator` for `Symbol.iterator`.
Symbol(String),
}

/// An abstract syntax tree representing a link to a module in Rust.
/// In contrast to Program, LinkToModule must expand to an expression.
/// linked_modules of the inner Program must contain exactly one element
Expand Down Expand Up @@ -248,9 +274,9 @@ pub enum OperationKind {
/// A standard method, nothing special
Regular,
/// A method for getting the value of the provided Ident or String
Getter(Option<String>),
Getter(Option<Name>),
/// A method for setting the value of the provided Ident or String
Setter(Option<String>),
Setter(Option<Name>),
/// A dynamically intercepted getter
IndexingGetter,
/// A dynamically intercepted setter
Expand Down Expand Up @@ -358,11 +384,9 @@ pub struct StringEnum {
#[derive(Clone)]
pub struct Function {
/// The name of the function
pub name: String,
pub name: Name,
/// The span of the function's name in Rust code
pub name_span: Span,
/// Whether the function has a js_name attribute
pub renamed_via_js_name: bool,
/// The arguments to the function
pub arguments: Vec<syn::PatType>,
/// The return type of the function, if provided
Expand Down Expand Up @@ -410,7 +434,7 @@ pub struct StructField {
/// The name of the field in Rust code
pub rust_name: syn::Member,
/// The name of the field in JS code
pub js_name: String,
pub js_name: Name,
/// The name of the struct this field is part of
pub struct_name: Ident,
/// Whether this value is read-only to JS
Expand Down Expand Up @@ -513,18 +537,18 @@ impl Export {
generated_name.push_str(class);
}
generated_name.push('_');
generated_name.push_str(&self.function.name.to_string());
generated_name.push_str(&self.function.name.as_ref().disambiguated_name());
Ident::new(&generated_name, Span::call_site())
}

/// This is the name of the shim function that gets exported and takes the raw
/// ABI form of its arguments and converts them back into their normal,
/// "high level" form before calling the actual function.
pub(crate) fn export_name(&self) -> String {
let fn_name = self.function.name.to_string();
let fn_name = self.function.name.as_ref();
match &self.js_class {
Some(class) => shared::struct_function_export_name(class, &fn_name),
None => shared::free_function_export_name(&fn_name),
Some(class) => shared::struct_function_export_name(class, fn_name),
None => shared::free_function_export_name(fn_name),
}
}
}
Expand All @@ -542,26 +566,41 @@ impl ImportKind {
}
}

impl Name {
/// Turn this into a name ref to take advantage of shared logic.
pub fn as_ref(&self) -> shared::NameRef<'_> {
match self {
Name::Identifier(s) => shared::NameRef::Identifier(s),
Name::Symbol(s) => shared::NameRef::Symbol(s),
}
}
}

impl Function {
/// If the rust object has a `fn xxx(&self) -> MyType` method, get the name for a getter in
/// javascript (in this case `xxx`, so you can write `val = obj.xxx`)
pub fn infer_getter_property(&self) -> &str {
pub fn infer_getter_property(&self) -> &Name {
&self.name
}

/// If the rust object has a `fn set_xxx(&mut self, MyType)` style method, get the name
/// for a setter in javascript (in this case `xxx`, so you can write `obj.xxx = val`)
pub fn infer_setter_property(&self) -> Result<String, Diagnostic> {
let name = self.name.to_string();

// Otherwise we infer names based on the Rust function name.
if !name.starts_with("set_") {
bail_span!(
syn::token::Pub(self.name_span),
"setters must start with `set_`, found: {}",
name,
);
pub fn infer_setter_property(&self) -> Result<Name, Diagnostic> {
match &self.name {
Name::Identifier(ref name) => {
let name = name.to_string();

// Otherwise we infer names based on the Rust function name.
if !name.starts_with("set_") {
bail_span!(
syn::token::Pub(self.name_span),
"setters must start with `set_`, found: {}",
name,
);
}
Ok(Name::Identifier(name[4..].to_string()))
}
Name::Symbol(_) => Ok(self.name.clone()),
}
Ok(name[4..].to_string())
}
}
30 changes: 20 additions & 10 deletions crates/backend/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,13 @@ fn shared_export<'a>(
})
}

fn shared_name<'a>(func: &ast::Name, intern: &'a Interner) -> Name<'a> {
match func {
ast::Name::Identifier(x) => Name::Identifier(intern.intern_str(x)),
ast::Name::Symbol(x) => Name::Symbol(intern.intern_str(x)),
}
}

fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Function<'a> {
let arg_names = func
.arguments
Expand All @@ -229,7 +236,7 @@ fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Functi
Function {
arg_names,
asyncness: func.r#async,
name: &func.name,
name: shared_name(&func.name, _intern),
generate_typescript: func.generate_typescript,
generate_jsdoc: func.generate_jsdoc,
variadic: func.variadic,
Expand Down Expand Up @@ -382,9 +389,9 @@ fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> {
}
}

fn shared_struct_field<'a>(s: &'a ast::StructField, _intern: &'a Interner) -> StructField<'a> {
fn shared_struct_field<'a>(s: &'a ast::StructField, intern: &'a Interner) -> StructField<'a> {
StructField {
name: &s.js_name,
name: shared_name(&s.js_name, intern),
readonly: s.readonly,
comments: s.comments.iter().map(|s| &**s).collect(),
generate_typescript: s.generate_typescript,
Expand Down Expand Up @@ -594,16 +601,19 @@ fn from_ast_method_kind<'a>(
let is_static = *is_static;
let kind = match kind {
ast::OperationKind::Getter(g) => {
let g = g.as_ref().map(|g| intern.intern_str(g));
OperationKind::Getter(g.unwrap_or_else(|| function.infer_getter_property()))
let g = g
.as_ref()
.unwrap_or_else(|| function.infer_getter_property());
OperationKind::Getter(shared_name(g, intern))
}
ast::OperationKind::Regular => OperationKind::Regular,
ast::OperationKind::Setter(s) => {
let s = s.as_ref().map(|s| intern.intern_str(s));
OperationKind::Setter(match s {
Some(s) => s,
None => intern.intern_str(&function.infer_setter_property()?),
})
let s = if let Some(s) = s {
shared_name(s, intern)
} else {
shared_name(&function.infer_setter_property()?, intern)
};
OperationKind::Setter(s)
}
ast::OperationKind::IndexingGetter => OperationKind::IndexingGetter,
ast::OperationKind::IndexingSetter => OperationKind::IndexingSetter,
Expand Down
16 changes: 16 additions & 0 deletions crates/cli-support/src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,19 @@ macro_rules! decode_api {
}

wasm_bindgen_shared::shared_api!(decode_api);

impl Name<'_> {
pub fn as_ref(&self) -> wasm_bindgen_shared::NameRef<'_> {
match self {
Name::Identifier(s) => wasm_bindgen_shared::NameRef::Identifier(s),
Name::Symbol(s) => wasm_bindgen_shared::NameRef::Symbol(s),
}
}

pub fn to_aux(&self) -> crate::wit::AuxName {
match self {
Name::Identifier(s) => crate::wit::AuxName::Identifier(s.to_string()),
Name::Symbol(s) => crate::wit::AuxName::Symbol(s.to_string()),
}
}
}
Loading

0 comments on commit 87597e5

Please sign in to comment.