squonk-ast 2.0.0

Dialect-agnostic SQL abstract syntax tree for the squonk toolkit
Documentation
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Moderately AI Inc.

//! @generated by `squonk-sourcegen`; do not edit by hand.
//! Regenerate with `cargo run -p squonk-sourcegen`.

#![allow(clippy::all)]

// Declaring these modules compiles trait impls and compile-time gates into the
// crate.
mod node_id_walk;
// `render_skeleton` is a drift-reference skeleton (thousands of `dead_code` LOC)
// whose only live use is the render-shape fingerprint pin in `render/nodes.rs`.
// Both are gated to test builds so product builds don't compile it; the drift
// alarm still fires under `cargo nextest run`, which is where regen is enforced.
#[cfg(test)]
pub(crate) mod render_skeleton;
pub(crate) mod size_asserts;
mod spanned;
pub mod visit;

pub use node_id_walk::NodeIdWalk;
pub use visit::{Visit, VisitMut};

#[cfg(test)]
mod tests {
    use crate::ast::*;
    use crate::generated::visit::{self, Visit, VisitMut};
    use crate::vocab::{Meta, NodeId, Span, Symbol};

    fn meta(start: u32, end: u32) -> Meta {
        Meta::new(
            Span::new(start, end),
            NodeId::new(1).expect("non-zero node id"),
        )
    }

    fn ident(sym: u32, start: u32, end: u32) -> Ident {
        Ident {
            sym: Symbol::new(sym).expect("non-zero symbol"),
            quote: QuoteStyle::None,
            meta: meta(start, end),
        }
    }

    fn literal_expr(start: u32, end: u32) -> Expr {
        Expr::Literal {
            literal: Literal {
                kind: LiteralKind::Integer,
                meta: meta(start, end),
            },
            meta: meta(start, end),
        }
    }

    #[test]
    fn struct_node_span_is_its_meta_span() {
        assert_eq!(ident(1, 3, 8).span(), Span::new(3, 8));
    }

    #[test]
    fn enum_node_span_is_its_meta_span() {
        let binary_op: Expr = Expr::BinaryOp {
            left: Box::new(literal_expr(0, 1)),
            op: BinaryOperator::Plus,
            right: Box::new(literal_expr(4, 5)),
            meta: meta(10, 12),
        };
        assert_eq!(binary_op.span(), Span::new(10, 12));
    }

    #[test]
    fn object_name_span_unions_its_idents() {
        let name = ObjectName(vec![ident(1, 2, 6), ident(2, 7, 12)].into());
        assert_eq!(name.span(), Span::new(2, 12));
    }

    #[test]
    fn field_less_variant_span_is_its_meta_span() {
        let wildcard: SelectItem = SelectItem::Wildcard {
            options: None,
            alias: None,
            alias_spelling: AliasSpelling::As,
            meta: meta(7, 8),
        };
        assert_eq!(wildcard.span(), Span::new(7, 8));
    }

    #[test]
    fn visit_threads_extension_nodes() {
        let expr = Expr::Other {
            ext: TestExt::default(),
            meta: meta(0, 0),
        };
        let mut visitor = CountingVisit::default();

        visitor.visit_expr(&expr);

        assert_eq!(visitor.exprs, 1);
        assert_eq!(visitor.extensions, 1);
    }

    #[test]
    fn visit_mut_threads_extension_nodes() {
        let mut expr = Expr::Other {
            ext: TestExt::default(),
            meta: meta(0, 0),
        };
        let mut visitor = TouchingVisitMut;

        visitor.visit_expr_mut(&mut expr);

        let Expr::Other { ext, .. } = expr else {
            panic!("test expression stays an extension node");
        };
        assert!(ext.touched);
    }

    #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
    struct TestExt {
        touched: bool,
    }

    impl Spanned for TestExt {
        fn span(&self) -> Span {
            Span::SYNTHETIC
        }
    }

    #[derive(Default)]
    struct CountingVisit {
        exprs: usize,
        extensions: usize,
    }

    impl<'ast> Visit<'ast, TestExt> for CountingVisit {
        fn visit_expr(&mut self, node: &'ast Expr<TestExt>) {
            self.exprs += 1;
            visit::walk_expr(self, node);
        }

        fn visit_extension(&mut self, _node: &'ast TestExt) {
            self.extensions += 1;
        }
    }

    struct TouchingVisitMut;

    impl VisitMut<TestExt> for TouchingVisitMut {
        fn visit_extension_mut(&mut self, node: &mut TestExt) {
            node.touched = true;
        }
    }
}