diff --git a/crates/pgls_treesitter/src/context/ancestors.rs b/crates/pgls_treesitter/src/context/ancestors.rs index a99f1054d..80c7b8de0 100644 --- a/crates/pgls_treesitter/src/context/ancestors.rs +++ b/crates/pgls_treesitter/src/context/ancestors.rs @@ -16,13 +16,13 @@ impl ScopeTracker { } pub fn register<'a>(&mut self, node: tree_sitter::Node<'a>, position: usize) { - if SCOPE_BOUNDARIES.contains(&node.kind()) { + if SCOPE_BOUNDARIES.contains(&node.kind()) || self.scopes.is_empty() { self.add_new_scope(node); } self.scopes .last_mut() - .unwrap_or_else(|| panic!("Unhandled node kind: {}", node.kind())) + .expect("scope must exist after initialization above") .ancestors .register(node, position); } diff --git a/crates/pgls_treesitter/src/context/mod.rs b/crates/pgls_treesitter/src/context/mod.rs index 54e80f15d..ffa63c4a2 100644 --- a/crates/pgls_treesitter/src/context/mod.rs +++ b/crates/pgls_treesitter/src/context/mod.rs @@ -992,4 +992,25 @@ mod tests { ); } } + + #[test] + fn does_not_crash_on_incomplete_function_body() { + // Regression test for #704: incomplete SQL with unhandled node kinds + // (function_arguments, type, function_language, object_reference, comment, + // keyword_begin, keyword_end, :=) must not panic. + let sql = "DECLARE\nBEGIN\n po_1 := length(pi_1);\nEND;"; + + let tree = get_tree(sql); + + // Try multiple positions across the statement to cover all node kinds + for pos in 0..sql.len() { + let params = TreeSitterContextParams { + position: (pos as u32).into(), + text: sql, + tree: &tree, + }; + // must not panic + let _ = TreesitterContext::new(params); + } + } }