use crate::decode;
use crate::descriptor::{Descriptor, VectorKind};
use crate::Bindgen;
use failure::{bail, Error, ResultExt};
use std::collections::{HashMap, HashSet};
use walrus::{MemoryId, Module};
use wasm_bindgen_wasm_interpreter::Interpreter;

mod js2rust;
use self::js2rust::{ExportedShim, Js2Rust};
mod rust2js;
use self::rust2js::Rust2Js;
mod closures;

pub struct Context<'a> {
    pub globals: String,
    pub imports: String,
    pub imports_post: String,
    pub footer: String,
    pub typescript: String,
    pub exposed_globals: Option<HashSet<&'static str>>,
    pub required_internal_exports: HashSet<&'static str>,
    pub imported_functions: HashSet<&'a str>,
    pub imported_statics: HashSet<&'a str>,
    pub config: &'a Bindgen,
    pub module: &'a mut Module,
    pub start: Option<String>,

    /// A map which maintains a list of what identifiers we've imported and what
    /// they're named locally.
    ///
    /// The `Option<String>` key is the module that identifiers were imported
    /// from, `None` being the global module. The second key is a map of
    /// identifiers we've already imported from the module to what they're
    /// called locally.
    pub imported_names: HashMap<Option<&'a str>, HashMap<&'a str, String>>,

    /// A set of all imported identifiers to the number of times they've been
    /// imported, used to generate new identifiers.
    pub imported_identifiers: HashMap<String, usize>,

    /// A map of all imported shim functions which can actually be directly
    /// imported from the containing module. The mapping here maps to a tuple,
    /// where the first element is the module to import from and the second
    /// element is the name to import from the module.
    ///
    /// Note that for `direct_imports` no shims are generated in JS that
    /// wasm-bindgen emits.
    pub direct_imports: HashMap<&'a str, (&'a str, &'a str)>,

    pub exported_classes: Option<HashMap<String, ExportedClass>>,
    pub function_table_needed: bool,
    pub interpreter: &'a mut Interpreter,
    pub memory: MemoryId,

    pub anyref: wasm_bindgen_anyref_xform::Context,
}

#[derive(Default)]
pub struct ExportedClass {
    comments: String,
    contents: String,
    typescript: String,
    has_constructor: bool,
    wrap_needed: bool,
}

pub struct SubContext<'a, 'b: 'a> {
    pub program: &'b decode::Program<'b>,
    pub cx: &'a mut Context<'b>,
    pub vendor_prefixes: HashMap<&'b str, Vec<&'b str>>,
}

pub enum ImportTarget {
    Function(String),
    Method(String),
    Constructor(String),
    StructuralMethod(String),
    StructuralGetter(Option<String>, String),
    StructuralSetter(Option<String>, String),
    StructuralIndexingGetter(Option<String>),
    StructuralIndexingSetter(Option<String>),
    StructuralIndexingDeleter(Option<String>),
}

/// Return value of `determine_import` which is where we look at an imported
/// function AST and figure out where it's actually being imported from
/// (performing some validation checks and whatnot).
enum Import<'a> {
    /// An item is imported from the global scope. The `name` is what's imported
    /// and the optional `field` is the field on that item we're importing.
    Global {
        name: &'a str,
        field: Option<&'a str>,
    },
    /// Same as `Global`, except the `name` is imported via an ESM import from
    /// the specified `module` path.
    Module {
        module: &'a str,
        name: &'a str,
        field: Option<&'a str>,
    },
    /// A global import which may have a number of vendor prefixes associated
    /// with it, like `webkitAudioPrefix`. The `name` is the name to test
    /// whether it's prefixed.
    VendorPrefixed {
        name: &'a str,
        prefixes: Vec<&'a str>,
    },
}

const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
// Must be kept in sync with `src/lib.rs` of the `wasm-bindgen` crate
const INITIAL_HEAP_OFFSET: usize = 32;

impl<'a> Context<'a> {
    fn should_write_global(&mut self, name: &'static str) -> bool {
        self.exposed_globals.as_mut().unwrap().insert(name)
    }

    fn export(&mut self, name: &str, contents: &str, comments: Option<String>) {
        let contents = contents.trim();
        if let Some(ref c) = comments {
            self.globals.push_str(c);
        }
        let global = if self.use_node_require() {
            if contents.starts_with("class") {
                format!("{1}\nmodule.exports.{0} = {0};\n", name, contents)
            } else {
                format!("module.exports.{} = {};\n", name, contents)
            }
        } else if self.config.no_modules {
            if contents.starts_with("class") {
                format!("{1}\n__exports.{0} = {0};\n", name, contents)
            } else {
                format!("__exports.{} = {};\n", name, contents)
            }
        } else {
            if contents.starts_with("function") {
                format!("export function {}{}\n", name, &contents[8..])
            } else if contents.starts_with("class") {
                format!("export {}\n", contents)
            } else {
                format!("export const {} = {};\n", name, contents)
            }
        };
        self.global(&global);
    }

    fn require_internal_export(&mut self, name: &'static str) -> Result<(), Error> {
        if !self.required_internal_exports.insert(name) {
            return Ok(());
        }

        if self.module.exports.iter().any(|e| e.name == name) {
            return Ok(());
        }

        bail!(
            "the exported function `{}` is required to generate bindings \
             but it was not found in the wasm file, perhaps the `std` feature \
             of the `wasm-bindgen` crate needs to be enabled?",
            name
        );
    }

    pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> {
        self.bind("__wbindgen_string_new", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_string_new",
                &[],
                true,
            );
            me.expose_get_string_from_wasm();
            Ok(format!(
                "function(p, l) {{ return {}; }}",
                me.add_heap_object("getStringFromWasm(p, l)")
            ))
        })?;

        self.bind("__wbindgen_number_new", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_number_new",
                &[],
                true,
            );
            Ok(format!(
                "function(i) {{ return {}; }}",
                me.add_heap_object("i")
            ))
        })?;

        self.bind("__wbindgen_number_get", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_number_get",
                &[(0, false)],
                false,
            );
            me.expose_uint8_memory();
            Ok(format!(
                "
                function(n, invalid) {{
                    let obj = {};
                    if (typeof(obj) === 'number') return obj;
                    getUint8Memory()[invalid] = 1;
                    return 0;
                }}
                ",
                me.get_object("n"),
            ))
        })?;

        self.bind("__wbindgen_is_null", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_is_null",
                &[(0, false)],
                false,
            );
            Ok(format!(
                "function(i) {{ return {} === null ? 1 : 0; }}",
                me.get_object("i")
            ))
        })?;

        self.bind("__wbindgen_is_undefined", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_is_undefined",
                &[(0, false)],
                false,
            );
            Ok(format!(
                "function(i) {{ return {} === undefined ? 1 : 0; }}",
                me.get_object("i")
            ))
        })?;

        self.bind("__wbindgen_boolean_get", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_boolean_get",
                &[(0, false)],
                false,
            );
            Ok(format!(
                "
                function(i) {{
                    let v = {};
                    return typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
                }}
                ",
                me.get_object("i"),
            ))
        })?;

        self.bind("__wbindgen_symbol_new", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_symbol_new",
                &[],
                true,
            );
            me.expose_get_string_from_wasm();
            let expr = "ptr === 0 ? Symbol() : Symbol(getStringFromWasm(ptr, len))";
            Ok(format!(
                "function(ptr, len) {{ return {}; }}",
                me.add_heap_object(expr)
            ))
        })?;

        self.bind("__wbindgen_is_symbol", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_is_symbol",
                &[(0, false)],
                false,
            );
            Ok(format!(
                "function(i) {{ return typeof({}) === 'symbol' ? 1 : 0; }}",
                me.get_object("i")
            ))
        })?;

        self.bind("__wbindgen_is_object", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_is_object",
                &[(0, false)],
                false,
            );
            Ok(format!(
                "
                function(i) {{
                    const val = {};
                    return typeof(val) === 'object' && val !== null ? 1 : 0;
                }}",
                me.get_object("i"),
            ))
        })?;

        self.bind("__wbindgen_is_function", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_is_function",
                &[(0, false)],
                false,
            );
            Ok(format!(
                "function(i) {{ return typeof({}) === 'function' ? 1 : 0; }}",
                me.get_object("i")
            ))
        })?;

        self.bind("__wbindgen_is_string", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_is_string",
                &[(0, false)],
                false,
            );
            Ok(format!(
                "function(i) {{ return typeof({}) === 'string' ? 1 : 0; }}",
                me.get_object("i")
            ))
        })?;

        self.bind("__wbindgen_string_get", &|me| {
            me.expose_pass_string_to_wasm()?;
            me.expose_uint32_memory();
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_string_get",
                &[(0, false)],
                false,
            );
            Ok(format!(
                "
                function(i, len_ptr) {{
                    let obj = {};
                    if (typeof(obj) !== 'string') return 0;
                    const ptr = passStringToWasm(obj);
                    getUint32Memory()[len_ptr / 4] = WASM_VECTOR_LEN;
                    return ptr;
                }}
                ",
                me.get_object("i"),
            ))
        })?;

        self.bind("__wbindgen_debug_string", &|me| {
            me.expose_pass_string_to_wasm()?;
            me.expose_uint32_memory();

            let debug_str = "
                val => {
                    // primitive types
                    const type = typeof val;
                    if (type == 'number' || type == 'boolean' || val == null) {
                        return  `${val}`;
                    }
                    if (type == 'string') {
                        return `\"${val}\"`;
                    }
                    if (type == 'symbol') {
                        const description = val.description;
                        if (description == null) {
                            return 'Symbol';
                        } else {
                            return `Symbol(${description})`;
                        }
                    }
                    if (type == 'function') {
                        const name = val.name;
                        if (typeof name == 'string' && name.length > 0) {
                            return `Function(${name})`;
                        } else {
                            return 'Function';
                        }
                    }
                    // objects
                    if (Array.isArray(val)) {
                        const length = val.length;
                        let debug = '[';
                        if (length > 0) {
                            debug += debug_str(val[0]);
                        }
                        for(let i = 1; i < length; i++) {
                            debug += ', ' + debug_str(val[i]);
                        }
                        debug += ']';
                        return debug;
                    }
                    // Test for built-in
                    const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));
                    let className;
                    if (builtInMatches.length > 1) {
                        className = builtInMatches[1];
                    } else {
                        // Failed to match the standard '[object ClassName]'
                        return toString.call(val);
                    }
                    if (className == 'Object') {
                        // we're a user defined class or Object
                        // JSON.stringify avoids problems with cycles, and is generally much
                        // easier than looping through ownProperties of `val`.
                        try {
                            return 'Object(' + JSON.stringify(val) + ')';
                        } catch (_) {
                            return 'Object';
                        }
                    }
                    // errors
                    if (val instanceof Error) {
                        return `${val.name}: ${val.message}\n${val.stack}`;
                    }
                    // TODO we could test for more things here, like `Set`s and `Map`s.
                    return className;
                }
            ";
            Ok(format!(
                "
                function(i, len_ptr) {{
                    const debug_str = {};
                    const toString = Object.prototype.toString;
                    const val = {};
                    const debug = debug_str(val);
                    const ptr = passStringToWasm(debug);
                    getUint32Memory()[len_ptr / 4] = WASM_VECTOR_LEN;
                    return ptr;
                }}
                ",
                debug_str,
                me.get_object("i"),
            ))
        })?;

        self.bind("__wbindgen_cb_drop", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_cb_drop",
                &[(0, true)],
                false,
            );
            Ok(format!(
                "
                function(i) {{
                    const obj = {}.original;
                    if (obj.cnt-- == 1) {{
                        obj.a = 0;
                        return 1;
                    }}
                    return 0;
                }}
                ",
                me.take_object("i"),
            ))
        })?;

        self.bind("__wbindgen_cb_forget", &|me| {
            Ok(if me.config.anyref {
                // TODO: we should rewrite this in the anyref xform to not even
                // call into JS
                me.anyref.import_xform(
                    "__wbindgen_placeholder__",
                    "__wbindgen_cb_drop",
                    &[(0, true)],
                    false,
                );
                String::from("function(obj) {}")
            } else {
                me.expose_drop_ref();
                "dropObject".to_string()
            })
        })?;

        self.bind("__wbindgen_json_parse", &|me| {
            me.expose_get_string_from_wasm();
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_json_parse",
                &[],
                true,
            );
            let expr = "JSON.parse(getStringFromWasm(ptr, len))";
            let expr = me.add_heap_object(expr);
            Ok(format!("function(ptr, len) {{ return {}; }}", expr))
        })?;

        self.bind("__wbindgen_json_serialize", &|me| {
            me.anyref.import_xform(
                "__wbindgen_placeholder__",
                "__wbindgen_json_serialize",
                &[(0, false)],
                false,
            );
            me.expose_pass_string_to_wasm()?;
            me.expose_uint32_memory();
            Ok(format!(
                "
                function(idx, ptrptr) {{
                    const ptr = passStringToWasm(JSON.stringify({}));
                    getUint32Memory()[ptrptr / 4] = ptr;
                    return WASM_VECTOR_LEN;
                }}
                ",
                me.get_object("idx"),
            ))
        })?;

        self.bind("__wbindgen_jsval_eq", &|me| {
            Ok(format!(
                "function(a, b) {{ return {} === {} ? 1 : 0; }}",
                me.get_object("a"),
                me.get_object("b")
            ))
        })?;

        self.bind("__wbindgen_memory", &|me| {
            let mem = me.memory();
            Ok(format!(
                "function() {{ return {}; }}",
                me.add_heap_object(mem)
            ))
        })?;

        self.bind("__wbindgen_module", &|me| {
            if !me.config.no_modules {
                bail!(
                    "`wasm_bindgen::module` is currently only supported with \
                     --no-modules"
                );
            }
            Ok(format!(
                "function() {{ return {}; }}",
                me.add_heap_object("init.__wbindgen_wasm_module")
            ))
        })?;

        self.bind("__wbindgen_rethrow", &|me| {
            Ok(format!(
                "function(idx) {{ throw {}; }}",
                me.take_object("idx")
            ))
        })?;

        closures::rewrite(self).with_context(|_| "failed to generate internal closure shims")?;
        self.write_classes()?;
        self.anyref.run(self.module)?;

        // After the anyref pass has executed, if this intrinsic is needed then
        // we expose a function which initializes it
        self.bind("__wbindgen_init_anyref_table", &|me| {
            me.expose_anyref_table();
            Ok(String::from(
                "function() {
                const table = wasm.__wbg_anyref_table;
                const offset = table.grow(4);
                table.set(offset + 0, undefined);
                table.set(offset + 1, null);
                table.set(offset + 2, true);
                table.set(offset + 3, false);
            }",
            ))
        })?;

        // make sure that the anyref pass runs before binding this as anyref may
        // remove calls to this import and then gc would remove it
        self.bind("__wbindgen_object_clone_ref", &|me| {
            me.expose_get_object();
            me.expose_add_heap_object();
            Ok(String::from(
                "
                function(idx) {
                    return addHeapObject(getObject(idx));
                }
                ",
            ))
        })?;

        // like above, make sure anyref runs first and the anyref pass may
        // remove usages of this.
        self.bind("__wbindgen_object_drop_ref", &|me| {
            me.expose_drop_ref();
            Ok(String::from("function(i) { dropObject(i); }"))
        })?;

        self.unexport_unused_internal_exports();

        // Handle the `start` function, if one was specified. If we're in a
        // --test mode (such as wasm-bindgen-test-runner) then we skip this
        // entirely. Otherwise we want to first add a start function to the
        // `start` section if one is specified.
        //
        // Afterwards, we need to perform what's a bit of a hack. Right after we
        // added the start function, we remove it again because no current
        // strategy for bundlers and deployment works well enough with it. For
        // `--no-modules` output we need to be sure to call the start function
        // after our exports are wired up (or most imported functions won't
        // work).
        //
        // For ESM outputs bundlers like webpack also don't work because
        // currently they run the wasm initialization before the JS glue
        // initialization, meaning that if the wasm start function calls
        // imported functions the JS glue isn't ready to go just yet.
        //
        // To handle `--no-modules` we just unstart the start function and call
        // it manually. To handle the ESM use case we switch the start function
        // to calling an imported function which defers the start function via
        // `Promise.resolve().then(...)` to execute on the next microtask tick.
        let mut has_start_function = false;
        if self.config.emit_start {
            self.add_start_function()?;
            has_start_function = self.unstart_start_function();
        }

        self.export_table()?;

        walrus::passes::gc::run(self.module);

        // Note that it's important `throw` comes last *after* we gc. The
        // `__wbindgen_malloc` function may call this but we only want to
        // generate code for this if it's actually live (and __wbindgen_malloc
        // isn't gc'd).
        self.bind("__wbindgen_throw", &|me| {
            me.expose_get_string_from_wasm();
            Ok(String::from(
                "
                function(ptr, len) {
                    throw new Error(getStringFromWasm(ptr, len));
                }
                ",
            ))
        })?;

        self.rewrite_imports(module_name);
        self.update_producers_section();

        // Cause any future calls to `should_write_global` to panic, making sure
        // we don't ask for items which we can no longer emit.
        drop(self.exposed_globals.take().unwrap());

        let mut js = if self.config.threads.is_some() {
            // TODO: It's not clear right now how to best use threads with
            // bundlers like webpack. We need a way to get the existing
            // module/memory into web workers for now and we don't quite know
            // idiomatically how to do that! In the meantime, always require
            // `--no-modules`
            if !self.config.no_modules {
                bail!("most use `--no-modules` with threads for now")
            }
            let mem = self.module.memories.get(self.memory);
            if mem.import.is_none() {
                bail!("must impot a shared memory with threads")
            }

            let mut memory = String::from("new WebAssembly.Memory({");
            memory.push_str(&format!("initial:{}", mem.initial));
            if let Some(max) = mem.maximum {
                memory.push_str(&format!(",maximum:{}", max));
            }
            if mem.shared {
                memory.push_str(",shared:true");
            }
            memory.push_str("})");

            format!(
                "\
(function() {{
    var wasm;
    var memory;
    const __exports = {{}};
    {globals}
    function init(module_or_path, maybe_memory) {{
        let result;
        const imports = {{ './{module}': __exports }};
        if (module_or_path instanceof WebAssembly.Module) {{
            memory = __exports.memory = maybe_memory;
            result = WebAssembly.instantiate(module_or_path, imports)
                .then(instance => {{
                    return {{ instance, module: module_or_path }}
                }});
        }} else {{
            memory = __exports.memory = {init_memory};
            const response = fetch(module_or_path);
            if (typeof WebAssembly.instantiateStreaming === 'function') {{
                result = WebAssembly.instantiateStreaming(response, imports)
                    .catch(e => {{
                        console.warn(\"`WebAssembly.instantiateStreaming` failed. Assuming this is \
                                        because your server does not serve wasm with \
                                        `application/wasm` MIME type. Falling back to \
                                        `WebAssembly.instantiate` which is slower. Original \
                                        error:\\n\", e);
                        return response
                            .then(r => r.arrayBuffer())
                            .then(bytes => WebAssembly.instantiate(bytes, imports));
                    }});
            }} else {{
                result = response
                    .then(r => r.arrayBuffer())
                    .then(bytes => WebAssembly.instantiate(bytes, imports));
            }}
        }}
        return result.then(({{instance, module}}) => {{
            wasm = init.wasm = instance.exports;
            init.__wbindgen_wasm_instance = instance;
            init.__wbindgen_wasm_module = module;
            init.__wbindgen_wasm_memory = __exports.memory;
            {start}
        }});
    }};
    self.{global_name} = Object.assign(init, __exports);
}})();",
                globals = self.globals,
                module = module_name,
                global_name = self
                    .config
                    .no_modules_global
                    .as_ref()
                    .map(|s| &**s)
                    .unwrap_or("wasm_bindgen"),
                init_memory = memory,
                start = if has_start_function {
                    "wasm.__wbindgen_start();"
                } else {
                    ""
                },
            )
        } else if self.config.no_modules {
            format!(
                "\
(function() {{
    var wasm;
    const __exports = {{}};
    {globals}
    function init(path_or_module) {{
        let instantiation;
        const imports = {{ './{module}': __exports }};
        if (path_or_module instanceof WebAssembly.Module) {{
            instantiation = WebAssembly.instantiate(path_or_module, imports)
                .then(instance => {{
                    return {{ instance, module: path_or_module }}
                }});
        }} else {{
            const data = fetch(path_or_module);
            if (typeof WebAssembly.instantiateStreaming === 'function') {{
                instantiation = WebAssembly.instantiateStreaming(data, imports)
                    .catch(e => {{
                        console.warn(\"`WebAssembly.instantiateStreaming` failed. Assuming this is \
                                       because your server does not serve wasm with \
                                       `application/wasm` MIME type. Falling back to \
                                       `WebAssembly.instantiate` which is slower. Original \
                                       error:\\n\", e);
                        return data
                            .then(r => r.arrayBuffer())
                            .then(bytes => WebAssembly.instantiate(bytes, imports));
                    }});
            }} else {{
                instantiation = data
                    .then(response => response.arrayBuffer())
                    .then(buffer => WebAssembly.instantiate(buffer, imports));
            }}
        }}
        return instantiation.then(({{instance}}) => {{
            wasm = init.wasm = instance.exports;
            {start}
        }});
    }};
    self.{global_name} = Object.assign(init, __exports);
}})();",
                globals = self.globals,
                module = module_name,
                global_name = self
                    .config
                    .no_modules_global
                    .as_ref()
                    .map(|s| &**s)
                    .unwrap_or("wasm_bindgen"),
                start = if has_start_function {
                    "wasm.__wbindgen_start();"
                } else {
                    ""
                },
            )
        } else {
            // In the "we're pretending to be an ES module use case if we've got
            // a start function then we use an injected shim to actually execute
            // the real start function on the next tick of the microtask queue
            // (explained above)
            if has_start_function {
                self.inject_start_shim();
            }

            let import_wasm = if self.globals.len() == 0 {
                String::new()
            } else if self.use_node_require() {
                self.footer
                    .push_str(&format!("wasm = require('./{}_bg');", module_name));
                format!("var wasm;")
            } else {
                format!("import * as wasm from './{}_bg';", module_name)
            };

            format!(
                "\
                /* tslint:disable */\n\
                {import_wasm}\n\
                {imports}\n\
                {imports_post}\n\

                {globals}\n\
                {footer}",
                import_wasm = import_wasm,
                globals = self.globals,
                imports = self.imports,
                imports_post = self.imports_post,
                footer = self.footer,
            )
        };

        while js.contains("\n\n\n") {
            js = js.replace("\n\n\n", "\n\n");
        }

        Ok((js, self.typescript.clone()))
    }

    fn bind(
        &mut self,
        name: &str,
        f: &Fn(&mut Self) -> Result<String, Error>,
    ) -> Result<(), Error> {
        if !self.wasm_import_needed(name) {
            return Ok(());
        }
        let contents = f(self)
            .with_context(|_| format!("failed to generate internal JS function `{}`", name))?;
        self.export(name, &contents, None);
        Ok(())
    }

    fn write_classes(&mut self) -> Result<(), Error> {
        for (class, exports) in self.exported_classes.take().unwrap() {
            self.write_class(&class, &exports)?;
        }
        Ok(())
    }

    fn write_class(&mut self, name: &str, class: &ExportedClass) -> Result<(), Error> {
        let mut dst = format!("class {} {{\n", name);
        let mut ts_dst = format!("export {}", dst);

        let (mkweakref, freeref) = if self.config.weak_refs {
            // When weak refs are enabled we use them to automatically free the
            // contents of an exported rust class when it's gc'd. Note that a
            // manual `free` function still exists for deterministic
            // destruction.
            //
            // This is implemented by using a `WeakRefGroup` to run finalizers
            // for all `WeakRef` objects that it creates. Upon construction of
            // a new wasm object we use `makeRef` with "holdings" of a thunk to
            // free the wasm instance.  Once the `this` (the instance we're
            // creating) is gc'd then the finalizer will run with the
            // `WeakRef`, and we'll pull out the `holdings`, our pointer.
            //
            // Note, though, that if manual finalization happens we want to
            // cancel the `WeakRef`-generated finalization, so we retain the
            // `WeakRef` in a global map. This global map is then used to
            // `drop()` the `WeakRef` (cancel finalization) whenever it is
            // finalized.
            self.expose_cleanup_groups();
            let mk = format!("addCleanup(this, this.ptr, free{});", name);
            let free = "
                CLEANUPS_MAP.get(ptr).drop();
                CLEANUPS_MAP.delete(ptr);
            ";
            (mk, free)
        } else {
            (String::new(), "")
        };

        if self.config.debug && !class.has_constructor {
            dst.push_str(
                "
                    constructor() {
                        throw new Error('cannot invoke `new` directly');
                    }
                ",
            );
        }

        let mut wrap_needed = class.wrap_needed;
        let new_name = wasm_bindgen_shared::new_function(&name);
        if self.wasm_import_needed(&new_name) {
            wrap_needed = true;
            self.anyref
                .import_xform("__wbindgen_placeholder__", &new_name, &[], true);
            let expr = format!("{}.__wrap(ptr)", name);
            let expr = self.add_heap_object(&expr);
            let body = format!("function(ptr) {{ return {}; }}", expr);
            self.export(&new_name, &body, None);
        }

        if wrap_needed {
            dst.push_str(&format!(
                "
                static __wrap(ptr) {{
                    const obj = Object.create({}.prototype);
                    obj.ptr = ptr;
                    {}
                    return obj;
                }}
                ",
                name,
                mkweakref.replace("this", "obj"),
            ));
        }

        self.global(&format!(
            "
            function free{}(ptr) {{
                {}
                wasm.{}(ptr);
            }}
            ",
            name,
            freeref,
            wasm_bindgen_shared::free_function(&name)
        ));
        dst.push_str(&format!(
            "
            free() {{
                const ptr = this.ptr;
                this.ptr = 0;
                free{}(ptr);
            }}
            ",
            name,
        ));
        ts_dst.push_str("  free(): void;");
        dst.push_str(&class.contents);
        ts_dst.push_str(&class.typescript);
        dst.push_str("}\n");
        ts_dst.push_str("}\n");

        self.export(&name, &dst, Some(class.comments.clone()));
        self.typescript.push_str(&ts_dst);

        Ok(())
    }

    fn export_table(&mut self) -> Result<(), Error> {
        if !self.function_table_needed {
            return Ok(());
        }
        let id = match self.module.tables.main_function_table()? {
            Some(id) => id,
            None => bail!("no function table found in module"),
        };
        self.module.exports.add("__wbg_function_table", id);
        Ok(())
    }

    fn rewrite_imports(&mut self, module_name: &str) {
        for (name, contents) in self._rewrite_imports(module_name) {
            self.export(&name, &contents, None);
        }
    }

    fn _rewrite_imports(&mut self, module_name: &str) -> Vec<(String, String)> {
        let mut math_imports = Vec::new();
        for import in self.module.imports.iter_mut() {
            if import.module == "__wbindgen_placeholder__" {
                import.module.truncate(0);
                if let Some((module, name)) = self.direct_imports.get(import.name.as_str()) {
                    import.name.truncate(0);
                    import.module.push_str(module);
                    import.name.push_str(name);
                } else {
                    import.module.push_str("./");
                    import.module.push_str(module_name);
                }
                continue;
            }

            if import.module != "env" {
                continue;
            }

            // If memory is imported we'll have exported it from the shim module
            // so let's import it from there.
            //
            // TODO: we should track this is in a more first-class fashion
            // rather than just matching on strings.
            if import.name == "memory" {
                import.module.truncate(0);
                import.module.push_str("./");
                import.module.push_str(module_name);
                continue;
            }

            let renamed_import = format!("__wbindgen_{}", import.name);
            let mut bind_math = |expr: &str| {
                math_imports.push((renamed_import.clone(), format!("function{}", expr)));
            };

            // Note that since Rust 1.32.0 this is no longer necessary. Imports
            // of these functions were fixed in rust-lang/rust#54257 and we're
            // just waiting until pre-1.32.0 compilers are basically no longer
            // in use to remove this.
            match import.name.as_str() {
                "Math_acos" => bind_math("(x) { return Math.acos(x); }"),
                "Math_asin" => bind_math("(x) { return Math.asin(x); }"),
                "Math_atan" => bind_math("(x) { return Math.atan(x); }"),
                "Math_atan2" => bind_math("(x, y) { return Math.atan2(x, y); }"),
                "Math_cbrt" => bind_math("(x) { return Math.cbrt(x); }"),
                "Math_cosh" => bind_math("(x) { return Math.cosh(x); }"),
                "Math_expm1" => bind_math("(x) { return Math.expm1(x); }"),
                "Math_hypot" => bind_math("(x, y) { return Math.hypot(x, y); }"),
                "Math_log1p" => bind_math("(x) { return Math.log1p(x); }"),
                "Math_sinh" => bind_math("(x) { return Math.sinh(x); }"),
                "Math_tan" => bind_math("(x) { return Math.tan(x); }"),
                "Math_tanh" => bind_math("(x) { return Math.tanh(x); }"),
                _ => continue,
            }

            import.module.truncate(0);
            import.module.push_str("./");
            import.module.push_str(module_name);
            import.name = renamed_import.clone();
        }

        math_imports
    }

    fn unexport_unused_internal_exports(&mut self) {
        let mut to_remove = Vec::new();
        for export in self.module.exports.iter() {
            match export.name.as_str() {
                // These are some internal imports set by LLD but currently
                // we've got no use case for continuing to export them, so
                // blacklist them.
                "__heap_base" | "__data_end" | "__indirect_function_table" => {
                    to_remove.push(export.id());
                }

                // Otherwise only consider our special exports, which all start
                // with the same prefix which hopefully only we're using.
                n if n.starts_with("__wbindgen") => {
                    if !self.required_internal_exports.contains(n) {
                        to_remove.push(export.id());
                    }
                }
                _ => {}
            }
        }
        for id in to_remove {
            self.module.exports.delete(id);
        }
    }

    fn expose_drop_ref(&mut self) {
        if !self.should_write_global("drop_ref") {
            return;
        }
        self.expose_global_heap();
        self.expose_global_heap_next();

        // Note that here we check if `idx` shouldn't actually be dropped. This
        // is due to the fact that `JsValue::null()` and friends can be passed
        // by value to JS where we'll automatically call this method. Those
        // constants, however, cannot be dropped. See #1054 for removing this
        // branch.
        //
        // Otherwise the free operation here is pretty simple, just appending to
        // the linked list of heap slots that are free.
        self.global(&format!(
            "
            function dropObject(idx) {{
                if (idx < {}) return;
                heap[idx] = heap_next;
                heap_next = idx;
            }}
            ",
            INITIAL_HEAP_OFFSET + INITIAL_HEAP_VALUES.len(),
        ));
    }

    fn expose_global_heap(&mut self) {
        if !self.should_write_global("heap") {
            return;
        }
        assert!(!self.config.anyref);
        self.global(&format!("const heap = new Array({});", INITIAL_HEAP_OFFSET));
        self.global("heap.fill(undefined);");
        self.global(&format!("heap.push({});", INITIAL_HEAP_VALUES.join(", ")));
    }

    fn expose_global_heap_next(&mut self) {
        if !self.should_write_global("heap_next") {
            return;
        }
        self.expose_global_heap();
        self.global("let heap_next = heap.length;");
    }

    fn expose_get_object(&mut self) {
        if !self.should_write_global("get_object") {
            return;
        }
        self.expose_global_heap();

        // Accessing a heap object is just a simple index operation due to how
        // the stack/heap are laid out.
        self.global("function getObject(idx) { return heap[idx]; }");
    }

    fn expose_assert_num(&mut self) {
        if !self.should_write_global("assert_num") {
            return;
        }
        self.global(&format!(
            "
            function _assertNum(n) {{
                if (typeof(n) !== 'number') throw new Error('expected a number argument');
            }}
            "
        ));
    }

    fn expose_assert_bool(&mut self) {
        if !self.should_write_global("assert_bool") {
            return;
        }
        self.global(&format!(
            "
            function _assertBoolean(n) {{
                if (typeof(n) !== 'boolean') {{
                    throw new Error('expected a boolean argument');
                }}
            }}
            "
        ));
    }

    fn expose_wasm_vector_len(&mut self) {
        if !self.should_write_global("wasm_vector_len") {
            return;
        }
        self.global("let WASM_VECTOR_LEN = 0;");
    }

    fn expose_pass_string_to_wasm(&mut self) -> Result<(), Error> {
        if !self.should_write_global("pass_string_to_wasm") {
            return Ok(());
        }
        self.require_internal_export("__wbindgen_malloc")?;
        self.expose_text_encoder();
        self.expose_uint8_memory();
        self.expose_wasm_vector_len();
        let debug = if self.config.debug {
            "
                if (typeof(arg) !== 'string') throw new Error('expected a string argument');
            "
        } else {
            ""
        };
        self.global(&format!(
            "
            function passStringToWasm(arg) {{
                {}
                const buf = cachedTextEncoder.encode(arg);
                const ptr = wasm.__wbindgen_malloc(buf.length);
                getUint8Memory().set(buf, ptr);
                WASM_VECTOR_LEN = buf.length;
                return ptr;
            }}
            ",
            debug
        ));
        Ok(())
    }

    fn expose_pass_array8_to_wasm(&mut self) -> Result<(), Error> {
        self.expose_uint8_memory();
        self.pass_array_to_wasm("passArray8ToWasm", "getUint8Memory", 1)
    }

    fn expose_pass_array16_to_wasm(&mut self) -> Result<(), Error> {
        self.expose_uint16_memory();
        self.pass_array_to_wasm("passArray16ToWasm", "getUint16Memory", 2)
    }

    fn expose_pass_array32_to_wasm(&mut self) -> Result<(), Error> {
        self.expose_uint32_memory();
        self.pass_array_to_wasm("passArray32ToWasm", "getUint32Memory", 4)
    }

    fn expose_pass_array64_to_wasm(&mut self) -> Result<(), Error> {
        self.expose_uint64_memory();
        self.pass_array_to_wasm("passArray64ToWasm", "getUint64Memory", 8)
    }

    fn expose_pass_array_f32_to_wasm(&mut self) -> Result<(), Error> {
        self.expose_f32_memory();
        self.pass_array_to_wasm("passArrayF32ToWasm", "getFloat32Memory", 4)
    }

    fn expose_pass_array_f64_to_wasm(&mut self) -> Result<(), Error> {
        self.expose_f64_memory();
        self.pass_array_to_wasm("passArrayF64ToWasm", "getFloat64Memory", 8)
    }

    fn expose_pass_array_jsvalue_to_wasm(&mut self) -> Result<(), Error> {
        if !self.should_write_global("pass_array_jsvalue") {
            return Ok(());
        }
        self.require_internal_export("__wbindgen_malloc")?;
        self.expose_uint32_memory();
        if self.config.anyref {
            // TODO: using `addToAnyrefTable` goes back and forth between wasm
            // and JS a lot, we should have a bulk operation for this.
            self.expose_add_to_anyref_table()?;
            self.global(
                "
                function passArrayJsValueToWasm(array) {
                    const ptr = wasm.__wbindgen_malloc(array.length * 4);
                    const mem = getUint32Memory();
                    for (let i = 0; i < array.length; i++) {
                        mem[ptr / 4 + i] = addToAnyrefTable(array[i]);
                    }
                    WASM_VECTOR_LEN = array.length;
                    return ptr;
                }
            ",
            );
        } else {
            self.expose_add_heap_object();
            self.global(
                "
                function passArrayJsValueToWasm(array) {
                    const ptr = wasm.__wbindgen_malloc(array.length * 4);
                    const mem = getUint32Memory();
                    for (let i = 0; i < array.length; i++) {
                        mem[ptr / 4 + i] = addHeapObject(array[i]);
                    }
                    WASM_VECTOR_LEN = array.length;
                    return ptr;
                }

            ",
            );
        }
        Ok(())
    }

    fn pass_array_to_wasm(
        &mut self,
        name: &'static str,
        delegate: &str,
        size: usize,
    ) -> Result<(), Error> {
        if !self.should_write_global(name) {
            return Ok(());
        }
        self.require_internal_export("__wbindgen_malloc")?;
        self.expose_wasm_vector_len();
        self.global(&format!(
            "
            function {}(arg) {{
                const ptr = wasm.__wbindgen_malloc(arg.length * {size});
                {}().set(arg, ptr / {size});
                WASM_VECTOR_LEN = arg.length;
                return ptr;
            }}
            ",
            name,
            delegate,
            size = size
        ));
        Ok(())
    }

    fn expose_text_encoder(&mut self) {
        if !self.should_write_global("text_encoder") {
            return;
        }
        self.expose_text_processor("TextEncoder");
    }

    fn expose_text_decoder(&mut self) {
        if !self.should_write_global("text_decoder") {
            return;
        }
        self.expose_text_processor("TextDecoder");
    }

    fn expose_text_processor(&mut self, s: &str) {
        if self.config.nodejs_experimental_modules {
            self.imports
                .push_str(&format!("import {{ {} }} from 'util';\n", s));
            self.global(&format!("let cached{0} = new {0}('utf-8');", s));
        } else if self.config.nodejs {
            self.global(&format!("const {0} = require('util').{0};", s));
            self.global(&format!("let cached{0} = new {0}('utf-8');", s));
        } else if !(self.config.browser || self.config.no_modules) {
            self.global(&format!(
                "
                    const l{0} = typeof {0} === 'undefined' ? \
                        require('util').{0} : {0};\
                ",
                s
            ));
            self.global(&format!("let cached{0} = new l{0}('utf-8');", s));
        } else {
            self.global(&format!("let cached{0} = new {0}('utf-8');", s));
        }
    }

    fn expose_get_string_from_wasm(&mut self) {
        if !self.should_write_global("get_string_from_wasm") {
            return;
        }
        self.expose_text_decoder();
        self.expose_uint8_memory();

        // Typically we try to give a raw view of memory out to `TextDecoder` to
        // avoid copying too much data. If, however, a `SharedArrayBuffer` is
        // being used it looks like that is rejected by `TextDecoder` or
        // otherwise doesn't work with it. When we detect a shared situation we
        // use `slice` which creates a new array instead of `subarray` which
        // creates just a view. That way in shared mode we copy more data but in
        // non-shared mode there's no need to copy the data except for the
        // string itself.
        let is_shared = self.module.memories.get(self.memory).shared;
        let method = if is_shared { "slice" } else { "subarray" };

        self.global(&format!(
            "
            function getStringFromWasm(ptr, len) {{
                return cachedTextDecoder.decode(getUint8Memory().{}(ptr, ptr + len));
            }}
        ",
            method
        ));
    }

    fn expose_get_array_js_value_from_wasm(&mut self) -> Result<(), Error> {
        if !self.should_write_global("get_array_js_value_from_wasm") {
            return Ok(());
        }
        self.expose_uint32_memory();
        if self.config.anyref {
            self.expose_anyref_table();
            self.global(
                "
                function getArrayJsValueFromWasm(ptr, len) {
                    const mem = getUint32Memory();
                    const slice = mem.subarray(ptr / 4, ptr / 4 + len);
                    const result = [];
                    for (let i = 0; i < slice.length; i++) {
                        result.push(wasm.__wbg_anyref_table.get(slice[i]));
                    }
                    wasm.__wbindgen_drop_anyref_slice(ptr, len);
                    return result;
                }
                ",
            );
            self.require_internal_export("__wbindgen_drop_anyref_slice")?;
        } else {
            self.expose_take_object();
            self.global(
                "
                function getArrayJsValueFromWasm(ptr, len) {
                    const mem = getUint32Memory();
                    const slice = mem.subarray(ptr / 4, ptr / 4 + len);
                    const result = [];
                    for (let i = 0; i < slice.length; i++) {
                        result.push(takeObject(slice[i]));
                    }
                    return result;
                }
                ",
            );
        }
        Ok(())
    }

    fn expose_get_array_i8_from_wasm(&mut self) {
        self.expose_int8_memory();
        self.arrayget("getArrayI8FromWasm", "getInt8Memory", 1);
    }

    fn expose_get_array_u8_from_wasm(&mut self) {
        self.expose_uint8_memory();
        self.arrayget("getArrayU8FromWasm", "getUint8Memory", 1);
    }

    fn expose_get_clamped_array_u8_from_wasm(&mut self) {
        self.expose_clamped_uint8_memory();
        self.arrayget("getClampedArrayU8FromWasm", "getUint8ClampedMemory", 1);
    }

    fn expose_get_array_i16_from_wasm(&mut self) {
        self.expose_int16_memory();
        self.arrayget("getArrayI16FromWasm", "getInt16Memory", 2);
    }

    fn expose_get_array_u16_from_wasm(&mut self) {
        self.expose_uint16_memory();
        self.arrayget("getArrayU16FromWasm", "getUint16Memory", 2);
    }

    fn expose_get_array_i32_from_wasm(&mut self) {
        self.expose_int32_memory();
        self.arrayget("getArrayI32FromWasm", "getInt32Memory", 4);
    }

    fn expose_get_array_u32_from_wasm(&mut self) {
        self.expose_uint32_memory();
        self.arrayget("getArrayU32FromWasm", "getUint32Memory", 4);
    }

    fn expose_get_array_i64_from_wasm(&mut self) {
        self.expose_int64_memory();
        self.arrayget("getArrayI64FromWasm", "getInt64Memory", 8);
    }

    fn expose_get_array_u64_from_wasm(&mut self) {
        self.expose_uint64_memory();
        self.arrayget("getArrayU64FromWasm", "getUint64Memory", 8);
    }

    fn expose_get_array_f32_from_wasm(&mut self) {
        self.expose_f32_memory();
        self.arrayget("getArrayF32FromWasm", "getFloat32Memory", 4);
    }

    fn expose_get_array_f64_from_wasm(&mut self) {
        self.expose_f64_memory();
        self.arrayget("getArrayF64FromWasm", "getFloat64Memory", 8);
    }

    fn arrayget(&mut self, name: &'static str, mem: &'static str, size: usize) {
        if !self.should_write_global(name) {
            return;
        }
        self.global(&format!(
            "
            function {name}(ptr, len) {{
                return {mem}().subarray(ptr / {size}, ptr / {size} + len);
            }}
            ",
            name = name,
            mem = mem,
            size = size,
        ));
    }

    fn expose_int8_memory(&mut self) {
        self.memview("getInt8Memory", "Int8Array");
    }

    fn expose_uint8_memory(&mut self) {
        self.memview("getUint8Memory", "Uint8Array");
    }

    fn expose_clamped_uint8_memory(&mut self) {
        self.memview("getUint8ClampedMemory", "Uint8ClampedArray");
    }

    fn expose_int16_memory(&mut self) {
        self.memview("getInt16Memory", "Int16Array");
    }

    fn expose_uint16_memory(&mut self) {
        self.memview("getUint16Memory", "Uint16Array");
    }

    fn expose_int32_memory(&mut self) {
        self.memview("getInt32Memory", "Int32Array");
    }

    fn expose_uint32_memory(&mut self) {
        self.memview("getUint32Memory", "Uint32Array");
    }

    fn expose_int64_memory(&mut self) {
        self.memview("getInt64Memory", "BigInt64Array");
    }

    fn expose_uint64_memory(&mut self) {
        self.memview("getUint64Memory", "BigUint64Array");
    }

    fn expose_f32_memory(&mut self) {
        self.memview("getFloat32Memory", "Float32Array");
    }

    fn expose_f64_memory(&mut self) {
        self.memview("getFloat64Memory", "Float64Array");
    }

    fn memview_function(&mut self, t: VectorKind) -> &'static str {
        match t {
            VectorKind::String => {
                self.expose_uint8_memory();
                "getUint8Memory"
            }
            VectorKind::I8 => {
                self.expose_int8_memory();
                "getInt8Memory"
            }
            VectorKind::U8 => {
                self.expose_uint8_memory();
                "getUint8Memory"
            }
            VectorKind::ClampedU8 => {
                self.expose_clamped_uint8_memory();
                "getUint8ClampedMemory"
            }
            VectorKind::I16 => {
                self.expose_int16_memory();
                "getInt16Memory"
            }
            VectorKind::U16 => {
                self.expose_uint16_memory();
                "getUint16Memory"
            }
            VectorKind::I32 => {
                self.expose_int32_memory();
                "getInt32Memory"
            }
            VectorKind::U32 => {
                self.expose_uint32_memory();
                "getUint32Memory"
            }
            VectorKind::I64 => {
                self.expose_int64_memory();
                "getInt64Memory"
            }
            VectorKind::U64 => {
                self.expose_uint64_memory();
                "getUint64Memory"
            }
            VectorKind::F32 => {
                self.expose_f32_memory();
                "getFloat32Memory"
            }
            VectorKind::F64 => {
                self.expose_f64_memory();
                "getFloat64Memory"
            }
            VectorKind::Anyref => {
                self.expose_uint32_memory();
                "getUint32Memory"
            }
        }
    }

    fn memview(&mut self, name: &'static str, js: &str) {
        if !self.should_write_global(name) {
            return;
        }
        let mem = self.memory();
        self.global(&format!(
            "
            let cache{name} = null;
            function {name}() {{
                if (cache{name} === null || cache{name}.buffer !== {mem}.buffer) {{
                    cache{name} = new {js}({mem}.buffer);
                }}
                return cache{name};
            }}
            ",
            name = name,
            js = js,
            mem = mem,
        ));
    }

    fn expose_assert_class(&mut self) {
        if !self.should_write_global("assert_class") {
            return;
        }
        self.global(
            "
            function _assertClass(instance, klass) {
                if (!(instance instanceof klass)) {
                    throw new Error(`expected instance of ${klass.name}`);
                }
                return instance.ptr;
            }
            ",
        );
    }

    fn expose_global_stack_pointer(&mut self) {
        if !self.should_write_global("stack_pointer") {
            return;
        }
        self.global(&format!("let stack_pointer = {};", INITIAL_HEAP_OFFSET));
    }

    fn expose_borrowed_objects(&mut self) {
        if !self.should_write_global("borrowed_objects") {
            return;
        }
        self.expose_global_heap();
        self.expose_global_stack_pointer();
        // Our `stack_pointer` points to where we should start writing stack
        // objects, and the `stack_pointer` is incremented in a `finally` block
        // after executing this. Once we've reserved stack space we write the
        // value. Eventually underflow will throw an exception, but JS sort of
        // just handles it today...
        self.global(
            "
            function addBorrowedObject(obj) {
                if (stack_pointer == 1) throw new Error('out of js stack');
                heap[--stack_pointer] = obj;
                return stack_pointer;
            }
            ",
        );
    }

    fn expose_take_object(&mut self) {
        if !self.should_write_global("take_object") {
            return;
        }
        self.expose_get_object();
        self.expose_drop_ref();
        self.global(
            "
            function takeObject(idx) {
                const ret = getObject(idx);
                dropObject(idx);
                return ret;
            }
            ",
        );
    }

    fn expose_add_heap_object(&mut self) {
        if !self.should_write_global("add_heap_object") {
            return;
        }
        self.expose_global_heap();
        self.expose_global_heap_next();
        let set_heap_next = if self.config.debug {
            String::from(
                "
                if (typeof(heap_next) !== 'number') throw new Error('corrupt heap');
                ",
            )
        } else {
            String::new()
        };

        // Allocating a slot on the heap first goes through the linked list
        // (starting at `heap_next`). Once that linked list is exhausted we'll
        // be pointing beyond the end of the array, at which point we'll reserve
        // one more slot and use that.
        self.global(&format!(
            "
            function addHeapObject(obj) {{
                if (heap_next === heap.length) heap.push(heap.length + 1);
                const idx = heap_next;
                heap_next = heap[idx];
                {}
                heap[idx] = obj;
                return idx;
            }}
            ",
            set_heap_next
        ));
    }

    fn expose_handle_error(&mut self) -> Result<(), Error> {
        if !self.should_write_global("handle_error") {
            return Ok(());
        }
        self.expose_uint32_memory();
        if self.config.anyref {
            self.expose_add_to_anyref_table()?;
            self.global(
                "
                function handleError(exnptr, e) {
                    const idx = addToAnyrefTable(e);
                    const view = getUint32Memory();
                    view[exnptr / 4] = 1;
                    view[exnptr / 4 + 1] = idx;
                }
                ",
            );
        } else {
            self.expose_add_heap_object();
            self.global(
                "
                function handleError(exnptr, e) {
                    const view = getUint32Memory();
                    view[exnptr / 4] = 1;
                    view[exnptr / 4 + 1] = addHeapObject(e);
                }
                ",
            );
        }
        Ok(())
    }

    fn wasm_import_needed(&self, name: &str) -> bool {
        self.module
            .imports
            .iter()
            .any(|i| i.module == "__wbindgen_placeholder__" && i.name == name)
    }

    fn pass_to_wasm_function(&mut self, t: VectorKind) -> Result<&'static str, Error> {
        let s = match t {
            VectorKind::String => {
                self.expose_pass_string_to_wasm()?;
                "passStringToWasm"
            }
            VectorKind::I8 | VectorKind::U8 | VectorKind::ClampedU8 => {
                self.expose_pass_array8_to_wasm()?;
                "passArray8ToWasm"
            }
            VectorKind::U16 | VectorKind::I16 => {
                self.expose_pass_array16_to_wasm()?;
                "passArray16ToWasm"
            }
            VectorKind::I32 | VectorKind::U32 => {
                self.expose_pass_array32_to_wasm()?;
                "passArray32ToWasm"
            }
            VectorKind::I64 | VectorKind::U64 => {
                self.expose_pass_array64_to_wasm()?;
                "passArray64ToWasm"
            }
            VectorKind::F32 => {
                self.expose_pass_array_f32_to_wasm()?;
                "passArrayF32ToWasm"
            }
            VectorKind::F64 => {
                self.expose_pass_array_f64_to_wasm()?;
                "passArrayF64ToWasm"
            }
            VectorKind::Anyref => {
                self.expose_pass_array_jsvalue_to_wasm()?;
                "passArrayJsValueToWasm"
            }
        };
        Ok(s)
    }

    fn expose_get_vector_from_wasm(&mut self, ty: VectorKind) -> Result<&'static str, Error> {
        Ok(match ty {
            VectorKind::String => {
                self.expose_get_string_from_wasm();
                "getStringFromWasm"
            }
            VectorKind::I8 => {
                self.expose_get_array_i8_from_wasm();
                "getArrayI8FromWasm"
            }
            VectorKind::U8 => {
                self.expose_get_array_u8_from_wasm();
                "getArrayU8FromWasm"
            }
            VectorKind::ClampedU8 => {
                self.expose_get_clamped_array_u8_from_wasm();
                "getClampedArrayU8FromWasm"
            }
            VectorKind::I16 => {
                self.expose_get_array_i16_from_wasm();
                "getArrayI16FromWasm"
            }
            VectorKind::U16 => {
                self.expose_get_array_u16_from_wasm();
                "getArrayU16FromWasm"
            }
            VectorKind::I32 => {
                self.expose_get_array_i32_from_wasm();
                "getArrayI32FromWasm"
            }
            VectorKind::U32 => {
                self.expose_get_array_u32_from_wasm();
                "getArrayU32FromWasm"
            }
            VectorKind::I64 => {
                self.expose_get_array_i64_from_wasm();
                "getArrayI64FromWasm"
            }
            VectorKind::U64 => {
                self.expose_get_array_u64_from_wasm();
                "getArrayU64FromWasm"
            }
            VectorKind::F32 => {
                self.expose_get_array_f32_from_wasm();
                "getArrayF32FromWasm"
            }
            VectorKind::F64 => {
                self.expose_get_array_f64_from_wasm();
                "getArrayF64FromWasm"
            }
            VectorKind::Anyref => {
                self.expose_get_array_js_value_from_wasm()?;
                "getArrayJsValueFromWasm"
            }
        })
    }

    fn expose_global_argument_ptr(&mut self) -> Result<(), Error> {
        if !self.should_write_global("global_argument_ptr") {
            return Ok(());
        }
        self.require_internal_export("__wbindgen_global_argument_ptr")?;
        self.global(
            "
            let cachedGlobalArgumentPtr = null;
            function globalArgumentPtr() {
                if (cachedGlobalArgumentPtr === null) {
                    cachedGlobalArgumentPtr = wasm.__wbindgen_global_argument_ptr();
                }
                return cachedGlobalArgumentPtr;
            }
            ",
        );
        Ok(())
    }

    fn expose_get_inherited_descriptor(&mut self) {
        if !self.should_write_global("get_inherited_descriptor") {
            return;
        }
        // It looks like while rare some browsers will move descriptors up the
        // property chain which runs the risk of breaking wasm-bindgen-generated
        // code because we're looking for precise descriptor functions rather
        // than relying on the prototype chain like most "normal JS" projects
        // do.
        //
        // As a result we have a small helper here which will walk the prototype
        // chain looking for a descriptor. For some more information on this see
        // #109
        self.global(
            "
            function GetOwnOrInheritedPropertyDescriptor(obj, id) {
              while (obj) {
                let desc = Object.getOwnPropertyDescriptor(obj, id);
                if (desc) return desc;
                obj = Object.getPrototypeOf(obj);
              }
              return {}
            }
            ",
        );
    }

    fn expose_u32_cvt_shim(&mut self) -> &'static str {
        let name = "u32CvtShim";
        if !self.should_write_global(name) {
            return name;
        }
        self.global(&format!("const {} = new Uint32Array(2);", name));
        name
    }

    fn expose_int64_cvt_shim(&mut self) -> &'static str {
        let name = "int64CvtShim";
        if !self.should_write_global(name) {
            return name;
        }
        let n = self.expose_u32_cvt_shim();
        self.global(&format!(
            "const {} = new BigInt64Array({}.buffer);",
            name, n
        ));
        name
    }

    fn expose_uint64_cvt_shim(&mut self) -> &'static str {
        let name = "uint64CvtShim";
        if !self.should_write_global(name) {
            return name;
        }
        let n = self.expose_u32_cvt_shim();
        self.global(&format!(
            "const {} = new BigUint64Array({}.buffer);",
            name, n
        ));
        name
    }

    fn expose_is_like_none(&mut self) {
        if !self.should_write_global("is_like_none") {
            return;
        }
        self.global(
            "
            function isLikeNone(x) {
                return x === undefined || x === null;
            }
        ",
        );
    }

    fn expose_cleanup_groups(&mut self) {
        if !self.should_write_global("cleanup_groups") {
            return;
        }
        self.global(
            "
                const CLEANUPS = new WeakRefGroup(x => x.holdings());
                const CLEANUPS_MAP = new Map();

                function addCleanup(obj, ptr, free) {
                    const ref = CLEANUPS.makeRef(obj, () => free(ptr));
                    CLEANUPS_MAP.set(ptr, ref);
                }
            ",
        );
    }

    fn describe(&mut self, name: &str) -> Option<Descriptor> {
        let name = format!("__wbindgen_describe_{}", name);
        let descriptor = self.interpreter.interpret_descriptor(&name, self.module)?;
        Some(Descriptor::decode(descriptor))
    }

    fn global(&mut self, s: &str) {
        let s = s.trim();

        // Ensure a blank line between adjacent items, and ensure everything is
        // terminated with a newline.
        while !self.globals.ends_with("\n\n\n") && !self.globals.ends_with("*/\n") {
            self.globals.push_str("\n");
        }
        self.globals.push_str(s);
        self.globals.push_str("\n");
    }

    fn use_node_require(&self) -> bool {
        self.config.nodejs && !self.config.nodejs_experimental_modules
    }

    fn memory(&mut self) -> &'static str {
        if self.module.memories.get(self.memory).import.is_some() {
            "memory"
        } else {
            "wasm.memory"
        }
    }

    fn require_class_wrap(&mut self, class: &str) {
        self.exported_classes
            .as_mut()
            .expect("classes already written")
            .entry(class.to_string())
            .or_insert_with(ExportedClass::default)
            .wrap_needed = true;
    }

    fn import_identifier(&mut self, import: Import<'a>) -> String {
        // Here's where it's a bit tricky. We need to make sure that importing
        // the same identifier from two different modules works, and they're
        // named uniquely below. Additionally if we've already imported the same
        // identifier from the module in question then we'd like to reuse the
        // one that was previously imported.
        //
        // Our `imported_names` map keeps track of all imported identifiers from
        // modules, mapping the imported names onto names actually available for
        // use in our own module. If our identifier isn't present then we
        // generate a new identifier and are sure to generate the appropriate JS
        // import for our new identifier.
        let use_node_require = self.use_node_require();
        let imported_identifiers = &mut self.imported_identifiers;
        let imports = &mut self.imports;
        let imports_post = &mut self.imports_post;
        let identifier = self
            .imported_names
            .entry(import.module())
            .or_insert_with(Default::default)
            .entry(import.name())
            .or_insert_with(|| {
                let name = generate_identifier(import.name(), imported_identifiers);
                match &import {
                    Import::Module { module, .. } => {
                        if use_node_require {
                            imports.push_str(&format!(
                                "const {} = require(String.raw`{}`).{};\n",
                                name,
                                module,
                                import.name()
                            ));
                        } else if import.name() == name {
                            imports
                                .push_str(&format!("import {{ {} }} from '{}';\n", name, module));
                        } else {
                            imports.push_str(&format!(
                                "import {{ {} as {} }} from '{}';\n",
                                import.name(),
                                name,
                                module
                            ));
                        }
                        name
                    }

                    Import::VendorPrefixed { prefixes, .. } => {
                        imports_post.push_str("const l");
                        imports_post.push_str(&name);
                        imports_post.push_str(" = ");
                        switch(imports_post, &name, "", prefixes);
                        imports_post.push_str(";\n");

                        fn switch(dst: &mut String, name: &str, prefix: &str, left: &[&str]) {
                            if left.len() == 0 {
                                dst.push_str(prefix);
                                return dst.push_str(name);
                            }
                            dst.push_str("(typeof ");
                            dst.push_str(prefix);
                            dst.push_str(name);
                            dst.push_str(" == 'undefined' ? ");
                            match left.len() {
                                1 => {
                                    dst.push_str(&left[0]);
                                    dst.push_str(name);
                                }
                                _ => switch(dst, name, &left[0], &left[1..]),
                            }
                            dst.push_str(" : ");
                            dst.push_str(prefix);
                            dst.push_str(name);
                            dst.push_str(")");
                        }
                        format!("l{}", name)
                    }

                    Import::Global { .. } => name,
                }
            });

        // If there's a namespace we didn't actually import `item` but rather
        // the namespace, so access through that.
        match import.field() {
            Some(field) => format!("{}.{}", identifier, field),
            None => identifier.clone(),
        }
    }

    fn generated_import_target(
        &mut self,
        name: Import<'a>,
        import: &decode::ImportFunction<'a>,
    ) -> Result<ImportTarget, Error> {
        let method_data = match &import.method {
            Some(data) => data,
            None => {
                let name = self.import_identifier(name);
                if import.structural || !name.contains(".") {
                    return Ok(ImportTarget::Function(name));
                }
                self.global(&format!("const {}_target = {};", import.shim, name));
                let target = format!("{}_target", import.shim);
                return Ok(ImportTarget::Function(target));
            }
        };

        let class = self.import_identifier(name);
        let op = match &method_data.kind {
            decode::MethodKind::Constructor => {
                return Ok(ImportTarget::Constructor(class.to_string()));
            }
            decode::MethodKind::Operation(op) => op,
        };
        if import.structural {
            let class = if op.is_static {
                Some(class.clone())
            } else {
                None
            };

            return Ok(match &op.kind {
                decode::OperationKind::Regular => {
                    let name = import.function.name.to_string();
                    match class {
                        Some(c) => ImportTarget::Function(format!("{}.{}", c, name)),
                        None => ImportTarget::StructuralMethod(name),
                    }
                }
                decode::OperationKind::Getter(g) => {
                    ImportTarget::StructuralGetter(class, g.to_string())
                }
                decode::OperationKind::Setter(s) => {
                    ImportTarget::StructuralSetter(class, s.to_string())
                }
                decode::OperationKind::IndexingGetter => {
                    ImportTarget::StructuralIndexingGetter(class)
                }
                decode::OperationKind::IndexingSetter => {
                    ImportTarget::StructuralIndexingSetter(class)
                }
                decode::OperationKind::IndexingDeleter => {
                    ImportTarget::StructuralIndexingDeleter(class)
                }
            });
        }

        let target = format!(
            "typeof {0} === 'undefined' ? null : {}{}",
            class,
            if op.is_static { "" } else { ".prototype" }
        );
        let (mut target, name) = match &op.kind {
            decode::OperationKind::Regular => (
                format!("{}.{}", target, import.function.name),
                &import.function.name,
            ),
            decode::OperationKind::Getter(g) => {
                self.expose_get_inherited_descriptor();
                (
                    format!(
                        "GetOwnOrInheritedPropertyDescriptor({}, '{}').get",
                        target, g,
                    ),
                    g,
                )
            }
            decode::OperationKind::Setter(s) => {
                self.expose_get_inherited_descriptor();
                (
                    format!(
                        "GetOwnOrInheritedPropertyDescriptor({}, '{}').set",
                        target, s,
                    ),
                    s,
                )
            }
            decode::OperationKind::IndexingGetter => panic!("indexing getter should be structural"),
            decode::OperationKind::IndexingSetter => panic!("indexing setter should be structural"),
            decode::OperationKind::IndexingDeleter => {
                panic!("indexing deleter should be structural")
            }
        };
        target.push_str(&format!(
            " || function() {{
            throw new Error(`wasm-bindgen: {}.{} does not exist`);
        }}",
            class, name
        ));
        if op.is_static {
            target.insert(0, '(');
            target.push_str(").bind(");
            target.push_str(&class);
            target.push_str(")");
        }

        self.global(&format!("const {}_target = {};", import.shim, target));
        Ok(if op.is_static {
            ImportTarget::Function(format!("{}_target", import.shim))
        } else {
            ImportTarget::Method(format!("{}_target", import.shim))
        })
    }

    /// Update the wasm file's `producers` section to include information about
    /// the wasm-bindgen tool.
    ///
    /// Specified at:
    /// https://github.com/WebAssembly/tool-conventions/blob/master/ProducersSection.md
    fn update_producers_section(&mut self) {
        self.module
            .producers
            .add_processed_by("wasm-bindgen", &wasm_bindgen_shared::version());
    }

    fn add_start_function(&mut self) -> Result<(), Error> {
        let start = match &self.start {
            Some(name) => name.clone(),
            None => return Ok(()),
        };
        let export = match self.module.exports.iter().find(|e| e.name == start) {
            Some(export) => export,
            None => bail!("export `{}` not found", start),
        };
        let id = match export.item {
            walrus::ExportItem::Function(i) => i,
            _ => bail!("export `{}` wasn't a function", start),
        };

        let prev_start = match self.module.start {
            Some(f) => f,
            None => {
                self.module.start = Some(id);
                return Ok(());
            }
        };

        // Note that we call the previous start function, if any, first. This is
        // because the start function currently only shows up when it's injected
        // through thread/anyref transforms. These injected start functions need
        // to happen before user code, so we always schedule them first.
        let mut builder = walrus::FunctionBuilder::new();
        let call1 = builder.call(prev_start, Box::new([]));
        let call2 = builder.call(id, Box::new([]));
        let ty = self.module.funcs.get(id).ty();
        let new_start = builder.finish(ty, Vec::new(), vec![call1, call2], self.module);
        self.module.start = Some(new_start);
        Ok(())
    }

    /// If a start function is present, it removes it from the `start` section
    /// of the wasm module and then moves it to an exported function, named
    /// `__wbindgen_start`.
    fn unstart_start_function(&mut self) -> bool {
        let start = match self.module.start.take() {
            Some(id) => id,
            None => return false,
        };
        self.module.exports.add("__wbindgen_start", start);
        true
    }

    /// Injects a `start` function into the wasm module. This start function
    /// calls a shim in the generated JS which defers the actual start function
    /// to the next microtask tick of the event queue.
    ///
    /// See docs above at callsite for why this happens.
    fn inject_start_shim(&mut self) {
        let body = "function() {
            Promise.resolve().then(() => wasm.__wbindgen_start());
        }";
        self.export("__wbindgen_defer_start", body, None);
        let ty = self.module.types.add(&[], &[]);
        let id =
            self.module
                .add_import_func("__wbindgen_placeholder__", "__wbindgen_defer_start", ty);
        assert!(self.module.start.is_none());
        self.module.start = Some(id);
    }

    fn expose_anyref_table(&mut self) {
        assert!(self.config.anyref);
        if !self.should_write_global("anyref_table") {
            return;
        }
        self.module
            .exports
            .add("__wbg_anyref_table", self.anyref.anyref_table_id());
    }

    fn expose_add_to_anyref_table(&mut self) -> Result<(), Error> {
        assert!(self.config.anyref);
        if !self.should_write_global("add_to_anyref_table") {
            return Ok(());
        }
        self.expose_anyref_table();
        self.require_internal_export("__wbindgen_anyref_table_alloc")?;
        self.global(
            "
                function addToAnyrefTable(obj) {
                    const idx = wasm.__wbindgen_anyref_table_alloc();
                    wasm.__wbg_anyref_table.set(idx, obj);
                    return idx;
                }
            ",
        );

        Ok(())
    }

    fn add_heap_object(&mut self, expr: &str) -> String {
        if self.config.anyref {
            expr.to_string()
        } else {
            self.expose_add_heap_object();
            format!("addHeapObject({})", expr)
        }
    }

    fn take_object(&mut self, expr: &str) -> String {
        if self.config.anyref {
            expr.to_string()
        } else {
            self.expose_take_object();
            format!("takeObject({})", expr)
        }
    }

    fn get_object(&mut self, expr: &str) -> String {
        if self.config.anyref {
            expr.to_string()
        } else {
            self.expose_get_object();
            format!("getObject({})", expr)
        }
    }
}

