Fix importing and exporting the same name

Run exports through the same identifier generation as imports to ensure
that everything gets a unique identifier and then just make sure all the
appropriate wires are hooked up when dealing with exports and imports.

Closes #1496
This commit is contained in:
Alex Crichton 2019-05-01 13:53:31 -07:00
parent 358ee18ab2
commit 3d43d6e5e8
9 changed files with 112 additions and 50 deletions

View File

@ -234,7 +234,7 @@ impl ClosureDescriptors {
js, js,
input.add_heap_object("real"), input.add_heap_object("real"),
); );
input.export(&import_name, &body, None); input.export(&import_name, &body, None)?;
let module = "__wbindgen_placeholder__"; let module = "__wbindgen_placeholder__";
let id = input.module.add_import_func(module, &import_name, ty); let id = input.module.add_import_func(module, &import_name, ty);

View File

@ -13,7 +13,7 @@ use crate::{
}; };
use failure::{bail, Error, ResultExt}; use failure::{bail, Error, ResultExt};
use std::{ use std::{
collections::{BTreeMap, HashMap, HashSet, BTreeSet}, collections::{BTreeMap, BTreeSet, HashMap, HashSet},
env, fs, env, fs,
}; };
use walrus::{MemoryId, Module}; use walrus::{MemoryId, Module};
@ -43,9 +43,10 @@ pub struct Context<'a> {
/// called locally. /// called locally.
pub imported_names: HashMap<ImportModule<'a>, HashMap<&'a str, String>>, pub imported_names: HashMap<ImportModule<'a>, HashMap<&'a str, String>>,
/// A set of all imported identifiers to the number of times they've been /// A set of all defined identifiers through either exports or imports to
/// imported, used to generate new identifiers. /// the number of times they've been used, used to generate new
pub imported_identifiers: HashMap<String, usize>, /// identifiers.
pub defined_identifiers: HashMap<String, usize>,
/// A map of all imported shim functions which can actually be directly /// A map of all imported shim functions which can actually be directly
/// imported from the containing module. The mapping here maps to a tuple, /// imported from the containing module. The mapping here maps to a tuple,
@ -157,7 +158,17 @@ impl<'a> Context<'a> {
self.exposed_globals.as_mut().unwrap().insert(name) self.exposed_globals.as_mut().unwrap().insert(name)
} }
fn export(&mut self, name: &str, contents: &str, comments: Option<String>) { fn export(
&mut self,
export_name: &str,
contents: &str,
comments: Option<String>,
) -> Result<(), Error> {
let definition_name = generate_identifier(export_name, &mut self.defined_identifiers);
if contents.starts_with("class") && definition_name != export_name {
bail!("cannot shadow already defined class `{}`", export_name);
}
let contents = contents.trim(); let contents = contents.trim();
if let Some(ref c) = comments { if let Some(ref c) = comments {
self.globals.push_str(c); self.globals.push_str(c);
@ -168,16 +179,16 @@ impl<'a> Context<'a> {
experimental_modules: false, experimental_modules: false,
} => { } => {
if contents.starts_with("class") { if contents.starts_with("class") {
format!("{1}\nmodule.exports.{0} = {0};\n", name, contents) format!("{}\nmodule.exports.{1} = {1};\n", contents, export_name)
} else { } else {
format!("module.exports.{} = {};\n", name, contents) format!("module.exports.{} = {};\n", export_name, contents)
} }
} }
OutputMode::NoModules { .. } => { OutputMode::NoModules { .. } => {
if contents.starts_with("class") { if contents.starts_with("class") {
format!("{1}\n__exports.{0} = {0};\n", name, contents) format!("{}\n__exports.{1} = {1};\n", contents, export_name)
} else { } else {
format!("__exports.{} = {};\n", name, contents) format!("__exports.{} = {};\n", export_name, contents)
} }
} }
OutputMode::Bundler { .. } OutputMode::Bundler { .. }
@ -185,11 +196,21 @@ impl<'a> Context<'a> {
experimental_modules: true, experimental_modules: true,
} => { } => {
if contents.starts_with("function") { if contents.starts_with("function") {
format!("export function {}{}\n", name, &contents[8..]) let body = &contents[8..];
if export_name == definition_name {
format!("export function {}{}\n", export_name, body)
} else {
format!(
"function {}{}\nexport {{ {} as {} }};\n",
definition_name, body, definition_name, export_name,
)
}
} else if contents.starts_with("class") { } else if contents.starts_with("class") {
assert_eq!(export_name, definition_name);
format!("export {}\n", contents) format!("export {}\n", contents)
} else { } else {
format!("export const {} = {};\n", name, contents) assert_eq!(export_name, definition_name);
format!("export const {} = {};\n", export_name, contents)
} }
} }
OutputMode::Web => { OutputMode::Web => {
@ -197,28 +218,43 @@ impl<'a> Context<'a> {
// wasm-bindgen as we're not using the module itself as the // wasm-bindgen as we're not using the module itself as the
// import object but rather the `__exports` map we'll be // import object but rather the `__exports` map we'll be
// initializing below. // initializing below.
let export = if name.starts_with("__wbindgen") let export = if export_name.starts_with("__wbindgen")
|| name.starts_with("__wbg_") || export_name.starts_with("__wbg_")
|| name.starts_with("__widl_") || export_name.starts_with("__widl_")
{ {
"" ""
} else { } else {
"export " "export "
}; };
if contents.starts_with("function") { if contents.starts_with("function") {
format!("{}function {}{}\n", export, name, &contents[8..]) let body = &contents[8..];
if export_name == definition_name {
format!(
"{}function {name}{}\n__exports.{name} = {name}",
export,
body,
name = export_name,
)
} else {
format!(
"{}function {defname}{}\n__exports.{name} = {defname}",
export,
body,
name = export_name,
defname = definition_name,
)
}
} else if contents.starts_with("class") { } else if contents.starts_with("class") {
assert_eq!(export_name, definition_name);
format!("{}{}\n", export, contents) format!("{}{}\n", export, contents)
} else { } else {
format!("{}const {} = {};\n", export, name, contents) assert_eq!(export_name, definition_name);
format!("{}const {} = {};\n", export, export_name, contents)
} }
} }
}; };
self.global(&global); self.global(&global);
Ok(())
if self.config.mode.web() {
self.global(&format!("__exports.{} = {0};", name));
}
} }
fn require_internal_export(&mut self, name: &'static str) -> Result<(), Error> { fn require_internal_export(&mut self, name: &'static str) -> Result<(), Error> {
@ -300,7 +336,7 @@ impl<'a> Context<'a> {
// field of all imports in the wasm module. The field is currently // field of all imports in the wasm module. The field is currently
// always `__wbindgen_placeholder__` coming out of rustc, but we need to // always `__wbindgen_placeholder__` coming out of rustc, but we need to
// update that here to the shim file or an actual ES module. // update that here to the shim file or an actual ES module.
self.rewrite_imports(module_name); self.rewrite_imports(module_name)?;
// We likely made a ton of modifications, so add ourselves to the // We likely made a ton of modifications, so add ourselves to the
// producers section! // producers section!
@ -938,7 +974,11 @@ impl<'a> Context<'a> {
if i != 0 { if i != 0 {
imports_init.push_str(", "); imports_init.push_str(", ");
} }
let import = Import::Module { module, name, field: None }; let import = Import::Module {
module,
name,
field: None,
};
let identifier = self.import_identifier(import); let identifier = self.import_identifier(import);
imports_init.push_str(name); imports_init.push_str(name);
imports_init.push_str(": "); imports_init.push_str(": ");
@ -1017,7 +1057,7 @@ impl<'a> Context<'a> {
} }
let contents = f(self) let contents = f(self)
.with_context(|_| format!("failed to generate internal JS function `{}`", name))?; .with_context(|_| format!("failed to generate internal JS function `{}`", name))?;
self.export(name, &contents, None); self.export(name, &contents, None)?;
Ok(()) Ok(())
} }
@ -1080,7 +1120,7 @@ impl<'a> Context<'a> {
let expr = format!("{}.__wrap(ptr)", name); let expr = format!("{}.__wrap(ptr)", name);
let expr = self.add_heap_object(&expr); let expr = self.add_heap_object(&expr);
let body = format!("function(ptr) {{ return {}; }}", expr); let body = format!("function(ptr) {{ return {}; }}", expr);
self.export(&new_name, &body, None); self.export(&new_name, &body, None)?;
} }
if wrap_needed { if wrap_needed {
@ -1125,7 +1165,7 @@ impl<'a> Context<'a> {
dst.push_str("}\n"); dst.push_str("}\n");
ts_dst.push_str("}\n"); ts_dst.push_str("}\n");
self.export(&name, &dst, Some(class.comments.clone())); self.export(&name, &dst, Some(class.comments.clone()))?;
self.typescript.push_str(&ts_dst); self.typescript.push_str(&ts_dst);
Ok(()) Ok(())
@ -1143,10 +1183,11 @@ impl<'a> Context<'a> {
Ok(()) Ok(())
} }
fn rewrite_imports(&mut self, module_name: &str) { fn rewrite_imports(&mut self, module_name: &str) -> Result<(), Error> {
for (name, contents) in self._rewrite_imports(module_name) { for (name, contents) in self._rewrite_imports(module_name) {
self.export(&name, &contents, None); self.export(&name, &contents, None)?;
} }
Ok(())
} }
fn _rewrite_imports(&mut self, module_name: &str) -> Vec<(String, String)> { fn _rewrite_imports(&mut self, module_name: &str) -> Vec<(String, String)> {
@ -2241,7 +2282,7 @@ impl<'a> Context<'a> {
// generate a new identifier and are sure to generate the appropriate JS // generate a new identifier and are sure to generate the appropriate JS
// import for our new identifier. // import for our new identifier.
let use_node_require = self.use_node_require(); let use_node_require = self.use_node_require();
let imported_identifiers = &mut self.imported_identifiers; let defined_identifiers = &mut self.defined_identifiers;
let imports = &mut self.imports; let imports = &mut self.imports;
let imports_post = &mut self.imports_post; let imports_post = &mut self.imports_post;
let identifier = self let identifier = self
@ -2250,7 +2291,7 @@ impl<'a> Context<'a> {
.or_insert_with(Default::default) .or_insert_with(Default::default)
.entry(import.name()) .entry(import.name())
.or_insert_with(|| { .or_insert_with(|| {
let name = generate_identifier(import.name(), imported_identifiers); let name = generate_identifier(import.name(), defined_identifiers);
match &import { match &import {
Import::Module { .. } Import::Module { .. }
| Import::LocalModule { .. } | Import::LocalModule { .. }
@ -2593,7 +2634,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
self.generate_import(f)?; self.generate_import(f)?;
} }
for e in self.program.enums.iter() { for e in self.program.enums.iter() {
self.generate_enum(e); self.generate_enum(e)?;
} }
for s in self.program.structs.iter() { for s in self.program.structs.iter() {
self.generate_struct(s).with_context(|_| { self.generate_struct(s).with_context(|_| {
@ -2638,7 +2679,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
&export.function.name, &export.function.name,
&js, &js,
Some(format_doc_comments(&export.comments, Some(js_doc))), Some(format_doc_comments(&export.comments, Some(js_doc))),
); )?;
self.cx.globals.push_str("\n"); self.cx.globals.push_str("\n");
self.cx.typescript.push_str("export "); self.cx.typescript.push_str("export ");
self.cx.typescript.push_str(&ts); self.cx.typescript.push_str(&ts);
@ -2777,7 +2818,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
.anyref .anyref
.import_xform("__wbindgen_placeholder__", &import.shim, &[], true); .import_xform("__wbindgen_placeholder__", &import.shim, &[], true);
let body = format!("function() {{ return {}; }}", self.cx.add_heap_object(&obj)); let body = format!("function() {{ return {}; }}", self.cx.add_heap_object(&obj));
self.cx.export(&import.shim, &body, None); self.cx.export(&import.shim, &body, None)?;
Ok(()) Ok(())
} }
@ -2849,7 +2890,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
// shim as the wasm will be importing the shim. // shim as the wasm will be importing the shim.
let target = shim.cx.generated_import_target(name, import)?; let target = shim.cx.generated_import_target(name, import)?;
let js = shim.finish(&target, &import.shim)?; let js = shim.finish(&target, &import.shim)?;
shim.cx.export(&import.shim, &js, None); shim.cx.export(&import.shim, &js, None)?;
Ok(()) Ok(())
} }
@ -2873,11 +2914,11 @@ impl<'a, 'b> SubContext<'a, 'b> {
self.cx.get_object("idx"), self.cx.get_object("idx"),
name name
); );
self.cx.export(&import.instanceof_shim, &body, None); self.cx.export(&import.instanceof_shim, &body, None)?;
Ok(()) Ok(())
} }
fn generate_enum(&mut self, enum_: &decode::Enum) { fn generate_enum(&mut self, enum_: &decode::Enum) -> Result<(), Error> {
let mut variants = String::new(); let mut variants = String::new();
for variant in enum_.variants.iter() { for variant in enum_.variants.iter() {
@ -2887,7 +2928,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
&enum_.name, &enum_.name,
&format!("Object.freeze({{ {} }})", variants), &format!("Object.freeze({{ {} }})", variants),
Some(format_doc_comments(&enum_.comments, None)), Some(format_doc_comments(&enum_.comments, None)),
); )?;
self.cx self.cx
.typescript .typescript
.push_str(&format!("export enum {} {{", enum_.name)); .push_str(&format!("export enum {} {{", enum_.name));
@ -2898,6 +2939,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
.push_str(&format!("\n {},", variant.name)); .push_str(&format!("\n {},", variant.name));
} }
self.cx.typescript.push_str("\n}\n"); self.cx.typescript.push_str("\n}\n");
Ok(())
} }
fn generate_struct(&mut self, struct_: &decode::Struct) -> Result<(), Error> { fn generate_struct(&mut self, struct_: &decode::Struct) -> Result<(), Error> {

View File

@ -328,7 +328,7 @@ impl Bindgen {
exposed_globals: Some(Default::default()), exposed_globals: Some(Default::default()),
required_internal_exports: Default::default(), required_internal_exports: Default::default(),
imported_names: Default::default(), imported_names: Default::default(),
imported_identifiers: Default::default(), defined_identifiers: Default::default(),
exported_classes: Some(Default::default()), exported_classes: Some(Default::default()),
config: &self, config: &self,
module: &mut module, module: &mut module,

2
tests/headless/main.js Normal file
View File

@ -0,0 +1,2 @@
export function import_export_same_name() {
}

View File

@ -38,5 +38,14 @@ fn can_log_html_strings() {
log("<script>alert('lol')</script>"); log("<script>alert('lol')</script>");
} }
#[wasm_bindgen]
pub fn import_export_same_name() {
#[wasm_bindgen(module = "/tests/headless/main.js")]
extern "C" {
fn import_export_same_name();
}
import_export_same_name();
}
pub mod snippets; pub mod snippets;
pub mod modules; pub mod modules;

View File

@ -111,15 +111,15 @@ fn another_vector_return() {
#[wasm_bindgen_test] #[wasm_bindgen_test]
fn serde() { fn serde() {
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct Foo { pub struct SerdeFoo {
a: u32, a: u32,
b: String, b: String,
c: Option<Bar>, c: Option<SerdeBar>,
d: Bar, d: SerdeBar,
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct Bar { pub struct SerdeBar {
a: u32, a: u32,
} }
@ -127,16 +127,16 @@ fn serde() {
assert_eq!(js.as_string(), Some("foo".to_string())); assert_eq!(js.as_string(), Some("foo".to_string()));
let ret = verify_serde( let ret = verify_serde(
JsValue::from_serde(&Foo { JsValue::from_serde(&SerdeFoo {
a: 0, a: 0,
b: "foo".to_string(), b: "foo".to_string(),
c: None, c: None,
d: Bar { a: 1 }, d: SerdeBar { a: 1 },
}) })
.unwrap(), .unwrap(),
); );
let foo = ret.into_serde::<Foo>().unwrap(); let foo = ret.into_serde::<SerdeFoo>().unwrap();
assert_eq!(foo.a, 2); assert_eq!(foo.a, 2);
assert_eq!(foo.b, "bar"); assert_eq!(foo.b, "bar");
assert!(foo.c.is_some()); assert!(foo.c.is_some());

View File

@ -90,3 +90,5 @@ exports.test_rust_optional = function() {
exports.RenamedInRust = class {}; exports.RenamedInRust = class {};
exports.new_renamed = () => new exports.RenamedInRust; exports.new_renamed = () => new exports.RenamedInRust;
exports.import_export_same_name = () => {};

View File

@ -21,6 +21,8 @@ extern "C" {
fn return_string_none() -> Option<String>; fn return_string_none() -> Option<String>;
fn return_string_some() -> Option<String>; fn return_string_some() -> Option<String>;
fn test_rust_optional(); fn test_rust_optional();
#[wasm_bindgen(js_name = import_export_same_name)]
fn js_import_export_same_name();
#[wasm_bindgen(js_name = RenamedInRust)] #[wasm_bindgen(js_name = RenamedInRust)]
type Renamed; type Renamed;
@ -194,3 +196,8 @@ fn renaming_imports_and_instanceof() {
let renamed: JsValue = new_renamed().into(); let renamed: JsValue = new_renamed().into();
assert!(renamed.is_instance_of::<Renamed>()); assert!(renamed.is_instance_of::<Renamed>());
} }
#[wasm_bindgen]
pub fn import_export_same_name() {
js_import_export_same_name();
}

View File

@ -8,18 +8,18 @@ extern "C" {
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
pub type Foo; pub type StructuralFoo;
#[wasm_bindgen(method, structural)] #[wasm_bindgen(method, structural)]
fn bar(this: &Foo); fn bar(this: &StructuralFoo);
#[wasm_bindgen(method, getter, structural)] #[wasm_bindgen(method, getter, structural)]
fn baz(this: &Foo) -> u32; fn baz(this: &StructuralFoo) -> u32;
#[wasm_bindgen(method, setter, structural)] #[wasm_bindgen(method, setter, structural)]
fn set_baz(this: &Foo, val: u32); fn set_baz(this: &StructuralFoo, val: u32);
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn run(a: &Foo) { pub fn run(a: &StructuralFoo) {
a.bar(); a.bar();
assert_eq!(a.baz(), 1); assert_eq!(a.baz(), 1);
a.set_baz(2); a.set_baz(2);