Skip to content

Commit 7191db1

Browse files
committed
Implement #[wasm_bindgen(extends = ...)]
This commit implements the `extends` attribute for `#[wasm_bindgen]` to statically draw the inheritance hierarchy in the generated bindings, generating appropriate `AsRef`, `AsMut`, and `From` implementations.
1 parent 127bb18 commit 7191db1

File tree

8 files changed

+133
-3
lines changed

8 files changed

+133
-3
lines changed

crates/backend/src/ast.rs

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ pub struct ImportType {
126126
pub attrs: Vec<syn::Attribute>,
127127
pub doc_comment: Option<String>,
128128
pub instanceof_shim: String,
129+
pub extends: Vec<Ident>,
129130
}
130131

131132
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]

crates/backend/src/codegen.rs

+25
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,31 @@ impl ToTokens for ast::ImportType {
656656
()
657657
};
658658
}).to_tokens(tokens);
659+
660+
for superclass in self.extends.iter() {
661+
(quote! {
662+
impl From<#name> for #superclass {
663+
fn from(obj: #name) -> #superclass {
664+
use wasm_bindgen::JsCast;
665+
#superclass::unchecked_from_js(obj.into())
666+
}
667+
}
668+
669+
impl AsRef<#superclass> for #name {
670+
fn as_ref(&self) -> &#superclass {
671+
use wasm_bindgen::JsCast;
672+
#superclass::unchecked_from_js_ref(self.as_ref())
673+
}
674+
}
675+
676+
impl AsMut<#superclass> for #name {
677+
fn as_mut(&mut self) -> &mut #superclass {
678+
use wasm_bindgen::JsCast;
679+
#superclass::unchecked_from_js_mut(self.as_mut())
680+
}
681+
}
682+
}).to_tokens(tokens);
683+
}
659684
}
660685
}
661686

crates/macro-support/src/parser.rs

+22-3
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,16 @@ impl BindgenAttrs {
169169
})
170170
.next()
171171
}
172+
173+
/// Return the list of classes that a type extends
174+
fn extends(&self) -> impl Iterator<Item = &Ident> {
175+
self.attrs
176+
.iter()
177+
.filter_map(|a| match a {
178+
BindgenAttr::Extends(s) => Some(s),
179+
_ => None,
180+
})
181+
}
172182
}
173183

174184
impl syn::synom::Synom for BindgenAttrs {
@@ -202,6 +212,7 @@ pub enum BindgenAttr {
202212
Readonly,
203213
JsName(Ident),
204214
JsClass(String),
215+
Extends(Ident),
205216
}
206217

207218
impl syn::synom::Synom for BindgenAttr {
@@ -277,6 +288,13 @@ impl syn::synom::Synom for BindgenAttr {
277288
s: syn!(syn::LitStr) >>
278289
(s.value())
279290
)=> { BindgenAttr::JsClass }
291+
|
292+
do_parse!(
293+
call!(term, "extends") >>
294+
punct!(=) >>
295+
ns: call!(term2ident) >>
296+
(ns)
297+
)=> { BindgenAttr::Extends }
280298
));
281299
}
282300

@@ -490,17 +508,18 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
490508
}
491509
}
492510

493-
impl ConvertToAst<()> for syn::ForeignItemType {
511+
impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
494512
type Target = ast::ImportKind;
495513

496-
fn convert(self, (): ()) -> Result<Self::Target, Diagnostic> {
514+
fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
497515
let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident));
498516
Ok(ast::ImportKind::Type(ast::ImportType {
499517
vis: self.vis,
500518
attrs: self.attrs,
501519
doc_comment: None,
502520
instanceof_shim: shim,
503521
name: self.ident,
522+
extends: attrs.extends().cloned().collect(),
504523
}))
505524
}
506525
}
@@ -907,7 +926,7 @@ impl<'a> MacroParse<&'a BindgenAttrs> for syn::ForeignItem {
907926
let js_namespace = item_opts.js_namespace().or(opts.js_namespace()).cloned();
908927
let kind = match self {
909928
syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?,
910-
syn::ForeignItem::Type(t) => t.convert(())?,
929+
syn::ForeignItem::Type(t) => t.convert(item_opts)?,
911930
syn::ForeignItem::Static(s) => s.convert(item_opts)?,
912931
_ => panic!("only foreign functions/types allowed for now"),
913932
};

crates/webidl/src/first_pass.rs

+29
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::{
1414
use webidl;
1515

1616
use super::Result;
17+
use util::camel_case_ident;
1718

1819
/// Collection of constructs that may use partial.
1920
#[derive(Default)]
@@ -33,6 +34,7 @@ pub(crate) struct InterfaceData {
3334
pub(crate) partial: bool,
3435
pub(crate) operations: BTreeMap<OperationId, OperationData>,
3536
pub(crate) global: bool,
37+
pub(crate) superclass: Option<String>,
3638
}
3739

3840
#[derive(PartialEq, Eq, PartialOrd, Ord)]
@@ -178,6 +180,7 @@ impl FirstPass<()> for webidl::ast::NonPartialInterface {
178180
.and_modify(|interface_data| {
179181
if interface_data.partial {
180182
interface_data.partial = false;
183+
interface_data.superclass = self.inherits.clone();
181184
} else {
182185
warn!("Encountered multiple declarations of {}", self.name);
183186
}
@@ -187,6 +190,7 @@ impl FirstPass<()> for webidl::ast::NonPartialInterface {
187190
partial: false,
188191
operations: Default::default(),
189192
global: false,
193+
superclass: self.inherits.clone(),
190194
},
191195
);
192196

@@ -216,6 +220,7 @@ impl FirstPass<()> for webidl::ast::PartialInterface {
216220
partial: true,
217221
operations: Default::default(),
218222
global: false,
223+
superclass: None,
219224
},
220225
);
221226

@@ -386,3 +391,27 @@ impl FirstPass<()> for webidl::ast::Typedef {
386391
Ok(())
387392
}
388393
}
394+
395+
impl<'a> FirstPassRecord<'a> {
396+
pub fn all_superclasses<'me>(&'me self, interface: &str)
397+
-> impl Iterator<Item = String> + 'me
398+
{
399+
let mut set = BTreeSet::new();
400+
self.fill_superclasses(interface, &mut set);
401+
set.into_iter()
402+
}
403+
404+
fn fill_superclasses(&self, interface: &str, set: &mut BTreeSet<String>) {
405+
let data = match self.interfaces.get(interface) {
406+
Some(data) => data,
407+
None => return,
408+
};
409+
let superclass = match &data.superclass {
410+
Some(class) => class,
411+
None => return,
412+
};
413+
if set.insert(camel_case_ident(superclass)) {
414+
self.fill_superclasses(superclass, set);
415+
}
416+
}
417+
}

crates/webidl/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports};
3838
use backend::util::{ident_ty, rust_ident, wrap_import_function};
3939
use failure::{ResultExt, Fail};
4040
use heck::{ShoutySnakeCase};
41+
use proc_macro2::{Ident, Span};
4142