impl<'a, 'b> SubContext<'a, 'b> {
    pub fn generate(&mut self) -> Result<(), Error> {
        for f in self.program.exports.iter() {
            self.generate_export(f).with_context(|_| {
                format!(
                    "failed to generate bindings for Rust export `{}`",
                    f.function.name
                )
            })?;
        }
        for f in self.program.imports.iter() {
            if let decode::ImportKind::Type(ty) = &f.kind {
                self.register_vendor_prefix(ty);
            }
        }
        for f in self.program.imports.iter() {
            self.generate_import(f)?;
        }
        for e in self.program.enums.iter() {
            self.generate_enum(e);
        }
        for s in self.program.structs.iter() {
            self.generate_struct(s).with_context(|_| {
                format!("failed to generate bindings for Rust struct `{}`", s.name,)
            })?;
        }
        for s in self.program.typescript_custom_sections.iter() {
            self.cx.typescript.push_str(s);
            self.cx.typescript.push_str("\n\n");
        }

        Ok(())
    }

    fn generate_export(&mut self, export: &decode::Export<'b>) -> Result<(), Error> {
        if let Some(ref class) = export.class {
            assert!(!export.start);
            return self.generate_export_for_class(class, export);
        }

        let descriptor = match self.cx.describe(&export.function.name) {
            None => return Ok(()),
            Some(d) => d,
        };

        if export.start {
            self.set_start_function(export.function.name)?;
        }

        let (js, ts, js_doc) = Js2Rust::new(&export.function.name, self.cx)
            .process(descriptor.unwrap_function())?
            .finish(
                "function",
                &format!("wasm.{}", export.function.name),
                ExportedShim::Named(&export.function.name),
            );
        self.cx.export(
            &export.function.name,
            &js,
            Some(format_doc_comments(&export.comments, Some(js_doc))),
        );
        self.cx.globals.push_str("\n");
        self.cx.typescript.push_str("export ");
        self.cx.typescript.push_str(&ts);
        self.cx.typescript.push_str("\n");
        Ok(())
    }

