fn walk(&mut self, tree: Spanned<CST>) -> Result<Spanned<SST>, Syntax> {
        let sst: SST = match tree.item {
            CST::Base(Base::Lit(data)) => SST::Base(Base::Lit(data)),
            CST::Base(Base::Symbol(name)) => self.symbol(name, tree.span.clone()),
            CST::Base(Base::Block(block)) => self.block(block)?,
            // TODO: hoist as well
            CST::Base(Base::Label(name)) => {
                todo!()
                // SST::label(
                //     // TODO: change this to the following lines after types:
                //     self.resolve_symbol(name, cst.span.clone()),
                // )
            }
            CST::Base(Base::Tuple(tuple)) => self.tuple(tuple)?,
            CST::Base(Base::Assign(pattern, expression)) => self.assign(pattern, *expression)?,
            CST::Lambda(Lambda { arg, body }) => self.lambda(arg, *body)?,
            CST::Base(Base::Call(fun, arg)) => self.call(*fun, *arg)?,
            CST::Base(Base::Module(_)) => todo!(),
            CST::Base(Base::Effect(_)) => todo!(),
        };

        return Ok(Spanned::new(sst, tree.span));
    }
Walks a `CST` to produce an `SST`.
This is fairly standard - hoisting happens in
`self.assign`, `self.lambda`, and `self.symbol`.
passerine/src/compiler/hoist.rs