Skip to content

Commit

Permalink
Getter/Setter for fields
Browse files Browse the repository at this point in the history
  • Loading branch information
c410-f3r committed Apr 11, 2019
1 parent 531aaca commit b79b6c4
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 89 deletions.
27 changes: 13 additions & 14 deletions crates/backend/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,18 @@ pub struct Program {
#[cfg_attr(feature = "extra-traits", derive(Debug))]
#[derive(Clone)]
pub struct Export {
/// The struct name, in Rust, this is attached to
pub rust_class: Option<Ident>,
/// Comments extracted from the rust source.
pub comments: Vec<String>,
/// The rust function
pub function: Function,
/// The class name in JS this is attached to
pub js_class: Option<String>,
/// The kind (static, named, regular)
pub method_kind: MethodKind,
/// The type of `self` (either `self`, `&self`, or `&mut self`)
pub method_self: Option<MethodSelf>,
/// Whether or not this export is flagged as a constructor, returning an
/// instance of the `impl` type
pub is_constructor: bool,
/// The rust function
pub function: Function,
/// Comments extracted from the rust source.
pub comments: Vec<String>,
/// The struct name, in Rust, this is attached to
pub rust_class: Option<Ident>,
/// The name of the rust function/method on the rust side.
pub rust_name: Ident,
/// Whether or not this function should be flagged as the wasm start
Expand Down Expand Up @@ -341,28 +340,28 @@ impl ImportKind {
}
}

impl ImportFunction {
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 {
&self.function.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.function.name.to_string();
let name = self.name.to_string();

// if `#[wasm_bindgen(js_name = "...")]` is used then that explicitly
// because it was hand-written anyway.
if self.function.renamed_via_js_name {
if self.renamed_via_js_name {
return Ok(name);
}

// Otherwise we infer names based on the Rust function name.
if !name.starts_with("set_") {
bail_span!(
syn::token::Pub(self.function.name_span),
syn::token::Pub(self.name_span),
"setters must start with `set_`, found: {}",
name,
);
Expand Down
80 changes: 45 additions & 35 deletions crates/backend/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ fn shared_program<'a>(
.exports
.iter()
.map(|a| shared_export(a, intern))
.collect(),
.collect::<Result<Vec<_>, _>>()?,
structs: prog
.structs
.iter()
Expand Down Expand Up @@ -172,21 +172,23 @@ fn shared_program<'a>(
})
}

fn shared_export<'a>(export: &'a ast::Export, intern: &'a Interner) -> Export<'a> {
let (method, consumed) = match export.method_self {
Some(ast::MethodSelf::ByValue) => (true, true),
Some(_) => (true, false),
None => (false, false),
fn shared_export<'a>(
export: &'a ast::Export,
intern: &'a Interner,
) -> Result<Export<'a>, Diagnostic> {
let consumed = match export.method_self {
Some(ast::MethodSelf::ByValue) => true,
_ => false,
};
Export {
let method_kind = from_ast_method_kind(&export.function, intern, &export.method_kind)?;
Ok(Export {
class: export.js_class.as_ref().map(|s| &**s),
method,
comments: export.comments.iter().map(|s| &**s).collect(),
consumed,
is_constructor: export.is_constructor,
function: shared_function(&export.function, intern),
comments: export.comments.iter().map(|s| &**s).collect(),
method_kind,
start: export.start,
}
})
}

fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Function<'a> {
Expand Down Expand Up @@ -260,30 +262,7 @@ fn shared_import_function<'a>(
) -> Result<ImportFunction<'a>, Diagnostic> {
let method = match &i.kind {
ast::ImportFunctionKind::Method { class, kind, .. } => {
let kind = match kind {
ast::MethodKind::Constructor => MethodKind::Constructor,
ast::MethodKind::Operation(ast::Operation { is_static, kind }) => {
let is_static = *is_static;
let kind = match kind {
ast::OperationKind::Regular => OperationKind::Regular,
ast::OperationKind::Getter(g) => {
let g = g.as_ref().map(|g| intern.intern(g));
OperationKind::Getter(g.unwrap_or_else(|| i.infer_getter_property()))
}
ast::OperationKind::Setter(s) => {
let s = s.as_ref().map(|s| intern.intern(s));
OperationKind::Setter(match s {
Some(s) => s,
None => intern.intern_str(&i.infer_setter_property()?),
})
}
ast::OperationKind::IndexingGetter => OperationKind::IndexingGetter,
ast::OperationKind::IndexingSetter => OperationKind::IndexingSetter,
ast::OperationKind::IndexingDeleter => OperationKind::IndexingDeleter,
};
MethodKind::Operation(Operation { is_static, kind })
}
};
let kind = from_ast_method_kind(&i.function, intern, kind)?;
Some(MethodData { class, kind })
}
ast::ImportFunctionKind::Normal => None,
Expand Down Expand Up @@ -507,3 +486,34 @@ macro_rules! encode_api {
);
}
wasm_bindgen_shared::shared_api!(encode_api);

fn from_ast_method_kind<'a>(
function: &'a ast::Function,
intern: &'a Interner,
method_kind: &'a ast::MethodKind,
) -> Result<MethodKind<'a>, Diagnostic> {
Ok(match method_kind {
ast::MethodKind::Constructor => MethodKind::Constructor,
ast::MethodKind::Operation(ast::Operation { is_static, kind }) => {
let is_static = *is_static;
let kind = match kind {
ast::OperationKind::Getter(g) => {
let g = g.as_ref().map(|g| intern.intern(g));
OperationKind::Getter(g.unwrap_or_else(|| function.infer_getter_property()))
}
ast::OperationKind::Regular => OperationKind::Regular,
ast::OperationKind::Setter(s) => {
let s = s.as_ref().map(|s| intern.intern(s));
OperationKind::Setter(match s {
Some(s) => s,
None => intern.intern_str(&function.infer_setter_property()?),
})
}
ast::OperationKind::IndexingGetter => OperationKind::IndexingGetter,
ast::OperationKind::IndexingSetter => OperationKind::IndexingSetter,
ast::OperationKind::IndexingDeleter => OperationKind::IndexingDeleter,
};
MethodKind::Operation(Operation { is_static, kind })
}
})
}
25 changes: 17 additions & 8 deletions crates/cli-support/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,14 @@ impl<'a> Context<'a> {
))
})?;

