Skip to content

Commit 0e89a29

Browse files
Replace all Self identifiers in method argument and return types (rustwasm#4155)
Co-authored-by: Oliver T <geronimooliver00@gmail.com>
1 parent 02679e1 commit 0e89a29

File tree

5 files changed

+64
-19
lines changed

5 files changed

+64
-19
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
* Added support for implicit discriminants in enums.
99
[#4152](https://github.com/rustwasm/wasm-bindgen/pull/4152)
1010

11+
* Added support for `Self` in complex type expressions in methods.
12+
[#4155](https://github.com/rustwasm/wasm-bindgen/pull/4155)
13+
1114
--------------------------------------------------------------------------------
1215

1316
## [0.2.94](https://github.com/rustwasm/wasm-bindgen/compare/0.2.93...0.2.94)

crates/macro-support/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ strict-macro = []
2121
[dependencies]
2222
proc-macro2 = "1.0"
2323
quote = '1.0'
24-
syn = { version = '2.0', features = ['visit', 'full'] }
24+
syn = { version = '2.0', features = ['visit', 'visit-mut', 'full'] }
2525
wasm-bindgen-backend = { path = "../backend", version = "=0.2.94" }
2626
wasm-bindgen-shared = { path = "../shared", version = "=0.2.94" }

crates/macro-support/src/parser.rs

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use quote::ToTokens;
1212
use syn::ext::IdentExt;
1313
use syn::parse::{Parse, ParseStream, Result as SynResult};
1414
use syn::spanned::Spanned;
15+
use syn::visit_mut::VisitMut;
1516
use syn::{ItemFn, Lit, MacroDelimiter, ReturnType};
1617

1718
use crate::ClassMarker;
@@ -912,26 +913,33 @@ fn function_from_decl(
912913

913914
let syn::Signature { inputs, output, .. } = sig;
914915

915-
let replace_self = |t: syn::Type| {
916-
let self_ty = match self_ty {
917-
Some(i) => i,
918-
None => return t,
919-
};
920-
let path = match get_ty(&t) {
921-
syn::Type::Path(syn::TypePath { qself: None, path }) => path.clone(),
922-
other => return other.clone(),
923-
};
924-
let new_path = if path.segments.len() == 1 && path.segments[0].ident == "Self" {
925-
self_ty.clone().into()
926-
} else {
927-
path
928-
};
929-
syn::Type::Path(syn::TypePath {
930-
qself: None,
931-
path: new_path,
932-
})
916+
// A helper function to replace `Self` in the function signature of methods.
917+
// E.g. `fn get(&self) -> Option<Self>` to `fn get(&self) -> Option<MyType>`
918+
// The following comment explains why this is necessary:
919+
// https://github.com/rustwasm/wasm-bindgen/issues/3105#issuecomment-1275160744
920+
let replace_self = |mut t: syn::Type| {
921+
if let Some(self_ty) = self_ty {
922+
// This uses a visitor to replace all occurrences of `Self` with
923+
// the actual type identifier. The visitor guarantees that we find
924+
// all occurrences of `Self`, even if deeply nested and even if
925+
// future Rust versions add more places where `Self` can appear.
926+
struct SelfReplace(Ident);
927+
impl VisitMut for SelfReplace {
928+
fn visit_ident_mut(&mut self, i: &mut proc_macro2::Ident) {
929+
if i == "Self" {
930+
*i = self.0.clone();
931+
}
932+
}
933+
}
934+
935+
let mut replace = SelfReplace(self_ty.clone());
936+
replace.visit_type_mut(&mut t);
937+
}
938+
t
933939
};
934940

941+
// A helper function to replace argument names that are JS keywords.
942+
// E.g. this will replace `fn foo(class: u32)` to `fn foo(_class: u32)`
935943
let replace_colliding_arg = |i: &mut syn::PatType| {
936944
if let syn::Pat::Ident(ref mut i) = *i.pat {
937945
let ident = i.ident.to_string();
@@ -946,14 +954,18 @@ fn function_from_decl(
946954
.into_iter()
947955
.filter_map(|arg| match arg {
948956
syn::FnArg::Typed(mut c) => {
957+
// typical arguments like foo: u32
949958
replace_colliding_arg(&mut c);
950959
c.ty = Box::new(replace_self(*c.ty));
951960
Some(c)
952961
}
953962
syn::FnArg::Receiver(r) => {
963+
// the self argument, so self, &self, &mut self, self: Box<Self>, etc.
954964
if !allow_self {
955965
panic!("arguments cannot be `self`")
956966
}
967+
968+
// write down the way in which `self` is passed for later
957969
assert!(method_self.is_none());
958970
if r.reference.is_none() {
959971
method_self = Some(ast::MethodSelf::ByValue);
@@ -962,6 +974,7 @@ fn function_from_decl(
962974
} else {
963975
method_self = Some(ast::MethodSelf::RefShared);
964976
}
977+
965978
None
966979
}
967980
})

tests/wasm/inner_self.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//! This tests that the `wasm_bindgen` macro produces code that compiles for these use cases.
2+
//! `cargo test --target wasm32-unknown-unknown` will not run if any of these tests breaks.
3+
use wasm_bindgen::prelude::*;
4+
5+
#[wasm_bindgen]
6+
pub struct A;
7+
8+
#[wasm_bindgen]
9+
pub struct SelfPortrait;
10+
11+
#[wasm_bindgen]
12+
impl A {
13+
pub fn test_only_self() -> Self {
14+
A
15+
}
16+
pub fn test_option_self() -> Option<Self> {
17+
None
18+
}
19+
pub fn test_nested_self() -> Result<Option<Self>, String> {
20+
Ok(None)
21+
}
22+
pub fn test_self_slice() -> Box<[Self]> {
23+
Box::new([])
24+
}
25+
pub fn test_selfish() -> Result<Self, SelfPortrait> {
26+
Ok(A)
27+
}
28+
}

tests/wasm/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub mod getters_and_setters;
3636
pub mod ignore;
3737
pub mod import_class;
3838
pub mod imports;
39+
pub mod inner_self;
3940
pub mod intrinsics;
4041
pub mod js_keywords;
4142
pub mod js_objects;

0 commit comments

Comments
 (0)