4243
use first_pass::{FirstPass, FirstPassRecord};
4344
use util::{ApplyTypedefs, public, webidl_const_ty_to_syn_ty, webidl_const_v_to_backend_const_v, camel_case_ident, mdn_doc};
@@ -252,6 +253,9 @@ impl WebidlParse<()> for webidl::ast::NonPartialInterface {
252253
attrs: Vec::new(),
253254
doc_comment,
254255
instanceof_shim: format!("__widl_instanceof_{}", self.name),
256+
extends: first_pass.all_superclasses(&self.name)
257+
.map(|name| Ident::new(&name, Span::call_site()))
258+
.collect(),
255259
}),
256260
});
257261

guide/src/design/import-customization.md

+22
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,25 @@ possibilities!
199199

200200
All of these functions will call `console.log` in JS, but each identifier
201201
will have only one signature in Rust.
202+
203+
* `extends = Bar` - this can be used to say that an imported type extends
204+
another type. This will generate `AsRef`, `AsMut`, and `From` impls for
205+
converting a type into another given that we statically know the inheritance
206+
hierarchy:
207+
208+
```rust
209+
#[wasm_bindgen]
210+
extern {
211+
type Foo;
212+
213+
#[wasm_bindgen(extends = Foo)]
214+
type Bar;
215+
}
216+
217+
let x: &Bar = ...;
218+
let y: &Foo = x.as_ref(); // zero cost cast
219+
```
220+
221+
The `extends = ...` attribute can be specified multiple times for longer
222+
inheritance chains, and `AsRef` and such impls will be generated for each of
223+
the types.

tests/wasm/jscast.js

+10
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,25 @@ class JsCast1 {
44
}
55
myval() { return this.val; }
66
}
7+
78
class JsCast2 {
89
}
10+
911
class JsCast3 extends JsCast1 {
1012
constructor() {
1113
super();
1214
this.val = 3;
1315
}
1416
}
1517

18+
class JsCast4 extends JsCast3 {
19+
constructor() {
20+
super();
21+
this.val = 4;
22+
}
23+
}
24+
1625
exports.JsCast1 = JsCast1;
1726
exports.JsCast2 = JsCast2;
1827
exports.JsCast3 = JsCast3;
28+
exports.JsCast4 = JsCast4;

tests/wasm/jscast.rs

+20
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,15 @@ extern {
1414
#[wasm_bindgen(constructor)]
1515
fn new() -> JsCast2;
1616

17+
#[wasm_bindgen(extends = JsCast1)]
1718
type JsCast3;
1819
#[wasm_bindgen(constructor)]
1920
fn new() -> JsCast3;
21+
22+
#[wasm_bindgen(extends = JsCast1, extends = JsCast3)]
23+
type JsCast4;
24+
#[wasm_bindgen(constructor)]
25+
fn new() -> JsCast4;
2026
}
2127

2228
#[wasm_bindgen_test]
@@ -65,4 +71,18 @@ fn method_calling() {
6571
assert_eq!(a.myval(), 1);
6672
assert_eq!(b.dyn_ref::<JsCast1>().unwrap().myval(), 3);
6773
assert_eq!(b.unchecked_ref::<JsCast1>().myval(), 3);
74+
let c: &JsCast1 = b.as_ref();
75+
assert_eq!(c.myval(), 3);
76+
}
77+
78+
#[wasm_bindgen_test]
79+
fn multiple_layers_of_inheritance() {
80+
let a = JsCast4::new();
81+
assert!(a.is_instance_of::<JsCast4>());
82+
assert!(a.is_instance_of::<JsCast3>());
83+
assert!(a.is_instance_of::<JsCast1>());
84+
85+
let _: &JsCast3 = a.as_ref();
86+
let b: &JsCast1 = a.as_ref();
87+
assert_eq!(b.myval(), 4);
6888
}

0 commit comments

Comments
 (0)