self.bind("__wbindgen_function_table", &|me| {
me.function_table_needed = true;
Ok(format!(
"function() {{ return {}; }}",
me.add_heap_object("wasm.__wbg_function_table")
))
})?;

self.bind("__wbindgen_rethrow", &|me| {
Ok(format!(
"function(idx) {{ throw {}; }}",
Expand Down Expand Up @@ -2641,14 +2649,15 @@ impl<'a, 'b> SubContext<'a, 'b> {
Some(d) => d,
};

let function_name = if export.is_constructor {
"constructor"
} else {
&export.function.name
let (function_name, is_constructor, has_self_in_fn) = match &export.method_kind {
decode::MethodKind::Constructor => ("constructor", true, false),
decode::MethodKind::Operation(operation) => {
(export.function.name, false, !operation.is_static)
}
};
let (js, ts, js_doc) = Js2Rust::new(function_name, self.cx)
.method(export.method, export.consumed)
.constructor(if export.is_constructor {
.method(has_self_in_fn, export.consumed)
.constructor(if is_constructor {
Some(class_name)
} else {
None
Expand All @@ -2673,12 +2682,12 @@ impl<'a, 'b> SubContext<'a, 'b> {

class.typescript.push_str(" "); // Indentation

if export.is_constructor {
if is_constructor {
if class.has_constructor {
bail!("found duplicate constructor `{}`", export.function.name);
}
class.has_constructor = true;
} else if !export.method {
} else if !has_self_in_fn {
class.contents.push_str("static ");
class.typescript.push_str("static ");
}
Expand Down
74 changes: 45 additions & 29 deletions crates/macro-support/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,22 +373,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a ast::ImportModule)> for syn::ForeignIte
wasm.ret.clone()
};

let mut operation_kind = ast::OperationKind::Regular;
if let Some(g) = opts.getter() {
operation_kind = ast::OperationKind::Getter(g.clone());
}
if let Some(s) = opts.setter() {
operation_kind = ast::OperationKind::Setter(s.clone());
}
if opts.indexing_getter().is_some() {
operation_kind = ast::OperationKind::IndexingGetter;
}
if opts.indexing_setter().is_some() {
operation_kind = ast::OperationKind::IndexingSetter;
}
if opts.indexing_deleter().is_some() {
operation_kind = ast::OperationKind::IndexingDeleter;
}
let operation_kind = operation_kind(&opts);

let kind = if opts.method().is_some() {
let class = wasm.arguments.get(0).ok_or_else(|| {
Expand Down Expand Up @@ -742,15 +727,21 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
bail_span!(&f.decl.inputs, "the start function cannot have arguments",);
}
}
let method_kind = ast::MethodKind::Operation(ast::Operation {
is_static: true,
kind: operation_kind(&opts),
});
let rust_name = f.ident.clone();
let start = opts.start().is_some();
program.exports.push(ast::Export {
rust_class: None,
js_class: None,
method_self: None,
is_constructor: false,
comments,
rust_name: f.ident.clone(),
start: opts.start().is_some(),
function: f.convert(opts)?,
js_class: None,
method_kind,
method_self: None,
rust_class: None,
rust_name,
start,
});
}
syn::Item::Struct(mut s) => {
Expand Down Expand Up @@ -929,7 +920,6 @@ impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod {

let opts = BindgenAttrs::find(&mut self.attrs)?;
let comments = extract_doc_comments(&self.attrs);
let is_constructor = opts.constructor().is_some();
let (function, method_self) = function_from_decl(
&self.sig.ident,
&opts,
Expand All @@ -939,16 +929,22 @@ impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod {
true,
Some(class),
)?;

let method_kind = if opts.constructor().is_some() {
ast::MethodKind::Constructor
} else {
let is_static = method_self.is_none();
let kind = operation_kind(&opts);
ast::MethodKind::Operation(ast::Operation { is_static, kind })
};
program.exports.push(ast::Export {
rust_class: Some(class.clone()),
comments,
function,
js_class: Some(js_class.to_string()),
method_kind,
method_self,
is_constructor,
function,
comments,
start: false,
rust_class: Some(class.clone()),
rust_name: self.sig.ident.clone(),
start: false,
});
opts.check_used()?;
Ok(())
Expand Down Expand Up @@ -1281,3 +1277,23 @@ pub fn assert_all_attrs_checked() {
assert_eq!(state.parsed.get(), state.checks.get());
})
}

fn operation_kind(opts: &BindgenAttrs) -> ast::OperationKind {
let mut operation_kind = ast::OperationKind::Regular;
if let Some(g) = opts.getter() {
operation_kind = ast::OperationKind::Getter(g.clone());
}
if let Some(s) = opts.setter() {
operation_kind = ast::OperationKind::Setter(s.clone());
}
if opts.indexing_getter().is_some() {
operation_kind = ast::OperationKind::IndexingGetter;
}
if opts.indexing_setter().is_some() {
operation_kind = ast::OperationKind::IndexingSetter;
}
if opts.indexing_deleter().is_some() {
operation_kind = ast::OperationKind::IndexingDeleter;
}
operation_kind
}
5 changes: 2 additions & 3 deletions crates/shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,10 @@ macro_rules! shared_api {

struct Export<'a> {
class: Option<&'a str>,
method: bool,
comments: Vec<&'a str>,
consumed: bool,
is_constructor: bool,
function: Function<'a>,
comments: Vec<&'a str>,
method_kind: MethodKind<'a>,
start: bool,
}

Expand Down
Loading

0 comments on commit b79b6c4

Please sign in to comment.