Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for symbols in js_name and getter/setter #4230

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading