fn hash_expr<'a>(
        &mut self,
        expr: &'a Expr,
        symbol_positions: &mut fnv::FnvHashMap<&'a Symbol, Vec<i32>>,
        max_id: &mut i32,
    ) -> WeldResult<()> {
        // In expressions that define new symbols, subexpressions will be handled in the match.
        let mut finished_subexpressions = false;
        // Hash the type.
        expr.ty.hash(&mut self.hasher);
        // Hash the name, which represents the kind of the expression.
        expr.kind.name().hash(&mut self.hasher);
        // Hash the kind.
        match expr.kind {
            Literal(ref kind) => match *kind {
                BoolLiteral(v) => v.hash(&mut self.hasher),
                I8Literal(v) => v.hash(&mut self.hasher),
                I16Literal(v) => v.hash(&mut self.hasher),
                I32Literal(v) => v.hash(&mut self.hasher),
                I64Literal(v) => v.hash(&mut self.hasher),
                U8Literal(v) => v.hash(&mut self.hasher),
                U16Literal(v) => v.hash(&mut self.hasher),
                U32Literal(v) => v.hash(&mut self.hasher),
                U64Literal(v) => v.hash(&mut self.hasher),
                F32Literal(v) => v.hash(&mut self.hasher),
                F64Literal(v) => v.hash(&mut self.hasher),
                StringLiteral(ref v) => v.hash(&mut self.hasher),
            },
            Ident(ref sym) => {
                // We track symbols to disambiguate redefinitions, but also to ignore the actual
                // textual symbol name. By hashing a number representing the symbol, expressions
                // which are structurally the same but have different names will produce the same
                // hash value.
                match symbol_positions.entry(sym) {
                    Entry::Occupied(ref ent) => {
                        ent.get().hash(&mut self.hasher);
                    }
                    _ => {
                        return compile_err!("Undefined symbol {}", sym);
                    }
                }
            }
            BinOp { ref kind, .. } => {
                kind.hash(&mut self.hasher);
            }
            UnaryOp { ref kind, .. } => {
                kind.hash(&mut self.hasher);
            }
            Cast { ref kind, .. } => {
                kind.hash(&mut self.hasher);
            }
            GetField { ref index, .. } => {
                index.hash(&mut self.hasher);
            }
            Let {
                ref name,
                ref value,
                ref body,
            } => {
                // Do the value before pushing onto the symbol staack.
                self.hash_expr(value, symbol_positions, max_id)?;
                {
                    let entry = symbol_positions.entry(name).or_insert_with(Vec::new);
                    entry.push(*max_id);
                    *max_id += 1;
                } // brackets to end the borrow.
                self.hash_expr(body, symbol_positions, max_id)?;
                // pop the stack.
                let entry = symbol_positions.entry(name).or_insert_with(Vec::new);
                let _ = entry.pop();
                finished_subexpressions = true;
            }
            Lambda {
                ref params,
                ref body,
            } => {
                // Push the stack for each param.
                for param in params.iter() {
                    let entry = symbol_positions.entry(&param.name).or_insert_with(Vec::new);
                    entry.push(*max_id);
                    *max_id += 1;
                }
                self.hash_expr(body, symbol_positions, max_id)?;
                // Pop the stack.
                for param in params.iter() {
                    let entry = symbol_positions.entry(&param.name).or_insert_with(Vec::new);
                    entry.pop();
                }
                finished_subexpressions = true;
            }
            CUDF {
                ref sym_name,
                ref return_ty,
                ..
            } => {
                sym_name.hash(&mut self.hasher);
                return_ty.hash(&mut self.hasher);
            }
            Deserialize { ref value_ty, .. } => {
                value_ty.hash(&mut self.hasher);
            }
            For { ref iters, .. } => {
                for iter in iters.iter() {
                    iter.kind.hash(&mut self.hasher);
                }
            }
            // Other expressions (listed explicitly so we don't forget to add new ones). If the
            // expression doesn't have a non-Expr field, it goes here.
            Negate(_)
            | Not(_)
            | Assert(_)
            | Broadcast(_)
            | Serialize(_)
            | ToVec { .. }
            | MakeStruct { .. }
            | MakeVector { .. }
            | Zip { .. }
            | Length { .. }
            | Lookup { .. }
            | OptLookup { .. }
            | KeyExists { .. }
            | Slice { .. }
            | Sort { .. }
            | If { .. }
            | Iterate { .. }
            | Select { .. }
            | Apply { .. }
            | NewBuilder(_)
            | Merge { .. }
            | Res { .. } => {}
        }
        if !finished_subexpressions {
            for child in expr.children() {
                self.hash_expr(child, symbol_positions, max_id)?;
            }
        }
        Ok(())
    }
Recurisvely computes signatures for an expression and each of its subexpressions.
The precise symbol names defined within the expression are ignored.
weld/src/ast/hash.rs