    fn set_start_function(&mut self, start: &str) -> Result<(), Error> {
        if let Some(prev) = &self.cx.start {
            bail!(
                "cannot flag `{}` as start function as `{}` is \
                 already the start function",
                start,
                prev
            );
        }
        self.cx.start = Some(start.to_string());
        Ok(())
    }

    fn generate_export_for_class(
        &mut self,
        class_name: &'b str,
        export: &decode::Export,
    ) -> Result<(), Error> {
        let wasm_name =
            wasm_bindgen_shared::struct_function_export_name(class_name, &export.function.name);

        let descriptor = match self.cx.describe(&wasm_name) {
            None => return Ok(()),
            Some(d) => d,
        };

        let function_name = if export.is_constructor {
            "constructor"
        } else {
            &export.function.name
        };
        let (js, ts, js_doc) = Js2Rust::new(function_name, self.cx)
            .method(export.method, export.consumed)
            .constructor(if export.is_constructor {
                Some(class_name)
            } else {
                None
            })
            .process(descriptor.unwrap_function())?
            .finish(
                "",
                &format!("wasm.{}", wasm_name),
                ExportedShim::Named(&wasm_name),
            );

        let class = self
            .cx
            .exported_classes
            .as_mut()
            .expect("classes already written")
            .entry(class_name.to_string())
            .or_insert(ExportedClass::default());
        class
            .contents
            .push_str(&format_doc_comments(&export.comments, Some(js_doc)));

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

        if export.is_constructor {
            if class.has_constructor {
                bail!("found duplicate constructor `{}`", export.function.name);
            }
            class.has_constructor = true;
        } else if !export.method {
            class.contents.push_str("static ");
            class.typescript.push_str("static ");
        }

        class.contents.push_str(function_name);
        class.contents.push_str(&js);
        class.contents.push_str("\n");
        class.typescript.push_str(&ts);
        class.typescript.push_str("\n");
        Ok(())
    }

    fn generate_import(&mut self, import: &decode::Import<'b>) -> Result<(), Error> {
        match import.kind {
            decode::ImportKind::Function(ref f) => {
                self.generate_import_function(import, f).with_context(|_| {
                    format!(
                        "failed to generate bindings for JS import `{}`",
                        f.function.name
                    )
                })?;
            }
            decode::ImportKind::Static(ref s) => {
                self.generate_import_static(import, s).with_context(|_| {
                    format!("failed to generate bindings for JS import `{}`", s.name)
                })?;
            }
            decode::ImportKind::Type(ref ty) => {
                self.generate_import_type(import, ty).with_context(|_| {
                    format!("failed to generate bindings for JS import `{}`", ty.name,)
                })?;
            }
            decode::ImportKind::Enum(_) => {}
        }
        Ok(())
    }

    fn generate_import_static(
        &mut self,
        info: &decode::Import<'b>,
        import: &decode::ImportStatic<'b>,
    ) -> Result<(), Error> {
        // The same static can be imported in multiple locations, so only
        // generate bindings once for it.
        if !self.cx.imported_statics.insert(import.shim) {
            return Ok(());
        }

        // TODO: should support more types to import here
        let obj = self.import_name(info, &import.name)?;
        self.cx
            .anyref
            .import_xform("__wbindgen_placeholder__", &import.shim, &[], true);
        let body = format!("function() {{ return {}; }}", self.cx.add_heap_object(&obj));
        self.cx.export(&import.shim, &body, None);
        Ok(())
    }

    fn generate_import_function(
        &mut self,
        info: &decode::Import<'b>,
        import: &decode::ImportFunction<'b>,
    ) -> Result<(), Error> {
        if !self.cx.wasm_import_needed(&import.shim) {
            return Ok(());
        }

        // It's possible for the same function to be imported in two locations,
        // but we only want to generate one.
        if !self.cx.imported_functions.insert(import.shim) {
            return Ok(());
        }

        let descriptor = match self.cx.describe(&import.shim) {
            None => return Ok(()),
            Some(d) => d,
        };

        // Figure out the name that we're importing to dangle further references
        // off of. This is the function name if there's no method all here, or
        // the class if there's a method call.
        let name = match &import.method {
            Some(data) => self.determine_import(info, &data.class)?,
            None => self.determine_import(info, &import.function.name)?,
        };

        // Build up our shim's state, and we'll use that to guide whether we
        // actually emit an import here or not.
        let mut shim = Rust2Js::new(self.cx);
        if shim.cx.config.debug {
            shim.catch_and_rethrow(true);
        }
        shim.catch(import.catch)
            .variadic(import.variadic)
            .process(descriptor.unwrap_function())?;

        // If this is a bare function import and the shim doesn't actually do
        // anything (all argument/return conversions are noops) then we can wire
        // up the wasm import directly to the destination. We don't actually
        // wire up anything here, but we record it to get wired up later.
        if import.method.is_none() && shim.is_noop() {
            if let Import::Module {
                module,
                name,
                field: None,
            } = name
            {
                shim.cx.direct_imports.insert(import.shim, (module, name));

                if shim.ret_anyref || shim.anyref_args.len() > 0 {
                    shim.cx.anyref.import_xform(
                        "__wbindgen_placeholder__",
                        &import.shim,
                        &shim.anyref_args,
                        shim.ret_anyref,
                    );
                }
                return Ok(());
            }
        }

        // If the above optimization fails then we actually generate the import
        // here (possibly emitting some glue in our JS module) and then emit the
        // shim as the wasm will be importing the shim.
        let target = shim.cx.generated_import_target(name, import)?;
        let js = shim.finish(&target, &import.shim)?;
        shim.cx.export(&import.shim, &js, None);
        Ok(())
    }

