fluidattacks-blends-domain 0.44.2

Blends functional core: pure AST graph to syntax graph (no_std)
Documentation
use crate::stack::StackGraph;
use crate::syntax::SyntaxGraph;
use crate::NodeId;

/// The syntax node `definition` stands for: itself, or else the first node it leads to in `syntax`.
#[must_use]
pub fn definition_site(
    stack: &StackGraph,
    syntax: &SyntaxGraph,
    definition: NodeId,
) -> Option<NodeId> {
    if syntax.nodes.contains_key(&definition) {
        return Some(definition);
    }
    stack
        .successors(definition)
        .into_iter()
        .map(|(sink, _)| sink)
        .find(|sink| syntax.nodes.contains_key(sink))
}

#[cfg(test)]
mod tests {
    use super::definition_site;
    use crate::stack::{
        build_stack_graph, Definition, StackGraph, StackNode, StackScope, SymbolPop,
    };
    use crate::syntax::{SyntaxGraph, SyntaxNode};
    use crate::{Language, NodeId};
    use alloc::borrow::ToOwned;
    use alloc::string::String;

    const FILE: NodeId = NodeId(1);
    const BLOCK: NodeId = NodeId(2);
    const FUNCTION: NodeId = NodeId(3);
    const SYNTHETIC: NodeId = NodeId(90);
    const SYNTHETIC_SCOPE: NodeId = NodeId(91);

    fn definition(symbol: &str) -> StackNode {
        StackNode::PopSymbol(SymbolPop {
            symbol: symbol.to_owned(),
            precedence: 1,
            definition: Some(Definition::default()),
            import: None,
        })
    }

    fn function_statement() -> SyntaxGraph {
        let mut syntax = SyntaxGraph::new();
        syntax.add_node(FILE, SyntaxNode::File);
        syntax.add_node(BLOCK, SyntaxNode::ExecutionBlock);
        syntax.add_ast_edge(FILE, BLOCK);
        syntax.add_node(
            FUNCTION,
            SyntaxNode::MethodDeclaration {
                name: Some(String::from("f")),
                access_modifiers: None,
                block_id: None,
                modifiers_id: None,
                parameters_id: None,
            },
        );
        syntax.add_ast_edge(BLOCK, FUNCTION);
        syntax.scope_parent.insert(FUNCTION, FILE);
        syntax
    }

    #[test]
    fn a_function_definition_stands_for_its_declaration() {
        let syntax = function_statement();
        let stack = build_stack_graph(&syntax, Language::Python, "mod.py");
        let definition = stack
            .nodes
            .iter()
            .find_map(|(id, node)| (node.is_definition() && *id != FUNCTION).then_some(*id))
            .expect("the function statement defines its name");

        assert_eq!(definition_site(&stack, &syntax, definition), Some(FUNCTION));
    }

    #[test]
    fn a_definition_with_a_syntax_id_stands_for_itself() {
        let syntax = function_statement();
        let mut stack = StackGraph::new();
        stack.add_node(FUNCTION, definition("f"));

        assert_eq!(definition_site(&stack, &syntax, FUNCTION), Some(FUNCTION));
    }

    #[test]
    fn a_definition_that_leads_to_no_syntax_node_stands_for_nothing() {
        let syntax = function_statement();
        let mut stack = StackGraph::new();
        stack.add_node(SYNTHETIC, definition("f"));
        stack.add_node(SYNTHETIC_SCOPE, StackNode::Scope(StackScope::default()));
        stack.add_edge(SYNTHETIC, SYNTHETIC_SCOPE, 0);

        assert_eq!(definition_site(&stack, &syntax, SYNTHETIC), None);
    }
}