From 8113c70e4a162f28a6fcfb4755e6a8d59977e5b5 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Tue, 19 May 2026 12:34:48 +0200 Subject: [PATCH] =?UTF-8?q?fix(lsp):=20walk=20all=20expression=20variants?= =?UTF-8?q?=20when=20collecting=20hints=20=F0=9F=94=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AST walker had a `_ => {}` catch-all that skipped Call, List, Tuple, Map, Logical, Assignment, OpAssignment, and range expressions, so any lambda nested inside those never fired `on_function_declaration` — losing its return-type inlay hint. The analyser was already computing the type; the LSP just never reached the node. Make the match exhaustive so a new Expression variant produces a compile error instead of a silent regression, and walk into Lvalue::Index for the same reason. Co-Authored-By: Claude Opus 4.7 (1M context) --- ndc_lsp/src/features/inlay_hints.rs | 16 ++++++++ ndc_lsp/src/visitor.rs | 59 ++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/ndc_lsp/src/features/inlay_hints.rs b/ndc_lsp/src/features/inlay_hints.rs index 339e8c94..a01883ba 100644 --- a/ndc_lsp/src/features/inlay_hints.rs +++ b/ndc_lsp/src/features/inlay_hints.rs @@ -174,4 +174,20 @@ mod tests { ) ); } + + #[test] + fn lambda_inside_call_gets_return_type_inlay() { + let info = collect_hints("[1,2,3].map(fn(y) => y / 2.0);"); + assert!(info.hints.iter().any( + |hint| matches!(&hint.label, InlayHintLabel::String(label) if label.starts_with(" -> ")) + )); + } + + #[test] + fn lambda_inside_list_literal_gets_return_type_inlay() { + let info = collect_hints("let fns = [fn(x) => x + 1];"); + assert!(info.hints.iter().any( + |hint| matches!(&hint.label, InlayHintLabel::String(label) if label.starts_with(" -> ")) + )); + } } diff --git a/ndc_lsp/src/visitor.rs b/ndc_lsp/src/visitor.rs index 417ff525..c87c1e6a 100644 --- a/ndc_lsp/src/visitor.rs +++ b/ndc_lsp/src/visitor.rs @@ -119,7 +119,59 @@ fn walk_expression(visitor: &mut impl AstVisitor, expr: &ExpressionLocation) { } } Expression::Return { value } => walk_expression(visitor, value), - _ => {} + Expression::Logical { left, right, .. } => { + walk_expression(visitor, left); + walk_expression(visitor, right); + } + Expression::Assignment { l_value, r_value } + | Expression::OpAssignment { + l_value, r_value, .. + } => { + walk_lvalue(visitor, l_value, false); + walk_expression(visitor, r_value); + } + Expression::Call { + function, + arguments, + } => { + walk_expression(visitor, function); + for arg in arguments { + walk_expression(visitor, arg); + } + } + Expression::Tuple { values } | Expression::List { values } => { + for v in values { + walk_expression(visitor, v); + } + } + Expression::Map { values, default } => { + for (key, value) in values { + walk_expression(visitor, key); + if let Some(v) = value { + walk_expression(visitor, v); + } + } + if let Some(d) = default { + walk_expression(visitor, d); + } + } + Expression::RangeInclusive { start, end } | Expression::RangeExclusive { start, end } => { + if let Some(s) = start { + walk_expression(visitor, s); + } + if let Some(e) = end { + walk_expression(visitor, e); + } + } + Expression::Identifier { .. } + | Expression::BoolLiteral(_) + | Expression::StringLiteral(_) + | Expression::Int64Literal(_) + | Expression::Float64Literal(_) + | Expression::BigIntLiteral(_) + | Expression::ComplexLiteral(_) + | Expression::Break + | Expression::Continue => {} } } @@ -138,6 +190,9 @@ fn walk_lvalue(visitor: &mut impl AstVisitor, lvalue: &Lvalue, has_annotation: b walk_lvalue(visitor, lv, has_annotation); } } - Lvalue::Index { .. } => {} + Lvalue::Index { value, index, .. } => { + walk_expression(visitor, value); + walk_expression(visitor, index); + } } }