    fn generate_import_type(
        &mut self,
        info: &decode::Import<'b>,
        import: &decode::ImportType<'b>,
    ) -> Result<(), Error> {
        if !self.cx.wasm_import_needed(&import.instanceof_shim) {
            return Ok(());
        }
        let name = self.import_name(info, &import.name)?;
        self.cx.anyref.import_xform(
            "__wbindgen_placeholder__",
            &import.instanceof_shim,
            &[(0, false)],
            false,
        );
        let body = format!(
            "function(idx) {{ return {} instanceof {} ? 1 : 0; }}",
            self.cx.get_object("idx"),
            name
        );
        self.cx.export(&import.instanceof_shim, &body, None);
        Ok(())
    }

    fn generate_enum(&mut self, enum_: &decode::Enum) {
        let mut variants = String::new();

        for variant in enum_.variants.iter() {
            variants.push_str(&format!("{}:{},", variant.name, variant.value));
        }
        self.cx.export(
            &enum_.name,
            &format!("Object.freeze({{ {} }})", variants),
            Some(format_doc_comments(&enum_.comments, None)),
        );
        self.cx
            .typescript
            .push_str(&format!("export enum {} {{", enum_.name));

        for variant in enum_.variants.iter() {
            self.cx
                .typescript
                .push_str(&format!("\n  {},", variant.name));
        }
        self.cx.typescript.push_str("\n}\n");
    }

