Skip to main content

shape_lsp/completion/
mod.rs

1//! Code completion provider for Shape
2//!
3//! Provides intelligent autocomplete suggestions based on context.
4//! All language information comes from shape-core metadata API (single source of truth).
5
6// Module declarations
7pub mod annotations;
8pub mod docs;
9pub mod functions;
10pub mod imports;
11pub mod inference;
12pub mod methods;
13pub mod providers;
14pub mod snippets;
15pub mod stdlib_methods;
16pub mod types;
17
18// Re-exports for backward compatibility
19pub use annotations::{annotation_completions, enum_value_completions, symbols_with_annotation};
20pub use functions::{
21    builtin_function_completions, comptime_builtin_function_completions,
22    function_argument_completions, function_completion_item, keyword_completions,
23    object_property_name_completions, object_property_value_completions,
24};
25pub use inference::{infer_param_types, infer_types, infer_types_with_context, type_to_string};
26pub use methods::{
27    extract_option_inner, extract_result_inner, method_completion_item, option_method_completions,
28    result_method_completions,
29};
30pub use providers::provider_completions;
31pub use snippets::{create_snippet, snippet_completions};
32pub use types::{
33    is_column_type, pipe_target_completions, property_completion_item, property_completions,
34    resolve_base_type, resolve_object_type, resolve_property_type, type_completions,
35};
36
37use crate::annotation_discovery::AnnotationDiscovery;
38use crate::context::{CompletionContext, analyze_context, is_inside_interpolation_expression};
39use crate::grammar_completion::get_grammar_completions;
40use crate::module_cache::ModuleCache;
41use crate::symbols::{SymbolKind, extract_symbols, symbols_to_completions};
42use crate::trait_lookup::resolve_trait_definition;
43use crate::type_inference::{
44    MethodCompletionInfo, extract_struct_fields, extract_type_methods, unified_metadata,
45};
46use crate::util::position_to_offset;
47use shape_ast::ast::{Item, MethodDef, Program, Span, Statement, TypeName};
48use shape_ast::parse_program_resilient;
49use shape_ast::parser::parse_program;
50use std::collections::{HashMap, HashSet};
51use std::path::Path;
52use tower_lsp_server::ls_types::{CompletionItem, CompletionItemKind, Position};
53
54/// Generate completion items for a given position in a document
55/// Returns (completions, updated_symbols, updated_types) where updated_symbols is Some if parsing succeeded
56pub fn get_completions(
57    text: &str,
58    position: Position,
59    cached_symbols: &[crate::symbols::SymbolInfo],
60    cached_types: &HashMap<String, String>,
61) -> (
62    Vec<CompletionItem>,
63    Option<Vec<crate::symbols::SymbolInfo>>,
64    Option<HashMap<String, String>>,
65) {
66    get_completions_with_context(
67        text,
68        position,
69        cached_symbols,
70        cached_types,
71        None,
72        None,
73        None,
74    )
75}
76
77/// Generate completion items with module-resolution context for imports/exports.
78pub fn get_completions_with_context(
79    text: &str,
80    position: Position,
81    cached_symbols: &[crate::symbols::SymbolInfo],
82    cached_types: &HashMap<String, String>,
83    module_cache: Option<&ModuleCache>,
84    current_file: Option<&Path>,
85    workspace_root: Option<&Path>,
86) -> (
87    Vec<CompletionItem>,
88    Option<Vec<crate::symbols::SymbolInfo>>,
89    Option<HashMap<String, String>>,
90) {
91    let mut completions = Vec::new();
92    let cursor_offset = position_to_offset(text, position);
93    let mut parsed_program = None;
94
95    // Analyze context to determine what completions to show
96    let context = analyze_context(text, position);
97
98    // Parse the document to extract user-defined symbols and annotations
99    let (
100        user_symbols,
101        updated_symbols,
102        updated_types,
103        annotation_discovery,
104        struct_fields,
105        impl_methods,
106        named_impls,
107        receiver_type_at_cursor,
108    ) = if let Ok(mut program) = parse_program(text) {
109        let analysis = analyze_parsed_program(
110            &mut program,
111            module_cache,
112            current_file,
113            workspace_root,
114            text,
115            cursor_offset,
116        );
117        parsed_program = Some(program);
118        analysis
119    } else {
120        // Strict parse failed — try resilient parse for partial recovery
121        let partial = parse_program_resilient(text);
122        if !partial.items.is_empty() {
123            let mut program = partial.into_program();
124            let analysis = analyze_parsed_program(
125                &mut program,
126                module_cache,
127                current_file,
128                workspace_root,
129                text,
130                cursor_offset,
131            );
132            parsed_program = Some(program);
133            analysis
134        } else {
135            // No items recovered — fall back to cached state
136            (
137                cached_symbols.to_vec(),
138                None,
139                None,
140                AnnotationDiscovery::new(),
141                HashMap::new(),
142                HashMap::new(),
143                extract_named_impl_names_fallback(text),
144                None,
145            )
146        }
147    };
148
149    let mut type_context = updated_types
150        .clone()
151        .unwrap_or_else(|| cached_types.clone());
152    if is_inside_interpolation_expression(text, position) {
153        if let Some(receiver_type) = receiver_type_at_cursor {
154            type_context.insert("self".to_string(), receiver_type);
155        }
156    }
157
158    if is_using_impl_selector_context(text, position) {
159        completions.extend(named_impl_selector_completions(&named_impls));
160        return (completions, updated_symbols, updated_types);
161    }
162
163    match context {
164        CompletionContext::ImportModule => {
165            completions.extend(imports::import_module_completions_with_context(
166                current_file,
167                workspace_root,
168                Some(text),
169            ));
170        }
171        CompletionContext::FromModule => {
172            completions.extend(imports::from_module_completions_with_context(
173                module_cache,
174                current_file,
175                workspace_root,
176            ));
177        }
178        CompletionContext::FromModulePartial { prefix } => {
179            completions.extend(imports::hierarchical_module_completions_with_context(
180                &prefix,
181                module_cache,
182                current_file,
183                workspace_root,
184            ));
185        }
186        CompletionContext::ImportItems { module } => {
187            let module_exports = imports::module_export_completions_with_context(
188                &module,
189                current_file,
190                workspace_root,
191                Some(text),
192            );
193            if !module_exports.is_empty() {
194                completions.extend(module_exports);
195            } else {
196                // Fall back to import-path module exports via ModuleCache resolution.
197                completions.extend(imports::import_path_export_completions_with_context(
198                    &module,
199                    module_cache,
200                    current_file,
201                    workspace_root,
202                ));
203            }
204        }
205        CompletionContext::PropertyAccess { object } => {
206            // If the object is a extension module, show module exports
207            if imports::is_module_namespace_with_context(
208                &object,
209                current_file,
210                workspace_root,
211                Some(text),
212            ) {
213                completions.extend(imports::module_member_completions_with_context(
214                    &object,
215                    current_file,
216                    workspace_root,
217                    Some(text),
218                ));
219                return (completions, updated_symbols, updated_types);
220            }
221            // Show properties based on object type
222            completions.extend(property_completions(
223                &object,
224                &type_context,
225                &struct_fields,
226                &impl_methods,
227            ));
228        }
229        CompletionContext::PatternReference => {
230            // Show all user-defined functions as potential pattern references
231            let function_symbols: Vec<_> = user_symbols
232                .iter()
233                .filter(|s| s.kind == SymbolKind::Function)
234                .cloned()
235                .collect();
236            completions.extend(symbols_to_completions(&function_symbols));
237        }
238        CompletionContext::TypeAnnotation => {
239            // Show type names
240            completions.extend(type_completions());
241        }
242        CompletionContext::FunctionCall {
243            function,
244            arg_context,
245        } => {
246            // Inside function call - intelligent argument-specific completions
247            completions.extend(function_argument_completions(
248                &user_symbols,
249                &function,
250                &arg_context,
251            ));
252        }
253        CompletionContext::Annotation => {
254            // Show discovered annotations after "@"
255            completions.extend(annotation_completions(
256                &annotation_discovery,
257                parsed_program.as_ref(),
258                module_cache,
259                current_file,
260                workspace_root,
261            ));
262        }
263        CompletionContext::AnnotationArgs { annotation } => {
264            // Show annotation-specific argument completions
265            // If the annotation is known, show its parameter names first
266            if let Some(info) = annotation_discovery.get(&annotation) {
267                for param in &info.params {
268                    completions.push(CompletionItem {
269                        label: param.clone(),
270                        kind: Some(CompletionItemKind::VARIABLE),
271                        detail: Some(format!("@{} parameter", annotation)),
272                        ..Default::default()
273                    });
274                }
275            }
276            // Also show all symbols as fallback
277            completions.extend(symbols_to_completions(&user_symbols));
278        }
279        CompletionContext::ComptimeBlock => {
280            // Inside `comptime { }` — offer comptime builtins first, then normal completions
281            completions.extend(comptime_builtin_function_completions());
282            completions.extend(symbols_to_completions(&user_symbols));
283            completions.extend(builtin_function_completions());
284        }
285        CompletionContext::ExprAnnotation => {
286            // After `@` in expression position — same as item-level annotations
287            completions.extend(annotation_completions(
288                &annotation_discovery,
289                parsed_program.as_ref(),
290                module_cache,
291                current_file,
292                workspace_root,
293            ));
294        }
295        CompletionContext::DocTag { prefix } => {
296            completions.extend(docs::doc_tag_completions(&prefix));
297        }
298        CompletionContext::DocParamName { prefix } => {
299            if let (Some(program), Some(offset)) = (parsed_program.as_ref(), cursor_offset) {
300                completions.extend(docs::doc_param_completions(program, offset, &prefix));
301            }
302        }
303        CompletionContext::DocTypeParamName { prefix } => {
304            if let (Some(program), Some(offset)) = (parsed_program.as_ref(), cursor_offset) {
305                completions.extend(docs::doc_type_param_completions(program, offset, &prefix));
306            }
307        }
308        CompletionContext::DocLinkTarget { prefix } => {
309            if let Some(program) = parsed_program.as_ref() {
310                completions.extend(docs::doc_link_completions(
311                    program,
312                    &prefix,
313                    module_cache,
314                    current_file,
315                    workspace_root,
316                ));
317            }
318        }
319        CompletionContext::PipeTarget { pipe_input_type } => {
320            completions.extend(types::pipe_target_completions(
321                pipe_input_type.as_deref(),
322                &type_context,
323                &impl_methods,
324            ));
325            // Also show user-defined functions
326            let func_symbols: Vec<_> = user_symbols
327                .iter()
328                .filter(|s| s.kind == crate::symbols::SymbolKind::Function)
329                .cloned()
330                .collect();
331            completions.extend(symbols_to_completions(&func_symbols));
332            // Show builtin functions
333            completions.extend(builtin_function_completions());
334        }
335        CompletionContext::ImplBlock {
336            trait_name,
337            target_type: _,
338            existing_methods,
339        } => {
340            // Inside an impl block — suggest unimplemented trait methods
341            completions.extend(impl_block_completions(
342                text,
343                &trait_name,
344                &existing_methods,
345                module_cache,
346                current_file,
347                workspace_root,
348            ));
349        }
350        CompletionContext::TypeAliasOverride { base_type } => {
351            // Inside `type X = Y { | }` — suggest comptime fields from base type
352            completions.extend(comptime_field_override_completions(
353                &struct_fields,
354                &base_type,
355            ));
356        }
357        CompletionContext::JoinStrategy => {
358            // After `await join ` — suggest join strategies
359            completions.extend(join_strategy_completions());
360        }
361        CompletionContext::JoinBody { strategy } => {
362            // Inside `join <strategy> { | }` — suggest labeled branch snippets
363            completions.extend(join_branch_completions(&strategy));
364            // Also show user symbols and builtins for branch expressions
365            completions.extend(symbols_to_completions(&user_symbols));
366            completions.extend(builtin_function_completions());
367        }
368        CompletionContext::TraitBound => {
369            // In trait bound position `<T: |>` — suggest known trait names
370            completions.extend(trait_bound_completions(text));
371        }
372        CompletionContext::InterpolationFormatSpec { spec_prefix } => {
373            completions.extend(interpolation_format_spec_completions(&spec_prefix));
374        }
375        _ => {
376            // General context - show all completions
377            // 1. Grammar-driven completions (based on parser expectations)
378            // Truncate text at cursor to simulate "typing here"
379            let byte_offset = position_to_offset(text, position);
380            if let Some(offset) = byte_offset {
381                let truncated = &text[..offset];
382                completions.extend(get_grammar_completions(truncated));
383            }
384
385            // 2. Standard completions
386            completions.extend(all_completions(&user_symbols));
387        }
388    }
389
390    // De-duplicate cross-source completion labels before ranking.
391    dedupe_completion_items(&mut completions);
392
393    // Type-aware scoring: boost completions matching expected type
394    if let Some(expected) = expected_type_at_cursor(text, position, &type_context) {
395        boost_completions_by_type(&mut completions, &expected, &type_context);
396    }
397
398    (completions, updated_symbols, updated_types)
399}
400
401fn is_using_impl_selector_context(text: &str, position: Position) -> bool {
402    let Some(offset) = position_to_offset(text, position) else {
403        return false;
404    };
405    let prefix = &text[..offset];
406    let line_prefix = prefix.rsplit('\n').next().unwrap_or(prefix);
407
408    let Some(using_idx) = line_prefix.rfind("using") else {
409        return false;
410    };
411
412    let before = &line_prefix[..using_idx];
413    if before
414        .chars()
415        .last()
416        .is_some_and(|c| c.is_ascii_alphanumeric() || c == '_')
417    {
418        return false;
419    }
420
421    let after = &line_prefix[using_idx + "using".len()..];
422    if !after
423        .chars()
424        .all(|c| c.is_ascii_whitespace() || c.is_ascii_alphanumeric() || c == '_')
425    {
426        return false;
427    }
428
429    after.chars().any(|c| c.is_ascii_whitespace())
430}
431
432fn named_impl_selector_completions(named_impls: &[String]) -> Vec<CompletionItem> {
433    named_impls
434        .iter()
435        .map(|name| CompletionItem {
436            label: name.clone(),
437            kind: Some(CompletionItemKind::REFERENCE),
438            detail: Some("Named trait implementation".to_string()),
439            documentation: Some(tower_lsp_server::ls_types::Documentation::String(
440                "Select self named implementation with `expr using ImplName`.".to_string(),
441            )),
442            ..Default::default()
443        })
444        .collect()
445}
446
447/// Shared analysis for a parsed program (used by both strict and resilient parse paths).
448#[allow(clippy::type_complexity)]
449fn analyze_parsed_program(
450    program: &mut Program,
451    module_cache: Option<&ModuleCache>,
452    current_file: Option<&Path>,
453    workspace_root: Option<&Path>,
454    text: &str,
455    cursor_offset: Option<usize>,
456) -> (
457    Vec<crate::symbols::SymbolInfo>,
458    Option<Vec<crate::symbols::SymbolInfo>>,
459    Option<HashMap<String, String>>,
460    AnnotationDiscovery,
461    HashMap<String, Vec<(String, String)>>,
462    HashMap<String, Vec<MethodCompletionInfo>>,
463    Vec<String>,
464    Option<String>,
465) {
466    // Desugar query syntax before analysis
467    shape_ast::transform::desugar_program(program);
468    let symbols = extract_symbols(program);
469    let mut inferred_types =
470        infer_types_with_context(program, current_file, workspace_root, Some(text));
471    if let Some(types) = inferred_types.as_mut() {
472        let param_types = infer_param_types(program, types);
473        types.extend(param_types);
474    }
475
476    // Discover annotations from the program
477    let mut ann_discovery = AnnotationDiscovery::new();
478    ann_discovery.discover_from_program(program);
479    if let (Some(cache), Some(file_path)) = (module_cache, current_file) {
480        ann_discovery.discover_from_imports_with_cache(program, file_path, cache, workspace_root);
481    } else {
482        ann_discovery.discover_from_imports(program);
483    }
484
485    // Extract struct type fields for property completions
486    let fields = extract_struct_fields(program);
487
488    // Extract methods from impl/extend/trait blocks
489    let mut impl_meths = extract_type_methods(program);
490    let mut named_impl_names: Vec<String> = program
491        .items
492        .iter()
493        .filter_map(|item| match item {
494            Item::Impl(impl_block, _) => impl_block.impl_name.clone(),
495            _ => None,
496        })
497        .collect();
498    named_impl_names.sort();
499    named_impl_names.dedup();
500
501    // Merge methods from extension .shape sources (e.g., duckdb.shape, openapi.shape)
502    for (type_name, ext_methods) in imports::extension_type_methods() {
503        let entry = impl_meths.entry(type_name).or_default();
504        for m in ext_methods {
505            if !entry.iter().any(|existing| existing.name == m.name) {
506                entry.push(m);
507            }
508        }
509    }
510
511    // Merge methods from Shape stdlib `.shape` sources (e.g., extend Vec<T>
512    // in stdlib-src/core/vec.shape). Cached process-wide so this is a
513    // single hashmap clone per request, not a re-parse. Without this
514    // merge, completion after `xs.` on `let xs = [1,2,3]` would never
515    // surface `map`/`filter`/etc., because those methods live in pure
516    // Shape source and the runtime `MethodTable` only carries universal
517    // methods (`toString`, `type`). See `stdlib_methods.rs` for
518    // background. LSP-C audit §A.1 / §D regression close.
519    for (type_name, std_methods) in stdlib_methods::stdlib_type_methods() {
520        let entry = impl_meths.entry(type_name.clone()).or_default();
521        for m in std_methods {
522            if !entry.iter().any(|existing| existing.name == m.name) {
523                entry.push(m.clone());
524            }
525        }
526    }
527
528    (
529        symbols.clone(),
530        Some(symbols),
531        inferred_types,
532        ann_discovery,
533        fields,
534        impl_meths,
535        named_impl_names,
536        cursor_offset.and_then(|offset| receiver_type_for_offset(program, offset)),
537    )
538}
539
540fn extract_named_impl_names_fallback(text: &str) -> Vec<String> {
541    let tokens: Vec<&str> = text
542        .split(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
543        .filter(|s| !s.is_empty())
544        .collect();
545    let mut out = Vec::new();
546
547    for i in 0..tokens.len() {
548        if tokens[i] != "impl" {
549            continue;
550        }
551        // Scan a bounded lookahead for `as Name`.
552        let upper = (i + 16).min(tokens.len());
553        for j in (i + 1)..upper {
554            if tokens[j] == "as" && j + 1 < tokens.len() {
555                out.push(tokens[j + 1].to_string());
556                break;
557            }
558        }
559    }
560
561    out.sort();
562    out.dedup();
563    out
564}
565
566/// Determine the expected type at cursor position from surrounding context
567fn expected_type_at_cursor(
568    text: &str,
569    position: Position,
570    type_context: &HashMap<String, String>,
571) -> Option<String> {
572    let lines: Vec<&str> = text.lines().collect();
573    let line_idx = position.line as usize;
574    if line_idx >= lines.len() {
575        return None;
576    }
577    let line = lines[line_idx];
578    let char_pos = (position.character as usize).min(line.len());
579    let before = &line[..char_pos];
580    let trimmed = before.trim();
581
582    // Case 1: `let x: TypeName = |` — assignment to typed variable
583    // Look for `: TypeName =` pattern before cursor
584    if let Some(eq_pos) = trimmed.rfind('=') {
585        // Make sure self isn't `==` or `!=` or `<=` or `>=`
586        let is_comparison = eq_pos > 0
587            && matches!(
588                trimmed.as_bytes().get(eq_pos.wrapping_sub(1)),
589                Some(b'=' | b'!' | b'<' | b'>')
590            )
591            || matches!(trimmed.as_bytes().get(eq_pos + 1), Some(b'='));
592        if !is_comparison {
593            let before_eq = trimmed[..eq_pos].trim();
594            if let Some(colon_pos) = before_eq.rfind(':') {
595                let type_str = before_eq[colon_pos + 1..].trim();
596                if !type_str.is_empty()
597                    && type_str.chars().next().map_or(false, |c| c.is_alphabetic())
598                {
599                    return Some(normalize_type(type_str));
600                }
601            }
602        }
603    }
604
605    // Case 2: `return |` inside a function with return type annotation
606    if trimmed.starts_with("return") {
607        // Walk backward to find enclosing function with return type
608        for i in (0..line_idx).rev() {
609            let prev_line = lines[i].trim();
610            // Look for function declaration with return type
611            if prev_line.starts_with("fn ") || prev_line.starts_with("function ") {
612                if let Some(arrow_pos) = prev_line.rfind("->") {
613                    let ret_type = prev_line[arrow_pos + 2..]
614                        .trim()
615                        .trim_end_matches('{')
616                        .trim();
617                    if !ret_type.is_empty() {
618                        return Some(normalize_type(ret_type));
619                    }
620                }
621                // Also check for `: ReturnType {` pattern
622                if let Some(colon_pos) = prev_line.rfind(')') {
623                    let after_paren = prev_line[colon_pos + 1..].trim();
624                    if let Some(rest) = after_paren.strip_prefix(':') {
625                        let ret_type = rest.trim().trim_end_matches('{').trim();
626                        if !ret_type.is_empty() {
627                            return Some(normalize_type(ret_type));
628                        }
629                    }
630                }
631                break;
632            }
633        }
634    }
635
636    // Case 3: Binary operator with known left-hand type: `x + |` where x is number
637    if let Some(op_pos) = trimmed.rfind(|c: char| matches!(c, '+' | '-' | '*' | '/')) {
638        let before_op = trimmed[..op_pos].trim();
639        // Extract the identifier before the operator
640        let ident = before_op.split_whitespace().last().unwrap_or("");
641        if let Some(t) = type_context.get(ident) {
642            return Some(normalize_type(t));
643        }
644    }
645
646    // Case 4: Function call argument `foo(|)` or `foo(a, |)`
647    // Check if we're inside parens and can identify the function
648    if let Some(paren_pos) = find_unclosed_paren(trimmed) {
649        let before_paren = trimmed[..paren_pos].trim();
650        let func_name = before_paren
651            .split(|c: char| !c.is_alphanumeric() && c != '_')
652            .last()
653            .unwrap_or("");
654        if !func_name.is_empty() {
655            // Count commas to determine argument index
656            let args_text = &trimmed[paren_pos + 1..];
657            let arg_idx = args_text.chars().filter(|&c| c == ',').count();
658            // Look up function parameter types from metadata
659            let metadata = unified_metadata();
660            if let Some(func) = metadata.get_function(func_name) {
661                if arg_idx < func.parameters.len() {
662                    let param = &func.parameters[arg_idx];
663                    if !param.param_type.is_empty() {
664                        return Some(normalize_type(&param.param_type));
665                    }
666                }
667            }
668        }
669    }
670
671    None
672}
673
674/// Find the position of the last unclosed '(' before cursor
675fn find_unclosed_paren(text: &str) -> Option<usize> {
676    let mut depth = 0i32;
677    let mut last_open = None;
678    for (i, c) in text.char_indices() {
679        match c {
680            '(' => {
681                depth += 1;
682                last_open = Some(i);
683            }
684            ')' => {
685                depth -= 1;
686                if depth < 0 {
687                    depth = 0;
688                }
689            }
690            _ => {}
691        }
692    }
693    if depth > 0 { last_open } else { None }
694}
695
696/// Normalize a type name for comparison (lowercase, strip whitespace)
697fn normalize_type(t: &str) -> String {
698    t.trim().to_lowercase()
699}
700
701/// Check if two type names are compatible
702fn types_compatible(actual: &str, expected: &str) -> TypeMatch {
703    let a = actual.to_lowercase();
704    let e = expected.to_lowercase();
705
706    if a == e {
707        return TypeMatch::Exact;
708    }
709
710    // Compatible numeric types
711    let numeric = ["number", "int", "decimal", "float", "f64", "i64"];
712    if numeric.contains(&a.as_str()) && numeric.contains(&e.as_str()) {
713        return TypeMatch::Compatible;
714    }
715
716    // Unknown/inferred types match everything
717    if a == "_" || e == "_" || a == "unknown" || e == "unknown" {
718        return TypeMatch::Compatible;
719    }
720
721    TypeMatch::Incompatible
722}
723
724#[derive(Debug, PartialEq)]
725enum TypeMatch {
726    Exact,
727    Compatible,
728    Incompatible,
729}
730
731/// Boost completion items whose result type matches the expected type
732fn boost_completions_by_type(
733    completions: &mut [CompletionItem],
734    expected: &str,
735    type_context: &HashMap<String, String>,
736) {
737    for item in completions.iter_mut() {
738        // Determine the result type of self completion item
739        let result_type = infer_completion_result_type(item, type_context);
740        let priority = match result_type {
741            Some(ref t) => match types_compatible(t, expected) {
742                TypeMatch::Exact => "0", // highest priority
743                TypeMatch::Compatible => "1",
744                TypeMatch::Incompatible => "2",
745            },
746            None => "2", // unknown type = lowest priority
747        };
748
749        // Prepend priority to existing sort_text (or label)
750        let base = item.sort_text.as_deref().unwrap_or(&item.label);
751        item.sort_text = Some(format!("{}_{}", priority, base));
752    }
753}
754
755/// Infer the result type of a completion item from its metadata
756fn infer_completion_result_type(
757    item: &CompletionItem,
758    type_context: &HashMap<String, String>,
759) -> Option<String> {
760    // Check the detail field for type info (e.g., "-> number", ": string")
761    if let Some(detail) = &item.detail {
762        // Functions: look for "-> RetType" in detail
763        if let Some(arrow_pos) = detail.rfind("->") {
764            let ret = detail[arrow_pos + 2..].trim();
765            if !ret.is_empty() {
766                return Some(normalize_type(ret));
767            }
768        }
769        // Variables: look for ": Type" in detail
770        if let Some(colon_pos) = detail.rfind(':') {
771            let t = detail[colon_pos + 1..].trim();
772            if !t.is_empty() && !t.contains(' ') {
773                return Some(normalize_type(t));
774            }
775        }
776    }
777
778    // For variable completions, check type_context
779    if item.kind == Some(CompletionItemKind::VARIABLE) {
780        return type_context.get(&item.label).map(|t| normalize_type(t));
781    }
782
783    // For function completions, check metadata
784    if item.kind == Some(CompletionItemKind::FUNCTION) {
785        let metadata = unified_metadata();
786        if let Some(func) = metadata.get_function(&item.label) {
787            if !func.return_type.is_empty() {
788                return Some(normalize_type(&func.return_type));
789            }
790        }
791    }
792
793    None
794}
795
796/// Get completions for inside an impl block — suggest unimplemented trait methods
797fn impl_block_completions(
798    text: &str,
799    trait_name: &str,
800    existing_methods: &[String],
801    module_cache: Option<&ModuleCache>,
802    current_file: Option<&Path>,
803    workspace_root: Option<&Path>,
804) -> Vec<CompletionItem> {
805    use tower_lsp_server::ls_types::{Documentation, InsertTextFormat, MarkupContent, MarkupKind};
806
807    let program = match parse_program(text) {
808        Ok(p) => p,
809        Err(_) => return Vec::new(),
810    };
811
812    let Some(resolved_trait) = resolve_trait_definition(
813        &program,
814        trait_name,
815        module_cache,
816        current_file,
817        workspace_root,
818    ) else {
819        // Fallback for builtin traits not defined in user source
820        if trait_name == "Content" && !existing_methods.contains(&"render".to_string()) {
821            return vec![CompletionItem {
822                label: "render".to_string(),
823                kind: Some(CompletionItemKind::METHOD),
824                detail: Some("trait Content method".to_string()),
825                documentation: Some(tower_lsp_server::ls_types::Documentation::MarkupContent(
826                    tower_lsp_server::ls_types::MarkupContent {
827                        kind: tower_lsp_server::ls_types::MarkupKind::Markdown,
828                        value: "Implement `render` from trait `Content`\n\n```\nmethod render() -> ContentNode;\n```\n\nReturn a `ContentNode` representing this value's rich content.".to_string(),
829                    },
830                )),
831                insert_text: Some("method render() -> ContentNode {\n    Content.text(f\"${1:self}\")\n}".to_string()),
832                insert_text_format: Some(InsertTextFormat::SNIPPET),
833                ..CompletionItem::default()
834            }];
835        }
836        return Vec::new();
837    };
838
839    // Extract unimplemented methods from trait members
840    let mut completions = Vec::new();
841    for member in &resolved_trait.trait_def.members {
842        match member {
843            shape_ast::ast::TraitMember::Required(shape_ast::ast::TraitMemberSignature::Method {
844                name,
845                params,
846                return_type,
847                ..
848            }) => {
849                // Skip methods already implemented
850                if existing_methods.iter().any(|m| m == name) {
851                    continue;
852                }
853
854                // Build parameter list for snippet
855                let param_names: Vec<String> = params
856                    .iter()
857                    .map(|p| p.name.clone().unwrap_or_else(|| "_".to_string()))
858                    .collect();
859
860                let return_type_str = crate::type_inference::type_annotation_to_string(return_type)
861                    .unwrap_or_else(|| "_".to_string());
862
863                // Build snippet: method name(params) { $0 }
864                let snippet_params: Vec<String> = param_names
865                    .iter()
866                    .enumerate()
867                    .map(|(i, n)| format!("${{{}:{}}}", i + 1, n))
868                    .collect();
869
870                let snippet = format!(
871                    "method {}({}) {{\n    $0\n}}",
872                    name,
873                    snippet_params.join(", ")
874                );
875
876                let signature =
877                    format!("{}({}): {}", name, param_names.join(", "), return_type_str);
878
879                completions.push(CompletionItem {
880                    label: name.clone(),
881                    kind: Some(CompletionItemKind::METHOD),
882                    detail: Some(format!("trait {} method", trait_name)),
883                    documentation: Some(Documentation::MarkupContent(MarkupContent {
884                        kind: MarkupKind::Markdown,
885                        value: format!(
886                            "Implement `{}` from trait `{}`\n\n```\n{}\n```",
887                            name, trait_name, signature
888                        ),
889                    })),
890                    insert_text: Some(snippet),
891                    insert_text_format: Some(InsertTextFormat::SNIPPET),
892                    ..CompletionItem::default()
893                });
894            }
895            shape_ast::ast::TraitMember::Default(method_def) => {
896                // Default methods can be overridden — suggest them with "(default)" marker
897                if existing_methods.iter().any(|m| m == &method_def.name) {
898                    continue;
899                }
900
901                let param_names: Vec<String> = method_def
902                    .params
903                    .iter()
904                    .map(|p| p.simple_name().unwrap_or("_").to_string())
905                    .collect();
906
907                let return_type_str = method_def
908                    .return_type
909                    .as_ref()
910                    .and_then(|rt| crate::type_inference::type_annotation_to_string(rt))
911                    .unwrap_or_else(|| "_".to_string());
912
913                let snippet_params: Vec<String> = param_names
914                    .iter()
915                    .enumerate()
916                    .map(|(i, n)| format!("${{{}:{}}}", i + 1, n))
917                    .collect();
918
919                let snippet = format!(
920                    "method {}({}) {{\n    $0\n}}",
921                    method_def.name,
922                    snippet_params.join(", ")
923                );
924
925                let signature = format!(
926                    "{}({}): {}",
927                    method_def.name,
928                    param_names.join(", "),
929                    return_type_str
930                );
931
932                completions.push(CompletionItem {
933                    label: method_def.name.clone(),
934                    kind: Some(CompletionItemKind::METHOD),
935                    detail: Some(format!("trait {} method (default)", trait_name)),
936                    documentation: Some(Documentation::MarkupContent(MarkupContent {
937                        kind: MarkupKind::Markdown,
938                        value: format!(
939                            "Override default method `{}` from trait `{}`\n\n```\n{}\n```\n\nThis method has a default implementation.",
940                            method_def.name, trait_name, signature
941                        ),
942                    })),
943                    insert_text: Some(snippet),
944                    insert_text_format: Some(InsertTextFormat::SNIPPET),
945                    ..CompletionItem::default()
946                });
947            }
948            _ => {}
949        }
950    }
951
952    completions
953}
954
955/// Get completions for comptime field overrides inside `type X = Y { | }`
956fn comptime_field_override_completions(
957    struct_fields: &HashMap<String, Vec<(String, String)>>,
958    base_type: &str,
959) -> Vec<CompletionItem> {
960    use tower_lsp_server::ls_types::{Documentation, InsertTextFormat};
961
962    let mut completions = Vec::new();
963
964    if let Some(fields) = struct_fields.get(base_type) {
965        for (name, type_str) in fields {
966            // Only show comptime fields — they start with "comptime " in the type string
967            if !type_str.starts_with("comptime ") {
968                continue;
969            }
970            let snippet = format!("{}: ${{1}}", name);
971            completions.push(CompletionItem {
972                label: name.clone(),
973                kind: Some(CompletionItemKind::FIELD),
974                detail: Some(type_str.clone()),
975                documentation: Some(Documentation::String(format!(
976                    "Override comptime field `{}` of type `{}`",
977                    name, base_type
978                ))),
979                insert_text: Some(snippet),
980                insert_text_format: Some(InsertTextFormat::SNIPPET),
981                ..CompletionItem::default()
982            });
983        }
984    }
985
986    completions
987}
988
989/// Get completions for trait names in bound position: `fn foo<T: |>`
990fn trait_bound_completions(text: &str) -> Vec<CompletionItem> {
991    use tower_lsp_server::ls_types::Documentation;
992
993    let mut completions = Vec::new();
994
995    // Try parsing the program first; if that fails (common when the user is
996    // still typing a trait bound like `<T: >`), fall back to text-based trait
997    // name extraction so completions still work for incomplete code.
998    if let Ok(program) = parse_program(text) {
999        for item in &program.items {
1000            if let shape_ast::ast::Item::Trait(trait_def, _) = item {
1001                completions.push(CompletionItem {
1002                    label: trait_def.name.clone(),
1003                    kind: Some(CompletionItemKind::INTERFACE),
1004                    detail: Some("trait".to_string()),
1005                    documentation: Some(Documentation::String(format!(
1006                        "Trait `{}`",
1007                        trait_def.name
1008                    ))),
1009                    ..CompletionItem::default()
1010                });
1011            }
1012        }
1013    }
1014
1015    // Fallback: scan text for `trait <Name> {` declarations
1016    if completions.is_empty() {
1017        for line in text.lines() {
1018            let trimmed = line.trim();
1019            if let Some(rest) = trimmed.strip_prefix("trait ") {
1020                if let Some(name) = rest.split_whitespace().next() {
1021                    // Avoid duplicates from the parsed path
1022                    let name = name.trim_end_matches(|c: char| !c.is_alphanumeric() && c != '_');
1023                    if !name.is_empty() {
1024                        completions.push(CompletionItem {
1025                            label: name.to_string(),
1026                            kind: Some(CompletionItemKind::INTERFACE),
1027                            detail: Some("trait".to_string()),
1028                            documentation: Some(Documentation::String(format!("Trait `{}`", name))),
1029                            ..CompletionItem::default()
1030                        });
1031                    }
1032                }
1033            }
1034        }
1035    }
1036
1037    completions
1038}
1039
1040/// Get all completions (keywords + built-ins + user symbols)
1041fn all_completions(user_symbols: &[crate::symbols::SymbolInfo]) -> Vec<CompletionItem> {
1042    let mut completions = Vec::new();
1043
1044    // Add user-defined symbols first (higher priority)
1045    completions.extend(symbols_to_completions(user_symbols));
1046
1047    // Add keyword completions from metadata
1048    completions.extend(keyword_completions());
1049
1050    // Add built-in function completions from metadata
1051    completions.extend(builtin_function_completions());
1052
1053    completions
1054}
1055
1056fn dedupe_completion_items(items: &mut Vec<CompletionItem>) {
1057    let mut seen = HashSet::new();
1058    items.retain(|item| seen.insert(item.label.clone()));
1059}
1060
1061// extract_struct_fields is imported from crate::type_inference (shared module)
1062
1063/// Get completions for join strategies after `await join `
1064fn join_strategy_completions() -> Vec<CompletionItem> {
1065    vec![
1066        CompletionItem {
1067            label: "all".to_string(),
1068            kind: Some(CompletionItemKind::KEYWORD),
1069            detail: Some("Join strategy".to_string()),
1070            documentation: Some(tower_lsp_server::ls_types::Documentation::String(
1071                "Wait for all branches to complete. Returns a tuple of all results.".to_string(),
1072            )),
1073            ..CompletionItem::default()
1074        },
1075        CompletionItem {
1076            label: "race".to_string(),
1077            kind: Some(CompletionItemKind::KEYWORD),
1078            detail: Some("Join strategy".to_string()),
1079            documentation: Some(tower_lsp_server::ls_types::Documentation::String(
1080                "Return the first branch to complete, cancel the rest.".to_string(),
1081            )),
1082            ..CompletionItem::default()
1083        },
1084        CompletionItem {
1085            label: "any".to_string(),
1086            kind: Some(CompletionItemKind::KEYWORD),
1087            detail: Some("Join strategy".to_string()),
1088            documentation: Some(tower_lsp_server::ls_types::Documentation::String(
1089                "Return the first branch to succeed (non-error), cancel the rest.".to_string(),
1090            )),
1091            ..CompletionItem::default()
1092        },
1093        CompletionItem {
1094            label: "settle".to_string(),
1095            kind: Some(CompletionItemKind::KEYWORD),
1096            detail: Some("Join strategy".to_string()),
1097            documentation: Some(tower_lsp_server::ls_types::Documentation::String(
1098                "Wait for all branches, preserving individual success/error results.".to_string(),
1099            )),
1100            ..CompletionItem::default()
1101        },
1102    ]
1103}
1104
1105/// Get completions for inside a join block body — labeled branch snippets
1106fn join_branch_completions(strategy: &str) -> Vec<CompletionItem> {
1107    use tower_lsp_server::ls_types::{Documentation, InsertTextFormat};
1108
1109    let strategy_hint = match strategy {
1110        "all" => "all branches must complete",
1111        "race" => "first to complete wins",
1112        "any" => "first to succeed wins",
1113        "settle" => "all branches settle (success or error)",
1114        _ => "concurrent branch",
1115    };
1116
1117    vec![
1118        CompletionItem {
1119            label: "label: expr".to_string(),
1120            kind: Some(CompletionItemKind::SNIPPET),
1121            detail: Some(format!("Named branch ({})", strategy_hint)),
1122            documentation: Some(Documentation::String(
1123                "Add a labeled branch to the join expression.\nLabels enable named access to results.".to_string(),
1124            )),
1125            insert_text: Some("${1:name}: ${2:expr}".to_string()),
1126            insert_text_format: Some(InsertTextFormat::SNIPPET),
1127            ..CompletionItem::default()
1128        },
1129        CompletionItem {
1130            label: "@annotation branch".to_string(),
1131            kind: Some(CompletionItemKind::SNIPPET),
1132            detail: Some("Annotated branch".to_string()),
1133            documentation: Some(Documentation::String(
1134                "Add an annotated branch with per-branch configuration.\nExample: @timeout(5s) fetch_data()".to_string(),
1135            )),
1136            insert_text: Some("@${1:annotation} ${2:expr}".to_string()),
1137            insert_text_format: Some(InsertTextFormat::SNIPPET),
1138            ..CompletionItem::default()
1139        },
1140    ]
1141}
1142
1143fn interpolation_format_spec_completions(spec_prefix: &str) -> Vec<CompletionItem> {
1144    use tower_lsp_server::ls_types::{Documentation, InsertTextFormat};
1145
1146    let mut items = vec![
1147        CompletionItem {
1148            label: "fixed(2)".to_string(),
1149            kind: Some(CompletionItemKind::FUNCTION),
1150            detail: Some("Numeric fixed precision format".to_string()),
1151            documentation: Some(Documentation::String(
1152                "Format numeric values with fixed precision.\nExample: `f\"{price:fixed(2)}\"`"
1153                    .to_string(),
1154            )),
1155            insert_text: Some("fixed(${1:2})".to_string()),
1156            insert_text_format: Some(InsertTextFormat::SNIPPET),
1157            ..CompletionItem::default()
1158        },
1159        CompletionItem {
1160            label: "table(...)".to_string(),
1161            kind: Some(CompletionItemKind::FUNCTION),
1162            detail: Some("Typed table formatting".to_string()),
1163            documentation: Some(Documentation::String(
1164                "Render table values with typed options (no stringly keys).\nExample: `table(max_rows=20, align=right, precision=2, border=on)`".to_string(),
1165            )),
1166            insert_text: Some(
1167                "table(max_rows=${1:20}, align=${2:right}, precision=${3:2}, border=${4:on})"
1168                    .to_string(),
1169            ),
1170            insert_text_format: Some(InsertTextFormat::SNIPPET),
1171            ..CompletionItem::default()
1172        },
1173    ];
1174
1175    let trimmed = spec_prefix.trim_start();
1176    if let Some(table_inner) = trimmed.strip_prefix("table(") {
1177        items.extend(table_format_argument_completions(table_inner));
1178    }
1179
1180    items
1181}
1182
1183fn table_format_argument_completions(table_inner: &str) -> Vec<CompletionItem> {
1184    use tower_lsp_server::ls_types::Documentation;
1185
1186    let mut items = Vec::new();
1187    let trailing = table_inner.rsplit(',').next().unwrap_or("").trim_start();
1188
1189    let push_value = |label: &str, detail: &str, doc: &str| CompletionItem {
1190        label: label.to_string(),
1191        kind: Some(CompletionItemKind::ENUM_MEMBER),
1192        detail: Some(detail.to_string()),
1193        documentation: Some(Documentation::String(doc.to_string())),
1194        ..CompletionItem::default()
1195    };
1196
1197    if let Some((key, value_prefix)) = trailing.split_once('=') {
1198        let key = key.trim();
1199        let value_prefix = value_prefix.trim();
1200
1201        let mut push_if_matches = |candidate: CompletionItem| {
1202            if value_prefix.is_empty() || candidate.label.starts_with(value_prefix) {
1203                items.push(candidate);
1204            }
1205        };
1206
1207        match key {
1208            "align" => {
1209                push_if_matches(push_value("left", "Alignment", "Left-aligned cells."));
1210                push_if_matches(push_value("center", "Alignment", "Center-aligned cells."));
1211                push_if_matches(push_value("right", "Alignment", "Right-aligned cells."));
1212            }
1213            "color" => {
1214                for color in [
1215                    "default", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
1216                ] {
1217                    push_if_matches(push_value(color, "Color", "Table color hint."));
1218                }
1219            }
1220            "border" => {
1221                push_if_matches(push_value("on", "Border", "Render table borders."));
1222                push_if_matches(push_value("off", "Border", "Render borderless table."));
1223            }
1224            _ => {}
1225        }
1226
1227        return items;
1228    }
1229
1230    // Key position completions.
1231    let key_prefix = trailing.trim();
1232    let key_item = |label: &str, detail: &str| CompletionItem {
1233        label: label.to_string(),
1234        kind: Some(CompletionItemKind::PROPERTY),
1235        detail: Some(detail.to_string()),
1236        ..CompletionItem::default()
1237    };
1238
1239    for (key, detail) in [
1240        ("max_rows=", "Maximum number of rendered rows"),
1241        ("align=", "Global cell alignment (left|center|right)"),
1242        ("precision=", "Numeric precision for float columns"),
1243        ("color=", "Optional color hint"),
1244        ("border=", "Border mode (on|off)"),
1245    ] {
1246        if key_prefix.is_empty() || key.starts_with(key_prefix) {
1247            items.push(key_item(key, detail));
1248        }
1249    }
1250
1251    items
1252}
1253
1254fn receiver_type_for_offset(program: &Program, offset: usize) -> Option<String> {
1255    for item in &program.items {
1256        match item {
1257            Item::Impl(impl_block, span) => {
1258                if !span_contains_offset(*span, offset) {
1259                    continue;
1260                }
1261                if impl_block
1262                    .methods
1263                    .iter()
1264                    .any(|m| method_body_contains_offset(m, offset))
1265                {
1266                    return Some(type_name_base_name(&impl_block.target_type));
1267                }
1268            }
1269            Item::Extend(extend_stmt, span) => {
1270                if !span_contains_offset(*span, offset) {
1271                    continue;
1272                }
1273                if extend_stmt
1274                    .methods
1275                    .iter()
1276                    .any(|m| method_body_contains_offset(m, offset))
1277                {
1278                    return Some(type_name_base_name(&extend_stmt.type_name));
1279                }
1280            }
1281            _ => {}
1282        }
1283    }
1284    None
1285}
1286
1287fn method_body_contains_offset(method: &MethodDef, offset: usize) -> bool {
1288    method.body.iter().any(|stmt| {
1289        let span = match stmt {
1290            Statement::Return(_, span)
1291            | Statement::Break(span)
1292            | Statement::Continue(span)
1293            | Statement::VariableDecl(_, span)
1294            | Statement::Assignment(_, span)
1295            | Statement::Expression(_, span)
1296            | Statement::For(_, span)
1297            | Statement::While(_, span)
1298            | Statement::If(_, span)
1299            | Statement::Extend(_, span)
1300            | Statement::RemoveTarget(span)
1301            | Statement::SetParamType { span, .. }
1302            | Statement::SetParamValue { span, .. }
1303            | Statement::SetReturnType { span, .. }
1304            | Statement::SetReturnExpr { span, .. }
1305            | Statement::ReplaceBodyExpr { span, .. }
1306            | Statement::ReplaceBody { span, .. }
1307            | Statement::ReplaceModuleExpr { span, .. } => *span,
1308        };
1309        span_contains_offset(span, offset)
1310    })
1311}
1312
1313fn type_name_base_name(type_name: &TypeName) -> String {
1314    match type_name {
1315        TypeName::Simple(name) => name.to_string(),
1316        TypeName::Generic { name, .. } => name.to_string(),
1317    }
1318}
1319
1320fn span_contains_offset(span: Span, offset: usize) -> bool {
1321    span.start <= offset && offset <= span.end
1322}
1323
1324#[cfg(test)]
1325mod tests {
1326    use super::*;
1327    use std::collections::HashMap;
1328
1329    fn completions_for(code: &str, position: Position) -> Vec<CompletionItem> {
1330        let (completions, _, _) = get_completions(code, position, &[], &HashMap::new());
1331        completions
1332    }
1333
1334    #[test]
1335    fn test_keyword_completions() {
1336        let keywords = keyword_completions();
1337        assert!(!keywords.is_empty());
1338
1339        // Check that we have core language keywords (from metadata API)
1340        let labels: Vec<_> = keywords.iter().map(|k| k.label.as_str()).collect();
1341        assert!(labels.contains(&"let"));
1342        assert!(labels.contains(&"const"));
1343        assert!(labels.contains(&"fn"));
1344        assert!(
1345            !labels.contains(&"function"),
1346            "Legacy alias should not be globally suggested"
1347        );
1348        // Note: "pattern" and "strategy" are user-defined annotation names, not language keywords
1349        // Note: Domain-specific keywords like "backtest" are now in stdlib, not core keywords
1350    }
1351
1352    #[test]
1353    fn test_builtin_functions() {
1354        let functions = builtin_function_completions();
1355        assert!(!functions.is_empty());
1356
1357        // Check for core builtins (generic functions, not domain-specific)
1358        let labels: Vec<_> = functions.iter().map(|f| f.label.as_str()).collect();
1359        // Note: Domain-specific functions like sma, ema, rsi, run_simulation are now in stdlib
1360        // Core builtins include math, utility, and time functions
1361        assert!(
1362            labels.contains(&"abs") || labels.contains(&"sqrt") || labels.contains(&"print"),
1363            "Should include core builtin functions"
1364        );
1365    }
1366
1367    #[test]
1368    fn test_get_completions() {
1369        let completions = completions_for(
1370            "",
1371            Position {
1372                line: 0,
1373                character: 0,
1374            },
1375        );
1376
1377        // Should return all completions (keywords + functions + snippets)
1378        assert!(completions.len() > 50);
1379    }
1380
1381    #[test]
1382    fn test_dynamic_variable_completion() {
1383        let code = r#"let myVar = 5;
1384const MY_CONST = 10;
1385
1386"#;
1387        let position = Position {
1388            line: 3,
1389            character: 0,
1390        };
1391        let completions = completions_for(code, position);
1392
1393        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1394        assert!(
1395            labels.contains(&"myVar"),
1396            "Should include user-defined variable"
1397        );
1398        assert!(
1399            labels.contains(&"MY_CONST"),
1400            "Should include user-defined constant"
1401        );
1402    }
1403
1404    #[test]
1405    fn test_dynamic_function_completion() {
1406        let code = r#"function myFunction(x, y) {
1407    return x + y;
1408}
1409
1410"#;
1411        let position = Position {
1412            line: 4,
1413            character: 0,
1414        };
1415        let completions = completions_for(code, position);
1416
1417        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1418        assert!(
1419            labels.contains(&"myFunction"),
1420            "Should include user-defined function"
1421        );
1422    }
1423
1424    #[test]
1425    fn test_doc_tag_completion() {
1426        let code = "/// @pa\nfn add(x: number) -> number { x }\n";
1427        let position = Position {
1428            line: 0,
1429            character: 6,
1430        };
1431        let completions = completions_for(code, position);
1432        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1433        assert!(labels.contains(&"param"));
1434    }
1435
1436    #[test]
1437    fn test_doc_param_completion_uses_attached_function_params() {
1438        let code = "/// Summary.\n/// @param va\nfn add(value: number, scale: number) -> number { value * scale }\n";
1439        let position = Position {
1440            line: 1,
1441            character: 13,
1442        };
1443        let completions = completions_for(code, position);
1444        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1445        assert!(labels.contains(&"value"));
1446        assert!(!labels.contains(&"scale"));
1447    }
1448
1449    #[test]
1450    fn test_interpolation_format_spec_completions_include_fixed_and_table() {
1451        let code = r#"let s = f"value: {price:f}""#;
1452        let pos = Position {
1453            line: 0,
1454            character: code.find("{price:f").unwrap() as u32 + 8,
1455        };
1456        let items = completions_for(code, pos);
1457        let labels: Vec<_> = items.iter().map(|c| c.label.as_str()).collect();
1458        assert!(
1459            labels.contains(&"fixed(2)"),
1460            "expected fixed completion, got {:?}",
1461            labels
1462        );
1463        assert!(
1464            labels.contains(&"table(...)"),
1465            "expected table completion, got {:?}",
1466            labels
1467        );
1468    }
1469
1470    #[test]
1471    fn test_interpolation_table_align_value_completions() {
1472        let code = r#"let s = f"{rows:table(align=)}""#;
1473        let pos = Position {
1474            line: 0,
1475            character: code.find("align=").unwrap() as u32 + 6,
1476        };
1477        let items = completions_for(code, pos);
1478        let labels: Vec<_> = items.iter().map(|c| c.label.as_str()).collect();
1479        assert!(
1480            labels.contains(&"left") && labels.contains(&"right"),
1481            "expected alignment value completions, got {:?}",
1482            labels
1483        );
1484    }
1485
1486    #[test]
1487    fn test_dynamic_pattern_completion() {
1488        let code = r#"function myPattern(candle) {
1489    return candle.close > candle.open;
1490}
1491
1492let x = 1
1493"#;
1494        let position = Position {
1495            line: 5,
1496            character: 0,
1497        };
1498        let completions = completions_for(code, position);
1499
1500        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1501        assert!(
1502            labels.contains(&"myPattern"),
1503            "Should include user-defined pattern. Got: {:?}",
1504            labels
1505        );
1506    }
1507
1508    #[test]
1509    fn test_property_completion_with_typed_variable() {
1510        // Row properties are dynamic based on schema - test with an explicitly typed variable
1511        let code = "type Point { x: number, y: number }\nlet p: Point\np.x\n";
1512        let position = Position {
1513            line: 2,
1514            character: 2,
1515        };
1516        let completions = completions_for(code, position);
1517
1518        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1519        assert!(
1520            labels.contains(&"x"),
1521            "Should include Point field 'x'. Got: {:?}",
1522            labels
1523        );
1524        assert!(
1525            labels.contains(&"y"),
1526            "Should include Point field 'y'. Got: {:?}",
1527            labels
1528        );
1529    }
1530
1531    #[test]
1532    fn test_property_completion_for_self_inside_dollar_interpolation() {
1533        let code = "type User { name: String }\nimpl Display for User as JsonDisplay {\n  method display() { f$\"\"\"{ \"name\": \"${self.na}\" }\"\"\" }\n}\n";
1534        let cursor_offset = code
1535            .find("self.na")
1536            .expect("expected self property access in interpolation")
1537            + "self.".len();
1538        let position = crate::util::offset_to_position(code, cursor_offset);
1539        let completions = completions_for(code, position);
1540        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1541        assert!(
1542            labels.contains(&"name"),
1543            "Expected `name` completion for self receiver in interpolation, got: {:?}",
1544            labels
1545        );
1546    }
1547
1548    #[test]
1549    fn test_struct_chained_property_completion() {
1550        // Test struct chained property completion with user-defined types
1551        let code = "type Summary { total: number, ratio: number }\ntype Result { summary: Summary }\nlet bt: Result\nbt.summary.x\n";
1552        let position = Position {
1553            line: 3,
1554            character: 11,
1555        };
1556        let completions = completions_for(code, position);
1557        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1558        assert!(
1559            labels.contains(&"total"),
1560            "Should include Summary field 'total'. Got: {:?}",
1561            labels
1562        );
1563        assert!(
1564            labels.contains(&"ratio"),
1565            "Should include Summary field 'ratio'. Got: {:?}",
1566            labels
1567        );
1568    }
1569
1570    #[test]
1571    fn test_user_defined_type_property_completion() {
1572        // Test property completion for a user-defined struct type
1573        let code =
1574            "type Item { name: string, price: number, quantity: number }\nlet item: Item\nitem.x\n";
1575        let position = Position {
1576            line: 2,
1577            character: 5,
1578        };
1579        let completions = completions_for(code, position);
1580        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1581        assert!(
1582            labels.contains(&"name"),
1583            "Should include Item field 'name'. Got: {:?}",
1584            labels
1585        );
1586        assert!(
1587            labels.contains(&"price"),
1588            "Should include Item field 'price'. Got: {:?}",
1589            labels
1590        );
1591        assert!(
1592            labels.contains(&"quantity"),
1593            "Should include Item field 'quantity'. Got: {:?}",
1594            labels
1595        );
1596    }
1597
1598    #[test]
1599    fn test_pattern_reference_completion() {
1600        let code = r#"function hammer(candle) {
1601    return candle.close > candle.open;
1602}
1603
1604let x = 1
1605"#;
1606        // Test that pattern shows up in general completions
1607        let position = Position {
1608            line: 5,
1609            character: 0,
1610        };
1611        let completions = completions_for(code, position);
1612
1613        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1614        // Should show user-defined pattern in completions
1615        assert!(
1616            labels.contains(&"hammer"),
1617            "Should include user-defined pattern 'hammer'. Got: {:?}",
1618            labels
1619        );
1620    }
1621
1622    #[test]
1623    fn test_metadata_keywords() {
1624        // Verify metadata API returns keywords
1625        let keywords = shape_runtime::metadata::LanguageMetadata::keywords();
1626        assert!(!keywords.is_empty());
1627        assert!(keywords.iter().any(|k| k.keyword == "let"));
1628        assert!(keywords.iter().any(|k| k.keyword == "function"));
1629    }
1630
1631    #[test]
1632    fn test_metadata_functions() {
1633        // Verify unified metadata API returns functions
1634        let metadata = unified_metadata();
1635        let functions = metadata.all_functions();
1636        assert!(!functions.is_empty(), "Should have some functions loaded");
1637        // Check for either core builtins OR stdlib functions
1638        // (stdlib may not be loaded in test environment)
1639        let has_builtins = functions.iter().any(|f| {
1640            f.name == "abs"
1641                || f.name == "sqrt"
1642                || f.name == "print"
1643                || f.name == "sma"
1644                || f.name == "rsi"
1645        });
1646        assert!(
1647            has_builtins,
1648            "Should include either core builtins or stdlib functions"
1649        );
1650    }
1651
1652    #[test]
1653    fn test_metadata_types() {
1654        // Verify metadata API returns types
1655        let types = shape_runtime::metadata::LanguageMetadata::builtin_types();
1656        assert!(!types.is_empty());
1657        assert!(types.iter().any(|t| t.name == "Number"));
1658        assert!(types.iter().any(|t| t.name == "Table"));
1659    }
1660
1661    #[test]
1662    fn test_metadata_row_properties() {
1663        // Verify unified metadata returns row properties
1664        // Row now has no hardcoded properties - they're dynamic based on data schema
1665        let meta = unified_metadata();
1666        let props = meta
1667            .get_type_properties("Row")
1668            .expect("Row type should exist");
1669        assert_eq!(
1670            props.len(),
1671            0,
1672            "Row should have no hardcoded properties - fields are dynamic"
1673        );
1674    }
1675
1676    #[test]
1677    fn test_metadata_column_methods() {
1678        // (W15-column, 2026-05-10) `Column` value-type deleted per ADR-006
1679        // §2.7.21 / Q22 — its semantics are absorbed by
1680        // `HeapKind::TableView` + `TableViewData::ColumnRef`. The LSP API
1681        // surface (`LanguageMetadata::column_methods()`) is preserved as
1682        // an empty-`Vec` stub; this test verifies the stub shape rather
1683        // than enumerating the deleted method list. When TableView
1684        // `ColumnRef` projection methods are surfaced through the LSP,
1685        // this test should re-flip to assert non-empty (and assert the
1686        // surviving names).
1687        let methods = shape_runtime::metadata::LanguageMetadata::column_methods();
1688        assert!(methods.is_empty());
1689    }
1690
1691    #[test]
1692    fn test_result_type_completions() {
1693        // Test that Result<Instrument> provides Result methods, NOT Instrument properties
1694        let code = "instr.";
1695        let position = Position {
1696            line: 0,
1697            character: 6,
1698        };
1699        // Provide type context that 'instr' is of type Result<Instrument>
1700        let mut type_context = HashMap::new();
1701        type_context.insert("instr".to_string(), "Result<Instrument>".to_string());
1702        let (completions, _, _) = get_completions(code, position, &[], &type_context);
1703        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1704
1705        // Should have Result methods
1706        assert!(
1707            labels.contains(&"unwrap"),
1708            "Should include Result method 'unwrap'. Got: {:?}",
1709            labels
1710        );
1711        assert!(
1712            labels.contains(&"unwrap_or"),
1713            "Should include Result method 'unwrap_or'. Got: {:?}",
1714            labels
1715        );
1716        assert!(
1717            labels.contains(&"is_ok"),
1718            "Should include Result method 'is_ok'. Got: {:?}",
1719            labels
1720        );
1721        assert!(
1722            labels.contains(&"is_err"),
1723            "Should include Result method 'is_err'. Got: {:?}",
1724            labels
1725        );
1726
1727        // Should NOT have Instrument properties (those are on the inner type)
1728        assert!(
1729            !labels.contains(&"symbol"),
1730            "Should NOT include Instrument property 'symbol' on Result type. Got: {:?}",
1731            labels
1732        );
1733    }
1734
1735    #[test]
1736    fn test_option_type_completions() {
1737        // Test that Option<T> provides Option methods
1738        let code = "opt.";
1739        let position = Position {
1740            line: 0,
1741            character: 4,
1742        };
1743        // Provide type context that 'opt' is of type Option<Number>
1744        let mut type_context = HashMap::new();
1745        type_context.insert("opt".to_string(), "Option<Number>".to_string());
1746        let (completions, _, _) = get_completions(code, position, &[], &type_context);
1747        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1748
1749        // Should have Option methods
1750        assert!(
1751            labels.contains(&"unwrap"),
1752            "Should include Option method 'unwrap'. Got: {:?}",
1753            labels
1754        );
1755        assert!(
1756            labels.contains(&"is_some"),
1757            "Should include Option method 'is_some'. Got: {:?}",
1758            labels
1759        );
1760        assert!(
1761            labels.contains(&"is_none"),
1762            "Should include Option method 'is_none'. Got: {:?}",
1763            labels
1764        );
1765    }
1766
1767    #[test]
1768    fn test_new_keywords_in_completions() {
1769        let keywords = keyword_completions();
1770        let labels: Vec<_> = keywords.iter().map(|k| k.label.as_str()).collect();
1771        // Verify actively supported general-scope keywords appear
1772        assert!(labels.contains(&"match"), "Should include 'match' keyword");
1773        assert!(labels.contains(&"try"), "Should include 'try' keyword");
1774        assert!(
1775            !labels.contains(&"stream"),
1776            "Deprecated/placeholder keyword should not be globally suggested"
1777        );
1778    }
1779
1780    #[test]
1781    fn test_using_impl_selector_context_detection() {
1782        let code = "let x = value using ";
1783        let position = Position {
1784            line: 0,
1785            character: 20,
1786        };
1787        assert!(is_using_impl_selector_context(code, position));
1788    }
1789
1790    #[test]
1791    fn test_using_impl_selector_completions() {
1792        let code = "trait Display { method display() -> string }\n\
1793                    type User { name: string }\n\
1794                    impl Display for User as JsonDisplay {\n\
1795                        method display() { \"json\" }\n\
1796                    }\n\
1797                    let u = User { name: \"a\" }\n\
1798                    print(u using )\n";
1799        let position = Position {
1800            line: 6,
1801            character: 14,
1802        };
1803        let (completions, _, _) = get_completions(code, position, &[], &HashMap::new());
1804        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1805        assert!(
1806            labels.contains(&"JsonDisplay"),
1807            "Expected named impl selector completion. Got: {:?}",
1808            labels
1809        );
1810    }
1811
1812    #[test]
1813    fn test_keyword_descriptions_have_examples() {
1814        let keywords = shape_runtime::metadata::LanguageMetadata::keywords();
1815        // Key keywords should have code examples in their description
1816        let type_kw = keywords
1817            .iter()
1818            .find(|k| k.keyword == "type")
1819            .expect("type keyword");
1820        assert!(
1821            type_kw.description.contains("type Point"),
1822            "'type' should have struct example"
1823        );
1824
1825        let comptime_kw = keywords
1826            .iter()
1827            .find(|k| k.keyword == "comptime")
1828            .expect("comptime keyword");
1829        assert!(
1830            comptime_kw.description.contains("comptime symbol"),
1831            "'comptime' should have example"
1832        );
1833
1834        let match_kw = keywords
1835            .iter()
1836            .find(|k| k.keyword == "match")
1837            .expect("match keyword");
1838        assert!(
1839            match_kw.description.contains("match color"),
1840            "'match' should have example"
1841        );
1842
1843        let for_kw = keywords
1844            .iter()
1845            .find(|k| k.keyword == "for")
1846            .expect("for keyword");
1847        assert!(
1848            for_kw.description.contains("for x in"),
1849            "'for' should have example"
1850        );
1851    }
1852
1853    #[test]
1854    fn test_builtin_function_metadata_has_signatures() {
1855        let metadata = unified_metadata();
1856        let abs_fn = metadata.get_function("abs").expect("abs should exist");
1857        assert!(
1858            abs_fn.signature.contains("abs"),
1859            "abs signature should contain function name"
1860        );
1861        assert!(
1862            abs_fn.signature.contains("number"),
1863            "abs signature should mention number type"
1864        );
1865        assert!(
1866            !abs_fn.description.is_empty(),
1867            "abs should have a description"
1868        );
1869        assert_eq!(abs_fn.parameters.len(), 1, "abs should have 1 parameter");
1870
1871        let print_fn = metadata.get_function("print").expect("print should exist");
1872        assert!(
1873            print_fn.signature.contains("print"),
1874            "print signature should contain name"
1875        );
1876
1877        let max_fn = metadata.get_function("max").expect("max should exist");
1878        assert_eq!(max_fn.parameters.len(), 2, "max should have 2 parameters");
1879    }
1880
1881    #[test]
1882    fn test_snippets_have_correct_format() {
1883        use tower_lsp_server::ls_types::InsertTextFormat;
1884        let snippets = snippet_completions();
1885        for snippet in &snippets {
1886            assert_eq!(
1887                snippet.insert_text_format,
1888                Some(InsertTextFormat::SNIPPET),
1889                "Snippet '{}' should have SNIPPET insert format",
1890                snippet.label
1891            );
1892            assert!(
1893                snippet.insert_text.is_some(),
1894                "Snippet '{}' should have insert text",
1895                snippet.label
1896            );
1897        }
1898    }
1899
1900    #[test]
1901    fn test_user_defined_struct_field_completions() {
1902        // Regression test: user-defined struct types should show their fields,
1903        // not the generic "length" fallback.
1904        // Use parseable code (b.i is valid) with cursor positioned after the dot.
1905        let code = "type MyType { i: int, name: string }\nlet b = MyType { i: 10, name: \"hello\" }\nb.i\n";
1906        let position = Position {
1907            line: 2,
1908            character: 2, // cursor after "b."
1909        };
1910        let completions = completions_for(code, position);
1911        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1912
1913        assert!(
1914            labels.contains(&"i"),
1915            "Should include struct field 'i'. Got: {:?}",
1916            labels
1917        );
1918        assert!(
1919            labels.contains(&"name"),
1920            "Should include struct field 'name'. Got: {:?}",
1921            labels
1922        );
1923        assert!(
1924            !labels.contains(&"length"),
1925            "Should NOT show generic 'length' fallback for typed struct. Got: {:?}",
1926            labels
1927        );
1928    }
1929
1930    #[test]
1931    fn test_unwrapped_type_has_inner_properties() {
1932        // Test that a user-defined struct shows its fields (not Result methods)
1933        let code = "type Device { name: string, status: string }\nlet dev: Device\ndev.x\n";
1934        let position = Position {
1935            line: 2,
1936            character: 4,
1937        };
1938        let completions = completions_for(code, position);
1939        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1940
1941        // Should have Device fields, NOT Result methods
1942        assert!(
1943            labels.contains(&"name"),
1944            "Device should include 'name'. Got: {:?}",
1945            labels
1946        );
1947        assert!(
1948            !labels.contains(&"is_ok"),
1949            "Device should NOT include Result method 'is_ok'. Got: {:?}",
1950            labels
1951        );
1952    }
1953
1954    #[test]
1955    fn test_impl_method_completions() {
1956        // Use parseable code (t.x is valid) with cursor positioned after "t."
1957        // Fixture migrated to Form B per cd7d97a4 (2026-05-18) grammar surgery.
1958        // FN-REG side (method completion on user-receiver returning 0 items)
1959        // belongs to LSP-C per audit §G.
1960        let code = "trait Q {\n    method filter(self, p) -> any;\n    method select(self, c) -> any;\n}\nimpl Q for T {\n    method filter(p) { self }\n}\nlet t: T\nt.x\n";
1961        let position = Position {
1962            line: 8,
1963            character: 2, // cursor after "t."
1964        };
1965        let mut type_context = HashMap::new();
1966        type_context.insert("t".to_string(), "T".to_string());
1967        let (completions, _, _) = get_completions(code, position, &[], &type_context);
1968        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1969        assert!(
1970            labels.contains(&"filter"),
1971            "Should include 'filter' from impl. Got: {:?}",
1972            labels
1973        );
1974        assert!(
1975            labels.contains(&"select"),
1976            "Should include 'select' from trait (via impl). Got: {:?}",
1977            labels
1978        );
1979    }
1980
1981    #[test]
1982    fn test_extend_method_completions() {
1983        // Use parseable code (a.x is valid) with cursor positioned after "a."
1984        let code =
1985            "extend Array {\n    method double() {\n        self\n    }\n}\nlet a: Array\na.x\n";
1986        let position = Position {
1987            line: 6,
1988            character: 2, // cursor after "a."
1989        };
1990        let mut type_context = HashMap::new();
1991        type_context.insert("a".to_string(), "Array".to_string());
1992        let (completions, _, _) = get_completions(code, position, &[], &type_context);
1993        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
1994        assert!(
1995            labels.contains(&"double"),
1996            "Should include 'double' from extend block. Got: {:?}",
1997            labels
1998        );
1999    }
2000
2001    #[test]
2002    fn test_property_completions_chained_struct() {
2003        // Multi-level chain: o.inner. should show Inner's fields
2004        let code = "type Outer { inner: Inner }\ntype Inner { val: number }\nlet o = Outer { inner: Inner { val: 1 } }\no.inner.x\n";
2005        let position = Position {
2006            line: 3,
2007            character: 8, // cursor after "o.inner."
2008        };
2009        let completions = completions_for(code, position);
2010        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2011        assert!(
2012            labels.contains(&"val"),
2013            "Should include 'val' from Inner struct. Got: {:?}",
2014            labels
2015        );
2016    }
2017
2018    #[test]
2019    fn test_string_method_completions() {
2020        // String-specific methods (toLowerCase, split, etc.) are now registered
2021        // from Shape stdlib (stdlib-src/core/string_methods.shape) during
2022        // compilation, not at MethodTable::new() time. The universal methods
2023        // (toString, type) are always available.
2024        let code = "let s = \"hi\"\ns.x\n";
2025        let position = Position {
2026            line: 1,
2027            character: 2, // cursor after "s."
2028        };
2029        let completions = completions_for(code, position);
2030        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2031        // Universal methods are always registered
2032        assert!(
2033            labels.contains(&"toString"),
2034            "Should include universal method 'toString'. Got: {:?}",
2035            labels
2036        );
2037        assert!(
2038            labels.contains(&"type"),
2039            "Should include universal method 'type'. Got: {:?}",
2040            labels
2041        );
2042    }
2043
2044    #[test]
2045    fn test_number_method_completions() {
2046        // Number-specific methods (abs, floor, etc.) are now registered from
2047        // Shape stdlib during compilation. Universal methods are always present.
2048        let code = "let n = 42\nn.x\n";
2049        let position = Position {
2050            line: 1,
2051            character: 2,
2052        };
2053        let completions = completions_for(code, position);
2054        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2055        assert!(
2056            labels.contains(&"toString"),
2057            "Should include universal method 'toString'. Got: {:?}",
2058            labels
2059        );
2060    }
2061
2062    #[test]
2063    fn test_array_method_completions() {
2064        // Array-specific methods (`map`, `filter`, etc.) live in pure Shape
2065        // source in `stdlib-src/core/vec.shape` via `extend Vec<T>`.
2066        // LSP-C audit §A.1 / §D regression close: those stdlib methods are
2067        // now loaded into the impl-methods cache by `stdlib_methods.rs` and
2068        // merged into the per-request method map, parallel to the existing
2069        // extension-source plumbing. Universal methods (`toString`, `type`)
2070        // remain available from the runtime `MethodTable`.
2071        let code = "let a = [1, 2]\na.x\n";
2072        let position = Position {
2073            line: 1,
2074            character: 2,
2075        };
2076        let completions = completions_for(code, position);
2077        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2078        assert!(
2079            labels.contains(&"toString"),
2080            "Should include universal method 'toString'. Got: {:?}",
2081            labels
2082        );
2083        assert!(
2084            labels.contains(&"type"),
2085            "Should include universal method 'type'. Got: {:?}",
2086            labels
2087        );
2088        assert!(
2089            labels.contains(&"map"),
2090            "Should include stdlib Vec.map. Got: {:?}",
2091            labels
2092        );
2093        assert!(
2094            labels.contains(&"filter"),
2095            "Should include stdlib Vec.filter. Got: {:?}",
2096            labels
2097        );
2098    }
2099
2100    #[test]
2101    fn test_closure_param_completions_with_struct() {
2102        // t.filter(|c| c.) should show Candle's fields for c
2103        let code = "type Candle { open: number, close: number }\nlet t: Table<Candle>\nt.filter(|c| c.x)\n";
2104        let position = Position {
2105            line: 2,
2106            character: 16, // cursor after "c."
2107        };
2108        let mut type_context = HashMap::new();
2109        type_context.insert("t".to_string(), "Table<Candle>".to_string());
2110        let (completions, _, _) = get_completions(code, position, &[], &type_context);
2111        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2112        assert!(
2113            labels.contains(&"open"),
2114            "Should include 'open' from Candle struct. Got: {:?}",
2115            labels
2116        );
2117        assert!(
2118            labels.contains(&"close"),
2119            "Should include 'close' from Candle struct. Got: {:?}",
2120            labels
2121        );
2122    }
2123
2124    #[test]
2125    fn test_pipe_target_completions() {
2126        // `let a = [1, 2]; a |> ` should show array-compatible functions
2127        let code = "let a = [1, 2]\na |> ";
2128        let position = Position {
2129            line: 1,
2130            character: 5,
2131        };
2132        let completions = completions_for(code, position);
2133        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2134        // Should include common pipe-friendly functions
2135        assert!(
2136            labels.contains(&"map"),
2137            "Pipe target should include 'map'. Got: {:?}",
2138            labels
2139        );
2140        assert!(
2141            labels.contains(&"filter"),
2142            "Pipe target should include 'filter'. Got: {:?}",
2143            labels
2144        );
2145    }
2146
2147    #[test]
2148    fn test_pipe_chain_type_tracking() {
2149        // After pipe chain, variable should still have universal methods at minimum.
2150        // Array-specific methods (from Shape stdlib) are available at full compilation time.
2151        let code = "let a = [1]\nlet b = a\nb.x\n";
2152        let position = Position {
2153            line: 2,
2154            character: 2,
2155        };
2156        let completions = completions_for(code, position);
2157        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2158        assert!(
2159            !labels.is_empty(),
2160            "Should have some completions for b. Got: {:?}",
2161            labels
2162        );
2163    }
2164
2165    #[test]
2166    fn test_impl_block_completions_suggests_unimplemented() {
2167        // Fixture migrated to Form B per cd7d97a4 (2026-05-18) grammar surgery.
2168        let code = "trait Queryable {\n    method filter(self, pred) -> any;\n    method select(self, cols) -> any;\n    method orderBy(self, col) -> any;\n}\nimpl Queryable for MyTable {\n    method filter(pred) { self }\n    \n}\n";
2169        let completions =
2170            impl_block_completions(code, "Queryable", &["filter".to_string()], None, None, None);
2171        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2172        assert!(
2173            labels.contains(&"select"),
2174            "Should suggest unimplemented 'select'. Got: {:?}",
2175            labels
2176        );
2177        assert!(
2178            labels.contains(&"orderBy"),
2179            "Should suggest unimplemented 'orderBy'. Got: {:?}",
2180            labels
2181        );
2182        assert!(
2183            !labels.contains(&"filter"),
2184            "Should NOT suggest already-implemented 'filter'. Got: {:?}",
2185            labels
2186        );
2187    }
2188
2189    #[test]
2190    fn test_impl_block_completions_empty_when_all_implemented() {
2191        let code = "trait Simple {\n    method foo() -> any\n}\nimpl Simple for Bar {\n    method foo() { self }\n}\n";
2192        let completions =
2193            impl_block_completions(code, "Simple", &["foo".to_string()], None, None, None);
2194        assert!(
2195            completions.is_empty(),
2196            "Should have no completions when all methods implemented"
2197        );
2198    }
2199
2200    #[test]
2201    fn test_impl_block_completions_unknown_trait_returns_empty() {
2202        let code = "let x = 42\n";
2203        let completions = impl_block_completions(code, "NonExistent", &[], None, None, None);
2204        assert!(
2205            completions.is_empty(),
2206            "Should return empty for unknown trait"
2207        );
2208    }
2209
2210    #[test]
2211    fn test_impl_block_completions_snippet_format() {
2212        // Fixture migrated to Form B per cd7d97a4 (2026-05-18) grammar surgery.
2213        let code = "trait Filt {\n    method filter(self, pred) -> any;\n}\n";
2214        let completions = impl_block_completions(code, "Filt", &[], None, None, None);
2215        assert_eq!(completions.len(), 1);
2216        let item = &completions[0];
2217        assert_eq!(item.label, "filter");
2218        // Should have snippet format
2219        assert_eq!(
2220            item.insert_text_format,
2221            Some(tower_lsp_server::ls_types::InsertTextFormat::SNIPPET)
2222        );
2223        // Snippet should contain method keyword and parameter placeholder
2224        let snippet = item.insert_text.as_ref().unwrap();
2225        assert!(
2226            snippet.starts_with("method filter("),
2227            "Snippet should start with 'method filter('. Got: {}",
2228            snippet
2229        );
2230    }
2231
2232    #[test]
2233    fn test_impl_block_completions_resolve_trait_from_modules() {
2234        let code = "type User { name: String }\nimpl Display for User {\n    \n}\n";
2235        let position = Position {
2236            line: 2,
2237            character: 4,
2238        };
2239        let cache = ModuleCache::new();
2240        let current_file = std::env::current_dir()
2241            .unwrap_or_else(|_| std::path::PathBuf::from("."))
2242            .join("__shape_lsp_impl_completion_test__.shape");
2243
2244        let (completions, _, _) = get_completions_with_context(
2245            code,
2246            position,
2247            &[],
2248            &HashMap::new(),
2249            Some(&cache),
2250            Some(current_file.as_path()),
2251            None,
2252        );
2253        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2254        assert!(
2255            labels.contains(&"display"),
2256            "Expected Display trait method completion from module resolution, got: {:?}",
2257            labels
2258        );
2259    }
2260
2261    #[test]
2262    fn test_comptime_field_override_completions() {
2263        let mut struct_fields = HashMap::new();
2264        struct_fields.insert(
2265            "Currency".to_string(),
2266            vec![
2267                ("symbol".to_string(), "comptime string = \"$\"".to_string()),
2268                ("decimals".to_string(), "comptime number = 2".to_string()),
2269                ("amount".to_string(), "number".to_string()),
2270            ],
2271        );
2272        let completions = comptime_field_override_completions(&struct_fields, "Currency");
2273        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2274        assert_eq!(labels.len(), 2, "Should only show comptime fields");
2275        assert!(
2276            labels.contains(&"symbol"),
2277            "Should include comptime field 'symbol'. Got: {:?}",
2278            labels
2279        );
2280        assert!(
2281            labels.contains(&"decimals"),
2282            "Should include comptime field 'decimals'. Got: {:?}",
2283            labels
2284        );
2285        assert!(
2286            !labels.contains(&"amount"),
2287            "Should NOT include runtime field 'amount'. Got: {:?}",
2288            labels
2289        );
2290    }
2291
2292    #[test]
2293    fn test_comptime_field_override_completions_integration() {
2294        // Full integration: typing inside `type EUR = Currency { | }` should suggest comptime fields.
2295        // The code must parse: use valid `meta_param_override` syntax (ident: expr).
2296        let code = "type Currency { comptime symbol: string = \"$\", comptime decimals: number = 2, amount: number }\ntype EUR = Currency { symbol: \"E\" }";
2297        // Cursor after "{ " on line 1 — position is inside the override braces
2298        let position = Position {
2299            line: 1,
2300            character: 22,
2301        };
2302        let completions = completions_for(code, position);
2303        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2304        assert!(
2305            labels.contains(&"symbol"),
2306            "Should suggest comptime field 'symbol'. Got: {:?}",
2307            labels
2308        );
2309        assert!(
2310            labels.contains(&"decimals"),
2311            "Should suggest comptime field 'decimals'. Got: {:?}",
2312            labels
2313        );
2314        assert!(
2315            !labels.contains(&"amount"),
2316            "Should NOT suggest runtime field 'amount'. Got: {:?}",
2317            labels
2318        );
2319    }
2320
2321    #[test]
2322    fn test_join_strategy_completions() {
2323        let completions = join_strategy_completions();
2324        assert_eq!(
2325            completions.len(),
2326            4,
2327            "Should have 4 join strategy completions"
2328        );
2329
2330        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2331        assert!(labels.contains(&"all"));
2332        assert!(labels.contains(&"race"));
2333        assert!(labels.contains(&"any"));
2334        assert!(labels.contains(&"settle"));
2335
2336        // All should be KEYWORD kind
2337        for item in &completions {
2338            assert_eq!(item.kind, Some(CompletionItemKind::KEYWORD));
2339        }
2340    }
2341
2342    #[test]
2343    fn test_join_strategy_completions_integration() {
2344        // After "await join " the context should be JoinStrategy
2345        let code = "async fn foo() {\n  await join ";
2346        let position = Position {
2347            line: 1,
2348            character: 13,
2349        };
2350        let context = crate::context::analyze_context(code, position);
2351        assert_eq!(
2352            context,
2353            crate::context::CompletionContext::JoinStrategy,
2354            "Should detect JoinStrategy context"
2355        );
2356    }
2357
2358    #[test]
2359    fn test_async_keywords_in_completions() {
2360        let keywords = keyword_completions();
2361        let labels: Vec<_> = keywords.iter().map(|k| k.label.as_str()).collect();
2362        assert!(labels.contains(&"await"), "Should include 'await' keyword");
2363        assert!(labels.contains(&"async"), "Should include 'async' keyword");
2364        assert!(
2365            !labels.contains(&"join"),
2366            "join should only appear in `await join` context completions"
2367        );
2368    }
2369
2370    #[test]
2371    fn test_join_branch_completions() {
2372        let completions = join_branch_completions("all");
2373        assert_eq!(completions.len(), 2);
2374        assert!(
2375            completions.iter().any(|c| c.label.contains("label")),
2376            "Should include labeled branch snippet"
2377        );
2378        assert!(
2379            completions.iter().any(|c| c.label.contains("annotation")),
2380            "Should include annotated branch snippet"
2381        );
2382    }
2383
2384    #[test]
2385    fn test_join_body_completions_integration() {
2386        // Inside a join all block, should get branch snippets + general completions
2387        let code = "async fn foo() {\n  await join all {\n    ";
2388        let position = Position {
2389            line: 2,
2390            character: 4,
2391        };
2392        let completions = completions_for(code, position);
2393        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
2394        assert!(
2395            labels.iter().any(|l| l.contains("label")),
2396            "Should include labeled branch snippet in join body, got: {:?}",
2397            labels
2398        );
2399    }
2400
2401    #[test]
2402    fn test_type_aware_completion_typed_assignment() {
2403        // `let x: number = |` — completions producing numbers should be boosted
2404        let code = "let n = 42\nlet s = \"hello\"\nlet x: number = ";
2405        let position = Position {
2406            line: 2,
2407            character: 16,
2408        };
2409        let mut type_context = HashMap::new();
2410        type_context.insert("n".to_string(), "number".to_string());
2411        type_context.insert("s".to_string(), "string".to_string());
2412        let (completions, _, _) = get_completions(code, position, &[], &type_context);
2413
2414        // All completions should have sort_text set
2415        let with_sort: Vec<_> = completions
2416            .iter()
2417            .filter(|c| c.sort_text.is_some())
2418            .collect();
2419        assert!(!with_sort.is_empty(), "expected sort_text on completions");
2420
2421        // Variable 'n' (number) should sort before 's' (string)
2422        let n_sort = completions
2423            .iter()
2424            .find(|c| c.label == "n")
2425            .and_then(|c| c.sort_text.as_deref());
2426        let s_sort = completions
2427            .iter()
2428            .find(|c| c.label == "s")
2429            .and_then(|c| c.sort_text.as_deref());
2430        if let (Some(n_s), Some(s_s)) = (n_sort, s_sort) {
2431            assert!(
2432                n_s < s_s,
2433                "number var 'n' should sort before string var 's'. n={}, s={}",
2434                n_s,
2435                s_s
2436            );
2437        }
2438    }
2439
2440    #[test]
2441    fn test_expected_type_from_typed_assignment() {
2442        let code = "let x: number = ";
2443        let position = Position {
2444            line: 0,
2445            character: 16,
2446        };
2447        let type_context = HashMap::new();
2448        let expected = expected_type_at_cursor(code, position, &type_context);
2449        assert_eq!(expected, Some("number".to_string()));
2450    }
2451
2452    #[test]
2453    fn test_expected_type_from_return() {
2454        let code = "fn foo() -> number {\n  return ";
2455        let position = Position {
2456            line: 1,
2457            character: 9,
2458        };
2459        let type_context = HashMap::new();
2460        let expected = expected_type_at_cursor(code, position, &type_context);
2461        assert_eq!(expected, Some("number".to_string()));
2462    }
2463
2464    #[test]
2465    fn test_expected_type_from_binary_op() {
2466        let code = "let x = n + ";
2467        let position = Position {
2468            line: 0,
2469            character: 12,
2470        };
2471        let mut type_context = HashMap::new();
2472        type_context.insert("n".to_string(), "number".to_string());
2473        let expected = expected_type_at_cursor(code, position, &type_context);
2474        assert_eq!(expected, Some("number".to_string()));
2475    }
2476
2477    #[test]
2478    fn test_types_compatible_exact() {
2479        assert_eq!(types_compatible("number", "number"), TypeMatch::Exact);
2480        assert_eq!(types_compatible("string", "string"), TypeMatch::Exact);
2481    }
2482
2483    #[test]
2484    fn test_types_compatible_numeric() {
2485        assert_eq!(types_compatible("int", "number"), TypeMatch::Compatible);
2486        assert_eq!(types_compatible("decimal", "number"), TypeMatch::Compatible);
2487    }
2488
2489    #[test]
2490    fn test_types_compatible_incompatible() {
2491        assert_eq!(
2492            types_compatible("string", "number"),
2493            TypeMatch::Incompatible
2494        );
2495        assert_eq!(types_compatible("bool", "number"), TypeMatch::Incompatible);
2496    }
2497}