Skip to main content

rex_lsp/
server.rs

1use std::collections::{BTreeSet, HashMap, HashSet};
2use std::fs;
3use std::hash::{Hash, Hasher};
4use std::path::PathBuf;
5use std::sync::{Mutex, OnceLock};
6
7use lsp_types::{
8    CodeAction, CodeActionKind, CodeActionOrCommand, CompletionItem, CompletionItemKind,
9    Diagnostic, DiagnosticSeverity, DocumentSymbol, GotoDefinitionResponse, Hover, HoverContents,
10    Location, MarkupContent, MarkupKind, Position, Range, SymbolKind, TextEdit, Url, WorkspaceEdit,
11};
12use rex_ast::{
13    ClassDecl, ClassMethodSig, CompilationUnit, Decl, DeclareFnDecl, Expr, FnDecl, ImportDecl,
14    ImportPath, InstanceDecl, InstanceMethodImpl, NameRef, Pattern, Symbol, TypeConstraint,
15    TypeDecl, TypeExpr, TypeVariant, Var,
16};
17use rex_ast::{Position as RexPosition, Span, Spanned};
18use rex_engine::{Engine, EngineError, ModuleError};
19use rex_parser::{
20    error::ParseError,
21    lexer::{LexicalError, Token, Tokens},
22    parse_with_tokens,
23};
24use rex_typesystem::{
25    error::TypeError as TsTypeError,
26    inference::infer_typed,
27    types::{BuiltinTypeId, Scheme, Type, TypeKind, TypedExpr, TypedExprKind, Types},
28    typesystem::{PreparedInstanceDecl, TypeSystem, instantiate},
29    unification::unify,
30};
31use rex_util::{resolve_local_import_path, sha256_hex, stdlib_source};
32use serde_json::{Value, json, to_value};
33
34const MAX_DIAGNOSTICS: usize = 50;
35pub const CMD_EXPECTED_TYPE_AT: &str = "rex.expectedTypeAt";
36pub const CMD_FUNCTIONS_PRODUCING_EXPECTED_TYPE_AT: &str = "rex.functionsProducingExpectedTypeAt";
37pub const CMD_FUNCTIONS_ACCEPTING_INFERRED_TYPE_AT: &str = "rex.functionsAcceptingInferredTypeAt";
38pub const CMD_ADAPTERS_FROM_INFERRED_TO_EXPECTED_AT: &str = "rex.adaptersFromInferredToExpectedAt";
39pub const CMD_FUNCTIONS_COMPATIBLE_WITH_IN_SCOPE_VALUES_AT: &str =
40    "rex.functionsCompatibleWithInScopeValuesAt";
41pub const CMD_HOLES_EXPECTED_TYPES: &str = "rex.holesExpectedTypes";
42pub const CMD_SEMANTIC_LOOP_STEP: &str = "rex.semanticLoopStep";
43pub const CMD_SEMANTIC_LOOP_APPLY_QUICK_FIX_AT: &str = "rex.semanticLoopApplyQuickFixAt";
44pub const CMD_SEMANTIC_LOOP_APPLY_BEST_QUICK_FIXES_AT: &str =
45    "rex.semanticLoopApplyBestQuickFixesAt";
46const NO_IMPROVEMENT_STREAK_LIMIT: usize = 2;
47pub const MAX_SEMANTIC_ENV_SCHEMES_SCAN: usize = 1024;
48pub const MAX_SEMANTIC_IN_SCOPE_VALUES: usize = 128;
49pub const MAX_SEMANTIC_CANDIDATES: usize = 64;
50pub const MAX_SEMANTIC_HOLE_FILL_ARITY: usize = 8;
51pub const MAX_SEMANTIC_HOLES: usize = 128;
52const BUILTIN_TYPES: &[&str] = &[
53    "u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64", "f32", "f64", "bool", "string", "uuid",
54    "datetime", "Dict", "List", "Array", "Option", "Result",
55];
56const BUILTIN_VALUES: &[&str] = &["true", "false", "null", "Some", "None", "Ok", "Err"];
57
58#[derive(Debug)]
59enum TokenizeOrParseError {
60    Lex(LexicalError),
61    Parse(Vec<ParseError>),
62}
63
64#[derive(Clone, Copy, Debug, Eq, PartialEq)]
65pub enum BulkQuickFixStrategy {
66    Conservative,
67    Aggressive,
68}
69
70impl BulkQuickFixStrategy {
71    pub fn parse(s: &str) -> Self {
72        if s.eq_ignore_ascii_case("aggressive") {
73            Self::Aggressive
74        } else {
75            Self::Conservative
76        }
77    }
78
79    pub fn as_str(self) -> &'static str {
80        match self {
81            Self::Conservative => "conservative",
82            Self::Aggressive => "aggressive",
83        }
84    }
85}
86
87#[derive(Clone)]
88struct CachedParse {
89    hash: u64,
90    tokens: Tokens,
91    program: CompilationUnit,
92}
93
94fn text_hash(text: &str) -> u64 {
95    let mut hasher = std::collections::hash_map::DefaultHasher::new();
96    text.hash(&mut hasher);
97    hasher.finish()
98}
99
100fn parse_cache() -> &'static Mutex<HashMap<Url, CachedParse>> {
101    static CACHE: OnceLock<Mutex<HashMap<Url, CachedParse>>> = OnceLock::new();
102    CACHE.get_or_init(|| Mutex::new(HashMap::new()))
103}
104
105fn semantic_candidate_values(ts: &TypeSystem) -> Vec<(Symbol, Vec<Scheme>)> {
106    let mut out = Vec::new();
107    let mut scanned = 0usize;
108    for (name, schemes) in &ts.env.values {
109        if scanned >= MAX_SEMANTIC_ENV_SCHEMES_SCAN {
110            break;
111        }
112        let remaining = MAX_SEMANTIC_ENV_SCHEMES_SCAN - scanned;
113        let kept = schemes.iter().take(remaining).cloned().collect::<Vec<_>>();
114        if kept.is_empty() {
115            continue;
116        }
117        scanned += kept.len();
118        out.push((name.clone(), kept));
119    }
120    out
121}
122
123pub fn clear_parse_cache(uri: &Url) {
124    let Ok(mut cache) = parse_cache().lock() else {
125        return;
126    };
127    cache.remove(uri);
128}
129
130#[cfg(not(target_arch = "wasm32"))]
131fn uri_to_file_path(uri: &Url) -> Option<PathBuf> {
132    uri.to_file_path().ok()
133}
134
135#[cfg(target_arch = "wasm32")]
136fn uri_to_file_path(_uri: &Url) -> Option<PathBuf> {
137    None
138}
139
140#[cfg(not(target_arch = "wasm32"))]
141fn url_from_file_path(path: &std::path::Path) -> Option<Url> {
142    Url::from_file_path(path).ok()
143}
144
145#[cfg(target_arch = "wasm32")]
146fn url_from_file_path(_path: &std::path::Path) -> Option<Url> {
147    None
148}
149
150fn tokenize_and_parse(
151    text: &str,
152) -> std::result::Result<(Tokens, CompilationUnit), TokenizeOrParseError> {
153    let tokens = Token::tokenize(text).map_err(TokenizeOrParseError::Lex)?;
154    let program = parse_with_tokens(tokens.clone()).map_err(TokenizeOrParseError::Parse)?;
155    Ok((tokens, program))
156}
157
158fn tokenize_and_parse_cached(
159    uri: &Url,
160    text: &str,
161) -> std::result::Result<(Tokens, CompilationUnit), TokenizeOrParseError> {
162    let hash = text_hash(text);
163    if let Ok(cache) = parse_cache().lock()
164        && let Some(cached) = cache.get(uri)
165        && cached.hash == hash
166    {
167        return Ok((cached.tokens.clone(), cached.program.clone()));
168    }
169
170    let (tokens, program) = tokenize_and_parse(text)?;
171    if let Ok(mut cache) = parse_cache().lock() {
172        cache.insert(
173            uri.clone(),
174            CachedParse {
175                hash,
176                tokens: tokens.clone(),
177                program: program.clone(),
178            },
179        );
180    }
181    Ok((tokens, program))
182}
183
184#[derive(Clone)]
185pub struct ImportModuleInfo {
186    #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
187    path: Option<PathBuf>,
188    value_map: HashMap<Symbol, Symbol>, // field -> internal name
189    type_map: HashMap<Symbol, Symbol>,
190    class_map: HashMap<Symbol, Symbol>,
191    #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
192    export_defs: HashMap<String, Span>,
193}
194
195fn is_ident_like(name: &str) -> bool {
196    let mut chars = name.chars();
197    let Some(first) = chars.next() else {
198        return false;
199    };
200    if !(first.is_ascii_alphabetic() || first == '_') {
201        return false;
202    }
203    chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
204}
205
206fn prelude_completion_values() -> &'static Vec<(String, CompletionItemKind)> {
207    static PRELUDE_VALUES: OnceLock<Vec<(String, CompletionItemKind)>> = OnceLock::new();
208    PRELUDE_VALUES.get_or_init(|| {
209        let ts = match TypeSystem::new_with_prelude() {
210            Ok(ts) => ts,
211            Err(e) => {
212                eprintln!("rex-lsp: failed to build prelude for completions: {e}");
213                return Vec::new();
214            }
215        };
216        let mut out = Vec::new();
217        for (name, schemes) in ts.env.values.iter() {
218            let name = name.as_ref().to_string();
219            if !is_ident_like(&name) {
220                continue;
221            }
222            let is_fun = schemes
223                .iter()
224                .any(|scheme| matches!(scheme.typ.as_ref(), TypeKind::Fun(..)));
225            let kind = if is_fun {
226                CompletionItemKind::FUNCTION
227            } else {
228                CompletionItemKind::VARIABLE
229            };
230            out.push((name, kind));
231        }
232        out.sort_by(|(a, _), (b, _)| a.cmp(b));
233        out
234    })
235}
236
237fn module_prefix(hash: &str) -> String {
238    let short = if hash.len() >= 16 { &hash[..16] } else { hash };
239    format!("@m{short}")
240}
241
242fn inject_program_decls(
243    ts: &mut TypeSystem,
244    compilation_unit: &CompilationUnit,
245    want_prepared_instance: Option<usize>,
246) -> std::result::Result<InjectedDecls, TsTypeError> {
247    let mut instances = Vec::new();
248    let mut prepared_target = None;
249    let mut pending_non_instances: Vec<Decl> = Vec::new();
250
251    let flush_non_instances =
252        |ts: &mut TypeSystem, pending: &mut Vec<Decl>| -> std::result::Result<(), TsTypeError> {
253            if pending.is_empty() {
254                return Ok(());
255            }
256            ts.register_decls(pending)?;
257            pending.clear();
258            Ok(())
259        };
260
261    for (idx, decl) in compilation_unit.decls.iter().enumerate() {
262        match decl {
263            Decl::Instance(inst_decl) => {
264                flush_non_instances(ts, &mut pending_non_instances)?;
265                let prepared = ts.register_instance_decl(inst_decl)?;
266                if want_prepared_instance.is_some_and(|want| want == idx) {
267                    prepared_target = Some(prepared.clone());
268                }
269                instances.push((idx, prepared));
270            }
271            _ => pending_non_instances.push(decl.clone()),
272        }
273    }
274    flush_non_instances(ts, &mut pending_non_instances)?;
275
276    Ok((instances, prepared_target))
277}
278
279type PreparedInstance = (usize, PreparedInstanceDecl);
280type InjectedDecls = (Vec<PreparedInstance>, Option<PreparedInstanceDecl>);
281
282fn rewrite_type_expr(ty: &TypeExpr, type_map: &HashMap<Symbol, Symbol>) -> TypeExpr {
283    match ty {
284        TypeExpr::Name(span, name) => {
285            if let Some(new) = type_map.get(&name.to_dotted_symbol()) {
286                TypeExpr::Name(*span, NameRef::Unqualified(new.clone()))
287            } else {
288                TypeExpr::Name(*span, name.clone())
289            }
290        }
291        TypeExpr::App(span, f, x) => TypeExpr::App(
292            *span,
293            Box::new(rewrite_type_expr(f, type_map)),
294            Box::new(rewrite_type_expr(x, type_map)),
295        ),
296        TypeExpr::Fun(span, a, b) => TypeExpr::Fun(
297            *span,
298            Box::new(rewrite_type_expr(a, type_map)),
299            Box::new(rewrite_type_expr(b, type_map)),
300        ),
301        TypeExpr::Tuple(span, elems) => TypeExpr::Tuple(
302            *span,
303            elems
304                .iter()
305                .map(|e| rewrite_type_expr(e, type_map))
306                .collect(),
307        ),
308        TypeExpr::Record(span, fields) => TypeExpr::Record(
309            *span,
310            fields
311                .iter()
312                .map(|(name, ty)| (name.clone(), rewrite_type_expr(ty, type_map)))
313                .collect(),
314        ),
315    }
316}
317
318fn collect_pattern_bindings(pat: &Pattern, out: &mut Vec<Symbol>) {
319    match pat {
320        Pattern::Wildcard(..) => {}
321        Pattern::Var(v) => out.push(v.name.clone()),
322        Pattern::Named(_, _, args) => {
323            for arg in args {
324                collect_pattern_bindings(arg, out);
325            }
326        }
327        Pattern::Tuple(_, elems) | Pattern::List(_, elems) => {
328            for elem in elems {
329                collect_pattern_bindings(elem, out);
330            }
331        }
332        Pattern::Cons(_, head, tail) => {
333            collect_pattern_bindings(head, out);
334            collect_pattern_bindings(tail, out);
335        }
336        Pattern::Dict(_, fields) => {
337            for (_, pat) in fields {
338                collect_pattern_bindings(pat, out);
339            }
340        }
341    }
342}
343
344fn rewrite_import_projections_expr(
345    expr: &Expr,
346    bound: &mut BTreeSet<Symbol>,
347    imports: &HashMap<Symbol, ImportModuleInfo>,
348    diagnostics: &mut Vec<Diagnostic>,
349) -> Expr {
350    match expr {
351        Expr::Project(span, base, field) => {
352            if let Expr::Var(v) = base.as_ref()
353                && !bound.contains(&v.name)
354                && let Some(info) = imports.get(&v.name)
355            {
356                if let Some(internal) = info.value_map.get(field) {
357                    return Expr::Var(Var {
358                        span: *span,
359                        name: internal.clone(),
360                    });
361                }
362                diagnostics.push(diagnostic_for_span(
363                    *span,
364                    format!("module `{}` does not export `{}`", v.name, field),
365                ));
366            }
367            Expr::Project(
368                *span,
369                std::sync::Arc::new(rewrite_import_projections_expr(
370                    base,
371                    bound,
372                    imports,
373                    diagnostics,
374                )),
375                field.clone(),
376            )
377        }
378        Expr::Var(v) => Expr::Var(v.clone()),
379        Expr::Bool(span, v) => Expr::Bool(*span, *v),
380        Expr::Uint(span, v) => Expr::Uint(*span, *v),
381        Expr::Int(span, v) => Expr::Int(*span, *v),
382        Expr::Float(span, v) => Expr::Float(*span, *v),
383        Expr::String(span, v) => Expr::String(*span, v.clone()),
384        Expr::Uuid(span, v) => Expr::Uuid(*span, *v),
385        Expr::DateTime(span, v) => Expr::DateTime(*span, *v),
386        Expr::Hole(span) => Expr::Hole(*span),
387        Expr::Tuple(span, elems) => Expr::Tuple(
388            *span,
389            elems
390                .iter()
391                .map(|e| {
392                    std::sync::Arc::new(rewrite_import_projections_expr(
393                        e,
394                        bound,
395                        imports,
396                        diagnostics,
397                    ))
398                })
399                .collect(),
400        ),
401        Expr::List(span, elems) => Expr::List(
402            *span,
403            elems
404                .iter()
405                .map(|e| {
406                    std::sync::Arc::new(rewrite_import_projections_expr(
407                        e,
408                        bound,
409                        imports,
410                        diagnostics,
411                    ))
412                })
413                .collect(),
414        ),
415        Expr::Dict(span, kvs) => Expr::Dict(
416            *span,
417            kvs.iter()
418                .map(|(k, v)| {
419                    (
420                        k.clone(),
421                        std::sync::Arc::new(rewrite_import_projections_expr(
422                            v,
423                            bound,
424                            imports,
425                            diagnostics,
426                        )),
427                    )
428                })
429                .collect(),
430        ),
431        Expr::RecordUpdate(span, base, updates) => Expr::RecordUpdate(
432            *span,
433            std::sync::Arc::new(rewrite_import_projections_expr(
434                base,
435                bound,
436                imports,
437                diagnostics,
438            )),
439            updates
440                .iter()
441                .map(|(k, v)| {
442                    (
443                        k.clone(),
444                        std::sync::Arc::new(rewrite_import_projections_expr(
445                            v,
446                            bound,
447                            imports,
448                            diagnostics,
449                        )),
450                    )
451                })
452                .collect(),
453        ),
454        Expr::App(span, f, x) => Expr::App(
455            *span,
456            std::sync::Arc::new(rewrite_import_projections_expr(
457                f,
458                bound,
459                imports,
460                diagnostics,
461            )),
462            std::sync::Arc::new(rewrite_import_projections_expr(
463                x,
464                bound,
465                imports,
466                diagnostics,
467            )),
468        ),
469        Expr::Lam(span, scope, param, ann, constraints, body) => {
470            let ann = ann
471                .as_ref()
472                .map(|t| rewrite_import_projections_type_expr(t, bound, imports));
473            let constraints = constraints
474                .iter()
475                .map(|c| TypeConstraint {
476                    class: rewrite_import_projections_class_name(&c.class, bound, imports),
477                    typ: rewrite_import_projections_type_expr(&c.typ, bound, imports),
478                })
479                .collect();
480            bound.insert(param.name.clone());
481            let out = Expr::Lam(
482                *span,
483                scope.clone(),
484                param.clone(),
485                ann,
486                constraints,
487                std::sync::Arc::new(rewrite_import_projections_expr(
488                    body,
489                    bound,
490                    imports,
491                    diagnostics,
492                )),
493            );
494            bound.remove(&param.name);
495            out
496        }
497        Expr::Let(span, var, ann, val, body) => {
498            let val = std::sync::Arc::new(rewrite_import_projections_expr(
499                val,
500                bound,
501                imports,
502                diagnostics,
503            ));
504            bound.insert(var.name.clone());
505            let body = std::sync::Arc::new(rewrite_import_projections_expr(
506                body,
507                bound,
508                imports,
509                diagnostics,
510            ));
511            bound.remove(&var.name);
512            Expr::Let(
513                *span,
514                var.clone(),
515                ann.as_ref()
516                    .map(|t| rewrite_import_projections_type_expr(t, bound, imports)),
517                val,
518                body,
519            )
520        }
521        Expr::LetRec(span, bindings, body) => {
522            let anns: Vec<Option<TypeExpr>> = bindings
523                .iter()
524                .map(|(_, ann, _)| {
525                    ann.as_ref()
526                        .map(|t| rewrite_import_projections_type_expr(t, bound, imports))
527                })
528                .collect();
529            let names: Vec<Symbol> = bindings
530                .iter()
531                .map(|(var, _, _)| var.name.clone())
532                .collect();
533            for name in &names {
534                bound.insert(name.clone());
535            }
536            let bindings = bindings
537                .iter()
538                .zip(anns)
539                .map(|((var, _ann, def), ann)| {
540                    (
541                        var.clone(),
542                        ann,
543                        std::sync::Arc::new(rewrite_import_projections_expr(
544                            def,
545                            bound,
546                            imports,
547                            diagnostics,
548                        )),
549                    )
550                })
551                .collect();
552            let body = std::sync::Arc::new(rewrite_import_projections_expr(
553                body,
554                bound,
555                imports,
556                diagnostics,
557            ));
558            for name in &names {
559                bound.remove(name);
560            }
561            Expr::LetRec(*span, bindings, body)
562        }
563        Expr::Ite(span, c, t, e) => Expr::Ite(
564            *span,
565            std::sync::Arc::new(rewrite_import_projections_expr(
566                c,
567                bound,
568                imports,
569                diagnostics,
570            )),
571            std::sync::Arc::new(rewrite_import_projections_expr(
572                t,
573                bound,
574                imports,
575                diagnostics,
576            )),
577            std::sync::Arc::new(rewrite_import_projections_expr(
578                e,
579                bound,
580                imports,
581                diagnostics,
582            )),
583        ),
584        Expr::Match(span, scrutinee, arms) => {
585            let scrutinee = std::sync::Arc::new(rewrite_import_projections_expr(
586                scrutinee,
587                bound,
588                imports,
589                diagnostics,
590            ));
591            let mut out_arms = Vec::new();
592            for (pat, arm_expr) in arms {
593                let mut binds = Vec::new();
594                collect_pattern_bindings(pat, &mut binds);
595                for b in &binds {
596                    bound.insert(b.clone());
597                }
598                let arm_expr = std::sync::Arc::new(rewrite_import_projections_expr(
599                    arm_expr,
600                    bound,
601                    imports,
602                    diagnostics,
603                ));
604                for b in &binds {
605                    bound.remove(b);
606                }
607                out_arms.push((pat.clone(), arm_expr));
608            }
609            Expr::Match(*span, scrutinee, out_arms)
610        }
611        Expr::Ann(span, e, t) => Expr::Ann(
612            *span,
613            std::sync::Arc::new(rewrite_import_projections_expr(
614                e,
615                bound,
616                imports,
617                diagnostics,
618            )),
619            rewrite_import_projections_type_expr(t, bound, imports),
620        ),
621    }
622}
623
624fn qualified_alias_member(name: &NameRef) -> Option<(&Symbol, &Symbol)> {
625    match name {
626        NameRef::Qualified(_, segments) if segments.len() == 2 => {
627            Some((&segments[0], &segments[1]))
628        }
629        _ => None,
630    }
631}
632
633fn rewrite_import_projections_class_name(
634    class: &NameRef,
635    bound: &BTreeSet<Symbol>,
636    imports: &HashMap<Symbol, ImportModuleInfo>,
637) -> NameRef {
638    let Some((alias, member)) = qualified_alias_member(class) else {
639        return class.clone();
640    };
641    if bound.contains(alias) {
642        return class.clone();
643    }
644    let Some(info) = imports.get(alias) else {
645        return class.clone();
646    };
647    info.class_map
648        .get(member)
649        .map(|s| NameRef::Unqualified(s.clone()))
650        .unwrap_or_else(|| class.clone())
651}
652
653fn rewrite_import_projections_type_expr(
654    ty: &TypeExpr,
655    bound: &BTreeSet<Symbol>,
656    imports: &HashMap<Symbol, ImportModuleInfo>,
657) -> TypeExpr {
658    match ty {
659        TypeExpr::Name(span, name) => {
660            let Some((alias, member)) = qualified_alias_member(name) else {
661                return TypeExpr::Name(*span, name.clone());
662            };
663            if bound.contains(alias) {
664                return TypeExpr::Name(*span, name.clone());
665            }
666            let Some(info) = imports.get(alias) else {
667                return TypeExpr::Name(*span, name.clone());
668            };
669            if let Some(new) = info.type_map.get(member) {
670                TypeExpr::Name(*span, NameRef::Unqualified(new.clone()))
671            } else if let Some(new) = info.class_map.get(member) {
672                TypeExpr::Name(*span, NameRef::Unqualified(new.clone()))
673            } else {
674                TypeExpr::Name(*span, name.clone())
675            }
676        }
677        TypeExpr::App(span, f, x) => TypeExpr::App(
678            *span,
679            Box::new(rewrite_import_projections_type_expr(f, bound, imports)),
680            Box::new(rewrite_import_projections_type_expr(x, bound, imports)),
681        ),
682        TypeExpr::Fun(span, a, b) => TypeExpr::Fun(
683            *span,
684            Box::new(rewrite_import_projections_type_expr(a, bound, imports)),
685            Box::new(rewrite_import_projections_type_expr(b, bound, imports)),
686        ),
687        TypeExpr::Tuple(span, elems) => TypeExpr::Tuple(
688            *span,
689            elems
690                .iter()
691                .map(|e| rewrite_import_projections_type_expr(e, bound, imports))
692                .collect(),
693        ),
694        TypeExpr::Record(span, fields) => TypeExpr::Record(
695            *span,
696            fields
697                .iter()
698                .map(|(name, t)| {
699                    (
700                        name.clone(),
701                        rewrite_import_projections_type_expr(t, bound, imports),
702                    )
703                })
704                .collect(),
705        ),
706    }
707}
708
709fn rewrite_program_import_projections(
710    compilation_unit: &CompilationUnit,
711    imports: &HashMap<Symbol, ImportModuleInfo>,
712    diagnostics: &mut Vec<Diagnostic>,
713) -> CompilationUnit {
714    let decl_bound = BTreeSet::new();
715    let decls = compilation_unit
716        .decls
717        .iter()
718        .map(|decl| match decl {
719            Decl::Fn(fd) => {
720                let mut bound: BTreeSet<Symbol> =
721                    fd.params.iter().map(|(v, _)| v.name.clone()).collect();
722                let body = std::sync::Arc::new(rewrite_import_projections_expr(
723                    fd.body.as_ref(),
724                    &mut bound,
725                    imports,
726                    diagnostics,
727                ));
728                Decl::Fn(FnDecl {
729                    span: fd.span,
730                    is_pub: fd.is_pub,
731                    name: fd.name.clone(),
732                    params: fd
733                        .params
734                        .iter()
735                        .map(|(v, t)| {
736                            (
737                                v.clone(),
738                                rewrite_import_projections_type_expr(t, &decl_bound, imports),
739                            )
740                        })
741                        .collect(),
742                    ret: rewrite_import_projections_type_expr(&fd.ret, &decl_bound, imports),
743                    constraints: fd
744                        .constraints
745                        .iter()
746                        .map(|c| TypeConstraint {
747                            class: rewrite_import_projections_class_name(
748                                &c.class,
749                                &decl_bound,
750                                imports,
751                            ),
752                            typ: rewrite_import_projections_type_expr(&c.typ, &decl_bound, imports),
753                        })
754                        .collect(),
755                    body,
756                })
757            }
758            Decl::DeclareFn(df) => Decl::DeclareFn(DeclareFnDecl {
759                span: df.span,
760                is_pub: df.is_pub,
761                name: df.name.clone(),
762                params: df
763                    .params
764                    .iter()
765                    .map(|(v, t)| {
766                        (
767                            v.clone(),
768                            rewrite_import_projections_type_expr(t, &decl_bound, imports),
769                        )
770                    })
771                    .collect(),
772                ret: rewrite_import_projections_type_expr(&df.ret, &decl_bound, imports),
773                constraints: df
774                    .constraints
775                    .iter()
776                    .map(|c| TypeConstraint {
777                        class: rewrite_import_projections_class_name(
778                            &c.class,
779                            &decl_bound,
780                            imports,
781                        ),
782                        typ: rewrite_import_projections_type_expr(&c.typ, &decl_bound, imports),
783                    })
784                    .collect(),
785            }),
786            Decl::Type(td) => Decl::Type(TypeDecl {
787                span: td.span,
788                is_pub: td.is_pub,
789                name: td.name.clone(),
790                params: td.params.clone(),
791                variants: td
792                    .variants
793                    .iter()
794                    .map(|v| TypeVariant {
795                        name: v.name.clone(),
796                        args: v
797                            .args
798                            .iter()
799                            .map(|t| rewrite_import_projections_type_expr(t, &decl_bound, imports))
800                            .collect(),
801                    })
802                    .collect(),
803            }),
804            Decl::Class(cd) => Decl::Class(ClassDecl {
805                span: cd.span,
806                is_pub: cd.is_pub,
807                name: cd.name.clone(),
808                params: cd.params.clone(),
809                supers: cd
810                    .supers
811                    .iter()
812                    .map(|c| TypeConstraint {
813                        class: rewrite_import_projections_class_name(
814                            &c.class,
815                            &decl_bound,
816                            imports,
817                        ),
818                        typ: rewrite_import_projections_type_expr(&c.typ, &decl_bound, imports),
819                    })
820                    .collect(),
821                methods: cd
822                    .methods
823                    .iter()
824                    .map(|m| ClassMethodSig {
825                        name: m.name.clone(),
826                        typ: rewrite_import_projections_type_expr(&m.typ, &decl_bound, imports),
827                    })
828                    .collect(),
829            }),
830            Decl::Instance(inst) => {
831                let methods = inst
832                    .methods
833                    .iter()
834                    .map(|m| {
835                        let mut bound = BTreeSet::new();
836                        let body = std::sync::Arc::new(rewrite_import_projections_expr(
837                            m.body.as_ref(),
838                            &mut bound,
839                            imports,
840                            diagnostics,
841                        ));
842                        InstanceMethodImpl {
843                            name: m.name.clone(),
844                            body,
845                        }
846                    })
847                    .collect();
848                Decl::Instance(InstanceDecl {
849                    span: inst.span,
850                    is_pub: inst.is_pub,
851                    class: rewrite_import_projections_class_name(
852                        &NameRef::from_dotted(inst.class.as_ref()),
853                        &decl_bound,
854                        imports,
855                    )
856                    .to_dotted_symbol(),
857                    head: rewrite_import_projections_type_expr(&inst.head, &decl_bound, imports),
858                    context: inst
859                        .context
860                        .iter()
861                        .map(|c| TypeConstraint {
862                            class: rewrite_import_projections_class_name(
863                                &c.class,
864                                &decl_bound,
865                                imports,
866                            ),
867                            typ: rewrite_import_projections_type_expr(&c.typ, &decl_bound, imports),
868                        })
869                        .collect(),
870                    methods,
871                })
872            }
873            other => other.clone(),
874        })
875        .collect();
876
877    let body = compilation_unit.body.as_ref().map(|body| {
878        let mut bound = BTreeSet::new();
879        std::sync::Arc::new(rewrite_import_projections_expr(
880            body.as_ref(),
881            &mut bound,
882            imports,
883            diagnostics,
884        ))
885    });
886
887    CompilationUnit { decls, body }
888}
889
890fn validate_import_projection_class_name(
891    class: &NameRef,
892    span: Span,
893    bound: &BTreeSet<Symbol>,
894    imports: &HashMap<Symbol, ImportModuleInfo>,
895    diagnostics: &mut Vec<Diagnostic>,
896) {
897    let Some((alias, member)) = qualified_alias_member(class) else {
898        return;
899    };
900    if bound.contains(alias) {
901        return;
902    }
903    let Some(info) = imports.get(alias) else {
904        return;
905    };
906    if info.class_map.contains_key(member) {
907        return;
908    }
909    diagnostics.push(diagnostic_for_span(
910        span,
911        format!("module `{alias}` does not export `{member}`"),
912    ));
913}
914
915fn validate_import_projection_type_expr(
916    ty: &TypeExpr,
917    bound: &BTreeSet<Symbol>,
918    imports: &HashMap<Symbol, ImportModuleInfo>,
919    diagnostics: &mut Vec<Diagnostic>,
920) {
921    match ty {
922        TypeExpr::Name(span, name) => {
923            let Some((alias, member)) = qualified_alias_member(name) else {
924                return;
925            };
926            if bound.contains(alias) {
927                return;
928            }
929            let Some(info) = imports.get(alias) else {
930                return;
931            };
932            if info.type_map.contains_key(member) || info.class_map.contains_key(member) {
933                return;
934            }
935            diagnostics.push(diagnostic_for_span(
936                *span,
937                format!("module `{alias}` does not export `{member}`"),
938            ));
939        }
940        TypeExpr::App(_, f, x) => {
941            validate_import_projection_type_expr(f, bound, imports, diagnostics);
942            validate_import_projection_type_expr(x, bound, imports, diagnostics);
943        }
944        TypeExpr::Fun(_, a, b) => {
945            validate_import_projection_type_expr(a, bound, imports, diagnostics);
946            validate_import_projection_type_expr(b, bound, imports, diagnostics);
947        }
948        TypeExpr::Tuple(_, elems) => {
949            for e in elems {
950                validate_import_projection_type_expr(e, bound, imports, diagnostics);
951            }
952        }
953        TypeExpr::Record(_, fields) => {
954            for (_, t) in fields {
955                validate_import_projection_type_expr(t, bound, imports, diagnostics);
956            }
957        }
958    }
959}
960
961fn validate_import_projection_expr(
962    expr: &Expr,
963    bound: &mut BTreeSet<Symbol>,
964    imports: &HashMap<Symbol, ImportModuleInfo>,
965    diagnostics: &mut Vec<Diagnostic>,
966) {
967    match expr {
968        Expr::Lam(_, _, param, ann, constraints, body) => {
969            if let Some(ann) = ann {
970                validate_import_projection_type_expr(ann, bound, imports, diagnostics);
971            }
972            for c in constraints {
973                validate_import_projection_class_name(
974                    &c.class,
975                    *c.typ.span(),
976                    bound,
977                    imports,
978                    diagnostics,
979                );
980                validate_import_projection_type_expr(&c.typ, bound, imports, diagnostics);
981            }
982            bound.insert(param.name.clone());
983            validate_import_projection_expr(body, bound, imports, diagnostics);
984            bound.remove(&param.name);
985        }
986        Expr::Let(_, var, ann, val, body) => {
987            if let Some(ann) = ann {
988                validate_import_projection_type_expr(ann, bound, imports, diagnostics);
989            }
990            validate_import_projection_expr(val, bound, imports, diagnostics);
991            bound.insert(var.name.clone());
992            validate_import_projection_expr(body, bound, imports, diagnostics);
993            bound.remove(&var.name);
994        }
995        Expr::LetRec(_, bindings, body) => {
996            for (_, ann, _) in bindings {
997                if let Some(ann) = ann {
998                    validate_import_projection_type_expr(ann, bound, imports, diagnostics);
999                }
1000            }
1001            let names: Vec<_> = bindings
1002                .iter()
1003                .map(|(var, _, _)| var.name.clone())
1004                .collect();
1005            for name in &names {
1006                bound.insert(name.clone());
1007            }
1008            for (_, _ann, def) in bindings {
1009                validate_import_projection_expr(def, bound, imports, diagnostics);
1010            }
1011            validate_import_projection_expr(body, bound, imports, diagnostics);
1012            for name in &names {
1013                bound.remove(name);
1014            }
1015        }
1016        Expr::Match(_, scrutinee, arms) => {
1017            validate_import_projection_expr(scrutinee, bound, imports, diagnostics);
1018            for (pat, arm_expr) in arms {
1019                let mut binds = Vec::new();
1020                collect_pattern_bindings(pat, &mut binds);
1021                for b in &binds {
1022                    bound.insert(b.clone());
1023                }
1024                validate_import_projection_expr(arm_expr, bound, imports, diagnostics);
1025                for b in &binds {
1026                    bound.remove(b);
1027                }
1028            }
1029        }
1030        Expr::Tuple(_, elems) | Expr::List(_, elems) => {
1031            for e in elems {
1032                validate_import_projection_expr(e, bound, imports, diagnostics);
1033            }
1034        }
1035        Expr::Dict(_, kvs) => {
1036            for v in kvs.values() {
1037                validate_import_projection_expr(v, bound, imports, diagnostics);
1038            }
1039        }
1040        Expr::RecordUpdate(_, base, updates) => {
1041            validate_import_projection_expr(base, bound, imports, diagnostics);
1042            for v in updates.values() {
1043                validate_import_projection_expr(v, bound, imports, diagnostics);
1044            }
1045        }
1046        Expr::App(_, f, x) => {
1047            validate_import_projection_expr(f, bound, imports, diagnostics);
1048            validate_import_projection_expr(x, bound, imports, diagnostics);
1049        }
1050        Expr::Ite(_, c, t, e) => {
1051            validate_import_projection_expr(c, bound, imports, diagnostics);
1052            validate_import_projection_expr(t, bound, imports, diagnostics);
1053            validate_import_projection_expr(e, bound, imports, diagnostics);
1054        }
1055        Expr::Ann(_, e, t) => {
1056            validate_import_projection_expr(e, bound, imports, diagnostics);
1057            validate_import_projection_type_expr(t, bound, imports, diagnostics);
1058        }
1059        Expr::Project(_, base, _) => {
1060            validate_import_projection_expr(base, bound, imports, diagnostics);
1061        }
1062        Expr::Var(..)
1063        | Expr::Bool(..)
1064        | Expr::Uint(..)
1065        | Expr::Int(..)
1066        | Expr::Float(..)
1067        | Expr::String(..)
1068        | Expr::Uuid(..)
1069        | Expr::DateTime(..)
1070        | Expr::Hole(..) => {}
1071    }
1072}
1073
1074fn validate_import_projection_uses(
1075    compilation_unit: &CompilationUnit,
1076    imports: &HashMap<Symbol, ImportModuleInfo>,
1077    diagnostics: &mut Vec<Diagnostic>,
1078) {
1079    let decl_bound = BTreeSet::new();
1080    for decl in &compilation_unit.decls {
1081        match decl {
1082            Decl::Fn(fd) => {
1083                for (_, t) in &fd.params {
1084                    validate_import_projection_type_expr(t, &decl_bound, imports, diagnostics);
1085                }
1086                validate_import_projection_type_expr(&fd.ret, &decl_bound, imports, diagnostics);
1087                for c in &fd.constraints {
1088                    validate_import_projection_class_name(
1089                        &c.class,
1090                        *c.typ.span(),
1091                        &decl_bound,
1092                        imports,
1093                        diagnostics,
1094                    );
1095                    validate_import_projection_type_expr(&c.typ, &decl_bound, imports, diagnostics);
1096                }
1097                let mut bound: BTreeSet<Symbol> =
1098                    fd.params.iter().map(|(v, _)| v.name.clone()).collect();
1099                validate_import_projection_expr(fd.body.as_ref(), &mut bound, imports, diagnostics);
1100            }
1101            Decl::DeclareFn(df) => {
1102                for (_, t) in &df.params {
1103                    validate_import_projection_type_expr(t, &decl_bound, imports, diagnostics);
1104                }
1105                validate_import_projection_type_expr(&df.ret, &decl_bound, imports, diagnostics);
1106                for c in &df.constraints {
1107                    validate_import_projection_class_name(
1108                        &c.class,
1109                        *c.typ.span(),
1110                        &decl_bound,
1111                        imports,
1112                        diagnostics,
1113                    );
1114                    validate_import_projection_type_expr(&c.typ, &decl_bound, imports, diagnostics);
1115                }
1116            }
1117            Decl::Type(td) => {
1118                for v in &td.variants {
1119                    for t in &v.args {
1120                        validate_import_projection_type_expr(t, &decl_bound, imports, diagnostics);
1121                    }
1122                }
1123            }
1124            Decl::Class(cd) => {
1125                for c in &cd.supers {
1126                    validate_import_projection_class_name(
1127                        &c.class,
1128                        *c.typ.span(),
1129                        &decl_bound,
1130                        imports,
1131                        diagnostics,
1132                    );
1133                    validate_import_projection_type_expr(&c.typ, &decl_bound, imports, diagnostics);
1134                }
1135                for m in &cd.methods {
1136                    validate_import_projection_type_expr(&m.typ, &decl_bound, imports, diagnostics);
1137                }
1138            }
1139            Decl::Instance(inst) => {
1140                validate_import_projection_class_name(
1141                    &NameRef::from_dotted(inst.class.as_ref()),
1142                    inst.span,
1143                    &decl_bound,
1144                    imports,
1145                    diagnostics,
1146                );
1147                validate_import_projection_type_expr(&inst.head, &decl_bound, imports, diagnostics);
1148                for c in &inst.context {
1149                    validate_import_projection_class_name(
1150                        &c.class,
1151                        *c.typ.span(),
1152                        &decl_bound,
1153                        imports,
1154                        diagnostics,
1155                    );
1156                    validate_import_projection_type_expr(&c.typ, &decl_bound, imports, diagnostics);
1157                }
1158                for m in &inst.methods {
1159                    let mut bound = BTreeSet::new();
1160                    validate_import_projection_expr(
1161                        m.body.as_ref(),
1162                        &mut bound,
1163                        imports,
1164                        diagnostics,
1165                    );
1166                }
1167            }
1168            Decl::Import(..) => {}
1169        }
1170    }
1171    if let Some(body) = &compilation_unit.body {
1172        let mut bound = BTreeSet::new();
1173        validate_import_projection_expr(body.as_ref(), &mut bound, imports, diagnostics);
1174    }
1175}
1176
1177pub type PreparedProgram = (
1178    CompilationUnit,
1179    TypeSystem,
1180    HashMap<Symbol, ImportModuleInfo>,
1181    Vec<Diagnostic>,
1182);
1183
1184pub fn prepare_program_with_imports(
1185    uri: &Url,
1186    compilation_unit: &CompilationUnit,
1187) -> std::result::Result<PreparedProgram, String> {
1188    let mut ts =
1189        TypeSystem::new_with_prelude().map_err(|e| format!("failed to build prelude: {e}"))?;
1190    let mut diagnostics = Vec::new();
1191
1192    let importer = uri_to_file_path(uri);
1193
1194    let mut imports: HashMap<Symbol, ImportModuleInfo> = HashMap::new();
1195
1196    for decl in &compilation_unit.decls {
1197        let Decl::Import(ImportDecl {
1198            span, path, alias, ..
1199        }) = decl
1200        else {
1201            continue;
1202        };
1203        let import_span = *span;
1204
1205        let (segments, expected_sha) = match path {
1206            ImportPath::Local { segments, sha } => (segments.as_slice(), sha.as_deref()),
1207            ImportPath::Remote { .. } => {
1208                // LSP does not attempt network fetches; leave it unresolved.
1209                continue;
1210            }
1211        };
1212
1213        let module_name = segments
1214            .iter()
1215            .map(|s| s.as_ref())
1216            .collect::<Vec<_>>()
1217            .join(".");
1218
1219        let (module_path, hash, source, module_label, keep_constraints) =
1220            if let Some(source) = stdlib_source(&module_name) {
1221                let hash = sha256_hex(source.as_bytes());
1222                if let Some(expected) = expected_sha {
1223                    let expected = expected.to_ascii_lowercase();
1224                    if !hash.starts_with(&expected) {
1225                        diagnostics.push(diagnostic_for_span(
1226                        import_span,
1227                        format!(
1228                            "sha mismatch for `{module_name}`: expected #{expected}, got #{hash}",
1229                        ),
1230                    ));
1231                    }
1232                }
1233                (None, hash, source.to_string(), module_name, true)
1234            } else {
1235                let Some(importer) = importer.as_ref() else {
1236                    // Without a stable file location we cannot resolve local imports.
1237                    // (Stdlib imports are handled above.)
1238                    continue;
1239                };
1240                let Some(base_dir) = importer.parent() else {
1241                    diagnostics.push(diagnostic_for_span(
1242                        import_span,
1243                        "cannot resolve local import without a base directory".to_string(),
1244                    ));
1245                    continue;
1246                };
1247                let module_path = match resolve_local_import_path(base_dir, segments) {
1248                    Ok(Some(p)) => p,
1249                    Ok(None) => {
1250                        diagnostics.push(diagnostic_for_span(
1251                            import_span,
1252                            format!("module not found for import `{module_name}`"),
1253                        ));
1254                        continue;
1255                    }
1256                    Err(err) => {
1257                        diagnostics.push(diagnostic_for_span(import_span, err.to_string()));
1258                        continue;
1259                    }
1260                };
1261                let Ok(module_path) = module_path.canonicalize() else {
1262                    diagnostics.push(diagnostic_for_span(
1263                        import_span,
1264                        format!("module not found for import `{module_name}`"),
1265                    ));
1266                    continue;
1267                };
1268
1269                let bytes = match fs::read(&module_path) {
1270                    Ok(b) => b,
1271                    Err(e) => {
1272                        diagnostics.push(diagnostic_for_span(
1273                            import_span,
1274                            format!("failed to read module `{}`: {e}", module_path.display()),
1275                        ));
1276                        continue;
1277                    }
1278                };
1279                let hash = sha256_hex(&bytes);
1280                if let Some(expected) = expected_sha {
1281                    let expected = expected.to_ascii_lowercase();
1282                    if !hash.starts_with(&expected) {
1283                        diagnostics.push(diagnostic_for_span(
1284                            import_span,
1285                            format!(
1286                                "sha mismatch for `{}`: expected #{expected}, got #{hash}",
1287                                module_path.display()
1288                            ),
1289                        ));
1290                    }
1291                }
1292
1293                let source = match String::from_utf8(bytes) {
1294                    Ok(s) => s,
1295                    Err(e) => {
1296                        diagnostics.push(diagnostic_for_span(
1297                            import_span,
1298                            format!("module `{}` is not utf-8: {e}", module_path.display()),
1299                        ));
1300                        continue;
1301                    }
1302                };
1303                (
1304                    Some(module_path.clone()),
1305                    hash,
1306                    source,
1307                    module_path.display().to_string(),
1308                    false,
1309                )
1310            };
1311
1312        let (tokens, module_program) = match tokenize_and_parse(&source) {
1313            Ok(v) => v,
1314            Err(TokenizeOrParseError::Lex(err)) => {
1315                let msg = match err {
1316                    LexicalError::UnexpectedToken(span) => format!(
1317                        "lex error in module `{}` at {}:{}",
1318                        module_label, span.begin.line, span.begin.column
1319                    ),
1320                    LexicalError::InvalidLiteral {
1321                        kind,
1322                        text,
1323                        error,
1324                        span,
1325                    } => format!(
1326                        "lex error in module `{}` at {}:{}: invalid {kind} literal `{text}`: {error}",
1327                        module_label, span.begin.line, span.begin.column
1328                    ),
1329                    LexicalError::Internal(msg) => {
1330                        format!("internal lexer error in module `{module_label}`: {msg}")
1331                    }
1332                };
1333                diagnostics.push(diagnostic_for_span(import_span, msg));
1334                continue;
1335            }
1336            Err(TokenizeOrParseError::Parse(errs)) => {
1337                for err in errs {
1338                    diagnostics.push(diagnostic_for_span(
1339                        import_span,
1340                        format!(
1341                            "parse error in module `{}` at {}:{}: {}",
1342                            module_label, err.span.begin.line, err.span.begin.column, err.message
1343                        ),
1344                    ));
1345                    if diagnostics.len() >= MAX_DIAGNOSTICS {
1346                        break;
1347                    }
1348                }
1349                continue;
1350            }
1351        };
1352
1353        let index = index_decl_spans(&module_program, &tokens);
1354        let prefix = module_prefix(&hash);
1355
1356        let mut type_map: HashMap<Symbol, Symbol> = HashMap::new();
1357        let mut class_map: HashMap<Symbol, Symbol> = HashMap::new();
1358        for decl in &module_program.decls {
1359            match decl {
1360                Decl::Type(td) => {
1361                    type_map.insert(
1362                        td.name.clone(),
1363                        Symbol::intern(&format!("{prefix}.{}", td.name.as_ref())),
1364                    );
1365                }
1366                Decl::Class(cd) => {
1367                    class_map.insert(
1368                        cd.name.clone(),
1369                        Symbol::intern(&format!("{prefix}.{}", cd.name.as_ref())),
1370                    );
1371                }
1372                _ => {}
1373            }
1374        }
1375
1376        // Inject module type decls (renamed) so exported signatures can refer to them.
1377        for decl in &module_program.decls {
1378            let Decl::Type(td) = decl else { continue };
1379            let name = type_map
1380                .get(&td.name)
1381                .cloned()
1382                .unwrap_or_else(|| td.name.clone());
1383            let variants = td
1384                .variants
1385                .iter()
1386                .map(|v| TypeVariant {
1387                    name: Symbol::intern(&format!("{prefix}.{}", v.name.as_ref())),
1388                    args: v
1389                        .args
1390                        .iter()
1391                        .map(|t| rewrite_type_expr(t, &type_map))
1392                        .collect(),
1393                })
1394                .collect();
1395            let td2 = TypeDecl {
1396                span: td.span,
1397                is_pub: td.is_pub,
1398                name,
1399                params: td.params.clone(),
1400                variants,
1401            };
1402            let _ = ts.register_type_decl(&td2);
1403        }
1404
1405        let mut value_map: HashMap<Symbol, Symbol> = HashMap::new();
1406        let mut export_names: BTreeSet<String> = BTreeSet::new();
1407
1408        // Exported functions (pub only)
1409        for decl in &module_program.decls {
1410            match decl {
1411                Decl::Fn(fd) if fd.is_pub => {
1412                    let internal = Symbol::intern(&format!("{prefix}.{}", fd.name.name.as_ref()));
1413                    value_map.insert(Symbol::intern(fd.name.name.as_ref()), internal.clone());
1414                    export_names.insert(fd.name.name.as_ref().to_string());
1415
1416                    let params = fd
1417                        .params
1418                        .iter()
1419                        .map(|(v, ty)| (v.clone(), rewrite_type_expr(ty, &type_map)))
1420                        .collect();
1421                    let ret = rewrite_type_expr(&fd.ret, &type_map);
1422                    let decl = DeclareFnDecl {
1423                        span: fd.span,
1424                        is_pub: true,
1425                        name: Var {
1426                            span: fd.name.span,
1427                            name: internal,
1428                        },
1429                        params,
1430                        ret,
1431                        constraints: if keep_constraints {
1432                            fd.constraints.clone()
1433                        } else {
1434                            Default::default()
1435                        },
1436                    };
1437                    let _ = ts.inject_declare_fn_decl(&decl);
1438                }
1439                Decl::DeclareFn(df) if df.is_pub => {
1440                    let internal = Symbol::intern(&format!("{prefix}.{}", df.name.name.as_ref()));
1441                    value_map.insert(Symbol::intern(df.name.name.as_ref()), internal.clone());
1442                    export_names.insert(df.name.name.as_ref().to_string());
1443
1444                    let params = df
1445                        .params
1446                        .iter()
1447                        .map(|(v, ty)| (v.clone(), rewrite_type_expr(ty, &type_map)))
1448                        .collect();
1449                    let ret = rewrite_type_expr(&df.ret, &type_map);
1450                    let decl = DeclareFnDecl {
1451                        span: df.span,
1452                        is_pub: true,
1453                        name: Var {
1454                            span: df.name.span,
1455                            name: internal,
1456                        },
1457                        params,
1458                        ret,
1459                        constraints: if keep_constraints {
1460                            df.constraints.clone()
1461                        } else {
1462                            Default::default()
1463                        },
1464                    };
1465                    let _ = ts.inject_declare_fn_decl(&decl);
1466                }
1467                Decl::Type(td) if td.is_pub => {
1468                    // Public constructors are accessible as values.
1469                    for variant in &td.variants {
1470                        let internal =
1471                            Symbol::intern(&format!("{prefix}.{}", variant.name.as_ref()));
1472                        value_map.insert(variant.name.clone(), internal);
1473                        export_names.insert(variant.name.as_ref().to_string());
1474                    }
1475                }
1476                _ => {}
1477            }
1478        }
1479
1480        let mut export_defs = HashMap::new();
1481        for name in &export_names {
1482            if let Some(span) = index
1483                .fn_defs
1484                .get(name)
1485                .copied()
1486                .or_else(|| index.ctor_defs.get(name).copied())
1487            {
1488                export_defs.insert(name.clone(), span);
1489            }
1490        }
1491
1492        imports.insert(
1493            alias.clone(),
1494            ImportModuleInfo {
1495                path: module_path,
1496                value_map,
1497                type_map,
1498                class_map,
1499                export_defs,
1500            },
1501        );
1502    }
1503
1504    validate_import_projection_uses(compilation_unit, &imports, &mut diagnostics);
1505    let rewritten =
1506        rewrite_program_import_projections(compilation_unit, &imports, &mut diagnostics);
1507    Ok((rewritten, ts, imports, diagnostics))
1508}
1509
1510fn completion_exports_for_module_alias(
1511    uri: &Url,
1512    compilation_unit: &CompilationUnit,
1513    alias: &str,
1514) -> std::result::Result<Vec<String>, String> {
1515    let alias_sym = Symbol::intern(alias);
1516    let Some(import_decl) = compilation_unit.decls.iter().find_map(|d| {
1517        let Decl::Import(id) = d else { return None };
1518        if id.alias == alias_sym {
1519            Some(id)
1520        } else {
1521            None
1522        }
1523    }) else {
1524        return Ok(Vec::new());
1525    };
1526
1527    let ImportPath::Local { segments, sha: _ } = &import_decl.path else {
1528        return Ok(Vec::new());
1529    };
1530
1531    let module_name = segments
1532        .iter()
1533        .map(|s| s.as_ref())
1534        .collect::<Vec<_>>()
1535        .join(".");
1536
1537    let source = if let Some(source) = stdlib_source(&module_name) {
1538        source.to_string()
1539    } else {
1540        let importer = uri_to_file_path(uri).ok_or_else(|| "not a file uri".to_string())?;
1541        let Some(base_dir) = importer.parent() else {
1542            return Ok(Vec::new());
1543        };
1544        let Some(module_path) = resolve_local_import_path(base_dir, segments)
1545            .ok()
1546            .flatten()
1547            .and_then(|p| p.canonicalize().ok())
1548        else {
1549            return Ok(Vec::new());
1550        };
1551        fs::read_to_string(&module_path).map_err(|e| e.to_string())?
1552    };
1553    let (_tokens, module_program) =
1554        tokenize_and_parse(&source).map_err(|_| "parse error".to_string())?;
1555
1556    let mut exports = BTreeSet::new();
1557    for decl in &module_program.decls {
1558        match decl {
1559            Decl::Fn(fd) if fd.is_pub => {
1560                exports.insert(fd.name.name.as_ref().to_string());
1561            }
1562            Decl::DeclareFn(df) if df.is_pub => {
1563                exports.insert(df.name.name.as_ref().to_string());
1564            }
1565            Decl::Type(td) if td.is_pub => {
1566                for variant in &td.variants {
1567                    exports.insert(variant.name.as_ref().to_string());
1568                }
1569            }
1570            _ => {}
1571        }
1572    }
1573    Ok(exports.into_iter().collect())
1574}
1575
1576pub(crate) fn goto_definition_response(
1577    uri: &Url,
1578    text: &str,
1579    position: Position,
1580) -> Option<GotoDefinitionResponse> {
1581    // Parse on-demand. This keeps steady-state typing latency low; “go to
1582    // definition” is an explicit user action where a little work is fine.
1583    let Ok((tokens, program)) = tokenize_and_parse_cached(uri, text) else {
1584        return None;
1585    };
1586
1587    let imported_projection = imported_projection_at_position(&tokens, position);
1588
1589    let (ident, _token_span) = ident_token_at_position(&tokens, position)?;
1590
1591    // If the cursor is on `alias.field` and `alias` is a local import, jump
1592    // to the exported declaration in the imported module.
1593    if let Some((alias, field)) = imported_projection
1594        && let Ok((_rewritten, _ts, imports, _diags)) = prepare_program_with_imports(uri, &program)
1595    {
1596        let alias_sym = Symbol::intern(&alias);
1597        if let Some(info) = imports.get(&alias_sym)
1598            && let Some(span) = info.export_defs.get(&field)
1599            && let Some(path) = info.path.as_ref()
1600            && let Some(module_uri) = url_from_file_path(path)
1601        {
1602            return Some(GotoDefinitionResponse::Scalar(Location {
1603                uri: module_uri,
1604                range: span_to_range(*span),
1605            }));
1606        }
1607    }
1608
1609    let index = index_decl_spans(&program, &tokens);
1610    let pos = lsp_to_rex_position(position);
1611
1612    // Pick the expression tree that actually contains the cursor. Top-level
1613    // instance method bodies are not part of `body_with_fns()`, so we have
1614    // to handle them explicitly.
1615    let body_with_fns = program.body_with_fns();
1616    let mut root_expr = body_with_fns.as_deref();
1617    for decl in &program.decls {
1618        let Decl::Instance(inst) = decl else {
1619            continue;
1620        };
1621        for method in &inst.methods {
1622            if position_in_span(pos, *method.body.span()) {
1623                root_expr = Some(method.body.as_ref());
1624                break;
1625            }
1626        }
1627    }
1628
1629    let value_def = root_expr.and_then(|expr| {
1630        definition_span_for_value_ident(expr, pos, &ident, &mut Vec::new(), &tokens)
1631    });
1632
1633    let instance_method_def = index
1634        .instance_method_defs
1635        .iter()
1636        .find_map(|(span, methods)| {
1637            if position_in_span(pos, *span) {
1638                methods.get(&ident).copied()
1639            } else {
1640                None
1641            }
1642        });
1643
1644    let target_span = value_def
1645        .or(instance_method_def)
1646        .or(index.class_method_defs.get(&ident).copied())
1647        .or(index.fn_defs.get(&ident).copied())
1648        .or(index.ctor_defs.get(&ident).copied())
1649        .or(index.type_defs.get(&ident).copied())
1650        .or(index.class_defs.get(&ident).copied())?;
1651
1652    Some(GotoDefinitionResponse::Scalar(Location {
1653        uri: uri.clone(),
1654        range: span_to_range(target_span),
1655    }))
1656}
1657
1658fn range_to_span(range: Range) -> Span {
1659    Span::new(
1660        (range.start.line + 1) as usize,
1661        (range.start.character + 1) as usize,
1662        (range.end.line + 1) as usize,
1663        (range.end.character + 1) as usize,
1664    )
1665}
1666
1667fn pattern_bindings_with_spans(pat: &Pattern, out: &mut Vec<(String, Span)>) {
1668    match pat {
1669        Pattern::Var(var) => out.push((var.name.to_string(), var.span)),
1670        Pattern::Named(_, _, args) => {
1671            for arg in args {
1672                pattern_bindings_with_spans(arg, out);
1673            }
1674        }
1675        Pattern::Tuple(_, elems) | Pattern::List(_, elems) => {
1676            for elem in elems {
1677                pattern_bindings_with_spans(elem, out);
1678            }
1679        }
1680        Pattern::Cons(_, head, tail) => {
1681            pattern_bindings_with_spans(head, out);
1682            pattern_bindings_with_spans(tail, out);
1683        }
1684        Pattern::Dict(_, fields) => {
1685            for (_, pat) in fields {
1686                pattern_bindings_with_spans(pat, out);
1687            }
1688        }
1689        Pattern::Wildcard(..) => {}
1690    }
1691}
1692
1693fn collect_references_in_expr(
1694    expr: &Expr,
1695    ident: &str,
1696    target_span: Span,
1697    uri: &Url,
1698    top_level_defs: &HashMap<String, Span>,
1699    scope: &mut Vec<(String, Span)>,
1700    out: &mut Vec<Location>,
1701) {
1702    match expr {
1703        Expr::Var(var) => {
1704            if var.name.as_ref() != ident {
1705                return;
1706            }
1707            let resolved = scope
1708                .iter()
1709                .rev()
1710                .find_map(|(name, span)| (name == ident).then_some(*span))
1711                .or_else(|| top_level_defs.get(ident).copied());
1712            if resolved.is_some_and(|span| span == target_span) {
1713                out.push(Location {
1714                    uri: uri.clone(),
1715                    range: span_to_range(var.span),
1716                });
1717            }
1718        }
1719        Expr::Let(_, var, _ann, def, body) => {
1720            collect_references_in_expr(def, ident, target_span, uri, top_level_defs, scope, out);
1721            scope.push((var.name.to_string(), var.span));
1722            collect_references_in_expr(body, ident, target_span, uri, top_level_defs, scope, out);
1723            scope.pop();
1724        }
1725        Expr::LetRec(_, bindings, body) => {
1726            let base_len = scope.len();
1727            for (var, _ann, _def) in bindings {
1728                scope.push((var.name.to_string(), var.span));
1729            }
1730            for (_var, _ann, def) in bindings {
1731                collect_references_in_expr(
1732                    def,
1733                    ident,
1734                    target_span,
1735                    uri,
1736                    top_level_defs,
1737                    scope,
1738                    out,
1739                );
1740            }
1741            collect_references_in_expr(body, ident, target_span, uri, top_level_defs, scope, out);
1742            scope.truncate(base_len);
1743        }
1744        Expr::Lam(_, _scope, param, _ann, _constraints, body) => {
1745            scope.push((param.name.to_string(), param.span));
1746            collect_references_in_expr(body, ident, target_span, uri, top_level_defs, scope, out);
1747            scope.pop();
1748        }
1749        Expr::Match(_, scrutinee, arms) => {
1750            collect_references_in_expr(
1751                scrutinee,
1752                ident,
1753                target_span,
1754                uri,
1755                top_level_defs,
1756                scope,
1757                out,
1758            );
1759            for (pat, arm) in arms {
1760                let base_len = scope.len();
1761                let mut binds = Vec::new();
1762                pattern_bindings_with_spans(pat, &mut binds);
1763                scope.extend(binds);
1764                collect_references_in_expr(
1765                    arm,
1766                    ident,
1767                    target_span,
1768                    uri,
1769                    top_level_defs,
1770                    scope,
1771                    out,
1772                );
1773                scope.truncate(base_len);
1774            }
1775        }
1776        Expr::App(_, fun, arg) => {
1777            collect_references_in_expr(fun, ident, target_span, uri, top_level_defs, scope, out);
1778            collect_references_in_expr(arg, ident, target_span, uri, top_level_defs, scope, out);
1779        }
1780        Expr::Project(_, base, _) => {
1781            collect_references_in_expr(base, ident, target_span, uri, top_level_defs, scope, out);
1782        }
1783        Expr::Tuple(_, elems) | Expr::List(_, elems) => {
1784            for elem in elems {
1785                collect_references_in_expr(
1786                    elem,
1787                    ident,
1788                    target_span,
1789                    uri,
1790                    top_level_defs,
1791                    scope,
1792                    out,
1793                );
1794            }
1795        }
1796        Expr::Dict(_, entries) => {
1797            for value in entries.values() {
1798                collect_references_in_expr(
1799                    value,
1800                    ident,
1801                    target_span,
1802                    uri,
1803                    top_level_defs,
1804                    scope,
1805                    out,
1806                );
1807            }
1808        }
1809        Expr::RecordUpdate(_, base, updates) => {
1810            collect_references_in_expr(base, ident, target_span, uri, top_level_defs, scope, out);
1811            for value in updates.values() {
1812                collect_references_in_expr(
1813                    value,
1814                    ident,
1815                    target_span,
1816                    uri,
1817                    top_level_defs,
1818                    scope,
1819                    out,
1820                );
1821            }
1822        }
1823        Expr::Ite(_, cond, then_expr, else_expr) => {
1824            collect_references_in_expr(cond, ident, target_span, uri, top_level_defs, scope, out);
1825            collect_references_in_expr(
1826                then_expr,
1827                ident,
1828                target_span,
1829                uri,
1830                top_level_defs,
1831                scope,
1832                out,
1833            );
1834            collect_references_in_expr(
1835                else_expr,
1836                ident,
1837                target_span,
1838                uri,
1839                top_level_defs,
1840                scope,
1841                out,
1842            );
1843        }
1844        Expr::Ann(_, inner, _) => {
1845            collect_references_in_expr(inner, ident, target_span, uri, top_level_defs, scope, out);
1846        }
1847        Expr::Bool(..)
1848        | Expr::Uint(..)
1849        | Expr::Int(..)
1850        | Expr::Float(..)
1851        | Expr::String(..)
1852        | Expr::Uuid(..)
1853        | Expr::DateTime(..)
1854        | Expr::Hole(..) => {}
1855    }
1856}
1857
1858pub(crate) fn references_for_source(
1859    uri: &Url,
1860    text: &str,
1861    position: Position,
1862    include_declaration: bool,
1863) -> Vec<Location> {
1864    let Ok((tokens, program)) = tokenize_and_parse_cached(uri, text) else {
1865        return Vec::new();
1866    };
1867    let Some((ident, _token_span)) = ident_token_at_position(&tokens, position) else {
1868        return Vec::new();
1869    };
1870
1871    let Some(def_response) = goto_definition_response(uri, text, position) else {
1872        return Vec::new();
1873    };
1874    let GotoDefinitionResponse::Scalar(def_location) = def_response else {
1875        return Vec::new();
1876    };
1877    if def_location.uri != *uri {
1878        return Vec::new();
1879    }
1880    let target_span = range_to_span(def_location.range);
1881
1882    let index = index_decl_spans(&program, &tokens);
1883    let mut top_level_defs = index.fn_defs;
1884    top_level_defs.extend(index.ctor_defs);
1885
1886    let mut refs = Vec::new();
1887    if include_declaration {
1888        refs.push(def_location);
1889    }
1890    if let Some(expr) = program.body_with_fns() {
1891        collect_references_in_expr(
1892            expr.as_ref(),
1893            &ident,
1894            target_span,
1895            uri,
1896            &top_level_defs,
1897            &mut Vec::new(),
1898            &mut refs,
1899        );
1900    }
1901    refs.sort_by_key(|location| {
1902        (
1903            location.range.start.line,
1904            location.range.start.character,
1905            location.range.end.line,
1906            location.range.end.character,
1907        )
1908    });
1909    refs.dedup_by(|a, b| a.range == b.range && a.uri == b.uri);
1910    refs
1911}
1912
1913pub(crate) fn rename_for_source(
1914    uri: &Url,
1915    text: &str,
1916    position: Position,
1917    new_name: &str,
1918) -> Option<WorkspaceEdit> {
1919    if !is_ident_like(new_name) {
1920        return None;
1921    }
1922    let refs = references_for_source(uri, text, position, true);
1923    if refs.is_empty() {
1924        return None;
1925    }
1926    let edits: Vec<TextEdit> = refs
1927        .into_iter()
1928        .map(|location| TextEdit {
1929            range: location.range,
1930            new_text: new_name.to_string(),
1931        })
1932        .collect();
1933    let mut changes = HashMap::new();
1934    changes.insert(uri.clone(), edits);
1935    Some(WorkspaceEdit {
1936        changes: Some(changes),
1937        document_changes: None,
1938        change_annotations: None,
1939    })
1940}
1941
1942pub fn code_actions_for_source(
1943    uri: &Url,
1944    text: &str,
1945    request_range: Range,
1946    diagnostics: &[Diagnostic],
1947) -> Vec<CodeActionOrCommand> {
1948    let parsed = tokenize_and_parse_cached(uri, text)
1949        .ok()
1950        .map(|(_tokens, program)| program);
1951    let mut actions = Vec::new();
1952
1953    // Hole fill is position-driven and should be available even when other diagnostics exist.
1954    actions.extend(code_actions_for_hole_fill(
1955        uri,
1956        text,
1957        parsed.as_ref(),
1958        request_range,
1959    ));
1960
1961    for diag in diagnostics {
1962        let usable_diag_range = range_is_usable_for_text(text, diag.range);
1963        if usable_diag_range
1964            && !range_is_empty(diag.range)
1965            && !ranges_overlap(diag.range, request_range)
1966            && !range_contains_position(diag.range, request_range.start)
1967            && !range_contains_position(diag.range, request_range.end)
1968        {
1969            continue;
1970        }
1971        actions.extend(code_actions_for_diagnostic(
1972            uri,
1973            text,
1974            parsed.as_ref(),
1975            request_range,
1976            diag,
1977        ));
1978    }
1979
1980    actions
1981}
1982
1983fn code_actions_for_hole_fill(
1984    uri: &Url,
1985    text: &str,
1986    compilation_unit: Option<&CompilationUnit>,
1987    request_range: Range,
1988) -> Vec<CodeActionOrCommand> {
1989    let Some(compilation_unit) = compilation_unit else {
1990        return Vec::new();
1991    };
1992    let Some(body) = compilation_unit.body_with_fns() else {
1993        return Vec::new();
1994    };
1995    let mut hole_spans = Vec::new();
1996    collect_hole_spans(body.as_ref(), &mut hole_spans);
1997    let Some(hole_span) = hole_spans
1998        .into_iter()
1999        .find(|span| ranges_overlap(span_to_range(*span), request_range))
2000    else {
2001        return Vec::new();
2002    };
2003    let hole_range = span_to_range(hole_span);
2004    let pos = hole_range.start;
2005    let candidates = hole_fill_candidates_at_position(uri, text, pos);
2006    let mut actions = Vec::new();
2007    for (name, replacement) in candidates.into_iter().take(8) {
2008        let diagnostic = Diagnostic {
2009            range: hole_range,
2010            severity: Some(DiagnosticSeverity::HINT),
2011            message: "hole".to_string(),
2012            source: Some("rex-lsp".to_string()),
2013            ..Diagnostic::default()
2014        };
2015        actions.push(code_action_replace(
2016            format!("Fill hole with `{name}`"),
2017            uri,
2018            hole_range,
2019            replacement,
2020            diagnostic,
2021        ));
2022    }
2023    actions
2024}
2025
2026fn code_actions_for_diagnostic(
2027    uri: &Url,
2028    text: &str,
2029    compilation_unit: Option<&CompilationUnit>,
2030    request_range: Range,
2031    diagnostic: &Diagnostic,
2032) -> Vec<CodeActionOrCommand> {
2033    let mut actions = Vec::new();
2034    let target_range = if range_is_usable_for_text(text, diagnostic.range) {
2035        diagnostic.range
2036    } else {
2037        request_range
2038    };
2039
2040    if diagnostic
2041        .message
2042        .contains("typed hole `?` must be filled before evaluation")
2043    {
2044        actions.extend(code_actions_for_hole_fill(
2045            uri,
2046            text,
2047            compilation_unit,
2048            target_range,
2049        ));
2050    }
2051
2052    if let Some(name) = unknown_var_name_from_message(&diagnostic.message) {
2053        if let Some(compilation_unit) = compilation_unit {
2054            let mut candidates: Vec<String> =
2055                values_in_scope_at_position(compilation_unit, target_range.start)
2056                    .into_keys()
2057                    .filter(|candidate| candidate != name)
2058                    .collect();
2059            candidates.sort_by_key(|candidate| levenshtein_distance(candidate, name));
2060            for candidate in candidates.into_iter().take(3) {
2061                actions.push(code_action_replace(
2062                    format!("Replace `{name}` with `{candidate}`"),
2063                    uri,
2064                    target_range,
2065                    candidate,
2066                    diagnostic.clone(),
2067                ));
2068            }
2069        }
2070
2071        actions.push(code_action_insert(
2072            format!("Introduce `let {name} = null`"),
2073            uri,
2074            Position {
2075                line: 0,
2076                character: 0,
2077            },
2078            format!("let {name} = null in\n"),
2079            diagnostic.clone(),
2080        ));
2081    }
2082
2083    if is_list_scalar_unification_error(&diagnostic.message)
2084        && let Some(selected) = text_for_range(text, target_range)
2085    {
2086        let trimmed = selected.trim();
2087        if !trimmed.is_empty() {
2088            actions.push(code_action_replace(
2089                "Wrap expression in list literal".to_string(),
2090                uri,
2091                target_range,
2092                format!("[{selected}]"),
2093                diagnostic.clone(),
2094            ));
2095            if trimmed.starts_with('[') && trimmed.ends_with(']') && trimmed.len() >= 2 {
2096                let unwrapped = trimmed[1..trimmed.len() - 1].to_string();
2097                actions.push(code_action_replace(
2098                    "Unwrap list literal".to_string(),
2099                    uri,
2100                    target_range,
2101                    unwrapped,
2102                    diagnostic.clone(),
2103                ));
2104            }
2105        }
2106    }
2107
2108    if is_array_list_unification_error(&diagnostic.message) {
2109        let selected_range =
2110            if !range_is_empty(request_range) && range_is_usable_for_text(text, request_range) {
2111                request_range
2112            } else {
2113                target_range
2114            };
2115        if let Some(selected) = text_for_range(text, selected_range) {
2116            let trimmed = selected.trim();
2117            if !trimmed.is_empty() && !trimmed.starts_with("to_list") {
2118                actions.push(code_action_replace(
2119                    "Convert expression to list with `to_list`".to_string(),
2120                    uri,
2121                    selected_range,
2122                    format!("to_list ({selected})"),
2123                    diagnostic.clone(),
2124                ));
2125            }
2126        }
2127    }
2128
2129    if is_function_value_unification_error(&diagnostic.message)
2130        && let Some(selected) = text_for_range(text, target_range)
2131    {
2132        let trimmed = selected.trim();
2133        if !trimmed.is_empty() {
2134            actions.push(code_action_replace(
2135                "Apply expression to missing argument".to_string(),
2136                uri,
2137                target_range,
2138                format!("({selected} null)"),
2139                diagnostic.clone(),
2140            ));
2141            actions.push(code_action_replace(
2142                "Wrap expression in lambda".to_string(),
2143                uri,
2144                target_range,
2145                format!("(\\_ -> {selected})"),
2146                diagnostic.clone(),
2147            ));
2148        }
2149    }
2150
2151    if diagnostic.message.starts_with("non-exhaustive match for ") {
2152        let (insert_pos, new_text) = wildcard_match_arm_insert(text, diagnostic.range)
2153            .unwrap_or_else(|| {
2154                let newline = if diagnostic.range.start.line == diagnostic.range.end.line {
2155                    " "
2156                } else {
2157                    "\n"
2158                };
2159                (diagnostic.range.end, format!("{newline}case _ -> null;"))
2160            });
2161        actions.push(code_action_insert(
2162            "Add wildcard arm to match".to_string(),
2163            uri,
2164            insert_pos,
2165            new_text,
2166            diagnostic.clone(),
2167        ));
2168    }
2169
2170    if let Some(field) = field_not_definitely_available_from_message(&diagnostic.message)
2171        && let Some(compilation_unit) = compilation_unit
2172        && let Some(selected) = text_for_range(text, target_range)
2173    {
2174        let candidates = default_record_candidates_for_field(compilation_unit, field);
2175        for ty_name in &candidates {
2176            if let Some(new_text) = replace_first_default_with_is(&selected, ty_name) {
2177                actions.push(code_action_replace(
2178                    format!("Disambiguate `default` as `{ty_name}`"),
2179                    uri,
2180                    target_range,
2181                    new_text,
2182                    diagnostic.clone(),
2183                ));
2184            }
2185        }
2186
2187        if let Some((binding_name, insert_pos)) =
2188            find_let_binding_for_def_range(compilation_unit, target_range)
2189        {
2190            for ty_name in &candidates {
2191                actions.push(code_action_insert(
2192                    format!("Annotate `{binding_name}` as `{ty_name}`"),
2193                    uri,
2194                    insert_pos,
2195                    format!(": {ty_name}"),
2196                    diagnostic.clone(),
2197                ));
2198            }
2199        }
2200    }
2201
2202    actions
2203}
2204
2205fn code_action_replace(
2206    title: String,
2207    uri: &Url,
2208    range: Range,
2209    new_text: String,
2210    diagnostic: Diagnostic,
2211) -> CodeActionOrCommand {
2212    code_action_with_edit(title, uri, TextEdit { range, new_text }, diagnostic)
2213}
2214
2215fn code_action_insert(
2216    title: String,
2217    uri: &Url,
2218    position: Position,
2219    new_text: String,
2220    diagnostic: Diagnostic,
2221) -> CodeActionOrCommand {
2222    code_action_with_edit(
2223        title,
2224        uri,
2225        TextEdit {
2226            range: Range {
2227                start: position,
2228                end: position,
2229            },
2230            new_text,
2231        },
2232        diagnostic,
2233    )
2234}
2235
2236fn code_action_with_edit(
2237    title: String,
2238    uri: &Url,
2239    edit: TextEdit,
2240    diagnostic: Diagnostic,
2241) -> CodeActionOrCommand {
2242    let mut changes = HashMap::new();
2243    changes.insert(uri.clone(), vec![edit]);
2244    CodeActionOrCommand::CodeAction(CodeAction {
2245        title,
2246        kind: Some(CodeActionKind::QUICKFIX),
2247        diagnostics: Some(vec![diagnostic]),
2248        edit: Some(WorkspaceEdit {
2249            changes: Some(changes),
2250            document_changes: None,
2251            change_annotations: None,
2252        }),
2253        command: None,
2254        is_preferred: Some(true),
2255        disabled: None,
2256        data: None,
2257    })
2258}
2259
2260fn text_for_range(text: &str, range: Range) -> Option<String> {
2261    let start = offset_at(text, range.start)?;
2262    let end = offset_at(text, range.end)?;
2263    (start <= end && end <= text.len()).then(|| text[start..end].to_string())
2264}
2265
2266fn range_is_usable_for_text(text: &str, range: Range) -> bool {
2267    let Some(start) = offset_at(text, range.start) else {
2268        return false;
2269    };
2270    let Some(end) = offset_at(text, range.end) else {
2271        return false;
2272    };
2273    start <= end && end <= text.len()
2274}
2275
2276fn ranges_overlap(a: Range, b: Range) -> bool {
2277    position_leq_lsp(a.start, b.end) && position_leq_lsp(b.start, a.end)
2278}
2279
2280fn position_leq_lsp(left: Position, right: Position) -> bool {
2281    left.line < right.line || (left.line == right.line && left.character <= right.character)
2282}
2283
2284fn range_is_empty(range: Range) -> bool {
2285    range.start.line == range.end.line && range.start.character == range.end.character
2286}
2287
2288fn unknown_var_name_from_message(message: &str) -> Option<&str> {
2289    message.strip_prefix("unbound variable ").map(str::trim)
2290}
2291
2292fn field_not_definitely_available_from_message(message: &str) -> Option<&str> {
2293    let rest = message.strip_prefix("field `")?;
2294    let (field, tail) = rest.split_once('`')?;
2295    tail.contains("is not definitely available on")
2296        .then_some(field)
2297}
2298
2299fn default_record_candidates_for_field(
2300    compilation_unit: &CompilationUnit,
2301    field: &str,
2302) -> Vec<String> {
2303    let mut out = Vec::new();
2304    let mut seen = HashSet::new();
2305    for decl in &compilation_unit.decls {
2306        let Decl::Instance(inst) = decl else {
2307            continue;
2308        };
2309        if inst.class.as_ref() != "Default" {
2310            continue;
2311        }
2312        let TypeExpr::Name(_, ty_name) = &inst.head else {
2313            continue;
2314        };
2315        if !type_decl_has_record_field(compilation_unit, ty_name.as_ref(), field) {
2316            continue;
2317        }
2318        let ty_name = ty_name.as_ref().to_string();
2319        if seen.insert(ty_name.clone()) {
2320            out.push(ty_name);
2321        }
2322    }
2323    out
2324}
2325
2326fn type_decl_has_record_field(
2327    compilation_unit: &CompilationUnit,
2328    type_name: &str,
2329    field: &str,
2330) -> bool {
2331    compilation_unit.decls.iter().any(|decl| {
2332        let Decl::Type(td) = decl else {
2333            return false;
2334        };
2335        if td.name.as_ref() != type_name {
2336            return false;
2337        }
2338        td.variants.iter().any(|variant| {
2339            variant.args.iter().any(|arg| {
2340                let TypeExpr::Record(_, fields) = arg else {
2341                    return false;
2342                };
2343                fields.iter().any(|(name, _)| name.as_ref() == field)
2344            })
2345        })
2346    })
2347}
2348
2349fn replace_first_default_with_is(source: &str, ty_name: &str) -> Option<String> {
2350    for (idx, _) in source.match_indices("default") {
2351        let left_ok = if idx == 0 {
2352            true
2353        } else {
2354            !is_ident_char(source[..idx].chars().next_back().unwrap_or('_'))
2355        };
2356        let right_idx = idx + "default".len();
2357        let right_ok = if right_idx >= source.len() {
2358            true
2359        } else {
2360            !is_ident_char(source[right_idx..].chars().next().unwrap_or('_'))
2361        };
2362        if !(left_ok && right_ok) {
2363            continue;
2364        }
2365
2366        let mut replaced = String::with_capacity(source.len() + ty_name.len() + 8);
2367        replaced.push_str(&source[..idx]);
2368        replaced.push_str("(default is ");
2369        replaced.push_str(ty_name);
2370        replaced.push(')');
2371        replaced.push_str(&source[right_idx..]);
2372        return Some(replaced);
2373    }
2374    None
2375}
2376
2377fn is_ident_char(c: char) -> bool {
2378    c.is_ascii_alphanumeric() || c == '_'
2379}
2380
2381fn is_hole_name(name: &str) -> bool {
2382    name == "_" || name.starts_with('_')
2383}
2384
2385pub fn is_list_scalar_unification_error(message: &str) -> bool {
2386    let Some(rest) = message.strip_prefix("types do not unify: ") else {
2387        return false;
2388    };
2389    let Some((left, right)) = rest.split_once(" vs ") else {
2390        return false;
2391    };
2392    list_inner_type(left.trim()).is_some_and(|inner| inner == right.trim())
2393        || list_inner_type(right.trim()).is_some_and(|inner| inner == left.trim())
2394}
2395
2396fn list_inner_type(typ: &str) -> Option<&str> {
2397    if let Some(inner) = typ
2398        .strip_prefix("List<")
2399        .and_then(|rest| rest.strip_suffix('>'))
2400    {
2401        return Some(inner);
2402    }
2403    typ.strip_prefix("(List ")
2404        .and_then(|rest| rest.strip_suffix(')'))
2405}
2406
2407pub fn is_array_list_unification_error(message: &str) -> bool {
2408    let Some(rest) = message.strip_prefix("types do not unify: ") else {
2409        return false;
2410    };
2411    let Some((left, right)) = rest.split_once(" vs ") else {
2412        return false;
2413    };
2414    let left = left.trim();
2415    let right = right.trim();
2416    let left_has_array = left.contains("Array");
2417    let left_has_list = left.contains("List");
2418    let right_has_array = right.contains("Array");
2419    let right_has_list = right.contains("List");
2420    (left_has_array && right_has_list) || (left_has_list && right_has_array)
2421}
2422
2423pub fn is_function_value_unification_error(message: &str) -> bool {
2424    let Some(rest) = message.strip_prefix("types do not unify: ") else {
2425        return false;
2426    };
2427    let Some((left, right)) = rest.split_once(" vs ") else {
2428        return false;
2429    };
2430    let left_is_fun = looks_like_fun_type(left.trim());
2431    let right_is_fun = looks_like_fun_type(right.trim());
2432    left_is_fun ^ right_is_fun
2433}
2434
2435fn looks_like_fun_type(typ: &str) -> bool {
2436    let mut depth = 0usize;
2437    let bytes = typ.as_bytes();
2438    let mut i = 0usize;
2439    while i + 1 < bytes.len() {
2440        match bytes[i] as char {
2441            '(' | '{' | '[' => depth += 1,
2442            ')' | '}' | ']' => depth = depth.saturating_sub(1),
2443            '-' if bytes[i + 1] as char == '>' && depth == 0 => return true,
2444            _ => {}
2445        }
2446        i += 1;
2447    }
2448
2449    if typ.starts_with('(') && typ.ends_with(')') {
2450        return looks_like_fun_type(&typ[1..typ.len() - 1]);
2451    }
2452    false
2453}
2454
2455fn split_fun_type(typ: &Type) -> (Vec<Type>, Type) {
2456    let mut args = Vec::new();
2457    let mut cur = typ.clone();
2458    while let TypeKind::Fun(arg, ret) = cur.as_ref() {
2459        args.push(arg.clone());
2460        cur = ret.clone();
2461    }
2462    (args, cur)
2463}
2464
2465fn in_scope_value_types_at_position(
2466    uri: &Url,
2467    text: &str,
2468    position: Position,
2469) -> Vec<(String, Type)> {
2470    let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) else {
2471        return Vec::new();
2472    };
2473    let Ok((program, mut ts, _imports, _import_diags)) =
2474        prepare_program_with_imports(uri, &program)
2475    else {
2476        return Vec::new();
2477    };
2478    if inject_program_decls(&mut ts, &program, None).is_err() {
2479        return Vec::new();
2480    }
2481
2482    let Some(expr) = program.body_with_fns() else {
2483        return Vec::new();
2484    };
2485    let Ok((typed, _preds, _ty)) = infer_typed(&mut ts, expr.as_ref()) else {
2486        return Vec::new();
2487    };
2488    let pos = lsp_to_rex_position(position);
2489
2490    fn visit(
2491        expr: &Expr,
2492        typed: &TypedExpr,
2493        pos: RexPosition,
2494        scope: &mut Vec<(String, Type)>,
2495        best: &mut Option<Vec<(String, Type)>>,
2496    ) {
2497        if !position_in_span(pos, *expr.span()) {
2498            return;
2499        }
2500        *best = Some(scope.clone());
2501
2502        match (expr, typed.kind.as_ref()) {
2503            (
2504                Expr::Let(_span, var, _ann, def, body),
2505                TypedExprKind::Let {
2506                    def: tdef,
2507                    body: tbody,
2508                    ..
2509                },
2510            ) => {
2511                if position_in_span(pos, *def.span()) {
2512                    visit(def.as_ref(), tdef.as_ref(), pos, scope, best);
2513                    return;
2514                }
2515                if position_in_span(pos, *body.span()) {
2516                    scope.push((var.name.to_string(), tdef.typ.clone()));
2517                    visit(body.as_ref(), tbody.as_ref(), pos, scope, best);
2518                    scope.pop();
2519                }
2520            }
2521            (
2522                Expr::LetRec(_span, bindings, body),
2523                TypedExprKind::LetRec {
2524                    bindings: typed_bindings,
2525                    body: typed_body,
2526                },
2527            ) => {
2528                let base = scope.len();
2529                for ((name, _ann, _def), (_typed_name, typed_def)) in
2530                    bindings.iter().zip(typed_bindings.iter())
2531                {
2532                    scope.push((name.name.to_string(), typed_def.typ.clone()));
2533                }
2534                for ((_, _, def), (_, typed_def)) in bindings.iter().zip(typed_bindings.iter()) {
2535                    if position_in_span(pos, *def.span()) {
2536                        visit(def.as_ref(), typed_def, pos, scope, best);
2537                        scope.truncate(base);
2538                        return;
2539                    }
2540                }
2541                if position_in_span(pos, *body.span()) {
2542                    visit(body.as_ref(), typed_body.as_ref(), pos, scope, best);
2543                }
2544                scope.truncate(base);
2545            }
2546            (
2547                Expr::Lam(_span, _scope, param, _ann, _constraints, body),
2548                TypedExprKind::Lam {
2549                    body: typed_body, ..
2550                },
2551            ) => {
2552                if let TypeKind::Fun(arg, _ret) = typed.typ.as_ref() {
2553                    scope.push((param.name.to_string(), arg.clone()));
2554                    visit(body.as_ref(), typed_body.as_ref(), pos, scope, best);
2555                    scope.pop();
2556                }
2557            }
2558            (Expr::App(_span, fun, arg), TypedExprKind::App(tfun, targ)) => {
2559                if position_in_span(pos, *fun.span()) {
2560                    visit(fun.as_ref(), tfun.as_ref(), pos, scope, best);
2561                } else if position_in_span(pos, *arg.span()) {
2562                    visit(arg.as_ref(), targ.as_ref(), pos, scope, best);
2563                }
2564            }
2565            (Expr::Project(_span, base, _field), TypedExprKind::Project { expr: tbase, .. }) => {
2566                visit(base.as_ref(), tbase.as_ref(), pos, scope, best);
2567            }
2568            (
2569                Expr::Ite(_span, cond, then_expr, else_expr),
2570                TypedExprKind::Ite {
2571                    cond: tcond,
2572                    then_expr: tthen,
2573                    else_expr: telse,
2574                },
2575            ) => {
2576                if position_in_span(pos, *cond.span()) {
2577                    visit(cond.as_ref(), tcond.as_ref(), pos, scope, best);
2578                } else if position_in_span(pos, *then_expr.span()) {
2579                    visit(then_expr.as_ref(), tthen.as_ref(), pos, scope, best);
2580                } else if position_in_span(pos, *else_expr.span()) {
2581                    visit(else_expr.as_ref(), telse.as_ref(), pos, scope, best);
2582                }
2583            }
2584            (Expr::Tuple(_span, elems), TypedExprKind::Tuple(typed_elems))
2585            | (Expr::List(_span, elems), TypedExprKind::List(typed_elems)) => {
2586                for (elem, typed_elem) in elems.iter().zip(typed_elems.iter()) {
2587                    if position_in_span(pos, *elem.span()) {
2588                        visit(elem.as_ref(), typed_elem, pos, scope, best);
2589                        break;
2590                    }
2591                }
2592            }
2593            (Expr::Dict(_span, kvs), TypedExprKind::Dict(typed_kvs)) => {
2594                for (key, value) in kvs {
2595                    if position_in_span(pos, *value.span())
2596                        && let Some(typed_v) = typed_kvs.get(key)
2597                    {
2598                        visit(value.as_ref(), typed_v, pos, scope, best);
2599                        break;
2600                    }
2601                }
2602            }
2603            (
2604                Expr::RecordUpdate(_span, base, updates),
2605                TypedExprKind::RecordUpdate {
2606                    base: tbase,
2607                    updates: typed_updates,
2608                },
2609            ) => {
2610                if position_in_span(pos, *base.span()) {
2611                    visit(base.as_ref(), tbase.as_ref(), pos, scope, best);
2612                } else {
2613                    for (key, value) in updates {
2614                        if position_in_span(pos, *value.span())
2615                            && let Some(typed_v) = typed_updates.get(key)
2616                        {
2617                            visit(value.as_ref(), typed_v, pos, scope, best);
2618                            break;
2619                        }
2620                    }
2621                }
2622            }
2623            (
2624                Expr::Match(_span, scrutinee, arms),
2625                TypedExprKind::Match {
2626                    scrutinee: tscrutinee,
2627                    arms: typed_arms,
2628                },
2629            ) => {
2630                if position_in_span(pos, *scrutinee.span()) {
2631                    visit(scrutinee.as_ref(), tscrutinee.as_ref(), pos, scope, best);
2632                } else {
2633                    for ((_pat, arm), (_typed_pat, typed_arm)) in arms.iter().zip(typed_arms.iter())
2634                    {
2635                        if position_in_span(pos, *arm.span()) {
2636                            visit(arm.as_ref(), typed_arm, pos, scope, best);
2637                            break;
2638                        }
2639                    }
2640                }
2641            }
2642            (Expr::Ann(_span, inner, _), _) => visit(inner.as_ref(), typed, pos, scope, best),
2643            _ => {}
2644        }
2645    }
2646
2647    let mut best = None;
2648    visit(expr.as_ref(), &typed, pos, &mut Vec::new(), &mut best);
2649    best.unwrap_or_default()
2650}
2651
2652fn hole_fill_candidates_at_position(
2653    uri: &Url,
2654    text: &str,
2655    position: Position,
2656) -> Vec<(String, String)> {
2657    let Some(target_type) = expected_type_at_position_type(uri, text, position) else {
2658        return Vec::new();
2659    };
2660    let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) else {
2661        return Vec::new();
2662    };
2663    let Ok((program, mut ts, _imports, _import_diags)) =
2664        prepare_program_with_imports(uri, &program)
2665    else {
2666        return Vec::new();
2667    };
2668    if inject_program_decls(&mut ts, &program, None).is_err() {
2669        return Vec::new();
2670    }
2671    let mut in_scope = in_scope_value_types_at_position(uri, text, position)
2672        .into_iter()
2673        .filter(|(name, _)| is_ident_like(name))
2674        .collect::<Vec<_>>();
2675    if in_scope.len() > MAX_SEMANTIC_IN_SCOPE_VALUES {
2676        in_scope = in_scope.split_off(in_scope.len().saturating_sub(MAX_SEMANTIC_IN_SCOPE_VALUES));
2677    }
2678
2679    let values = semantic_candidate_values(&ts);
2680
2681    let mut adapters: Vec<(String, Type, Type)> = Vec::new();
2682    for (name, schemes) in &values {
2683        let name = name.to_string();
2684        if !is_ident_like(&name) {
2685            continue;
2686        }
2687        for scheme in schemes {
2688            let (_preds, inst_ty) = instantiate(scheme, &mut ts.supply);
2689            let (args, ret) = split_fun_type(&inst_ty);
2690            if args.len() == 1 {
2691                adapters.push((name.clone(), args[0].clone(), ret));
2692            }
2693        }
2694    }
2695
2696    let mut out: Vec<(usize, usize, String, String)> = Vec::new();
2697    for (name, schemes) in values {
2698        let name = name.to_string();
2699        if !is_ident_like(&name) {
2700            continue;
2701        }
2702        for scheme in schemes {
2703            let (_preds, inst_ty) = instantiate(&scheme, &mut ts.supply);
2704            let (args, ret) = split_fun_type(&inst_ty);
2705            if args.is_empty()
2706                || args.len() > MAX_SEMANTIC_HOLE_FILL_ARITY
2707                || unify(&ret, &target_type).is_err()
2708            {
2709                continue;
2710            }
2711
2712            let mut unresolved = 0usize;
2713            let mut adapter_uses = 0usize;
2714            let mut rendered_args = Vec::new();
2715            for arg_ty in args {
2716                if let Some((value_name, _value_ty)) = in_scope
2717                    .iter()
2718                    .rev()
2719                    .find(|(_, value_ty)| unify(value_ty, &arg_ty).is_ok())
2720                {
2721                    rendered_args.push(value_name.clone());
2722                    continue;
2723                }
2724
2725                let mut adapted = None;
2726                for (adapter_name, adapter_arg, adapter_ret) in &adapters {
2727                    if unify(adapter_ret, &arg_ty).is_err() {
2728                        continue;
2729                    }
2730                    if let Some((value_name, _value_ty)) = in_scope
2731                        .iter()
2732                        .rev()
2733                        .find(|(_, value_ty)| unify(value_ty, adapter_arg).is_ok())
2734                    {
2735                        adapted = Some(format!("({adapter_name} {value_name})"));
2736                        break;
2737                    }
2738                }
2739                if let Some(expr) = adapted {
2740                    adapter_uses += 1;
2741                    rendered_args.push(expr);
2742                } else {
2743                    unresolved += 1;
2744                    rendered_args.push("?".to_string());
2745                }
2746            }
2747
2748            let replacement = format!("{name} {}", rendered_args.join(" "));
2749            out.push((unresolved, adapter_uses, name.clone(), replacement));
2750        }
2751    }
2752
2753    out.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)).then(a.2.cmp(&b.2)));
2754    out.dedup_by(|a, b| a.2 == b.2 && a.3 == b.3);
2755    if out.len() > MAX_SEMANTIC_CANDIDATES {
2756        out.truncate(MAX_SEMANTIC_CANDIDATES);
2757    }
2758    out.into_iter()
2759        .map(|(_u, _a, name, replacement)| (name, replacement))
2760        .collect()
2761}
2762
2763fn levenshtein_distance(left: &str, right: &str) -> usize {
2764    if left == right {
2765        return 0;
2766    }
2767    if left.is_empty() {
2768        return right.chars().count();
2769    }
2770    if right.is_empty() {
2771        return left.chars().count();
2772    }
2773
2774    let right_len = right.chars().count();
2775    let mut prev: Vec<usize> = (0..=right_len).collect();
2776    let mut cur = vec![0usize; right_len + 1];
2777
2778    for (i, lc) in left.chars().enumerate() {
2779        cur[0] = i + 1;
2780        for (j, rc) in right.chars().enumerate() {
2781            let insert_cost = cur[j] + 1;
2782            let delete_cost = prev[j + 1] + 1;
2783            let replace_cost = prev[j] + usize::from(lc != rc);
2784            cur[j + 1] = insert_cost.min(delete_cost).min(replace_cost);
2785        }
2786        std::mem::swap(&mut prev, &mut cur);
2787    }
2788
2789    prev[right_len]
2790}
2791
2792#[allow(deprecated)]
2793fn symbol_for_decl(decl: &Decl) -> Option<DocumentSymbol> {
2794    match decl {
2795        Decl::Type(td) => Some(DocumentSymbol {
2796            name: td.name.to_string(),
2797            detail: Some("type".to_string()),
2798            kind: SymbolKind::ENUM,
2799            tags: None,
2800            deprecated: None,
2801            range: span_to_range(td.span),
2802            selection_range: span_to_range(td.span),
2803            children: Some(
2804                td.variants
2805                    .iter()
2806                    .map(|variant| DocumentSymbol {
2807                        name: variant.name.to_string(),
2808                        detail: Some("variant".to_string()),
2809                        kind: SymbolKind::ENUM_MEMBER,
2810                        tags: None,
2811                        deprecated: None,
2812                        range: span_to_range(td.span),
2813                        selection_range: span_to_range(td.span),
2814                        children: None,
2815                    })
2816                    .collect(),
2817            ),
2818        }),
2819        Decl::Fn(fd) => Some(DocumentSymbol {
2820            name: fd.name.name.to_string(),
2821            detail: Some("fn".to_string()),
2822            kind: SymbolKind::FUNCTION,
2823            tags: None,
2824            deprecated: None,
2825            range: span_to_range(fd.span),
2826            selection_range: span_to_range(fd.name.span),
2827            children: None,
2828        }),
2829        Decl::DeclareFn(df) => Some(DocumentSymbol {
2830            name: df.name.name.to_string(),
2831            detail: Some("declare fn".to_string()),
2832            kind: SymbolKind::FUNCTION,
2833            tags: None,
2834            deprecated: None,
2835            range: span_to_range(df.span),
2836            selection_range: span_to_range(df.name.span),
2837            children: None,
2838        }),
2839        Decl::Import(id) => Some(DocumentSymbol {
2840            name: id.alias.to_string(),
2841            detail: Some("import".to_string()),
2842            kind: SymbolKind::MODULE,
2843            tags: None,
2844            deprecated: None,
2845            range: span_to_range(id.span),
2846            selection_range: span_to_range(id.span),
2847            children: None,
2848        }),
2849        Decl::Class(cd) => Some(DocumentSymbol {
2850            name: cd.name.to_string(),
2851            detail: Some("class".to_string()),
2852            kind: SymbolKind::INTERFACE,
2853            tags: None,
2854            deprecated: None,
2855            range: span_to_range(cd.span),
2856            selection_range: span_to_range(cd.span),
2857            children: Some(
2858                cd.methods
2859                    .iter()
2860                    .map(|method| DocumentSymbol {
2861                        name: method.name.to_string(),
2862                        detail: Some("method".to_string()),
2863                        kind: SymbolKind::METHOD,
2864                        tags: None,
2865                        deprecated: None,
2866                        range: span_to_range(cd.span),
2867                        selection_range: span_to_range(cd.span),
2868                        children: None,
2869                    })
2870                    .collect(),
2871            ),
2872        }),
2873        Decl::Instance(id) => Some(DocumentSymbol {
2874            name: format!("instance {}", id.class),
2875            detail: Some("instance".to_string()),
2876            kind: SymbolKind::OBJECT,
2877            tags: None,
2878            deprecated: None,
2879            range: span_to_range(id.span),
2880            selection_range: span_to_range(id.span),
2881            children: Some(
2882                id.methods
2883                    .iter()
2884                    .map(|method| DocumentSymbol {
2885                        name: method.name.to_string(),
2886                        detail: Some("method".to_string()),
2887                        kind: SymbolKind::METHOD,
2888                        tags: None,
2889                        deprecated: None,
2890                        range: span_to_range(*method.body.span()),
2891                        selection_range: span_to_range(*method.body.span()),
2892                        children: None,
2893                    })
2894                    .collect(),
2895            ),
2896        }),
2897    }
2898}
2899
2900pub(crate) fn document_symbols_for_source(uri: &Url, text: &str) -> Vec<DocumentSymbol> {
2901    let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) else {
2902        return Vec::new();
2903    };
2904    program.decls.iter().filter_map(symbol_for_decl).collect()
2905}
2906
2907pub fn full_document_range(text: &str) -> Range {
2908    let mut line = 0u32;
2909    let mut col = 0u32;
2910    for ch in text.chars() {
2911        if ch == '\n' {
2912            line += 1;
2913            col = 0;
2914        } else {
2915            col += 1;
2916        }
2917    }
2918    Range {
2919        start: Position {
2920            line: 0,
2921            character: 0,
2922        },
2923        end: Position {
2924            line,
2925            character: col,
2926        },
2927    }
2928}
2929
2930fn format_source(text: &str) -> String {
2931    let mut out = String::new();
2932    let mut first = true;
2933    for line in text.lines() {
2934        if !first {
2935            out.push('\n');
2936        }
2937        first = false;
2938        out.push_str(line.trim_end());
2939    }
2940    if text.ends_with('\n') || !out.is_empty() {
2941        out.push('\n');
2942    }
2943    out
2944}
2945
2946pub(crate) fn format_edits_for_source(text: &str) -> Option<Vec<TextEdit>> {
2947    let formatted = format_source(text);
2948    if formatted == text {
2949        return None;
2950    }
2951    Some(vec![TextEdit {
2952        range: full_document_range(text),
2953        new_text: formatted,
2954    }])
2955}
2956
2957pub fn diagnostics_from_text(uri: &Url, text: &str) -> Vec<Diagnostic> {
2958    let mut diagnostics = Vec::new();
2959
2960    match tokenize_and_parse_cached(uri, text) {
2961        Ok((tokens, program)) => {
2962            push_comment_diagnostics(&tokens, &mut diagnostics);
2963            if diagnostics.len() < MAX_DIAGNOSTICS {
2964                push_type_diagnostics(uri, text, &program, &mut diagnostics);
2965            }
2966        }
2967        Err(TokenizeOrParseError::Lex(err)) => {
2968            diagnostics.push(diagnostic_for_lexical_error(&err));
2969        }
2970        Err(TokenizeOrParseError::Parse(errors)) => {
2971            for err in errors {
2972                diagnostics.push(diagnostic_for_span(err.span, err.message));
2973                if diagnostics.len() >= MAX_DIAGNOSTICS {
2974                    break;
2975                }
2976            }
2977        }
2978    }
2979
2980    diagnostics
2981}
2982
2983fn diagnostic_for_lexical_error(err: &LexicalError) -> Diagnostic {
2984    let (span, message) = match err {
2985        LexicalError::UnexpectedToken(span) => (*span, "Unexpected token".to_string()),
2986        LexicalError::InvalidLiteral {
2987            kind,
2988            text,
2989            error,
2990            span,
2991        } => (*span, format!("invalid {kind} literal `{text}`: {error}")),
2992        LexicalError::Internal(msg) => (
2993            Span::new(1, 1, 1, 1),
2994            format!("internal lexer error: {msg}"),
2995        ),
2996    };
2997    diagnostic_for_span(span, message)
2998}
2999
3000struct HoverType {
3001    span: Span,
3002    label: String,
3003    typ: String,
3004    overloads: Vec<String>,
3005}
3006
3007pub(crate) fn hover_type_contents(
3008    uri: &Url,
3009    text: &str,
3010    position: Position,
3011) -> Option<HoverContents> {
3012    let (tokens, program) = tokenize_and_parse_cached(uri, text).ok()?;
3013    let (name, name_span, name_is_ident) = name_token_at_position(&tokens, position)?;
3014    let (program, mut ts, _imports, _import_diags) =
3015        prepare_program_with_imports(uri, &program).ok()?;
3016
3017    let pos = lsp_to_rex_position(position);
3018
3019    // If the cursor is inside an instance method body, typecheck that method
3020    // body using the instance context rules (so hover works inside methods).
3021    let mut target_instance: Option<(usize, usize)> = None;
3022    for (decl_idx, decl) in program.decls.iter().enumerate() {
3023        let Decl::Instance(inst) = decl else {
3024            continue;
3025        };
3026        for (method_idx, method) in inst.methods.iter().enumerate() {
3027            if position_in_span(pos, *method.body.span()) {
3028                target_instance = Some((decl_idx, method_idx));
3029                break;
3030            }
3031        }
3032        if target_instance.is_some() {
3033            break;
3034        }
3035    }
3036
3037    let (_instances, prepared_target_instance) = inject_program_decls(
3038        &mut ts,
3039        &program,
3040        target_instance.map(|(decl_idx, _)| decl_idx),
3041    )
3042    .ok()?;
3043
3044    let body_with_fns = program.body_with_fns();
3045
3046    let root_expr: &Expr;
3047    let typed_root: TypedExpr;
3048
3049    if let Some((decl_idx, method_idx)) = target_instance {
3050        let Decl::Instance(inst) = &program.decls[decl_idx] else {
3051            return None;
3052        };
3053        let prepared = prepared_target_instance?;
3054        let method = inst.methods.get(method_idx)?;
3055        typed_root = ts.typecheck_instance_method(&prepared, method).ok()?;
3056        root_expr = method.body.as_ref();
3057    } else {
3058        let body_with_fns = body_with_fns.as_ref()?;
3059        let (typed, _preds, _) = infer_typed(&mut ts, body_with_fns.as_ref()).ok()?;
3060        typed_root = typed;
3061        root_expr = body_with_fns.as_ref();
3062    }
3063
3064    let hover = hover_type_in_expr(
3065        &mut ts,
3066        root_expr,
3067        &typed_root,
3068        pos,
3069        &name,
3070        name_span,
3071        name_is_ident,
3072    )?;
3073
3074    let mut md = String::new();
3075    md.push_str("```rex\n");
3076    md.push_str(&hover.label);
3077    md.push_str(" : ");
3078    md.push_str(&hover.typ);
3079    md.push_str("\n```");
3080
3081    if !hover.overloads.is_empty() {
3082        md.push_str("\n\nOverloads:\n");
3083        for ov in &hover.overloads {
3084            md.push_str("- `");
3085            md.push_str(ov);
3086            md.push_str("`\n");
3087        }
3088    }
3089
3090    Some(HoverContents::Markup(MarkupContent {
3091        kind: MarkupKind::Markdown,
3092        value: md,
3093    }))
3094}
3095
3096fn expected_type_at_position(uri: &Url, text: &str, position: Position) -> Option<String> {
3097    expected_type_at_position_type(uri, text, position).map(|ty| ty.to_string())
3098}
3099
3100fn inferred_type_at_position(uri: &Url, text: &str, position: Position) -> Option<String> {
3101    inferred_type_at_position_type(uri, text, position).map(|ty| ty.to_string())
3102}
3103
3104fn expected_type_at_position_type(uri: &Url, text: &str, position: Position) -> Option<Type> {
3105    let (_tokens, program) = tokenize_and_parse_cached(uri, text).ok()?;
3106    let (program, mut ts, _imports, _import_diags) =
3107        prepare_program_with_imports(uri, &program).ok()?;
3108
3109    let pos = lsp_to_rex_position(position);
3110
3111    // Mirror hover behavior inside instance methods.
3112    let mut target_instance: Option<(usize, usize)> = None;
3113    for (decl_idx, decl) in program.decls.iter().enumerate() {
3114        let Decl::Instance(inst) = decl else {
3115            continue;
3116        };
3117        for (method_idx, method) in inst.methods.iter().enumerate() {
3118            if position_in_span(pos, *method.body.span()) {
3119                target_instance = Some((decl_idx, method_idx));
3120                break;
3121            }
3122        }
3123        if target_instance.is_some() {
3124            break;
3125        }
3126    }
3127
3128    let (_instances, prepared_target_instance) = inject_program_decls(
3129        &mut ts,
3130        &program,
3131        target_instance.map(|(decl_idx, _)| decl_idx),
3132    )
3133    .ok()?;
3134
3135    let body_with_fns = program.body_with_fns();
3136    let root_expr: &Expr;
3137    let typed_root: TypedExpr;
3138
3139    if let Some((decl_idx, method_idx)) = target_instance {
3140        let Decl::Instance(inst) = &program.decls[decl_idx] else {
3141            return None;
3142        };
3143        let prepared = prepared_target_instance?;
3144        let method = inst.methods.get(method_idx)?;
3145        typed_root = ts.typecheck_instance_method(&prepared, method).ok()?;
3146        root_expr = method.body.as_ref();
3147    } else {
3148        let body_with_fns = body_with_fns.as_ref()?;
3149        let (typed, _preds, _) = infer_typed(&mut ts, body_with_fns.as_ref()).ok()?;
3150        typed_root = typed;
3151        root_expr = body_with_fns.as_ref();
3152    }
3153
3154    expected_type_in_expr(root_expr, &typed_root, pos)
3155}
3156
3157fn inferred_type_at_position_type(uri: &Url, text: &str, position: Position) -> Option<Type> {
3158    let (_tokens, program) = tokenize_and_parse_cached(uri, text).ok()?;
3159    let (program, mut ts, _imports, _import_diags) =
3160        prepare_program_with_imports(uri, &program).ok()?;
3161
3162    let pos = lsp_to_rex_position(position);
3163
3164    let mut target_instance: Option<(usize, usize)> = None;
3165    for (decl_idx, decl) in program.decls.iter().enumerate() {
3166        let Decl::Instance(inst) = decl else {
3167            continue;
3168        };
3169        for (method_idx, method) in inst.methods.iter().enumerate() {
3170            if position_in_span(pos, *method.body.span()) {
3171                target_instance = Some((decl_idx, method_idx));
3172                break;
3173            }
3174        }
3175        if target_instance.is_some() {
3176            break;
3177        }
3178    }
3179
3180    let (_instances, prepared_target_instance) = inject_program_decls(
3181        &mut ts,
3182        &program,
3183        target_instance.map(|(decl_idx, _)| decl_idx),
3184    )
3185    .ok()?;
3186
3187    let body_with_fns = program.body_with_fns();
3188    let root_expr: &Expr;
3189    let typed_root: TypedExpr;
3190
3191    if let Some((decl_idx, method_idx)) = target_instance {
3192        let Decl::Instance(inst) = &program.decls[decl_idx] else {
3193            return None;
3194        };
3195        let prepared = prepared_target_instance?;
3196        let method = inst.methods.get(method_idx)?;
3197        typed_root = ts.typecheck_instance_method(&prepared, method).ok()?;
3198        root_expr = method.body.as_ref();
3199    } else {
3200        let body_with_fns = body_with_fns.as_ref()?;
3201        let (typed, _preds, _) = infer_typed(&mut ts, body_with_fns.as_ref()).ok()?;
3202        typed_root = typed;
3203        root_expr = body_with_fns.as_ref();
3204    }
3205
3206    inferred_type_in_expr(root_expr, &typed_root, pos)
3207}
3208
3209fn expected_type_in_expr(expr: &Expr, typed: &TypedExpr, pos: RexPosition) -> Option<Type> {
3210    #[derive(Clone)]
3211    struct Candidate {
3212        span: Span,
3213        typ: Type,
3214    }
3215
3216    fn span_size(span: Span) -> (usize, usize) {
3217        (
3218            span.end.line.saturating_sub(span.begin.line),
3219            span.end.column.saturating_sub(span.begin.column),
3220        )
3221    }
3222
3223    fn consider(best: &mut Option<Candidate>, span: Span, typ: &Type) {
3224        let replace = best
3225            .as_ref()
3226            .is_none_or(|cur| span_size(span) < span_size(cur.span));
3227        if replace {
3228            *best = Some(Candidate {
3229                span,
3230                typ: typ.clone(),
3231            });
3232        }
3233    }
3234
3235    fn visit(
3236        expr: &Expr,
3237        typed: &TypedExpr,
3238        pos: RexPosition,
3239        expected: Option<&Type>,
3240        best: &mut Option<Candidate>,
3241    ) {
3242        if !position_in_span(pos, *expr.span()) {
3243            return;
3244        }
3245
3246        if let Some(expected) = expected {
3247            consider(best, *expr.span(), expected);
3248        }
3249
3250        match (expr, typed.kind.as_ref()) {
3251            (
3252                Expr::Let(_span, _name, _ann, def, body),
3253                TypedExprKind::Let {
3254                    def: tdef,
3255                    body: tbody,
3256                    ..
3257                },
3258            ) => {
3259                visit(def.as_ref(), tdef.as_ref(), pos, Some(&tdef.typ), best);
3260                visit(body.as_ref(), tbody.as_ref(), pos, Some(&typed.typ), best);
3261            }
3262            (
3263                Expr::LetRec(_span, bindings, body),
3264                TypedExprKind::LetRec {
3265                    bindings: typed_bindings,
3266                    body: typed_body,
3267                },
3268            ) => {
3269                for ((_name, _ann, def), (_typed_name, typed_def)) in
3270                    bindings.iter().zip(typed_bindings.iter())
3271                {
3272                    visit(def.as_ref(), typed_def, pos, Some(&typed_def.typ), best);
3273                }
3274                visit(
3275                    body.as_ref(),
3276                    typed_body.as_ref(),
3277                    pos,
3278                    Some(&typed.typ),
3279                    best,
3280                );
3281            }
3282            (
3283                Expr::Lam(_span, _scope, _param, _ann, _constraints, body),
3284                TypedExprKind::Lam {
3285                    body: typed_body, ..
3286                },
3287            ) => {
3288                let body_expected = match typed.typ.as_ref() {
3289                    TypeKind::Fun(_arg, ret) => Some(ret),
3290                    _ => None,
3291                };
3292                visit(body.as_ref(), typed_body.as_ref(), pos, body_expected, best);
3293            }
3294            (Expr::App(_span, f, x), TypedExprKind::App(tf, tx)) => {
3295                let expected_arg = match tf.typ.as_ref() {
3296                    TypeKind::Fun(arg, _ret) => Some(arg),
3297                    _ => None,
3298                };
3299                visit(x.as_ref(), tx.as_ref(), pos, expected_arg, best);
3300
3301                let expected_fun = Type::fun(tx.typ.clone(), typed.typ.clone());
3302                visit(f.as_ref(), tf.as_ref(), pos, Some(&expected_fun), best);
3303            }
3304            (Expr::Project(_span, base, _field), TypedExprKind::Project { expr: tbase, .. }) => {
3305                visit(base.as_ref(), tbase.as_ref(), pos, None, best);
3306            }
3307            (
3308                Expr::Ite(_span, cond, then_expr, else_expr),
3309                TypedExprKind::Ite {
3310                    cond: tcond,
3311                    then_expr: tthen,
3312                    else_expr: telse,
3313                },
3314            ) => {
3315                let bool_ty = Type::builtin(BuiltinTypeId::Bool);
3316                visit(cond.as_ref(), tcond.as_ref(), pos, Some(&bool_ty), best);
3317                visit(
3318                    then_expr.as_ref(),
3319                    tthen.as_ref(),
3320                    pos,
3321                    Some(&typed.typ),
3322                    best,
3323                );
3324                visit(
3325                    else_expr.as_ref(),
3326                    telse.as_ref(),
3327                    pos,
3328                    Some(&typed.typ),
3329                    best,
3330                );
3331            }
3332            (Expr::Tuple(_span, elems), TypedExprKind::Tuple(typed_elems)) => {
3333                for (elem, typed_elem) in elems.iter().zip(typed_elems.iter()) {
3334                    visit(elem.as_ref(), typed_elem, pos, Some(&typed_elem.typ), best);
3335                }
3336            }
3337            (Expr::List(_span, elems), TypedExprKind::List(typed_elems)) => {
3338                let list_elem_expected = match typed.typ.as_ref() {
3339                    TypeKind::App(head, elem) => match head.as_ref() {
3340                        TypeKind::Con(tc)
3341                            if tc.is_builtin(BuiltinTypeId::List) && tc.arity() == 1 =>
3342                        {
3343                            Some(elem)
3344                        }
3345                        _ => None,
3346                    },
3347                    _ => None,
3348                };
3349                for (elem, typed_elem) in elems.iter().zip(typed_elems.iter()) {
3350                    let expected = list_elem_expected.unwrap_or(&typed_elem.typ);
3351                    visit(elem.as_ref(), typed_elem, pos, Some(expected), best);
3352                }
3353            }
3354            (Expr::Dict(_span, kvs), TypedExprKind::Dict(typed_kvs)) => {
3355                for (key, value) in kvs {
3356                    if let Some(typed_value) = typed_kvs.get(key) {
3357                        visit(
3358                            value.as_ref(),
3359                            typed_value,
3360                            pos,
3361                            Some(&typed_value.typ),
3362                            best,
3363                        );
3364                    }
3365                }
3366            }
3367            (
3368                Expr::RecordUpdate(_span, base, updates),
3369                TypedExprKind::RecordUpdate {
3370                    base: typed_base,
3371                    updates: typed_updates,
3372                },
3373            ) => {
3374                visit(base.as_ref(), typed_base.as_ref(), pos, None, best);
3375                for (key, value) in updates {
3376                    if let Some(typed_value) = typed_updates.get(key) {
3377                        visit(
3378                            value.as_ref(),
3379                            typed_value,
3380                            pos,
3381                            Some(&typed_value.typ),
3382                            best,
3383                        );
3384                    }
3385                }
3386            }
3387            (
3388                Expr::Match(_span, scrutinee, arms),
3389                TypedExprKind::Match {
3390                    scrutinee: tscrutinee,
3391                    arms: typed_arms,
3392                },
3393            ) => {
3394                visit(
3395                    scrutinee.as_ref(),
3396                    tscrutinee.as_ref(),
3397                    pos,
3398                    Some(&tscrutinee.typ),
3399                    best,
3400                );
3401                for ((_pat, arm), (_typed_pat, typed_arm)) in arms.iter().zip(typed_arms.iter()) {
3402                    visit(arm.as_ref(), typed_arm, pos, Some(&typed.typ), best);
3403                }
3404            }
3405            (Expr::Ann(_span, inner, _ann), _) => {
3406                visit(inner.as_ref(), typed, pos, Some(&typed.typ), best);
3407            }
3408            _ => {}
3409        }
3410    }
3411
3412    let mut best: Option<Candidate> = None;
3413    visit(expr, typed, pos, None, &mut best);
3414    best.map(|candidate| candidate.typ)
3415}
3416
3417fn inferred_type_in_expr(expr: &Expr, typed: &TypedExpr, pos: RexPosition) -> Option<Type> {
3418    fn span_size(span: Span) -> (usize, usize) {
3419        (
3420            span.end.line.saturating_sub(span.begin.line),
3421            span.end.column.saturating_sub(span.begin.column),
3422        )
3423    }
3424
3425    fn visit(expr: &Expr, typed: &TypedExpr, pos: RexPosition, best: &mut Option<(Span, Type)>) {
3426        let span = *expr.span();
3427        if !position_in_span(pos, span) {
3428            return;
3429        }
3430        if best
3431            .as_ref()
3432            .is_none_or(|(best_span, _)| span_size(span) < span_size(*best_span))
3433        {
3434            *best = Some((span, typed.typ.clone()));
3435        }
3436
3437        match (expr, typed.kind.as_ref()) {
3438            (
3439                Expr::Let(_, _, _, def, body),
3440                TypedExprKind::Let {
3441                    def: tdef,
3442                    body: tbody,
3443                    ..
3444                },
3445            ) => {
3446                visit(def.as_ref(), tdef.as_ref(), pos, best);
3447                visit(body.as_ref(), tbody.as_ref(), pos, best);
3448            }
3449            (
3450                Expr::LetRec(_, bindings, body),
3451                TypedExprKind::LetRec {
3452                    bindings: typed_bindings,
3453                    body: typed_body,
3454                },
3455            ) => {
3456                for ((_, _, def), (_, typed_def)) in bindings.iter().zip(typed_bindings.iter()) {
3457                    visit(def.as_ref(), typed_def, pos, best);
3458                }
3459                visit(body.as_ref(), typed_body.as_ref(), pos, best);
3460            }
3461            (
3462                Expr::Lam(_, _, _, _, _, body),
3463                TypedExprKind::Lam {
3464                    body: typed_body, ..
3465                },
3466            ) => {
3467                visit(body.as_ref(), typed_body.as_ref(), pos, best);
3468            }
3469            (Expr::App(_, f, x), TypedExprKind::App(tf, tx)) => {
3470                visit(f.as_ref(), tf.as_ref(), pos, best);
3471                visit(x.as_ref(), tx.as_ref(), pos, best);
3472            }
3473            (Expr::Project(_, base, _), TypedExprKind::Project { expr: tbase, .. }) => {
3474                visit(base.as_ref(), tbase.as_ref(), pos, best);
3475            }
3476            (
3477                Expr::Ite(_, cond, then_expr, else_expr),
3478                TypedExprKind::Ite {
3479                    cond: tcond,
3480                    then_expr: tthen,
3481                    else_expr: telse,
3482                },
3483            ) => {
3484                visit(cond.as_ref(), tcond.as_ref(), pos, best);
3485                visit(then_expr.as_ref(), tthen.as_ref(), pos, best);
3486                visit(else_expr.as_ref(), telse.as_ref(), pos, best);
3487            }
3488            (Expr::Tuple(_, elems), TypedExprKind::Tuple(typed_elems))
3489            | (Expr::List(_, elems), TypedExprKind::List(typed_elems)) => {
3490                for (elem, typed_elem) in elems.iter().zip(typed_elems.iter()) {
3491                    visit(elem.as_ref(), typed_elem, pos, best);
3492                }
3493            }
3494            (Expr::Dict(_, kvs), TypedExprKind::Dict(typed_kvs)) => {
3495                for (key, value) in kvs {
3496                    if let Some(typed_value) = typed_kvs.get(key) {
3497                        visit(value.as_ref(), typed_value, pos, best);
3498                    }
3499                }
3500            }
3501            (
3502                Expr::RecordUpdate(_, base, updates),
3503                TypedExprKind::RecordUpdate {
3504                    base: typed_base,
3505                    updates: typed_updates,
3506                },
3507            ) => {
3508                visit(base.as_ref(), typed_base.as_ref(), pos, best);
3509                for (key, value) in updates {
3510                    if let Some(typed_value) = typed_updates.get(key) {
3511                        visit(value.as_ref(), typed_value, pos, best);
3512                    }
3513                }
3514            }
3515            (
3516                Expr::Match(_, scrutinee, arms),
3517                TypedExprKind::Match {
3518                    scrutinee: tscrutinee,
3519                    arms: typed_arms,
3520                },
3521            ) => {
3522                visit(scrutinee.as_ref(), tscrutinee.as_ref(), pos, best);
3523                for ((_pat, arm), (_typed_pat, typed_arm)) in arms.iter().zip(typed_arms.iter()) {
3524                    visit(arm.as_ref(), typed_arm, pos, best);
3525                }
3526            }
3527            (Expr::Ann(_, inner, _), _) => visit(inner.as_ref(), typed, pos, best),
3528            _ => {}
3529        }
3530    }
3531
3532    let mut best: Option<(Span, Type)> = None;
3533    visit(expr, typed, pos, &mut best);
3534    best.map(|(_, ty)| ty)
3535}
3536
3537fn functions_producing_expected_type_at_position(
3538    uri: &Url,
3539    text: &str,
3540    position: Position,
3541) -> Vec<(String, String)> {
3542    let Some(target_type) = expected_type_at_position_type(uri, text, position) else {
3543        return Vec::new();
3544    };
3545
3546    let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) else {
3547        return Vec::new();
3548    };
3549    let Ok((program, mut ts, _imports, _import_diags)) =
3550        prepare_program_with_imports(uri, &program)
3551    else {
3552        return Vec::new();
3553    };
3554    if inject_program_decls(&mut ts, &program, None).is_err() {
3555        return Vec::new();
3556    }
3557
3558    let values = semantic_candidate_values(&ts);
3559
3560    let mut out = Vec::new();
3561    for (name, schemes) in values {
3562        for scheme in schemes {
3563            let (_preds, inst_ty) = instantiate(&scheme, &mut ts.supply);
3564            let mut cur = &inst_ty;
3565            let mut is_function = false;
3566            while let TypeKind::Fun(_, ret) = cur.as_ref() {
3567                is_function = true;
3568                cur = ret;
3569            }
3570            if !is_function {
3571                continue;
3572            }
3573            if unify(cur, &target_type).is_ok() {
3574                out.push((name.to_string(), scheme.typ.to_string()));
3575            }
3576        }
3577    }
3578
3579    out.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
3580    out.dedup();
3581    if out.len() > MAX_SEMANTIC_CANDIDATES {
3582        out.truncate(MAX_SEMANTIC_CANDIDATES);
3583    }
3584    out
3585}
3586
3587fn functions_accepting_inferred_type_at_position(
3588    uri: &Url,
3589    text: &str,
3590    position: Position,
3591) -> Vec<(String, String)> {
3592    let Some(source_type) = inferred_type_at_position_type(uri, text, position) else {
3593        return Vec::new();
3594    };
3595
3596    let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) else {
3597        return Vec::new();
3598    };
3599    let Ok((program, mut ts, _imports, _import_diags)) =
3600        prepare_program_with_imports(uri, &program)
3601    else {
3602        return Vec::new();
3603    };
3604    if inject_program_decls(&mut ts, &program, None).is_err() {
3605        return Vec::new();
3606    }
3607
3608    let values = semantic_candidate_values(&ts);
3609
3610    let mut out = Vec::new();
3611    for (name, schemes) in values {
3612        let name = name.to_string();
3613        if !is_ident_like(&name) {
3614            continue;
3615        }
3616        for scheme in schemes {
3617            let (_preds, inst_ty) = instantiate(&scheme, &mut ts.supply);
3618            let (args, _ret) = split_fun_type(&inst_ty);
3619            if let Some(first_arg) = args.first()
3620                && unify(first_arg, &source_type).is_ok()
3621            {
3622                out.push((name.clone(), scheme.typ.to_string()));
3623            }
3624        }
3625    }
3626
3627    out.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
3628    out.dedup();
3629    if out.len() > MAX_SEMANTIC_CANDIDATES {
3630        out.truncate(MAX_SEMANTIC_CANDIDATES);
3631    }
3632    out
3633}
3634
3635fn adapters_from_inferred_to_expected_at_position(
3636    uri: &Url,
3637    text: &str,
3638    position: Position,
3639) -> Vec<(String, String)> {
3640    let Some(source_type) = inferred_type_at_position_type(uri, text, position) else {
3641        return Vec::new();
3642    };
3643    let Some(target_type) = expected_type_at_position_type(uri, text, position) else {
3644        return Vec::new();
3645    };
3646
3647    let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) else {
3648        return Vec::new();
3649    };
3650    let Ok((program, mut ts, _imports, _import_diags)) =
3651        prepare_program_with_imports(uri, &program)
3652    else {
3653        return Vec::new();
3654    };
3655    if inject_program_decls(&mut ts, &program, None).is_err() {
3656        return Vec::new();
3657    }
3658
3659    let values = semantic_candidate_values(&ts);
3660
3661    let mut out = Vec::new();
3662    for (name, schemes) in values {
3663        let name = name.to_string();
3664        if !is_ident_like(&name) {
3665            continue;
3666        }
3667        for scheme in schemes {
3668            let (_preds, inst_ty) = instantiate(&scheme, &mut ts.supply);
3669            let (args, ret) = split_fun_type(&inst_ty);
3670            if args.len() == 1
3671                && unify(&args[0], &source_type).is_ok()
3672                && unify(&ret, &target_type).is_ok()
3673            {
3674                out.push((name.clone(), scheme.typ.to_string()));
3675            }
3676        }
3677    }
3678
3679    out.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
3680    out.dedup();
3681    if out.len() > MAX_SEMANTIC_CANDIDATES {
3682        out.truncate(MAX_SEMANTIC_CANDIDATES);
3683    }
3684    out
3685}
3686
3687fn functions_compatible_with_in_scope_values_at_position(
3688    uri: &Url,
3689    text: &str,
3690    position: Position,
3691) -> Vec<String> {
3692    let produced = functions_producing_expected_type_at_position(uri, text, position);
3693    let mut produced_by_name: HashMap<String, Vec<String>> = HashMap::new();
3694    for (name, typ) in produced {
3695        produced_by_name.entry(name).or_default().push(typ);
3696    }
3697
3698    let mut out = Vec::new();
3699    for (name, replacement) in hole_fill_candidates_at_position(uri, text, position) {
3700        if replacement.contains('?') {
3701            continue;
3702        }
3703        if let Some(types) = produced_by_name.get(&name) {
3704            for typ in types {
3705                out.push(format!("{name} : {typ} => {replacement}"));
3706            }
3707        } else {
3708            out.push(format!("{name} => {replacement}"));
3709        }
3710    }
3711    out.sort();
3712    out.dedup();
3713    if out.len() > MAX_SEMANTIC_CANDIDATES {
3714        out.truncate(MAX_SEMANTIC_CANDIDATES);
3715    }
3716    out
3717}
3718
3719pub fn execute_query_command_for_document(
3720    command: &str,
3721    uri: &Url,
3722    text: &str,
3723    position: Position,
3724) -> Option<Value> {
3725    match command {
3726        CMD_EXPECTED_TYPE_AT => Some(match expected_type_at_position(uri, text, position) {
3727            Some(typ) => json!({ "expectedType": typ }),
3728            None => Value::Null,
3729        }),
3730        CMD_FUNCTIONS_ACCEPTING_INFERRED_TYPE_AT => Some(json!({
3731            "inferredType": inferred_type_at_position(uri, text, position),
3732            "items": functions_accepting_inferred_type_at_position(uri, text, position)
3733                .into_iter()
3734                .map(|(name, typ)| format!("{name} : {typ}"))
3735                .collect::<Vec<_>>()
3736        })),
3737        CMD_ADAPTERS_FROM_INFERRED_TO_EXPECTED_AT => Some(json!({
3738            "inferredType": inferred_type_at_position(uri, text, position),
3739            "expectedType": expected_type_at_position(uri, text, position),
3740            "items": adapters_from_inferred_to_expected_at_position(uri, text, position)
3741                .into_iter()
3742                .map(|(name, typ)| format!("{name} : {typ}"))
3743                .collect::<Vec<_>>()
3744        })),
3745        CMD_FUNCTIONS_COMPATIBLE_WITH_IN_SCOPE_VALUES_AT => Some(json!({
3746            "items": functions_compatible_with_in_scope_values_at_position(uri, text, position)
3747        })),
3748        CMD_FUNCTIONS_PRODUCING_EXPECTED_TYPE_AT => {
3749            let items = functions_producing_expected_type_at_position(uri, text, position)
3750                .into_iter()
3751                .map(|(name, typ)| format!("{name} : {typ}"))
3752                .collect::<Vec<_>>();
3753            Some(json!({ "items": items }))
3754        }
3755        _ => None,
3756    }
3757}
3758
3759pub fn execute_query_command_for_document_without_position(
3760    command: &str,
3761    uri: &Url,
3762    text: &str,
3763) -> Option<Value> {
3764    match command {
3765        CMD_HOLES_EXPECTED_TYPES => Some(json!({
3766            "holes": hole_expected_types_for_document(uri, text)
3767        })),
3768        _ => None,
3769    }
3770}
3771
3772fn workspace_edit_fingerprint(edit: &WorkspaceEdit) -> String {
3773    let mut payload = String::new();
3774    if let Some(changes) = &edit.changes {
3775        let mut uris = changes.keys().cloned().collect::<Vec<_>>();
3776        uris.sort_by(|a, b| a.as_str().cmp(b.as_str()));
3777        for uri in uris {
3778            payload.push_str(uri.as_str());
3779            payload.push('\n');
3780            if let Some(edits) = changes.get(&uri) {
3781                for edit in edits {
3782                    payload.push_str(&format!(
3783                        "{}:{}-{}:{}\n",
3784                        edit.range.start.line,
3785                        edit.range.start.character,
3786                        edit.range.end.line,
3787                        edit.range.end.character
3788                    ));
3789                    payload.push_str(&edit.new_text);
3790                    payload.push('\n');
3791                }
3792            }
3793        }
3794    }
3795    if let Some(document_changes) = &edit.document_changes
3796        && let Ok(encoded) = serde_json::to_string(document_changes)
3797    {
3798        payload.push_str(&encoded);
3799    }
3800    if let Some(change_annotations) = &edit.change_annotations
3801        && let Ok(encoded) = serde_json::to_string(change_annotations)
3802    {
3803        payload.push_str(&encoded);
3804    }
3805    sha256_hex(payload.as_bytes())
3806}
3807
3808fn semantic_quick_fixes_for_range(
3809    uri: &Url,
3810    text: &str,
3811    cursor_range: Range,
3812    diagnostics: &[Diagnostic],
3813) -> Vec<Value> {
3814    let mut out = code_actions_for_source(uri, text, cursor_range, diagnostics)
3815        .into_iter()
3816        .filter_map(|action| match action {
3817            CodeActionOrCommand::CodeAction(action) => Some(action),
3818            CodeActionOrCommand::Command(_) => None,
3819        })
3820        .map(|action| {
3821            let kind = action
3822                .kind
3823                .and_then(|k| to_value(k).ok())
3824                .and_then(|v| v.as_str().map(str::to_string));
3825            let edit = action.edit.unwrap_or(WorkspaceEdit {
3826                changes: None,
3827                document_changes: None,
3828                change_annotations: None,
3829            });
3830            let fingerprint = workspace_edit_fingerprint(&edit);
3831            json!({
3832                "id": format!("qf-{}", &fingerprint[..16]),
3833                "title": action.title,
3834                "kind": kind,
3835                "edit": to_value(edit).unwrap_or(Value::Null),
3836            })
3837        })
3838        .collect::<Vec<_>>();
3839
3840    out.sort_by_key(|item| {
3841        (
3842            item.get("title")
3843                .and_then(Value::as_str)
3844                .unwrap_or("")
3845                .to_string(),
3846            item.get("id")
3847                .and_then(Value::as_str)
3848                .unwrap_or("")
3849                .to_string(),
3850        )
3851    });
3852    out.dedup_by(|a, b| a.get("id") == b.get("id"));
3853    out
3854}
3855
3856pub fn execute_semantic_loop_step(uri: &Url, text: &str, position: Position) -> Option<Value> {
3857    let expected_type = expected_type_at_position(uri, text, position)
3858        .or_else(|| expected_type_from_syntax_context(uri, text, position));
3859    let inferred_type = inferred_type_at_position(uri, text, position);
3860
3861    let mut in_scope_values = in_scope_value_types_at_position(uri, text, position)
3862        .into_iter()
3863        .filter(|(name, _)| is_ident_like(name))
3864        .map(|(name, typ)| format!("{name} : {typ}"))
3865        .collect::<Vec<_>>();
3866    in_scope_values.sort();
3867    in_scope_values.dedup();
3868    if in_scope_values.len() > MAX_SEMANTIC_IN_SCOPE_VALUES {
3869        in_scope_values.truncate(MAX_SEMANTIC_IN_SCOPE_VALUES);
3870    }
3871
3872    let function_candidates = functions_producing_expected_type_at_position(uri, text, position)
3873        .into_iter()
3874        .map(|(name, typ)| format!("{name} : {typ}"))
3875        .collect::<Vec<_>>();
3876
3877    let hole_fill_candidates = hole_fill_candidates_at_position(uri, text, position)
3878        .into_iter()
3879        .map(|(name, replacement)| json!({ "name": name, "replacement": replacement }))
3880        .collect::<Vec<_>>();
3881    let functions_accepting_inferred_type =
3882        functions_accepting_inferred_type_at_position(uri, text, position)
3883            .into_iter()
3884            .map(|(name, typ)| format!("{name} : {typ}"))
3885            .collect::<Vec<_>>();
3886    let adapters_from_inferred_to_expected =
3887        adapters_from_inferred_to_expected_at_position(uri, text, position)
3888            .into_iter()
3889            .map(|(name, typ)| format!("{name} : {typ}"))
3890            .collect::<Vec<_>>();
3891    let compatible_with_in_scope_values =
3892        functions_compatible_with_in_scope_values_at_position(uri, text, position);
3893
3894    let cursor_range = Range {
3895        start: position,
3896        end: position,
3897    };
3898    let mut local_diagnostics: Vec<Diagnostic> = diagnostics_from_text(uri, text)
3899        .into_iter()
3900        .filter(|diag| ranges_overlap(diag.range, cursor_range))
3901        .collect();
3902    local_diagnostics.sort_by_key(|diag| {
3903        (
3904            diag.range.start.line,
3905            diag.range.start.character,
3906            diag.range.end.line,
3907            diag.range.end.character,
3908            diag.message.clone(),
3909        )
3910    });
3911
3912    let quick_fixes = semantic_quick_fixes_for_range(uri, text, cursor_range, &local_diagnostics);
3913    let mut quick_fix_titles = quick_fixes
3914        .iter()
3915        .filter_map(|item| item.get("title").and_then(Value::as_str))
3916        .map(ToString::to_string)
3917        .collect::<Vec<_>>();
3918    quick_fix_titles.sort();
3919    quick_fix_titles.dedup();
3920
3921    Some(json!({
3922        "expectedType": expected_type,
3923        "inferredType": inferred_type,
3924        "inScopeValues": in_scope_values,
3925        "functionCandidates": function_candidates,
3926        "holeFillCandidates": hole_fill_candidates,
3927        "functionsAcceptingInferredType": functions_accepting_inferred_type,
3928        "adaptersFromInferredToExpectedType": adapters_from_inferred_to_expected,
3929        "functionsCompatibleWithInScopeValues": compatible_with_in_scope_values,
3930        "localDiagnostics": local_diagnostics.into_iter().map(|diag| {
3931            json!({
3932                "message": diag.message,
3933                "line": diag.range.start.line,
3934                "character": diag.range.start.character,
3935            })
3936        }).collect::<Vec<_>>(),
3937        "quickFixes": quick_fixes,
3938        "quickFixTitles": quick_fix_titles,
3939        "holes": hole_expected_types_for_document(uri, text),
3940    }))
3941}
3942
3943pub fn execute_semantic_loop_apply_quick_fix(
3944    uri: &Url,
3945    text: &str,
3946    position: Position,
3947    quick_fix_id: &str,
3948) -> Option<Value> {
3949    let cursor_range = Range {
3950        start: position,
3951        end: position,
3952    };
3953    let local_diagnostics: Vec<Diagnostic> = diagnostics_from_text(uri, text)
3954        .into_iter()
3955        .filter(|diag| ranges_overlap(diag.range, cursor_range))
3956        .collect();
3957    let quick_fixes = semantic_quick_fixes_for_range(uri, text, cursor_range, &local_diagnostics);
3958    let quick_fix = quick_fixes.into_iter().find(|item| {
3959        item.get("id")
3960            .and_then(Value::as_str)
3961            .is_some_and(|id| id == quick_fix_id)
3962    });
3963
3964    Some(match quick_fix {
3965        Some(quick_fix) => json!({ "quickFix": quick_fix }),
3966        None => Value::Null,
3967    })
3968}
3969
3970fn quick_fix_priority(strategy: BulkQuickFixStrategy, title: &str) -> usize {
3971    let aggressive_introduce =
3972        strategy == BulkQuickFixStrategy::Aggressive && title.starts_with("Introduce `let ");
3973    if title.starts_with("Fill hole with `") {
3974        0
3975    } else if title.starts_with("Replace `") || aggressive_introduce {
3976        1
3977    } else if title.starts_with("Add wildcard arm") {
3978        2
3979    } else if title.starts_with("Wrap expression in list literal") {
3980        3
3981    } else if title.starts_with("Unwrap single-item list literal") {
3982        4
3983    } else if title.starts_with("Apply expression to missing argument") {
3984        5
3985    } else if title.starts_with("Wrap expression in lambda") {
3986        6
3987    } else if title.starts_with("Introduce `let ") {
3988        7
3989    } else {
3990        10
3991    }
3992}
3993
3994pub fn best_quick_fix_from_candidates(
3995    candidates: &[Value],
3996    strategy: BulkQuickFixStrategy,
3997) -> Option<Value> {
3998    candidates
3999        .iter()
4000        .min_by_key(|item| {
4001            let title = item.get("title").and_then(Value::as_str).unwrap_or("");
4002            let id = item.get("id").and_then(Value::as_str).unwrap_or("");
4003            (
4004                quick_fix_priority(strategy, title),
4005                title.to_string(),
4006                id.to_string(),
4007            )
4008        })
4009        .cloned()
4010}
4011
4012pub fn apply_workspace_edit_to_text(uri: &Url, text: &str, edit: &WorkspaceEdit) -> Option<String> {
4013    let changes = edit.changes.as_ref()?;
4014    let edits = changes.get(uri)?.clone();
4015    if edits.is_empty() {
4016        return Some(text.to_string());
4017    }
4018    let mut with_offsets = Vec::new();
4019    for edit in edits {
4020        let start = offset_at(text, edit.range.start)?;
4021        let end = offset_at(text, edit.range.end)?;
4022        if start > end || end > text.len() {
4023            return None;
4024        }
4025        with_offsets.push((start, end, edit.new_text));
4026    }
4027    with_offsets.sort_by(|a, b| b.0.cmp(&a.0).then(b.1.cmp(&a.1)));
4028
4029    let mut out = text.to_string();
4030    for (start, end, replacement) in with_offsets {
4031        out.replace_range(start..end, &replacement);
4032    }
4033    Some(out)
4034}
4035
4036pub fn text_state_hash(text: &str) -> String {
4037    sha256_hex(text.as_bytes())
4038}
4039
4040pub fn next_no_improvement_streak(streak: usize, diagnostics_delta: i64) -> usize {
4041    if diagnostics_delta > 0 { 0 } else { streak + 1 }
4042}
4043
4044pub fn execute_semantic_loop_apply_best_quick_fixes(
4045    uri: &Url,
4046    text: &str,
4047    position: Position,
4048    max_steps: usize,
4049    strategy: BulkQuickFixStrategy,
4050    dry_run: bool,
4051) -> Option<Value> {
4052    let cursor_range = Range {
4053        start: position,
4054        end: position,
4055    };
4056    let mut current_text = text.to_string();
4057    let mut applied = Vec::new();
4058    let mut steps = Vec::new();
4059    let mut stopped_reason = "noQuickFix".to_string();
4060    let mut stopped_reason_detail = "no quick-fixes available at cursor".to_string();
4061    let mut no_improvement_streak = 0usize;
4062    let mut last_diagnostics_delta = 0i64;
4063    let mut seen_states: HashSet<String> = HashSet::new();
4064    seen_states.insert(text_state_hash(&current_text));
4065
4066    for step_index in 0..max_steps {
4067        let local_diagnostics: Vec<Diagnostic> = diagnostics_from_text(uri, &current_text)
4068            .into_iter()
4069            .filter(|diag| ranges_overlap(diag.range, cursor_range))
4070            .collect();
4071        let diagnostics_before = local_diagnostics
4072            .iter()
4073            .map(|diag| {
4074                json!({
4075                    "message": diag.message,
4076                    "line": diag.range.start.line,
4077                    "character": diag.range.start.character,
4078                })
4079            })
4080            .collect::<Vec<_>>();
4081        let quick_fixes =
4082            semantic_quick_fixes_for_range(uri, &current_text, cursor_range, &local_diagnostics);
4083        let Some(best) = best_quick_fix_from_candidates(&quick_fixes, strategy) else {
4084            stopped_reason = "noQuickFix".to_string();
4085            stopped_reason_detail = "no candidate quick-fix was available".to_string();
4086            break;
4087        };
4088        let edit_value = best.get("edit").cloned().unwrap_or(Value::Null);
4089        let Ok(edit) = serde_json::from_value::<WorkspaceEdit>(edit_value) else {
4090            stopped_reason = "invalidEdit".to_string();
4091            stopped_reason_detail = "selected quick-fix edit was invalid".to_string();
4092            break;
4093        };
4094        let Some(next_text) = apply_workspace_edit_to_text(uri, &current_text, &edit) else {
4095            stopped_reason = "applyFailed".to_string();
4096            stopped_reason_detail = "failed to apply selected workspace edit".to_string();
4097            break;
4098        };
4099        if next_text == current_text {
4100            stopped_reason = "noTextChange".to_string();
4101            stopped_reason_detail = "selected quick-fix did not change text".to_string();
4102            break;
4103        }
4104        let next_hash = text_state_hash(&next_text);
4105        if seen_states.contains(&next_hash) {
4106            stopped_reason = "cycleDetected".to_string();
4107            stopped_reason_detail = "next text state already seen in this run".to_string();
4108            break;
4109        }
4110        let diagnostics_after_step: Vec<Value> = diagnostics_from_text(uri, &next_text)
4111            .into_iter()
4112            .filter(|diag| ranges_overlap(diag.range, cursor_range))
4113            .map(|diag| {
4114                json!({
4115                    "message": diag.message,
4116                    "line": diag.range.start.line,
4117                    "character": diag.range.start.character,
4118                })
4119            })
4120            .collect();
4121        let before_count = diagnostics_before.len();
4122        let after_count = diagnostics_after_step.len();
4123        let diagnostics_delta = (before_count as i64) - (after_count as i64);
4124        last_diagnostics_delta = diagnostics_delta;
4125        no_improvement_streak =
4126            next_no_improvement_streak(no_improvement_streak, diagnostics_delta);
4127        steps.push(json!({
4128            "index": step_index,
4129            "quickFix": best.clone(),
4130            "diagnosticsBefore": diagnostics_before,
4131            "diagnosticsAfter": diagnostics_after_step,
4132            "diagnosticsBeforeCount": before_count,
4133            "diagnosticsAfterCount": after_count,
4134            "diagnosticsDelta": diagnostics_delta,
4135            "noImprovementStreak": no_improvement_streak,
4136        }));
4137        applied.push(best);
4138        current_text = next_text;
4139        seen_states.insert(next_hash);
4140        if no_improvement_streak >= NO_IMPROVEMENT_STREAK_LIMIT {
4141            stopped_reason = "noImprovementStreak".to_string();
4142            stopped_reason_detail =
4143                format!("diagnostics did not improve for {NO_IMPROVEMENT_STREAK_LIMIT} step(s)");
4144            break;
4145        }
4146        stopped_reason = "maxStepsReached".to_string();
4147        stopped_reason_detail = format!("reached maxSteps={max_steps}");
4148    }
4149
4150    let diagnostics_after: Vec<Value> = diagnostics_from_text(uri, &current_text)
4151        .into_iter()
4152        .filter(|diag| ranges_overlap(diag.range, cursor_range))
4153        .map(|diag| {
4154            json!({
4155                "message": diag.message,
4156                "line": diag.range.start.line,
4157                "character": diag.range.start.character,
4158            })
4159        })
4160        .collect();
4161
4162    Some(json!({
4163        "strategy": strategy.as_str(),
4164        "dryRun": dry_run,
4165        "appliedQuickFixes": applied,
4166        "appliedCount": applied.len(),
4167        "steps": steps,
4168        "updatedText": current_text,
4169        "localDiagnosticsAfter": diagnostics_after,
4170        "stoppedReason": stopped_reason,
4171        "stoppedReasonDetail": stopped_reason_detail,
4172        "lastDiagnosticsDelta": last_diagnostics_delta,
4173        "noImprovementStreak": no_improvement_streak,
4174        "seenStatesCount": seen_states.len(),
4175    }))
4176}
4177
4178pub fn hole_expected_types_for_document(uri: &Url, text: &str) -> Vec<Value> {
4179    let mut holes = Vec::new();
4180
4181    // First-class holes: parse `?` nodes directly.
4182    if let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text)
4183        && let Some(body) = program.body_with_fns()
4184    {
4185        let mut spans = Vec::new();
4186        collect_hole_spans(body.as_ref(), &mut spans);
4187        for span in spans {
4188            let pos = span_to_range(span).start;
4189            if let Some(expected_type) = expected_type_at_position(uri, text, pos)
4190                .or_else(|| expected_type_from_syntax_context(uri, text, pos))
4191            {
4192                holes.push(json!({
4193                    "name": "?",
4194                    "line": pos.line,
4195                    "character": pos.character,
4196                    "expectedType": expected_type
4197                }));
4198            }
4199        }
4200    }
4201
4202    // Backward-compat fallback: `_foo` placeholder variables still treated as holes.
4203    let diagnostics = diagnostics_from_text(uri, text);
4204    for diag in diagnostics {
4205        let Some(name) = unknown_var_name_from_message(&diag.message) else {
4206            continue;
4207        };
4208        if !is_hole_name(name) {
4209            continue;
4210        }
4211        if !range_is_usable_for_text(text, diag.range) {
4212            continue;
4213        }
4214        let pos = diag.range.start;
4215        if let Some(expected_type) = expected_type_at_position(uri, text, pos)
4216            .or_else(|| expected_type_from_syntax_context(uri, text, pos))
4217        {
4218            holes.push(json!({
4219                "name": name,
4220                "line": pos.line,
4221                "character": pos.character,
4222                "expectedType": expected_type
4223            }));
4224        }
4225    }
4226    holes.sort_by_key(|item| {
4227        let line = item.get("line").and_then(Value::as_u64).unwrap_or(0);
4228        let ch = item.get("character").and_then(Value::as_u64).unwrap_or(0);
4229        let name = item
4230            .get("name")
4231            .and_then(Value::as_str)
4232            .unwrap_or("")
4233            .to_string();
4234        (line, ch, name)
4235    });
4236    holes.dedup_by(|a, b| {
4237        a.get("name") == b.get("name")
4238            && a.get("line") == b.get("line")
4239            && a.get("character") == b.get("character")
4240    });
4241    if holes.len() > MAX_SEMANTIC_HOLES {
4242        holes.truncate(MAX_SEMANTIC_HOLES);
4243    }
4244    holes
4245}
4246
4247fn collect_hole_spans(expr: &Expr, out: &mut Vec<Span>) {
4248    match expr {
4249        Expr::Hole(span) => out.push(*span),
4250        Expr::App(_, f, x) => {
4251            collect_hole_spans(f, out);
4252            collect_hole_spans(x, out);
4253        }
4254        Expr::Project(_, base, _) => collect_hole_spans(base, out),
4255        Expr::Lam(_, _scope, _param, _ann, _constraints, body) => collect_hole_spans(body, out),
4256        Expr::Let(_, _var, _ann, def, body) => {
4257            collect_hole_spans(def, out);
4258            collect_hole_spans(body, out);
4259        }
4260        Expr::LetRec(_, bindings, body) => {
4261            for (_var, _ann, def) in bindings {
4262                collect_hole_spans(def, out);
4263            }
4264            collect_hole_spans(body, out);
4265        }
4266        Expr::Ite(_, cond, then_expr, else_expr) => {
4267            collect_hole_spans(cond, out);
4268            collect_hole_spans(then_expr, out);
4269            collect_hole_spans(else_expr, out);
4270        }
4271        Expr::Match(_, scrutinee, arms) => {
4272            collect_hole_spans(scrutinee, out);
4273            for (_pat, arm) in arms {
4274                collect_hole_spans(arm, out);
4275            }
4276        }
4277        Expr::Ann(_, inner, _) => collect_hole_spans(inner, out),
4278        Expr::Tuple(_, elems) | Expr::List(_, elems) => {
4279            for elem in elems {
4280                collect_hole_spans(elem, out);
4281            }
4282        }
4283        Expr::Dict(_, kvs) => {
4284            for value in kvs.values() {
4285                collect_hole_spans(value, out);
4286            }
4287        }
4288        Expr::RecordUpdate(_, base, updates) => {
4289            collect_hole_spans(base, out);
4290            for value in updates.values() {
4291                collect_hole_spans(value, out);
4292            }
4293        }
4294        Expr::Var(_)
4295        | Expr::Bool(..)
4296        | Expr::Uint(..)
4297        | Expr::Int(..)
4298        | Expr::Float(..)
4299        | Expr::String(..)
4300        | Expr::Uuid(..)
4301        | Expr::DateTime(..) => {}
4302    }
4303}
4304
4305fn expected_type_from_syntax_context(uri: &Url, text: &str, position: Position) -> Option<String> {
4306    let (_tokens, program) = tokenize_and_parse_cached(uri, text).ok()?;
4307    let pos = lsp_to_rex_position(position);
4308
4309    fn visit(expr: &Expr, pos: RexPosition) -> Option<String> {
4310        if !position_in_span(pos, *expr.span()) {
4311            return None;
4312        }
4313        match expr {
4314            Expr::Let(_span, _name, ann, def, body) => {
4315                if position_in_span(pos, *def.span())
4316                    && let Some(ann) = ann
4317                {
4318                    return Some(ann.to_string());
4319                }
4320                visit(def.as_ref(), pos).or_else(|| visit(body.as_ref(), pos))
4321            }
4322            Expr::Ann(_span, inner, ann) => {
4323                if position_in_span(pos, *inner.span()) {
4324                    return Some(ann.to_string());
4325                }
4326                visit(inner.as_ref(), pos)
4327            }
4328            Expr::Ite(_span, cond, then_expr, else_expr) => {
4329                if position_in_span(pos, *cond.span()) {
4330                    return Some("bool".to_string());
4331                }
4332                visit(cond.as_ref(), pos)
4333                    .or_else(|| visit(then_expr.as_ref(), pos))
4334                    .or_else(|| visit(else_expr.as_ref(), pos))
4335            }
4336            Expr::App(_span, f, x) => visit(f.as_ref(), pos).or_else(|| visit(x.as_ref(), pos)),
4337            Expr::Project(_span, base, _field) => visit(base.as_ref(), pos),
4338            Expr::Lam(_span, _scope, _param, _ann, _constraints, body) => visit(body.as_ref(), pos),
4339            Expr::LetRec(_span, bindings, body) => {
4340                for (_name, _ann, def) in bindings {
4341                    if let Some(found) = visit(def.as_ref(), pos) {
4342                        return Some(found);
4343                    }
4344                }
4345                visit(body.as_ref(), pos)
4346            }
4347            Expr::Match(_span, scrutinee, arms) => {
4348                if let Some(found) = visit(scrutinee.as_ref(), pos) {
4349                    return Some(found);
4350                }
4351                for (_pat, arm) in arms {
4352                    if let Some(found) = visit(arm.as_ref(), pos) {
4353                        return Some(found);
4354                    }
4355                }
4356                None
4357            }
4358            Expr::Tuple(_span, elems) | Expr::List(_span, elems) => {
4359                for elem in elems {
4360                    if let Some(found) = visit(elem.as_ref(), pos) {
4361                        return Some(found);
4362                    }
4363                }
4364                None
4365            }
4366            Expr::Dict(_span, kvs) => {
4367                for value in kvs.values() {
4368                    if let Some(found) = visit(value.as_ref(), pos) {
4369                        return Some(found);
4370                    }
4371                }
4372                None
4373            }
4374            Expr::RecordUpdate(_span, base, updates) => {
4375                if let Some(found) = visit(base.as_ref(), pos) {
4376                    return Some(found);
4377                }
4378                for value in updates.values() {
4379                    if let Some(found) = visit(value.as_ref(), pos) {
4380                        return Some(found);
4381                    }
4382                }
4383                None
4384            }
4385            Expr::Var(_)
4386            | Expr::Bool(..)
4387            | Expr::Uint(..)
4388            | Expr::Int(..)
4389            | Expr::Float(..)
4390            | Expr::String(..)
4391            | Expr::Uuid(..)
4392            | Expr::DateTime(..)
4393            | Expr::Hole(..) => None,
4394        }
4395    }
4396
4397    let body = program.body_with_fns()?;
4398    visit(body.as_ref(), pos)
4399}
4400
4401pub fn command_uri_and_position(arguments: &[Value]) -> Option<(Url, Position)> {
4402    if arguments.len() >= 3 {
4403        let uri = arguments.first()?.as_str()?;
4404        let line = arguments.get(1)?.as_u64()? as u32;
4405        let character = arguments.get(2)?.as_u64()? as u32;
4406        let uri = Url::parse(uri).ok()?;
4407        return Some((uri, Position { line, character }));
4408    }
4409
4410    let obj = arguments.first()?.as_object()?;
4411    let uri = obj.get("uri")?.as_str()?;
4412    let line = obj.get("line")?.as_u64()? as u32;
4413    let character = obj.get("character")?.as_u64()? as u32;
4414    let uri = Url::parse(uri).ok()?;
4415    Some((uri, Position { line, character }))
4416}
4417
4418#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
4419pub(crate) fn command_uri(arguments: &[Value]) -> Option<Url> {
4420    if arguments.is_empty() {
4421        return None;
4422    }
4423    if let Some(uri) = arguments.first().and_then(Value::as_str) {
4424        return Url::parse(uri).ok();
4425    }
4426    let obj = arguments.first()?.as_object()?;
4427    let uri = obj.get("uri")?.as_str()?;
4428    Url::parse(uri).ok()
4429}
4430
4431pub fn command_uri_position_and_id(arguments: &[Value]) -> Option<(Url, Position, String)> {
4432    if arguments.len() >= 4 {
4433        let uri = arguments.first()?.as_str()?;
4434        let line = arguments.get(1)?.as_u64()? as u32;
4435        let character = arguments.get(2)?.as_u64()? as u32;
4436        let id = arguments.get(3)?.as_str()?.to_string();
4437        let uri = Url::parse(uri).ok()?;
4438        return Some((uri, Position { line, character }, id));
4439    }
4440
4441    let obj = arguments.first()?.as_object()?;
4442    let uri = obj.get("uri")?.as_str()?;
4443    let line = obj.get("line")?.as_u64()? as u32;
4444    let character = obj.get("character")?.as_u64()? as u32;
4445    let id = obj.get("id")?.as_str()?.to_string();
4446    let uri = Url::parse(uri).ok()?;
4447    Some((uri, Position { line, character }, id))
4448}
4449
4450pub fn command_uri_position_max_steps_strategy_and_dry_run(
4451    arguments: &[Value],
4452) -> Option<(Url, Position, usize, BulkQuickFixStrategy, bool)> {
4453    if arguments.len() >= 3 {
4454        let uri = arguments.first()?.as_str()?;
4455        let line = arguments.get(1)?.as_u64()? as u32;
4456        let character = arguments.get(2)?.as_u64()? as u32;
4457        let max_steps = arguments
4458            .get(3)
4459            .and_then(Value::as_u64)
4460            .map(|n| n as usize)
4461            .unwrap_or(3);
4462        let strategy = arguments
4463            .get(4)
4464            .and_then(Value::as_str)
4465            .map(BulkQuickFixStrategy::parse)
4466            .unwrap_or(BulkQuickFixStrategy::Conservative);
4467        let dry_run = arguments.get(5).and_then(Value::as_bool).unwrap_or(false);
4468        let uri = Url::parse(uri).ok()?;
4469        return Some((
4470            uri,
4471            Position { line, character },
4472            max_steps.clamp(1, 20),
4473            strategy,
4474            dry_run,
4475        ));
4476    }
4477
4478    let obj = arguments.first()?.as_object()?;
4479    let uri = obj.get("uri")?.as_str()?;
4480    let line = obj.get("line")?.as_u64()? as u32;
4481    let character = obj.get("character")?.as_u64()? as u32;
4482    let max_steps = obj
4483        .get("maxSteps")
4484        .and_then(Value::as_u64)
4485        .map(|n| n as usize)
4486        .unwrap_or(3)
4487        .clamp(1, 20);
4488    let strategy = obj
4489        .get("strategy")
4490        .and_then(Value::as_str)
4491        .map(BulkQuickFixStrategy::parse)
4492        .unwrap_or(BulkQuickFixStrategy::Conservative);
4493    let dry_run = obj.get("dryRun").and_then(Value::as_bool).unwrap_or(false);
4494    let uri = Url::parse(uri).ok()?;
4495    Some((
4496        uri,
4497        Position { line, character },
4498        max_steps,
4499        strategy,
4500        dry_run,
4501    ))
4502}
4503
4504fn hover_type_in_expr(
4505    ts: &mut TypeSystem,
4506    expr: &Expr,
4507    typed: &TypedExpr,
4508    pos: RexPosition,
4509    name: &str,
4510    name_span: Span,
4511    name_is_ident: bool,
4512) -> Option<HoverType> {
4513    fn span_contains_pos(span: Span, pos: RexPosition) -> bool {
4514        position_in_span(pos, span)
4515    }
4516
4517    fn span_contains_span(outer: Span, inner: Span) -> bool {
4518        position_leq(outer.begin, inner.begin) && position_leq(inner.end, outer.end)
4519    }
4520
4521    fn span_size(span: Span) -> (usize, usize) {
4522        (
4523            span.end.line.saturating_sub(span.begin.line),
4524            span.end.column.saturating_sub(span.begin.column),
4525        )
4526    }
4527
4528    fn peel_fun(ty: &Type) -> (Vec<Type>, Type) {
4529        let mut args = Vec::new();
4530        let mut cur = ty.clone();
4531        while let TypeKind::Fun(a, b) = cur.as_ref() {
4532            args.push(a.clone());
4533            cur = b.clone();
4534        }
4535        (args, cur)
4536    }
4537
4538    fn add_bindings_from_pattern(
4539        ts: &mut TypeSystem,
4540        scrutinee_ty: &Type,
4541        pat: &Pattern,
4542        out: &mut HashMap<String, Type>,
4543    ) {
4544        match pat {
4545            Pattern::Wildcard(..) => {}
4546            Pattern::Var(v) => {
4547                out.insert(v.name.as_ref().to_string(), scrutinee_ty.clone());
4548            }
4549            Pattern::Named(_span, ctor, args) => {
4550                let ctor_name = ctor.to_dotted_symbol();
4551                let Some(schemes) = ts.env.lookup(&ctor_name) else {
4552                    return;
4553                };
4554                let Some(scheme) = schemes.first() else {
4555                    return;
4556                };
4557
4558                let (_preds, ctor_ty) = instantiate(scheme, &mut ts.supply);
4559                let (arg_tys, result_ty) = peel_fun(&ctor_ty);
4560                let Ok(s) = unify(&result_ty, scrutinee_ty) else {
4561                    return;
4562                };
4563
4564                for (subpat, arg_ty) in args.iter().zip(arg_tys.iter()) {
4565                    add_bindings_from_pattern(ts, &arg_ty.apply(&s), subpat, out);
4566                }
4567            }
4568            Pattern::Tuple(_span, elems) => {
4569                let elem_tys: Vec<Type> = (0..elems.len())
4570                    .map(|_| Type::var(ts.supply.fresh(None)))
4571                    .collect();
4572                let expected = Type::tuple(elem_tys.clone());
4573                let Ok(s) = unify(scrutinee_ty, &expected) else {
4574                    return;
4575                };
4576                for (p, ty) in elems.iter().zip(elem_tys.iter()) {
4577                    add_bindings_from_pattern(ts, &ty.apply(&s), p, out);
4578                }
4579            }
4580            Pattern::List(_span, elems) => {
4581                let tv = ts.supply.fresh(None);
4582                let elem = Type::var(tv.clone());
4583                let list_ty = Type::app(Type::builtin(BuiltinTypeId::List), elem.clone());
4584                let Ok(s) = unify(scrutinee_ty, &list_ty) else {
4585                    return;
4586                };
4587                let elem_ty = elem.apply(&s);
4588                for p in elems {
4589                    add_bindings_from_pattern(ts, &elem_ty, p, out);
4590                }
4591            }
4592            Pattern::Cons(_span, head, tail) => {
4593                let tv = ts.supply.fresh(None);
4594                let elem = Type::var(tv.clone());
4595                let list_ty = Type::app(Type::builtin(BuiltinTypeId::List), elem.clone());
4596                let Ok(s) = unify(scrutinee_ty, &list_ty) else {
4597                    return;
4598                };
4599                let elem_ty = elem.apply(&s);
4600                let list_ty = list_ty.apply(&s);
4601                add_bindings_from_pattern(ts, &elem_ty, head.as_ref(), out);
4602                add_bindings_from_pattern(ts, &list_ty, tail.as_ref(), out);
4603            }
4604            Pattern::Dict(_span, keys) => match scrutinee_ty.as_ref() {
4605                TypeKind::Record(fields) => {
4606                    for (key, pat) in keys {
4607                        if let Some((_, ty)) = fields.iter().find(|(n, _)| n == key) {
4608                            add_bindings_from_pattern(ts, ty, pat, out);
4609                        }
4610                    }
4611                }
4612                _ => {
4613                    let tv = ts.supply.fresh(None);
4614                    let elem = Type::var(tv.clone());
4615                    let dict_ty = Type::app(Type::builtin(BuiltinTypeId::Dict), elem.clone());
4616                    let Ok(s) = unify(scrutinee_ty, &dict_ty) else {
4617                        return;
4618                    };
4619                    let elem_ty = elem.apply(&s);
4620                    for (_key, pat) in keys {
4621                        add_bindings_from_pattern(ts, &elem_ty, pat, out);
4622                    }
4623                }
4624            },
4625        }
4626    }
4627
4628    struct VisitCtx<'a> {
4629        pos: RexPosition,
4630        name: &'a str,
4631        name_span: Span,
4632        name_is_ident: bool,
4633        best: &'a mut Option<HoverType>,
4634    }
4635
4636    fn visit(ts: &mut TypeSystem, expr: &Expr, typed: &TypedExpr, ctx: &mut VisitCtx<'_>) {
4637        if !span_contains_pos(*expr.span(), ctx.pos) {
4638            return;
4639        }
4640
4641        let consider = |best: &mut Option<HoverType>, candidate: HoverType| {
4642            let take = best
4643                .as_ref()
4644                .is_none_or(|b| span_size(candidate.span) < span_size(b.span));
4645            if take {
4646                *best = Some(candidate);
4647            }
4648        };
4649
4650        // 1) Pattern-bound variables (match arms).
4651        if ctx.name_is_ident
4652            && let (
4653                Expr::Match(_span, _scrutinee, arms),
4654                TypedExprKind::Match {
4655                    scrutinee,
4656                    arms: typed_arms,
4657                },
4658            ) = (&expr, typed.kind.as_ref())
4659            && span_contains_span(*expr.span(), ctx.name_span)
4660        {
4661            for ((_pat, _arm_body), (typed_pat, _typed_arm_body)) in
4662                arms.iter().zip(typed_arms.iter())
4663            {
4664                // The `Pattern` is cloned into the typed tree; use either.
4665                let pat_span = *typed_pat.span();
4666                if span_contains_span(pat_span, ctx.name_span) {
4667                    let mut bindings: HashMap<String, Type> = HashMap::new();
4668                    add_bindings_from_pattern(ts, &scrutinee.typ, typed_pat, &mut bindings);
4669                    if let Some(ty) = bindings.get(ctx.name) {
4670                        consider(
4671                            ctx.best,
4672                            HoverType {
4673                                span: ctx.name_span,
4674                                label: ctx.name.to_string(),
4675                                typ: ty.to_string(),
4676                                overloads: Vec::new(),
4677                            },
4678                        );
4679                    }
4680                    break;
4681                }
4682            }
4683        }
4684
4685        // 2) Binding sites: `let x = ...` and lambda params.
4686        match (expr, typed.kind.as_ref()) {
4687            (
4688                Expr::Let(_span, binding, _ann, def, body),
4689                TypedExprKind::Let {
4690                    def: tdef,
4691                    body: tbody,
4692                    ..
4693                },
4694            ) => {
4695                if span_contains_pos(binding.span, ctx.pos) {
4696                    consider(
4697                        ctx.best,
4698                        HoverType {
4699                            span: binding.span,
4700                            label: binding.name.as_ref().to_string(),
4701                            typ: tdef.typ.to_string(),
4702                            overloads: Vec::new(),
4703                        },
4704                    );
4705                }
4706                visit(ts, def.as_ref(), tdef.as_ref(), ctx);
4707                visit(ts, body.as_ref(), tbody.as_ref(), ctx);
4708            }
4709            (
4710                Expr::LetRec(_span, bindings, body),
4711                TypedExprKind::LetRec {
4712                    bindings: typed_bindings,
4713                    body: typed_body,
4714                },
4715            ) => {
4716                for ((binding, _ann, def), (_name, typed_def)) in
4717                    bindings.iter().zip(typed_bindings.iter())
4718                {
4719                    if span_contains_pos(binding.span, ctx.pos) {
4720                        consider(
4721                            ctx.best,
4722                            HoverType {
4723                                span: binding.span,
4724                                label: binding.name.as_ref().to_string(),
4725                                typ: typed_def.typ.to_string(),
4726                                overloads: Vec::new(),
4727                            },
4728                        );
4729                    }
4730                    visit(ts, def.as_ref(), typed_def, ctx);
4731                }
4732                visit(ts, body.as_ref(), typed_body.as_ref(), ctx);
4733            }
4734            (
4735                Expr::Lam(_span, _scope, param, _ann, _constraints, body),
4736                TypedExprKind::Lam { body: tbody, .. },
4737            ) => {
4738                if span_contains_pos(param.span, ctx.pos) {
4739                    let param_ty = match typed.typ.as_ref() {
4740                        TypeKind::Fun(a, _b) => a.to_string(),
4741                        _ => "<unknown>".to_string(),
4742                    };
4743                    consider(
4744                        ctx.best,
4745                        HoverType {
4746                            span: param.span,
4747                            label: param.name.as_ref().to_string(),
4748                            typ: param_ty,
4749                            overloads: Vec::new(),
4750                        },
4751                    );
4752                }
4753                visit(ts, body.as_ref(), tbody.as_ref(), ctx);
4754            }
4755            (Expr::Var(v), TypedExprKind::Var { overloads, .. })
4756                if span_contains_pos(v.span, ctx.pos) =>
4757            {
4758                consider(
4759                    ctx.best,
4760                    HoverType {
4761                        span: v.span,
4762                        label: v.name.as_ref().to_string(),
4763                        typ: typed.typ.to_string(),
4764                        overloads: overloads.iter().map(|t| t.to_string()).collect(),
4765                    },
4766                );
4767            }
4768            (Expr::Ann(_span, inner, _ann), _) => {
4769                visit(ts, inner.as_ref(), typed, ctx);
4770            }
4771            (Expr::Tuple(_span, elems), TypedExprKind::Tuple(telems)) => {
4772                for (e, t) in elems.iter().zip(telems.iter()) {
4773                    visit(ts, e.as_ref(), t, ctx);
4774                }
4775            }
4776            (Expr::List(_span, elems), TypedExprKind::List(telems)) => {
4777                for (e, t) in elems.iter().zip(telems.iter()) {
4778                    visit(ts, e.as_ref(), t, ctx);
4779                }
4780            }
4781            (Expr::Dict(_span, kvs), TypedExprKind::Dict(tkvs)) => {
4782                for (k, v) in kvs {
4783                    if let Some(tv) = tkvs.get(k) {
4784                        visit(ts, v.as_ref(), tv, ctx);
4785                    }
4786                }
4787            }
4788            (
4789                Expr::RecordUpdate(_span, base, updates),
4790                TypedExprKind::RecordUpdate {
4791                    base: tbase,
4792                    updates: tupdates,
4793                },
4794            ) => {
4795                visit(ts, base.as_ref(), tbase.as_ref(), ctx);
4796                for (k, v) in updates {
4797                    if let Some(tv) = tupdates.get(k) {
4798                        visit(ts, v.as_ref(), tv, ctx);
4799                    }
4800                }
4801            }
4802            (Expr::App(_span, f, x), TypedExprKind::App(tf, tx)) => {
4803                visit(ts, f.as_ref(), tf.as_ref(), ctx);
4804                visit(ts, x.as_ref(), tx.as_ref(), ctx);
4805            }
4806            (Expr::Project(_span, e, _field), TypedExprKind::Project { expr: te, .. }) => {
4807                visit(ts, e.as_ref(), te.as_ref(), ctx);
4808            }
4809            (
4810                Expr::Ite(_span, c, t, e),
4811                TypedExprKind::Ite {
4812                    cond,
4813                    then_expr,
4814                    else_expr,
4815                },
4816            ) => {
4817                visit(ts, c.as_ref(), cond.as_ref(), ctx);
4818                visit(ts, t.as_ref(), then_expr.as_ref(), ctx);
4819                visit(ts, e.as_ref(), else_expr.as_ref(), ctx);
4820            }
4821            (
4822                Expr::Match(_span, scrutinee, arms),
4823                TypedExprKind::Match {
4824                    scrutinee: tscrut,
4825                    arms: tarms,
4826                },
4827            ) => {
4828                visit(ts, scrutinee.as_ref(), tscrut.as_ref(), ctx);
4829                for ((_pat, arm_body), (_tpat, tarm_body)) in arms.iter().zip(tarms.iter()) {
4830                    visit(ts, arm_body.as_ref(), tarm_body, ctx);
4831                }
4832            }
4833            _ => {}
4834        }
4835    }
4836
4837    let mut best = None;
4838    let mut ctx = VisitCtx {
4839        pos,
4840        name,
4841        name_span,
4842        name_is_ident,
4843        best: &mut best,
4844    };
4845    visit(ts, expr, typed, &mut ctx);
4846    best
4847}
4848
4849fn name_token_at_position(tokens: &Tokens, position: Position) -> Option<(String, Span, bool)> {
4850    for token in &tokens.items {
4851        let (name, span, is_ident) = match token {
4852            Token::Ident(name, span, ..) => (name.clone(), *span, true),
4853            Token::Add(span) => ("+".to_string(), *span, false),
4854            Token::Sub(span) => ("-".to_string(), *span, false),
4855            Token::Mul(span) => ("*".to_string(), *span, false),
4856            Token::Div(span) => ("/".to_string(), *span, false),
4857            Token::Mod(span) => ("%".to_string(), *span, false),
4858            Token::Concat(span) => ("++".to_string(), *span, false),
4859            Token::Eq(span) => ("==".to_string(), *span, false),
4860            Token::Ne(span) => ("!=".to_string(), *span, false),
4861            Token::Lt(span) => ("<".to_string(), *span, false),
4862            Token::Le(span) => ("<=".to_string(), *span, false),
4863            Token::Gt(span) => (">".to_string(), *span, false),
4864            Token::Ge(span) => (">=".to_string(), *span, false),
4865            Token::And(span) => ("&&".to_string(), *span, false),
4866            Token::Or(span) => ("||".to_string(), *span, false),
4867            _ => continue,
4868        };
4869        if range_touches_position(span_to_range(span), position) {
4870            return Some((name, span, is_ident));
4871        }
4872    }
4873    None
4874}
4875
4876fn push_comment_diagnostics(tokens: &Tokens, diagnostics: &mut Vec<Diagnostic>) {
4877    let mut index = 0;
4878
4879    while index < tokens.items.len() && diagnostics.len() < MAX_DIAGNOSTICS {
4880        match tokens.items[index] {
4881            Token::CommentL(span) => {
4882                let mut cursor = index + 1;
4883                while cursor < tokens.items.len() {
4884                    if matches!(tokens.items[cursor], Token::CommentR(_)) {
4885                        break;
4886                    }
4887                    cursor += 1;
4888                }
4889
4890                if cursor >= tokens.items.len() {
4891                    diagnostics.push(diagnostic_for_span(
4892                        span,
4893                        "Unclosed block comment opener ({-).",
4894                    ));
4895                    break;
4896                }
4897
4898                index = cursor + 1;
4899            }
4900            Token::CommentR(span) => {
4901                diagnostics.push(diagnostic_for_span(
4902                    span,
4903                    "Unmatched block comment closer (-}).",
4904                ));
4905                index += 1;
4906            }
4907            _ => index += 1,
4908        }
4909    }
4910}
4911
4912fn diagnostic_for_span(span: Span, message: impl Into<String>) -> Diagnostic {
4913    Diagnostic {
4914        range: span_to_range(span),
4915        severity: Some(DiagnosticSeverity::ERROR),
4916        message: message.into(),
4917        source: Some("rex-lsp".to_string()),
4918        ..Diagnostic::default()
4919    }
4920}
4921
4922fn push_type_diagnostics(
4923    uri: &Url,
4924    text: &str,
4925    compilation_unit: &CompilationUnit,
4926    diagnostics: &mut Vec<Diagnostic>,
4927) {
4928    // Type inference is meaningfully more expensive than lex/parse, and we run
4929    // diagnostics on every full-text change. Keep the cost model explicit.
4930    const MAX_TYPECHECK_BYTES: usize = 256 * 1024;
4931    if text.len() > MAX_TYPECHECK_BYTES {
4932        return;
4933    }
4934
4935    let mut engine = match Engine::with_prelude(()) {
4936        Ok(engine) => engine,
4937        Err(err) => {
4938            push_engine_error(err, diagnostics, compilation_unit);
4939            return;
4940        }
4941    };
4942    engine.add_default_resolvers();
4943
4944    let result = if let Some(path) = uri_to_file_path(uri) {
4945        engine.infer_snippet_at(text, path)
4946    } else {
4947        engine.infer_snippet(text)
4948    };
4949
4950    if let Err(err) = result {
4951        push_engine_error(err.into_engine_error(), diagnostics, compilation_unit);
4952        return;
4953    }
4954
4955    push_hole_diagnostics(compilation_unit, diagnostics);
4956}
4957
4958fn push_engine_error(
4959    err: EngineError,
4960    diagnostics: &mut Vec<Diagnostic>,
4961    compilation_unit: &CompilationUnit,
4962) {
4963    match err {
4964        EngineError::Type(err) => {
4965            let expr = compilation_unit.body_with_fns();
4966            let before = diagnostics.len();
4967            push_ts_error(err, diagnostics, expr.as_deref(), None, None);
4968            if let Some(primary) = diagnostics.get(before).cloned()
4969                && let Some(expr) = expr.as_deref()
4970            {
4971                push_additional_default_record_update_ambiguity_diagnostics(
4972                    expr,
4973                    &primary.message,
4974                    diagnostics,
4975                );
4976            }
4977        }
4978        EngineError::Module(module_err) => {
4979            push_module_error(&module_err, diagnostics, compilation_unit);
4980        }
4981        other => {
4982            diagnostics.push(diagnostic_for_span(
4983                primary_program_span(compilation_unit),
4984                other.to_string(),
4985            ));
4986        }
4987    }
4988}
4989
4990fn push_module_error(
4991    err: &ModuleError,
4992    diagnostics: &mut Vec<Diagnostic>,
4993    compilation_unit: &CompilationUnit,
4994) {
4995    match err {
4996        ModuleError::Lex { source } => {
4997            diagnostics.push(diagnostic_for_lexical_error(source));
4998        }
4999        ModuleError::Parse { errors } => {
5000            for err in errors {
5001                diagnostics.push(diagnostic_for_span(err.span, err.message.clone()));
5002                if diagnostics.len() >= MAX_DIAGNOSTICS {
5003                    break;
5004                }
5005            }
5006        }
5007        _ => {
5008            diagnostics.push(diagnostic_for_span(
5009                primary_program_span(compilation_unit),
5010                err.to_string(),
5011            ));
5012        }
5013    }
5014}
5015
5016fn primary_program_span(compilation_unit: &CompilationUnit) -> Span {
5017    match compilation_unit.decls.first() {
5018        Some(Decl::Type(d)) => d.span,
5019        Some(Decl::Fn(d)) => d.span,
5020        Some(Decl::DeclareFn(d)) => d.span,
5021        Some(Decl::Import(d)) => d.span,
5022        Some(Decl::Class(d)) => d.span,
5023        Some(Decl::Instance(d)) => d.span,
5024        None => compilation_unit
5025            .body
5026            .as_deref()
5027            .map(|expr| *expr.span())
5028            .unwrap_or_default(),
5029    }
5030}
5031
5032fn push_hole_diagnostics(compilation_unit: &CompilationUnit, diagnostics: &mut Vec<Diagnostic>) {
5033    let Some(body) = compilation_unit.body_with_fns() else {
5034        return;
5035    };
5036    let mut spans = Vec::new();
5037    collect_hole_spans(body.as_ref(), &mut spans);
5038    spans.sort_unstable_by_key(|s| (s.begin.line, s.begin.column, s.end.line, s.end.column));
5039    spans.dedup();
5040
5041    for span in spans {
5042        if diagnostics.len() >= MAX_DIAGNOSTICS {
5043            break;
5044        }
5045        diagnostics.push(Diagnostic {
5046            range: span_to_range(span),
5047            severity: Some(DiagnosticSeverity::ERROR),
5048            message: "typed hole `?` must be filled before evaluation".to_string(),
5049            source: Some("rex-typesystem".to_string()),
5050            ..Diagnostic::default()
5051        });
5052    }
5053}
5054
5055fn unknown_var_name(err: &TsTypeError) -> Option<Symbol> {
5056    match err {
5057        TsTypeError::UnknownVar(name) => Some(name.clone()),
5058        TsTypeError::Spanned { error, .. } => unknown_var_name(error),
5059        _ => None,
5060    }
5061}
5062
5063fn field_not_definitely_available_tail(message: &str) -> Option<(&str, &str)> {
5064    let rest = message.strip_prefix("field `")?;
5065    let (field, tail) = rest.split_once('`')?;
5066    tail.contains("is not definitely available on")
5067        .then_some((field, tail))
5068}
5069
5070fn push_additional_default_record_update_ambiguity_diagnostics(
5071    expr: &Expr,
5072    primary_message: &str,
5073    diagnostics: &mut Vec<Diagnostic>,
5074) {
5075    let Some((_field, tail)) = field_not_definitely_available_tail(primary_message) else {
5076        return;
5077    };
5078    let mut updates = Vec::new();
5079    collect_default_record_updates(expr, &mut updates);
5080    for (span, fields) in updates {
5081        if diagnostics.len() >= MAX_DIAGNOSTICS {
5082            break;
5083        }
5084        let Some(field) = fields.first() else {
5085            continue;
5086        };
5087        let message = format!("field `{field}`{tail}");
5088        let range = span_to_range(span);
5089        if diagnostics
5090            .iter()
5091            .any(|d| d.range == range && d.message == message)
5092        {
5093            continue;
5094        }
5095        diagnostics.push(Diagnostic {
5096            range,
5097            severity: Some(DiagnosticSeverity::ERROR),
5098            message,
5099            source: Some("rex-typesystem".to_string()),
5100            ..Diagnostic::default()
5101        });
5102    }
5103}
5104
5105fn collect_default_record_updates(expr: &Expr, out: &mut Vec<(Span, Vec<String>)>) {
5106    match expr {
5107        Expr::RecordUpdate(span, base, updates) => {
5108            if matches!(base.as_ref(), Expr::Var(v) if v.name.as_ref() == "default") {
5109                let fields = updates
5110                    .keys()
5111                    .map(|name| name.as_ref().to_string())
5112                    .collect::<Vec<_>>();
5113                if !fields.is_empty() {
5114                    out.push((*span, fields));
5115                }
5116            }
5117            collect_default_record_updates(base, out);
5118            for value in updates.values() {
5119                collect_default_record_updates(value, out);
5120            }
5121        }
5122        Expr::App(_, fun, arg) => {
5123            collect_default_record_updates(fun, out);
5124            collect_default_record_updates(arg, out);
5125        }
5126        Expr::Project(_, base, _) => collect_default_record_updates(base, out),
5127        Expr::Lam(_, _, _, _, _, body) => collect_default_record_updates(body, out),
5128        Expr::Let(_, _, _, def, body) => {
5129            collect_default_record_updates(def, out);
5130            collect_default_record_updates(body, out);
5131        }
5132        Expr::LetRec(_, bindings, body) => {
5133            for (_var, _ann, def) in bindings {
5134                collect_default_record_updates(def, out);
5135            }
5136            collect_default_record_updates(body, out);
5137        }
5138        Expr::Ite(_, cond, then_expr, else_expr) => {
5139            collect_default_record_updates(cond, out);
5140            collect_default_record_updates(then_expr, out);
5141            collect_default_record_updates(else_expr, out);
5142        }
5143        Expr::Match(_, scrutinee, arms) => {
5144            collect_default_record_updates(scrutinee, out);
5145            for (_pat, arm) in arms {
5146                collect_default_record_updates(arm, out);
5147            }
5148        }
5149        Expr::Ann(_, inner, _) => collect_default_record_updates(inner, out),
5150        Expr::Tuple(_, items) | Expr::List(_, items) => {
5151            for item in items {
5152                collect_default_record_updates(item, out);
5153            }
5154        }
5155        Expr::Dict(_, entries) => {
5156            for value in entries.values() {
5157                collect_default_record_updates(value, out);
5158            }
5159        }
5160        Expr::Var(..)
5161        | Expr::Bool(..)
5162        | Expr::Uint(..)
5163        | Expr::Int(..)
5164        | Expr::Float(..)
5165        | Expr::String(..)
5166        | Expr::Uuid(..)
5167        | Expr::DateTime(..)
5168        | Expr::Hole(..) => {}
5169    }
5170}
5171
5172fn find_let_binding_for_def_range(
5173    compilation_unit: &CompilationUnit,
5174    target: Range,
5175) -> Option<(String, Position)> {
5176    let body = compilation_unit.body_with_fns()?;
5177    find_let_binding_for_def_range_in_expr(body.as_ref(), target)
5178}
5179
5180fn find_let_binding_for_def_range_in_expr(
5181    expr: &Expr,
5182    target: Range,
5183) -> Option<(String, Position)> {
5184    match expr {
5185        Expr::Let(_, var, ann, def, body) => {
5186            let def_range = span_to_range(*def.span());
5187            if ranges_overlap(def_range, target) && ann.is_none() {
5188                return Some((var.name.as_ref().to_string(), span_to_range(var.span).end));
5189            }
5190            find_let_binding_for_def_range_in_expr(def.as_ref(), target)
5191                .or_else(|| find_let_binding_for_def_range_in_expr(body.as_ref(), target))
5192        }
5193        Expr::LetRec(_, bindings, body) => {
5194            for (var, ann, def) in bindings {
5195                let def_range = span_to_range(*def.span());
5196                if ranges_overlap(def_range, target) && ann.is_none() {
5197                    return Some((var.name.as_ref().to_string(), span_to_range(var.span).end));
5198                }
5199                if let Some(found) = find_let_binding_for_def_range_in_expr(def.as_ref(), target) {
5200                    return Some(found);
5201                }
5202            }
5203            find_let_binding_for_def_range_in_expr(body.as_ref(), target)
5204        }
5205        Expr::App(_, fun, arg) => find_let_binding_for_def_range_in_expr(fun.as_ref(), target)
5206            .or_else(|| find_let_binding_for_def_range_in_expr(arg.as_ref(), target)),
5207        Expr::Project(_, base, _) => find_let_binding_for_def_range_in_expr(base.as_ref(), target),
5208        Expr::RecordUpdate(_, base, updates) => {
5209            find_let_binding_for_def_range_in_expr(base.as_ref(), target).or_else(|| {
5210                updates
5211                    .values()
5212                    .find_map(|expr| find_let_binding_for_def_range_in_expr(expr.as_ref(), target))
5213            })
5214        }
5215        Expr::Lam(_, _, _, _, _, body) => {
5216            find_let_binding_for_def_range_in_expr(body.as_ref(), target)
5217        }
5218        Expr::Ite(_, cond, then_expr, else_expr) => {
5219            find_let_binding_for_def_range_in_expr(cond.as_ref(), target)
5220                .or_else(|| find_let_binding_for_def_range_in_expr(then_expr.as_ref(), target))
5221                .or_else(|| find_let_binding_for_def_range_in_expr(else_expr.as_ref(), target))
5222        }
5223        Expr::Match(_, scrutinee, arms) => {
5224            find_let_binding_for_def_range_in_expr(scrutinee.as_ref(), target).or_else(|| {
5225                arms.iter().find_map(|(_, arm)| {
5226                    find_let_binding_for_def_range_in_expr(arm.as_ref(), target)
5227                })
5228            })
5229        }
5230        Expr::Ann(_, inner, _) => find_let_binding_for_def_range_in_expr(inner.as_ref(), target),
5231        Expr::Tuple(_, items) | Expr::List(_, items) => items
5232            .iter()
5233            .find_map(|item| find_let_binding_for_def_range_in_expr(item.as_ref(), target)),
5234        Expr::Dict(_, entries) => entries
5235            .values()
5236            .find_map(|value| find_let_binding_for_def_range_in_expr(value.as_ref(), target)),
5237        Expr::Var(..)
5238        | Expr::Bool(..)
5239        | Expr::Uint(..)
5240        | Expr::Int(..)
5241        | Expr::Float(..)
5242        | Expr::String(..)
5243        | Expr::Uuid(..)
5244        | Expr::DateTime(..)
5245        | Expr::Hole(..) => None,
5246    }
5247}
5248
5249fn collect_unbound_var_spans(
5250    expr: &Expr,
5251    target: &Symbol,
5252    bound: &mut Vec<Symbol>,
5253    out: &mut Vec<Span>,
5254) {
5255    match expr {
5256        Expr::Var(var) => {
5257            if var.name == *target && !bound.iter().any(|name| name == &var.name) {
5258                out.push(var.span);
5259            }
5260        }
5261        Expr::App(_, fun, arg) => {
5262            collect_unbound_var_spans(fun, target, bound, out);
5263            collect_unbound_var_spans(arg, target, bound, out);
5264        }
5265        Expr::Project(_, base, _) => {
5266            collect_unbound_var_spans(base, target, bound, out);
5267        }
5268        Expr::Lam(_, _scope, param, _ann, _constraints, body) => {
5269            bound.push(param.name.clone());
5270            collect_unbound_var_spans(body, target, bound, out);
5271            bound.pop();
5272        }
5273        Expr::Let(_, var, _ann, def, body) => {
5274            collect_unbound_var_spans(def, target, bound, out);
5275            bound.push(var.name.clone());
5276            collect_unbound_var_spans(body, target, bound, out);
5277            bound.pop();
5278        }
5279        Expr::LetRec(_, bindings, body) => {
5280            let base_len = bound.len();
5281            for (var, _ann, _def) in bindings {
5282                bound.push(var.name.clone());
5283            }
5284            for (_var, _ann, def) in bindings {
5285                collect_unbound_var_spans(def, target, bound, out);
5286            }
5287            collect_unbound_var_spans(body, target, bound, out);
5288            bound.truncate(base_len);
5289        }
5290        Expr::Ite(_, cond, then_expr, else_expr) => {
5291            collect_unbound_var_spans(cond, target, bound, out);
5292            collect_unbound_var_spans(then_expr, target, bound, out);
5293            collect_unbound_var_spans(else_expr, target, bound, out);
5294        }
5295        Expr::Match(_, scrutinee, arms) => {
5296            collect_unbound_var_spans(scrutinee, target, bound, out);
5297            for (pat, arm) in arms {
5298                let base_len = bound.len();
5299                let mut pat_bindings = Vec::new();
5300                collect_pattern_bindings(pat, &mut pat_bindings);
5301                bound.extend(pat_bindings);
5302                collect_unbound_var_spans(arm, target, bound, out);
5303                bound.truncate(base_len);
5304            }
5305        }
5306        Expr::Ann(_, inner, _) => {
5307            collect_unbound_var_spans(inner, target, bound, out);
5308        }
5309        Expr::Tuple(_, items) | Expr::List(_, items) => {
5310            for item in items {
5311                collect_unbound_var_spans(item, target, bound, out);
5312            }
5313        }
5314        Expr::Dict(_, kvs) | Expr::RecordUpdate(_, _, kvs) => {
5315            for expr in kvs.values() {
5316                collect_unbound_var_spans(expr, target, bound, out);
5317            }
5318            if let Expr::RecordUpdate(_, base, _) = expr {
5319                collect_unbound_var_spans(base, target, bound, out);
5320            }
5321        }
5322        Expr::Bool(..)
5323        | Expr::Uint(..)
5324        | Expr::Int(..)
5325        | Expr::Float(..)
5326        | Expr::String(..)
5327        | Expr::Uuid(..)
5328        | Expr::DateTime(..)
5329        | Expr::Hole(..) => {}
5330    }
5331}
5332
5333fn push_ts_error(
5334    err: TsTypeError,
5335    diagnostics: &mut Vec<Diagnostic>,
5336    expr: Option<&Expr>,
5337    ts: Option<&TypeSystem>,
5338    fallback_span: Option<Span>,
5339) {
5340    let unknown_target = unknown_var_name(&err);
5341    let (span, message) = match &err {
5342        TsTypeError::Spanned { span, error } => (*span, error.to_string()),
5343        other => (
5344            fallback_span
5345                .or_else(|| expr.map(|e| *e.span()))
5346                .unwrap_or_default(),
5347            other.to_string(),
5348        ),
5349    };
5350
5351    if let (Some(target), Some(expr)) = (unknown_target, expr)
5352        && ts.is_none_or(|ts| ts.env.lookup(&target).is_none())
5353    {
5354        let mut spans = Vec::new();
5355        collect_unbound_var_spans(expr, &target, &mut Vec::new(), &mut spans);
5356        spans.sort_unstable_by_key(|s| (s.begin.line, s.begin.column, s.end.line, s.end.column));
5357        spans.dedup();
5358        if !spans.is_empty() {
5359            for unbound_span in spans {
5360                if diagnostics.len() >= MAX_DIAGNOSTICS {
5361                    break;
5362                }
5363                diagnostics.push(Diagnostic {
5364                    range: span_to_range(unbound_span),
5365                    severity: Some(DiagnosticSeverity::ERROR),
5366                    message: message.clone(),
5367                    source: Some("rex-typesystem".to_string()),
5368                    ..Diagnostic::default()
5369                });
5370            }
5371            return;
5372        }
5373    }
5374
5375    diagnostics.push(Diagnostic {
5376        range: span_to_range(span),
5377        severity: Some(DiagnosticSeverity::ERROR),
5378        message,
5379        source: Some("rex-typesystem".to_string()),
5380        ..Diagnostic::default()
5381    });
5382}
5383
5384fn range_contains_position(range: Range, position: Position) -> bool {
5385    let after_start = position.line > range.start.line
5386        || (position.line == range.start.line && position.character >= range.start.character);
5387    let before_end = position.line < range.end.line
5388        || (position.line == range.end.line && position.character < range.end.character);
5389    after_start && before_end
5390}
5391
5392fn range_touches_position(range: Range, position: Position) -> bool {
5393    // LSP ranges are end-exclusive, but VS Code often sends positions at the
5394    // *end* of a word (especially for single-character identifiers). For user
5395    // interactions like go-to-definition, treating `position == end` as “still
5396    // on that token” is a better UX trade.
5397    if range_contains_position(range, position) {
5398        return true;
5399    }
5400    if position.line != range.end.line || position.character != range.end.character {
5401        return false;
5402    }
5403    // Exclude the degenerate case (empty range), just in case.
5404    position.line != range.start.line || position.character != range.start.character
5405}
5406
5407fn span_to_range(span: Span) -> Range {
5408    Range {
5409        start: position_from_span(span.begin.line, span.begin.column),
5410        end: position_from_span(span.end.line, span.end.column),
5411    }
5412}
5413
5414fn position_from_span(line: usize, column: usize) -> Position {
5415    Position {
5416        line: line.saturating_sub(1) as u32,
5417        character: column.saturating_sub(1) as u32,
5418    }
5419}
5420
5421pub(crate) fn hover_contents(word: &str) -> Option<HoverContents> {
5422    if let Some(doc) = keyword_doc(word) {
5423        return Some(markdown_hover(word, "keyword", doc));
5424    }
5425
5426    if let Some(doc) = type_doc(word) {
5427        return Some(markdown_hover(word, "type", doc));
5428    }
5429
5430    if let Some(doc) = value_doc(word) {
5431        return Some(markdown_hover(word, "value", doc));
5432    }
5433
5434    None
5435}
5436
5437fn markdown_hover(word: &str, kind: &str, doc: &str) -> HoverContents {
5438    HoverContents::Markup(MarkupContent {
5439        kind: MarkupKind::Markdown,
5440        value: format!("**{}** {}\n\n{}", word, kind, doc),
5441    })
5442}
5443
5444fn keyword_doc(word: &str) -> Option<&'static str> {
5445    match word {
5446        "let" => Some("Introduces local bindings."),
5447        "in" => Some("Begins the expression body for a let binding."),
5448        "type" => Some("Declares a type or ADT."),
5449        "match" => Some("Starts a pattern match expression."),
5450        "with" => {
5451            Some("Separates a match scrutinee from its arm block, or introduces a record update.")
5452        }
5453        "case" => Some("Introduces a match arm."),
5454        "if" => Some("Conditional expression keyword."),
5455        "then" => Some("Conditional expression branch."),
5456        "else" => Some("Fallback branch of a conditional expression."),
5457        "as" => Some("Type ascription or aliasing keyword."),
5458        "for" => Some("List/dict comprehension keyword (when supported)."),
5459        "is" => Some("Type assertion keyword."),
5460        _ => None,
5461    }
5462}
5463
5464fn type_doc(word: &str) -> Option<&'static str> {
5465    match word {
5466        "bool" => Some("Boolean type."),
5467        "string" => Some("UTF-8 string type."),
5468        "uuid" => Some("UUID type."),
5469        "datetime" => Some("Datetime type."),
5470        "u8" => Some("Unsigned 8-bit integer."),
5471        "u16" => Some("Unsigned 16-bit integer."),
5472        "u32" => Some("Unsigned 32-bit integer."),
5473        "u64" => Some("Unsigned 64-bit integer."),
5474        "i8" => Some("Signed 8-bit integer."),
5475        "i16" => Some("Signed 16-bit integer."),
5476        "i32" => Some("Signed 32-bit integer."),
5477        "i64" => Some("Signed 64-bit integer."),
5478        "f32" => Some("32-bit float."),
5479        "f64" => Some("64-bit float."),
5480        "List" => Some("List type constructor."),
5481        "Dict" => Some("Dictionary type constructor."),
5482        "Array" => Some("Array type constructor."),
5483        "Option" => Some("Optional type constructor."),
5484        "Result" => Some("Result type constructor."),
5485        _ => None,
5486    }
5487}
5488
5489fn value_doc(word: &str) -> Option<&'static str> {
5490    match word {
5491        "true" => Some("Boolean literal."),
5492        "false" => Some("Boolean literal."),
5493        "null" => Some("Null literal."),
5494        "Some" => Some("Option constructor."),
5495        "None" => Some("Option empty constructor."),
5496        "Ok" => Some("Result success constructor."),
5497        "Err" => Some("Result error constructor."),
5498        _ => None,
5499    }
5500}
5501
5502pub(crate) fn completion_items(uri: &Url, text: &str, position: Position) -> Vec<CompletionItem> {
5503    let field_mode = is_field_completion(text, position);
5504    let base_ident = if field_mode {
5505        field_base_ident(text, position)
5506    } else {
5507        None
5508    };
5509    if let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) {
5510        return completion_items_from_program(
5511            &program,
5512            position,
5513            field_mode,
5514            base_ident.as_deref(),
5515            uri,
5516        );
5517    }
5518
5519    completion_items_fallback(text, base_ident.as_deref(), field_mode)
5520}
5521
5522fn completion_items_from_program(
5523    compilation_unit: &CompilationUnit,
5524    position: Position,
5525    field_mode: bool,
5526    base_ident: Option<&str>,
5527    uri: &Url,
5528) -> Vec<CompletionItem> {
5529    if field_mode {
5530        if let Some(base_ident) = base_ident
5531            && let Ok(exports) =
5532                completion_exports_for_module_alias(uri, compilation_unit, base_ident)
5533            && !exports.is_empty()
5534        {
5535            return exports
5536                .into_iter()
5537                .map(|label| completion_item(label, CompletionItemKind::FIELD))
5538                .collect();
5539        }
5540        if let Some(fields) = field_completion_for_position(compilation_unit, position, base_ident)
5541        {
5542            return fields
5543                .into_iter()
5544                .map(|label| completion_item(label, CompletionItemKind::FIELD))
5545                .collect();
5546        }
5547        return Vec::new();
5548    }
5549
5550    let mut value_kinds = values_in_scope_at_position(compilation_unit, position);
5551    let pos = lsp_to_rex_position(position);
5552    for decl in &compilation_unit.decls {
5553        let Decl::Import(id) = decl else { continue };
5554        if position_in_span(pos, id.span) || position_leq(id.span.end, pos) {
5555            value_kinds
5556                .entry(id.alias.as_ref().to_string())
5557                .or_insert(CompletionItemKind::MODULE);
5558        }
5559    }
5560    for value in BUILTIN_VALUES {
5561        value_kinds
5562            .entry((*value).to_string())
5563            .or_insert(CompletionItemKind::VARIABLE);
5564    }
5565    for (value, kind) in prelude_completion_values() {
5566        value_kinds.entry(value.clone()).or_insert(*kind);
5567    }
5568    for ctor in collect_constructors(compilation_unit) {
5569        value_kinds
5570            .entry(ctor)
5571            .or_insert(CompletionItemKind::CONSTRUCTOR);
5572    }
5573
5574    let mut type_names = collect_type_names(compilation_unit);
5575    type_names.extend(BUILTIN_TYPES.iter().map(|value| value.to_string()));
5576
5577    let mut items = Vec::new();
5578    items.extend(
5579        value_kinds
5580            .into_iter()
5581            .map(|(label, kind)| completion_item(label, kind)),
5582    );
5583    items.extend(
5584        type_names
5585            .into_iter()
5586            .map(|label| completion_item(label, CompletionItemKind::CLASS)),
5587    );
5588
5589    items
5590}
5591
5592fn completion_items_fallback(
5593    text: &str,
5594    base_ident: Option<&str>,
5595    field_mode: bool,
5596) -> Vec<CompletionItem> {
5597    let mut identifiers: HashMap<String, CompletionItemKind> = HashMap::new();
5598
5599    if let Ok(tokens) = Token::tokenize(text) {
5600        identifiers.extend(function_defs_from_tokens(&tokens));
5601
5602        let mut index = 0usize;
5603        while index < tokens.items.len() {
5604            if let Token::Ident(name, ..) = &tokens.items[index] {
5605                identifiers
5606                    .entry(name.clone())
5607                    .or_insert(CompletionItemKind::VARIABLE);
5608            }
5609            index += 1;
5610        }
5611
5612        if field_mode {
5613            if let Some(base_ident) = base_ident
5614                && let Some(fields) = fallback_field_map(&tokens).get(base_ident)
5615            {
5616                return fields
5617                    .iter()
5618                    .cloned()
5619                    .map(|label| completion_item(label, CompletionItemKind::FIELD))
5620                    .collect();
5621            }
5622            return Vec::new();
5623        }
5624    }
5625
5626    let mut items: Vec<CompletionItem> = identifiers
5627        .into_iter()
5628        .map(|(label, kind)| completion_item(label, kind))
5629        .collect();
5630    items.extend(
5631        BUILTIN_TYPES
5632            .iter()
5633            .map(|label| completion_item((*label).to_string(), CompletionItemKind::CLASS)),
5634    );
5635    items
5636}
5637
5638fn completion_item(label: String, kind: CompletionItemKind) -> CompletionItem {
5639    CompletionItem {
5640        label,
5641        kind: Some(kind),
5642        ..CompletionItem::default()
5643    }
5644}
5645
5646fn values_in_scope_at_position(
5647    compilation_unit: &CompilationUnit,
5648    position: Position,
5649) -> HashMap<String, CompletionItemKind> {
5650    let pos = lsp_to_rex_position(position);
5651    let Some(expr) = compilation_unit.body_with_fns() else {
5652        return HashMap::new();
5653    };
5654    values_in_scope_at_expr(expr.as_ref(), pos, &mut Vec::new()).unwrap_or_default()
5655}
5656
5657fn values_in_scope_at_expr(
5658    expr: &Expr,
5659    position: RexPosition,
5660    scope: &mut Vec<(String, CompletionItemKind)>,
5661) -> Option<HashMap<String, CompletionItemKind>> {
5662    if !position_in_span(position, *expr.span()) {
5663        return None;
5664    }
5665
5666    fn scope_to_map(scope: &[(String, CompletionItemKind)]) -> HashMap<String, CompletionItemKind> {
5667        // If a name appears multiple times, prefer the most “specific” kind.
5668        // (A function is still a value, but it’s nicer to present it as a function.)
5669        let mut map = HashMap::new();
5670        for (name, kind) in scope {
5671            let slot = map.entry(name.clone()).or_insert(*kind);
5672            if *slot != CompletionItemKind::FUNCTION && *kind == CompletionItemKind::FUNCTION {
5673                *slot = *kind;
5674            }
5675        }
5676        map
5677    }
5678
5679    match expr {
5680        Expr::Let(_span, var, _ann, def, body) => {
5681            if position_in_span(position, *def.span()) {
5682                return values_in_scope_at_expr(def, position, scope)
5683                    .or_else(|| Some(scope_to_map(scope)));
5684            }
5685
5686            if position_in_span(position, *body.span()) {
5687                let kind = matches!(def.as_ref(), Expr::Lam(..))
5688                    .then_some(CompletionItemKind::FUNCTION)
5689                    .unwrap_or(CompletionItemKind::VARIABLE);
5690                scope.push((var.name.to_string(), kind));
5691                let out = values_in_scope_at_expr(body, position, scope)
5692                    .or_else(|| Some(scope_to_map(scope)));
5693                scope.pop();
5694                return out;
5695            }
5696
5697            Some(scope_to_map(scope))
5698        }
5699        Expr::LetRec(_span, bindings, body) => {
5700            let base_len = scope.len();
5701            scope.extend(bindings.iter().map(|(var, _ann, def)| {
5702                let kind = matches!(def.as_ref(), Expr::Lam(..))
5703                    .then_some(CompletionItemKind::FUNCTION)
5704                    .unwrap_or(CompletionItemKind::VARIABLE);
5705                (var.name.to_string(), kind)
5706            }));
5707
5708            for (_, _, def) in bindings {
5709                if position_in_span(position, *def.span()) {
5710                    let out = values_in_scope_at_expr(def, position, scope)
5711                        .or_else(|| Some(scope_to_map(scope)));
5712                    scope.truncate(base_len);
5713                    return out;
5714                }
5715            }
5716
5717            if position_in_span(position, *body.span()) {
5718                let out = values_in_scope_at_expr(body, position, scope)
5719                    .or_else(|| Some(scope_to_map(scope)));
5720                scope.truncate(base_len);
5721                return out;
5722            }
5723
5724            scope.truncate(base_len);
5725            Some(scope_to_map(scope))
5726        }
5727        Expr::Lam(_span, _scope, param, _ann, _constraints, body) => {
5728            if position_in_span(position, *body.span()) {
5729                scope.push((param.name.to_string(), CompletionItemKind::VARIABLE));
5730                let out = values_in_scope_at_expr(body, position, scope)
5731                    .or_else(|| Some(scope_to_map(scope)));
5732                scope.pop();
5733                return out;
5734            }
5735            Some(scope_to_map(scope))
5736        }
5737        Expr::Match(_span, scrutinee, arms) => {
5738            if position_in_span(position, *scrutinee.span()) {
5739                return values_in_scope_at_expr(scrutinee, position, scope)
5740                    .or_else(|| Some(scope_to_map(scope)));
5741            }
5742            for (pattern, arm) in arms {
5743                if position_in_span(position, *pattern.span()) {
5744                    return Some(scope_to_map(scope));
5745                }
5746                if position_in_span(position, *arm.span()) {
5747                    let base_len = scope.len();
5748                    scope.extend(
5749                        pattern_vars(pattern)
5750                            .into_iter()
5751                            .map(|name| (name, CompletionItemKind::VARIABLE)),
5752                    );
5753                    let out = values_in_scope_at_expr(arm, position, scope)
5754                        .or_else(|| Some(scope_to_map(scope)));
5755                    scope.truncate(base_len);
5756                    return out;
5757                }
5758            }
5759            Some(scope_to_map(scope))
5760        }
5761        Expr::App(_span, fun, arg) => {
5762            if position_in_span(position, *fun.span()) {
5763                return values_in_scope_at_expr(fun, position, scope)
5764                    .or_else(|| Some(scope_to_map(scope)));
5765            }
5766            if position_in_span(position, *arg.span()) {
5767                return values_in_scope_at_expr(arg, position, scope)
5768                    .or_else(|| Some(scope_to_map(scope)));
5769            }
5770            Some(scope_to_map(scope))
5771        }
5772        Expr::Project(_span, base, _field) => {
5773            if position_in_span(position, *base.span()) {
5774                return values_in_scope_at_expr(base, position, scope)
5775                    .or_else(|| Some(scope_to_map(scope)));
5776            }
5777            Some(scope_to_map(scope))
5778        }
5779        Expr::Tuple(_span, elems) | Expr::List(_span, elems) => {
5780            for elem in elems {
5781                if position_in_span(position, *elem.span()) {
5782                    return values_in_scope_at_expr(elem, position, scope)
5783                        .or_else(|| Some(scope_to_map(scope)));
5784                }
5785            }
5786            Some(scope_to_map(scope))
5787        }
5788        Expr::Dict(_span, entries) => {
5789            for value in entries.values() {
5790                if position_in_span(position, *value.span()) {
5791                    return values_in_scope_at_expr(value, position, scope)
5792                        .or_else(|| Some(scope_to_map(scope)));
5793                }
5794            }
5795            Some(scope_to_map(scope))
5796        }
5797        Expr::Ite(_span, cond, then_expr, else_expr) => {
5798            if position_in_span(position, *cond.span()) {
5799                return values_in_scope_at_expr(cond, position, scope)
5800                    .or_else(|| Some(scope_to_map(scope)));
5801            }
5802            if position_in_span(position, *then_expr.span()) {
5803                return values_in_scope_at_expr(then_expr, position, scope)
5804                    .or_else(|| Some(scope_to_map(scope)));
5805            }
5806            if position_in_span(position, *else_expr.span()) {
5807                return values_in_scope_at_expr(else_expr, position, scope)
5808                    .or_else(|| Some(scope_to_map(scope)));
5809            }
5810            Some(scope_to_map(scope))
5811        }
5812        Expr::Ann(_span, inner, _ann) => {
5813            if position_in_span(position, *inner.span()) {
5814                return values_in_scope_at_expr(inner, position, scope)
5815                    .or_else(|| Some(scope_to_map(scope)));
5816            }
5817            Some(scope_to_map(scope))
5818        }
5819        _ => Some(scope_to_map(scope)),
5820    }
5821}
5822
5823fn function_defs_from_tokens(tokens: &Tokens) -> HashMap<String, CompletionItemKind> {
5824    // Heuristic fallback when parsing fails: detect `let name = \...` and mark
5825    // `name` as a function completion.
5826    //
5827    // Also detects `fn name ...` declarations.
5828    //
5829    // This keeps completion useful while the user is mid-edit and the AST is
5830    // temporarily invalid.
5831    let mut out = HashMap::new();
5832    let items = &tokens.items;
5833    let mut index = 0usize;
5834
5835    let next_non_ws = |i: usize| -> Option<usize> { (i < items.len()).then_some(i) };
5836
5837    while index < items.len() {
5838        if matches!(items[index], Token::Fn(..)) {
5839            let Some(i) = next_non_ws(index + 1) else {
5840                break;
5841            };
5842            if let Token::Ident(name, ..) = &items[i] {
5843                out.insert(name.clone(), CompletionItemKind::FUNCTION);
5844            }
5845            index += 1;
5846            continue;
5847        }
5848
5849        if !matches!(items[index], Token::Let(..)) {
5850            index += 1;
5851            continue;
5852        }
5853
5854        let Some(mut i) = next_non_ws(index + 1) else {
5855            break;
5856        };
5857
5858        let name = match &items[i] {
5859            Token::Ident(name, ..) => name.clone(),
5860            _ => {
5861                index += 1;
5862                continue;
5863            }
5864        };
5865
5866        // Walk to `=` (skipping whitespace) and then check if the next token is `\` / `λ`.
5867        i += 1;
5868        while let Some(j) = next_non_ws(i) {
5869            match &items[j] {
5870                Token::Assign(..) => {
5871                    let Some(k) = next_non_ws(j + 1) else {
5872                        break;
5873                    };
5874                    if matches!(items[k], Token::BackSlash(..)) {
5875                        out.insert(name, CompletionItemKind::FUNCTION);
5876                    }
5877                    break;
5878                }
5879                Token::SemiColon(..) => break,
5880                _ => i = j + 1,
5881            }
5882        }
5883
5884        index += 1;
5885    }
5886
5887    out
5888}
5889
5890fn collect_type_names(compilation_unit: &CompilationUnit) -> BTreeSet<String> {
5891    let mut names = BTreeSet::new();
5892    for decl in &compilation_unit.decls {
5893        if let Decl::Type(TypeDecl { name, .. }) = decl {
5894            names.insert(name.to_string());
5895        }
5896    }
5897    names
5898}
5899
5900fn collect_constructors(compilation_unit: &CompilationUnit) -> BTreeSet<String> {
5901    let mut names = BTreeSet::new();
5902    for decl in &compilation_unit.decls {
5903        if let Decl::Type(TypeDecl { variants, .. }) = decl {
5904            for variant in variants {
5905                names.insert(variant.name.to_string());
5906            }
5907        }
5908    }
5909    names
5910}
5911
5912fn collect_fields_type_expr(typ: &TypeExpr, fields: &mut BTreeSet<String>) {
5913    match typ {
5914        TypeExpr::Record(_, entries) => {
5915            for (name, _ty) in entries {
5916                fields.insert(name.to_string());
5917            }
5918        }
5919        TypeExpr::App(_, fun, arg) => {
5920            collect_fields_type_expr(fun, fields);
5921            collect_fields_type_expr(arg, fields);
5922        }
5923        TypeExpr::Fun(_, arg, ret) => {
5924            collect_fields_type_expr(arg, fields);
5925            collect_fields_type_expr(ret, fields);
5926        }
5927        TypeExpr::Tuple(_, elems) => {
5928            for elem in elems {
5929                collect_fields_type_expr(elem, fields);
5930            }
5931        }
5932        TypeExpr::Name(..) => {}
5933    }
5934}
5935
5936fn field_completion_for_position(
5937    compilation_unit: &CompilationUnit,
5938    position: Position,
5939    base_ident: Option<&str>,
5940) -> Option<BTreeSet<String>> {
5941    let type_fields = type_fields_map(compilation_unit);
5942    let env = field_env_at_position(compilation_unit, position, &type_fields);
5943    let pos = lsp_to_rex_position(position);
5944
5945    if let Some(expr) = compilation_unit.body_with_fns()
5946        && let Some(base) = project_base_at_position(expr.as_ref(), pos)
5947        && let Some(fields) = fields_for_expr(base, &env, &type_fields)
5948    {
5949        return Some(fields);
5950    }
5951
5952    if let Some(base_ident) = base_ident {
5953        if let Some(fields) = env.get(base_ident) {
5954            return Some(fields.clone());
5955        }
5956        if let Some(fields) = type_fields.get(base_ident) {
5957            return Some(fields.clone());
5958        }
5959    }
5960
5961    None
5962}
5963
5964fn type_fields_map(compilation_unit: &CompilationUnit) -> HashMap<String, BTreeSet<String>> {
5965    let mut map = HashMap::new();
5966    for decl in &compilation_unit.decls {
5967        if let Decl::Type(TypeDecl { name, variants, .. }) = decl {
5968            let mut fields = BTreeSet::new();
5969            for variant in variants {
5970                for arg in &variant.args {
5971                    collect_fields_type_expr(arg, &mut fields);
5972                }
5973            }
5974            if !fields.is_empty() {
5975                map.insert(name.to_string(), fields);
5976            }
5977        }
5978    }
5979    map
5980}
5981
5982fn field_env_at_position(
5983    compilation_unit: &CompilationUnit,
5984    position: Position,
5985    type_fields: &HashMap<String, BTreeSet<String>>,
5986) -> HashMap<String, BTreeSet<String>> {
5987    let pos = lsp_to_rex_position(position);
5988    let Some(expr) = compilation_unit.body_with_fns() else {
5989        return HashMap::new();
5990    };
5991    field_env_at_expr(expr.as_ref(), pos, &HashMap::new(), type_fields).unwrap_or_default()
5992}
5993
5994fn field_env_at_expr(
5995    expr: &Expr,
5996    position: RexPosition,
5997    env: &HashMap<String, BTreeSet<String>>,
5998    type_fields: &HashMap<String, BTreeSet<String>>,
5999) -> Option<HashMap<String, BTreeSet<String>>> {
6000    if !position_in_span(position, *expr.span()) {
6001        return None;
6002    }
6003
6004    match expr {
6005        Expr::Let(_, var, ann, def, body) => {
6006            if position_in_span(position, *def.span()) {
6007                return field_env_at_expr(def, position, env, type_fields)
6008                    .or_else(|| Some(env.clone()));
6009            }
6010            if position_in_span(position, *body.span()) {
6011                let mut env_with = env.clone();
6012                let fields = binding_fields(ann.as_ref(), def, type_fields).unwrap_or_default();
6013                env_with.insert(var.name.to_string(), fields);
6014                if let Some(inner) = field_env_at_expr(body, position, &env_with, type_fields) {
6015                    return Some(inner);
6016                }
6017                return Some(env_with);
6018            }
6019            Some(env.clone())
6020        }
6021        Expr::LetRec(_, bindings, body) => {
6022            let mut env_with = env.clone();
6023            for (var, ann, def) in bindings {
6024                let fields = binding_fields(ann.as_ref(), def, type_fields).unwrap_or_default();
6025                env_with.insert(var.name.to_string(), fields);
6026            }
6027            for (_, _, def) in bindings {
6028                if position_in_span(position, *def.span()) {
6029                    return field_env_at_expr(def, position, &env_with, type_fields)
6030                        .or_else(|| Some(env_with.clone()));
6031                }
6032            }
6033            if position_in_span(position, *body.span()) {
6034                if let Some(inner) = field_env_at_expr(body, position, &env_with, type_fields) {
6035                    return Some(inner);
6036                }
6037                return Some(env_with);
6038            }
6039            Some(env.clone())
6040        }
6041        Expr::Lam(_, _scope, param, ann, _constraints, body) => {
6042            if position_in_span(position, *body.span()) {
6043                let mut env_with = env.clone();
6044                let fields = ann
6045                    .as_ref()
6046                    .and_then(|ann| fields_from_type_expr(ann, type_fields))
6047                    .unwrap_or_default();
6048                env_with.insert(param.name.to_string(), fields);
6049                if let Some(inner) = field_env_at_expr(body, position, &env_with, type_fields) {
6050                    return Some(inner);
6051                }
6052                return Some(env_with);
6053            }
6054            Some(env.clone())
6055        }
6056        Expr::Match(_, scrutinee, arms) => {
6057            if position_in_span(position, *scrutinee.span()) {
6058                return field_env_at_expr(scrutinee, position, env, type_fields)
6059                    .or_else(|| Some(env.clone()));
6060            }
6061            for (pattern, arm) in arms {
6062                if position_in_span(position, *pattern.span()) {
6063                    return Some(env.clone());
6064                }
6065                if position_in_span(position, *arm.span()) {
6066                    let mut env_with = env.clone();
6067                    env_with.extend(
6068                        pattern_vars(pattern)
6069                            .into_iter()
6070                            .map(|name| (name, BTreeSet::new())),
6071                    );
6072                    if let Some(inner) = field_env_at_expr(arm, position, &env_with, type_fields) {
6073                        return Some(inner);
6074                    }
6075                    return Some(env_with);
6076                }
6077            }
6078            Some(env.clone())
6079        }
6080        Expr::App(_, fun, arg) => {
6081            if position_in_span(position, *fun.span()) {
6082                return field_env_at_expr(fun, position, env, type_fields)
6083                    .or_else(|| Some(env.clone()));
6084            }
6085            if position_in_span(position, *arg.span()) {
6086                return field_env_at_expr(arg, position, env, type_fields)
6087                    .or_else(|| Some(env.clone()));
6088            }
6089            Some(env.clone())
6090        }
6091        Expr::Project(_, base, _field) => {
6092            if position_in_span(position, *base.span()) {
6093                return field_env_at_expr(base, position, env, type_fields)
6094                    .or_else(|| Some(env.clone()));
6095            }
6096            Some(env.clone())
6097        }
6098        Expr::Tuple(_, elems) | Expr::List(_, elems) => {
6099            for elem in elems {
6100                if position_in_span(position, *elem.span()) {
6101                    return field_env_at_expr(elem, position, env, type_fields)
6102                        .or_else(|| Some(env.clone()));
6103                }
6104            }
6105            Some(env.clone())
6106        }
6107        Expr::Dict(_, entries) => {
6108            for value in entries.values() {
6109                if position_in_span(position, *value.span()) {
6110                    return field_env_at_expr(value, position, env, type_fields)
6111                        .or_else(|| Some(env.clone()));
6112                }
6113            }
6114            Some(env.clone())
6115        }
6116        Expr::Ite(_, cond, then_expr, else_expr) => {
6117            if position_in_span(position, *cond.span()) {
6118                return field_env_at_expr(cond, position, env, type_fields)
6119                    .or_else(|| Some(env.clone()));
6120            }
6121            if position_in_span(position, *then_expr.span()) {
6122                return field_env_at_expr(then_expr, position, env, type_fields)
6123                    .or_else(|| Some(env.clone()));
6124            }
6125            if position_in_span(position, *else_expr.span()) {
6126                return field_env_at_expr(else_expr, position, env, type_fields)
6127                    .or_else(|| Some(env.clone()));
6128            }
6129            Some(env.clone())
6130        }
6131        Expr::Ann(_, inner, _ann) => {
6132            if position_in_span(position, *inner.span()) {
6133                return field_env_at_expr(inner, position, env, type_fields)
6134                    .or_else(|| Some(env.clone()));
6135            }
6136            Some(env.clone())
6137        }
6138        _ => Some(env.clone()),
6139    }
6140}
6141
6142fn binding_fields(
6143    ann: Option<&TypeExpr>,
6144    def: &Expr,
6145    type_fields: &HashMap<String, BTreeSet<String>>,
6146) -> Option<BTreeSet<String>> {
6147    if let Some(ann) = ann
6148        && let Some(fields) = fields_from_type_expr(ann, type_fields)
6149    {
6150        return Some(fields);
6151    }
6152
6153    if let Expr::Ann(_, _inner, ann) = def
6154        && let Some(fields) = fields_from_type_expr(ann, type_fields)
6155    {
6156        return Some(fields);
6157    }
6158
6159    if let Expr::Dict(_, entries) = def {
6160        let fields: BTreeSet<String> = entries.keys().map(|name| name.to_string()).collect();
6161        if !fields.is_empty() {
6162            return Some(fields);
6163        }
6164    }
6165
6166    None
6167}
6168
6169fn fields_from_type_expr(
6170    typ: &TypeExpr,
6171    type_fields: &HashMap<String, BTreeSet<String>>,
6172) -> Option<BTreeSet<String>> {
6173    match typ {
6174        TypeExpr::Record(_, entries) => {
6175            let fields: BTreeSet<String> =
6176                entries.iter().map(|(name, _)| name.to_string()).collect();
6177            if fields.is_empty() {
6178                None
6179            } else {
6180                Some(fields)
6181            }
6182        }
6183        _ => {
6184            if let Some(type_name) = type_name_from_type_expr(typ) {
6185                return type_fields.get(&type_name).cloned();
6186            }
6187            None
6188        }
6189    }
6190}
6191
6192fn type_name_from_type_expr(typ: &TypeExpr) -> Option<String> {
6193    match typ {
6194        TypeExpr::Name(_, name) => Some(name.to_string()),
6195        TypeExpr::App(_, fun, _) => type_name_from_type_expr(fun),
6196        _ => None,
6197    }
6198}
6199
6200fn fields_for_expr(
6201    expr: &Expr,
6202    env: &HashMap<String, BTreeSet<String>>,
6203    type_fields: &HashMap<String, BTreeSet<String>>,
6204) -> Option<BTreeSet<String>> {
6205    match expr {
6206        Expr::Dict(_, entries) => {
6207            let fields: BTreeSet<String> = entries.keys().map(|name| name.to_string()).collect();
6208            if fields.is_empty() {
6209                None
6210            } else {
6211                Some(fields)
6212            }
6213        }
6214        Expr::Var(var) => {
6215            if let Some(fields) = env.get(var.name.as_ref()) {
6216                return Some(fields.clone());
6217            }
6218            if let Some(fields) = type_fields.get(var.name.as_ref()) {
6219                return Some(fields.clone());
6220            }
6221            None
6222        }
6223        Expr::Ann(_, inner, ann) => fields_from_type_expr(ann, type_fields)
6224            .or_else(|| fields_for_expr(inner, env, type_fields)),
6225        Expr::Project(_, base, _) => fields_for_expr(base, env, type_fields),
6226        _ => None,
6227    }
6228}
6229
6230fn project_base_at_position(expr: &Expr, position: RexPosition) -> Option<&Expr> {
6231    if !position_in_span(position, *expr.span()) {
6232        return None;
6233    }
6234
6235    match expr {
6236        Expr::Project(_, base, _) => {
6237            if position_in_span(position, *base.span()) {
6238                return project_base_at_position(base, position);
6239            }
6240            Some(base.as_ref())
6241        }
6242        Expr::Let(_, _var, _ann, def, body) => {
6243            if let Some(found) = project_base_at_position(def, position) {
6244                return Some(found);
6245            }
6246            project_base_at_position(body, position)
6247        }
6248        Expr::LetRec(_, bindings, body) => {
6249            for (_, _, def) in bindings {
6250                if let Some(found) = project_base_at_position(def, position) {
6251                    return Some(found);
6252                }
6253            }
6254            project_base_at_position(body, position)
6255        }
6256        Expr::Lam(_, _scope, _param, _ann, _constraints, body) => {
6257            project_base_at_position(body, position)
6258        }
6259        Expr::Match(_, scrutinee, arms) => {
6260            if let Some(found) = project_base_at_position(scrutinee, position) {
6261                return Some(found);
6262            }
6263            for (_pattern, arm) in arms {
6264                if let Some(found) = project_base_at_position(arm, position) {
6265                    return Some(found);
6266                }
6267            }
6268            None
6269        }
6270        Expr::App(_, fun, arg) => {
6271            if let Some(found) = project_base_at_position(fun, position) {
6272                return Some(found);
6273            }
6274            project_base_at_position(arg, position)
6275        }
6276        Expr::Tuple(_, elems) | Expr::List(_, elems) => {
6277            for elem in elems {
6278                if let Some(found) = project_base_at_position(elem, position) {
6279                    return Some(found);
6280                }
6281            }
6282            None
6283        }
6284        Expr::Dict(_, entries) => {
6285            for value in entries.values() {
6286                if let Some(found) = project_base_at_position(value, position) {
6287                    return Some(found);
6288                }
6289            }
6290            None
6291        }
6292        Expr::Ite(_, cond, then_expr, else_expr) => {
6293            if let Some(found) = project_base_at_position(cond, position) {
6294                return Some(found);
6295            }
6296            if let Some(found) = project_base_at_position(then_expr, position) {
6297                return Some(found);
6298            }
6299            project_base_at_position(else_expr, position)
6300        }
6301        Expr::Ann(_, inner, _ann) => project_base_at_position(inner, position),
6302        _ => None,
6303    }
6304}
6305
6306fn fallback_field_map(tokens: &Tokens) -> HashMap<String, BTreeSet<String>> {
6307    let mut map = HashMap::new();
6308    let items = &tokens.items;
6309    let mut index = 0usize;
6310    while index + 2 < items.len() {
6311        if let Token::Ident(name, ..) = &items[index]
6312            && matches!(items[index + 1], Token::Assign(..) | Token::Colon(..))
6313            && matches!(items[index + 2], Token::BraceL(..))
6314            && let Some((fields, end_index)) = parse_record_fields(items, index + 2)
6315        {
6316            if !fields.is_empty() {
6317                map.insert(name.clone(), fields);
6318            }
6319            index = end_index + 1;
6320            continue;
6321        }
6322        index += 1;
6323    }
6324    map
6325}
6326
6327fn parse_record_fields(tokens: &[Token], start_index: usize) -> Option<(BTreeSet<String>, usize)> {
6328    if !matches!(tokens.get(start_index), Some(Token::BraceL(..))) {
6329        return None;
6330    }
6331
6332    let mut depth = 0usize;
6333    let mut fields = BTreeSet::new();
6334    let mut index = start_index;
6335    while index < tokens.len() {
6336        match &tokens[index] {
6337            Token::BraceL(..) => depth += 1,
6338            Token::BraceR(..) => {
6339                depth = depth.saturating_sub(1);
6340                if depth == 0 {
6341                    return Some((fields, index));
6342                }
6343            }
6344            Token::Ident(name, ..) if depth == 1 => {
6345                if let Some(next) = tokens.get(index + 1)
6346                    && matches!(next, Token::Assign(..) | Token::Colon(..))
6347                {
6348                    fields.insert(name.clone());
6349                }
6350            }
6351            _ => {}
6352        }
6353        index += 1;
6354    }
6355
6356    None
6357}
6358
6359fn field_base_ident(text: &str, position: Position) -> Option<String> {
6360    let offset = offset_at(text, position)?;
6361    if offset == 0 {
6362        return None;
6363    }
6364
6365    let bytes = text.as_bytes();
6366    let mut index = offset.min(bytes.len());
6367
6368    while index > 0 && bytes[index - 1].is_ascii_whitespace() {
6369        index -= 1;
6370    }
6371    while index > 0 && is_word_byte(bytes[index - 1]) {
6372        index -= 1;
6373    }
6374    while index > 0 && bytes[index - 1].is_ascii_whitespace() {
6375        index -= 1;
6376    }
6377
6378    if index == 0 || bytes[index - 1] != b'.' {
6379        return None;
6380    }
6381
6382    index -= 1;
6383    while index > 0 && bytes[index - 1].is_ascii_whitespace() {
6384        index -= 1;
6385    }
6386
6387    let end = index;
6388    while index > 0 && is_word_byte(bytes[index - 1]) {
6389        index -= 1;
6390    }
6391
6392    if index == end {
6393        return None;
6394    }
6395
6396    Some(text[index..end].to_string())
6397}
6398
6399fn is_word_byte(byte: u8) -> bool {
6400    let ch = byte as char;
6401    ch.is_ascii_alphanumeric() || ch == '_'
6402}
6403
6404fn ident_token_at_position(tokens: &Tokens, position: Position) -> Option<(String, Span)> {
6405    for token in &tokens.items {
6406        let Token::Ident(name, span, ..) = token else {
6407            continue;
6408        };
6409        if range_touches_position(span_to_range(*span), position) {
6410            return Some((name.clone(), *span));
6411        }
6412    }
6413    None
6414}
6415
6416fn imported_projection_at_position(
6417    tokens: &Tokens,
6418    position: Position,
6419) -> Option<(String, String)> {
6420    fn is_trivia(token: &Token) -> bool {
6421        matches!(token, Token::CommentL(..) | Token::CommentR(..))
6422    }
6423
6424    fn prev_non_trivia(tokens: &Tokens, start: usize) -> Option<usize> {
6425        let mut idx = start;
6426        while idx > 0 {
6427            idx -= 1;
6428            if !is_trivia(&tokens.items[idx]) {
6429                return Some(idx);
6430            }
6431        }
6432        None
6433    }
6434
6435    let mut ident_index = None;
6436    let mut field = None;
6437    for (idx, token) in tokens.items.iter().enumerate() {
6438        let Token::Ident(name, span, ..) = token else {
6439            continue;
6440        };
6441        if range_touches_position(span_to_range(*span), position) {
6442            ident_index = Some(idx);
6443            field = Some(name.clone());
6444            break;
6445        }
6446    }
6447    let ident_index = ident_index?;
6448    let field = field?;
6449
6450    let dot_idx = prev_non_trivia(tokens, ident_index)?;
6451    if !matches!(tokens.items[dot_idx], Token::Dot(..)) {
6452        return None;
6453    }
6454    let base_idx = prev_non_trivia(tokens, dot_idx)?;
6455    let Token::Ident(base, ..) = &tokens.items[base_idx] else {
6456        return None;
6457    };
6458
6459    Some((base.clone(), field))
6460}
6461
6462struct DeclSpanIndex {
6463    type_defs: HashMap<String, Span>,
6464    ctor_defs: HashMap<String, Span>,
6465    class_defs: HashMap<String, Span>,
6466    fn_defs: HashMap<String, Span>,
6467    class_method_defs: HashMap<String, Span>,
6468    instance_method_defs: Vec<(Span, HashMap<String, Span>)>,
6469}
6470
6471fn index_decl_spans(compilation_unit: &CompilationUnit, tokens: &Tokens) -> DeclSpanIndex {
6472    fn span_contains_span(outer: Span, inner: Span) -> bool {
6473        position_leq(outer.begin, inner.begin) && position_leq(inner.end, outer.end)
6474    }
6475
6476    let mut type_defs = HashMap::new();
6477    let mut ctor_defs = HashMap::new();
6478    let mut class_defs = HashMap::new();
6479    let mut fn_defs = HashMap::new();
6480    let mut class_method_defs = HashMap::new();
6481    let mut instance_method_defs = Vec::new();
6482
6483    for decl in &compilation_unit.decls {
6484        match decl {
6485            Decl::Type(td) => {
6486                let decl_span = td.span;
6487                let mut expect_type_name = false;
6488                let mut expect_ctor_name = false;
6489
6490                for token in &tokens.items {
6491                    let token_span = *token.span();
6492                    if !span_contains_span(decl_span, token_span) {
6493                        continue;
6494                    }
6495
6496                    match token {
6497                        Token::Type(..) => {
6498                            expect_type_name = true;
6499                            expect_ctor_name = false;
6500                        }
6501                        Token::Ident(name, span, ..) if expect_type_name => {
6502                            type_defs.insert(name.clone(), *span);
6503                            expect_type_name = false;
6504                        }
6505                        Token::Assign(..) | Token::Pipe(..) => {
6506                            expect_ctor_name = true;
6507                        }
6508                        Token::Ident(name, span, ..) if expect_ctor_name => {
6509                            ctor_defs.insert(name.clone(), *span);
6510                            expect_ctor_name = false;
6511                        }
6512                        _ => {}
6513                    }
6514                }
6515            }
6516            Decl::Class(cd) => {
6517                let decl_span = cd.span;
6518                let mut expect_class_name = false;
6519                for i in 0..tokens.items.len() {
6520                    let token = &tokens.items[i];
6521                    let token_span = *token.span();
6522                    if !span_contains_span(decl_span, token_span) {
6523                        continue;
6524                    }
6525                    match token {
6526                        Token::Class(..) => expect_class_name = true,
6527                        Token::Ident(name, span, ..) if expect_class_name => {
6528                            class_defs.insert(name.clone(), *span);
6529                            expect_class_name = false;
6530                        }
6531                        Token::Ident(name, span, ..) => {
6532                            if let Some(next) = tokens.items.get(i + 1)
6533                                && matches!(next, Token::Colon(..))
6534                            {
6535                                class_method_defs.insert(name.clone(), *span);
6536                            }
6537                        }
6538                        _ => {}
6539                    }
6540                }
6541            }
6542            Decl::Instance(id) => {
6543                let decl_span = id.span;
6544                let mut methods = HashMap::new();
6545                for i in 0..tokens.items.len() {
6546                    let token = &tokens.items[i];
6547                    let token_span = *token.span();
6548                    if !span_contains_span(decl_span, token_span) {
6549                        continue;
6550                    }
6551                    if let Token::Ident(name, span, ..) = token
6552                        && let Some(next) = tokens.items.get(i + 1)
6553                        && matches!(next, Token::Assign(..))
6554                    {
6555                        methods.insert(name.clone(), *span);
6556                    }
6557                }
6558                instance_method_defs.push((decl_span, methods));
6559            }
6560            Decl::Fn(fd) => {
6561                fn_defs.insert(fd.name.name.as_ref().to_string(), fd.name.span);
6562            }
6563            Decl::DeclareFn(fd) => {
6564                fn_defs.insert(fd.name.name.as_ref().to_string(), fd.name.span);
6565            }
6566            Decl::Import(..) => {}
6567        }
6568    }
6569
6570    DeclSpanIndex {
6571        type_defs,
6572        ctor_defs,
6573        class_defs,
6574        fn_defs,
6575        class_method_defs,
6576        instance_method_defs,
6577    }
6578}
6579
6580fn definition_span_for_value_ident(
6581    expr: &Expr,
6582    position: RexPosition,
6583    ident: &str,
6584    bindings: &mut Vec<(String, Span)>,
6585    tokens: &Tokens,
6586) -> Option<Span> {
6587    if !position_in_span(position, *expr.span()) {
6588        return None;
6589    }
6590
6591    fn lookup_binding(bindings: &[(String, Span)], ident: &str) -> Option<Span> {
6592        bindings
6593            .iter()
6594            .rev()
6595            .find_map(|(name, span)| (name == ident).then_some(*span))
6596    }
6597
6598    fn definition_in_pattern(
6599        pat: &Pattern,
6600        position: RexPosition,
6601        ident: &str,
6602        _tokens: &Tokens,
6603    ) -> Option<Span> {
6604        if !position_in_span(position, *pat.span()) {
6605            return None;
6606        }
6607
6608        match pat {
6609            Pattern::Var(var) => (var.name.as_ref() == ident).then_some(var.span),
6610            Pattern::Named(_span, _name, args) => args
6611                .iter()
6612                .find_map(|arg| definition_in_pattern(arg, position, ident, _tokens)),
6613            Pattern::Tuple(_span, elems) => elems
6614                .iter()
6615                .find_map(|elem| definition_in_pattern(elem, position, ident, _tokens)),
6616            Pattern::List(_span, elems) => elems
6617                .iter()
6618                .find_map(|elem| definition_in_pattern(elem, position, ident, _tokens)),
6619            Pattern::Cons(_span, head, tail) => {
6620                definition_in_pattern(head, position, ident, _tokens)
6621                    .or_else(|| definition_in_pattern(tail, position, ident, _tokens))
6622            }
6623            Pattern::Dict(_span, fields) => fields
6624                .iter()
6625                .find_map(|(_, p)| definition_in_pattern(p, position, ident, _tokens)),
6626            Pattern::Wildcard(..) => None,
6627        }
6628    }
6629
6630    fn push_pattern_bindings(pat: &Pattern, bindings: &mut Vec<(String, Span)>, _tokens: &Tokens) {
6631        match pat {
6632            Pattern::Var(var) => bindings.push((var.name.to_string(), var.span)),
6633            Pattern::Named(_span, _name, args) => {
6634                for arg in args {
6635                    push_pattern_bindings(arg, bindings, _tokens);
6636                }
6637            }
6638            Pattern::Tuple(_span, elems) => {
6639                for elem in elems {
6640                    push_pattern_bindings(elem, bindings, _tokens);
6641                }
6642            }
6643            Pattern::List(_span, elems) => {
6644                for elem in elems {
6645                    push_pattern_bindings(elem, bindings, _tokens);
6646                }
6647            }
6648            Pattern::Cons(_span, head, tail) => {
6649                push_pattern_bindings(head, bindings, _tokens);
6650                push_pattern_bindings(tail, bindings, _tokens);
6651            }
6652            Pattern::Dict(_span, fields) => {
6653                for (_key, pat) in fields {
6654                    push_pattern_bindings(pat, bindings, _tokens);
6655                }
6656            }
6657            Pattern::Wildcard(..) => {}
6658        }
6659    }
6660
6661    match expr {
6662        Expr::Var(var) => {
6663            if position_in_span(position, var.span) && var.name.as_ref() == ident {
6664                return lookup_binding(bindings, ident);
6665            }
6666            None
6667        }
6668        Expr::Let(_span, var, _ann, def, body) => {
6669            if position_in_span(position, var.span) && var.name.as_ref() == ident {
6670                return Some(var.span);
6671            }
6672
6673            if position_in_span(position, *def.span()) {
6674                return definition_span_for_value_ident(def, position, ident, bindings, tokens);
6675            }
6676            if position_in_span(position, *body.span()) {
6677                bindings.push((var.name.to_string(), var.span));
6678                let out = definition_span_for_value_ident(body, position, ident, bindings, tokens);
6679                bindings.pop();
6680                return out;
6681            }
6682            None
6683        }
6684        Expr::LetRec(_span, rec_bindings, body) => {
6685            for (var, _ann, _def) in rec_bindings {
6686                if position_in_span(position, var.span) && var.name.as_ref() == ident {
6687                    return Some(var.span);
6688                }
6689            }
6690
6691            let base_len = bindings.len();
6692            for (var, _ann, _def) in rec_bindings {
6693                bindings.push((var.name.to_string(), var.span));
6694            }
6695
6696            for (_var, _ann, def) in rec_bindings {
6697                if position_in_span(position, *def.span()) {
6698                    let out =
6699                        definition_span_for_value_ident(def, position, ident, bindings, tokens);
6700                    bindings.truncate(base_len);
6701                    return out;
6702                }
6703            }
6704            if position_in_span(position, *body.span()) {
6705                let out = definition_span_for_value_ident(body, position, ident, bindings, tokens);
6706                bindings.truncate(base_len);
6707                return out;
6708            }
6709
6710            bindings.truncate(base_len);
6711            None
6712        }
6713        Expr::Lam(_span, _scope, param, _ann, _constraints, body) => {
6714            if position_in_span(position, param.span) && param.name.as_ref() == ident {
6715                return Some(param.span);
6716            }
6717
6718            if position_in_span(position, *body.span()) {
6719                bindings.push((param.name.to_string(), param.span));
6720                let out = definition_span_for_value_ident(body, position, ident, bindings, tokens);
6721                bindings.pop();
6722                return out;
6723            }
6724            None
6725        }
6726        Expr::Match(_span, scrutinee, arms) => {
6727            if position_in_span(position, *scrutinee.span()) {
6728                return definition_span_for_value_ident(
6729                    scrutinee, position, ident, bindings, tokens,
6730                );
6731            }
6732
6733            for (pat, arm) in arms {
6734                if position_in_span(position, *pat.span()) {
6735                    return definition_in_pattern(pat, position, ident, tokens);
6736                }
6737
6738                if position_in_span(position, *arm.span()) {
6739                    let base_len = bindings.len();
6740                    push_pattern_bindings(pat, bindings, tokens);
6741                    let out =
6742                        definition_span_for_value_ident(arm, position, ident, bindings, tokens);
6743                    bindings.truncate(base_len);
6744                    return out;
6745                }
6746            }
6747            None
6748        }
6749        Expr::App(_span, fun, arg) => {
6750            if position_in_span(position, *fun.span()) {
6751                return definition_span_for_value_ident(fun, position, ident, bindings, tokens);
6752            }
6753            if position_in_span(position, *arg.span()) {
6754                return definition_span_for_value_ident(arg, position, ident, bindings, tokens);
6755            }
6756            None
6757        }
6758        Expr::Project(_span, base, _field) => {
6759            if position_in_span(position, *base.span()) {
6760                return definition_span_for_value_ident(base, position, ident, bindings, tokens);
6761            }
6762            None
6763        }
6764        Expr::Tuple(_span, elems) | Expr::List(_span, elems) => elems.iter().find_map(|elem| {
6765            position_in_span(position, *elem.span())
6766                .then(|| definition_span_for_value_ident(elem, position, ident, bindings, tokens))
6767                .flatten()
6768        }),
6769        Expr::Dict(_span, entries) => entries.values().find_map(|value| {
6770            position_in_span(position, *value.span())
6771                .then(|| definition_span_for_value_ident(value, position, ident, bindings, tokens))
6772                .flatten()
6773        }),
6774        Expr::Ite(_span, cond, then_expr, else_expr) => {
6775            if position_in_span(position, *cond.span()) {
6776                return definition_span_for_value_ident(cond, position, ident, bindings, tokens);
6777            }
6778            if position_in_span(position, *then_expr.span()) {
6779                return definition_span_for_value_ident(
6780                    then_expr, position, ident, bindings, tokens,
6781                );
6782            }
6783            if position_in_span(position, *else_expr.span()) {
6784                return definition_span_for_value_ident(
6785                    else_expr, position, ident, bindings, tokens,
6786                );
6787            }
6788            None
6789        }
6790        Expr::Ann(_span, inner, _ann) => {
6791            if position_in_span(position, *inner.span()) {
6792                return definition_span_for_value_ident(inner, position, ident, bindings, tokens);
6793            }
6794            None
6795        }
6796        _ => None,
6797    }
6798}
6799
6800// Note: completion uses `values_in_scope_at_position` instead of a plain list of
6801// names so it can classify `fn`/`let name = \...` as `CompletionItemKind::FUNCTION`.
6802
6803fn pattern_vars(pattern: &Pattern) -> Vec<String> {
6804    let mut vars = Vec::new();
6805    collect_pattern_vars(pattern, &mut vars);
6806    vars
6807}
6808
6809fn collect_pattern_vars(pattern: &Pattern, vars: &mut Vec<String>) {
6810    match pattern {
6811        Pattern::Var(var) => vars.push(var.name.to_string()),
6812        Pattern::Named(_, _name, args) => {
6813            for arg in args {
6814                collect_pattern_vars(arg, vars);
6815            }
6816        }
6817        Pattern::Tuple(_, elems) => {
6818            for elem in elems {
6819                collect_pattern_vars(elem, vars);
6820            }
6821        }
6822        Pattern::List(_, elems) => {
6823            for elem in elems {
6824                collect_pattern_vars(elem, vars);
6825            }
6826        }
6827        Pattern::Cons(_, head, tail) => {
6828            collect_pattern_vars(head, vars);
6829            collect_pattern_vars(tail, vars);
6830        }
6831        Pattern::Dict(_, fields) => {
6832            for (_key, pat) in fields {
6833                collect_pattern_vars(pat, vars);
6834            }
6835        }
6836        Pattern::Wildcard(_) => {}
6837    }
6838}
6839
6840fn is_field_completion(text: &str, position: Position) -> bool {
6841    let offset = match offset_at(text, position) {
6842        Some(offset) => offset,
6843        None => return false,
6844    };
6845
6846    if offset == 0 {
6847        return false;
6848    }
6849
6850    let mut start = offset;
6851    while start > 0 {
6852        let prev = text.as_bytes()[start - 1] as char;
6853        if is_word_char(prev) {
6854            start -= 1;
6855            continue;
6856        }
6857        break;
6858    }
6859
6860    if start > 0 && text.as_bytes()[start - 1] as char == '.' {
6861        return true;
6862    }
6863
6864    text.as_bytes()[offset.saturating_sub(1)] as char == '.'
6865}
6866
6867fn lsp_to_rex_position(position: Position) -> RexPosition {
6868    RexPosition::new(position.line as usize + 1, position.character as usize + 1)
6869}
6870
6871fn position_in_span(position: RexPosition, span: Span) -> bool {
6872    position_leq(span.begin, position) && position_leq(position, span.end)
6873}
6874
6875fn position_leq(left: RexPosition, right: RexPosition) -> bool {
6876    left.line < right.line || (left.line == right.line && left.column <= right.column)
6877}
6878
6879pub(crate) fn word_at_position(text: &str, position: Position) -> Option<String> {
6880    let offset = offset_at(text, position)?;
6881    if offset >= text.len() {
6882        return None;
6883    }
6884
6885    let chars: Vec<(usize, char)> = text.char_indices().collect();
6886    let mut idx = None;
6887    for (i, (byte_index, _)) in chars.iter().enumerate() {
6888        if *byte_index == offset {
6889            idx = Some(i);
6890            break;
6891        }
6892    }
6893
6894    let idx = idx?;
6895    if !is_word_char(chars[idx].1) {
6896        return None;
6897    }
6898
6899    let mut start = idx;
6900    while start > 0 && is_word_char(chars[start - 1].1) {
6901        start -= 1;
6902    }
6903
6904    let mut end = idx + 1;
6905    while end < chars.len() && is_word_char(chars[end].1) {
6906        end += 1;
6907    }
6908
6909    let start_byte = chars[start].0;
6910    let end_byte = if end < chars.len() {
6911        chars[end].0
6912    } else {
6913        text.len()
6914    };
6915
6916    Some(text[start_byte..end_byte].to_string())
6917}
6918
6919fn offset_at(text: &str, position: Position) -> Option<usize> {
6920    let mut offset = 0usize;
6921    let mut current_line = 0u32;
6922
6923    for mut line in text.split('\n') {
6924        if line.ends_with('\r') {
6925            line = &line[..line.len().saturating_sub(1)];
6926        }
6927
6928        if current_line == position.line {
6929            let mut remaining = position.character as usize;
6930            for (byte_index, _) in line.char_indices() {
6931                if remaining == 0 {
6932                    return Some(offset + byte_index);
6933                }
6934                remaining -= 1;
6935            }
6936            return Some(offset + line.len());
6937        }
6938
6939        offset += line.len() + 1;
6940        current_line += 1;
6941    }
6942
6943    if current_line == position.line {
6944        Some(offset)
6945    } else {
6946        None
6947    }
6948}
6949
6950fn position_at_offset(text: &str, target: usize) -> Position {
6951    let mut line = 0u32;
6952    let mut character = 0u32;
6953
6954    for (offset, ch) in text.char_indices() {
6955        if offset >= target {
6956            break;
6957        }
6958        if ch == '\n' {
6959            line += 1;
6960            character = 0;
6961        } else {
6962            character += 1;
6963        }
6964    }
6965
6966    Position { line, character }
6967}
6968
6969fn wildcard_match_arm_insert(text: &str, range: Range) -> Option<(Position, String)> {
6970    let end = offset_at(text, range.end)?;
6971    let search = &text[..end.min(text.len())];
6972    let close_offset = search.rfind('}')?;
6973    let line_start = text[..close_offset]
6974        .rfind('\n')
6975        .map(|idx| idx + 1)
6976        .unwrap_or(0);
6977    let closing_prefix = &text[line_start..close_offset];
6978
6979    if closing_prefix.chars().all(char::is_whitespace) {
6980        let arm_indent = format!("{closing_prefix}  ");
6981        Some((
6982            position_at_offset(text, line_start),
6983            format!("{arm_indent}case _ -> null;\n"),
6984        ))
6985    } else {
6986        Some((
6987            position_at_offset(text, close_offset),
6988            " case _ -> null;".to_string(),
6989        ))
6990    }
6991}
6992
6993fn is_word_char(ch: char) -> bool {
6994    ch.is_ascii_alphanumeric() || ch == '_'
6995}
6996
6997pub fn in_memory_doc_uri() -> Url {
6998    match Url::parse("inmemory:///docs.rex") {
6999        Ok(url) => url,
7000        Err(_) => panic!("static in-memory URI must parse"),
7001    }
7002}
7003
7004pub fn diagnostics_for_source(source: &str) -> Vec<Diagnostic> {
7005    let uri = in_memory_doc_uri();
7006    clear_parse_cache(&uri);
7007    diagnostics_from_text(&uri, source)
7008}
7009
7010pub fn completion_for_source(source: &str, line: u32, character: u32) -> Vec<CompletionItem> {
7011    let uri = in_memory_doc_uri();
7012    clear_parse_cache(&uri);
7013    completion_items(&uri, source, Position { line, character })
7014}
7015
7016pub fn hover_for_source(source: &str, line: u32, character: u32) -> Option<Hover> {
7017    let uri = in_memory_doc_uri();
7018    clear_parse_cache(&uri);
7019    let position = Position { line, character };
7020    let contents = hover_type_contents(&uri, source, position).or_else(|| {
7021        let word = word_at_position(source, position)?;
7022        hover_contents(&word)
7023    })?;
7024    Some(Hover {
7025        contents,
7026        range: None,
7027    })
7028}
7029
7030pub fn expected_type_for_source_public(source: &str, line: u32, character: u32) -> Option<String> {
7031    let uri = in_memory_doc_uri();
7032    clear_parse_cache(&uri);
7033    expected_type_at_position(&uri, source, Position { line, character })
7034}
7035
7036pub fn functions_producing_expected_type_for_source_public(
7037    source: &str,
7038    line: u32,
7039    character: u32,
7040) -> Vec<String> {
7041    let uri = in_memory_doc_uri();
7042    clear_parse_cache(&uri);
7043    functions_producing_expected_type_at_position(&uri, source, Position { line, character })
7044        .into_iter()
7045        .map(|(name, typ)| format!("{name} : {typ}"))
7046        .collect()
7047}
7048
7049pub fn references_for_source_public(
7050    source: &str,
7051    line: u32,
7052    character: u32,
7053    include_declaration: bool,
7054) -> Vec<Location> {
7055    let uri = in_memory_doc_uri();
7056    clear_parse_cache(&uri);
7057    references_for_source(
7058        &uri,
7059        source,
7060        Position { line, character },
7061        include_declaration,
7062    )
7063}
7064
7065pub fn rename_for_source_public(
7066    source: &str,
7067    line: u32,
7068    character: u32,
7069    new_name: &str,
7070) -> Option<WorkspaceEdit> {
7071    let uri = in_memory_doc_uri();
7072    clear_parse_cache(&uri);
7073    rename_for_source(&uri, source, Position { line, character }, new_name)
7074}
7075
7076pub fn document_symbols_for_source_public(source: &str) -> Vec<DocumentSymbol> {
7077    let uri = in_memory_doc_uri();
7078    clear_parse_cache(&uri);
7079    document_symbols_for_source(&uri, source)
7080}
7081
7082pub fn format_for_source_public(source: &str) -> Option<Vec<TextEdit>> {
7083    format_edits_for_source(source)
7084}
7085
7086pub fn code_actions_for_source_public(
7087    source: &str,
7088    line: u32,
7089    character: u32,
7090) -> Vec<CodeActionOrCommand> {
7091    let uri = in_memory_doc_uri();
7092    clear_parse_cache(&uri);
7093    let position = Position { line, character };
7094    let range = Range {
7095        start: position,
7096        end: position,
7097    };
7098    let diagnostics: Vec<Diagnostic> = diagnostics_from_text(&uri, source)
7099        .into_iter()
7100        .filter(|diag| {
7101            range_contains_position(diag.range, position)
7102                || range_touches_position(diag.range, position)
7103        })
7104        .collect();
7105    code_actions_for_source(&uri, source, range, &diagnostics)
7106}
7107
7108pub fn goto_definition_for_source(source: &str, line: u32, character: u32) -> Option<Location> {
7109    let uri = in_memory_doc_uri();
7110    clear_parse_cache(&uri);
7111    let pos = Position { line, character };
7112    let response = goto_definition_response(&uri, source, pos)?;
7113    match response {
7114        GotoDefinitionResponse::Scalar(location) => Some(location),
7115        GotoDefinitionResponse::Array(locations) => locations.into_iter().next(),
7116        GotoDefinitionResponse::Link(links) => links.into_iter().next().map(|link| Location {
7117            uri: link.target_uri,
7118            range: link.target_range,
7119        }),
7120    }
7121}