    fn generate_struct(&mut self, struct_: &decode::Struct) -> Result<(), Error> {
        let mut dst = String::new();
        let mut ts_dst = String::new();
        for field in struct_.fields.iter() {
            let wasm_getter = wasm_bindgen_shared::struct_field_get(&struct_.name, &field.name);
            let wasm_setter = wasm_bindgen_shared::struct_field_set(&struct_.name, &field.name);
            let descriptor = match self.cx.describe(&wasm_getter) {
                None => continue,
                Some(d) => d,
            };

            let set = {
                let setter = ExportedShim::Named(&wasm_setter);
                let mut cx = Js2Rust::new(&field.name, self.cx);
                cx.method(true, false)
                    .argument(&descriptor)?
                    .ret(&Descriptor::Unit)?;
                ts_dst.push_str(&format!(
                    "\n  {}{}: {};",
                    if field.readonly { "readonly " } else { "" },
                    field.name,
                    &cx.js_arguments[0].1
                ));
                cx.finish("", &format!("wasm.{}", wasm_setter), setter).0
            };
            let getter = ExportedShim::Named(&wasm_getter);
            let (get, _ts, js_doc) = Js2Rust::new(&field.name, self.cx)
                .method(true, false)
                .ret(&descriptor)?
                .finish("", &format!("wasm.{}", wasm_getter), getter);
            if !dst.ends_with("\n") {
                dst.push_str("\n");
            }
            dst.push_str(&format_doc_comments(&field.comments, Some(js_doc)));
            dst.push_str("get ");
            dst.push_str(&field.name);
            dst.push_str(&get);
            dst.push_str("\n");
            if !field.readonly {
                dst.push_str("set ");
                dst.push_str(&field.name);
                dst.push_str(&set);
            }
        }

        let class = self
            .cx
            .exported_classes
            .as_mut()
            .expect("classes already written")
            .entry(struct_.name.to_string())
            .or_insert_with(Default::default);
        class.comments = format_doc_comments(&struct_.comments, None);
        class.contents.push_str(&dst);
        class.contents.push_str("\n");
        class.typescript.push_str(&ts_dst);
        class.typescript.push_str("\n");
        Ok(())
    }

    fn register_vendor_prefix(&mut self, info: &decode::ImportType<'b>) {
        if info.vendor_prefixes.len() == 0 {
            return;
        }
        self.vendor_prefixes
            .entry(info.name)
            .or_insert(Vec::new())
            .extend(info.vendor_prefixes.iter().cloned());
    }

    fn determine_import(
        &self,
        import: &decode::Import<'b>,
        item: &'b str,
    ) -> Result<Import<'b>, Error> {
        // First up, imports don't work at all in `--no-modules` mode as we're
        // not sure how to import them.
        if self.cx.config.no_modules {
            if let Some(module) = &import.module {
                bail!(
                    "import from `{}` module not allowed with `--no-modules`; \
                     use `--nodejs` or `--browser` instead",
                    module
                );
            }
        }

        // Similar to `--no-modules`, only allow vendor prefixes basically for web
        // apis, shouldn't be necessary for things like npm packages or other
        // imported items.
        let vendor_prefixes = self.vendor_prefixes.get(item);
        if let Some(vendor_prefixes) = vendor_prefixes {
            assert!(vendor_prefixes.len() > 0);

            if let Some(module) = &import.module {
                bail!(
                    "import of `{}` from `{}` has a polyfill of `{}` listed, but
                     vendor prefixes aren't supported when importing from modules",
                    item,
                    module,
                    &vendor_prefixes[0],
                );
            }
            if let Some(ns) = &import.js_namespace {
                bail!(
                    "import of `{}` through js namespace `{}` isn't supported \
                     right now when it lists a polyfill",
                    item,
                    ns
                );
            }
            return Ok(Import::VendorPrefixed {
                name: item,
                prefixes: vendor_prefixes.clone(),
            });
        }

        let name = import.js_namespace.as_ref().map(|s| &**s).unwrap_or(item);
        let field = if import.js_namespace.is_some() {
            Some(item)
        } else {
            None
        };

        Ok(match import.module {
            Some(module) => Import::Module {
                module,
                name,
                field,
            },
            None => Import::Global { name, field },
        })
    }

    fn import_name(&mut self, import: &decode::Import<'b>, item: &'b str) -> Result<String, Error> {
        let import = self.determine_import(import, item)?;
        Ok(self.cx.import_identifier(import))
    }
}

impl<'a> Import<'a> {
    fn module(&self) -> Option<&'a str> {
        match self {
            Import::Module { module, .. } => Some(module),
            _ => None,
        }
    }

    fn field(&self) -> Option<&'a str> {
        match self {
            Import::Module { field, .. } | Import::Global { field, .. } => *field,
            Import::VendorPrefixed { .. } => None,
        }
    }

    fn name(&self) -> &'a str {
        match self {
            Import::Module { name, .. }
            | Import::Global { name, .. }
            | Import::VendorPrefixed { name, .. } => *name,
        }
    }
}

fn generate_identifier(name: &str, used_names: &mut HashMap<String, usize>) -> String {
    let cnt = used_names.entry(name.to_string()).or_insert(0);
    *cnt += 1;
    // We want to mangle `default` at once, so we can support default exports and don't generate
    // invalid glue code like this: `import { default } from './module';`.
    if *cnt == 1 && name != "default" {
        name.to_string()
    } else {
        format!("{}{}", name, cnt)
    }
}

fn format_doc_comments(comments: &[&str], js_doc_comments: Option<String>) -> String {
    let body: String = comments
        .iter()
        .map(|c| format!("*{}\n", c.trim_matches('"')))
        .collect();
    let doc = if let Some(docs) = js_doc_comments {
        docs.lines().map(|l| format!("* {} \n", l)).collect()
    } else {
        String::new()
    };
    format!("/**\n{}{}*/\n", body, doc)
}

#[test]
fn test_generate_identifier() {
    let mut used_names: HashMap<String, usize> = HashMap::new();
    assert_eq!(
        generate_identifier("someVar", &mut used_names),
        "someVar".to_string()
    );
    assert_eq!(
        generate_identifier("someVar", &mut used_names),
        "someVar2".to_string()
    );
    assert_eq!(
        generate_identifier("default", &mut used_names),
        "default1".to_string()
    );
    assert_eq!(
        generate_identifier("default", &mut used_names),
        "default2".to_string()
    );
}