pub fn hoist(
        tree: Spanned<CST>,
        symbols: HashMap<String, SharedSymbol>,
    ) -> Result<(Spanned<SST>, Scope), Syntax> {
        let mut hoister = Hoister::new();

        let sst = hoister.walk(tree)?;
        let scope = hoister.scopes.pop().unwrap();

        if !hoister.unresolved_hoists.is_empty() {
            let num_unresolved = hoister.unresolved_hoists.len();

            let mut error = Syntax::error_no_note(&format!(
                "{} variable{} referenced before assignment",
                num_unresolved,
                if num_unresolved == 1 { "" } else { "s" }
            ));

            // TODO: sort by occurence, earliest first?
            for (_symbol, spanned) in hoister.unresolved_hoists.iter() {
                // TODO: hints to correct to similar names, etc.
                // dbg!(&spanned.span);
                error = error.add_note(Note::new(spanned.span.clone()));
            }

            Err(error)
        } else {
            Ok((sst, scope))
        }
    }
Simple function that a scoped syntax tree (`SST`)
from an `CST`. Replaces all symbols with unique
identifiers; symbols by the same name in
different scopes will get different identifiers.
Also resolves closure captures and closure hoisting.
passerine/src/compiler/hoist.rs