1#![forbid(unsafe_code)]
2#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
3
4use std::collections::{BTreeSet, HashMap, HashSet};
5use std::fs;
6use std::hash::{Hash, Hasher};
7use std::path::PathBuf;
8use std::sync::{Mutex, OnceLock};
9
10use lsp_types::{
11 CodeAction, CodeActionKind, CodeActionOrCommand, CompletionItem, CompletionItemKind,
12 Diagnostic, DiagnosticSeverity, DocumentSymbol, GotoDefinitionResponse, Hover, HoverContents,
13 Location, MarkupContent, MarkupKind, Position, Range, SymbolKind, TextEdit, Url, WorkspaceEdit,
14};
15use rexlang_ast::expr::{
16 Decl, DeclareFnDecl, Expr, FnDecl, ImportDecl, ImportPath, InstanceDecl, Pattern, Program,
17 Symbol, TypeConstraint, TypeDecl, TypeExpr, Var, intern,
18};
19use rexlang_lexer::{
20 LexicalError, Token, Tokens,
21 span::{Position as RexPosition, Span, Spanned},
22};
23use rexlang_parser::{Parser, error::ParserErr};
24use rexlang_typesystem::{
25 BuiltinTypeId, Scheme, Type, TypeError as TsTypeError, TypeKind, TypeSystem, TypedExpr,
26 TypedExprKind, Types, instantiate, unify,
27};
28use rexlang_util::{GasMeter, sha256_hex};
29use serde_json::{Value, json, to_value};
30#[cfg(not(target_arch = "wasm32"))]
31use tokio::sync::RwLock;
32#[cfg(not(target_arch = "wasm32"))]
33use tower_lsp::jsonrpc::Result;
34#[cfg(not(target_arch = "wasm32"))]
35use tower_lsp::lsp_types::{
36 CodeActionOptions, CodeActionParams, CodeActionResponse, CompletionOptions, CompletionParams,
37 CompletionResponse, DidChangeTextDocumentParams, DidCloseTextDocumentParams,
38 DidOpenTextDocumentParams, DocumentFormattingParams, DocumentSymbolParams,
39 DocumentSymbolResponse, ExecuteCommandOptions, ExecuteCommandParams, GotoDefinitionParams,
40 HoverParams, HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams,
41 MessageType, OneOf, ReferenceParams, RenameParams, ServerCapabilities, ServerInfo,
42 TextDocumentSyncCapability, TextDocumentSyncKind,
43};
44#[cfg(not(target_arch = "wasm32"))]
45use tower_lsp::{Client, LanguageServer, LspService, Server};
46
47const MAX_DIAGNOSTICS: usize = 50;
48const CMD_EXPECTED_TYPE_AT: &str = "rex.expectedTypeAt";
49const CMD_FUNCTIONS_PRODUCING_EXPECTED_TYPE_AT: &str = "rex.functionsProducingExpectedTypeAt";
50const CMD_FUNCTIONS_ACCEPTING_INFERRED_TYPE_AT: &str = "rex.functionsAcceptingInferredTypeAt";
51const CMD_ADAPTERS_FROM_INFERRED_TO_EXPECTED_AT: &str = "rex.adaptersFromInferredToExpectedAt";
52const CMD_FUNCTIONS_COMPATIBLE_WITH_IN_SCOPE_VALUES_AT: &str =
53 "rex.functionsCompatibleWithInScopeValuesAt";
54const CMD_HOLES_EXPECTED_TYPES: &str = "rex.holesExpectedTypes";
55const CMD_SEMANTIC_LOOP_STEP: &str = "rex.semanticLoopStep";
56const CMD_SEMANTIC_LOOP_APPLY_QUICK_FIX_AT: &str = "rex.semanticLoopApplyQuickFixAt";
57const CMD_SEMANTIC_LOOP_APPLY_BEST_QUICK_FIXES_AT: &str = "rex.semanticLoopApplyBestQuickFixesAt";
58const NO_IMPROVEMENT_STREAK_LIMIT: usize = 2;
59const MAX_SEMANTIC_ENV_SCHEMES_SCAN: usize = 1024;
60const MAX_SEMANTIC_IN_SCOPE_VALUES: usize = 128;
61const MAX_SEMANTIC_CANDIDATES: usize = 64;
62const MAX_SEMANTIC_HOLE_FILL_ARITY: usize = 8;
63const MAX_SEMANTIC_HOLES: usize = 128;
64const BUILTIN_TYPES: &[&str] = &[
65 "u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64", "f32", "f64", "bool", "string", "uuid",
66 "datetime", "Dict", "List", "Array", "Option", "Result",
67];
68const BUILTIN_VALUES: &[&str] = &["true", "false", "null", "Some", "None", "Ok", "Err"];
69
70#[derive(Debug)]
71enum TokenizeOrParseError {
72 Lex(LexicalError),
73 Parse(Vec<ParserErr>),
74}
75
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
77enum BulkQuickFixStrategy {
78 Conservative,
79 Aggressive,
80}
81
82impl BulkQuickFixStrategy {
83 fn parse(s: &str) -> Self {
84 if s.eq_ignore_ascii_case("aggressive") {
85 Self::Aggressive
86 } else {
87 Self::Conservative
88 }
89 }
90
91 fn as_str(self) -> &'static str {
92 match self {
93 Self::Conservative => "conservative",
94 Self::Aggressive => "aggressive",
95 }
96 }
97}
98
99#[derive(Clone)]
100struct CachedParse {
101 hash: u64,
102 tokens: Tokens,
103 program: Program,
104}
105
106fn text_hash(text: &str) -> u64 {
107 let mut hasher = std::collections::hash_map::DefaultHasher::new();
108 text.hash(&mut hasher);
109 hasher.finish()
110}
111
112fn parse_cache() -> &'static Mutex<HashMap<Url, CachedParse>> {
113 static CACHE: OnceLock<Mutex<HashMap<Url, CachedParse>>> = OnceLock::new();
114 CACHE.get_or_init(|| Mutex::new(HashMap::new()))
115}
116
117fn semantic_candidate_values(ts: &TypeSystem) -> Vec<(Symbol, Vec<Scheme>)> {
118 let mut out = Vec::new();
119 let mut scanned = 0usize;
120 for (name, schemes) in &ts.env.values {
121 if scanned >= MAX_SEMANTIC_ENV_SCHEMES_SCAN {
122 break;
123 }
124 let remaining = MAX_SEMANTIC_ENV_SCHEMES_SCAN - scanned;
125 let kept = schemes.iter().take(remaining).cloned().collect::<Vec<_>>();
126 if kept.is_empty() {
127 continue;
128 }
129 scanned += kept.len();
130 out.push((name.clone(), kept));
131 }
132 out
133}
134
135fn clear_parse_cache(uri: &Url) {
136 let Ok(mut cache) = parse_cache().lock() else {
137 return;
138 };
139 cache.remove(uri);
140}
141
142#[cfg(not(target_arch = "wasm32"))]
143fn uri_to_file_path(uri: &Url) -> Option<PathBuf> {
144 uri.to_file_path().ok()
145}
146
147#[cfg(target_arch = "wasm32")]
148fn uri_to_file_path(_uri: &Url) -> Option<PathBuf> {
149 None
150}
151
152#[cfg(not(target_arch = "wasm32"))]
153fn url_from_file_path(path: &std::path::Path) -> Option<Url> {
154 Url::from_file_path(path).ok()
155}
156
157#[cfg(target_arch = "wasm32")]
158fn url_from_file_path(_path: &std::path::Path) -> Option<Url> {
159 None
160}
161
162fn tokenize_and_parse(text: &str) -> std::result::Result<(Tokens, Program), TokenizeOrParseError> {
163 let tokens = Token::tokenize(text).map_err(TokenizeOrParseError::Lex)?;
164 let mut parser = Parser::new(tokens.clone());
165 let program = parser
166 .parse_program(&mut GasMeter::default())
167 .map_err(TokenizeOrParseError::Parse)?;
168 Ok((tokens, program))
169}
170
171fn tokenize_and_parse_cached(
172 uri: &Url,
173 text: &str,
174) -> std::result::Result<(Tokens, Program), TokenizeOrParseError> {
175 let hash = text_hash(text);
176 if let Ok(cache) = parse_cache().lock()
177 && let Some(cached) = cache.get(uri)
178 && cached.hash == hash
179 {
180 return Ok((cached.tokens.clone(), cached.program.clone()));
181 }
182
183 let (tokens, program) = tokenize_and_parse(text)?;
184 if let Ok(mut cache) = parse_cache().lock() {
185 cache.insert(
186 uri.clone(),
187 CachedParse {
188 hash,
189 tokens: tokens.clone(),
190 program: program.clone(),
191 },
192 );
193 }
194 Ok((tokens, program))
195}
196
197#[derive(Clone)]
198struct ImportLibraryInfo {
199 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
200 path: Option<PathBuf>,
201 value_map: HashMap<rexlang_ast::expr::Symbol, rexlang_ast::expr::Symbol>, type_map: HashMap<rexlang_ast::expr::Symbol, rexlang_ast::expr::Symbol>,
203 class_map: HashMap<rexlang_ast::expr::Symbol, rexlang_ast::expr::Symbol>,
204 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
205 export_defs: HashMap<String, Span>,
206}
207
208fn is_ident_like(name: &str) -> bool {
209 let mut chars = name.chars();
210 let Some(first) = chars.next() else {
211 return false;
212 };
213 if !(first.is_ascii_alphabetic() || first == '_') {
214 return false;
215 }
216 chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
217}
218
219fn prelude_completion_values() -> &'static Vec<(String, CompletionItemKind)> {
220 static PRELUDE_VALUES: OnceLock<Vec<(String, CompletionItemKind)>> = OnceLock::new();
221 PRELUDE_VALUES.get_or_init(|| {
222 let ts = match TypeSystem::with_prelude() {
223 Ok(ts) => ts,
224 Err(e) => {
225 eprintln!("rexlang-lsp: failed to build prelude for completions: {e}");
226 return Vec::new();
227 }
228 };
229 let mut out = Vec::new();
230 for (name, schemes) in ts.env.values.iter() {
231 let name = name.as_ref().to_string();
232 if !is_ident_like(&name) {
233 continue;
234 }
235 let is_fun = schemes
236 .iter()
237 .any(|scheme| matches!(scheme.typ.as_ref(), TypeKind::Fun(..)));
238 let kind = if is_fun {
239 CompletionItemKind::FUNCTION
240 } else {
241 CompletionItemKind::VARIABLE
242 };
243 out.push((name, kind));
244 }
245 out.sort_by(|(a, _), (b, _)| a.cmp(b));
246 out
247 })
248}
249
250fn library_prefix(hash: &str) -> String {
251 let short = if hash.len() >= 16 { &hash[..16] } else { hash };
252 format!("@m{short}")
253}
254
255fn inject_program_decls(
256 ts: &mut TypeSystem,
257 program: &Program,
258 want_prepared_instance: Option<usize>,
259) -> std::result::Result<InjectedDecls, TsTypeError> {
260 let mut instances = Vec::new();
261 let mut prepared_target = None;
262 let mut pending_non_instances: Vec<Decl> = Vec::new();
263
264 let flush_non_instances =
265 |ts: &mut TypeSystem, pending: &mut Vec<Decl>| -> std::result::Result<(), TsTypeError> {
266 if pending.is_empty() {
267 return Ok(());
268 }
269 ts.inject_decls(pending)?;
270 pending.clear();
271 Ok(())
272 };
273
274 for (idx, decl) in program.decls.iter().enumerate() {
275 match decl {
276 Decl::Instance(inst_decl) => {
277 flush_non_instances(ts, &mut pending_non_instances)?;
278 let prepared = ts.inject_instance_decl(inst_decl)?;
279 if want_prepared_instance.is_some_and(|want| want == idx) {
280 prepared_target = Some(prepared.clone());
281 }
282 instances.push((idx, prepared));
283 }
284 _ => pending_non_instances.push(decl.clone()),
285 }
286 }
287 flush_non_instances(ts, &mut pending_non_instances)?;
288
289 Ok((instances, prepared_target))
290}
291
292type PreparedInstanceDecl = rexlang_typesystem::PreparedInstanceDecl;
293type PreparedInstance = (usize, PreparedInstanceDecl);
294type InjectedDecls = (Vec<PreparedInstance>, Option<PreparedInstanceDecl>);
295
296fn rewrite_type_expr(
297 ty: &TypeExpr,
298 type_map: &HashMap<rexlang_ast::expr::Symbol, rexlang_ast::expr::Symbol>,
299) -> TypeExpr {
300 match ty {
301 TypeExpr::Name(span, name) => {
302 if let Some(new) = type_map.get(&name.to_dotted_symbol()) {
303 TypeExpr::Name(*span, rexlang_ast::expr::NameRef::Unqualified(new.clone()))
304 } else {
305 TypeExpr::Name(*span, name.clone())
306 }
307 }
308 TypeExpr::App(span, f, x) => TypeExpr::App(
309 *span,
310 Box::new(rewrite_type_expr(f, type_map)),
311 Box::new(rewrite_type_expr(x, type_map)),
312 ),
313 TypeExpr::Fun(span, a, b) => TypeExpr::Fun(
314 *span,
315 Box::new(rewrite_type_expr(a, type_map)),
316 Box::new(rewrite_type_expr(b, type_map)),
317 ),
318 TypeExpr::Tuple(span, elems) => TypeExpr::Tuple(
319 *span,
320 elems
321 .iter()
322 .map(|e| rewrite_type_expr(e, type_map))
323 .collect(),
324 ),
325 TypeExpr::Record(span, fields) => TypeExpr::Record(
326 *span,
327 fields
328 .iter()
329 .map(|(name, ty)| (name.clone(), rewrite_type_expr(ty, type_map)))
330 .collect(),
331 ),
332 }
333}
334
335fn collect_pattern_bindings(pat: &Pattern, out: &mut Vec<rexlang_ast::expr::Symbol>) {
336 match pat {
337 Pattern::Wildcard(..) => {}
338 Pattern::Var(v) => out.push(v.name.clone()),
339 Pattern::Named(_, _, args) => {
340 for arg in args {
341 collect_pattern_bindings(arg, out);
342 }
343 }
344 Pattern::Tuple(_, elems) | Pattern::List(_, elems) => {
345 for elem in elems {
346 collect_pattern_bindings(elem, out);
347 }
348 }
349 Pattern::Cons(_, head, tail) => {
350 collect_pattern_bindings(head, out);
351 collect_pattern_bindings(tail, out);
352 }
353 Pattern::Dict(_, fields) => {
354 for (_, pat) in fields {
355 collect_pattern_bindings(pat, out);
356 }
357 }
358 }
359}
360
361fn rewrite_import_projections_expr(
362 expr: &Expr,
363 bound: &mut BTreeSet<rexlang_ast::expr::Symbol>,
364 imports: &HashMap<rexlang_ast::expr::Symbol, ImportLibraryInfo>,
365 diagnostics: &mut Vec<Diagnostic>,
366) -> Expr {
367 match expr {
368 Expr::Project(span, base, field) => {
369 if let Expr::Var(v) = base.as_ref()
370 && !bound.contains(&v.name)
371 && let Some(info) = imports.get(&v.name)
372 {
373 if let Some(internal) = info.value_map.get(field) {
374 return Expr::Var(Var {
375 span: *span,
376 name: internal.clone(),
377 });
378 }
379 diagnostics.push(diagnostic_for_span(
380 *span,
381 format!("library `{}` does not export `{}`", v.name, field),
382 ));
383 }
384 Expr::Project(
385 *span,
386 std::sync::Arc::new(rewrite_import_projections_expr(
387 base,
388 bound,
389 imports,
390 diagnostics,
391 )),
392 field.clone(),
393 )
394 }
395 Expr::Var(v) => Expr::Var(v.clone()),
396 Expr::Bool(span, v) => Expr::Bool(*span, *v),
397 Expr::Uint(span, v) => Expr::Uint(*span, *v),
398 Expr::Int(span, v) => Expr::Int(*span, *v),
399 Expr::Float(span, v) => Expr::Float(*span, *v),
400 Expr::String(span, v) => Expr::String(*span, v.clone()),
401 Expr::Uuid(span, v) => Expr::Uuid(*span, *v),
402 Expr::DateTime(span, v) => Expr::DateTime(*span, *v),
403 Expr::Hole(span) => Expr::Hole(*span),
404 Expr::Tuple(span, elems) => Expr::Tuple(
405 *span,
406 elems
407 .iter()
408 .map(|e| {
409 std::sync::Arc::new(rewrite_import_projections_expr(
410 e,
411 bound,
412 imports,
413 diagnostics,
414 ))
415 })
416 .collect(),
417 ),
418 Expr::List(span, elems) => Expr::List(
419 *span,
420 elems
421 .iter()
422 .map(|e| {
423 std::sync::Arc::new(rewrite_import_projections_expr(
424 e,
425 bound,
426 imports,
427 diagnostics,
428 ))
429 })
430 .collect(),
431 ),
432 Expr::Dict(span, kvs) => Expr::Dict(
433 *span,
434 kvs.iter()
435 .map(|(k, v)| {
436 (
437 k.clone(),
438 std::sync::Arc::new(rewrite_import_projections_expr(
439 v,
440 bound,
441 imports,
442 diagnostics,
443 )),
444 )
445 })
446 .collect(),
447 ),
448 Expr::RecordUpdate(span, base, updates) => Expr::RecordUpdate(
449 *span,
450 std::sync::Arc::new(rewrite_import_projections_expr(
451 base,
452 bound,
453 imports,
454 diagnostics,
455 )),
456 updates
457 .iter()
458 .map(|(k, v)| {
459 (
460 k.clone(),
461 std::sync::Arc::new(rewrite_import_projections_expr(
462 v,
463 bound,
464 imports,
465 diagnostics,
466 )),
467 )
468 })
469 .collect(),
470 ),
471 Expr::App(span, f, x) => Expr::App(
472 *span,
473 std::sync::Arc::new(rewrite_import_projections_expr(
474 f,
475 bound,
476 imports,
477 diagnostics,
478 )),
479 std::sync::Arc::new(rewrite_import_projections_expr(
480 x,
481 bound,
482 imports,
483 diagnostics,
484 )),
485 ),
486 Expr::Lam(span, scope, param, ann, constraints, body) => {
487 let ann = ann
488 .as_ref()
489 .map(|t| rewrite_import_projections_type_expr(t, bound, imports));
490 let constraints = constraints
491 .iter()
492 .map(|c| TypeConstraint {
493 class: rewrite_import_projections_class_name(&c.class, bound, imports),
494 typ: rewrite_import_projections_type_expr(&c.typ, bound, imports),
495 })
496 .collect();
497 bound.insert(param.name.clone());
498 let out = Expr::Lam(
499 *span,
500 scope.clone(),
501 param.clone(),
502 ann,
503 constraints,
504 std::sync::Arc::new(rewrite_import_projections_expr(
505 body,
506 bound,
507 imports,
508 diagnostics,
509 )),
510 );
511 bound.remove(¶m.name);
512 out
513 }
514 Expr::Let(span, var, ann, val, body) => {
515 let val = std::sync::Arc::new(rewrite_import_projections_expr(
516 val,
517 bound,
518 imports,
519 diagnostics,
520 ));
521 bound.insert(var.name.clone());
522 let body = std::sync::Arc::new(rewrite_import_projections_expr(
523 body,
524 bound,
525 imports,
526 diagnostics,
527 ));
528 bound.remove(&var.name);
529 Expr::Let(
530 *span,
531 var.clone(),
532 ann.as_ref()
533 .map(|t| rewrite_import_projections_type_expr(t, bound, imports)),
534 val,
535 body,
536 )
537 }
538 Expr::LetRec(span, bindings, body) => {
539 let anns: Vec<Option<TypeExpr>> = bindings
540 .iter()
541 .map(|(_, ann, _)| {
542 ann.as_ref()
543 .map(|t| rewrite_import_projections_type_expr(t, bound, imports))
544 })
545 .collect();
546 let names: Vec<Symbol> = bindings
547 .iter()
548 .map(|(var, _, _)| var.name.clone())
549 .collect();
550 for name in &names {
551 bound.insert(name.clone());
552 }
553 let bindings = bindings
554 .iter()
555 .zip(anns)
556 .map(|((var, _ann, def), ann)| {
557 (
558 var.clone(),
559 ann,
560 std::sync::Arc::new(rewrite_import_projections_expr(
561 def,
562 bound,
563 imports,
564 diagnostics,
565 )),
566 )
567 })
568 .collect();
569 let body = std::sync::Arc::new(rewrite_import_projections_expr(
570 body,
571 bound,
572 imports,
573 diagnostics,
574 ));
575 for name in &names {
576 bound.remove(name);
577 }
578 Expr::LetRec(*span, bindings, body)
579 }
580 Expr::Ite(span, c, t, e) => Expr::Ite(
581 *span,
582 std::sync::Arc::new(rewrite_import_projections_expr(
583 c,
584 bound,
585 imports,
586 diagnostics,
587 )),
588 std::sync::Arc::new(rewrite_import_projections_expr(
589 t,
590 bound,
591 imports,
592 diagnostics,
593 )),
594 std::sync::Arc::new(rewrite_import_projections_expr(
595 e,
596 bound,
597 imports,
598 diagnostics,
599 )),
600 ),
601 Expr::Match(span, scrutinee, arms) => {
602 let scrutinee = std::sync::Arc::new(rewrite_import_projections_expr(
603 scrutinee,
604 bound,
605 imports,
606 diagnostics,
607 ));
608 let mut out_arms = Vec::new();
609 for (pat, arm_expr) in arms {
610 let mut binds = Vec::new();
611 collect_pattern_bindings(pat, &mut binds);
612 for b in &binds {
613 bound.insert(b.clone());
614 }
615 let arm_expr = std::sync::Arc::new(rewrite_import_projections_expr(
616 arm_expr,
617 bound,
618 imports,
619 diagnostics,
620 ));
621 for b in &binds {
622 bound.remove(b);
623 }
624 out_arms.push((pat.clone(), arm_expr));
625 }
626 Expr::Match(*span, scrutinee, out_arms)
627 }
628 Expr::Ann(span, e, t) => Expr::Ann(
629 *span,
630 std::sync::Arc::new(rewrite_import_projections_expr(
631 e,
632 bound,
633 imports,
634 diagnostics,
635 )),
636 rewrite_import_projections_type_expr(t, bound, imports),
637 ),
638 }
639}
640
641fn qualified_alias_member(
642 name: &rexlang_ast::expr::NameRef,
643) -> Option<(&rexlang_ast::expr::Symbol, &rexlang_ast::expr::Symbol)> {
644 match name {
645 rexlang_ast::expr::NameRef::Qualified(_, segments) if segments.len() == 2 => {
646 Some((&segments[0], &segments[1]))
647 }
648 _ => None,
649 }
650}
651
652fn rewrite_import_projections_class_name(
653 class: &rexlang_ast::expr::NameRef,
654 bound: &BTreeSet<rexlang_ast::expr::Symbol>,
655 imports: &HashMap<rexlang_ast::expr::Symbol, ImportLibraryInfo>,
656) -> rexlang_ast::expr::NameRef {
657 let Some((alias, member)) = qualified_alias_member(class) else {
658 return class.clone();
659 };
660 if bound.contains(alias) {
661 return class.clone();
662 }
663 let Some(info) = imports.get(alias) else {
664 return class.clone();
665 };
666 info.class_map
667 .get(member)
668 .map(|s| rexlang_ast::expr::NameRef::Unqualified(s.clone()))
669 .unwrap_or_else(|| class.clone())
670}
671
672fn rewrite_import_projections_type_expr(
673 ty: &TypeExpr,
674 bound: &BTreeSet<rexlang_ast::expr::Symbol>,
675 imports: &HashMap<rexlang_ast::expr::Symbol, ImportLibraryInfo>,
676) -> TypeExpr {
677 match ty {
678 TypeExpr::Name(span, name) => {
679 let Some((alias, member)) = qualified_alias_member(name) else {
680 return TypeExpr::Name(*span, name.clone());
681 };
682 if bound.contains(alias) {
683 return TypeExpr::Name(*span, name.clone());
684 }
685 let Some(info) = imports.get(alias) else {
686 return TypeExpr::Name(*span, name.clone());
687 };
688 if let Some(new) = info.type_map.get(member) {
689 TypeExpr::Name(*span, rexlang_ast::expr::NameRef::Unqualified(new.clone()))
690 } else if let Some(new) = info.class_map.get(member) {
691 TypeExpr::Name(*span, rexlang_ast::expr::NameRef::Unqualified(new.clone()))
692 } else {
693 TypeExpr::Name(*span, name.clone())
694 }
695 }
696 TypeExpr::App(span, f, x) => TypeExpr::App(
697 *span,
698 Box::new(rewrite_import_projections_type_expr(f, bound, imports)),
699 Box::new(rewrite_import_projections_type_expr(x, bound, imports)),
700 ),
701 TypeExpr::Fun(span, a, b) => TypeExpr::Fun(
702 *span,
703 Box::new(rewrite_import_projections_type_expr(a, bound, imports)),
704 Box::new(rewrite_import_projections_type_expr(b, bound, imports)),
705 ),
706 TypeExpr::Tuple(span, elems) => TypeExpr::Tuple(
707 *span,
708 elems
709 .iter()
710 .map(|e| rewrite_import_projections_type_expr(e, bound, imports))
711 .collect(),
712 ),
713 TypeExpr::Record(span, fields) => TypeExpr::Record(
714 *span,
715 fields
716 .iter()
717 .map(|(name, t)| {
718 (
719 name.clone(),
720 rewrite_import_projections_type_expr(t, bound, imports),
721 )
722 })
723 .collect(),
724 ),
725 }
726}
727
728fn rewrite_program_import_projections(
729 program: &Program,
730 imports: &HashMap<rexlang_ast::expr::Symbol, ImportLibraryInfo>,
731 diagnostics: &mut Vec<Diagnostic>,
732) -> Program {
733 let decl_bound = BTreeSet::new();
734 let decls = program
735 .decls
736 .iter()
737 .map(|decl| match decl {
738 Decl::Fn(fd) => {
739 let mut bound: BTreeSet<rexlang_ast::expr::Symbol> =
740 fd.params.iter().map(|(v, _)| v.name.clone()).collect();
741 let body = std::sync::Arc::new(rewrite_import_projections_expr(
742 fd.body.as_ref(),
743 &mut bound,
744 imports,
745 diagnostics,
746 ));
747 Decl::Fn(FnDecl {
748 span: fd.span,
749 is_pub: fd.is_pub,
750 name: fd.name.clone(),
751 params: fd
752 .params
753 .iter()
754 .map(|(v, t)| {
755 (
756 v.clone(),
757 rewrite_import_projections_type_expr(t, &decl_bound, imports),
758 )
759 })
760 .collect(),
761 ret: rewrite_import_projections_type_expr(&fd.ret, &decl_bound, imports),
762 constraints: fd
763 .constraints
764 .iter()
765 .map(|c| TypeConstraint {
766 class: rewrite_import_projections_class_name(
767 &c.class,
768 &decl_bound,
769 imports,
770 ),
771 typ: rewrite_import_projections_type_expr(&c.typ, &decl_bound, imports),
772 })
773 .collect(),
774 body,
775 })
776 }
777 Decl::DeclareFn(df) => Decl::DeclareFn(DeclareFnDecl {
778 span: df.span,
779 is_pub: df.is_pub,
780 name: df.name.clone(),
781 params: df
782 .params
783 .iter()
784 .map(|(v, t)| {
785 (
786 v.clone(),
787 rewrite_import_projections_type_expr(t, &decl_bound, imports),
788 )
789 })
790 .collect(),
791 ret: rewrite_import_projections_type_expr(&df.ret, &decl_bound, imports),
792 constraints: df
793 .constraints
794 .iter()
795 .map(|c| TypeConstraint {
796 class: rewrite_import_projections_class_name(
797 &c.class,
798 &decl_bound,
799 imports,
800 ),
801 typ: rewrite_import_projections_type_expr(&c.typ, &decl_bound, imports),
802 })
803 .collect(),
804 }),
805 Decl::Type(td) => Decl::Type(TypeDecl {
806 span: td.span,
807 is_pub: td.is_pub,
808 name: td.name.clone(),
809 params: td.params.clone(),
810 variants: td
811 .variants
812 .iter()
813 .map(|v| rexlang_ast::expr::TypeVariant {
814 name: v.name.clone(),
815 args: v
816 .args
817 .iter()
818 .map(|t| rewrite_import_projections_type_expr(t, &decl_bound, imports))
819 .collect(),
820 })
821 .collect(),
822 }),
823 Decl::Class(cd) => Decl::Class(rexlang_ast::expr::ClassDecl {
824 span: cd.span,
825 is_pub: cd.is_pub,
826 name: cd.name.clone(),
827 params: cd.params.clone(),
828 supers: cd
829 .supers
830 .iter()
831 .map(|c| TypeConstraint {
832 class: rewrite_import_projections_class_name(
833 &c.class,
834 &decl_bound,
835 imports,
836 ),
837 typ: rewrite_import_projections_type_expr(&c.typ, &decl_bound, imports),
838 })
839 .collect(),
840 methods: cd
841 .methods
842 .iter()
843 .map(|m| rexlang_ast::expr::ClassMethodSig {
844 name: m.name.clone(),
845 typ: rewrite_import_projections_type_expr(&m.typ, &decl_bound, imports),
846 })
847 .collect(),
848 }),
849 Decl::Instance(inst) => {
850 let methods = inst
851 .methods
852 .iter()
853 .map(|m| {
854 let mut bound = BTreeSet::new();
855 let body = std::sync::Arc::new(rewrite_import_projections_expr(
856 m.body.as_ref(),
857 &mut bound,
858 imports,
859 diagnostics,
860 ));
861 rexlang_ast::expr::InstanceMethodImpl {
862 name: m.name.clone(),
863 body,
864 }
865 })
866 .collect();
867 Decl::Instance(InstanceDecl {
868 span: inst.span,
869 is_pub: inst.is_pub,
870 class: rewrite_import_projections_class_name(
871 &rexlang_ast::expr::NameRef::from_dotted(inst.class.as_ref()),
872 &decl_bound,
873 imports,
874 )
875 .to_dotted_symbol(),
876 head: rewrite_import_projections_type_expr(&inst.head, &decl_bound, imports),
877 context: inst
878 .context
879 .iter()
880 .map(|c| TypeConstraint {
881 class: rewrite_import_projections_class_name(
882 &c.class,
883 &decl_bound,
884 imports,
885 ),
886 typ: rewrite_import_projections_type_expr(&c.typ, &decl_bound, imports),
887 })
888 .collect(),
889 methods,
890 })
891 }
892 other => other.clone(),
893 })
894 .collect();
895
896 let mut bound = BTreeSet::new();
897 let expr = std::sync::Arc::new(rewrite_import_projections_expr(
898 program.expr.as_ref(),
899 &mut bound,
900 imports,
901 diagnostics,
902 ));
903
904 Program { decls, expr }
905}
906
907fn validate_import_projection_class_name(
908 class: &rexlang_ast::expr::NameRef,
909 span: Span,
910 bound: &BTreeSet<rexlang_ast::expr::Symbol>,
911 imports: &HashMap<rexlang_ast::expr::Symbol, ImportLibraryInfo>,
912 diagnostics: &mut Vec<Diagnostic>,
913) {
914 let Some((alias, member)) = qualified_alias_member(class) else {
915 return;
916 };
917 if bound.contains(alias) {
918 return;
919 }
920 let Some(info) = imports.get(alias) else {
921 return;
922 };
923 if info.class_map.contains_key(member) {
924 return;
925 }
926 diagnostics.push(diagnostic_for_span(
927 span,
928 format!("library `{alias}` does not export `{member}`"),
929 ));
930}
931
932fn validate_import_projection_type_expr(
933 ty: &TypeExpr,
934 bound: &BTreeSet<rexlang_ast::expr::Symbol>,
935 imports: &HashMap<rexlang_ast::expr::Symbol, ImportLibraryInfo>,
936 diagnostics: &mut Vec<Diagnostic>,
937) {
938 match ty {
939 TypeExpr::Name(span, name) => {
940 let Some((alias, member)) = qualified_alias_member(name) else {
941 return;
942 };
943 if bound.contains(alias) {
944 return;
945 }
946 let Some(info) = imports.get(alias) else {
947 return;
948 };
949 if info.type_map.contains_key(member) || info.class_map.contains_key(member) {
950 return;
951 }
952 diagnostics.push(diagnostic_for_span(
953 *span,
954 format!("library `{alias}` does not export `{member}`"),
955 ));
956 }
957 TypeExpr::App(_, f, x) => {
958 validate_import_projection_type_expr(f, bound, imports, diagnostics);
959 validate_import_projection_type_expr(x, bound, imports, diagnostics);
960 }
961 TypeExpr::Fun(_, a, b) => {
962 validate_import_projection_type_expr(a, bound, imports, diagnostics);
963 validate_import_projection_type_expr(b, bound, imports, diagnostics);
964 }
965 TypeExpr::Tuple(_, elems) => {
966 for e in elems {
967 validate_import_projection_type_expr(e, bound, imports, diagnostics);
968 }
969 }
970 TypeExpr::Record(_, fields) => {
971 for (_, t) in fields {
972 validate_import_projection_type_expr(t, bound, imports, diagnostics);
973 }
974 }
975 }
976}
977
978fn validate_import_projection_expr(
979 expr: &Expr,
980 bound: &mut BTreeSet<rexlang_ast::expr::Symbol>,
981 imports: &HashMap<rexlang_ast::expr::Symbol, ImportLibraryInfo>,
982 diagnostics: &mut Vec<Diagnostic>,
983) {
984 match expr {
985 Expr::Lam(_, _, param, ann, constraints, body) => {
986 if let Some(ann) = ann {
987 validate_import_projection_type_expr(ann, bound, imports, diagnostics);
988 }
989 for c in constraints {
990 validate_import_projection_class_name(
991 &c.class,
992 *c.typ.span(),
993 bound,
994 imports,
995 diagnostics,
996 );
997 validate_import_projection_type_expr(&c.typ, bound, imports, diagnostics);
998 }
999 bound.insert(param.name.clone());
1000 validate_import_projection_expr(body, bound, imports, diagnostics);
1001 bound.remove(¶m.name);
1002 }
1003 Expr::Let(_, var, ann, val, body) => {
1004 if let Some(ann) = ann {
1005 validate_import_projection_type_expr(ann, bound, imports, diagnostics);
1006 }
1007 validate_import_projection_expr(val, bound, imports, diagnostics);
1008 bound.insert(var.name.clone());
1009 validate_import_projection_expr(body, bound, imports, diagnostics);
1010 bound.remove(&var.name);
1011 }
1012 Expr::LetRec(_, bindings, body) => {
1013 for (_, ann, _) in bindings {
1014 if let Some(ann) = ann {
1015 validate_import_projection_type_expr(ann, bound, imports, diagnostics);
1016 }
1017 }
1018 let names: Vec<_> = bindings
1019 .iter()
1020 .map(|(var, _, _)| var.name.clone())
1021 .collect();
1022 for name in &names {
1023 bound.insert(name.clone());
1024 }
1025 for (_, _ann, def) in bindings {
1026 validate_import_projection_expr(def, bound, imports, diagnostics);
1027 }
1028 validate_import_projection_expr(body, bound, imports, diagnostics);
1029 for name in &names {
1030 bound.remove(name);
1031 }
1032 }
1033 Expr::Match(_, scrutinee, arms) => {
1034 validate_import_projection_expr(scrutinee, bound, imports, diagnostics);
1035 for (pat, arm_expr) in arms {
1036 let mut binds = Vec::new();
1037 collect_pattern_bindings(pat, &mut binds);
1038 for b in &binds {
1039 bound.insert(b.clone());
1040 }
1041 validate_import_projection_expr(arm_expr, bound, imports, diagnostics);
1042 for b in &binds {
1043 bound.remove(b);
1044 }
1045 }
1046 }
1047 Expr::Tuple(_, elems) | Expr::List(_, elems) => {
1048 for e in elems {
1049 validate_import_projection_expr(e, bound, imports, diagnostics);
1050 }
1051 }
1052 Expr::Dict(_, kvs) => {
1053 for v in kvs.values() {
1054 validate_import_projection_expr(v, bound, imports, diagnostics);
1055 }
1056 }
1057 Expr::RecordUpdate(_, base, updates) => {
1058 validate_import_projection_expr(base, bound, imports, diagnostics);
1059 for v in updates.values() {
1060 validate_import_projection_expr(v, bound, imports, diagnostics);
1061 }
1062 }
1063 Expr::App(_, f, x) => {
1064 validate_import_projection_expr(f, bound, imports, diagnostics);
1065 validate_import_projection_expr(x, bound, imports, diagnostics);
1066 }
1067 Expr::Ite(_, c, t, e) => {
1068 validate_import_projection_expr(c, bound, imports, diagnostics);
1069 validate_import_projection_expr(t, bound, imports, diagnostics);
1070 validate_import_projection_expr(e, bound, imports, diagnostics);
1071 }
1072 Expr::Ann(_, e, t) => {
1073 validate_import_projection_expr(e, bound, imports, diagnostics);
1074 validate_import_projection_type_expr(t, bound, imports, diagnostics);
1075 }
1076 Expr::Project(_, base, _) => {
1077 validate_import_projection_expr(base, bound, imports, diagnostics);
1078 }
1079 Expr::Var(..)
1080 | Expr::Bool(..)
1081 | Expr::Uint(..)
1082 | Expr::Int(..)
1083 | Expr::Float(..)
1084 | Expr::String(..)
1085 | Expr::Uuid(..)
1086 | Expr::DateTime(..)
1087 | Expr::Hole(..) => {}
1088 }
1089}
1090
1091fn validate_import_projection_uses(
1092 program: &Program,
1093 imports: &HashMap<rexlang_ast::expr::Symbol, ImportLibraryInfo>,
1094 diagnostics: &mut Vec<Diagnostic>,
1095) {
1096 let decl_bound = BTreeSet::new();
1097 for decl in &program.decls {
1098 match decl {
1099 Decl::Fn(fd) => {
1100 for (_, t) in &fd.params {
1101 validate_import_projection_type_expr(t, &decl_bound, imports, diagnostics);
1102 }
1103 validate_import_projection_type_expr(&fd.ret, &decl_bound, imports, diagnostics);
1104 for c in &fd.constraints {
1105 validate_import_projection_class_name(
1106 &c.class,
1107 *c.typ.span(),
1108 &decl_bound,
1109 imports,
1110 diagnostics,
1111 );
1112 validate_import_projection_type_expr(&c.typ, &decl_bound, imports, diagnostics);
1113 }
1114 let mut bound: BTreeSet<rexlang_ast::expr::Symbol> =
1115 fd.params.iter().map(|(v, _)| v.name.clone()).collect();
1116 validate_import_projection_expr(fd.body.as_ref(), &mut bound, imports, diagnostics);
1117 }
1118 Decl::DeclareFn(df) => {
1119 for (_, t) in &df.params {
1120 validate_import_projection_type_expr(t, &decl_bound, imports, diagnostics);
1121 }
1122 validate_import_projection_type_expr(&df.ret, &decl_bound, imports, diagnostics);
1123 for c in &df.constraints {
1124 validate_import_projection_class_name(
1125 &c.class,
1126 *c.typ.span(),
1127 &decl_bound,
1128 imports,
1129 diagnostics,
1130 );
1131 validate_import_projection_type_expr(&c.typ, &decl_bound, imports, diagnostics);
1132 }
1133 }
1134 Decl::Type(td) => {
1135 for v in &td.variants {
1136 for t in &v.args {
1137 validate_import_projection_type_expr(t, &decl_bound, imports, diagnostics);
1138 }
1139 }
1140 }
1141 Decl::Class(cd) => {
1142 for c in &cd.supers {
1143 validate_import_projection_class_name(
1144 &c.class,
1145 *c.typ.span(),
1146 &decl_bound,
1147 imports,
1148 diagnostics,
1149 );
1150 validate_import_projection_type_expr(&c.typ, &decl_bound, imports, diagnostics);
1151 }
1152 for m in &cd.methods {
1153 validate_import_projection_type_expr(&m.typ, &decl_bound, imports, diagnostics);
1154 }
1155 }
1156 Decl::Instance(inst) => {
1157 validate_import_projection_class_name(
1158 &rexlang_ast::expr::NameRef::from_dotted(inst.class.as_ref()),
1159 inst.span,
1160 &decl_bound,
1161 imports,
1162 diagnostics,
1163 );
1164 validate_import_projection_type_expr(&inst.head, &decl_bound, imports, diagnostics);
1165 for c in &inst.context {
1166 validate_import_projection_class_name(
1167 &c.class,
1168 *c.typ.span(),
1169 &decl_bound,
1170 imports,
1171 diagnostics,
1172 );
1173 validate_import_projection_type_expr(&c.typ, &decl_bound, imports, diagnostics);
1174 }
1175 for m in &inst.methods {
1176 let mut bound = BTreeSet::new();
1177 validate_import_projection_expr(
1178 m.body.as_ref(),
1179 &mut bound,
1180 imports,
1181 diagnostics,
1182 );
1183 }
1184 }
1185 Decl::Import(..) => {}
1186 }
1187 }
1188 let mut bound = BTreeSet::new();
1189 validate_import_projection_expr(program.expr.as_ref(), &mut bound, imports, diagnostics);
1190}
1191
1192type PreparedProgram = (
1193 Program,
1194 TypeSystem,
1195 HashMap<rexlang_ast::expr::Symbol, ImportLibraryInfo>,
1196 Vec<Diagnostic>,
1197);
1198
1199fn prepare_program_with_imports(
1200 uri: &Url,
1201 program: &Program,
1202) -> std::result::Result<PreparedProgram, String> {
1203 let mut ts = TypeSystem::with_prelude().map_err(|e| format!("failed to build prelude: {e}"))?;
1204 let mut diagnostics = Vec::new();
1205
1206 let importer = uri_to_file_path(uri);
1207
1208 let mut imports: HashMap<rexlang_ast::expr::Symbol, ImportLibraryInfo> = HashMap::new();
1209
1210 for decl in &program.decls {
1211 let Decl::Import(ImportDecl {
1212 span, path, alias, ..
1213 }) = decl
1214 else {
1215 continue;
1216 };
1217 let import_span = *span;
1218
1219 let (segments, expected_sha) = match path {
1220 ImportPath::Local { segments, sha } => (segments.as_slice(), sha.as_deref()),
1221 ImportPath::Remote { .. } => {
1222 continue;
1224 }
1225 };
1226
1227 let library_name = segments
1228 .iter()
1229 .map(|s| s.as_ref())
1230 .collect::<Vec<_>>()
1231 .join(".");
1232
1233 let (library_path, hash, source, library_label, keep_constraints) = if let Some(source) =
1234 rexlang_util::stdlib_source(&library_name)
1235 {
1236 let hash = sha256_hex(source.as_bytes());
1237 if let Some(expected) = expected_sha {
1238 let expected = expected.to_ascii_lowercase();
1239 if !hash.starts_with(&expected) {
1240 diagnostics.push(diagnostic_for_span(
1241 import_span,
1242 format!(
1243 "sha mismatch for `{library_name}`: expected #{expected}, got #{hash}",
1244 ),
1245 ));
1246 }
1247 }
1248 (None, hash, source.to_string(), library_name, true)
1249 } else {
1250 let Some(importer) = importer.as_ref() else {
1251 continue;
1254 };
1255 let Some(base_dir) = importer.parent() else {
1256 diagnostics.push(diagnostic_for_span(
1257 import_span,
1258 "cannot resolve local import without a base directory".to_string(),
1259 ));
1260 continue;
1261 };
1262 let library_path = match rexlang_util::resolve_local_import_path(base_dir, segments) {
1263 Ok(Some(p)) => p,
1264 Ok(None) => {
1265 diagnostics.push(diagnostic_for_span(
1266 import_span,
1267 format!("library not found for import `{library_name}`"),
1268 ));
1269 continue;
1270 }
1271 Err(err) => {
1272 diagnostics.push(diagnostic_for_span(import_span, err.to_string()));
1273 continue;
1274 }
1275 };
1276 let Ok(library_path) = library_path.canonicalize() else {
1277 diagnostics.push(diagnostic_for_span(
1278 import_span,
1279 format!("library not found for import `{library_name}`"),
1280 ));
1281 continue;
1282 };
1283
1284 let bytes = match fs::read(&library_path) {
1285 Ok(b) => b,
1286 Err(e) => {
1287 diagnostics.push(diagnostic_for_span(
1288 import_span,
1289 format!("failed to read library `{}`: {e}", library_path.display()),
1290 ));
1291 continue;
1292 }
1293 };
1294 let hash = sha256_hex(&bytes);
1295 if let Some(expected) = expected_sha {
1296 let expected = expected.to_ascii_lowercase();
1297 if !hash.starts_with(&expected) {
1298 diagnostics.push(diagnostic_for_span(
1299 import_span,
1300 format!(
1301 "sha mismatch for `{}`: expected #{expected}, got #{hash}",
1302 library_path.display()
1303 ),
1304 ));
1305 }
1306 }
1307
1308 let source = match String::from_utf8(bytes) {
1309 Ok(s) => s,
1310 Err(e) => {
1311 diagnostics.push(diagnostic_for_span(
1312 import_span,
1313 format!("library `{}` is not utf-8: {e}", library_path.display()),
1314 ));
1315 continue;
1316 }
1317 };
1318 (
1319 Some(library_path.clone()),
1320 hash,
1321 source,
1322 library_path.display().to_string(),
1323 false,
1324 )
1325 };
1326
1327 let (tokens, library_program) = match tokenize_and_parse(&source) {
1328 Ok(v) => v,
1329 Err(TokenizeOrParseError::Lex(err)) => {
1330 let msg = match err {
1331 LexicalError::UnexpectedToken(span) => format!(
1332 "lex error in library `{}` at {}:{}",
1333 library_label, span.begin.line, span.begin.column
1334 ),
1335 LexicalError::InvalidLiteral {
1336 kind,
1337 text,
1338 error,
1339 span,
1340 } => format!(
1341 "lex error in library `{}` at {}:{}: invalid {kind} literal `{text}`: {error}",
1342 library_label, span.begin.line, span.begin.column
1343 ),
1344 LexicalError::Internal(msg) => {
1345 format!("internal lexer error in library `{library_label}`: {msg}")
1346 }
1347 };
1348 diagnostics.push(diagnostic_for_span(import_span, msg));
1349 continue;
1350 }
1351 Err(TokenizeOrParseError::Parse(errs)) => {
1352 for err in errs {
1353 diagnostics.push(diagnostic_for_span(
1354 import_span,
1355 format!(
1356 "parse error in library `{}` at {}:{}: {}",
1357 library_label, err.span.begin.line, err.span.begin.column, err.message
1358 ),
1359 ));
1360 if diagnostics.len() >= MAX_DIAGNOSTICS {
1361 break;
1362 }
1363 }
1364 continue;
1365 }
1366 };
1367
1368 let index = index_decl_spans(&library_program, &tokens);
1369 let prefix = library_prefix(&hash);
1370
1371 let mut type_map: HashMap<rexlang_ast::expr::Symbol, rexlang_ast::expr::Symbol> =
1372 HashMap::new();
1373 let mut class_map: HashMap<rexlang_ast::expr::Symbol, rexlang_ast::expr::Symbol> =
1374 HashMap::new();
1375 for decl in &library_program.decls {
1376 match decl {
1377 Decl::Type(td) => {
1378 type_map.insert(
1379 td.name.clone(),
1380 intern(&format!("{prefix}.{}", td.name.as_ref())),
1381 );
1382 }
1383 Decl::Class(cd) => {
1384 class_map.insert(
1385 cd.name.clone(),
1386 intern(&format!("{prefix}.{}", cd.name.as_ref())),
1387 );
1388 }
1389 _ => {}
1390 }
1391 }
1392
1393 for decl in &library_program.decls {
1395 let Decl::Type(td) = decl else { continue };
1396 let name = type_map
1397 .get(&td.name)
1398 .cloned()
1399 .unwrap_or_else(|| td.name.clone());
1400 let variants = td
1401 .variants
1402 .iter()
1403 .map(|v| rexlang_ast::expr::TypeVariant {
1404 name: intern(&format!("{prefix}.{}", v.name.as_ref())),
1405 args: v
1406 .args
1407 .iter()
1408 .map(|t| rewrite_type_expr(t, &type_map))
1409 .collect(),
1410 })
1411 .collect();
1412 let td2 = TypeDecl {
1413 span: td.span,
1414 is_pub: td.is_pub,
1415 name,
1416 params: td.params.clone(),
1417 variants,
1418 };
1419 let _ = ts.inject_type_decl(&td2);
1420 }
1421
1422 let mut value_map: HashMap<rexlang_ast::expr::Symbol, rexlang_ast::expr::Symbol> =
1423 HashMap::new();
1424 let mut export_names: BTreeSet<String> = BTreeSet::new();
1425
1426 for decl in &library_program.decls {
1428 match decl {
1429 Decl::Fn(fd) if fd.is_pub => {
1430 let internal = intern(&format!("{prefix}.{}", fd.name.name.as_ref()));
1431 value_map.insert(intern(fd.name.name.as_ref()), internal.clone());
1432 export_names.insert(fd.name.name.as_ref().to_string());
1433
1434 let params = fd
1435 .params
1436 .iter()
1437 .map(|(v, ty)| (v.clone(), rewrite_type_expr(ty, &type_map)))
1438 .collect();
1439 let ret = rewrite_type_expr(&fd.ret, &type_map);
1440 let decl = DeclareFnDecl {
1441 span: fd.span,
1442 is_pub: true,
1443 name: Var {
1444 span: fd.name.span,
1445 name: internal,
1446 },
1447 params,
1448 ret,
1449 constraints: if keep_constraints {
1450 fd.constraints.clone()
1451 } else {
1452 Default::default()
1453 },
1454 };
1455 let _ = ts.inject_declare_fn_decl(&decl);
1456 }
1457 Decl::DeclareFn(df) if df.is_pub => {
1458 let internal = intern(&format!("{prefix}.{}", df.name.name.as_ref()));
1459 value_map.insert(intern(df.name.name.as_ref()), internal.clone());
1460 export_names.insert(df.name.name.as_ref().to_string());
1461
1462 let params = df
1463 .params
1464 .iter()
1465 .map(|(v, ty)| (v.clone(), rewrite_type_expr(ty, &type_map)))
1466 .collect();
1467 let ret = rewrite_type_expr(&df.ret, &type_map);
1468 let decl = DeclareFnDecl {
1469 span: df.span,
1470 is_pub: true,
1471 name: Var {
1472 span: df.name.span,
1473 name: internal,
1474 },
1475 params,
1476 ret,
1477 constraints: if keep_constraints {
1478 df.constraints.clone()
1479 } else {
1480 Default::default()
1481 },
1482 };
1483 let _ = ts.inject_declare_fn_decl(&decl);
1484 }
1485 Decl::Type(td) if td.is_pub => {
1486 for variant in &td.variants {
1488 let internal = intern(&format!("{prefix}.{}", variant.name.as_ref()));
1489 value_map.insert(variant.name.clone(), internal);
1490 export_names.insert(variant.name.as_ref().to_string());
1491 }
1492 }
1493 _ => {}
1494 }
1495 }
1496
1497 let mut export_defs = HashMap::new();
1498 for name in &export_names {
1499 if let Some(span) = index
1500 .fn_defs
1501 .get(name)
1502 .copied()
1503 .or_else(|| index.ctor_defs.get(name).copied())
1504 {
1505 export_defs.insert(name.clone(), span);
1506 }
1507 }
1508
1509 imports.insert(
1510 alias.clone(),
1511 ImportLibraryInfo {
1512 path: library_path,
1513 value_map,
1514 type_map,
1515 class_map,
1516 export_defs,
1517 },
1518 );
1519 }
1520
1521 validate_import_projection_uses(program, &imports, &mut diagnostics);
1522 let rewritten = rewrite_program_import_projections(program, &imports, &mut diagnostics);
1523 Ok((rewritten, ts, imports, diagnostics))
1524}
1525
1526fn completion_exports_for_library_alias(
1527 uri: &Url,
1528 program: &Program,
1529 alias: &str,
1530) -> std::result::Result<Vec<String>, String> {
1531 let alias_sym = intern(alias);
1532 let Some(import_decl) = program.decls.iter().find_map(|d| {
1533 let Decl::Import(id) = d else { return None };
1534 if id.alias == alias_sym {
1535 Some(id)
1536 } else {
1537 None
1538 }
1539 }) else {
1540 return Ok(Vec::new());
1541 };
1542
1543 let ImportPath::Local { segments, sha: _ } = &import_decl.path else {
1544 return Ok(Vec::new());
1545 };
1546
1547 let library_name = segments
1548 .iter()
1549 .map(|s| s.as_ref())
1550 .collect::<Vec<_>>()
1551 .join(".");
1552
1553 let source = if let Some(source) = rexlang_util::stdlib_source(&library_name) {
1554 source.to_string()
1555 } else {
1556 let importer = uri_to_file_path(uri).ok_or_else(|| "not a file uri".to_string())?;
1557 let Some(base_dir) = importer.parent() else {
1558 return Ok(Vec::new());
1559 };
1560 let Some(library_path) = rexlang_util::resolve_local_import_path(base_dir, segments)
1561 .ok()
1562 .flatten()
1563 .and_then(|p| p.canonicalize().ok())
1564 else {
1565 return Ok(Vec::new());
1566 };
1567 fs::read_to_string(&library_path).map_err(|e| e.to_string())?
1568 };
1569 let (_tokens, library_program) =
1570 tokenize_and_parse(&source).map_err(|_| "parse error".to_string())?;
1571
1572 let mut exports = BTreeSet::new();
1573 for decl in &library_program.decls {
1574 match decl {
1575 Decl::Fn(fd) if fd.is_pub => {
1576 exports.insert(fd.name.name.as_ref().to_string());
1577 }
1578 Decl::DeclareFn(df) if df.is_pub => {
1579 exports.insert(df.name.name.as_ref().to_string());
1580 }
1581 Decl::Type(td) if td.is_pub => {
1582 for variant in &td.variants {
1583 exports.insert(variant.name.as_ref().to_string());
1584 }
1585 }
1586 _ => {}
1587 }
1588 }
1589 Ok(exports.into_iter().collect())
1590}
1591
1592#[cfg(not(target_arch = "wasm32"))]
1593struct RexServer {
1594 client: Client,
1595 documents: RwLock<HashMap<Url, String>>,
1596}
1597
1598#[cfg(not(target_arch = "wasm32"))]
1599impl RexServer {
1600 fn new(client: Client) -> Self {
1601 Self {
1602 client,
1603 documents: RwLock::new(HashMap::new()),
1604 }
1605 }
1606
1607 async fn publish_diagnostics(&self, uri: Url, text: &str) {
1608 let uri_for_job = uri.clone();
1609 let text_for_job = text.to_string();
1610 let diagnostics = match tokio::task::spawn_blocking(move || {
1611 diagnostics_from_text(&uri_for_job, &text_for_job)
1612 })
1613 .await
1614 {
1615 Ok(diags) => diags,
1616 Err(err) => {
1617 self.client
1618 .log_message(
1619 MessageType::ERROR,
1620 format!("failed to compute diagnostics: {err}"),
1621 )
1622 .await;
1623 Vec::new()
1624 }
1625 };
1626 self.client
1627 .publish_diagnostics(uri, diagnostics, None)
1628 .await;
1629 }
1630}
1631
1632#[cfg(not(target_arch = "wasm32"))]
1633#[tower_lsp::async_trait]
1634impl LanguageServer for RexServer {
1635 async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
1636 Ok(InitializeResult {
1637 capabilities: ServerCapabilities {
1638 text_document_sync: Some(TextDocumentSyncCapability::Kind(
1639 TextDocumentSyncKind::FULL,
1640 )),
1641 hover_provider: Some(HoverProviderCapability::Simple(true)),
1642 completion_provider: Some(CompletionOptions {
1643 trigger_characters: Some(vec![".".to_string()]),
1644 ..CompletionOptions::default()
1645 }),
1646 code_action_provider: Some(
1647 tower_lsp::lsp_types::CodeActionProviderCapability::Options(
1648 CodeActionOptions {
1649 code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
1650 ..CodeActionOptions::default()
1651 },
1652 ),
1653 ),
1654 execute_command_provider: Some(ExecuteCommandOptions {
1655 commands: vec![
1656 CMD_EXPECTED_TYPE_AT.to_string(),
1657 CMD_FUNCTIONS_PRODUCING_EXPECTED_TYPE_AT.to_string(),
1658 CMD_FUNCTIONS_ACCEPTING_INFERRED_TYPE_AT.to_string(),
1659 CMD_ADAPTERS_FROM_INFERRED_TO_EXPECTED_AT.to_string(),
1660 CMD_FUNCTIONS_COMPATIBLE_WITH_IN_SCOPE_VALUES_AT.to_string(),
1661 CMD_HOLES_EXPECTED_TYPES.to_string(),
1662 CMD_SEMANTIC_LOOP_STEP.to_string(),
1663 CMD_SEMANTIC_LOOP_APPLY_QUICK_FIX_AT.to_string(),
1664 CMD_SEMANTIC_LOOP_APPLY_BEST_QUICK_FIXES_AT.to_string(),
1665 ],
1666 ..ExecuteCommandOptions::default()
1667 }),
1668 definition_provider: Some(OneOf::Left(true)),
1669 references_provider: Some(OneOf::Left(true)),
1670 rename_provider: Some(OneOf::Left(true)),
1671 document_symbol_provider: Some(OneOf::Left(true)),
1672 document_formatting_provider: Some(OneOf::Left(true)),
1673 ..ServerCapabilities::default()
1674 },
1675 server_info: Some(ServerInfo {
1676 name: "rexlang-lsp".to_string(),
1677 version: Some("0.1.0".to_string()),
1678 }),
1679 })
1680 }
1681
1682 async fn initialized(&self, _: InitializedParams) {
1683 self.client
1684 .log_message(MessageType::INFO, "Rex LSP initialized")
1685 .await;
1686 }
1687
1688 async fn shutdown(&self) -> Result<()> {
1689 Ok(())
1690 }
1691
1692 async fn did_open(&self, params: DidOpenTextDocumentParams) {
1693 let uri = params.text_document.uri;
1694 let text = params.text_document.text;
1695
1696 self.documents
1697 .write()
1698 .await
1699 .insert(uri.clone(), text.clone());
1700 clear_parse_cache(&uri);
1701 self.publish_diagnostics(uri, &text).await;
1702 }
1703
1704 async fn did_change(&self, params: DidChangeTextDocumentParams) {
1705 let uri = params.text_document.uri;
1706 let text = params
1707 .content_changes
1708 .into_iter()
1709 .last()
1710 .map(|change| change.text);
1711
1712 if let Some(text) = text {
1713 self.documents
1714 .write()
1715 .await
1716 .insert(uri.clone(), text.clone());
1717 clear_parse_cache(&uri);
1718 self.publish_diagnostics(uri, &text).await;
1719 }
1720 }
1721
1722 async fn did_close(&self, params: DidCloseTextDocumentParams) {
1723 let uri = params.text_document.uri;
1724 self.documents.write().await.remove(&uri);
1725 clear_parse_cache(&uri);
1726 self.client.publish_diagnostics(uri, Vec::new(), None).await;
1727 }
1728
1729 async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
1730 let uri = params.text_document_position_params.text_document.uri;
1731 let position = params.text_document_position_params.position;
1732 let text = { self.documents.read().await.get(&uri).cloned() };
1733
1734 let Some(text) = text else {
1735 return Ok(None);
1736 };
1737
1738 let uri_for_job = uri.clone();
1739 let text_for_job = text.clone();
1740 let type_contents = match tokio::task::spawn_blocking(move || {
1741 hover_type_contents(&uri_for_job, &text_for_job, position)
1742 })
1743 .await
1744 {
1745 Ok(contents) => contents,
1746 Err(err) => {
1747 self.client
1748 .log_message(MessageType::ERROR, format!("hover failed: {err}"))
1749 .await;
1750 None
1751 }
1752 };
1753
1754 let contents = type_contents.or_else(|| {
1755 let word = word_at_position(&text, position)?;
1756 hover_contents(&word)
1757 });
1758
1759 Ok(contents.map(|contents| Hover {
1760 contents,
1761 range: None,
1762 }))
1763 }
1764
1765 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
1766 let uri = params.text_document_position.text_document.uri;
1767 let position = params.text_document_position.position;
1768 let text = { self.documents.read().await.get(&uri).cloned() };
1769
1770 let Some(text) = text else {
1771 return Ok(None);
1772 };
1773
1774 let uri_for_job = uri.clone();
1775 let text_for_job = text;
1776 let items = match tokio::task::spawn_blocking(move || {
1777 completion_items(&uri_for_job, &text_for_job, position)
1778 })
1779 .await
1780 {
1781 Ok(items) => items,
1782 Err(err) => {
1783 self.client
1784 .log_message(MessageType::ERROR, format!("completion failed: {err}"))
1785 .await;
1786 Vec::new()
1787 }
1788 };
1789 Ok(Some(CompletionResponse::Array(items)))
1790 }
1791
1792 async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
1793 let uri = params.text_document.uri;
1794 let text = { self.documents.read().await.get(&uri).cloned() };
1795 let Some(text) = text else {
1796 return Ok(None);
1797 };
1798
1799 let range = params.range;
1800 let diagnostics = params.context.diagnostics;
1801 let uri_for_job = uri.clone();
1802 let text_for_job = text;
1803 let actions = match tokio::task::spawn_blocking(move || {
1804 code_actions_for_source(&uri_for_job, &text_for_job, range, &diagnostics)
1805 })
1806 .await
1807 {
1808 Ok(actions) => actions,
1809 Err(err) => {
1810 self.client
1811 .log_message(MessageType::ERROR, format!("code action failed: {err}"))
1812 .await;
1813 Vec::new()
1814 }
1815 };
1816
1817 Ok(Some(actions))
1818 }
1819
1820 async fn execute_command(&self, params: ExecuteCommandParams) -> Result<Option<Value>> {
1821 let arguments = params.arguments;
1822 let command = params.command;
1823 if command == CMD_HOLES_EXPECTED_TYPES {
1824 let Some(uri) = command_uri(&arguments) else {
1825 return Ok(None);
1826 };
1827 let text = { self.documents.read().await.get(&uri).cloned() };
1828 let Some(text) = text else {
1829 return Ok(None);
1830 };
1831 return Ok(execute_query_command_for_document_without_position(
1832 &command, &uri, &text,
1833 ));
1834 }
1835 if command == CMD_SEMANTIC_LOOP_STEP {
1836 let Some((uri, position)) = command_uri_and_position(&arguments) else {
1837 return Ok(None);
1838 };
1839 let text = { self.documents.read().await.get(&uri).cloned() };
1840 let Some(text) = text else {
1841 return Ok(None);
1842 };
1843 return Ok(execute_semantic_loop_step(&uri, &text, position));
1844 }
1845 if command == CMD_SEMANTIC_LOOP_APPLY_QUICK_FIX_AT {
1846 let Some((uri, position, quick_fix_id)) = command_uri_position_and_id(&arguments)
1847 else {
1848 return Ok(None);
1849 };
1850 let text = { self.documents.read().await.get(&uri).cloned() };
1851 let Some(text) = text else {
1852 return Ok(None);
1853 };
1854 return Ok(execute_semantic_loop_apply_quick_fix(
1855 &uri,
1856 &text,
1857 position,
1858 &quick_fix_id,
1859 ));
1860 }
1861 if command == CMD_SEMANTIC_LOOP_APPLY_BEST_QUICK_FIXES_AT {
1862 let Some((uri, position, max_steps, strategy, dry_run)) =
1863 command_uri_position_max_steps_strategy_and_dry_run(&arguments)
1864 else {
1865 return Ok(None);
1866 };
1867 let text = { self.documents.read().await.get(&uri).cloned() };
1868 let Some(text) = text else {
1869 return Ok(None);
1870 };
1871 return Ok(execute_semantic_loop_apply_best_quick_fixes(
1872 &uri, &text, position, max_steps, strategy, dry_run,
1873 ));
1874 }
1875
1876 let Some((uri, position)) = command_uri_and_position(&arguments) else {
1877 return Ok(None);
1878 };
1879 let text = { self.documents.read().await.get(&uri).cloned() };
1880 let Some(text) = text else {
1881 return Ok(None);
1882 };
1883 Ok(execute_query_command_for_document(
1884 &command, &uri, &text, position,
1885 ))
1886 }
1887
1888 async fn goto_definition(
1889 &self,
1890 params: GotoDefinitionParams,
1891 ) -> Result<Option<GotoDefinitionResponse>> {
1892 let uri = params.text_document_position_params.text_document.uri;
1893 let position = params.text_document_position_params.position;
1894 let text = { self.documents.read().await.get(&uri).cloned() };
1895
1896 let Some(text) = text else {
1897 return Ok(None);
1898 };
1899
1900 let uri_for_job = uri.clone();
1901 let text_for_job = text;
1902 let response = match tokio::task::spawn_blocking(move || {
1903 goto_definition_response(&uri_for_job, &text_for_job, position)
1904 })
1905 .await
1906 {
1907 Ok(resp) => resp,
1908 Err(err) => {
1909 self.client
1910 .log_message(MessageType::ERROR, format!("goto definition failed: {err}"))
1911 .await;
1912 None
1913 }
1914 };
1915 Ok(response)
1916 }
1917
1918 async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
1919 let uri = params.text_document_position.text_document.uri;
1920 let position = params.text_document_position.position;
1921 let include_declaration = params.context.include_declaration;
1922 let text = { self.documents.read().await.get(&uri).cloned() };
1923
1924 let Some(text) = text else {
1925 return Ok(None);
1926 };
1927
1928 let uri_for_job = uri.clone();
1929 let text_for_job = text;
1930 let refs = match tokio::task::spawn_blocking(move || {
1931 references_for_source(&uri_for_job, &text_for_job, position, include_declaration)
1932 })
1933 .await
1934 {
1935 Ok(items) => items,
1936 Err(err) => {
1937 self.client
1938 .log_message(MessageType::ERROR, format!("references failed: {err}"))
1939 .await;
1940 Vec::new()
1941 }
1942 };
1943 Ok(Some(refs))
1944 }
1945
1946 async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
1947 let uri = params.text_document_position.text_document.uri;
1948 let position = params.text_document_position.position;
1949 let new_name = params.new_name;
1950 let text = { self.documents.read().await.get(&uri).cloned() };
1951
1952 let Some(text) = text else {
1953 return Ok(None);
1954 };
1955
1956 let uri_for_job = uri.clone();
1957 let text_for_job = text;
1958 let edit = match tokio::task::spawn_blocking(move || {
1959 rename_for_source(&uri_for_job, &text_for_job, position, &new_name)
1960 })
1961 .await
1962 {
1963 Ok(edit) => edit,
1964 Err(err) => {
1965 self.client
1966 .log_message(MessageType::ERROR, format!("rename failed: {err}"))
1967 .await;
1968 None
1969 }
1970 };
1971 Ok(edit)
1972 }
1973
1974 async fn document_symbol(
1975 &self,
1976 params: DocumentSymbolParams,
1977 ) -> Result<Option<DocumentSymbolResponse>> {
1978 let uri = params.text_document.uri;
1979 let text = { self.documents.read().await.get(&uri).cloned() };
1980 let Some(text) = text else {
1981 return Ok(None);
1982 };
1983 let symbols = document_symbols_for_source(&uri, &text);
1984 Ok(Some(DocumentSymbolResponse::Nested(symbols)))
1985 }
1986
1987 async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
1988 let uri = params.text_document.uri;
1989 let text = { self.documents.read().await.get(&uri).cloned() };
1990 let Some(text) = text else {
1991 return Ok(None);
1992 };
1993 Ok(format_edits_for_source(&text))
1994 }
1995}
1996
1997fn goto_definition_response(
1998 uri: &Url,
1999 text: &str,
2000 position: Position,
2001) -> Option<GotoDefinitionResponse> {
2002 let Ok((tokens, program)) = tokenize_and_parse_cached(uri, text) else {
2005 return None;
2006 };
2007
2008 let imported_projection = imported_projection_at_position(&tokens, position);
2009
2010 let (ident, _token_span) = ident_token_at_position(&tokens, position)?;
2011
2012 if let Some((alias, field)) = imported_projection
2015 && let Ok((_rewritten, _ts, imports, _diags)) = prepare_program_with_imports(uri, &program)
2016 {
2017 let alias_sym = intern(&alias);
2018 if let Some(info) = imports.get(&alias_sym)
2019 && let Some(span) = info.export_defs.get(&field)
2020 && let Some(path) = info.path.as_ref()
2021 && let Some(module_uri) = url_from_file_path(path)
2022 {
2023 return Some(GotoDefinitionResponse::Scalar(Location {
2024 uri: module_uri,
2025 range: span_to_range(*span),
2026 }));
2027 }
2028 }
2029
2030 let index = index_decl_spans(&program, &tokens);
2031 let pos = lsp_to_rex_position(position);
2032
2033 let expr_with_fns = program.expr_with_fns();
2037 let mut root_expr: &Expr = expr_with_fns.as_ref();
2038 for decl in &program.decls {
2039 let Decl::Instance(inst) = decl else {
2040 continue;
2041 };
2042 for method in &inst.methods {
2043 if position_in_span(pos, *method.body.span()) {
2044 root_expr = method.body.as_ref();
2045 break;
2046 }
2047 }
2048 }
2049
2050 let value_def =
2051 definition_span_for_value_ident(root_expr, pos, &ident, &mut Vec::new(), &tokens);
2052
2053 let instance_method_def = index
2054 .instance_method_defs
2055 .iter()
2056 .find_map(|(span, methods)| {
2057 if position_in_span(pos, *span) {
2058 methods.get(&ident).copied()
2059 } else {
2060 None
2061 }
2062 });
2063
2064 let target_span = value_def
2065 .or(instance_method_def)
2066 .or(index.class_method_defs.get(&ident).copied())
2067 .or(index.fn_defs.get(&ident).copied())
2068 .or(index.ctor_defs.get(&ident).copied())
2069 .or(index.type_defs.get(&ident).copied())
2070 .or(index.class_defs.get(&ident).copied())?;
2071
2072 Some(GotoDefinitionResponse::Scalar(Location {
2073 uri: uri.clone(),
2074 range: span_to_range(target_span),
2075 }))
2076}
2077
2078fn range_to_span(range: Range) -> Span {
2079 Span::new(
2080 (range.start.line + 1) as usize,
2081 (range.start.character + 1) as usize,
2082 (range.end.line + 1) as usize,
2083 (range.end.character + 1) as usize,
2084 )
2085}
2086
2087fn pattern_bindings_with_spans(pat: &Pattern, out: &mut Vec<(String, Span)>) {
2088 match pat {
2089 Pattern::Var(var) => out.push((var.name.to_string(), var.span)),
2090 Pattern::Named(_, _, args) => {
2091 for arg in args {
2092 pattern_bindings_with_spans(arg, out);
2093 }
2094 }
2095 Pattern::Tuple(_, elems) | Pattern::List(_, elems) => {
2096 for elem in elems {
2097 pattern_bindings_with_spans(elem, out);
2098 }
2099 }
2100 Pattern::Cons(_, head, tail) => {
2101 pattern_bindings_with_spans(head, out);
2102 pattern_bindings_with_spans(tail, out);
2103 }
2104 Pattern::Dict(_, fields) => {
2105 for (_, pat) in fields {
2106 pattern_bindings_with_spans(pat, out);
2107 }
2108 }
2109 Pattern::Wildcard(..) => {}
2110 }
2111}
2112
2113fn collect_references_in_expr(
2114 expr: &Expr,
2115 ident: &str,
2116 target_span: Span,
2117 uri: &Url,
2118 top_level_defs: &HashMap<String, Span>,
2119 scope: &mut Vec<(String, Span)>,
2120 out: &mut Vec<Location>,
2121) {
2122 match expr {
2123 Expr::Var(var) => {
2124 if var.name.as_ref() != ident {
2125 return;
2126 }
2127 let resolved = scope
2128 .iter()
2129 .rev()
2130 .find_map(|(name, span)| (name == ident).then_some(*span))
2131 .or_else(|| top_level_defs.get(ident).copied());
2132 if resolved.is_some_and(|span| span == target_span) {
2133 out.push(Location {
2134 uri: uri.clone(),
2135 range: span_to_range(var.span),
2136 });
2137 }
2138 }
2139 Expr::Let(_, var, _ann, def, body) => {
2140 collect_references_in_expr(def, ident, target_span, uri, top_level_defs, scope, out);
2141 scope.push((var.name.to_string(), var.span));
2142 collect_references_in_expr(body, ident, target_span, uri, top_level_defs, scope, out);
2143 scope.pop();
2144 }
2145 Expr::LetRec(_, bindings, body) => {
2146 let base_len = scope.len();
2147 for (var, _ann, _def) in bindings {
2148 scope.push((var.name.to_string(), var.span));
2149 }
2150 for (_var, _ann, def) in bindings {
2151 collect_references_in_expr(
2152 def,
2153 ident,
2154 target_span,
2155 uri,
2156 top_level_defs,
2157 scope,
2158 out,
2159 );
2160 }
2161 collect_references_in_expr(body, ident, target_span, uri, top_level_defs, scope, out);
2162 scope.truncate(base_len);
2163 }
2164 Expr::Lam(_, _scope, param, _ann, _constraints, body) => {
2165 scope.push((param.name.to_string(), param.span));
2166 collect_references_in_expr(body, ident, target_span, uri, top_level_defs, scope, out);
2167 scope.pop();
2168 }
2169 Expr::Match(_, scrutinee, arms) => {
2170 collect_references_in_expr(
2171 scrutinee,
2172 ident,
2173 target_span,
2174 uri,
2175 top_level_defs,
2176 scope,
2177 out,
2178 );
2179 for (pat, arm) in arms {
2180 let base_len = scope.len();
2181 let mut binds = Vec::new();
2182 pattern_bindings_with_spans(pat, &mut binds);
2183 scope.extend(binds);
2184 collect_references_in_expr(
2185 arm,
2186 ident,
2187 target_span,
2188 uri,
2189 top_level_defs,
2190 scope,
2191 out,
2192 );
2193 scope.truncate(base_len);
2194 }
2195 }
2196 Expr::App(_, fun, arg) => {
2197 collect_references_in_expr(fun, ident, target_span, uri, top_level_defs, scope, out);
2198 collect_references_in_expr(arg, ident, target_span, uri, top_level_defs, scope, out);
2199 }
2200 Expr::Project(_, base, _) => {
2201 collect_references_in_expr(base, ident, target_span, uri, top_level_defs, scope, out);
2202 }
2203 Expr::Tuple(_, elems) | Expr::List(_, elems) => {
2204 for elem in elems {
2205 collect_references_in_expr(
2206 elem,
2207 ident,
2208 target_span,
2209 uri,
2210 top_level_defs,
2211 scope,
2212 out,
2213 );
2214 }
2215 }
2216 Expr::Dict(_, entries) => {
2217 for value in entries.values() {
2218 collect_references_in_expr(
2219 value,
2220 ident,
2221 target_span,
2222 uri,
2223 top_level_defs,
2224 scope,
2225 out,
2226 );
2227 }
2228 }
2229 Expr::RecordUpdate(_, base, updates) => {
2230 collect_references_in_expr(base, ident, target_span, uri, top_level_defs, scope, out);
2231 for value in updates.values() {
2232 collect_references_in_expr(
2233 value,
2234 ident,
2235 target_span,
2236 uri,
2237 top_level_defs,
2238 scope,
2239 out,
2240 );
2241 }
2242 }
2243 Expr::Ite(_, cond, then_expr, else_expr) => {
2244 collect_references_in_expr(cond, ident, target_span, uri, top_level_defs, scope, out);
2245 collect_references_in_expr(
2246 then_expr,
2247 ident,
2248 target_span,
2249 uri,
2250 top_level_defs,
2251 scope,
2252 out,
2253 );
2254 collect_references_in_expr(
2255 else_expr,
2256 ident,
2257 target_span,
2258 uri,
2259 top_level_defs,
2260 scope,
2261 out,
2262 );
2263 }
2264 Expr::Ann(_, inner, _) => {
2265 collect_references_in_expr(inner, ident, target_span, uri, top_level_defs, scope, out);
2266 }
2267 Expr::Bool(..)
2268 | Expr::Uint(..)
2269 | Expr::Int(..)
2270 | Expr::Float(..)
2271 | Expr::String(..)
2272 | Expr::Uuid(..)
2273 | Expr::DateTime(..)
2274 | Expr::Hole(..) => {}
2275 }
2276}
2277
2278fn references_for_source(
2279 uri: &Url,
2280 text: &str,
2281 position: Position,
2282 include_declaration: bool,
2283) -> Vec<Location> {
2284 let Ok((tokens, program)) = tokenize_and_parse_cached(uri, text) else {
2285 return Vec::new();
2286 };
2287 let Some((ident, _token_span)) = ident_token_at_position(&tokens, position) else {
2288 return Vec::new();
2289 };
2290
2291 let Some(def_response) = goto_definition_response(uri, text, position) else {
2292 return Vec::new();
2293 };
2294 let GotoDefinitionResponse::Scalar(def_location) = def_response else {
2295 return Vec::new();
2296 };
2297 if def_location.uri != *uri {
2298 return Vec::new();
2299 }
2300 let target_span = range_to_span(def_location.range);
2301
2302 let index = index_decl_spans(&program, &tokens);
2303 let mut top_level_defs = index.fn_defs;
2304 top_level_defs.extend(index.ctor_defs);
2305
2306 let mut refs = Vec::new();
2307 if include_declaration {
2308 refs.push(def_location);
2309 }
2310 let expr = program.expr_with_fns();
2311 collect_references_in_expr(
2312 expr.as_ref(),
2313 &ident,
2314 target_span,
2315 uri,
2316 &top_level_defs,
2317 &mut Vec::new(),
2318 &mut refs,
2319 );
2320 refs.sort_by_key(|location| {
2321 (
2322 location.range.start.line,
2323 location.range.start.character,
2324 location.range.end.line,
2325 location.range.end.character,
2326 )
2327 });
2328 refs.dedup_by(|a, b| a.range == b.range && a.uri == b.uri);
2329 refs
2330}
2331
2332fn rename_for_source(
2333 uri: &Url,
2334 text: &str,
2335 position: Position,
2336 new_name: &str,
2337) -> Option<WorkspaceEdit> {
2338 if !is_ident_like(new_name) {
2339 return None;
2340 }
2341 let refs = references_for_source(uri, text, position, true);
2342 if refs.is_empty() {
2343 return None;
2344 }
2345 let edits: Vec<TextEdit> = refs
2346 .into_iter()
2347 .map(|location| TextEdit {
2348 range: location.range,
2349 new_text: new_name.to_string(),
2350 })
2351 .collect();
2352 let mut changes = HashMap::new();
2353 changes.insert(uri.clone(), edits);
2354 Some(WorkspaceEdit {
2355 changes: Some(changes),
2356 document_changes: None,
2357 change_annotations: None,
2358 })
2359}
2360
2361fn code_actions_for_source(
2362 uri: &Url,
2363 text: &str,
2364 request_range: Range,
2365 diagnostics: &[Diagnostic],
2366) -> Vec<CodeActionOrCommand> {
2367 let parsed = tokenize_and_parse_cached(uri, text)
2368 .ok()
2369 .map(|(_tokens, program)| program);
2370 let mut actions = Vec::new();
2371
2372 actions.extend(code_actions_for_hole_fill(
2374 uri,
2375 text,
2376 parsed.as_ref(),
2377 request_range,
2378 ));
2379
2380 for diag in diagnostics {
2381 let usable_diag_range = range_is_usable_for_text(text, diag.range);
2382 if usable_diag_range
2383 && !range_is_empty(diag.range)
2384 && !ranges_overlap(diag.range, request_range)
2385 && !range_contains_position(diag.range, request_range.start)
2386 && !range_contains_position(diag.range, request_range.end)
2387 {
2388 continue;
2389 }
2390 actions.extend(code_actions_for_diagnostic(
2391 uri,
2392 text,
2393 parsed.as_ref(),
2394 request_range,
2395 diag,
2396 ));
2397 }
2398
2399 actions
2400}
2401
2402fn code_actions_for_hole_fill(
2403 uri: &Url,
2404 text: &str,
2405 program: Option<&Program>,
2406 request_range: Range,
2407) -> Vec<CodeActionOrCommand> {
2408 let Some(program) = program else {
2409 return Vec::new();
2410 };
2411 let mut hole_spans = Vec::new();
2412 collect_hole_spans(program.expr_with_fns().as_ref(), &mut hole_spans);
2413 let Some(hole_span) = hole_spans
2414 .into_iter()
2415 .find(|span| ranges_overlap(span_to_range(*span), request_range))
2416 else {
2417 return Vec::new();
2418 };
2419 let hole_range = span_to_range(hole_span);
2420 let pos = hole_range.start;
2421 let candidates = hole_fill_candidates_at_position(uri, text, pos);
2422 let mut actions = Vec::new();
2423 for (name, replacement) in candidates.into_iter().take(8) {
2424 let diagnostic = Diagnostic {
2425 range: hole_range,
2426 severity: Some(DiagnosticSeverity::HINT),
2427 message: "hole".to_string(),
2428 source: Some("rexlang-lsp".to_string()),
2429 ..Diagnostic::default()
2430 };
2431 actions.push(code_action_replace(
2432 format!("Fill hole with `{name}`"),
2433 uri,
2434 hole_range,
2435 replacement,
2436 diagnostic,
2437 ));
2438 }
2439 actions
2440}
2441
2442fn code_actions_for_diagnostic(
2443 uri: &Url,
2444 text: &str,
2445 program: Option<&Program>,
2446 request_range: Range,
2447 diagnostic: &Diagnostic,
2448) -> Vec<CodeActionOrCommand> {
2449 let mut actions = Vec::new();
2450 let target_range = if range_is_usable_for_text(text, diagnostic.range) {
2451 diagnostic.range
2452 } else {
2453 request_range
2454 };
2455
2456 if diagnostic
2457 .message
2458 .contains("typed hole `?` must be filled before evaluation")
2459 {
2460 actions.extend(code_actions_for_hole_fill(uri, text, program, target_range));
2461 }
2462
2463 if let Some(name) = unknown_var_name_from_message(&diagnostic.message) {
2464 if let Some(program) = program {
2465 let mut candidates: Vec<String> =
2466 values_in_scope_at_position(program, target_range.start)
2467 .into_keys()
2468 .filter(|candidate| candidate != name)
2469 .collect();
2470 candidates.sort_by_key(|candidate| levenshtein_distance(candidate, name));
2471 for candidate in candidates.into_iter().take(3) {
2472 actions.push(code_action_replace(
2473 format!("Replace `{name}` with `{candidate}`"),
2474 uri,
2475 target_range,
2476 candidate,
2477 diagnostic.clone(),
2478 ));
2479 }
2480 }
2481
2482 actions.push(code_action_insert(
2483 format!("Introduce `let {name} = null`"),
2484 uri,
2485 Position {
2486 line: 0,
2487 character: 0,
2488 },
2489 format!("let {name} = null in\n"),
2490 diagnostic.clone(),
2491 ));
2492 }
2493
2494 if is_list_scalar_unification_error(&diagnostic.message)
2495 && let Some(selected) = text_for_range(text, target_range)
2496 {
2497 let trimmed = selected.trim();
2498 if !trimmed.is_empty() {
2499 actions.push(code_action_replace(
2500 "Wrap expression in list literal".to_string(),
2501 uri,
2502 target_range,
2503 format!("[{selected}]"),
2504 diagnostic.clone(),
2505 ));
2506 if trimmed.starts_with('[') && trimmed.ends_with(']') && trimmed.len() >= 2 {
2507 let unwrapped = trimmed[1..trimmed.len() - 1].to_string();
2508 actions.push(code_action_replace(
2509 "Unwrap list literal".to_string(),
2510 uri,
2511 target_range,
2512 unwrapped,
2513 diagnostic.clone(),
2514 ));
2515 }
2516 }
2517 }
2518
2519 if is_array_list_unification_error(&diagnostic.message) {
2520 let selected_range =
2521 if !range_is_empty(request_range) && range_is_usable_for_text(text, request_range) {
2522 request_range
2523 } else {
2524 target_range
2525 };
2526 if let Some(selected) = text_for_range(text, selected_range) {
2527 let trimmed = selected.trim();
2528 if !trimmed.is_empty() && !trimmed.starts_with("to_list") {
2529 actions.push(code_action_replace(
2530 "Convert expression to list with `to_list`".to_string(),
2531 uri,
2532 selected_range,
2533 format!("to_list ({selected})"),
2534 diagnostic.clone(),
2535 ));
2536 }
2537 }
2538 }
2539
2540 if is_function_value_unification_error(&diagnostic.message)
2541 && let Some(selected) = text_for_range(text, target_range)
2542 {
2543 let trimmed = selected.trim();
2544 if !trimmed.is_empty() {
2545 actions.push(code_action_replace(
2546 "Apply expression to missing argument".to_string(),
2547 uri,
2548 target_range,
2549 format!("({selected} null)"),
2550 diagnostic.clone(),
2551 ));
2552 actions.push(code_action_replace(
2553 "Wrap expression in lambda".to_string(),
2554 uri,
2555 target_range,
2556 format!("(\\_ -> {selected})"),
2557 diagnostic.clone(),
2558 ));
2559 }
2560 }
2561
2562 if diagnostic.message.starts_with("non-exhaustive match for ") {
2563 let newline = if diagnostic.range.start.line == diagnostic.range.end.line {
2564 " "
2565 } else {
2566 "\n"
2567 };
2568 actions.push(code_action_insert(
2569 "Add wildcard arm to match".to_string(),
2570 uri,
2571 diagnostic.range.end,
2572 format!("{newline}when _ -> null"),
2573 diagnostic.clone(),
2574 ));
2575 }
2576
2577 if let Some(field) = field_not_definitely_available_from_message(&diagnostic.message)
2578 && let Some(program) = program
2579 && let Some(selected) = text_for_range(text, target_range)
2580 {
2581 let candidates = default_record_candidates_for_field(program, field);
2582 for ty_name in &candidates {
2583 if let Some(new_text) = replace_first_default_with_is(&selected, ty_name) {
2584 actions.push(code_action_replace(
2585 format!("Disambiguate `default` as `{ty_name}`"),
2586 uri,
2587 target_range,
2588 new_text,
2589 diagnostic.clone(),
2590 ));
2591 }
2592 }
2593
2594 if let Some((binding_name, insert_pos)) =
2595 find_let_binding_for_def_range(program, target_range)
2596 {
2597 for ty_name in &candidates {
2598 actions.push(code_action_insert(
2599 format!("Annotate `{binding_name}` as `{ty_name}`"),
2600 uri,
2601 insert_pos,
2602 format!(": {ty_name}"),
2603 diagnostic.clone(),
2604 ));
2605 }
2606 }
2607 }
2608
2609 actions
2610}
2611
2612fn code_action_replace(
2613 title: String,
2614 uri: &Url,
2615 range: Range,
2616 new_text: String,
2617 diagnostic: Diagnostic,
2618) -> CodeActionOrCommand {
2619 code_action_with_edit(title, uri, TextEdit { range, new_text }, diagnostic)
2620}
2621
2622fn code_action_insert(
2623 title: String,
2624 uri: &Url,
2625 position: Position,
2626 new_text: String,
2627 diagnostic: Diagnostic,
2628) -> CodeActionOrCommand {
2629 code_action_with_edit(
2630 title,
2631 uri,
2632 TextEdit {
2633 range: Range {
2634 start: position,
2635 end: position,
2636 },
2637 new_text,
2638 },
2639 diagnostic,
2640 )
2641}
2642
2643fn code_action_with_edit(
2644 title: String,
2645 uri: &Url,
2646 edit: TextEdit,
2647 diagnostic: Diagnostic,
2648) -> CodeActionOrCommand {
2649 let mut changes = HashMap::new();
2650 changes.insert(uri.clone(), vec![edit]);
2651 CodeActionOrCommand::CodeAction(CodeAction {
2652 title,
2653 kind: Some(CodeActionKind::QUICKFIX),
2654 diagnostics: Some(vec![diagnostic]),
2655 edit: Some(WorkspaceEdit {
2656 changes: Some(changes),
2657 document_changes: None,
2658 change_annotations: None,
2659 }),
2660 command: None,
2661 is_preferred: Some(true),
2662 disabled: None,
2663 data: None,
2664 })
2665}
2666
2667fn text_for_range(text: &str, range: Range) -> Option<String> {
2668 let start = offset_at(text, range.start)?;
2669 let end = offset_at(text, range.end)?;
2670 (start <= end && end <= text.len()).then(|| text[start..end].to_string())
2671}
2672
2673fn range_is_usable_for_text(text: &str, range: Range) -> bool {
2674 let Some(start) = offset_at(text, range.start) else {
2675 return false;
2676 };
2677 let Some(end) = offset_at(text, range.end) else {
2678 return false;
2679 };
2680 start <= end && end <= text.len()
2681}
2682
2683fn ranges_overlap(a: Range, b: Range) -> bool {
2684 position_leq_lsp(a.start, b.end) && position_leq_lsp(b.start, a.end)
2685}
2686
2687fn position_leq_lsp(left: Position, right: Position) -> bool {
2688 left.line < right.line || (left.line == right.line && left.character <= right.character)
2689}
2690
2691fn range_is_empty(range: Range) -> bool {
2692 range.start.line == range.end.line && range.start.character == range.end.character
2693}
2694
2695fn unknown_var_name_from_message(message: &str) -> Option<&str> {
2696 message.strip_prefix("unbound variable ").map(str::trim)
2697}
2698
2699fn field_not_definitely_available_from_message(message: &str) -> Option<&str> {
2700 let rest = message.strip_prefix("field `")?;
2701 let (field, tail) = rest.split_once('`')?;
2702 tail.contains("is not definitely available on")
2703 .then_some(field)
2704}
2705
2706fn default_record_candidates_for_field(program: &Program, field: &str) -> Vec<String> {
2707 let mut out = Vec::new();
2708 let mut seen = HashSet::new();
2709 for decl in &program.decls {
2710 let Decl::Instance(inst) = decl else {
2711 continue;
2712 };
2713 if inst.class.as_ref() != "Default" {
2714 continue;
2715 }
2716 let TypeExpr::Name(_, ty_name) = &inst.head else {
2717 continue;
2718 };
2719 if !type_decl_has_record_field(program, ty_name.as_ref(), field) {
2720 continue;
2721 }
2722 let ty_name = ty_name.as_ref().to_string();
2723 if seen.insert(ty_name.clone()) {
2724 out.push(ty_name);
2725 }
2726 }
2727 out
2728}
2729
2730fn type_decl_has_record_field(program: &Program, type_name: &str, field: &str) -> bool {
2731 program.decls.iter().any(|decl| {
2732 let Decl::Type(td) = decl else {
2733 return false;
2734 };
2735 if td.name.as_ref() != type_name {
2736 return false;
2737 }
2738 td.variants.iter().any(|variant| {
2739 variant.args.iter().any(|arg| {
2740 let TypeExpr::Record(_, fields) = arg else {
2741 return false;
2742 };
2743 fields.iter().any(|(name, _)| name.as_ref() == field)
2744 })
2745 })
2746 })
2747}
2748
2749fn replace_first_default_with_is(source: &str, ty_name: &str) -> Option<String> {
2750 for (idx, _) in source.match_indices("default") {
2751 let left_ok = if idx == 0 {
2752 true
2753 } else {
2754 !is_ident_char(source[..idx].chars().next_back().unwrap_or('_'))
2755 };
2756 let right_idx = idx + "default".len();
2757 let right_ok = if right_idx >= source.len() {
2758 true
2759 } else {
2760 !is_ident_char(source[right_idx..].chars().next().unwrap_or('_'))
2761 };
2762 if !(left_ok && right_ok) {
2763 continue;
2764 }
2765
2766 let mut replaced = String::with_capacity(source.len() + ty_name.len() + 8);
2767 replaced.push_str(&source[..idx]);
2768 replaced.push_str("(default is ");
2769 replaced.push_str(ty_name);
2770 replaced.push(')');
2771 replaced.push_str(&source[right_idx..]);
2772 return Some(replaced);
2773 }
2774 None
2775}
2776
2777fn is_ident_char(c: char) -> bool {
2778 c.is_ascii_alphanumeric() || c == '_'
2779}
2780
2781fn is_hole_name(name: &str) -> bool {
2782 name == "_" || name.starts_with('_')
2783}
2784
2785fn is_list_scalar_unification_error(message: &str) -> bool {
2786 let Some(rest) = message.strip_prefix("types do not unify: ") else {
2787 return false;
2788 };
2789 let Some((left, right)) = rest.split_once(" vs ") else {
2790 return false;
2791 };
2792 list_inner_type(left.trim()).is_some_and(|inner| inner == right.trim())
2793 || list_inner_type(right.trim()).is_some_and(|inner| inner == left.trim())
2794}
2795
2796fn list_inner_type(typ: &str) -> Option<&str> {
2797 if let Some(inner) = typ
2798 .strip_prefix("List<")
2799 .and_then(|rest| rest.strip_suffix('>'))
2800 {
2801 return Some(inner);
2802 }
2803 typ.strip_prefix("(List ")
2804 .and_then(|rest| rest.strip_suffix(')'))
2805}
2806
2807fn is_array_list_unification_error(message: &str) -> bool {
2808 let Some(rest) = message.strip_prefix("types do not unify: ") else {
2809 return false;
2810 };
2811 let Some((left, right)) = rest.split_once(" vs ") else {
2812 return false;
2813 };
2814 let left = left.trim();
2815 let right = right.trim();
2816 let left_has_array = left.contains("Array");
2817 let left_has_list = left.contains("List");
2818 let right_has_array = right.contains("Array");
2819 let right_has_list = right.contains("List");
2820 (left_has_array && right_has_list) || (left_has_list && right_has_array)
2821}
2822
2823fn is_function_value_unification_error(message: &str) -> bool {
2824 let Some(rest) = message.strip_prefix("types do not unify: ") else {
2825 return false;
2826 };
2827 let Some((left, right)) = rest.split_once(" vs ") else {
2828 return false;
2829 };
2830 let left_is_fun = looks_like_fun_type(left.trim());
2831 let right_is_fun = looks_like_fun_type(right.trim());
2832 left_is_fun ^ right_is_fun
2833}
2834
2835fn looks_like_fun_type(typ: &str) -> bool {
2836 let mut depth = 0usize;
2837 let bytes = typ.as_bytes();
2838 let mut i = 0usize;
2839 while i + 1 < bytes.len() {
2840 match bytes[i] as char {
2841 '(' | '{' | '[' => depth += 1,
2842 ')' | '}' | ']' => depth = depth.saturating_sub(1),
2843 '-' if bytes[i + 1] as char == '>' && depth == 0 => return true,
2844 _ => {}
2845 }
2846 i += 1;
2847 }
2848
2849 if typ.starts_with('(') && typ.ends_with(')') {
2850 return looks_like_fun_type(&typ[1..typ.len() - 1]);
2851 }
2852 false
2853}
2854
2855fn split_fun_type(typ: &Type) -> (Vec<Type>, Type) {
2856 let mut args = Vec::new();
2857 let mut cur = typ.clone();
2858 while let TypeKind::Fun(arg, ret) = cur.as_ref() {
2859 args.push(arg.clone());
2860 cur = ret.clone();
2861 }
2862 (args, cur)
2863}
2864
2865fn in_scope_value_types_at_position(
2866 uri: &Url,
2867 text: &str,
2868 position: Position,
2869) -> Vec<(String, Type)> {
2870 let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) else {
2871 return Vec::new();
2872 };
2873 let Ok((program, mut ts, _imports, _import_diags)) =
2874 prepare_program_with_imports(uri, &program)
2875 else {
2876 return Vec::new();
2877 };
2878 if inject_program_decls(&mut ts, &program, None).is_err() {
2879 return Vec::new();
2880 }
2881
2882 let expr = program.expr_with_fns();
2883 let Ok((typed, _preds, _ty)) = ts.infer_typed(expr.as_ref()) else {
2884 return Vec::new();
2885 };
2886 let pos = lsp_to_rex_position(position);
2887
2888 fn visit(
2889 expr: &Expr,
2890 typed: &TypedExpr,
2891 pos: RexPosition,
2892 scope: &mut Vec<(String, Type)>,
2893 best: &mut Option<Vec<(String, Type)>>,
2894 ) {
2895 if !position_in_span(pos, *expr.span()) {
2896 return;
2897 }
2898 *best = Some(scope.clone());
2899
2900 match (expr, &typed.kind) {
2901 (
2902 Expr::Let(_span, var, _ann, def, body),
2903 TypedExprKind::Let {
2904 def: tdef,
2905 body: tbody,
2906 ..
2907 },
2908 ) => {
2909 if position_in_span(pos, *def.span()) {
2910 visit(def.as_ref(), tdef.as_ref(), pos, scope, best);
2911 return;
2912 }
2913 if position_in_span(pos, *body.span()) {
2914 scope.push((var.name.to_string(), tdef.typ.clone()));
2915 visit(body.as_ref(), tbody.as_ref(), pos, scope, best);
2916 scope.pop();
2917 }
2918 }
2919 (
2920 Expr::LetRec(_span, bindings, body),
2921 TypedExprKind::LetRec {
2922 bindings: typed_bindings,
2923 body: typed_body,
2924 },
2925 ) => {
2926 let base = scope.len();
2927 for ((name, _ann, _def), (_typed_name, typed_def)) in
2928 bindings.iter().zip(typed_bindings.iter())
2929 {
2930 scope.push((name.name.to_string(), typed_def.typ.clone()));
2931 }
2932 for ((_, _, def), (_, typed_def)) in bindings.iter().zip(typed_bindings.iter()) {
2933 if position_in_span(pos, *def.span()) {
2934 visit(def.as_ref(), typed_def, pos, scope, best);
2935 scope.truncate(base);
2936 return;
2937 }
2938 }
2939 if position_in_span(pos, *body.span()) {
2940 visit(body.as_ref(), typed_body.as_ref(), pos, scope, best);
2941 }
2942 scope.truncate(base);
2943 }
2944 (
2945 Expr::Lam(_span, _scope, param, _ann, _constraints, body),
2946 TypedExprKind::Lam {
2947 body: typed_body, ..
2948 },
2949 ) => {
2950 if let TypeKind::Fun(arg, _ret) = typed.typ.as_ref() {
2951 scope.push((param.name.to_string(), arg.clone()));
2952 visit(body.as_ref(), typed_body.as_ref(), pos, scope, best);
2953 scope.pop();
2954 }
2955 }
2956 (Expr::App(_span, fun, arg), TypedExprKind::App(tfun, targ)) => {
2957 if position_in_span(pos, *fun.span()) {
2958 visit(fun.as_ref(), tfun.as_ref(), pos, scope, best);
2959 } else if position_in_span(pos, *arg.span()) {
2960 visit(arg.as_ref(), targ.as_ref(), pos, scope, best);
2961 }
2962 }
2963 (Expr::Project(_span, base, _field), TypedExprKind::Project { expr: tbase, .. }) => {
2964 visit(base.as_ref(), tbase.as_ref(), pos, scope, best);
2965 }
2966 (
2967 Expr::Ite(_span, cond, then_expr, else_expr),
2968 TypedExprKind::Ite {
2969 cond: tcond,
2970 then_expr: tthen,
2971 else_expr: telse,
2972 },
2973 ) => {
2974 if position_in_span(pos, *cond.span()) {
2975 visit(cond.as_ref(), tcond.as_ref(), pos, scope, best);
2976 } else if position_in_span(pos, *then_expr.span()) {
2977 visit(then_expr.as_ref(), tthen.as_ref(), pos, scope, best);
2978 } else if position_in_span(pos, *else_expr.span()) {
2979 visit(else_expr.as_ref(), telse.as_ref(), pos, scope, best);
2980 }
2981 }
2982 (Expr::Tuple(_span, elems), TypedExprKind::Tuple(typed_elems))
2983 | (Expr::List(_span, elems), TypedExprKind::List(typed_elems)) => {
2984 for (elem, typed_elem) in elems.iter().zip(typed_elems.iter()) {
2985 if position_in_span(pos, *elem.span()) {
2986 visit(elem.as_ref(), typed_elem, pos, scope, best);
2987 break;
2988 }
2989 }
2990 }
2991 (Expr::Dict(_span, kvs), TypedExprKind::Dict(typed_kvs)) => {
2992 for (key, value) in kvs {
2993 if position_in_span(pos, *value.span())
2994 && let Some(typed_v) = typed_kvs.get(key)
2995 {
2996 visit(value.as_ref(), typed_v, pos, scope, best);
2997 break;
2998 }
2999 }
3000 }
3001 (
3002 Expr::RecordUpdate(_span, base, updates),
3003 TypedExprKind::RecordUpdate {
3004 base: tbase,
3005 updates: typed_updates,
3006 },
3007 ) => {
3008 if position_in_span(pos, *base.span()) {
3009 visit(base.as_ref(), tbase.as_ref(), pos, scope, best);
3010 } else {
3011 for (key, value) in updates {
3012 if position_in_span(pos, *value.span())
3013 && let Some(typed_v) = typed_updates.get(key)
3014 {
3015 visit(value.as_ref(), typed_v, pos, scope, best);
3016 break;
3017 }
3018 }
3019 }
3020 }
3021 (
3022 Expr::Match(_span, scrutinee, arms),
3023 TypedExprKind::Match {
3024 scrutinee: tscrutinee,
3025 arms: typed_arms,
3026 },
3027 ) => {
3028 if position_in_span(pos, *scrutinee.span()) {
3029 visit(scrutinee.as_ref(), tscrutinee.as_ref(), pos, scope, best);
3030 } else {
3031 for ((_pat, arm), (_typed_pat, typed_arm)) in arms.iter().zip(typed_arms.iter())
3032 {
3033 if position_in_span(pos, *arm.span()) {
3034 visit(arm.as_ref(), typed_arm, pos, scope, best);
3035 break;
3036 }
3037 }
3038 }
3039 }
3040 (Expr::Ann(_span, inner, _), _) => visit(inner.as_ref(), typed, pos, scope, best),
3041 _ => {}
3042 }
3043 }
3044
3045 let mut best = None;
3046 visit(expr.as_ref(), &typed, pos, &mut Vec::new(), &mut best);
3047 best.unwrap_or_default()
3048}
3049
3050fn hole_fill_candidates_at_position(
3051 uri: &Url,
3052 text: &str,
3053 position: Position,
3054) -> Vec<(String, String)> {
3055 let Some(target_type) = expected_type_at_position_type(uri, text, position) else {
3056 return Vec::new();
3057 };
3058 let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) else {
3059 return Vec::new();
3060 };
3061 let Ok((program, mut ts, _imports, _import_diags)) =
3062 prepare_program_with_imports(uri, &program)
3063 else {
3064 return Vec::new();
3065 };
3066 if inject_program_decls(&mut ts, &program, None).is_err() {
3067 return Vec::new();
3068 }
3069 let mut in_scope = in_scope_value_types_at_position(uri, text, position)
3070 .into_iter()
3071 .filter(|(name, _)| is_ident_like(name))
3072 .collect::<Vec<_>>();
3073 if in_scope.len() > MAX_SEMANTIC_IN_SCOPE_VALUES {
3074 in_scope = in_scope.split_off(in_scope.len().saturating_sub(MAX_SEMANTIC_IN_SCOPE_VALUES));
3075 }
3076
3077 let values = semantic_candidate_values(&ts);
3078
3079 let mut adapters: Vec<(String, Type, Type)> = Vec::new();
3080 for (name, schemes) in &values {
3081 let name = name.to_string();
3082 if !is_ident_like(&name) {
3083 continue;
3084 }
3085 for scheme in schemes {
3086 let (_preds, inst_ty) = instantiate(scheme, &mut ts.supply);
3087 let (args, ret) = split_fun_type(&inst_ty);
3088 if args.len() == 1 {
3089 adapters.push((name.clone(), args[0].clone(), ret));
3090 }
3091 }
3092 }
3093
3094 let mut out: Vec<(usize, usize, String, String)> = Vec::new();
3095 for (name, schemes) in values {
3096 let name = name.to_string();
3097 if !is_ident_like(&name) {
3098 continue;
3099 }
3100 for scheme in schemes {
3101 let (_preds, inst_ty) = instantiate(&scheme, &mut ts.supply);
3102 let (args, ret) = split_fun_type(&inst_ty);
3103 if args.is_empty()
3104 || args.len() > MAX_SEMANTIC_HOLE_FILL_ARITY
3105 || unify(&ret, &target_type).is_err()
3106 {
3107 continue;
3108 }
3109
3110 let mut unresolved = 0usize;
3111 let mut adapter_uses = 0usize;
3112 let mut rendered_args = Vec::new();
3113 for arg_ty in args {
3114 if let Some((value_name, _value_ty)) = in_scope
3115 .iter()
3116 .rev()
3117 .find(|(_, value_ty)| unify(value_ty, &arg_ty).is_ok())
3118 {
3119 rendered_args.push(value_name.clone());
3120 continue;
3121 }
3122
3123 let mut adapted = None;
3124 for (adapter_name, adapter_arg, adapter_ret) in &adapters {
3125 if unify(adapter_ret, &arg_ty).is_err() {
3126 continue;
3127 }
3128 if let Some((value_name, _value_ty)) = in_scope
3129 .iter()
3130 .rev()
3131 .find(|(_, value_ty)| unify(value_ty, adapter_arg).is_ok())
3132 {
3133 adapted = Some(format!("({adapter_name} {value_name})"));
3134 break;
3135 }
3136 }
3137 if let Some(expr) = adapted {
3138 adapter_uses += 1;
3139 rendered_args.push(expr);
3140 } else {
3141 unresolved += 1;
3142 rendered_args.push("?".to_string());
3143 }
3144 }
3145
3146 let replacement = format!("{name} {}", rendered_args.join(" "));
3147 out.push((unresolved, adapter_uses, name.clone(), replacement));
3148 }
3149 }
3150
3151 out.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)).then(a.2.cmp(&b.2)));
3152 out.dedup_by(|a, b| a.2 == b.2 && a.3 == b.3);
3153 if out.len() > MAX_SEMANTIC_CANDIDATES {
3154 out.truncate(MAX_SEMANTIC_CANDIDATES);
3155 }
3156 out.into_iter()
3157 .map(|(_u, _a, name, replacement)| (name, replacement))
3158 .collect()
3159}
3160
3161fn levenshtein_distance(left: &str, right: &str) -> usize {
3162 if left == right {
3163 return 0;
3164 }
3165 if left.is_empty() {
3166 return right.chars().count();
3167 }
3168 if right.is_empty() {
3169 return left.chars().count();
3170 }
3171
3172 let right_len = right.chars().count();
3173 let mut prev: Vec<usize> = (0..=right_len).collect();
3174 let mut cur = vec![0usize; right_len + 1];
3175
3176 for (i, lc) in left.chars().enumerate() {
3177 cur[0] = i + 1;
3178 for (j, rc) in right.chars().enumerate() {
3179 let insert_cost = cur[j] + 1;
3180 let delete_cost = prev[j + 1] + 1;
3181 let replace_cost = prev[j] + usize::from(lc != rc);
3182 cur[j + 1] = insert_cost.min(delete_cost).min(replace_cost);
3183 }
3184 std::mem::swap(&mut prev, &mut cur);
3185 }
3186
3187 prev[right_len]
3188}
3189
3190#[allow(deprecated)]
3191fn symbol_for_decl(decl: &Decl) -> Option<DocumentSymbol> {
3192 match decl {
3193 Decl::Type(td) => Some(DocumentSymbol {
3194 name: td.name.to_string(),
3195 detail: Some("type".to_string()),
3196 kind: SymbolKind::ENUM,
3197 tags: None,
3198 deprecated: None,
3199 range: span_to_range(td.span),
3200 selection_range: span_to_range(td.span),
3201 children: Some(
3202 td.variants
3203 .iter()
3204 .map(|variant| DocumentSymbol {
3205 name: variant.name.to_string(),
3206 detail: Some("variant".to_string()),
3207 kind: SymbolKind::ENUM_MEMBER,
3208 tags: None,
3209 deprecated: None,
3210 range: span_to_range(td.span),
3211 selection_range: span_to_range(td.span),
3212 children: None,
3213 })
3214 .collect(),
3215 ),
3216 }),
3217 Decl::Fn(fd) => Some(DocumentSymbol {
3218 name: fd.name.name.to_string(),
3219 detail: Some("fn".to_string()),
3220 kind: SymbolKind::FUNCTION,
3221 tags: None,
3222 deprecated: None,
3223 range: span_to_range(fd.span),
3224 selection_range: span_to_range(fd.name.span),
3225 children: None,
3226 }),
3227 Decl::DeclareFn(df) => Some(DocumentSymbol {
3228 name: df.name.name.to_string(),
3229 detail: Some("declare fn".to_string()),
3230 kind: SymbolKind::FUNCTION,
3231 tags: None,
3232 deprecated: None,
3233 range: span_to_range(df.span),
3234 selection_range: span_to_range(df.name.span),
3235 children: None,
3236 }),
3237 Decl::Import(id) => Some(DocumentSymbol {
3238 name: id.alias.to_string(),
3239 detail: Some("import".to_string()),
3240 kind: SymbolKind::MODULE,
3241 tags: None,
3242 deprecated: None,
3243 range: span_to_range(id.span),
3244 selection_range: span_to_range(id.span),
3245 children: None,
3246 }),
3247 Decl::Class(cd) => Some(DocumentSymbol {
3248 name: cd.name.to_string(),
3249 detail: Some("class".to_string()),
3250 kind: SymbolKind::INTERFACE,
3251 tags: None,
3252 deprecated: None,
3253 range: span_to_range(cd.span),
3254 selection_range: span_to_range(cd.span),
3255 children: Some(
3256 cd.methods
3257 .iter()
3258 .map(|method| DocumentSymbol {
3259 name: method.name.to_string(),
3260 detail: Some("method".to_string()),
3261 kind: SymbolKind::METHOD,
3262 tags: None,
3263 deprecated: None,
3264 range: span_to_range(cd.span),
3265 selection_range: span_to_range(cd.span),
3266 children: None,
3267 })
3268 .collect(),
3269 ),
3270 }),
3271 Decl::Instance(id) => Some(DocumentSymbol {
3272 name: format!("instance {}", id.class),
3273 detail: Some("instance".to_string()),
3274 kind: SymbolKind::OBJECT,
3275 tags: None,
3276 deprecated: None,
3277 range: span_to_range(id.span),
3278 selection_range: span_to_range(id.span),
3279 children: Some(
3280 id.methods
3281 .iter()
3282 .map(|method| DocumentSymbol {
3283 name: method.name.to_string(),
3284 detail: Some("method".to_string()),
3285 kind: SymbolKind::METHOD,
3286 tags: None,
3287 deprecated: None,
3288 range: span_to_range(*method.body.span()),
3289 selection_range: span_to_range(*method.body.span()),
3290 children: None,
3291 })
3292 .collect(),
3293 ),
3294 }),
3295 }
3296}
3297
3298fn document_symbols_for_source(uri: &Url, text: &str) -> Vec<DocumentSymbol> {
3299 let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) else {
3300 return Vec::new();
3301 };
3302 program.decls.iter().filter_map(symbol_for_decl).collect()
3303}
3304
3305fn full_document_range(text: &str) -> Range {
3306 let mut line = 0u32;
3307 let mut col = 0u32;
3308 for ch in text.chars() {
3309 if ch == '\n' {
3310 line += 1;
3311 col = 0;
3312 } else {
3313 col += 1;
3314 }
3315 }
3316 Range {
3317 start: Position {
3318 line: 0,
3319 character: 0,
3320 },
3321 end: Position {
3322 line,
3323 character: col,
3324 },
3325 }
3326}
3327
3328fn format_source(text: &str) -> String {
3329 let mut out = String::new();
3330 let mut first = true;
3331 for line in text.lines() {
3332 if !first {
3333 out.push('\n');
3334 }
3335 first = false;
3336 out.push_str(line.trim_end());
3337 }
3338 if text.ends_with('\n') || !out.is_empty() {
3339 out.push('\n');
3340 }
3341 out
3342}
3343
3344fn format_edits_for_source(text: &str) -> Option<Vec<TextEdit>> {
3345 let formatted = format_source(text);
3346 if formatted == text {
3347 return None;
3348 }
3349 Some(vec![TextEdit {
3350 range: full_document_range(text),
3351 new_text: formatted,
3352 }])
3353}
3354
3355fn diagnostics_from_text(uri: &Url, text: &str) -> Vec<Diagnostic> {
3356 let mut diagnostics = Vec::new();
3357
3358 match tokenize_and_parse_cached(uri, text) {
3359 Ok((tokens, program)) => {
3360 push_comment_diagnostics(&tokens, &mut diagnostics);
3361 if diagnostics.len() < MAX_DIAGNOSTICS {
3362 push_type_diagnostics(uri, text, &program, &mut diagnostics);
3363 }
3364 }
3365 Err(TokenizeOrParseError::Lex(err)) => {
3366 let (span, message) = match err {
3367 LexicalError::UnexpectedToken(span) => (span, "Unexpected token".to_string()),
3368 LexicalError::InvalidLiteral {
3369 kind,
3370 text,
3371 error,
3372 span,
3373 } => (span, format!("invalid {kind} literal `{text}`: {error}")),
3374 LexicalError::Internal(msg) => (
3375 Span::new(1, 1, 1, 1),
3376 format!("internal lexer error: {msg}"),
3377 ),
3378 };
3379 diagnostics.push(diagnostic_for_span(span, message));
3380 }
3381 Err(TokenizeOrParseError::Parse(errors)) => {
3382 for err in errors {
3383 diagnostics.push(diagnostic_for_span(err.span, err.message));
3384 if diagnostics.len() >= MAX_DIAGNOSTICS {
3385 break;
3386 }
3387 }
3388 }
3389 }
3390
3391 diagnostics
3392}
3393
3394struct HoverType {
3395 span: Span,
3396 label: String,
3397 typ: String,
3398 overloads: Vec<String>,
3399}
3400
3401fn hover_type_contents(uri: &Url, text: &str, position: Position) -> Option<HoverContents> {
3402 let (tokens, program) = tokenize_and_parse_cached(uri, text).ok()?;
3403 let (name, name_span, name_is_ident) = name_token_at_position(&tokens, position)?;
3404 let (program, mut ts, _imports, _import_diags) =
3405 prepare_program_with_imports(uri, &program).ok()?;
3406
3407 let pos = lsp_to_rex_position(position);
3408
3409 let mut target_instance: Option<(usize, usize)> = None;
3412 for (decl_idx, decl) in program.decls.iter().enumerate() {
3413 let Decl::Instance(inst) = decl else {
3414 continue;
3415 };
3416 for (method_idx, method) in inst.methods.iter().enumerate() {
3417 if position_in_span(pos, *method.body.span()) {
3418 target_instance = Some((decl_idx, method_idx));
3419 break;
3420 }
3421 }
3422 if target_instance.is_some() {
3423 break;
3424 }
3425 }
3426
3427 let (_instances, prepared_target_instance) = inject_program_decls(
3428 &mut ts,
3429 &program,
3430 target_instance.map(|(decl_idx, _)| decl_idx),
3431 )
3432 .ok()?;
3433
3434 let expr_with_fns = program.expr_with_fns();
3435
3436 let root_expr: &Expr;
3437 let typed_root: TypedExpr;
3438
3439 if let Some((decl_idx, method_idx)) = target_instance {
3440 let Decl::Instance(inst) = &program.decls[decl_idx] else {
3441 return None;
3442 };
3443 let prepared = prepared_target_instance?;
3444 let method = inst.methods.get(method_idx)?;
3445 typed_root = ts.typecheck_instance_method(&prepared, method).ok()?;
3446 root_expr = method.body.as_ref();
3447 } else {
3448 let (typed, _preds, _) = ts.infer_typed(expr_with_fns.as_ref()).ok()?;
3449 typed_root = typed;
3450 root_expr = expr_with_fns.as_ref();
3451 }
3452
3453 let hover = hover_type_in_expr(
3454 &mut ts,
3455 root_expr,
3456 &typed_root,
3457 pos,
3458 &name,
3459 name_span,
3460 name_is_ident,
3461 )?;
3462
3463 let mut md = String::new();
3464 md.push_str("```rex\n");
3465 md.push_str(&hover.label);
3466 md.push_str(" : ");
3467 md.push_str(&hover.typ);
3468 md.push_str("\n```");
3469
3470 if !hover.overloads.is_empty() {
3471 md.push_str("\n\nOverloads:\n");
3472 for ov in &hover.overloads {
3473 md.push_str("- `");
3474 md.push_str(ov);
3475 md.push_str("`\n");
3476 }
3477 }
3478
3479 Some(HoverContents::Markup(MarkupContent {
3480 kind: MarkupKind::Markdown,
3481 value: md,
3482 }))
3483}
3484
3485fn expected_type_at_position(uri: &Url, text: &str, position: Position) -> Option<String> {
3486 expected_type_at_position_type(uri, text, position).map(|ty| ty.to_string())
3487}
3488
3489fn inferred_type_at_position(uri: &Url, text: &str, position: Position) -> Option<String> {
3490 inferred_type_at_position_type(uri, text, position).map(|ty| ty.to_string())
3491}
3492
3493fn expected_type_at_position_type(uri: &Url, text: &str, position: Position) -> Option<Type> {
3494 let (_tokens, program) = tokenize_and_parse_cached(uri, text).ok()?;
3495 let (program, mut ts, _imports, _import_diags) =
3496 prepare_program_with_imports(uri, &program).ok()?;
3497
3498 let pos = lsp_to_rex_position(position);
3499
3500 let mut target_instance: Option<(usize, usize)> = None;
3502 for (decl_idx, decl) in program.decls.iter().enumerate() {
3503 let Decl::Instance(inst) = decl else {
3504 continue;
3505 };
3506 for (method_idx, method) in inst.methods.iter().enumerate() {
3507 if position_in_span(pos, *method.body.span()) {
3508 target_instance = Some((decl_idx, method_idx));
3509 break;
3510 }
3511 }
3512 if target_instance.is_some() {
3513 break;
3514 }
3515 }
3516
3517 let (_instances, prepared_target_instance) = inject_program_decls(
3518 &mut ts,
3519 &program,
3520 target_instance.map(|(decl_idx, _)| decl_idx),
3521 )
3522 .ok()?;
3523
3524 let expr_with_fns = program.expr_with_fns();
3525 let root_expr: &Expr;
3526 let typed_root: TypedExpr;
3527
3528 if let Some((decl_idx, method_idx)) = target_instance {
3529 let Decl::Instance(inst) = &program.decls[decl_idx] else {
3530 return None;
3531 };
3532 let prepared = prepared_target_instance?;
3533 let method = inst.methods.get(method_idx)?;
3534 typed_root = ts.typecheck_instance_method(&prepared, method).ok()?;
3535 root_expr = method.body.as_ref();
3536 } else {
3537 let (typed, _preds, _) = ts.infer_typed(expr_with_fns.as_ref()).ok()?;
3538 typed_root = typed;
3539 root_expr = expr_with_fns.as_ref();
3540 }
3541
3542 expected_type_in_expr(root_expr, &typed_root, pos)
3543}
3544
3545fn inferred_type_at_position_type(uri: &Url, text: &str, position: Position) -> Option<Type> {
3546 let (_tokens, program) = tokenize_and_parse_cached(uri, text).ok()?;
3547 let (program, mut ts, _imports, _import_diags) =
3548 prepare_program_with_imports(uri, &program).ok()?;
3549
3550 let pos = lsp_to_rex_position(position);
3551
3552 let mut target_instance: Option<(usize, usize)> = None;
3553 for (decl_idx, decl) in program.decls.iter().enumerate() {
3554 let Decl::Instance(inst) = decl else {
3555 continue;
3556 };
3557 for (method_idx, method) in inst.methods.iter().enumerate() {
3558 if position_in_span(pos, *method.body.span()) {
3559 target_instance = Some((decl_idx, method_idx));
3560 break;
3561 }
3562 }
3563 if target_instance.is_some() {
3564 break;
3565 }
3566 }
3567
3568 let (_instances, prepared_target_instance) = inject_program_decls(
3569 &mut ts,
3570 &program,
3571 target_instance.map(|(decl_idx, _)| decl_idx),
3572 )
3573 .ok()?;
3574
3575 let expr_with_fns = program.expr_with_fns();
3576 let root_expr: &Expr;
3577 let typed_root: TypedExpr;
3578
3579 if let Some((decl_idx, method_idx)) = target_instance {
3580 let Decl::Instance(inst) = &program.decls[decl_idx] else {
3581 return None;
3582 };
3583 let prepared = prepared_target_instance?;
3584 let method = inst.methods.get(method_idx)?;
3585 typed_root = ts.typecheck_instance_method(&prepared, method).ok()?;
3586 root_expr = method.body.as_ref();
3587 } else {
3588 let (typed, _preds, _) = ts.infer_typed(expr_with_fns.as_ref()).ok()?;
3589 typed_root = typed;
3590 root_expr = expr_with_fns.as_ref();
3591 }
3592
3593 inferred_type_in_expr(root_expr, &typed_root, pos)
3594}
3595
3596fn expected_type_in_expr(expr: &Expr, typed: &TypedExpr, pos: RexPosition) -> Option<Type> {
3597 #[derive(Clone)]
3598 struct Candidate {
3599 span: Span,
3600 typ: Type,
3601 }
3602
3603 fn span_size(span: Span) -> (usize, usize) {
3604 (
3605 span.end.line.saturating_sub(span.begin.line),
3606 span.end.column.saturating_sub(span.begin.column),
3607 )
3608 }
3609
3610 fn consider(best: &mut Option<Candidate>, span: Span, typ: &Type) {
3611 let replace = best
3612 .as_ref()
3613 .is_none_or(|cur| span_size(span) < span_size(cur.span));
3614 if replace {
3615 *best = Some(Candidate {
3616 span,
3617 typ: typ.clone(),
3618 });
3619 }
3620 }
3621
3622 fn visit(
3623 expr: &Expr,
3624 typed: &TypedExpr,
3625 pos: RexPosition,
3626 expected: Option<&Type>,
3627 best: &mut Option<Candidate>,
3628 ) {
3629 if !position_in_span(pos, *expr.span()) {
3630 return;
3631 }
3632
3633 if let Some(expected) = expected {
3634 consider(best, *expr.span(), expected);
3635 }
3636
3637 match (expr, &typed.kind) {
3638 (
3639 Expr::Let(_span, _name, _ann, def, body),
3640 TypedExprKind::Let {
3641 def: tdef,
3642 body: tbody,
3643 ..
3644 },
3645 ) => {
3646 visit(def.as_ref(), tdef.as_ref(), pos, Some(&tdef.typ), best);
3647 visit(body.as_ref(), tbody.as_ref(), pos, Some(&typed.typ), best);
3648 }
3649 (
3650 Expr::LetRec(_span, bindings, body),
3651 TypedExprKind::LetRec {
3652 bindings: typed_bindings,
3653 body: typed_body,
3654 },
3655 ) => {
3656 for ((_name, _ann, def), (_typed_name, typed_def)) in
3657 bindings.iter().zip(typed_bindings.iter())
3658 {
3659 visit(def.as_ref(), typed_def, pos, Some(&typed_def.typ), best);
3660 }
3661 visit(
3662 body.as_ref(),
3663 typed_body.as_ref(),
3664 pos,
3665 Some(&typed.typ),
3666 best,
3667 );
3668 }
3669 (
3670 Expr::Lam(_span, _scope, _param, _ann, _constraints, body),
3671 TypedExprKind::Lam {
3672 body: typed_body, ..
3673 },
3674 ) => {
3675 let body_expected = match typed.typ.as_ref() {
3676 TypeKind::Fun(_arg, ret) => Some(ret),
3677 _ => None,
3678 };
3679 visit(body.as_ref(), typed_body.as_ref(), pos, body_expected, best);
3680 }
3681 (Expr::App(_span, f, x), TypedExprKind::App(tf, tx)) => {
3682 let expected_arg = match tf.typ.as_ref() {
3683 TypeKind::Fun(arg, _ret) => Some(arg),
3684 _ => None,
3685 };
3686 visit(x.as_ref(), tx.as_ref(), pos, expected_arg, best);
3687
3688 let expected_fun = Type::fun(tx.typ.clone(), typed.typ.clone());
3689 visit(f.as_ref(), tf.as_ref(), pos, Some(&expected_fun), best);
3690 }
3691 (Expr::Project(_span, base, _field), TypedExprKind::Project { expr: tbase, .. }) => {
3692 visit(base.as_ref(), tbase.as_ref(), pos, None, best);
3693 }
3694 (
3695 Expr::Ite(_span, cond, then_expr, else_expr),
3696 TypedExprKind::Ite {
3697 cond: tcond,
3698 then_expr: tthen,
3699 else_expr: telse,
3700 },
3701 ) => {
3702 let bool_ty = Type::builtin(BuiltinTypeId::Bool);
3703 visit(cond.as_ref(), tcond.as_ref(), pos, Some(&bool_ty), best);
3704 visit(
3705 then_expr.as_ref(),
3706 tthen.as_ref(),
3707 pos,
3708 Some(&typed.typ),
3709 best,
3710 );
3711 visit(
3712 else_expr.as_ref(),
3713 telse.as_ref(),
3714 pos,
3715 Some(&typed.typ),
3716 best,
3717 );
3718 }
3719 (Expr::Tuple(_span, elems), TypedExprKind::Tuple(typed_elems)) => {
3720 for (elem, typed_elem) in elems.iter().zip(typed_elems.iter()) {
3721 visit(elem.as_ref(), typed_elem, pos, Some(&typed_elem.typ), best);
3722 }
3723 }
3724 (Expr::List(_span, elems), TypedExprKind::List(typed_elems)) => {
3725 let list_elem_expected = match typed.typ.as_ref() {
3726 TypeKind::App(head, elem) => match head.as_ref() {
3727 TypeKind::Con(tc)
3728 if tc.builtin_id == Some(BuiltinTypeId::List) && tc.arity == 1 =>
3729 {
3730 Some(elem)
3731 }
3732 _ => None,
3733 },
3734 _ => None,
3735 };
3736 for (elem, typed_elem) in elems.iter().zip(typed_elems.iter()) {
3737 let expected = list_elem_expected.unwrap_or(&typed_elem.typ);
3738 visit(elem.as_ref(), typed_elem, pos, Some(expected), best);
3739 }
3740 }
3741 (Expr::Dict(_span, kvs), TypedExprKind::Dict(typed_kvs)) => {
3742 for (key, value) in kvs {
3743 if let Some(typed_value) = typed_kvs.get(key) {
3744 visit(
3745 value.as_ref(),
3746 typed_value,
3747 pos,
3748 Some(&typed_value.typ),
3749 best,
3750 );
3751 }
3752 }
3753 }
3754 (
3755 Expr::RecordUpdate(_span, base, updates),
3756 TypedExprKind::RecordUpdate {
3757 base: typed_base,
3758 updates: typed_updates,
3759 },
3760 ) => {
3761 visit(base.as_ref(), typed_base.as_ref(), pos, None, best);
3762 for (key, value) in updates {
3763 if let Some(typed_value) = typed_updates.get(key) {
3764 visit(
3765 value.as_ref(),
3766 typed_value,
3767 pos,
3768 Some(&typed_value.typ),
3769 best,
3770 );
3771 }
3772 }
3773 }
3774 (
3775 Expr::Match(_span, scrutinee, arms),
3776 TypedExprKind::Match {
3777 scrutinee: tscrutinee,
3778 arms: typed_arms,
3779 },
3780 ) => {
3781 visit(
3782 scrutinee.as_ref(),
3783 tscrutinee.as_ref(),
3784 pos,
3785 Some(&tscrutinee.typ),
3786 best,
3787 );
3788 for ((_pat, arm), (_typed_pat, typed_arm)) in arms.iter().zip(typed_arms.iter()) {
3789 visit(arm.as_ref(), typed_arm, pos, Some(&typed.typ), best);
3790 }
3791 }
3792 (Expr::Ann(_span, inner, _ann), _) => {
3793 visit(inner.as_ref(), typed, pos, Some(&typed.typ), best);
3794 }
3795 _ => {}
3796 }
3797 }
3798
3799 let mut best: Option<Candidate> = None;
3800 visit(expr, typed, pos, None, &mut best);
3801 best.map(|candidate| candidate.typ)
3802}
3803
3804fn inferred_type_in_expr(expr: &Expr, typed: &TypedExpr, pos: RexPosition) -> Option<Type> {
3805 fn span_size(span: Span) -> (usize, usize) {
3806 (
3807 span.end.line.saturating_sub(span.begin.line),
3808 span.end.column.saturating_sub(span.begin.column),
3809 )
3810 }
3811
3812 fn visit(expr: &Expr, typed: &TypedExpr, pos: RexPosition, best: &mut Option<(Span, Type)>) {
3813 let span = *expr.span();
3814 if !position_in_span(pos, span) {
3815 return;
3816 }
3817 if best
3818 .as_ref()
3819 .is_none_or(|(best_span, _)| span_size(span) < span_size(*best_span))
3820 {
3821 *best = Some((span, typed.typ.clone()));
3822 }
3823
3824 match (expr, &typed.kind) {
3825 (
3826 Expr::Let(_, _, _, def, body),
3827 TypedExprKind::Let {
3828 def: tdef,
3829 body: tbody,
3830 ..
3831 },
3832 ) => {
3833 visit(def.as_ref(), tdef.as_ref(), pos, best);
3834 visit(body.as_ref(), tbody.as_ref(), pos, best);
3835 }
3836 (
3837 Expr::LetRec(_, bindings, body),
3838 TypedExprKind::LetRec {
3839 bindings: typed_bindings,
3840 body: typed_body,
3841 },
3842 ) => {
3843 for ((_, _, def), (_, typed_def)) in bindings.iter().zip(typed_bindings.iter()) {
3844 visit(def.as_ref(), typed_def, pos, best);
3845 }
3846 visit(body.as_ref(), typed_body.as_ref(), pos, best);
3847 }
3848 (
3849 Expr::Lam(_, _, _, _, _, body),
3850 TypedExprKind::Lam {
3851 body: typed_body, ..
3852 },
3853 ) => {
3854 visit(body.as_ref(), typed_body.as_ref(), pos, best);
3855 }
3856 (Expr::App(_, f, x), TypedExprKind::App(tf, tx)) => {
3857 visit(f.as_ref(), tf.as_ref(), pos, best);
3858 visit(x.as_ref(), tx.as_ref(), pos, best);
3859 }
3860 (Expr::Project(_, base, _), TypedExprKind::Project { expr: tbase, .. }) => {
3861 visit(base.as_ref(), tbase.as_ref(), pos, best);
3862 }
3863 (
3864 Expr::Ite(_, cond, then_expr, else_expr),
3865 TypedExprKind::Ite {
3866 cond: tcond,
3867 then_expr: tthen,
3868 else_expr: telse,
3869 },
3870 ) => {
3871 visit(cond.as_ref(), tcond.as_ref(), pos, best);
3872 visit(then_expr.as_ref(), tthen.as_ref(), pos, best);
3873 visit(else_expr.as_ref(), telse.as_ref(), pos, best);
3874 }
3875 (Expr::Tuple(_, elems), TypedExprKind::Tuple(typed_elems))
3876 | (Expr::List(_, elems), TypedExprKind::List(typed_elems)) => {
3877 for (elem, typed_elem) in elems.iter().zip(typed_elems.iter()) {
3878 visit(elem.as_ref(), typed_elem, pos, best);
3879 }
3880 }
3881 (Expr::Dict(_, kvs), TypedExprKind::Dict(typed_kvs)) => {
3882 for (key, value) in kvs {
3883 if let Some(typed_value) = typed_kvs.get(key) {
3884 visit(value.as_ref(), typed_value, pos, best);
3885 }
3886 }
3887 }
3888 (
3889 Expr::RecordUpdate(_, base, updates),
3890 TypedExprKind::RecordUpdate {
3891 base: typed_base,
3892 updates: typed_updates,
3893 },
3894 ) => {
3895 visit(base.as_ref(), typed_base.as_ref(), pos, best);
3896 for (key, value) in updates {
3897 if let Some(typed_value) = typed_updates.get(key) {
3898 visit(value.as_ref(), typed_value, pos, best);
3899 }
3900 }
3901 }
3902 (
3903 Expr::Match(_, scrutinee, arms),
3904 TypedExprKind::Match {
3905 scrutinee: tscrutinee,
3906 arms: typed_arms,
3907 },
3908 ) => {
3909 visit(scrutinee.as_ref(), tscrutinee.as_ref(), pos, best);
3910 for ((_pat, arm), (_typed_pat, typed_arm)) in arms.iter().zip(typed_arms.iter()) {
3911 visit(arm.as_ref(), typed_arm, pos, best);
3912 }
3913 }
3914 (Expr::Ann(_, inner, _), _) => visit(inner.as_ref(), typed, pos, best),
3915 _ => {}
3916 }
3917 }
3918
3919 let mut best: Option<(Span, Type)> = None;
3920 visit(expr, typed, pos, &mut best);
3921 best.map(|(_, ty)| ty)
3922}
3923
3924fn functions_producing_expected_type_at_position(
3925 uri: &Url,
3926 text: &str,
3927 position: Position,
3928) -> Vec<(String, String)> {
3929 let Some(target_type) = expected_type_at_position_type(uri, text, position) else {
3930 return Vec::new();
3931 };
3932
3933 let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) else {
3934 return Vec::new();
3935 };
3936 let Ok((program, mut ts, _imports, _import_diags)) =
3937 prepare_program_with_imports(uri, &program)
3938 else {
3939 return Vec::new();
3940 };
3941 if inject_program_decls(&mut ts, &program, None).is_err() {
3942 return Vec::new();
3943 }
3944
3945 let values = semantic_candidate_values(&ts);
3946
3947 let mut out = Vec::new();
3948 for (name, schemes) in values {
3949 for scheme in schemes {
3950 let (_preds, inst_ty) = instantiate(&scheme, &mut ts.supply);
3951 let mut cur = &inst_ty;
3952 let mut is_function = false;
3953 while let TypeKind::Fun(_, ret) = cur.as_ref() {
3954 is_function = true;
3955 cur = ret;
3956 }
3957 if !is_function {
3958 continue;
3959 }
3960 if unify(cur, &target_type).is_ok() {
3961 out.push((name.to_string(), scheme.typ.to_string()));
3962 }
3963 }
3964 }
3965
3966 out.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
3967 out.dedup();
3968 if out.len() > MAX_SEMANTIC_CANDIDATES {
3969 out.truncate(MAX_SEMANTIC_CANDIDATES);
3970 }
3971 out
3972}
3973
3974fn functions_accepting_inferred_type_at_position(
3975 uri: &Url,
3976 text: &str,
3977 position: Position,
3978) -> Vec<(String, String)> {
3979 let Some(source_type) = inferred_type_at_position_type(uri, text, position) else {
3980 return Vec::new();
3981 };
3982
3983 let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) else {
3984 return Vec::new();
3985 };
3986 let Ok((program, mut ts, _imports, _import_diags)) =
3987 prepare_program_with_imports(uri, &program)
3988 else {
3989 return Vec::new();
3990 };
3991 if inject_program_decls(&mut ts, &program, None).is_err() {
3992 return Vec::new();
3993 }
3994
3995 let values = semantic_candidate_values(&ts);
3996
3997 let mut out = Vec::new();
3998 for (name, schemes) in values {
3999 let name = name.to_string();
4000 if !is_ident_like(&name) {
4001 continue;
4002 }
4003 for scheme in schemes {
4004 let (_preds, inst_ty) = instantiate(&scheme, &mut ts.supply);
4005 let (args, _ret) = split_fun_type(&inst_ty);
4006 if let Some(first_arg) = args.first()
4007 && unify(first_arg, &source_type).is_ok()
4008 {
4009 out.push((name.clone(), scheme.typ.to_string()));
4010 }
4011 }
4012 }
4013
4014 out.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
4015 out.dedup();
4016 if out.len() > MAX_SEMANTIC_CANDIDATES {
4017 out.truncate(MAX_SEMANTIC_CANDIDATES);
4018 }
4019 out
4020}
4021
4022fn adapters_from_inferred_to_expected_at_position(
4023 uri: &Url,
4024 text: &str,
4025 position: Position,
4026) -> Vec<(String, String)> {
4027 let Some(source_type) = inferred_type_at_position_type(uri, text, position) else {
4028 return Vec::new();
4029 };
4030 let Some(target_type) = expected_type_at_position_type(uri, text, position) else {
4031 return Vec::new();
4032 };
4033
4034 let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) else {
4035 return Vec::new();
4036 };
4037 let Ok((program, mut ts, _imports, _import_diags)) =
4038 prepare_program_with_imports(uri, &program)
4039 else {
4040 return Vec::new();
4041 };
4042 if inject_program_decls(&mut ts, &program, None).is_err() {
4043 return Vec::new();
4044 }
4045
4046 let values = semantic_candidate_values(&ts);
4047
4048 let mut out = Vec::new();
4049 for (name, schemes) in values {
4050 let name = name.to_string();
4051 if !is_ident_like(&name) {
4052 continue;
4053 }
4054 for scheme in schemes {
4055 let (_preds, inst_ty) = instantiate(&scheme, &mut ts.supply);
4056 let (args, ret) = split_fun_type(&inst_ty);
4057 if args.len() == 1
4058 && unify(&args[0], &source_type).is_ok()
4059 && unify(&ret, &target_type).is_ok()
4060 {
4061 out.push((name.clone(), scheme.typ.to_string()));
4062 }
4063 }
4064 }
4065
4066 out.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
4067 out.dedup();
4068 if out.len() > MAX_SEMANTIC_CANDIDATES {
4069 out.truncate(MAX_SEMANTIC_CANDIDATES);
4070 }
4071 out
4072}
4073
4074fn functions_compatible_with_in_scope_values_at_position(
4075 uri: &Url,
4076 text: &str,
4077 position: Position,
4078) -> Vec<String> {
4079 let produced = functions_producing_expected_type_at_position(uri, text, position);
4080 let mut produced_by_name: HashMap<String, Vec<String>> = HashMap::new();
4081 for (name, typ) in produced {
4082 produced_by_name.entry(name).or_default().push(typ);
4083 }
4084
4085 let mut out = Vec::new();
4086 for (name, replacement) in hole_fill_candidates_at_position(uri, text, position) {
4087 if replacement.contains('?') {
4088 continue;
4089 }
4090 if let Some(types) = produced_by_name.get(&name) {
4091 for typ in types {
4092 out.push(format!("{name} : {typ} => {replacement}"));
4093 }
4094 } else {
4095 out.push(format!("{name} => {replacement}"));
4096 }
4097 }
4098 out.sort();
4099 out.dedup();
4100 if out.len() > MAX_SEMANTIC_CANDIDATES {
4101 out.truncate(MAX_SEMANTIC_CANDIDATES);
4102 }
4103 out
4104}
4105
4106fn execute_query_command_for_document(
4107 command: &str,
4108 uri: &Url,
4109 text: &str,
4110 position: Position,
4111) -> Option<Value> {
4112 match command {
4113 CMD_EXPECTED_TYPE_AT => Some(match expected_type_at_position(uri, text, position) {
4114 Some(typ) => json!({ "expectedType": typ }),
4115 None => Value::Null,
4116 }),
4117 CMD_FUNCTIONS_ACCEPTING_INFERRED_TYPE_AT => Some(json!({
4118 "inferredType": inferred_type_at_position(uri, text, position),
4119 "items": functions_accepting_inferred_type_at_position(uri, text, position)
4120 .into_iter()
4121 .map(|(name, typ)| format!("{name} : {typ}"))
4122 .collect::<Vec<_>>()
4123 })),
4124 CMD_ADAPTERS_FROM_INFERRED_TO_EXPECTED_AT => Some(json!({
4125 "inferredType": inferred_type_at_position(uri, text, position),
4126 "expectedType": expected_type_at_position(uri, text, position),
4127 "items": adapters_from_inferred_to_expected_at_position(uri, text, position)
4128 .into_iter()
4129 .map(|(name, typ)| format!("{name} : {typ}"))
4130 .collect::<Vec<_>>()
4131 })),
4132 CMD_FUNCTIONS_COMPATIBLE_WITH_IN_SCOPE_VALUES_AT => Some(json!({
4133 "items": functions_compatible_with_in_scope_values_at_position(uri, text, position)
4134 })),
4135 CMD_FUNCTIONS_PRODUCING_EXPECTED_TYPE_AT => {
4136 let items = functions_producing_expected_type_at_position(uri, text, position)
4137 .into_iter()
4138 .map(|(name, typ)| format!("{name} : {typ}"))
4139 .collect::<Vec<_>>();
4140 Some(json!({ "items": items }))
4141 }
4142 _ => None,
4143 }
4144}
4145
4146fn execute_query_command_for_document_without_position(
4147 command: &str,
4148 uri: &Url,
4149 text: &str,
4150) -> Option<Value> {
4151 match command {
4152 CMD_HOLES_EXPECTED_TYPES => Some(json!({
4153 "holes": hole_expected_types_for_document(uri, text)
4154 })),
4155 _ => None,
4156 }
4157}
4158
4159fn workspace_edit_fingerprint(edit: &WorkspaceEdit) -> String {
4160 let mut payload = String::new();
4161 if let Some(changes) = &edit.changes {
4162 let mut uris = changes.keys().cloned().collect::<Vec<_>>();
4163 uris.sort_by(|a, b| a.as_str().cmp(b.as_str()));
4164 for uri in uris {
4165 payload.push_str(uri.as_str());
4166 payload.push('\n');
4167 if let Some(edits) = changes.get(&uri) {
4168 for edit in edits {
4169 payload.push_str(&format!(
4170 "{}:{}-{}:{}\n",
4171 edit.range.start.line,
4172 edit.range.start.character,
4173 edit.range.end.line,
4174 edit.range.end.character
4175 ));
4176 payload.push_str(&edit.new_text);
4177 payload.push('\n');
4178 }
4179 }
4180 }
4181 }
4182 if let Some(document_changes) = &edit.document_changes
4183 && let Ok(encoded) = serde_json::to_string(document_changes)
4184 {
4185 payload.push_str(&encoded);
4186 }
4187 if let Some(change_annotations) = &edit.change_annotations
4188 && let Ok(encoded) = serde_json::to_string(change_annotations)
4189 {
4190 payload.push_str(&encoded);
4191 }
4192 sha256_hex(payload.as_bytes())
4193}
4194
4195fn semantic_quick_fixes_for_range(
4196 uri: &Url,
4197 text: &str,
4198 cursor_range: Range,
4199 diagnostics: &[Diagnostic],
4200) -> Vec<Value> {
4201 let mut out = code_actions_for_source(uri, text, cursor_range, diagnostics)
4202 .into_iter()
4203 .filter_map(|action| match action {
4204 CodeActionOrCommand::CodeAction(action) => Some(action),
4205 CodeActionOrCommand::Command(_) => None,
4206 })
4207 .map(|action| {
4208 let kind = action
4209 .kind
4210 .and_then(|k| to_value(k).ok())
4211 .and_then(|v| v.as_str().map(str::to_string));
4212 let edit = action.edit.unwrap_or(WorkspaceEdit {
4213 changes: None,
4214 document_changes: None,
4215 change_annotations: None,
4216 });
4217 let fingerprint = workspace_edit_fingerprint(&edit);
4218 json!({
4219 "id": format!("qf-{}", &fingerprint[..16]),
4220 "title": action.title,
4221 "kind": kind,
4222 "edit": to_value(edit).unwrap_or(Value::Null),
4223 })
4224 })
4225 .collect::<Vec<_>>();
4226
4227 out.sort_by_key(|item| {
4228 (
4229 item.get("title")
4230 .and_then(Value::as_str)
4231 .unwrap_or("")
4232 .to_string(),
4233 item.get("id")
4234 .and_then(Value::as_str)
4235 .unwrap_or("")
4236 .to_string(),
4237 )
4238 });
4239 out.dedup_by(|a, b| a.get("id") == b.get("id"));
4240 out
4241}
4242
4243fn execute_semantic_loop_step(uri: &Url, text: &str, position: Position) -> Option<Value> {
4244 let expected_type = expected_type_at_position(uri, text, position)
4245 .or_else(|| expected_type_from_syntax_context(uri, text, position));
4246 let inferred_type = inferred_type_at_position(uri, text, position);
4247
4248 let mut in_scope_values = in_scope_value_types_at_position(uri, text, position)
4249 .into_iter()
4250 .filter(|(name, _)| is_ident_like(name))
4251 .map(|(name, typ)| format!("{name} : {typ}"))
4252 .collect::<Vec<_>>();
4253 in_scope_values.sort();
4254 in_scope_values.dedup();
4255 if in_scope_values.len() > MAX_SEMANTIC_IN_SCOPE_VALUES {
4256 in_scope_values.truncate(MAX_SEMANTIC_IN_SCOPE_VALUES);
4257 }
4258
4259 let function_candidates = functions_producing_expected_type_at_position(uri, text, position)
4260 .into_iter()
4261 .map(|(name, typ)| format!("{name} : {typ}"))
4262 .collect::<Vec<_>>();
4263
4264 let hole_fill_candidates = hole_fill_candidates_at_position(uri, text, position)
4265 .into_iter()
4266 .map(|(name, replacement)| json!({ "name": name, "replacement": replacement }))
4267 .collect::<Vec<_>>();
4268 let functions_accepting_inferred_type =
4269 functions_accepting_inferred_type_at_position(uri, text, position)
4270 .into_iter()
4271 .map(|(name, typ)| format!("{name} : {typ}"))
4272 .collect::<Vec<_>>();
4273 let adapters_from_inferred_to_expected =
4274 adapters_from_inferred_to_expected_at_position(uri, text, position)
4275 .into_iter()
4276 .map(|(name, typ)| format!("{name} : {typ}"))
4277 .collect::<Vec<_>>();
4278 let compatible_with_in_scope_values =
4279 functions_compatible_with_in_scope_values_at_position(uri, text, position);
4280
4281 let cursor_range = Range {
4282 start: position,
4283 end: position,
4284 };
4285 let mut local_diagnostics: Vec<Diagnostic> = diagnostics_from_text(uri, text)
4286 .into_iter()
4287 .filter(|diag| ranges_overlap(diag.range, cursor_range))
4288 .collect();
4289 local_diagnostics.sort_by_key(|diag| {
4290 (
4291 diag.range.start.line,
4292 diag.range.start.character,
4293 diag.range.end.line,
4294 diag.range.end.character,
4295 diag.message.clone(),
4296 )
4297 });
4298
4299 let quick_fixes = semantic_quick_fixes_for_range(uri, text, cursor_range, &local_diagnostics);
4300 let mut quick_fix_titles = quick_fixes
4301 .iter()
4302 .filter_map(|item| item.get("title").and_then(Value::as_str))
4303 .map(ToString::to_string)
4304 .collect::<Vec<_>>();
4305 quick_fix_titles.sort();
4306 quick_fix_titles.dedup();
4307
4308 Some(json!({
4309 "expectedType": expected_type,
4310 "inferredType": inferred_type,
4311 "inScopeValues": in_scope_values,
4312 "functionCandidates": function_candidates,
4313 "holeFillCandidates": hole_fill_candidates,
4314 "functionsAcceptingInferredType": functions_accepting_inferred_type,
4315 "adaptersFromInferredToExpectedType": adapters_from_inferred_to_expected,
4316 "functionsCompatibleWithInScopeValues": compatible_with_in_scope_values,
4317 "localDiagnostics": local_diagnostics.into_iter().map(|diag| {
4318 json!({
4319 "message": diag.message,
4320 "line": diag.range.start.line,
4321 "character": diag.range.start.character,
4322 })
4323 }).collect::<Vec<_>>(),
4324 "quickFixes": quick_fixes,
4325 "quickFixTitles": quick_fix_titles,
4326 "holes": hole_expected_types_for_document(uri, text),
4327 }))
4328}
4329
4330fn execute_semantic_loop_apply_quick_fix(
4331 uri: &Url,
4332 text: &str,
4333 position: Position,
4334 quick_fix_id: &str,
4335) -> Option<Value> {
4336 let cursor_range = Range {
4337 start: position,
4338 end: position,
4339 };
4340 let local_diagnostics: Vec<Diagnostic> = diagnostics_from_text(uri, text)
4341 .into_iter()
4342 .filter(|diag| ranges_overlap(diag.range, cursor_range))
4343 .collect();
4344 let quick_fixes = semantic_quick_fixes_for_range(uri, text, cursor_range, &local_diagnostics);
4345 let quick_fix = quick_fixes.into_iter().find(|item| {
4346 item.get("id")
4347 .and_then(Value::as_str)
4348 .is_some_and(|id| id == quick_fix_id)
4349 });
4350
4351 Some(match quick_fix {
4352 Some(quick_fix) => json!({ "quickFix": quick_fix }),
4353 None => Value::Null,
4354 })
4355}
4356
4357fn quick_fix_priority(strategy: BulkQuickFixStrategy, title: &str) -> usize {
4358 let aggressive_introduce =
4359 strategy == BulkQuickFixStrategy::Aggressive && title.starts_with("Introduce `let ");
4360 if title.starts_with("Fill hole with `") {
4361 0
4362 } else if title.starts_with("Replace `") || aggressive_introduce {
4363 1
4364 } else if title.starts_with("Add wildcard arm") {
4365 2
4366 } else if title.starts_with("Wrap expression in list literal") {
4367 3
4368 } else if title.starts_with("Unwrap single-item list literal") {
4369 4
4370 } else if title.starts_with("Apply expression to missing argument") {
4371 5
4372 } else if title.starts_with("Wrap expression in lambda") {
4373 6
4374 } else if title.starts_with("Introduce `let ") {
4375 7
4376 } else {
4377 10
4378 }
4379}
4380
4381fn best_quick_fix_from_candidates(
4382 candidates: &[Value],
4383 strategy: BulkQuickFixStrategy,
4384) -> Option<Value> {
4385 candidates
4386 .iter()
4387 .min_by_key(|item| {
4388 let title = item.get("title").and_then(Value::as_str).unwrap_or("");
4389 let id = item.get("id").and_then(Value::as_str).unwrap_or("");
4390 (
4391 quick_fix_priority(strategy, title),
4392 title.to_string(),
4393 id.to_string(),
4394 )
4395 })
4396 .cloned()
4397}
4398
4399fn apply_workspace_edit_to_text(uri: &Url, text: &str, edit: &WorkspaceEdit) -> Option<String> {
4400 let changes = edit.changes.as_ref()?;
4401 let edits = changes.get(uri)?.clone();
4402 if edits.is_empty() {
4403 return Some(text.to_string());
4404 }
4405 let mut with_offsets = Vec::new();
4406 for edit in edits {
4407 let start = offset_at(text, edit.range.start)?;
4408 let end = offset_at(text, edit.range.end)?;
4409 if start > end || end > text.len() {
4410 return None;
4411 }
4412 with_offsets.push((start, end, edit.new_text));
4413 }
4414 with_offsets.sort_by(|a, b| b.0.cmp(&a.0).then(b.1.cmp(&a.1)));
4415
4416 let mut out = text.to_string();
4417 for (start, end, replacement) in with_offsets {
4418 out.replace_range(start..end, &replacement);
4419 }
4420 Some(out)
4421}
4422
4423fn text_state_hash(text: &str) -> String {
4424 sha256_hex(text.as_bytes())
4425}
4426
4427fn next_no_improvement_streak(streak: usize, diagnostics_delta: i64) -> usize {
4428 if diagnostics_delta > 0 { 0 } else { streak + 1 }
4429}
4430
4431fn execute_semantic_loop_apply_best_quick_fixes(
4432 uri: &Url,
4433 text: &str,
4434 position: Position,
4435 max_steps: usize,
4436 strategy: BulkQuickFixStrategy,
4437 dry_run: bool,
4438) -> Option<Value> {
4439 let cursor_range = Range {
4440 start: position,
4441 end: position,
4442 };
4443 let mut current_text = text.to_string();
4444 let mut applied = Vec::new();
4445 let mut steps = Vec::new();
4446 let mut stopped_reason = "noQuickFix".to_string();
4447 let mut stopped_reason_detail = "no quick-fixes available at cursor".to_string();
4448 let mut no_improvement_streak = 0usize;
4449 let mut last_diagnostics_delta = 0i64;
4450 let mut seen_states: HashSet<String> = HashSet::new();
4451 seen_states.insert(text_state_hash(¤t_text));
4452
4453 for step_index in 0..max_steps {
4454 let local_diagnostics: Vec<Diagnostic> = diagnostics_from_text(uri, ¤t_text)
4455 .into_iter()
4456 .filter(|diag| ranges_overlap(diag.range, cursor_range))
4457 .collect();
4458 let diagnostics_before = local_diagnostics
4459 .iter()
4460 .map(|diag| {
4461 json!({
4462 "message": diag.message,
4463 "line": diag.range.start.line,
4464 "character": diag.range.start.character,
4465 })
4466 })
4467 .collect::<Vec<_>>();
4468 let quick_fixes =
4469 semantic_quick_fixes_for_range(uri, ¤t_text, cursor_range, &local_diagnostics);
4470 let Some(best) = best_quick_fix_from_candidates(&quick_fixes, strategy) else {
4471 stopped_reason = "noQuickFix".to_string();
4472 stopped_reason_detail = "no candidate quick-fix was available".to_string();
4473 break;
4474 };
4475 let edit_value = best.get("edit").cloned().unwrap_or(Value::Null);
4476 let Ok(edit) = serde_json::from_value::<WorkspaceEdit>(edit_value) else {
4477 stopped_reason = "invalidEdit".to_string();
4478 stopped_reason_detail = "selected quick-fix edit was invalid".to_string();
4479 break;
4480 };
4481 let Some(next_text) = apply_workspace_edit_to_text(uri, ¤t_text, &edit) else {
4482 stopped_reason = "applyFailed".to_string();
4483 stopped_reason_detail = "failed to apply selected workspace edit".to_string();
4484 break;
4485 };
4486 if next_text == current_text {
4487 stopped_reason = "noTextChange".to_string();
4488 stopped_reason_detail = "selected quick-fix did not change text".to_string();
4489 break;
4490 }
4491 let next_hash = text_state_hash(&next_text);
4492 if seen_states.contains(&next_hash) {
4493 stopped_reason = "cycleDetected".to_string();
4494 stopped_reason_detail = "next text state already seen in this run".to_string();
4495 break;
4496 }
4497 let diagnostics_after_step: Vec<Value> = diagnostics_from_text(uri, &next_text)
4498 .into_iter()
4499 .filter(|diag| ranges_overlap(diag.range, cursor_range))
4500 .map(|diag| {
4501 json!({
4502 "message": diag.message,
4503 "line": diag.range.start.line,
4504 "character": diag.range.start.character,
4505 })
4506 })
4507 .collect();
4508 let before_count = diagnostics_before.len();
4509 let after_count = diagnostics_after_step.len();
4510 let diagnostics_delta = (before_count as i64) - (after_count as i64);
4511 last_diagnostics_delta = diagnostics_delta;
4512 no_improvement_streak =
4513 next_no_improvement_streak(no_improvement_streak, diagnostics_delta);
4514 steps.push(json!({
4515 "index": step_index,
4516 "quickFix": best.clone(),
4517 "diagnosticsBefore": diagnostics_before,
4518 "diagnosticsAfter": diagnostics_after_step,
4519 "diagnosticsBeforeCount": before_count,
4520 "diagnosticsAfterCount": after_count,
4521 "diagnosticsDelta": diagnostics_delta,
4522 "noImprovementStreak": no_improvement_streak,
4523 }));
4524 applied.push(best);
4525 current_text = next_text;
4526 seen_states.insert(next_hash);
4527 if no_improvement_streak >= NO_IMPROVEMENT_STREAK_LIMIT {
4528 stopped_reason = "noImprovementStreak".to_string();
4529 stopped_reason_detail =
4530 format!("diagnostics did not improve for {NO_IMPROVEMENT_STREAK_LIMIT} step(s)");
4531 break;
4532 }
4533 stopped_reason = "maxStepsReached".to_string();
4534 stopped_reason_detail = format!("reached maxSteps={max_steps}");
4535 }
4536
4537 let diagnostics_after: Vec<Value> = diagnostics_from_text(uri, ¤t_text)
4538 .into_iter()
4539 .filter(|diag| ranges_overlap(diag.range, cursor_range))
4540 .map(|diag| {
4541 json!({
4542 "message": diag.message,
4543 "line": diag.range.start.line,
4544 "character": diag.range.start.character,
4545 })
4546 })
4547 .collect();
4548
4549 Some(json!({
4550 "strategy": strategy.as_str(),
4551 "dryRun": dry_run,
4552 "appliedQuickFixes": applied,
4553 "appliedCount": applied.len(),
4554 "steps": steps,
4555 "updatedText": current_text,
4556 "localDiagnosticsAfter": diagnostics_after,
4557 "stoppedReason": stopped_reason,
4558 "stoppedReasonDetail": stopped_reason_detail,
4559 "lastDiagnosticsDelta": last_diagnostics_delta,
4560 "noImprovementStreak": no_improvement_streak,
4561 "seenStatesCount": seen_states.len(),
4562 }))
4563}
4564
4565fn hole_expected_types_for_document(uri: &Url, text: &str) -> Vec<Value> {
4566 let mut holes = Vec::new();
4567
4568 if let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) {
4570 let mut spans = Vec::new();
4571 collect_hole_spans(program.expr_with_fns().as_ref(), &mut spans);
4572 for span in spans {
4573 let pos = span_to_range(span).start;
4574 if let Some(expected_type) = expected_type_at_position(uri, text, pos)
4575 .or_else(|| expected_type_from_syntax_context(uri, text, pos))
4576 {
4577 holes.push(json!({
4578 "name": "?",
4579 "line": pos.line,
4580 "character": pos.character,
4581 "expectedType": expected_type
4582 }));
4583 }
4584 }
4585 }
4586
4587 let diagnostics = diagnostics_from_text(uri, text);
4589 for diag in diagnostics {
4590 let Some(name) = unknown_var_name_from_message(&diag.message) else {
4591 continue;
4592 };
4593 if !is_hole_name(name) {
4594 continue;
4595 }
4596 if !range_is_usable_for_text(text, diag.range) {
4597 continue;
4598 }
4599 let pos = diag.range.start;
4600 if let Some(expected_type) = expected_type_at_position(uri, text, pos)
4601 .or_else(|| expected_type_from_syntax_context(uri, text, pos))
4602 {
4603 holes.push(json!({
4604 "name": name,
4605 "line": pos.line,
4606 "character": pos.character,
4607 "expectedType": expected_type
4608 }));
4609 }
4610 }
4611 holes.sort_by_key(|item| {
4612 let line = item.get("line").and_then(Value::as_u64).unwrap_or(0);
4613 let ch = item.get("character").and_then(Value::as_u64).unwrap_or(0);
4614 let name = item
4615 .get("name")
4616 .and_then(Value::as_str)
4617 .unwrap_or("")
4618 .to_string();
4619 (line, ch, name)
4620 });
4621 holes.dedup_by(|a, b| {
4622 a.get("name") == b.get("name")
4623 && a.get("line") == b.get("line")
4624 && a.get("character") == b.get("character")
4625 });
4626 if holes.len() > MAX_SEMANTIC_HOLES {
4627 holes.truncate(MAX_SEMANTIC_HOLES);
4628 }
4629 holes
4630}
4631
4632fn collect_hole_spans(expr: &Expr, out: &mut Vec<Span>) {
4633 match expr {
4634 Expr::Hole(span) => out.push(*span),
4635 Expr::App(_, f, x) => {
4636 collect_hole_spans(f, out);
4637 collect_hole_spans(x, out);
4638 }
4639 Expr::Project(_, base, _) => collect_hole_spans(base, out),
4640 Expr::Lam(_, _scope, _param, _ann, _constraints, body) => collect_hole_spans(body, out),
4641 Expr::Let(_, _var, _ann, def, body) => {
4642 collect_hole_spans(def, out);
4643 collect_hole_spans(body, out);
4644 }
4645 Expr::LetRec(_, bindings, body) => {
4646 for (_var, _ann, def) in bindings {
4647 collect_hole_spans(def, out);
4648 }
4649 collect_hole_spans(body, out);
4650 }
4651 Expr::Ite(_, cond, then_expr, else_expr) => {
4652 collect_hole_spans(cond, out);
4653 collect_hole_spans(then_expr, out);
4654 collect_hole_spans(else_expr, out);
4655 }
4656 Expr::Match(_, scrutinee, arms) => {
4657 collect_hole_spans(scrutinee, out);
4658 for (_pat, arm) in arms {
4659 collect_hole_spans(arm, out);
4660 }
4661 }
4662 Expr::Ann(_, inner, _) => collect_hole_spans(inner, out),
4663 Expr::Tuple(_, elems) | Expr::List(_, elems) => {
4664 for elem in elems {
4665 collect_hole_spans(elem, out);
4666 }
4667 }
4668 Expr::Dict(_, kvs) => {
4669 for value in kvs.values() {
4670 collect_hole_spans(value, out);
4671 }
4672 }
4673 Expr::RecordUpdate(_, base, updates) => {
4674 collect_hole_spans(base, out);
4675 for value in updates.values() {
4676 collect_hole_spans(value, out);
4677 }
4678 }
4679 Expr::Var(_)
4680 | Expr::Bool(..)
4681 | Expr::Uint(..)
4682 | Expr::Int(..)
4683 | Expr::Float(..)
4684 | Expr::String(..)
4685 | Expr::Uuid(..)
4686 | Expr::DateTime(..) => {}
4687 }
4688}
4689
4690fn expected_type_from_syntax_context(uri: &Url, text: &str, position: Position) -> Option<String> {
4691 let (_tokens, program) = tokenize_and_parse_cached(uri, text).ok()?;
4692 let pos = lsp_to_rex_position(position);
4693
4694 fn visit(expr: &Expr, pos: RexPosition) -> Option<String> {
4695 if !position_in_span(pos, *expr.span()) {
4696 return None;
4697 }
4698 match expr {
4699 Expr::Let(_span, _name, ann, def, body) => {
4700 if position_in_span(pos, *def.span())
4701 && let Some(ann) = ann
4702 {
4703 return Some(ann.to_string());
4704 }
4705 visit(def.as_ref(), pos).or_else(|| visit(body.as_ref(), pos))
4706 }
4707 Expr::Ann(_span, inner, ann) => {
4708 if position_in_span(pos, *inner.span()) {
4709 return Some(ann.to_string());
4710 }
4711 visit(inner.as_ref(), pos)
4712 }
4713 Expr::Ite(_span, cond, then_expr, else_expr) => {
4714 if position_in_span(pos, *cond.span()) {
4715 return Some("bool".to_string());
4716 }
4717 visit(cond.as_ref(), pos)
4718 .or_else(|| visit(then_expr.as_ref(), pos))
4719 .or_else(|| visit(else_expr.as_ref(), pos))
4720 }
4721 Expr::App(_span, f, x) => visit(f.as_ref(), pos).or_else(|| visit(x.as_ref(), pos)),
4722 Expr::Project(_span, base, _field) => visit(base.as_ref(), pos),
4723 Expr::Lam(_span, _scope, _param, _ann, _constraints, body) => visit(body.as_ref(), pos),
4724 Expr::LetRec(_span, bindings, body) => {
4725 for (_name, _ann, def) in bindings {
4726 if let Some(found) = visit(def.as_ref(), pos) {
4727 return Some(found);
4728 }
4729 }
4730 visit(body.as_ref(), pos)
4731 }
4732 Expr::Match(_span, scrutinee, arms) => {
4733 if let Some(found) = visit(scrutinee.as_ref(), pos) {
4734 return Some(found);
4735 }
4736 for (_pat, arm) in arms {
4737 if let Some(found) = visit(arm.as_ref(), pos) {
4738 return Some(found);
4739 }
4740 }
4741 None
4742 }
4743 Expr::Tuple(_span, elems) | Expr::List(_span, elems) => {
4744 for elem in elems {
4745 if let Some(found) = visit(elem.as_ref(), pos) {
4746 return Some(found);
4747 }
4748 }
4749 None
4750 }
4751 Expr::Dict(_span, kvs) => {
4752 for value in kvs.values() {
4753 if let Some(found) = visit(value.as_ref(), pos) {
4754 return Some(found);
4755 }
4756 }
4757 None
4758 }
4759 Expr::RecordUpdate(_span, base, updates) => {
4760 if let Some(found) = visit(base.as_ref(), pos) {
4761 return Some(found);
4762 }
4763 for value in updates.values() {
4764 if let Some(found) = visit(value.as_ref(), pos) {
4765 return Some(found);
4766 }
4767 }
4768 None
4769 }
4770 Expr::Var(_)
4771 | Expr::Bool(..)
4772 | Expr::Uint(..)
4773 | Expr::Int(..)
4774 | Expr::Float(..)
4775 | Expr::String(..)
4776 | Expr::Uuid(..)
4777 | Expr::DateTime(..)
4778 | Expr::Hole(..) => None,
4779 }
4780 }
4781
4782 visit(program.expr_with_fns().as_ref(), pos)
4783}
4784
4785fn command_uri_and_position(arguments: &[Value]) -> Option<(Url, Position)> {
4786 if arguments.len() >= 3 {
4787 let uri = arguments.first()?.as_str()?;
4788 let line = arguments.get(1)?.as_u64()? as u32;
4789 let character = arguments.get(2)?.as_u64()? as u32;
4790 let uri = Url::parse(uri).ok()?;
4791 return Some((uri, Position { line, character }));
4792 }
4793
4794 let obj = arguments.first()?.as_object()?;
4795 let uri = obj.get("uri")?.as_str()?;
4796 let line = obj.get("line")?.as_u64()? as u32;
4797 let character = obj.get("character")?.as_u64()? as u32;
4798 let uri = Url::parse(uri).ok()?;
4799 Some((uri, Position { line, character }))
4800}
4801
4802fn command_uri(arguments: &[Value]) -> Option<Url> {
4803 if arguments.is_empty() {
4804 return None;
4805 }
4806 if let Some(uri) = arguments.first().and_then(Value::as_str) {
4807 return Url::parse(uri).ok();
4808 }
4809 let obj = arguments.first()?.as_object()?;
4810 let uri = obj.get("uri")?.as_str()?;
4811 Url::parse(uri).ok()
4812}
4813
4814fn command_uri_position_and_id(arguments: &[Value]) -> Option<(Url, Position, String)> {
4815 if arguments.len() >= 4 {
4816 let uri = arguments.first()?.as_str()?;
4817 let line = arguments.get(1)?.as_u64()? as u32;
4818 let character = arguments.get(2)?.as_u64()? as u32;
4819 let id = arguments.get(3)?.as_str()?.to_string();
4820 let uri = Url::parse(uri).ok()?;
4821 return Some((uri, Position { line, character }, id));
4822 }
4823
4824 let obj = arguments.first()?.as_object()?;
4825 let uri = obj.get("uri")?.as_str()?;
4826 let line = obj.get("line")?.as_u64()? as u32;
4827 let character = obj.get("character")?.as_u64()? as u32;
4828 let id = obj.get("id")?.as_str()?.to_string();
4829 let uri = Url::parse(uri).ok()?;
4830 Some((uri, Position { line, character }, id))
4831}
4832
4833fn command_uri_position_max_steps_strategy_and_dry_run(
4834 arguments: &[Value],
4835) -> Option<(Url, Position, usize, BulkQuickFixStrategy, bool)> {
4836 if arguments.len() >= 3 {
4837 let uri = arguments.first()?.as_str()?;
4838 let line = arguments.get(1)?.as_u64()? as u32;
4839 let character = arguments.get(2)?.as_u64()? as u32;
4840 let max_steps = arguments
4841 .get(3)
4842 .and_then(Value::as_u64)
4843 .map(|n| n as usize)
4844 .unwrap_or(3);
4845 let strategy = arguments
4846 .get(4)
4847 .and_then(Value::as_str)
4848 .map(BulkQuickFixStrategy::parse)
4849 .unwrap_or(BulkQuickFixStrategy::Conservative);
4850 let dry_run = arguments.get(5).and_then(Value::as_bool).unwrap_or(false);
4851 let uri = Url::parse(uri).ok()?;
4852 return Some((
4853 uri,
4854 Position { line, character },
4855 max_steps.clamp(1, 20),
4856 strategy,
4857 dry_run,
4858 ));
4859 }
4860
4861 let obj = arguments.first()?.as_object()?;
4862 let uri = obj.get("uri")?.as_str()?;
4863 let line = obj.get("line")?.as_u64()? as u32;
4864 let character = obj.get("character")?.as_u64()? as u32;
4865 let max_steps = obj
4866 .get("maxSteps")
4867 .and_then(Value::as_u64)
4868 .map(|n| n as usize)
4869 .unwrap_or(3)
4870 .clamp(1, 20);
4871 let strategy = obj
4872 .get("strategy")
4873 .and_then(Value::as_str)
4874 .map(BulkQuickFixStrategy::parse)
4875 .unwrap_or(BulkQuickFixStrategy::Conservative);
4876 let dry_run = obj.get("dryRun").and_then(Value::as_bool).unwrap_or(false);
4877 let uri = Url::parse(uri).ok()?;
4878 Some((
4879 uri,
4880 Position { line, character },
4881 max_steps,
4882 strategy,
4883 dry_run,
4884 ))
4885}
4886
4887fn hover_type_in_expr(
4888 ts: &mut TypeSystem,
4889 expr: &Expr,
4890 typed: &TypedExpr,
4891 pos: RexPosition,
4892 name: &str,
4893 name_span: Span,
4894 name_is_ident: bool,
4895) -> Option<HoverType> {
4896 fn span_contains_pos(span: Span, pos: RexPosition) -> bool {
4897 position_in_span(pos, span)
4898 }
4899
4900 fn span_contains_span(outer: Span, inner: Span) -> bool {
4901 position_leq(outer.begin, inner.begin) && position_leq(inner.end, outer.end)
4902 }
4903
4904 fn span_size(span: Span) -> (usize, usize) {
4905 (
4906 span.end.line.saturating_sub(span.begin.line),
4907 span.end.column.saturating_sub(span.begin.column),
4908 )
4909 }
4910
4911 fn peel_fun(ty: &Type) -> (Vec<Type>, Type) {
4912 let mut args = Vec::new();
4913 let mut cur = ty.clone();
4914 while let TypeKind::Fun(a, b) = cur.as_ref() {
4915 args.push(a.clone());
4916 cur = b.clone();
4917 }
4918 (args, cur)
4919 }
4920
4921 fn add_bindings_from_pattern(
4922 ts: &mut TypeSystem,
4923 scrutinee_ty: &Type,
4924 pat: &Pattern,
4925 out: &mut HashMap<String, Type>,
4926 ) {
4927 match pat {
4928 Pattern::Wildcard(..) => {}
4929 Pattern::Var(v) => {
4930 out.insert(v.name.as_ref().to_string(), scrutinee_ty.clone());
4931 }
4932 Pattern::Named(_span, ctor, args) => {
4933 let ctor_name = ctor.to_dotted_symbol();
4934 let Some(schemes) = ts.env.lookup(&ctor_name) else {
4935 return;
4936 };
4937 let Some(scheme) = schemes.first() else {
4938 return;
4939 };
4940
4941 let (_preds, ctor_ty) = instantiate(scheme, &mut ts.supply);
4942 let (arg_tys, result_ty) = peel_fun(&ctor_ty);
4943 let Ok(s) = unify(&result_ty, scrutinee_ty) else {
4944 return;
4945 };
4946
4947 for (subpat, arg_ty) in args.iter().zip(arg_tys.iter()) {
4948 add_bindings_from_pattern(ts, &arg_ty.apply(&s), subpat, out);
4949 }
4950 }
4951 Pattern::Tuple(_span, elems) => {
4952 let elem_tys: Vec<Type> = (0..elems.len())
4953 .map(|_| Type::var(ts.supply.fresh(None)))
4954 .collect();
4955 let expected = Type::tuple(elem_tys.clone());
4956 let Ok(s) = unify(scrutinee_ty, &expected) else {
4957 return;
4958 };
4959 for (p, ty) in elems.iter().zip(elem_tys.iter()) {
4960 add_bindings_from_pattern(ts, &ty.apply(&s), p, out);
4961 }
4962 }
4963 Pattern::List(_span, elems) => {
4964 let tv = ts.supply.fresh(None);
4965 let elem = Type::var(tv.clone());
4966 let list_ty = Type::app(Type::builtin(BuiltinTypeId::List), elem.clone());
4967 let Ok(s) = unify(scrutinee_ty, &list_ty) else {
4968 return;
4969 };
4970 let elem_ty = elem.apply(&s);
4971 for p in elems {
4972 add_bindings_from_pattern(ts, &elem_ty, p, out);
4973 }
4974 }
4975 Pattern::Cons(_span, head, tail) => {
4976 let tv = ts.supply.fresh(None);
4977 let elem = Type::var(tv.clone());
4978 let list_ty = Type::app(Type::builtin(BuiltinTypeId::List), elem.clone());
4979 let Ok(s) = unify(scrutinee_ty, &list_ty) else {
4980 return;
4981 };
4982 let elem_ty = elem.apply(&s);
4983 let list_ty = list_ty.apply(&s);
4984 add_bindings_from_pattern(ts, &elem_ty, head.as_ref(), out);
4985 add_bindings_from_pattern(ts, &list_ty, tail.as_ref(), out);
4986 }
4987 Pattern::Dict(_span, keys) => match scrutinee_ty.as_ref() {
4988 TypeKind::Record(fields) => {
4989 for (key, pat) in keys {
4990 if let Some((_, ty)) = fields.iter().find(|(n, _)| n == key) {
4991 add_bindings_from_pattern(ts, ty, pat, out);
4992 }
4993 }
4994 }
4995 _ => {
4996 let tv = ts.supply.fresh(None);
4997 let elem = Type::var(tv.clone());
4998 let dict_ty = Type::app(Type::builtin(BuiltinTypeId::Dict), elem.clone());
4999 let Ok(s) = unify(scrutinee_ty, &dict_ty) else {
5000 return;
5001 };
5002 let elem_ty = elem.apply(&s);
5003 for (_key, pat) in keys {
5004 add_bindings_from_pattern(ts, &elem_ty, pat, out);
5005 }
5006 }
5007 },
5008 }
5009 }
5010
5011 struct VisitCtx<'a> {
5012 pos: RexPosition,
5013 name: &'a str,
5014 name_span: Span,
5015 name_is_ident: bool,
5016 best: &'a mut Option<HoverType>,
5017 }
5018
5019 fn visit(ts: &mut TypeSystem, expr: &Expr, typed: &TypedExpr, ctx: &mut VisitCtx<'_>) {
5020 if !span_contains_pos(*expr.span(), ctx.pos) {
5021 return;
5022 }
5023
5024 let consider = |best: &mut Option<HoverType>, candidate: HoverType| {
5025 let take = best
5026 .as_ref()
5027 .is_none_or(|b| span_size(candidate.span) < span_size(b.span));
5028 if take {
5029 *best = Some(candidate);
5030 }
5031 };
5032
5033 if ctx.name_is_ident
5035 && let (
5036 Expr::Match(_span, _scrutinee, arms),
5037 TypedExprKind::Match {
5038 scrutinee,
5039 arms: typed_arms,
5040 },
5041 ) = (&expr, &typed.kind)
5042 && span_contains_span(*expr.span(), ctx.name_span)
5043 {
5044 for ((_pat, _arm_body), (typed_pat, _typed_arm_body)) in
5045 arms.iter().zip(typed_arms.iter())
5046 {
5047 let pat_span = *typed_pat.span();
5049 if span_contains_span(pat_span, ctx.name_span) {
5050 let mut bindings: HashMap<String, Type> = HashMap::new();
5051 add_bindings_from_pattern(ts, &scrutinee.typ, typed_pat, &mut bindings);
5052 if let Some(ty) = bindings.get(ctx.name) {
5053 consider(
5054 ctx.best,
5055 HoverType {
5056 span: ctx.name_span,
5057 label: ctx.name.to_string(),
5058 typ: ty.to_string(),
5059 overloads: Vec::new(),
5060 },
5061 );
5062 }
5063 break;
5064 }
5065 }
5066 }
5067
5068 match (expr, &typed.kind) {
5070 (
5071 Expr::Let(_span, binding, _ann, def, body),
5072 TypedExprKind::Let {
5073 def: tdef,
5074 body: tbody,
5075 ..
5076 },
5077 ) => {
5078 if span_contains_pos(binding.span, ctx.pos) {
5079 consider(
5080 ctx.best,
5081 HoverType {
5082 span: binding.span,
5083 label: binding.name.as_ref().to_string(),
5084 typ: tdef.typ.to_string(),
5085 overloads: Vec::new(),
5086 },
5087 );
5088 }
5089 visit(ts, def.as_ref(), tdef.as_ref(), ctx);
5090 visit(ts, body.as_ref(), tbody.as_ref(), ctx);
5091 }
5092 (
5093 Expr::LetRec(_span, bindings, body),
5094 TypedExprKind::LetRec {
5095 bindings: typed_bindings,
5096 body: typed_body,
5097 },
5098 ) => {
5099 for ((binding, _ann, def), (_name, typed_def)) in
5100 bindings.iter().zip(typed_bindings.iter())
5101 {
5102 if span_contains_pos(binding.span, ctx.pos) {
5103 consider(
5104 ctx.best,
5105 HoverType {
5106 span: binding.span,
5107 label: binding.name.as_ref().to_string(),
5108 typ: typed_def.typ.to_string(),
5109 overloads: Vec::new(),
5110 },
5111 );
5112 }
5113 visit(ts, def.as_ref(), typed_def, ctx);
5114 }
5115 visit(ts, body.as_ref(), typed_body.as_ref(), ctx);
5116 }
5117 (
5118 Expr::Lam(_span, _scope, param, _ann, _constraints, body),
5119 TypedExprKind::Lam { body: tbody, .. },
5120 ) => {
5121 if span_contains_pos(param.span, ctx.pos) {
5122 let param_ty = match typed.typ.as_ref() {
5123 TypeKind::Fun(a, _b) => a.to_string(),
5124 _ => "<unknown>".to_string(),
5125 };
5126 consider(
5127 ctx.best,
5128 HoverType {
5129 span: param.span,
5130 label: param.name.as_ref().to_string(),
5131 typ: param_ty,
5132 overloads: Vec::new(),
5133 },
5134 );
5135 }
5136 visit(ts, body.as_ref(), tbody.as_ref(), ctx);
5137 }
5138 (Expr::Var(v), TypedExprKind::Var { overloads, .. }) => {
5139 if span_contains_pos(v.span, ctx.pos) {
5140 consider(
5141 ctx.best,
5142 HoverType {
5143 span: v.span,
5144 label: v.name.as_ref().to_string(),
5145 typ: typed.typ.to_string(),
5146 overloads: overloads.iter().map(|t| t.to_string()).collect(),
5147 },
5148 );
5149 }
5150 }
5151 (Expr::Ann(_span, inner, _ann), _) => {
5152 visit(ts, inner.as_ref(), typed, ctx);
5153 }
5154 (Expr::Tuple(_span, elems), TypedExprKind::Tuple(telems)) => {
5155 for (e, t) in elems.iter().zip(telems.iter()) {
5156 visit(ts, e.as_ref(), t, ctx);
5157 }
5158 }
5159 (Expr::List(_span, elems), TypedExprKind::List(telems)) => {
5160 for (e, t) in elems.iter().zip(telems.iter()) {
5161 visit(ts, e.as_ref(), t, ctx);
5162 }
5163 }
5164 (Expr::Dict(_span, kvs), TypedExprKind::Dict(tkvs)) => {
5165 for (k, v) in kvs {
5166 if let Some(tv) = tkvs.get(k) {
5167 visit(ts, v.as_ref(), tv, ctx);
5168 }
5169 }
5170 }
5171 (
5172 Expr::RecordUpdate(_span, base, updates),
5173 TypedExprKind::RecordUpdate {
5174 base: tbase,
5175 updates: tupdates,
5176 },
5177 ) => {
5178 visit(ts, base.as_ref(), tbase.as_ref(), ctx);
5179 for (k, v) in updates {
5180 if let Some(tv) = tupdates.get(k) {
5181 visit(ts, v.as_ref(), tv, ctx);
5182 }
5183 }
5184 }
5185 (Expr::App(_span, f, x), TypedExprKind::App(tf, tx)) => {
5186 visit(ts, f.as_ref(), tf.as_ref(), ctx);
5187 visit(ts, x.as_ref(), tx.as_ref(), ctx);
5188 }
5189 (Expr::Project(_span, e, _field), TypedExprKind::Project { expr: te, .. }) => {
5190 visit(ts, e.as_ref(), te.as_ref(), ctx);
5191 }
5192 (
5193 Expr::Ite(_span, c, t, e),
5194 TypedExprKind::Ite {
5195 cond,
5196 then_expr,
5197 else_expr,
5198 },
5199 ) => {
5200 visit(ts, c.as_ref(), cond.as_ref(), ctx);
5201 visit(ts, t.as_ref(), then_expr.as_ref(), ctx);
5202 visit(ts, e.as_ref(), else_expr.as_ref(), ctx);
5203 }
5204 (
5205 Expr::Match(_span, scrutinee, arms),
5206 TypedExprKind::Match {
5207 scrutinee: tscrut,
5208 arms: tarms,
5209 },
5210 ) => {
5211 visit(ts, scrutinee.as_ref(), tscrut.as_ref(), ctx);
5212 for ((_pat, arm_body), (_tpat, tarm_body)) in arms.iter().zip(tarms.iter()) {
5213 visit(ts, arm_body.as_ref(), tarm_body, ctx);
5214 }
5215 }
5216 _ => {}
5217 }
5218 }
5219
5220 let mut best = None;
5221 let mut ctx = VisitCtx {
5222 pos,
5223 name,
5224 name_span,
5225 name_is_ident,
5226 best: &mut best,
5227 };
5228 visit(ts, expr, typed, &mut ctx);
5229 best
5230}
5231
5232fn name_token_at_position(tokens: &Tokens, position: Position) -> Option<(String, Span, bool)> {
5233 for token in &tokens.items {
5234 let (name, span, is_ident) = match token {
5235 Token::Ident(name, span, ..) => (name.clone(), *span, true),
5236 Token::Add(span) => ("+".to_string(), *span, false),
5237 Token::Sub(span) => ("-".to_string(), *span, false),
5238 Token::Mul(span) => ("*".to_string(), *span, false),
5239 Token::Div(span) => ("/".to_string(), *span, false),
5240 Token::Mod(span) => ("%".to_string(), *span, false),
5241 Token::Concat(span) => ("++".to_string(), *span, false),
5242 Token::Eq(span) => ("==".to_string(), *span, false),
5243 Token::Ne(span) => ("!=".to_string(), *span, false),
5244 Token::Lt(span) => ("<".to_string(), *span, false),
5245 Token::Le(span) => ("<=".to_string(), *span, false),
5246 Token::Gt(span) => (">".to_string(), *span, false),
5247 Token::Ge(span) => (">=".to_string(), *span, false),
5248 Token::And(span) => ("&&".to_string(), *span, false),
5249 Token::Or(span) => ("||".to_string(), *span, false),
5250 _ => continue,
5251 };
5252 if range_touches_position(span_to_range(span), position) {
5253 return Some((name, span, is_ident));
5254 }
5255 }
5256 None
5257}
5258
5259fn push_comment_diagnostics(tokens: &Tokens, diagnostics: &mut Vec<Diagnostic>) {
5260 let mut index = 0;
5261
5262 while index < tokens.items.len() && diagnostics.len() < MAX_DIAGNOSTICS {
5263 match tokens.items[index] {
5264 Token::CommentL(span) => {
5265 let mut cursor = index + 1;
5266 while cursor < tokens.items.len() {
5267 if matches!(tokens.items[cursor], Token::CommentR(_)) {
5268 break;
5269 }
5270 cursor += 1;
5271 }
5272
5273 if cursor >= tokens.items.len() {
5274 diagnostics.push(diagnostic_for_span(
5275 span,
5276 "Unclosed block comment opener ({-).",
5277 ));
5278 break;
5279 }
5280
5281 index = cursor + 1;
5282 }
5283 Token::CommentR(span) => {
5284 diagnostics.push(diagnostic_for_span(
5285 span,
5286 "Unmatched block comment closer (-}).",
5287 ));
5288 index += 1;
5289 }
5290 _ => index += 1,
5291 }
5292 }
5293}
5294
5295fn diagnostic_for_span(span: Span, message: impl Into<String>) -> Diagnostic {
5296 Diagnostic {
5297 range: span_to_range(span),
5298 severity: Some(DiagnosticSeverity::ERROR),
5299 message: message.into(),
5300 source: Some("rexlang-lsp".to_string()),
5301 ..Diagnostic::default()
5302 }
5303}
5304
5305fn push_type_diagnostics(
5306 uri: &Url,
5307 text: &str,
5308 program: &Program,
5309 diagnostics: &mut Vec<Diagnostic>,
5310) {
5311 const MAX_TYPECHECK_BYTES: usize = 256 * 1024;
5314 if text.len() > MAX_TYPECHECK_BYTES {
5315 return;
5316 }
5317
5318 let (program, mut ts, _imports, import_diags) = match prepare_program_with_imports(uri, program)
5319 {
5320 Ok(v) => v,
5321 Err(err) => {
5322 diagnostics.push(diagnostic_for_span(primary_program_span(program), err));
5323 return;
5324 }
5325 };
5326 diagnostics.extend(import_diags);
5327 if diagnostics.len() >= MAX_DIAGNOSTICS {
5328 diagnostics.truncate(MAX_DIAGNOSTICS);
5329 return;
5330 }
5331
5332 let (instances, _prepared_target) = match inject_program_decls(&mut ts, &program, None) {
5333 Ok(v) => v,
5334 Err(err) => {
5335 push_ts_error(
5336 err,
5337 diagnostics,
5338 None,
5339 Some(&ts),
5340 Some(primary_program_span(&program)),
5341 );
5342 return;
5343 }
5344 };
5345
5346 for (decl_idx, prepared) in instances {
5349 if diagnostics.len() >= MAX_DIAGNOSTICS {
5350 break;
5351 }
5352 let Decl::Instance(inst_decl) = &program.decls[decl_idx] else {
5353 continue;
5354 };
5355 for method in &inst_decl.methods {
5356 if let Err(err) = ts.typecheck_instance_method(&prepared, method) {
5357 push_ts_error(
5358 err,
5359 diagnostics,
5360 Some(method.body.as_ref()),
5361 Some(&ts),
5362 None,
5363 );
5364 if diagnostics.len() >= MAX_DIAGNOSTICS {
5365 break;
5366 }
5367 }
5368 }
5369 }
5370
5371 if let Err(err) = ts.infer(program.expr.as_ref()) {
5372 let before = diagnostics.len();
5373 push_ts_error(
5374 err,
5375 diagnostics,
5376 Some(program.expr.as_ref()),
5377 Some(&ts),
5378 None,
5379 );
5380 if let Some(primary) = diagnostics.get(before).cloned() {
5381 push_additional_default_record_update_ambiguity_diagnostics(
5382 program.expr.as_ref(),
5383 &primary.message,
5384 diagnostics,
5385 );
5386 }
5387 return;
5388 }
5389
5390 push_hole_diagnostics(&program, diagnostics);
5391}
5392
5393fn primary_program_span(program: &Program) -> Span {
5394 match program.decls.first() {
5395 Some(Decl::Type(d)) => d.span,
5396 Some(Decl::Fn(d)) => d.span,
5397 Some(Decl::DeclareFn(d)) => d.span,
5398 Some(Decl::Import(d)) => d.span,
5399 Some(Decl::Class(d)) => d.span,
5400 Some(Decl::Instance(d)) => d.span,
5401 None => *program.expr.span(),
5402 }
5403}
5404
5405fn push_hole_diagnostics(program: &Program, diagnostics: &mut Vec<Diagnostic>) {
5406 let mut spans = Vec::new();
5407 collect_hole_spans(program.expr_with_fns().as_ref(), &mut spans);
5408 spans.sort_unstable_by_key(|s| (s.begin.line, s.begin.column, s.end.line, s.end.column));
5409 spans.dedup();
5410
5411 for span in spans {
5412 if diagnostics.len() >= MAX_DIAGNOSTICS {
5413 break;
5414 }
5415 diagnostics.push(Diagnostic {
5416 range: span_to_range(span),
5417 severity: Some(DiagnosticSeverity::ERROR),
5418 message: "typed hole `?` must be filled before evaluation".to_string(),
5419 source: Some("rexlang-typesystem".to_string()),
5420 ..Diagnostic::default()
5421 });
5422 }
5423}
5424
5425fn unknown_var_name(err: &TsTypeError) -> Option<Symbol> {
5426 match err {
5427 TsTypeError::UnknownVar(name) => Some(name.clone()),
5428 TsTypeError::Spanned { error, .. } => unknown_var_name(error),
5429 _ => None,
5430 }
5431}
5432
5433fn field_not_definitely_available_tail(message: &str) -> Option<(&str, &str)> {
5434 let rest = message.strip_prefix("field `")?;
5435 let (field, tail) = rest.split_once('`')?;
5436 tail.contains("is not definitely available on")
5437 .then_some((field, tail))
5438}
5439
5440fn push_additional_default_record_update_ambiguity_diagnostics(
5441 expr: &Expr,
5442 primary_message: &str,
5443 diagnostics: &mut Vec<Diagnostic>,
5444) {
5445 let Some((_field, tail)) = field_not_definitely_available_tail(primary_message) else {
5446 return;
5447 };
5448 let mut updates = Vec::new();
5449 collect_default_record_updates(expr, &mut updates);
5450 for (span, fields) in updates {
5451 if diagnostics.len() >= MAX_DIAGNOSTICS {
5452 break;
5453 }
5454 let Some(field) = fields.first() else {
5455 continue;
5456 };
5457 let message = format!("field `{field}`{tail}");
5458 let range = span_to_range(span);
5459 if diagnostics
5460 .iter()
5461 .any(|d| d.range == range && d.message == message)
5462 {
5463 continue;
5464 }
5465 diagnostics.push(Diagnostic {
5466 range,
5467 severity: Some(DiagnosticSeverity::ERROR),
5468 message,
5469 source: Some("rexlang-typesystem".to_string()),
5470 ..Diagnostic::default()
5471 });
5472 }
5473}
5474
5475fn collect_default_record_updates(expr: &Expr, out: &mut Vec<(Span, Vec<String>)>) {
5476 match expr {
5477 Expr::RecordUpdate(span, base, updates) => {
5478 if matches!(base.as_ref(), Expr::Var(v) if v.name.as_ref() == "default") {
5479 let fields = updates
5480 .keys()
5481 .map(|name| name.as_ref().to_string())
5482 .collect::<Vec<_>>();
5483 if !fields.is_empty() {
5484 out.push((*span, fields));
5485 }
5486 }
5487 collect_default_record_updates(base, out);
5488 for value in updates.values() {
5489 collect_default_record_updates(value, out);
5490 }
5491 }
5492 Expr::App(_, fun, arg) => {
5493 collect_default_record_updates(fun, out);
5494 collect_default_record_updates(arg, out);
5495 }
5496 Expr::Project(_, base, _) => collect_default_record_updates(base, out),
5497 Expr::Lam(_, _, _, _, _, body) => collect_default_record_updates(body, out),
5498 Expr::Let(_, _, _, def, body) => {
5499 collect_default_record_updates(def, out);
5500 collect_default_record_updates(body, out);
5501 }
5502 Expr::LetRec(_, bindings, body) => {
5503 for (_var, _ann, def) in bindings {
5504 collect_default_record_updates(def, out);
5505 }
5506 collect_default_record_updates(body, out);
5507 }
5508 Expr::Ite(_, cond, then_expr, else_expr) => {
5509 collect_default_record_updates(cond, out);
5510 collect_default_record_updates(then_expr, out);
5511 collect_default_record_updates(else_expr, out);
5512 }
5513 Expr::Match(_, scrutinee, arms) => {
5514 collect_default_record_updates(scrutinee, out);
5515 for (_pat, arm) in arms {
5516 collect_default_record_updates(arm, out);
5517 }
5518 }
5519 Expr::Ann(_, inner, _) => collect_default_record_updates(inner, out),
5520 Expr::Tuple(_, items) | Expr::List(_, items) => {
5521 for item in items {
5522 collect_default_record_updates(item, out);
5523 }
5524 }
5525 Expr::Dict(_, entries) => {
5526 for value in entries.values() {
5527 collect_default_record_updates(value, out);
5528 }
5529 }
5530 Expr::Var(..)
5531 | Expr::Bool(..)
5532 | Expr::Uint(..)
5533 | Expr::Int(..)
5534 | Expr::Float(..)
5535 | Expr::String(..)
5536 | Expr::Uuid(..)
5537 | Expr::DateTime(..)
5538 | Expr::Hole(..) => {}
5539 }
5540}
5541
5542fn find_let_binding_for_def_range(program: &Program, target: Range) -> Option<(String, Position)> {
5543 find_let_binding_for_def_range_in_expr(program.expr_with_fns().as_ref(), target)
5544}
5545
5546fn find_let_binding_for_def_range_in_expr(
5547 expr: &Expr,
5548 target: Range,
5549) -> Option<(String, Position)> {
5550 match expr {
5551 Expr::Let(_, var, ann, def, body) => {
5552 let def_range = span_to_range(*def.span());
5553 if ranges_overlap(def_range, target) && ann.is_none() {
5554 return Some((var.name.as_ref().to_string(), span_to_range(var.span).end));
5555 }
5556 find_let_binding_for_def_range_in_expr(def.as_ref(), target)
5557 .or_else(|| find_let_binding_for_def_range_in_expr(body.as_ref(), target))
5558 }
5559 Expr::LetRec(_, bindings, body) => {
5560 for (var, ann, def) in bindings {
5561 let def_range = span_to_range(*def.span());
5562 if ranges_overlap(def_range, target) && ann.is_none() {
5563 return Some((var.name.as_ref().to_string(), span_to_range(var.span).end));
5564 }
5565 if let Some(found) = find_let_binding_for_def_range_in_expr(def.as_ref(), target) {
5566 return Some(found);
5567 }
5568 }
5569 find_let_binding_for_def_range_in_expr(body.as_ref(), target)
5570 }
5571 Expr::App(_, fun, arg) => find_let_binding_for_def_range_in_expr(fun.as_ref(), target)
5572 .or_else(|| find_let_binding_for_def_range_in_expr(arg.as_ref(), target)),
5573 Expr::Project(_, base, _) => find_let_binding_for_def_range_in_expr(base.as_ref(), target),
5574 Expr::RecordUpdate(_, base, updates) => {
5575 find_let_binding_for_def_range_in_expr(base.as_ref(), target).or_else(|| {
5576 updates
5577 .values()
5578 .find_map(|expr| find_let_binding_for_def_range_in_expr(expr.as_ref(), target))
5579 })
5580 }
5581 Expr::Lam(_, _, _, _, _, body) => {
5582 find_let_binding_for_def_range_in_expr(body.as_ref(), target)
5583 }
5584 Expr::Ite(_, cond, then_expr, else_expr) => {
5585 find_let_binding_for_def_range_in_expr(cond.as_ref(), target)
5586 .or_else(|| find_let_binding_for_def_range_in_expr(then_expr.as_ref(), target))
5587 .or_else(|| find_let_binding_for_def_range_in_expr(else_expr.as_ref(), target))
5588 }
5589 Expr::Match(_, scrutinee, arms) => {
5590 find_let_binding_for_def_range_in_expr(scrutinee.as_ref(), target).or_else(|| {
5591 arms.iter().find_map(|(_, arm)| {
5592 find_let_binding_for_def_range_in_expr(arm.as_ref(), target)
5593 })
5594 })
5595 }
5596 Expr::Ann(_, inner, _) => find_let_binding_for_def_range_in_expr(inner.as_ref(), target),
5597 Expr::Tuple(_, items) | Expr::List(_, items) => items
5598 .iter()
5599 .find_map(|item| find_let_binding_for_def_range_in_expr(item.as_ref(), target)),
5600 Expr::Dict(_, entries) => entries
5601 .values()
5602 .find_map(|value| find_let_binding_for_def_range_in_expr(value.as_ref(), target)),
5603 Expr::Var(..)
5604 | Expr::Bool(..)
5605 | Expr::Uint(..)
5606 | Expr::Int(..)
5607 | Expr::Float(..)
5608 | Expr::String(..)
5609 | Expr::Uuid(..)
5610 | Expr::DateTime(..)
5611 | Expr::Hole(..) => None,
5612 }
5613}
5614
5615fn collect_unbound_var_spans(
5616 expr: &Expr,
5617 target: &Symbol,
5618 bound: &mut Vec<Symbol>,
5619 out: &mut Vec<Span>,
5620) {
5621 match expr {
5622 Expr::Var(var) => {
5623 if var.name == *target && !bound.iter().any(|name| name == &var.name) {
5624 out.push(var.span);
5625 }
5626 }
5627 Expr::App(_, fun, arg) => {
5628 collect_unbound_var_spans(fun, target, bound, out);
5629 collect_unbound_var_spans(arg, target, bound, out);
5630 }
5631 Expr::Project(_, base, _) => {
5632 collect_unbound_var_spans(base, target, bound, out);
5633 }
5634 Expr::Lam(_, _scope, param, _ann, _constraints, body) => {
5635 bound.push(param.name.clone());
5636 collect_unbound_var_spans(body, target, bound, out);
5637 bound.pop();
5638 }
5639 Expr::Let(_, var, _ann, def, body) => {
5640 collect_unbound_var_spans(def, target, bound, out);
5641 bound.push(var.name.clone());
5642 collect_unbound_var_spans(body, target, bound, out);
5643 bound.pop();
5644 }
5645 Expr::LetRec(_, bindings, body) => {
5646 let base_len = bound.len();
5647 for (var, _ann, _def) in bindings {
5648 bound.push(var.name.clone());
5649 }
5650 for (_var, _ann, def) in bindings {
5651 collect_unbound_var_spans(def, target, bound, out);
5652 }
5653 collect_unbound_var_spans(body, target, bound, out);
5654 bound.truncate(base_len);
5655 }
5656 Expr::Ite(_, cond, then_expr, else_expr) => {
5657 collect_unbound_var_spans(cond, target, bound, out);
5658 collect_unbound_var_spans(then_expr, target, bound, out);
5659 collect_unbound_var_spans(else_expr, target, bound, out);
5660 }
5661 Expr::Match(_, scrutinee, arms) => {
5662 collect_unbound_var_spans(scrutinee, target, bound, out);
5663 for (pat, arm) in arms {
5664 let base_len = bound.len();
5665 let mut pat_bindings = Vec::new();
5666 collect_pattern_bindings(pat, &mut pat_bindings);
5667 bound.extend(pat_bindings);
5668 collect_unbound_var_spans(arm, target, bound, out);
5669 bound.truncate(base_len);
5670 }
5671 }
5672 Expr::Ann(_, inner, _) => {
5673 collect_unbound_var_spans(inner, target, bound, out);
5674 }
5675 Expr::Tuple(_, items) | Expr::List(_, items) => {
5676 for item in items {
5677 collect_unbound_var_spans(item, target, bound, out);
5678 }
5679 }
5680 Expr::Dict(_, kvs) | Expr::RecordUpdate(_, _, kvs) => {
5681 for expr in kvs.values() {
5682 collect_unbound_var_spans(expr, target, bound, out);
5683 }
5684 if let Expr::RecordUpdate(_, base, _) = expr {
5685 collect_unbound_var_spans(base, target, bound, out);
5686 }
5687 }
5688 Expr::Bool(..)
5689 | Expr::Uint(..)
5690 | Expr::Int(..)
5691 | Expr::Float(..)
5692 | Expr::String(..)
5693 | Expr::Uuid(..)
5694 | Expr::DateTime(..)
5695 | Expr::Hole(..) => {}
5696 }
5697}
5698
5699fn push_ts_error(
5700 err: TsTypeError,
5701 diagnostics: &mut Vec<Diagnostic>,
5702 expr: Option<&Expr>,
5703 ts: Option<&TypeSystem>,
5704 fallback_span: Option<Span>,
5705) {
5706 let unknown_target = unknown_var_name(&err);
5707 let (span, message) = match &err {
5708 TsTypeError::Spanned { span, error } => (*span, error.to_string()),
5709 other => (
5710 fallback_span
5711 .or_else(|| expr.map(|e| *e.span()))
5712 .unwrap_or_default(),
5713 other.to_string(),
5714 ),
5715 };
5716
5717 if let (Some(target), Some(expr), Some(ts)) = (unknown_target, expr, ts)
5718 && ts.env.lookup(&target).is_none()
5719 {
5720 let mut spans = Vec::new();
5721 collect_unbound_var_spans(expr, &target, &mut Vec::new(), &mut spans);
5722 spans.sort_unstable_by_key(|s| (s.begin.line, s.begin.column, s.end.line, s.end.column));
5723 spans.dedup();
5724 if !spans.is_empty() {
5725 for unbound_span in spans {
5726 if diagnostics.len() >= MAX_DIAGNOSTICS {
5727 break;
5728 }
5729 diagnostics.push(Diagnostic {
5730 range: span_to_range(unbound_span),
5731 severity: Some(DiagnosticSeverity::ERROR),
5732 message: message.clone(),
5733 source: Some("rexlang-typesystem".to_string()),
5734 ..Diagnostic::default()
5735 });
5736 }
5737 return;
5738 }
5739 }
5740
5741 diagnostics.push(Diagnostic {
5742 range: span_to_range(span),
5743 severity: Some(DiagnosticSeverity::ERROR),
5744 message,
5745 source: Some("rexlang-typesystem".to_string()),
5746 ..Diagnostic::default()
5747 });
5748}
5749
5750fn range_contains_position(range: Range, position: Position) -> bool {
5751 let after_start = position.line > range.start.line
5752 || (position.line == range.start.line && position.character >= range.start.character);
5753 let before_end = position.line < range.end.line
5754 || (position.line == range.end.line && position.character < range.end.character);
5755 after_start && before_end
5756}
5757
5758fn range_touches_position(range: Range, position: Position) -> bool {
5759 if range_contains_position(range, position) {
5764 return true;
5765 }
5766 if position.line != range.end.line || position.character != range.end.character {
5767 return false;
5768 }
5769 position.line != range.start.line || position.character != range.start.character
5771}
5772
5773fn span_to_range(span: Span) -> Range {
5774 Range {
5775 start: position_from_span(span.begin.line, span.begin.column),
5776 end: position_from_span(span.end.line, span.end.column),
5777 }
5778}
5779
5780fn position_from_span(line: usize, column: usize) -> Position {
5781 Position {
5782 line: line.saturating_sub(1) as u32,
5783 character: column.saturating_sub(1) as u32,
5784 }
5785}
5786
5787fn hover_contents(word: &str) -> Option<HoverContents> {
5788 if let Some(doc) = keyword_doc(word) {
5789 return Some(markdown_hover(word, "keyword", doc));
5790 }
5791
5792 if let Some(doc) = type_doc(word) {
5793 return Some(markdown_hover(word, "type", doc));
5794 }
5795
5796 if let Some(doc) = value_doc(word) {
5797 return Some(markdown_hover(word, "value", doc));
5798 }
5799
5800 None
5801}
5802
5803fn markdown_hover(word: &str, kind: &str, doc: &str) -> HoverContents {
5804 HoverContents::Markup(MarkupContent {
5805 kind: MarkupKind::Markdown,
5806 value: format!("**{}** {}\n\n{}", word, kind, doc),
5807 })
5808}
5809
5810fn keyword_doc(word: &str) -> Option<&'static str> {
5811 match word {
5812 "let" => Some("Introduces local bindings."),
5813 "in" => Some("Begins the expression body for a let binding."),
5814 "type" => Some("Declares a type or ADT."),
5815 "match" => Some("Starts a pattern match expression."),
5816 "when" => Some("Introduces a match arm."),
5817 "if" => Some("Conditional expression keyword."),
5818 "then" => Some("Conditional expression branch."),
5819 "else" => Some("Fallback branch of a conditional expression."),
5820 "as" => Some("Type ascription or aliasing keyword."),
5821 "for" => Some("List/dict comprehension keyword (when supported)."),
5822 "is" => Some("Type assertion keyword."),
5823 _ => None,
5824 }
5825}
5826
5827fn type_doc(word: &str) -> Option<&'static str> {
5828 match word {
5829 "bool" => Some("Boolean type."),
5830 "string" => Some("UTF-8 string type."),
5831 "uuid" => Some("UUID type."),
5832 "datetime" => Some("Datetime type."),
5833 "u8" => Some("Unsigned 8-bit integer."),
5834 "u16" => Some("Unsigned 16-bit integer."),
5835 "u32" => Some("Unsigned 32-bit integer."),
5836 "u64" => Some("Unsigned 64-bit integer."),
5837 "i8" => Some("Signed 8-bit integer."),
5838 "i16" => Some("Signed 16-bit integer."),
5839 "i32" => Some("Signed 32-bit integer."),
5840 "i64" => Some("Signed 64-bit integer."),
5841 "f32" => Some("32-bit float."),
5842 "f64" => Some("64-bit float."),
5843 "List" => Some("List type constructor."),
5844 "Dict" => Some("Dictionary type constructor."),
5845 "Array" => Some("Array type constructor."),
5846 "Option" => Some("Optional type constructor."),
5847 "Result" => Some("Result type constructor."),
5848 _ => None,
5849 }
5850}
5851
5852fn value_doc(word: &str) -> Option<&'static str> {
5853 match word {
5854 "true" => Some("Boolean literal."),
5855 "false" => Some("Boolean literal."),
5856 "null" => Some("Null literal."),
5857 "Some" => Some("Option constructor."),
5858 "None" => Some("Option empty constructor."),
5859 "Ok" => Some("Result success constructor."),
5860 "Err" => Some("Result error constructor."),
5861 _ => None,
5862 }
5863}
5864
5865fn completion_items(uri: &Url, text: &str, position: Position) -> Vec<CompletionItem> {
5866 let field_mode = is_field_completion(text, position);
5867 let base_ident = if field_mode {
5868 field_base_ident(text, position)
5869 } else {
5870 None
5871 };
5872 if let Ok((_tokens, program)) = tokenize_and_parse_cached(uri, text) {
5873 return completion_items_from_program(
5874 &program,
5875 position,
5876 field_mode,
5877 base_ident.as_deref(),
5878 uri,
5879 );
5880 }
5881
5882 completion_items_fallback(text, base_ident.as_deref(), field_mode)
5883}
5884
5885fn completion_items_from_program(
5886 program: &Program,
5887 position: Position,
5888 field_mode: bool,
5889 base_ident: Option<&str>,
5890 uri: &Url,
5891) -> Vec<CompletionItem> {
5892 if field_mode {
5893 if let Some(base_ident) = base_ident
5894 && let Ok(exports) = completion_exports_for_library_alias(uri, program, base_ident)
5895 && !exports.is_empty()
5896 {
5897 return exports
5898 .into_iter()
5899 .map(|label| completion_item(label, CompletionItemKind::FIELD))
5900 .collect();
5901 }
5902 if let Some(fields) = field_completion_for_position(program, position, base_ident) {
5903 return fields
5904 .into_iter()
5905 .map(|label| completion_item(label, CompletionItemKind::FIELD))
5906 .collect();
5907 }
5908 return Vec::new();
5909 }
5910
5911 let mut value_kinds = values_in_scope_at_position(program, position);
5912 let pos = lsp_to_rex_position(position);
5913 for decl in &program.decls {
5914 let Decl::Import(id) = decl else { continue };
5915 if position_in_span(pos, id.span) || position_leq(id.span.end, pos) {
5916 value_kinds
5917 .entry(id.alias.as_ref().to_string())
5918 .or_insert(CompletionItemKind::MODULE);
5919 }
5920 }
5921 for value in BUILTIN_VALUES {
5922 value_kinds
5923 .entry((*value).to_string())
5924 .or_insert(CompletionItemKind::VARIABLE);
5925 }
5926 for (value, kind) in prelude_completion_values() {
5927 value_kinds.entry(value.clone()).or_insert(*kind);
5928 }
5929 for ctor in collect_constructors(program) {
5930 value_kinds
5931 .entry(ctor)
5932 .or_insert(CompletionItemKind::CONSTRUCTOR);
5933 }
5934
5935 let mut type_names = collect_type_names(program);
5936 type_names.extend(BUILTIN_TYPES.iter().map(|value| value.to_string()));
5937
5938 let mut items = Vec::new();
5939 items.extend(
5940 value_kinds
5941 .into_iter()
5942 .map(|(label, kind)| completion_item(label, kind)),
5943 );
5944 items.extend(
5945 type_names
5946 .into_iter()
5947 .map(|label| completion_item(label, CompletionItemKind::CLASS)),
5948 );
5949
5950 items
5951}
5952
5953fn completion_items_fallback(
5954 text: &str,
5955 base_ident: Option<&str>,
5956 field_mode: bool,
5957) -> Vec<CompletionItem> {
5958 let mut identifiers: HashMap<String, CompletionItemKind> = HashMap::new();
5959
5960 if let Ok(tokens) = Token::tokenize(text) {
5961 identifiers.extend(function_defs_from_tokens(&tokens));
5962
5963 let mut index = 0usize;
5964 while index < tokens.items.len() {
5965 if let Token::Ident(name, ..) = &tokens.items[index] {
5966 identifiers
5967 .entry(name.clone())
5968 .or_insert(CompletionItemKind::VARIABLE);
5969 }
5970 index += 1;
5971 }
5972
5973 if field_mode {
5974 if let Some(base_ident) = base_ident
5975 && let Some(fields) = fallback_field_map(&tokens).get(base_ident)
5976 {
5977 return fields
5978 .iter()
5979 .cloned()
5980 .map(|label| completion_item(label, CompletionItemKind::FIELD))
5981 .collect();
5982 }
5983 return Vec::new();
5984 }
5985 }
5986
5987 let mut items: Vec<CompletionItem> = identifiers
5988 .into_iter()
5989 .map(|(label, kind)| completion_item(label, kind))
5990 .collect();
5991 items.extend(
5992 BUILTIN_TYPES
5993 .iter()
5994 .map(|label| completion_item((*label).to_string(), CompletionItemKind::CLASS)),
5995 );
5996 items
5997}
5998
5999fn completion_item(label: String, kind: CompletionItemKind) -> CompletionItem {
6000 CompletionItem {
6001 label,
6002 kind: Some(kind),
6003 ..CompletionItem::default()
6004 }
6005}
6006
6007fn values_in_scope_at_position(
6008 program: &Program,
6009 position: Position,
6010) -> HashMap<String, CompletionItemKind> {
6011 let pos = lsp_to_rex_position(position);
6012 let expr = program.expr_with_fns();
6013 values_in_scope_at_expr(&expr, pos, &mut Vec::new()).unwrap_or_default()
6014}
6015
6016fn values_in_scope_at_expr(
6017 expr: &Expr,
6018 position: RexPosition,
6019 scope: &mut Vec<(String, CompletionItemKind)>,
6020) -> Option<HashMap<String, CompletionItemKind>> {
6021 if !position_in_span(position, *expr.span()) {
6022 return None;
6023 }
6024
6025 fn scope_to_map(scope: &[(String, CompletionItemKind)]) -> HashMap<String, CompletionItemKind> {
6026 let mut map = HashMap::new();
6029 for (name, kind) in scope {
6030 let slot = map.entry(name.clone()).or_insert(*kind);
6031 if *slot != CompletionItemKind::FUNCTION && *kind == CompletionItemKind::FUNCTION {
6032 *slot = *kind;
6033 }
6034 }
6035 map
6036 }
6037
6038 match expr {
6039 Expr::Let(_span, var, _ann, def, body) => {
6040 if position_in_span(position, *def.span()) {
6041 return values_in_scope_at_expr(def, position, scope)
6042 .or_else(|| Some(scope_to_map(scope)));
6043 }
6044
6045 if position_in_span(position, *body.span()) {
6046 let kind = matches!(def.as_ref(), Expr::Lam(..))
6047 .then_some(CompletionItemKind::FUNCTION)
6048 .unwrap_or(CompletionItemKind::VARIABLE);
6049 scope.push((var.name.to_string(), kind));
6050 let out = values_in_scope_at_expr(body, position, scope)
6051 .or_else(|| Some(scope_to_map(scope)));
6052 scope.pop();
6053 return out;
6054 }
6055
6056 Some(scope_to_map(scope))
6057 }
6058 Expr::LetRec(_span, bindings, body) => {
6059 let base_len = scope.len();
6060 scope.extend(bindings.iter().map(|(var, _ann, def)| {
6061 let kind = matches!(def.as_ref(), Expr::Lam(..))
6062 .then_some(CompletionItemKind::FUNCTION)
6063 .unwrap_or(CompletionItemKind::VARIABLE);
6064 (var.name.to_string(), kind)
6065 }));
6066
6067 for (_, _, def) in bindings {
6068 if position_in_span(position, *def.span()) {
6069 let out = values_in_scope_at_expr(def, position, scope)
6070 .or_else(|| Some(scope_to_map(scope)));
6071 scope.truncate(base_len);
6072 return out;
6073 }
6074 }
6075
6076 if position_in_span(position, *body.span()) {
6077 let out = values_in_scope_at_expr(body, position, scope)
6078 .or_else(|| Some(scope_to_map(scope)));
6079 scope.truncate(base_len);
6080 return out;
6081 }
6082
6083 scope.truncate(base_len);
6084 Some(scope_to_map(scope))
6085 }
6086 Expr::Lam(_span, _scope, param, _ann, _constraints, body) => {
6087 if position_in_span(position, *body.span()) {
6088 scope.push((param.name.to_string(), CompletionItemKind::VARIABLE));
6089 let out = values_in_scope_at_expr(body, position, scope)
6090 .or_else(|| Some(scope_to_map(scope)));
6091 scope.pop();
6092 return out;
6093 }
6094 Some(scope_to_map(scope))
6095 }
6096 Expr::Match(_span, scrutinee, arms) => {
6097 if position_in_span(position, *scrutinee.span()) {
6098 return values_in_scope_at_expr(scrutinee, position, scope)
6099 .or_else(|| Some(scope_to_map(scope)));
6100 }
6101 for (pattern, arm) in arms {
6102 if position_in_span(position, *pattern.span()) {
6103 return Some(scope_to_map(scope));
6104 }
6105 if position_in_span(position, *arm.span()) {
6106 let base_len = scope.len();
6107 scope.extend(
6108 pattern_vars(pattern)
6109 .into_iter()
6110 .map(|name| (name, CompletionItemKind::VARIABLE)),
6111 );
6112 let out = values_in_scope_at_expr(arm, position, scope)
6113 .or_else(|| Some(scope_to_map(scope)));
6114 scope.truncate(base_len);
6115 return out;
6116 }
6117 }
6118 Some(scope_to_map(scope))
6119 }
6120 Expr::App(_span, fun, arg) => {
6121 if position_in_span(position, *fun.span()) {
6122 return values_in_scope_at_expr(fun, position, scope)
6123 .or_else(|| Some(scope_to_map(scope)));
6124 }
6125 if position_in_span(position, *arg.span()) {
6126 return values_in_scope_at_expr(arg, position, scope)
6127 .or_else(|| Some(scope_to_map(scope)));
6128 }
6129 Some(scope_to_map(scope))
6130 }
6131 Expr::Project(_span, base, _field) => {
6132 if position_in_span(position, *base.span()) {
6133 return values_in_scope_at_expr(base, position, scope)
6134 .or_else(|| Some(scope_to_map(scope)));
6135 }
6136 Some(scope_to_map(scope))
6137 }
6138 Expr::Tuple(_span, elems) | Expr::List(_span, elems) => {
6139 for elem in elems {
6140 if position_in_span(position, *elem.span()) {
6141 return values_in_scope_at_expr(elem, position, scope)
6142 .or_else(|| Some(scope_to_map(scope)));
6143 }
6144 }
6145 Some(scope_to_map(scope))
6146 }
6147 Expr::Dict(_span, entries) => {
6148 for value in entries.values() {
6149 if position_in_span(position, *value.span()) {
6150 return values_in_scope_at_expr(value, position, scope)
6151 .or_else(|| Some(scope_to_map(scope)));
6152 }
6153 }
6154 Some(scope_to_map(scope))
6155 }
6156 Expr::Ite(_span, cond, then_expr, else_expr) => {
6157 if position_in_span(position, *cond.span()) {
6158 return values_in_scope_at_expr(cond, position, scope)
6159 .or_else(|| Some(scope_to_map(scope)));
6160 }
6161 if position_in_span(position, *then_expr.span()) {
6162 return values_in_scope_at_expr(then_expr, position, scope)
6163 .or_else(|| Some(scope_to_map(scope)));
6164 }
6165 if position_in_span(position, *else_expr.span()) {
6166 return values_in_scope_at_expr(else_expr, position, scope)
6167 .or_else(|| Some(scope_to_map(scope)));
6168 }
6169 Some(scope_to_map(scope))
6170 }
6171 Expr::Ann(_span, inner, _ann) => {
6172 if position_in_span(position, *inner.span()) {
6173 return values_in_scope_at_expr(inner, position, scope)
6174 .or_else(|| Some(scope_to_map(scope)));
6175 }
6176 Some(scope_to_map(scope))
6177 }
6178 _ => Some(scope_to_map(scope)),
6179 }
6180}
6181
6182fn function_defs_from_tokens(tokens: &Tokens) -> HashMap<String, CompletionItemKind> {
6183 let mut out = HashMap::new();
6191 let items = &tokens.items;
6192 let mut index = 0usize;
6193
6194 let next_non_ws = |mut i: usize| -> Option<usize> {
6195 while i < items.len() && items[i].is_whitespace() {
6196 i += 1;
6197 }
6198 (i < items.len()).then_some(i)
6199 };
6200
6201 while index < items.len() {
6202 if matches!(items[index], Token::Fn(..)) {
6203 let Some(i) = next_non_ws(index + 1) else {
6204 break;
6205 };
6206 if let Token::Ident(name, ..) = &items[i] {
6207 out.insert(name.clone(), CompletionItemKind::FUNCTION);
6208 }
6209 index += 1;
6210 continue;
6211 }
6212
6213 if !matches!(items[index], Token::Let(..)) {
6214 index += 1;
6215 continue;
6216 }
6217
6218 let Some(mut i) = next_non_ws(index + 1) else {
6219 break;
6220 };
6221
6222 let name = match &items[i] {
6223 Token::Ident(name, ..) => name.clone(),
6224 _ => {
6225 index += 1;
6226 continue;
6227 }
6228 };
6229
6230 i += 1;
6232 loop {
6233 let Some(j) = next_non_ws(i) else {
6234 break;
6235 };
6236 match &items[j] {
6237 Token::Assign(..) => {
6238 let Some(k) = next_non_ws(j + 1) else {
6239 break;
6240 };
6241 if matches!(items[k], Token::BackSlash(..)) {
6242 out.insert(name, CompletionItemKind::FUNCTION);
6243 }
6244 break;
6245 }
6246 Token::SemiColon(..) => break,
6247 _ => i = j + 1,
6248 }
6249 }
6250
6251 index += 1;
6252 }
6253
6254 out
6255}
6256
6257fn collect_type_names(program: &Program) -> BTreeSet<String> {
6258 let mut names = BTreeSet::new();
6259 for decl in &program.decls {
6260 if let Decl::Type(TypeDecl { name, .. }) = decl {
6261 names.insert(name.to_string());
6262 }
6263 }
6264 names
6265}
6266
6267fn collect_constructors(program: &Program) -> BTreeSet<String> {
6268 let mut names = BTreeSet::new();
6269 for decl in &program.decls {
6270 if let Decl::Type(TypeDecl { variants, .. }) = decl {
6271 for variant in variants {
6272 names.insert(variant.name.to_string());
6273 }
6274 }
6275 }
6276 names
6277}
6278
6279fn collect_fields_type_expr(typ: &TypeExpr, fields: &mut BTreeSet<String>) {
6280 match typ {
6281 TypeExpr::Record(_, entries) => {
6282 for (name, _ty) in entries {
6283 fields.insert(name.to_string());
6284 }
6285 }
6286 TypeExpr::App(_, fun, arg) => {
6287 collect_fields_type_expr(fun, fields);
6288 collect_fields_type_expr(arg, fields);
6289 }
6290 TypeExpr::Fun(_, arg, ret) => {
6291 collect_fields_type_expr(arg, fields);
6292 collect_fields_type_expr(ret, fields);
6293 }
6294 TypeExpr::Tuple(_, elems) => {
6295 for elem in elems {
6296 collect_fields_type_expr(elem, fields);
6297 }
6298 }
6299 TypeExpr::Name(..) => {}
6300 }
6301}
6302
6303fn field_completion_for_position(
6304 program: &Program,
6305 position: Position,
6306 base_ident: Option<&str>,
6307) -> Option<BTreeSet<String>> {
6308 let type_fields = type_fields_map(program);
6309 let env = field_env_at_position(program, position, &type_fields);
6310 let pos = lsp_to_rex_position(position);
6311
6312 let expr = program.expr_with_fns();
6313 if let Some(base) = project_base_at_position(&expr, pos)
6314 && let Some(fields) = fields_for_expr(base, &env, &type_fields)
6315 {
6316 return Some(fields);
6317 }
6318
6319 if let Some(base_ident) = base_ident {
6320 if let Some(fields) = env.get(base_ident) {
6321 return Some(fields.clone());
6322 }
6323 if let Some(fields) = type_fields.get(base_ident) {
6324 return Some(fields.clone());
6325 }
6326 }
6327
6328 None
6329}
6330
6331fn type_fields_map(program: &Program) -> HashMap<String, BTreeSet<String>> {
6332 let mut map = HashMap::new();
6333 for decl in &program.decls {
6334 if let Decl::Type(TypeDecl { name, variants, .. }) = decl {
6335 let mut fields = BTreeSet::new();
6336 for variant in variants {
6337 for arg in &variant.args {
6338 collect_fields_type_expr(arg, &mut fields);
6339 }
6340 }
6341 if !fields.is_empty() {
6342 map.insert(name.to_string(), fields);
6343 }
6344 }
6345 }
6346 map
6347}
6348
6349fn field_env_at_position(
6350 program: &Program,
6351 position: Position,
6352 type_fields: &HashMap<String, BTreeSet<String>>,
6353) -> HashMap<String, BTreeSet<String>> {
6354 let pos = lsp_to_rex_position(position);
6355 let expr = program.expr_with_fns();
6356 field_env_at_expr(&expr, pos, &HashMap::new(), type_fields).unwrap_or_default()
6357}
6358
6359fn field_env_at_expr(
6360 expr: &Expr,
6361 position: RexPosition,
6362 env: &HashMap<String, BTreeSet<String>>,
6363 type_fields: &HashMap<String, BTreeSet<String>>,
6364) -> Option<HashMap<String, BTreeSet<String>>> {
6365 if !position_in_span(position, *expr.span()) {
6366 return None;
6367 }
6368
6369 match expr {
6370 Expr::Let(_, var, ann, def, body) => {
6371 if position_in_span(position, *def.span()) {
6372 return field_env_at_expr(def, position, env, type_fields)
6373 .or_else(|| Some(env.clone()));
6374 }
6375 if position_in_span(position, *body.span()) {
6376 let mut env_with = env.clone();
6377 let fields = binding_fields(ann.as_ref(), def, type_fields).unwrap_or_default();
6378 env_with.insert(var.name.to_string(), fields);
6379 if let Some(inner) = field_env_at_expr(body, position, &env_with, type_fields) {
6380 return Some(inner);
6381 }
6382 return Some(env_with);
6383 }
6384 Some(env.clone())
6385 }
6386 Expr::LetRec(_, bindings, body) => {
6387 let mut env_with = env.clone();
6388 for (var, ann, def) in bindings {
6389 let fields = binding_fields(ann.as_ref(), def, type_fields).unwrap_or_default();
6390 env_with.insert(var.name.to_string(), fields);
6391 }
6392 for (_, _, def) in bindings {
6393 if position_in_span(position, *def.span()) {
6394 return field_env_at_expr(def, position, &env_with, type_fields)
6395 .or_else(|| Some(env_with.clone()));
6396 }
6397 }
6398 if position_in_span(position, *body.span()) {
6399 if let Some(inner) = field_env_at_expr(body, position, &env_with, type_fields) {
6400 return Some(inner);
6401 }
6402 return Some(env_with);
6403 }
6404 Some(env.clone())
6405 }
6406 Expr::Lam(_, _scope, param, ann, _constraints, body) => {
6407 if position_in_span(position, *body.span()) {
6408 let mut env_with = env.clone();
6409 let fields = ann
6410 .as_ref()
6411 .and_then(|ann| fields_from_type_expr(ann, type_fields))
6412 .unwrap_or_default();
6413 env_with.insert(param.name.to_string(), fields);
6414 if let Some(inner) = field_env_at_expr(body, position, &env_with, type_fields) {
6415 return Some(inner);
6416 }
6417 return Some(env_with);
6418 }
6419 Some(env.clone())
6420 }
6421 Expr::Match(_, scrutinee, arms) => {
6422 if position_in_span(position, *scrutinee.span()) {
6423 return field_env_at_expr(scrutinee, position, env, type_fields)
6424 .or_else(|| Some(env.clone()));
6425 }
6426 for (pattern, arm) in arms {
6427 if position_in_span(position, *pattern.span()) {
6428 return Some(env.clone());
6429 }
6430 if position_in_span(position, *arm.span()) {
6431 let mut env_with = env.clone();
6432 env_with.extend(
6433 pattern_vars(pattern)
6434 .into_iter()
6435 .map(|name| (name, BTreeSet::new())),
6436 );
6437 if let Some(inner) = field_env_at_expr(arm, position, &env_with, type_fields) {
6438 return Some(inner);
6439 }
6440 return Some(env_with);
6441 }
6442 }
6443 Some(env.clone())
6444 }
6445 Expr::App(_, fun, arg) => {
6446 if position_in_span(position, *fun.span()) {
6447 return field_env_at_expr(fun, position, env, type_fields)
6448 .or_else(|| Some(env.clone()));
6449 }
6450 if position_in_span(position, *arg.span()) {
6451 return field_env_at_expr(arg, position, env, type_fields)
6452 .or_else(|| Some(env.clone()));
6453 }
6454 Some(env.clone())
6455 }
6456 Expr::Project(_, base, _field) => {
6457 if position_in_span(position, *base.span()) {
6458 return field_env_at_expr(base, position, env, type_fields)
6459 .or_else(|| Some(env.clone()));
6460 }
6461 Some(env.clone())
6462 }
6463 Expr::Tuple(_, elems) | Expr::List(_, elems) => {
6464 for elem in elems {
6465 if position_in_span(position, *elem.span()) {
6466 return field_env_at_expr(elem, position, env, type_fields)
6467 .or_else(|| Some(env.clone()));
6468 }
6469 }
6470 Some(env.clone())
6471 }
6472 Expr::Dict(_, entries) => {
6473 for value in entries.values() {
6474 if position_in_span(position, *value.span()) {
6475 return field_env_at_expr(value, position, env, type_fields)
6476 .or_else(|| Some(env.clone()));
6477 }
6478 }
6479 Some(env.clone())
6480 }
6481 Expr::Ite(_, cond, then_expr, else_expr) => {
6482 if position_in_span(position, *cond.span()) {
6483 return field_env_at_expr(cond, position, env, type_fields)
6484 .or_else(|| Some(env.clone()));
6485 }
6486 if position_in_span(position, *then_expr.span()) {
6487 return field_env_at_expr(then_expr, position, env, type_fields)
6488 .or_else(|| Some(env.clone()));
6489 }
6490 if position_in_span(position, *else_expr.span()) {
6491 return field_env_at_expr(else_expr, position, env, type_fields)
6492 .or_else(|| Some(env.clone()));
6493 }
6494 Some(env.clone())
6495 }
6496 Expr::Ann(_, inner, _ann) => {
6497 if position_in_span(position, *inner.span()) {
6498 return field_env_at_expr(inner, position, env, type_fields)
6499 .or_else(|| Some(env.clone()));
6500 }
6501 Some(env.clone())
6502 }
6503 _ => Some(env.clone()),
6504 }
6505}
6506
6507fn binding_fields(
6508 ann: Option<&TypeExpr>,
6509 def: &Expr,
6510 type_fields: &HashMap<String, BTreeSet<String>>,
6511) -> Option<BTreeSet<String>> {
6512 if let Some(ann) = ann
6513 && let Some(fields) = fields_from_type_expr(ann, type_fields)
6514 {
6515 return Some(fields);
6516 }
6517
6518 if let Expr::Ann(_, _inner, ann) = def
6519 && let Some(fields) = fields_from_type_expr(ann, type_fields)
6520 {
6521 return Some(fields);
6522 }
6523
6524 if let Expr::Dict(_, entries) = def {
6525 let fields: BTreeSet<String> = entries.keys().map(|name| name.to_string()).collect();
6526 if !fields.is_empty() {
6527 return Some(fields);
6528 }
6529 }
6530
6531 None
6532}
6533
6534fn fields_from_type_expr(
6535 typ: &TypeExpr,
6536 type_fields: &HashMap<String, BTreeSet<String>>,
6537) -> Option<BTreeSet<String>> {
6538 match typ {
6539 TypeExpr::Record(_, entries) => {
6540 let fields: BTreeSet<String> =
6541 entries.iter().map(|(name, _)| name.to_string()).collect();
6542 if fields.is_empty() {
6543 None
6544 } else {
6545 Some(fields)
6546 }
6547 }
6548 _ => {
6549 if let Some(type_name) = type_name_from_type_expr(typ) {
6550 return type_fields.get(&type_name).cloned();
6551 }
6552 None
6553 }
6554 }
6555}
6556
6557fn type_name_from_type_expr(typ: &TypeExpr) -> Option<String> {
6558 match typ {
6559 TypeExpr::Name(_, name) => Some(name.to_string()),
6560 TypeExpr::App(_, fun, _) => type_name_from_type_expr(fun),
6561 _ => None,
6562 }
6563}
6564
6565fn fields_for_expr(
6566 expr: &Expr,
6567 env: &HashMap<String, BTreeSet<String>>,
6568 type_fields: &HashMap<String, BTreeSet<String>>,
6569) -> Option<BTreeSet<String>> {
6570 match expr {
6571 Expr::Dict(_, entries) => {
6572 let fields: BTreeSet<String> = entries.keys().map(|name| name.to_string()).collect();
6573 if fields.is_empty() {
6574 None
6575 } else {
6576 Some(fields)
6577 }
6578 }
6579 Expr::Var(var) => {
6580 if let Some(fields) = env.get(var.name.as_ref()) {
6581 return Some(fields.clone());
6582 }
6583 if let Some(fields) = type_fields.get(var.name.as_ref()) {
6584 return Some(fields.clone());
6585 }
6586 None
6587 }
6588 Expr::Ann(_, inner, ann) => fields_from_type_expr(ann, type_fields)
6589 .or_else(|| fields_for_expr(inner, env, type_fields)),
6590 Expr::Project(_, base, _) => fields_for_expr(base, env, type_fields),
6591 _ => None,
6592 }
6593}
6594
6595fn project_base_at_position(expr: &Expr, position: RexPosition) -> Option<&Expr> {
6596 if !position_in_span(position, *expr.span()) {
6597 return None;
6598 }
6599
6600 match expr {
6601 Expr::Project(_, base, _) => {
6602 if position_in_span(position, *base.span()) {
6603 return project_base_at_position(base, position);
6604 }
6605 Some(base.as_ref())
6606 }
6607 Expr::Let(_, _var, _ann, def, body) => {
6608 if let Some(found) = project_base_at_position(def, position) {
6609 return Some(found);
6610 }
6611 project_base_at_position(body, position)
6612 }
6613 Expr::LetRec(_, bindings, body) => {
6614 for (_, _, def) in bindings {
6615 if let Some(found) = project_base_at_position(def, position) {
6616 return Some(found);
6617 }
6618 }
6619 project_base_at_position(body, position)
6620 }
6621 Expr::Lam(_, _scope, _param, _ann, _constraints, body) => {
6622 project_base_at_position(body, position)
6623 }
6624 Expr::Match(_, scrutinee, arms) => {
6625 if let Some(found) = project_base_at_position(scrutinee, position) {
6626 return Some(found);
6627 }
6628 for (_pattern, arm) in arms {
6629 if let Some(found) = project_base_at_position(arm, position) {
6630 return Some(found);
6631 }
6632 }
6633 None
6634 }
6635 Expr::App(_, fun, arg) => {
6636 if let Some(found) = project_base_at_position(fun, position) {
6637 return Some(found);
6638 }
6639 project_base_at_position(arg, position)
6640 }
6641 Expr::Tuple(_, elems) | Expr::List(_, elems) => {
6642 for elem in elems {
6643 if let Some(found) = project_base_at_position(elem, position) {
6644 return Some(found);
6645 }
6646 }
6647 None
6648 }
6649 Expr::Dict(_, entries) => {
6650 for value in entries.values() {
6651 if let Some(found) = project_base_at_position(value, position) {
6652 return Some(found);
6653 }
6654 }
6655 None
6656 }
6657 Expr::Ite(_, cond, then_expr, else_expr) => {
6658 if let Some(found) = project_base_at_position(cond, position) {
6659 return Some(found);
6660 }
6661 if let Some(found) = project_base_at_position(then_expr, position) {
6662 return Some(found);
6663 }
6664 project_base_at_position(else_expr, position)
6665 }
6666 Expr::Ann(_, inner, _ann) => project_base_at_position(inner, position),
6667 _ => None,
6668 }
6669}
6670
6671fn fallback_field_map(tokens: &Tokens) -> HashMap<String, BTreeSet<String>> {
6672 let mut map = HashMap::new();
6673 let items = &tokens.items;
6674 let mut index = 0usize;
6675 while index + 2 < items.len() {
6676 if let Token::Ident(name, ..) = &items[index]
6677 && matches!(items[index + 1], Token::Assign(..) | Token::Colon(..))
6678 && matches!(items[index + 2], Token::BraceL(..))
6679 && let Some((fields, end_index)) = parse_record_fields(items, index + 2)
6680 {
6681 if !fields.is_empty() {
6682 map.insert(name.clone(), fields);
6683 }
6684 index = end_index + 1;
6685 continue;
6686 }
6687 index += 1;
6688 }
6689 map
6690}
6691
6692fn parse_record_fields(tokens: &[Token], start_index: usize) -> Option<(BTreeSet<String>, usize)> {
6693 if !matches!(tokens.get(start_index), Some(Token::BraceL(..))) {
6694 return None;
6695 }
6696
6697 let mut depth = 0usize;
6698 let mut fields = BTreeSet::new();
6699 let mut index = start_index;
6700 while index < tokens.len() {
6701 match &tokens[index] {
6702 Token::BraceL(..) => depth += 1,
6703 Token::BraceR(..) => {
6704 depth = depth.saturating_sub(1);
6705 if depth == 0 {
6706 return Some((fields, index));
6707 }
6708 }
6709 Token::Ident(name, ..) if depth == 1 => {
6710 if let Some(next) = tokens.get(index + 1)
6711 && matches!(next, Token::Assign(..) | Token::Colon(..))
6712 {
6713 fields.insert(name.clone());
6714 }
6715 }
6716 _ => {}
6717 }
6718 index += 1;
6719 }
6720
6721 None
6722}
6723
6724fn field_base_ident(text: &str, position: Position) -> Option<String> {
6725 let offset = offset_at(text, position)?;
6726 if offset == 0 {
6727 return None;
6728 }
6729
6730 let bytes = text.as_bytes();
6731 let mut index = offset.min(bytes.len());
6732
6733 while index > 0 && bytes[index - 1].is_ascii_whitespace() {
6734 index -= 1;
6735 }
6736 while index > 0 && is_word_byte(bytes[index - 1]) {
6737 index -= 1;
6738 }
6739 while index > 0 && bytes[index - 1].is_ascii_whitespace() {
6740 index -= 1;
6741 }
6742
6743 if index == 0 || bytes[index - 1] != b'.' {
6744 return None;
6745 }
6746
6747 index -= 1;
6748 while index > 0 && bytes[index - 1].is_ascii_whitespace() {
6749 index -= 1;
6750 }
6751
6752 let end = index;
6753 while index > 0 && is_word_byte(bytes[index - 1]) {
6754 index -= 1;
6755 }
6756
6757 if index == end {
6758 return None;
6759 }
6760
6761 Some(text[index..end].to_string())
6762}
6763
6764fn is_word_byte(byte: u8) -> bool {
6765 let ch = byte as char;
6766 ch.is_ascii_alphanumeric() || ch == '_'
6767}
6768
6769fn ident_token_at_position(tokens: &Tokens, position: Position) -> Option<(String, Span)> {
6770 for token in &tokens.items {
6771 let Token::Ident(name, span, ..) = token else {
6772 continue;
6773 };
6774 if range_touches_position(span_to_range(*span), position) {
6775 return Some((name.clone(), *span));
6776 }
6777 }
6778 None
6779}
6780
6781fn imported_projection_at_position(
6782 tokens: &Tokens,
6783 position: Position,
6784) -> Option<(String, String)> {
6785 fn is_trivia(token: &Token) -> bool {
6786 matches!(
6787 token,
6788 Token::Whitespace(..) | Token::CommentL(..) | Token::CommentR(..)
6789 )
6790 }
6791
6792 fn prev_non_trivia(tokens: &Tokens, start: usize) -> Option<usize> {
6793 let mut idx = start;
6794 while idx > 0 {
6795 idx -= 1;
6796 if !is_trivia(&tokens.items[idx]) {
6797 return Some(idx);
6798 }
6799 }
6800 None
6801 }
6802
6803 let mut ident_index = None;
6804 let mut field = None;
6805 for (idx, token) in tokens.items.iter().enumerate() {
6806 let Token::Ident(name, span, ..) = token else {
6807 continue;
6808 };
6809 if range_touches_position(span_to_range(*span), position) {
6810 ident_index = Some(idx);
6811 field = Some(name.clone());
6812 break;
6813 }
6814 }
6815 let ident_index = ident_index?;
6816 let field = field?;
6817
6818 let dot_idx = prev_non_trivia(tokens, ident_index)?;
6819 if !matches!(tokens.items[dot_idx], Token::Dot(..)) {
6820 return None;
6821 }
6822 let base_idx = prev_non_trivia(tokens, dot_idx)?;
6823 let Token::Ident(base, ..) = &tokens.items[base_idx] else {
6824 return None;
6825 };
6826
6827 Some((base.clone(), field))
6828}
6829
6830struct DeclSpanIndex {
6831 type_defs: HashMap<String, Span>,
6832 ctor_defs: HashMap<String, Span>,
6833 class_defs: HashMap<String, Span>,
6834 fn_defs: HashMap<String, Span>,
6835 class_method_defs: HashMap<String, Span>,
6836 instance_method_defs: Vec<(Span, HashMap<String, Span>)>,
6837}
6838
6839fn index_decl_spans(program: &Program, tokens: &Tokens) -> DeclSpanIndex {
6840 fn span_contains_span(outer: Span, inner: Span) -> bool {
6841 position_leq(outer.begin, inner.begin) && position_leq(inner.end, outer.end)
6842 }
6843
6844 let mut type_defs = HashMap::new();
6845 let mut ctor_defs = HashMap::new();
6846 let mut class_defs = HashMap::new();
6847 let mut fn_defs = HashMap::new();
6848 let mut class_method_defs = HashMap::new();
6849 let mut instance_method_defs = Vec::new();
6850
6851 for decl in &program.decls {
6852 match decl {
6853 Decl::Type(td) => {
6854 let decl_span = td.span;
6855 let mut expect_type_name = false;
6856 let mut expect_ctor_name = false;
6857
6858 for token in &tokens.items {
6859 let token_span = *token.span();
6860 if !span_contains_span(decl_span, token_span) {
6861 continue;
6862 }
6863
6864 match token {
6865 Token::Type(..) => {
6866 expect_type_name = true;
6867 expect_ctor_name = false;
6868 }
6869 Token::Ident(name, span, ..) if expect_type_name => {
6870 type_defs.insert(name.clone(), *span);
6871 expect_type_name = false;
6872 }
6873 Token::Assign(..) | Token::Pipe(..) => {
6874 expect_ctor_name = true;
6875 }
6876 Token::Ident(name, span, ..) if expect_ctor_name => {
6877 ctor_defs.insert(name.clone(), *span);
6878 expect_ctor_name = false;
6879 }
6880 _ => {}
6881 }
6882 }
6883 }
6884 Decl::Class(cd) => {
6885 let decl_span = cd.span;
6886 let mut expect_class_name = false;
6887 for i in 0..tokens.items.len() {
6888 let token = &tokens.items[i];
6889 let token_span = *token.span();
6890 if !span_contains_span(decl_span, token_span) {
6891 continue;
6892 }
6893 match token {
6894 Token::Class(..) => expect_class_name = true,
6895 Token::Ident(name, span, ..) if expect_class_name => {
6896 class_defs.insert(name.clone(), *span);
6897 expect_class_name = false;
6898 }
6899 Token::Ident(name, span, ..) => {
6900 if let Some(next) = tokens.items.get(i + 1)
6901 && matches!(next, Token::Colon(..))
6902 {
6903 class_method_defs.insert(name.clone(), *span);
6904 }
6905 }
6906 _ => {}
6907 }
6908 }
6909 }
6910 Decl::Instance(id) => {
6911 let decl_span = id.span;
6912 let mut methods = HashMap::new();
6913 for i in 0..tokens.items.len() {
6914 let token = &tokens.items[i];
6915 let token_span = *token.span();
6916 if !span_contains_span(decl_span, token_span) {
6917 continue;
6918 }
6919 if let Token::Ident(name, span, ..) = token
6920 && let Some(next) = tokens.items.get(i + 1)
6921 && matches!(next, Token::Assign(..))
6922 {
6923 methods.insert(name.clone(), *span);
6924 }
6925 }
6926 instance_method_defs.push((decl_span, methods));
6927 }
6928 Decl::Fn(fd) => {
6929 fn_defs.insert(fd.name.name.as_ref().to_string(), fd.name.span);
6930 }
6931 Decl::DeclareFn(fd) => {
6932 fn_defs.insert(fd.name.name.as_ref().to_string(), fd.name.span);
6933 }
6934 Decl::Import(..) => {}
6935 }
6936 }
6937
6938 DeclSpanIndex {
6939 type_defs,
6940 ctor_defs,
6941 class_defs,
6942 fn_defs,
6943 class_method_defs,
6944 instance_method_defs,
6945 }
6946}
6947
6948fn definition_span_for_value_ident(
6949 expr: &Expr,
6950 position: RexPosition,
6951 ident: &str,
6952 bindings: &mut Vec<(String, Span)>,
6953 tokens: &Tokens,
6954) -> Option<Span> {
6955 if !position_in_span(position, *expr.span()) {
6956 return None;
6957 }
6958
6959 fn lookup_binding(bindings: &[(String, Span)], ident: &str) -> Option<Span> {
6960 bindings
6961 .iter()
6962 .rev()
6963 .find_map(|(name, span)| (name == ident).then_some(*span))
6964 }
6965
6966 fn definition_in_pattern(
6967 pat: &Pattern,
6968 position: RexPosition,
6969 ident: &str,
6970 _tokens: &Tokens,
6971 ) -> Option<Span> {
6972 if !position_in_span(position, *pat.span()) {
6973 return None;
6974 }
6975
6976 match pat {
6977 Pattern::Var(var) => (var.name.as_ref() == ident).then_some(var.span),
6978 Pattern::Named(_span, _name, args) => args
6979 .iter()
6980 .find_map(|arg| definition_in_pattern(arg, position, ident, _tokens)),
6981 Pattern::Tuple(_span, elems) => elems
6982 .iter()
6983 .find_map(|elem| definition_in_pattern(elem, position, ident, _tokens)),
6984 Pattern::List(_span, elems) => elems
6985 .iter()
6986 .find_map(|elem| definition_in_pattern(elem, position, ident, _tokens)),
6987 Pattern::Cons(_span, head, tail) => {
6988 definition_in_pattern(head, position, ident, _tokens)
6989 .or_else(|| definition_in_pattern(tail, position, ident, _tokens))
6990 }
6991 Pattern::Dict(_span, fields) => fields
6992 .iter()
6993 .find_map(|(_, p)| definition_in_pattern(p, position, ident, _tokens)),
6994 Pattern::Wildcard(..) => None,
6995 }
6996 }
6997
6998 fn push_pattern_bindings(pat: &Pattern, bindings: &mut Vec<(String, Span)>, _tokens: &Tokens) {
6999 match pat {
7000 Pattern::Var(var) => bindings.push((var.name.to_string(), var.span)),
7001 Pattern::Named(_span, _name, args) => {
7002 for arg in args {
7003 push_pattern_bindings(arg, bindings, _tokens);
7004 }
7005 }
7006 Pattern::Tuple(_span, elems) => {
7007 for elem in elems {
7008 push_pattern_bindings(elem, bindings, _tokens);
7009 }
7010 }
7011 Pattern::List(_span, elems) => {
7012 for elem in elems {
7013 push_pattern_bindings(elem, bindings, _tokens);
7014 }
7015 }
7016 Pattern::Cons(_span, head, tail) => {
7017 push_pattern_bindings(head, bindings, _tokens);
7018 push_pattern_bindings(tail, bindings, _tokens);
7019 }
7020 Pattern::Dict(_span, fields) => {
7021 for (_key, pat) in fields {
7022 push_pattern_bindings(pat, bindings, _tokens);
7023 }
7024 }
7025 Pattern::Wildcard(..) => {}
7026 }
7027 }
7028
7029 match expr {
7030 Expr::Var(var) => {
7031 if position_in_span(position, var.span) && var.name.as_ref() == ident {
7032 return lookup_binding(bindings, ident);
7033 }
7034 None
7035 }
7036 Expr::Let(_span, var, _ann, def, body) => {
7037 if position_in_span(position, var.span) && var.name.as_ref() == ident {
7038 return Some(var.span);
7039 }
7040
7041 if position_in_span(position, *def.span()) {
7042 return definition_span_for_value_ident(def, position, ident, bindings, tokens);
7043 }
7044 if position_in_span(position, *body.span()) {
7045 bindings.push((var.name.to_string(), var.span));
7046 let out = definition_span_for_value_ident(body, position, ident, bindings, tokens);
7047 bindings.pop();
7048 return out;
7049 }
7050 None
7051 }
7052 Expr::LetRec(_span, rec_bindings, body) => {
7053 for (var, _ann, _def) in rec_bindings {
7054 if position_in_span(position, var.span) && var.name.as_ref() == ident {
7055 return Some(var.span);
7056 }
7057 }
7058
7059 let base_len = bindings.len();
7060 for (var, _ann, _def) in rec_bindings {
7061 bindings.push((var.name.to_string(), var.span));
7062 }
7063
7064 for (_var, _ann, def) in rec_bindings {
7065 if position_in_span(position, *def.span()) {
7066 let out =
7067 definition_span_for_value_ident(def, position, ident, bindings, tokens);
7068 bindings.truncate(base_len);
7069 return out;
7070 }
7071 }
7072 if position_in_span(position, *body.span()) {
7073 let out = definition_span_for_value_ident(body, position, ident, bindings, tokens);
7074 bindings.truncate(base_len);
7075 return out;
7076 }
7077
7078 bindings.truncate(base_len);
7079 None
7080 }
7081 Expr::Lam(_span, _scope, param, _ann, _constraints, body) => {
7082 if position_in_span(position, param.span) && param.name.as_ref() == ident {
7083 return Some(param.span);
7084 }
7085
7086 if position_in_span(position, *body.span()) {
7087 bindings.push((param.name.to_string(), param.span));
7088 let out = definition_span_for_value_ident(body, position, ident, bindings, tokens);
7089 bindings.pop();
7090 return out;
7091 }
7092 None
7093 }
7094 Expr::Match(_span, scrutinee, arms) => {
7095 if position_in_span(position, *scrutinee.span()) {
7096 return definition_span_for_value_ident(
7097 scrutinee, position, ident, bindings, tokens,
7098 );
7099 }
7100
7101 for (pat, arm) in arms {
7102 if position_in_span(position, *pat.span()) {
7103 return definition_in_pattern(pat, position, ident, tokens);
7104 }
7105
7106 if position_in_span(position, *arm.span()) {
7107 let base_len = bindings.len();
7108 push_pattern_bindings(pat, bindings, tokens);
7109 let out =
7110 definition_span_for_value_ident(arm, position, ident, bindings, tokens);
7111 bindings.truncate(base_len);
7112 return out;
7113 }
7114 }
7115 None
7116 }
7117 Expr::App(_span, fun, arg) => {
7118 if position_in_span(position, *fun.span()) {
7119 return definition_span_for_value_ident(fun, position, ident, bindings, tokens);
7120 }
7121 if position_in_span(position, *arg.span()) {
7122 return definition_span_for_value_ident(arg, position, ident, bindings, tokens);
7123 }
7124 None
7125 }
7126 Expr::Project(_span, base, _field) => {
7127 if position_in_span(position, *base.span()) {
7128 return definition_span_for_value_ident(base, position, ident, bindings, tokens);
7129 }
7130 None
7131 }
7132 Expr::Tuple(_span, elems) | Expr::List(_span, elems) => elems.iter().find_map(|elem| {
7133 position_in_span(position, *elem.span())
7134 .then(|| definition_span_for_value_ident(elem, position, ident, bindings, tokens))
7135 .flatten()
7136 }),
7137 Expr::Dict(_span, entries) => entries.values().find_map(|value| {
7138 position_in_span(position, *value.span())
7139 .then(|| definition_span_for_value_ident(value, position, ident, bindings, tokens))
7140 .flatten()
7141 }),
7142 Expr::Ite(_span, cond, then_expr, else_expr) => {
7143 if position_in_span(position, *cond.span()) {
7144 return definition_span_for_value_ident(cond, position, ident, bindings, tokens);
7145 }
7146 if position_in_span(position, *then_expr.span()) {
7147 return definition_span_for_value_ident(
7148 then_expr, position, ident, bindings, tokens,
7149 );
7150 }
7151 if position_in_span(position, *else_expr.span()) {
7152 return definition_span_for_value_ident(
7153 else_expr, position, ident, bindings, tokens,
7154 );
7155 }
7156 None
7157 }
7158 Expr::Ann(_span, inner, _ann) => {
7159 if position_in_span(position, *inner.span()) {
7160 return definition_span_for_value_ident(inner, position, ident, bindings, tokens);
7161 }
7162 None
7163 }
7164 _ => None,
7165 }
7166}
7167
7168fn pattern_vars(pattern: &Pattern) -> Vec<String> {
7172 let mut vars = Vec::new();
7173 collect_pattern_vars(pattern, &mut vars);
7174 vars
7175}
7176
7177fn collect_pattern_vars(pattern: &Pattern, vars: &mut Vec<String>) {
7178 match pattern {
7179 Pattern::Var(var) => vars.push(var.name.to_string()),
7180 Pattern::Named(_, _name, args) => {
7181 for arg in args {
7182 collect_pattern_vars(arg, vars);
7183 }
7184 }
7185 Pattern::Tuple(_, elems) => {
7186 for elem in elems {
7187 collect_pattern_vars(elem, vars);
7188 }
7189 }
7190 Pattern::List(_, elems) => {
7191 for elem in elems {
7192 collect_pattern_vars(elem, vars);
7193 }
7194 }
7195 Pattern::Cons(_, head, tail) => {
7196 collect_pattern_vars(head, vars);
7197 collect_pattern_vars(tail, vars);
7198 }
7199 Pattern::Dict(_, fields) => {
7200 for (_key, pat) in fields {
7201 collect_pattern_vars(pat, vars);
7202 }
7203 }
7204 Pattern::Wildcard(_) => {}
7205 }
7206}
7207
7208fn is_field_completion(text: &str, position: Position) -> bool {
7209 let offset = match offset_at(text, position) {
7210 Some(offset) => offset,
7211 None => return false,
7212 };
7213
7214 if offset == 0 {
7215 return false;
7216 }
7217
7218 let mut start = offset;
7219 while start > 0 {
7220 let prev = text.as_bytes()[start - 1] as char;
7221 if is_word_char(prev) {
7222 start -= 1;
7223 continue;
7224 }
7225 break;
7226 }
7227
7228 if start > 0 && text.as_bytes()[start - 1] as char == '.' {
7229 return true;
7230 }
7231
7232 text.as_bytes()[offset.saturating_sub(1)] as char == '.'
7233}
7234
7235fn lsp_to_rex_position(position: Position) -> RexPosition {
7236 RexPosition::new(position.line as usize + 1, position.character as usize + 1)
7237}
7238
7239fn position_in_span(position: RexPosition, span: Span) -> bool {
7240 position_leq(span.begin, position) && position_leq(position, span.end)
7241}
7242
7243fn position_leq(left: RexPosition, right: RexPosition) -> bool {
7244 left.line < right.line || (left.line == right.line && left.column <= right.column)
7245}
7246
7247fn word_at_position(text: &str, position: Position) -> Option<String> {
7248 let offset = offset_at(text, position)?;
7249 if offset >= text.len() {
7250 return None;
7251 }
7252
7253 let chars: Vec<(usize, char)> = text.char_indices().collect();
7254 let mut idx = None;
7255 for (i, (byte_index, _)) in chars.iter().enumerate() {
7256 if *byte_index == offset {
7257 idx = Some(i);
7258 break;
7259 }
7260 }
7261
7262 let idx = idx?;
7263 if !is_word_char(chars[idx].1) {
7264 return None;
7265 }
7266
7267 let mut start = idx;
7268 while start > 0 && is_word_char(chars[start - 1].1) {
7269 start -= 1;
7270 }
7271
7272 let mut end = idx + 1;
7273 while end < chars.len() && is_word_char(chars[end].1) {
7274 end += 1;
7275 }
7276
7277 let start_byte = chars[start].0;
7278 let end_byte = if end < chars.len() {
7279 chars[end].0
7280 } else {
7281 text.len()
7282 };
7283
7284 Some(text[start_byte..end_byte].to_string())
7285}
7286
7287fn offset_at(text: &str, position: Position) -> Option<usize> {
7288 let mut offset = 0usize;
7289 let mut current_line = 0u32;
7290
7291 for mut line in text.split('\n') {
7292 if line.ends_with('\r') {
7293 line = &line[..line.len().saturating_sub(1)];
7294 }
7295
7296 if current_line == position.line {
7297 let mut remaining = position.character as usize;
7298 for (byte_index, _) in line.char_indices() {
7299 if remaining == 0 {
7300 return Some(offset + byte_index);
7301 }
7302 remaining -= 1;
7303 }
7304 return Some(offset + line.len());
7305 }
7306
7307 offset += line.len() + 1;
7308 current_line += 1;
7309 }
7310
7311 if current_line == position.line {
7312 Some(offset)
7313 } else {
7314 None
7315 }
7316}
7317
7318fn is_word_char(ch: char) -> bool {
7319 ch.is_ascii_alphanumeric() || ch == '_'
7320}
7321
7322fn in_memory_doc_uri() -> Url {
7323 match Url::parse("inmemory:///docs.rex") {
7324 Ok(url) => url,
7325 Err(_) => panic!("static in-memory URI must parse"),
7326 }
7327}
7328
7329pub fn diagnostics_for_source(source: &str) -> Vec<Diagnostic> {
7330 let uri = in_memory_doc_uri();
7331 clear_parse_cache(&uri);
7332 diagnostics_from_text(&uri, source)
7333}
7334
7335pub fn completion_for_source(source: &str, line: u32, character: u32) -> Vec<CompletionItem> {
7336 let uri = in_memory_doc_uri();
7337 clear_parse_cache(&uri);
7338 completion_items(&uri, source, Position { line, character })
7339}
7340
7341pub fn hover_for_source(source: &str, line: u32, character: u32) -> Option<Hover> {
7342 let uri = in_memory_doc_uri();
7343 clear_parse_cache(&uri);
7344 let position = Position { line, character };
7345 let contents = hover_type_contents(&uri, source, position).or_else(|| {
7346 let word = word_at_position(source, position)?;
7347 hover_contents(&word)
7348 })?;
7349 Some(Hover {
7350 contents,
7351 range: None,
7352 })
7353}
7354
7355pub fn expected_type_for_source_public(source: &str, line: u32, character: u32) -> Option<String> {
7356 let uri = in_memory_doc_uri();
7357 clear_parse_cache(&uri);
7358 expected_type_at_position(&uri, source, Position { line, character })
7359}
7360
7361pub fn functions_producing_expected_type_for_source_public(
7362 source: &str,
7363 line: u32,
7364 character: u32,
7365) -> Vec<String> {
7366 let uri = in_memory_doc_uri();
7367 clear_parse_cache(&uri);
7368 functions_producing_expected_type_at_position(&uri, source, Position { line, character })
7369 .into_iter()
7370 .map(|(name, typ)| format!("{name} : {typ}"))
7371 .collect()
7372}
7373
7374pub fn references_for_source_public(
7375 source: &str,
7376 line: u32,
7377 character: u32,
7378 include_declaration: bool,
7379) -> Vec<Location> {
7380 let uri = in_memory_doc_uri();
7381 clear_parse_cache(&uri);
7382 references_for_source(
7383 &uri,
7384 source,
7385 Position { line, character },
7386 include_declaration,
7387 )
7388}
7389
7390pub fn rename_for_source_public(
7391 source: &str,
7392 line: u32,
7393 character: u32,
7394 new_name: &str,
7395) -> Option<WorkspaceEdit> {
7396 let uri = in_memory_doc_uri();
7397 clear_parse_cache(&uri);
7398 rename_for_source(&uri, source, Position { line, character }, new_name)
7399}
7400
7401pub fn document_symbols_for_source_public(source: &str) -> Vec<DocumentSymbol> {
7402 let uri = in_memory_doc_uri();
7403 clear_parse_cache(&uri);
7404 document_symbols_for_source(&uri, source)
7405}
7406
7407pub fn format_for_source_public(source: &str) -> Option<Vec<TextEdit>> {
7408 format_edits_for_source(source)
7409}
7410
7411pub fn code_actions_for_source_public(
7412 source: &str,
7413 line: u32,
7414 character: u32,
7415) -> Vec<CodeActionOrCommand> {
7416 let uri = in_memory_doc_uri();
7417 clear_parse_cache(&uri);
7418 let position = Position { line, character };
7419 let range = Range {
7420 start: position,
7421 end: position,
7422 };
7423 let diagnostics: Vec<Diagnostic> = diagnostics_from_text(&uri, source)
7424 .into_iter()
7425 .filter(|diag| {
7426 range_contains_position(diag.range, position)
7427 || range_touches_position(diag.range, position)
7428 })
7429 .collect();
7430 code_actions_for_source(&uri, source, range, &diagnostics)
7431}
7432
7433pub fn goto_definition_for_source(source: &str, line: u32, character: u32) -> Option<Location> {
7434 let uri = in_memory_doc_uri();
7435 clear_parse_cache(&uri);
7436 let pos = Position { line, character };
7437 let response = goto_definition_response(&uri, source, pos)?;
7438 match response {
7439 GotoDefinitionResponse::Scalar(location) => Some(location),
7440 GotoDefinitionResponse::Array(locations) => locations.into_iter().next(),
7441 GotoDefinitionResponse::Link(links) => links.into_iter().next().map(|link| Location {
7442 uri: link.target_uri,
7443 range: link.target_range,
7444 }),
7445 }
7446}
7447
7448#[cfg(not(target_arch = "wasm32"))]
7449pub async fn run_stdio() {
7450 let stdin = tokio::io::stdin();
7451 let stdout = tokio::io::stdout();
7452
7453 let (service, socket) = LspService::new(RexServer::new);
7454 Server::new(stdin, stdout, socket).serve(service).await;
7455}
7456
7457#[cfg(test)]
7458mod tests {
7459 use super::*;
7460 use rexlang_core::{Engine, GasMeter, Parser, Token};
7461 use rexlang_engine::{ValueDisplayOptions, pointer_display_with};
7462 use serde_json::Map;
7463 use std::fs;
7464 use std::path::PathBuf;
7465
7466 fn expect_object(value: &Value) -> &Map<String, Value> {
7467 value.as_object().expect("object")
7468 }
7469
7470 fn expect_array_field<'a>(obj: &'a Map<String, Value>, key: &str) -> &'a Vec<Value> {
7471 obj.get(key)
7472 .unwrap_or_else(|| panic!("missing `{key}`"))
7473 .as_array()
7474 .unwrap_or_else(|| panic!("`{key}` should be array"))
7475 }
7476
7477 fn expect_string_field<'a>(obj: &'a Map<String, Value>, key: &str) -> &'a str {
7478 obj.get(key)
7479 .unwrap_or_else(|| panic!("missing `{key}`"))
7480 .as_str()
7481 .unwrap_or_else(|| panic!("`{key}` should be string"))
7482 }
7483
7484 fn temp_dir(name: &str) -> PathBuf {
7485 let mut dir = std::env::temp_dir();
7486 let nonce = std::time::SystemTime::now()
7487 .duration_since(std::time::UNIX_EPOCH)
7488 .expect("clock before epoch")
7489 .as_nanos();
7490 dir.push(format!("rexlang-lsp-test-{name}-{nonce}"));
7491 fs::create_dir_all(&dir).expect("create temp dir");
7492 dir
7493 }
7494
7495 fn assert_internal_name_ref(name: &rexlang_ast::expr::NameRef) {
7496 match name {
7497 rexlang_ast::expr::NameRef::Unqualified(sym) => {
7498 assert!(
7499 sym.as_ref().starts_with("@m"),
7500 "expected internal rewritten symbol, got `{sym}`"
7501 );
7502 }
7503 other => panic!("expected unqualified rewritten name, got {other:?}"),
7504 }
7505 }
7506
7507 async fn eval_source_to_display(code: &str) -> (String, String) {
7508 let tokens = Token::tokenize(code).expect("tokenize source");
7509 let mut parser = Parser::new(tokens);
7510 let program = parser
7511 .parse_program(&mut GasMeter::default())
7512 .expect("parse source");
7513 let mut engine = Engine::with_prelude(()).expect("build engine");
7514 engine.inject_decls(&program.decls).expect("inject decls");
7515 let (ptr, ty) = rexlang_engine::Evaluator::new_with_compiler(
7516 rexlang_engine::RuntimeEnv::new(engine.clone()),
7517 rexlang_engine::Compiler::new(engine.clone()),
7518 )
7519 .eval(program.expr.as_ref(), &mut GasMeter::default())
7520 .await
7521 .expect("evaluate source");
7522 let display = pointer_display_with(
7523 &engine.heap,
7524 &ptr,
7525 ValueDisplayOptions {
7526 include_numeric_suffixes: true,
7527 ..ValueDisplayOptions::default()
7528 },
7529 )
7530 .expect("display value");
7531 (display, ty.to_string())
7532 }
7533
7534 #[test]
7535 fn stdlib_imports_typecheck_for_non_file_uri() {
7536 let uri = Url::parse("untitled:Test.rex").expect("uri");
7537 let text = r#"
7538import std.io
7539import std.process
7540
7541let _ = io.debug "hi" in
7542let p = process.spawn { cmd = "sh", args = ["-c"] } in
7543process.wait p
7544"#;
7545
7546 let diags = diagnostics_from_text(&uri, text);
7547 assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7548 }
7549
7550 #[test]
7551 fn prepare_program_rewrites_imported_type_refs_in_annotations() {
7552 let dir = temp_dir("prepare_program_rewrites_imported_type_refs_in_annotations");
7553 let main = dir.join("main.rex");
7554 let dep = dir.join("dep.rex");
7555 fs::write(
7556 &dep,
7557 r#"
7558pub type Boxed = Boxed i32
7559"#,
7560 )
7561 .expect("write dep");
7562 fs::write(&main, "()").expect("write main");
7563
7564 let uri = Url::from_file_path(&main).expect("main file uri");
7565 let source = r#"
7566import dep as D
7567
7568let x : D.Boxed = D.Boxed 1 in
7569x is D.Boxed
7570"#;
7571 let tokens = Token::tokenize(source).expect("tokenize");
7572 let mut parser = Parser::new(tokens);
7573 let program = parser
7574 .parse_program(&mut GasMeter::default())
7575 .expect("parse");
7576 let (rewritten, _ts, _imports, diags) =
7577 prepare_program_with_imports(&uri, &program).expect("prepare");
7578 assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7579
7580 let Expr::Let(_, _, Some(let_ann), _, body) = rewritten.expr.as_ref() else {
7581 panic!("expected rewritten let expression");
7582 };
7583 if let TypeExpr::Name(_, name) = let_ann {
7584 assert_internal_name_ref(name);
7585 } else {
7586 panic!("expected rewritten let annotation");
7587 }
7588
7589 let Expr::Ann(_, _, ann_ty) = body.as_ref() else {
7590 panic!("expected rewritten trailing annotation");
7591 };
7592 if let TypeExpr::Name(_, name) = ann_ty {
7593 assert_internal_name_ref(name);
7594 } else {
7595 panic!("expected rewritten annotation type");
7596 }
7597
7598 if let Expr::Let(_, _, _, def, _) = rewritten.expr.as_ref()
7599 && let Expr::App(_, ctor, _) = def.as_ref()
7600 && let Expr::Var(v) = ctor.as_ref()
7601 {
7602 assert!(
7603 v.name.as_ref().starts_with("@m"),
7604 "expected constructor projection rewrite to internal symbol"
7605 );
7606 } else {
7607 panic!("expected rewritten constructor application");
7608 }
7609 }
7610
7611 #[test]
7612 fn prepare_program_rewrites_imported_class_refs_in_instance_headers() {
7613 let dir = temp_dir("prepare_program_rewrites_imported_class_refs_in_instance_headers");
7614 let main = dir.join("main.rex");
7615 let dep = dir.join("dep.rex");
7616 fs::write(
7617 &dep,
7618 r#"
7619pub class Pick a where
7620 pick : a
7621()
7622"#,
7623 )
7624 .expect("write dep");
7625 fs::write(&main, "()").expect("write main");
7626
7627 let uri = Url::from_file_path(&main).expect("main file uri");
7628 let source = r#"
7629import dep as D
7630
7631instance D.Pick i32 where
7632 pick = 7
7633
7634pick is i32
7635"#;
7636 let tokens = Token::tokenize(source).expect("tokenize");
7637 let mut parser = Parser::new(tokens);
7638 let program = parser
7639 .parse_program(&mut GasMeter::default())
7640 .expect("parse");
7641 let (rewritten, _ts, _imports, diags) =
7642 prepare_program_with_imports(&uri, &program).expect("prepare");
7643 assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7644
7645 let Some(inst) = rewritten.decls.iter().find_map(|decl| match decl {
7646 Decl::Instance(inst) => Some(inst),
7647 _ => None,
7648 }) else {
7649 panic!("expected instance declaration");
7650 };
7651 assert!(
7652 inst.class.as_ref().starts_with("@m"),
7653 "expected rewritten internal class symbol, got `{}`",
7654 inst.class
7655 );
7656 }
7657
7658 #[test]
7659 fn diagnostics_report_missing_class_export_in_instance_header() {
7660 let dir = temp_dir("diagnostics_report_missing_class_export_in_instance_header");
7661 let main = dir.join("main.rex");
7662 let dep = dir.join("dep.rex");
7663 fs::write(
7664 &dep,
7665 r#"
7666pub class Present a where
7667 present : a
7668()
7669"#,
7670 )
7671 .expect("write dep");
7672 fs::write(&main, "()").expect("write main");
7673
7674 let uri = Url::from_file_path(&main).expect("main file uri");
7675 let source = r#"
7676import dep as D
7677
7678instance D.Missing i32 where
7679 missing = 1
7680
76810
7682"#;
7683 let diags = diagnostics_from_text(&uri, source);
7684 assert!(
7685 diags
7686 .iter()
7687 .any(|d| d.message.contains("does not export") && d.message.contains("Missing")),
7688 "diagnostics: {diags:#?}"
7689 );
7690 }
7691
7692 #[test]
7693 fn diagnostics_report_missing_type_export_in_annotation() {
7694 let dir = temp_dir("diagnostics_report_missing_type_export_in_annotation");
7695 let main = dir.join("main.rex");
7696 let dep = dir.join("dep.rex");
7697 fs::write(
7698 &dep,
7699 r#"
7700pub type Present = Present i32
7701()
7702"#,
7703 )
7704 .expect("write dep");
7705 fs::write(&main, "()").expect("write main");
7706
7707 let uri = Url::from_file_path(&main).expect("main file uri");
7708 let source = r#"
7709import dep as D
7710
7711fn id x: D.Missing -> D.Missing = x
7712
77130
7714"#;
7715 let diags = diagnostics_from_text(&uri, source);
7716 assert!(
7717 diags
7718 .iter()
7719 .any(|d| d.message.contains("does not export") && d.message.contains("Missing")),
7720 "diagnostics: {diags:#?}"
7721 );
7722 }
7723
7724 #[test]
7725 fn diagnostics_report_missing_type_export_in_instance_head() {
7726 let dir = temp_dir("diagnostics_report_missing_type_export_in_instance_head");
7727 let main = dir.join("main.rex");
7728 let dep = dir.join("dep.rex");
7729 fs::write(
7730 &dep,
7731 r#"
7732pub class Marker a where
7733 marker : i32
7734()
7735"#,
7736 )
7737 .expect("write dep");
7738 fs::write(&main, "()").expect("write main");
7739
7740 let uri = Url::from_file_path(&main).expect("main file uri");
7741 let source = r#"
7742import dep as D
7743
7744instance D.Marker D.Missing where
7745 marker = 1
7746
77470
7748"#;
7749 let diags = diagnostics_from_text(&uri, source);
7750 assert!(
7751 diags
7752 .iter()
7753 .any(|d| d.message.contains("does not export") && d.message.contains("Missing")),
7754 "diagnostics: {diags:#?}"
7755 );
7756 }
7757
7758 #[test]
7759 fn diagnostics_report_missing_class_export_in_fn_where_constraint() {
7760 let dir = temp_dir("diagnostics_report_missing_class_export_in_fn_where_constraint");
7761 let main = dir.join("main.rex");
7762 let dep = dir.join("dep.rex");
7763 fs::write(
7764 &dep,
7765 r#"
7766pub class Present a where
7767 present : a
7768()
7769"#,
7770 )
7771 .expect("write dep");
7772 fs::write(&main, "()").expect("write main");
7773
7774 let uri = Url::from_file_path(&main).expect("main file uri");
7775 let source = r#"
7776import dep as D
7777
7778fn id x: i32 -> i32 where D.Missing i32 = x
7779
77800
7781"#;
7782 let diags = diagnostics_from_text(&uri, source);
7783 assert!(
7784 diags
7785 .iter()
7786 .any(|d| d.message.contains("does not export") && d.message.contains("Missing")),
7787 "diagnostics: {diags:#?}"
7788 );
7789 }
7790
7791 #[test]
7792 fn diagnostics_report_missing_class_export_in_declare_fn_where_constraint() {
7793 let dir =
7794 temp_dir("diagnostics_report_missing_class_export_in_declare_fn_where_constraint");
7795 let main = dir.join("main.rex");
7796 let dep = dir.join("dep.rex");
7797 fs::write(
7798 &dep,
7799 r#"
7800pub class Present a where
7801 present : a
7802()
7803"#,
7804 )
7805 .expect("write dep");
7806 fs::write(&main, "()").expect("write main");
7807
7808 let uri = Url::from_file_path(&main).expect("main file uri");
7809 let source = r#"
7810import dep as D
7811
7812declare fn id x: i32 -> i32 where D.Missing i32
7813
78140
7815"#;
7816 let diags = diagnostics_from_text(&uri, source);
7817 assert!(
7818 diags
7819 .iter()
7820 .any(|d| d.message.contains("does not export") && d.message.contains("Missing")),
7821 "diagnostics: {diags:#?}"
7822 );
7823 }
7824
7825 #[test]
7826 fn diagnostics_report_missing_class_export_in_class_super_constraint() {
7827 let dir = temp_dir("diagnostics_report_missing_class_export_in_class_super_constraint");
7828 let main = dir.join("main.rex");
7829 let dep = dir.join("dep.rex");
7830 fs::write(
7831 &dep,
7832 r#"
7833pub class Present a where
7834 present : a
7835()
7836"#,
7837 )
7838 .expect("write dep");
7839 fs::write(&main, "()").expect("write main");
7840
7841 let uri = Url::from_file_path(&main).expect("main file uri");
7842 let source = r#"
7843import dep as D
7844
7845class Local a <= D.Missing a where
7846 local : a
7847
78480
7849"#;
7850 let diags = diagnostics_from_text(&uri, source);
7851 assert!(
7852 diags
7853 .iter()
7854 .any(|d| d.message.contains("does not export") && d.message.contains("Missing")),
7855 "diagnostics: {diags:#?}"
7856 );
7857 }
7858
7859 #[test]
7860 fn diagnostics_allow_lambda_param_named_like_import_alias_in_annotation() {
7861 let dir = temp_dir("diagnostics_allow_lambda_param_named_like_import_alias_in_annotation");
7862 let main = dir.join("main.rex");
7863 let dep = dir.join("dep.rex");
7864 fs::write(
7865 &dep,
7866 r#"
7867pub type Boxed = Boxed i32
7868()
7869"#,
7870 )
7871 .expect("write dep");
7872 fs::write(&main, "()").expect("write main");
7873
7874 let uri = Url::from_file_path(&main).expect("main file uri");
7875 let source = r#"
7876import dep as D
7877
7878let f = \ (D : D.Boxed) -> 0 in
78790
7880"#;
7881 let diags = diagnostics_from_text(&uri, source);
7882 assert!(diags.is_empty(), "diagnostics: {diags:#?}");
7883 }
7884
7885 #[test]
7886 fn diagnostics_report_missing_type_export_in_letrec_annotation_with_alias_named_binding() {
7887 let dir = temp_dir(
7888 "diagnostics_report_missing_type_export_in_letrec_annotation_with_alias_named_binding",
7889 );
7890 let main = dir.join("main.rex");
7891 let dep = dir.join("dep.rex");
7892 fs::write(
7893 &dep,
7894 r#"
7895pub type Present = Present i32
7896()
7897"#,
7898 )
7899 .expect("write dep");
7900 fs::write(&main, "()").expect("write main");
7901
7902 let uri = Url::from_file_path(&main).expect("main file uri");
7903 let source = r#"
7904import dep as D
7905
7906let rec D: D.Missing = 1 in
79070
7908"#;
7909 let diags = diagnostics_from_text(&uri, source);
7910 assert!(
7911 diags
7912 .iter()
7913 .any(|d| d.message.contains("does not export") && d.message.contains("Missing")),
7914 "diagnostics: {diags:#?}"
7915 );
7916 }
7917
7918 #[test]
7919 fn diagnostics_allow_letrec_annotation_with_alias_named_binding_for_valid_type() {
7920 let dir =
7921 temp_dir("diagnostics_allow_letrec_annotation_with_alias_named_binding_for_valid_type");
7922 let main = dir.join("main.rex");
7923 let dep = dir.join("dep.rex");
7924 fs::write(
7925 &dep,
7926 r#"
7927pub type Num = Num i32
7928()
7929"#,
7930 )
7931 .expect("write dep");
7932 fs::write(&main, "()").expect("write main");
7933
7934 let uri = Url::from_file_path(&main).expect("main file uri");
7935 let source = r#"
7936import dep as D
7937import dep (Num)
7938
7939let rec D: D.Num -> i32 = \_ -> 0 in
79400
7941"#;
7942 let diags = diagnostics_from_text(&uri, source);
7943 assert!(diags.is_empty(), "diagnostics: {diags:#?}");
7944 }
7945
7946 #[test]
7947 fn diagnostics_allow_let_annotation_with_alias_named_binding_for_valid_type() {
7948 let dir =
7949 temp_dir("diagnostics_allow_let_annotation_with_alias_named_binding_for_valid_type");
7950 let main = dir.join("main.rex");
7951 let dep = dir.join("dep.rex");
7952 fs::write(
7953 &dep,
7954 r#"
7955pub type Num = Num i32
7956()
7957"#,
7958 )
7959 .expect("write dep");
7960 fs::write(&main, "()").expect("write main");
7961
7962 let uri = Url::from_file_path(&main).expect("main file uri");
7963 let source = r#"
7964import dep as D
7965
7966let D: D.Num -> i32 = \_ -> 0 in
79670
7968"#;
7969 let diags = diagnostics_from_text(&uri, source);
7970 assert!(diags.is_empty(), "diagnostics: {diags:#?}");
7971 }
7972
7973 #[test]
7974 fn diagnostics_report_missing_type_export_in_let_annotation_with_alias_named_binding() {
7975 let dir = temp_dir(
7976 "diagnostics_report_missing_type_export_in_let_annotation_with_alias_named_binding",
7977 );
7978 let main = dir.join("main.rex");
7979 let dep = dir.join("dep.rex");
7980 fs::write(
7981 &dep,
7982 r#"
7983pub type Present = Present i32
7984()
7985"#,
7986 )
7987 .expect("write dep");
7988 fs::write(&main, "()").expect("write main");
7989
7990 let uri = Url::from_file_path(&main).expect("main file uri");
7991 let source = r#"
7992import dep as D
7993
7994let D: D.Missing = 1 in
79950
7996"#;
7997 let diags = diagnostics_from_text(&uri, source);
7998 assert!(
7999 diags
8000 .iter()
8001 .any(|d| d.message.contains("does not export") && d.message.contains("Missing")),
8002 "diagnostics: {diags:#?}"
8003 );
8004 }
8005
8006 #[test]
8007 fn reports_all_unknown_var_usages() {
8008 let text = r#"
8009let
8010 f = \x -> missing + x
8011in
8012 missing + (f missing)
8013"#;
8014
8015 let diags = diagnostics_for_source(text);
8016 let missing_diags = diags
8017 .iter()
8018 .filter(|d| d.message.contains("unbound variable") && d.message.contains("missing"))
8019 .count();
8020 assert_eq!(missing_diags, 3, "diagnostics: {diags:#?}");
8021 }
8022
8023 #[test]
8024 fn diagnostics_report_typed_hole_error() {
8025 let text = "let y : i32 = ? in y";
8026 let diags = diagnostics_for_source(text);
8027 assert!(
8028 diags.iter().any(|d| d
8029 .message
8030 .contains("typed hole `?` must be filled before evaluation")),
8031 "diagnostics: {diags:#?}"
8032 );
8033 }
8034
8035 #[test]
8036 fn diagnostics_report_both_default_record_update_ambiguities() {
8037 let text = r#"
8038type A = A { x: i32, y: i32 }
8039type B = B { x: i32, y: i32 }
8040
8041instance Default A
8042 default = A { x = 1, y = 2 }
8043
8044instance Default B
8045 default = B { x = 10, y = 20 }
8046
8047let
8048 a = { default with { x = 9 } },
8049 b = { default with { y = 8 } }
8050in
8051 (a, b)
8052"#;
8053 let diags = diagnostics_for_source(text);
8054 let field_diags: Vec<&Diagnostic> = diags
8055 .iter()
8056 .filter(|d| d.message.contains("is not definitely available on"))
8057 .collect();
8058 assert_eq!(field_diags.len(), 2, "diagnostics: {diags:#?}");
8059 assert!(
8060 field_diags.iter().any(|d| d.message.contains("field `x`")),
8061 "diagnostics: {diags:#?}"
8062 );
8063 assert!(
8064 field_diags.iter().any(|d| d.message.contains("field `y`")),
8065 "diagnostics: {diags:#?}"
8066 );
8067 }
8068
8069 #[tokio::test]
8070 async fn e2e_ambiguous_default_record_updates_two_quick_fix_styles_then_eval() {
8071 let text = r#"type A = A { x: i32, y: i32 }
8072type B = B { x: i32, y: i32 }
8073
8074instance Default A
8075 default = A { x = 1, y = 2 }
8076
8077instance Default B
8078 default = B { x = 10, y = 20 }
8079
8080let
8081 a = { default with { x = 9 } },
8082 b = { default with { y = 8 } }
8083in
8084 (a, b)
8085"#;
8086 let uri = in_memory_doc_uri();
8087 clear_parse_cache(&uri);
8088
8089 let mut field_diags: Vec<Diagnostic> = diagnostics_from_text(&uri, text)
8090 .into_iter()
8091 .filter(|diag| diag.message.contains("is not definitely available on"))
8092 .collect();
8093 field_diags.sort_by_key(|diag| {
8094 (
8095 diag.range.start.line,
8096 diag.range.start.character,
8097 diag.range.end.line,
8098 diag.range.end.character,
8099 diag.message.clone(),
8100 )
8101 });
8102 assert_eq!(field_diags.len(), 2, "diagnostics: {field_diags:#?}");
8103 assert_eq!(
8104 field_diags[0].message,
8105 "field `x` is not definitely available on 'a"
8106 );
8107 assert_eq!(
8108 field_diags[0].range,
8109 Range {
8110 start: Position {
8111 line: 10,
8112 character: 8,
8113 },
8114 end: Position {
8115 line: 10,
8116 character: 34,
8117 },
8118 }
8119 );
8120 assert_eq!(
8121 field_diags[1].message,
8122 "field `y` is not definitely available on 'a"
8123 );
8124 assert_eq!(
8125 field_diags[1].range,
8126 Range {
8127 start: Position {
8128 line: 11,
8129 character: 8,
8130 },
8131 end: Position {
8132 line: 11,
8133 character: 34,
8134 },
8135 }
8136 );
8137
8138 let step_a = execute_semantic_loop_step(
8139 &uri,
8140 text,
8141 Position {
8142 line: 10,
8143 character: 25,
8144 },
8145 )
8146 .expect("step for a");
8147 let quick_fix_a = step_a
8148 .get("quickFixes")
8149 .and_then(Value::as_array)
8150 .and_then(|items| {
8151 items.iter().find(|item| {
8152 item.get("title").and_then(Value::as_str)
8153 == Some("Disambiguate `default` as `A`")
8154 })
8155 })
8156 .cloned()
8157 .expect("quick fix for a using `is`");
8158 let edit_a: WorkspaceEdit =
8159 serde_json::from_value(quick_fix_a.get("edit").cloned().expect("edit for a"))
8160 .expect("workspace edit for a");
8161 let after_a = apply_workspace_edit_to_text(&uri, text, &edit_a).expect("apply edit for a");
8162
8163 let step_b = execute_semantic_loop_step(
8164 &uri,
8165 &after_a,
8166 Position {
8167 line: 11,
8168 character: 25,
8169 },
8170 )
8171 .expect("step for b");
8172 let quick_fix_b = step_b
8173 .get("quickFixes")
8174 .and_then(Value::as_array)
8175 .and_then(|items| {
8176 items.iter().find(|item| {
8177 item.get("title").and_then(Value::as_str) == Some("Annotate `b` as `B`")
8178 })
8179 })
8180 .cloned()
8181 .expect("quick fix for b using let annotation");
8182 let edit_b: WorkspaceEdit =
8183 serde_json::from_value(quick_fix_b.get("edit").cloned().expect("edit for b"))
8184 .expect("workspace edit for b");
8185 let after_b =
8186 apply_workspace_edit_to_text(&uri, &after_a, &edit_b).expect("apply edit for b");
8187
8188 let diagnostics_after = diagnostics_from_text(&uri, &after_b);
8189 assert!(
8190 diagnostics_after.is_empty(),
8191 "unexpected diagnostics after fixes: {diagnostics_after:#?}\nupdated=\n{after_b}"
8192 );
8193
8194 let (value, ty) = eval_source_to_display(&after_b).await;
8195 assert_eq!(value, "(A {x = 9i32, y = 2i32}, B {x = 10i32, y = 8i32})");
8196 assert_eq!(ty, "(A, B)");
8197 }
8198
8199 #[test]
8200 fn diagnostics_for_decl_type_errors_are_not_whole_document() {
8201 let text = r#"
8202fn parse_ph : string -> Result string f64 = \raw ->
8203 if raw == "7.3" then Ok 7.3 else Err "bad reading"
8204"#;
8205 let uri = in_memory_doc_uri();
8206 clear_parse_cache(&uri);
8207 let diags = diagnostics_from_text(&uri, text);
8208 let full = full_document_range(text);
8209 let unification = diags
8210 .iter()
8211 .find(|d| d.message.contains("types do not unify"))
8212 .expect("unification diagnostic");
8213 assert_ne!(
8214 unification.range, full,
8215 "diagnostic unexpectedly spans whole document: {unification:#?}"
8216 );
8217 }
8218
8219 #[test]
8220 fn references_find_all_usages() {
8221 let text = r#"
8222let
8223 x = 1
8224in
8225 x + x
8226"#;
8227 let refs = references_for_source_public(text, 4, 2, true);
8228 assert_eq!(refs.len(), 3, "refs: {refs:#?}");
8229 }
8230
8231 #[test]
8232 fn rename_returns_workspace_edit() {
8233 let text = r#"
8234let
8235 x = 1
8236in
8237 x + x
8238"#;
8239 let edit = rename_for_source_public(text, 4, 2, "value").expect("rename edit");
8240 let changes = edit.changes.expect("changes map");
8241 let uri = Url::parse("inmemory:///docs.rex").expect("uri");
8242 let edits = changes.get(&uri).expect("uri edits");
8243 assert_eq!(edits.len(), 3, "edits: {edits:#?}");
8244 }
8245
8246 #[test]
8247 fn document_symbols_returns_top_level_items() {
8248 let text = r#"
8249type T = A | B
8250fn f : i32 -> i32 = \x -> x + 1
8251let x = 0 in f x
8252"#;
8253 let symbols = document_symbols_for_source_public(text);
8254 assert!(
8255 symbols.iter().any(|s| s.name == "T"),
8256 "symbols: {symbols:#?}"
8257 );
8258 assert!(
8259 symbols.iter().any(|s| s.name == "f"),
8260 "symbols: {symbols:#?}"
8261 );
8262 }
8263
8264 #[test]
8265 fn formatter_returns_text_edit() {
8266 let text = "let x = 1 \n";
8267 let edits = format_for_source_public(text).expect("format edits");
8268 assert_eq!(edits.len(), 1);
8269 assert_eq!(edits[0].new_text, "let x = 1\n");
8270 }
8271
8272 #[test]
8273 fn code_actions_offer_unknown_var_replacement() {
8274 let text = r#"
8275let
8276 x = 1
8277in
8278 y + x
8279"#;
8280 let actions = code_actions_for_source_public(text, 4, 2);
8281 let titles: Vec<String> = actions
8282 .into_iter()
8283 .filter_map(|action| match action {
8284 CodeActionOrCommand::CodeAction(action) => Some(action.title),
8285 CodeActionOrCommand::Command(_) => None,
8286 })
8287 .collect();
8288 assert!(
8289 titles
8290 .iter()
8291 .any(|title| title.contains("Replace `y` with `x`")),
8292 "titles: {titles:#?}"
8293 );
8294 }
8295
8296 #[test]
8297 fn code_actions_offer_list_wrap_fix() {
8298 let text = r#"
8299let
8300 xs : List i32 = 1
8301in
8302 xs
8303"#;
8304 let uri = in_memory_doc_uri();
8305 clear_parse_cache(&uri);
8306 let diagnostics = diagnostics_from_text(&uri, text);
8307 let list_diag = diagnostics
8308 .into_iter()
8309 .find(|diag| diag.message.contains("types do not unify"))
8310 .expect("expected unification diagnostic");
8311 let list_diag_message = list_diag.message.clone();
8312 assert!(
8313 is_list_scalar_unification_error(&list_diag_message),
8314 "expected list/scalar mismatch, got: {list_diag_message}"
8315 );
8316 let request_range = full_document_range(text);
8317 let actions = code_actions_for_source(&uri, text, request_range, &[list_diag]);
8318 let titles: Vec<String> = actions
8319 .into_iter()
8320 .filter_map(|action| match action {
8321 CodeActionOrCommand::CodeAction(action) => Some(action.title),
8322 CodeActionOrCommand::Command(_) => None,
8323 })
8324 .collect();
8325 assert!(
8326 titles
8327 .iter()
8328 .any(|title| title == "Wrap expression in list literal"),
8329 "diag: {:?}; titles: {titles:#?}",
8330 list_diag_message
8331 );
8332 }
8333
8334 #[test]
8335 fn code_actions_offer_non_exhaustive_match_fix() {
8336 let text = "match (Some 1) when Some x -> x";
8337 let actions = code_actions_for_source_public(text, 0, 2);
8338 let titles: Vec<String> = actions
8339 .into_iter()
8340 .filter_map(|action| match action {
8341 CodeActionOrCommand::CodeAction(action) => Some(action.title),
8342 CodeActionOrCommand::Command(_) => None,
8343 })
8344 .collect();
8345 assert!(
8346 titles
8347 .iter()
8348 .any(|title| title == "Add wildcard arm to match"),
8349 "titles: {titles:#?}"
8350 );
8351 }
8352
8353 #[test]
8354 fn code_actions_offer_function_value_mismatch_fixes() {
8355 let text = "let f = \\x -> x in f + 1";
8356 let uri = in_memory_doc_uri();
8357 clear_parse_cache(&uri);
8358 let diagnostics = diagnostics_from_text(&uri, text);
8359 let fun_mismatch = diagnostics
8360 .into_iter()
8361 .find(|diag| is_function_value_unification_error(&diag.message))
8362 .expect("expected function/value mismatch diagnostic");
8363 let actions =
8364 code_actions_for_source(&uri, text, full_document_range(text), &[fun_mismatch]);
8365 let titles: Vec<String> = actions
8366 .into_iter()
8367 .filter_map(|action| match action {
8368 CodeActionOrCommand::CodeAction(action) => Some(action.title),
8369 CodeActionOrCommand::Command(_) => None,
8370 })
8371 .collect();
8372 assert!(
8373 titles
8374 .iter()
8375 .any(|title| title == "Apply expression to missing argument"),
8376 "titles: {titles:#?}"
8377 );
8378 assert!(
8379 titles
8380 .iter()
8381 .any(|title| title == "Wrap expression in lambda"),
8382 "titles: {titles:#?}"
8383 );
8384 }
8385
8386 #[test]
8387 fn code_actions_offer_to_list_fix_for_array_list_mismatch() {
8388 let text = r#"
8389let
8390 arr = prim_array_from_list [1, 2, 3],
8391 xs : List i32 = arr
8392in
8393 xs
8394"#;
8395 let uri = in_memory_doc_uri();
8396 clear_parse_cache(&uri);
8397 let diagnostics = diagnostics_from_text(&uri, text);
8398 let mismatch = diagnostics
8399 .into_iter()
8400 .find(|diag| is_array_list_unification_error(&diag.message))
8401 .expect("expected array/list mismatch diagnostic");
8402 let arr_range = Range {
8403 start: Position {
8404 line: 3,
8405 character: 18,
8406 },
8407 end: Position {
8408 line: 3,
8409 character: 21,
8410 },
8411 };
8412 let actions =
8413 code_actions_for_source(&uri, text, arr_range, std::slice::from_ref(&mismatch));
8414 let code_actions: Vec<CodeAction> = actions
8415 .into_iter()
8416 .filter_map(|action| match action {
8417 CodeActionOrCommand::CodeAction(action) => Some(action),
8418 CodeActionOrCommand::Command(_) => None,
8419 })
8420 .collect();
8421 let fix = code_actions
8422 .iter()
8423 .find(|action| action.title == "Convert expression to list with `to_list`")
8424 .expect("expected to_list quick fix");
8425
8426 let edit = fix
8427 .edit
8428 .as_ref()
8429 .expect("to_list quick fix must include edit");
8430 let changes = edit
8431 .changes
8432 .as_ref()
8433 .expect("to_list quick fix must include changes");
8434 let edits = changes
8435 .get(&uri)
8436 .expect("to_list quick fix must target current document");
8437 assert!(
8438 edits.iter().any(|e| e.new_text.contains("to_list (arr)")),
8439 "edits: {edits:#?}"
8440 );
8441 }
8442
8443 #[test]
8444 fn code_actions_offer_default_disambiguation_with_is_for_record_update() {
8445 let text = r#"
8446type A = A { x: i32, y: i32 }
8447type B = B { x: i32, y: i32 }
8448
8449instance Default A
8450 default = A { x = 1, y = 2 }
8451
8452instance Default B
8453 default = B { x = 10, y = 20 }
8454
8455let
8456 a = { default with { x = 9 } },
8457 b = { default with { y = 8 } }
8458in
8459 (a, b)
8460"#;
8461 let uri = in_memory_doc_uri();
8462 clear_parse_cache(&uri);
8463 let diagnostics = diagnostics_from_text(&uri, text);
8464 let diag = diagnostics
8465 .into_iter()
8466 .find(|d| d.message.contains("field `x` is not definitely available"))
8467 .expect("expected field availability diagnostic");
8468 let actions = code_actions_for_source(&uri, text, diag.range, std::slice::from_ref(&diag));
8469 let code_actions: Vec<CodeAction> = actions
8470 .into_iter()
8471 .filter_map(|action| match action {
8472 CodeActionOrCommand::CodeAction(action) => Some(action),
8473 CodeActionOrCommand::Command(_) => None,
8474 })
8475 .collect();
8476
8477 let titles: Vec<String> = code_actions.iter().map(|a| a.title.clone()).collect();
8478 assert!(
8479 titles
8480 .iter()
8481 .any(|title| title == "Disambiguate `default` as `A`"),
8482 "titles: {titles:#?}"
8483 );
8484 assert!(
8485 titles
8486 .iter()
8487 .any(|title| title == "Disambiguate `default` as `B`"),
8488 "titles: {titles:#?}"
8489 );
8490 assert!(
8491 titles.iter().any(|title| title == "Annotate `a` as `A`"),
8492 "titles: {titles:#?}"
8493 );
8494 assert!(
8495 titles.iter().any(|title| title == "Annotate `a` as `B`"),
8496 "titles: {titles:#?}"
8497 );
8498
8499 let contains_fix_for = |needle: &str| {
8500 code_actions.iter().any(|action| {
8501 action
8502 .edit
8503 .as_ref()
8504 .and_then(|edit| edit.changes.as_ref())
8505 .and_then(|changes| changes.get(&uri))
8506 .is_some_and(|edits| edits.iter().any(|e| e.new_text.contains(needle)))
8507 })
8508 };
8509 assert!(
8510 contains_fix_for("(default is A)"),
8511 "expected `(default is A)` edit"
8512 );
8513 assert!(
8514 contains_fix_for("(default is B)"),
8515 "expected `(default is B)` edit"
8516 );
8517 assert!(contains_fix_for(": A"), "expected `: A` annotation edit");
8518 assert!(contains_fix_for(": B"), "expected `: B` annotation edit");
8519 }
8520
8521 #[test]
8522 fn code_actions_offer_hole_fill_candidates() {
8523 let text = r#"
8524fn aa_mk : i32 -> i32 = \x -> x
8525let y : i32 = ? in y
8526"#;
8527 let actions = code_actions_for_source_public(text, 2, 14);
8528 let titles: Vec<String> = actions
8529 .into_iter()
8530 .filter_map(|action| match action {
8531 CodeActionOrCommand::CodeAction(action) => Some(action.title),
8532 CodeActionOrCommand::Command(_) => None,
8533 })
8534 .collect();
8535 assert!(
8536 titles
8537 .iter()
8538 .any(|title| title.contains("Fill hole with `aa_mk`")),
8539 "titles: {titles:#?}"
8540 );
8541 }
8542
8543 #[test]
8544 fn code_actions_offer_hole_fill_even_with_diagnostics_present() {
8545 let text = r#"
8546fn aa_mk : i32 -> i32 = \x -> x
8547let y : i32 = ? in y
8548"#;
8549 let uri = in_memory_doc_uri();
8550 clear_parse_cache(&uri);
8551 let request_range = Range {
8552 start: Position {
8553 line: 2,
8554 character: 14,
8555 },
8556 end: Position {
8557 line: 2,
8558 character: 14,
8559 },
8560 };
8561 let diagnostics = vec![Diagnostic {
8562 range: request_range,
8563 severity: Some(DiagnosticSeverity::ERROR),
8564 message: "synthetic diagnostic".to_string(),
8565 source: Some("test".to_string()),
8566 ..Diagnostic::default()
8567 }];
8568 let actions = code_actions_for_source(&uri, text, request_range, &diagnostics);
8569 let titles: Vec<String> = actions
8570 .into_iter()
8571 .filter_map(|action| match action {
8572 CodeActionOrCommand::CodeAction(action) => Some(action.title),
8573 CodeActionOrCommand::Command(_) => None,
8574 })
8575 .collect();
8576 assert!(
8577 titles
8578 .iter()
8579 .any(|title| title.contains("Fill hole with `aa_mk`")),
8580 "titles: {titles:#?}"
8581 );
8582 }
8583
8584 #[test]
8585 fn code_actions_offer_hole_fill_for_real_typed_hole_diagnostic() {
8586 let text = r#"
8587fn aa_mk : i32 -> i32 = \x -> x
8588let y : i32 = ? in y
8589"#;
8590 let uri = in_memory_doc_uri();
8591 clear_parse_cache(&uri);
8592 let diagnostics = diagnostics_from_text(&uri, text);
8593 let hole_diag = diagnostics
8594 .iter()
8595 .find(|d| {
8596 d.message
8597 .contains("typed hole `?` must be filled before evaluation")
8598 })
8599 .expect("typed hole diagnostic")
8600 .clone();
8601 let actions = code_actions_for_source(&uri, text, hole_diag.range, &[hole_diag]);
8602 let titles: Vec<String> = actions
8603 .into_iter()
8604 .filter_map(|action| match action {
8605 CodeActionOrCommand::CodeAction(action) => Some(action.title),
8606 CodeActionOrCommand::Command(_) => None,
8607 })
8608 .collect();
8609 assert!(
8610 titles
8611 .iter()
8612 .any(|title| title.contains("Fill hole with `aa_mk`")),
8613 "titles: {titles:#?}"
8614 );
8615 }
8616
8617 #[test]
8618 fn expected_type_reports_if_condition_bool() {
8619 let text = "if true then 1 else 2";
8620 let ty = expected_type_for_source_public(text, 0, 3).expect("expected type at condition");
8621 assert_eq!(ty, "bool");
8622 }
8623
8624 #[test]
8625 fn expected_type_reports_if_branch_type() {
8626 let text = "let x : i32 = if true then 1 else 2 in x";
8627 let ty = expected_type_for_source_public(text, 0, 27).expect("expected type at branch");
8628 assert_eq!(ty, "i32");
8629 }
8630
8631 #[test]
8632 fn expected_type_reports_function_argument_type() {
8633 let text = "let f = \\x -> x + 1 in f 2";
8634 let ty = expected_type_for_source_public(text, 0, 26).expect("expected type at argument");
8635 assert_eq!(ty, "'r");
8636 }
8637
8638 #[test]
8639 fn functions_producing_expected_type_include_user_fn() {
8640 let text = r#"
8641fn mk : i32 -> i32 = \x -> x
8642if true then 0 else 1
8643"#;
8644 let items = functions_producing_expected_type_for_source_public(text, 2, 13);
8645 assert!(
8646 items.iter().any(|item| item.starts_with("mk : ")),
8647 "items: {items:#?}"
8648 );
8649 }
8650
8651 #[test]
8652 fn command_parses_uri_position_tuple_args() {
8653 let args = vec![
8654 Value::String("inmemory:///docs.rex".to_string()),
8655 Value::from(2u64),
8656 Value::from(3u64),
8657 ];
8658 let (uri, pos) = command_uri_and_position(&args).expect("parsed command args");
8659 assert_eq!(uri.as_str(), "inmemory:///docs.rex");
8660 assert_eq!(pos.line, 2);
8661 assert_eq!(pos.character, 3);
8662 }
8663
8664 #[test]
8665 fn command_parses_uri_position_and_id_tuple_args() {
8666 let args = vec![
8667 Value::String("inmemory:///docs.rex".to_string()),
8668 Value::from(2u64),
8669 Value::from(3u64),
8670 Value::String("qf-abc".to_string()),
8671 ];
8672 let (uri, pos, id) = command_uri_position_and_id(&args).expect("parsed command args");
8673 assert_eq!(uri.as_str(), "inmemory:///docs.rex");
8674 assert_eq!(pos.line, 2);
8675 assert_eq!(pos.character, 3);
8676 assert_eq!(id, "qf-abc");
8677 }
8678
8679 #[test]
8680 fn command_parses_uri_position_max_steps_strategy_and_dry_run_tuple_args() {
8681 let args = vec![
8682 Value::String("inmemory:///docs.rex".to_string()),
8683 Value::from(2u64),
8684 Value::from(3u64),
8685 Value::from(5u64),
8686 Value::String("aggressive".to_string()),
8687 Value::Bool(true),
8688 ];
8689 let (uri, pos, max_steps, strategy, dry_run) =
8690 command_uri_position_max_steps_strategy_and_dry_run(&args)
8691 .expect("parsed command args");
8692 assert_eq!(uri.as_str(), "inmemory:///docs.rex");
8693 assert_eq!(pos.line, 2);
8694 assert_eq!(pos.character, 3);
8695 assert_eq!(max_steps, 5);
8696 assert_eq!(strategy, BulkQuickFixStrategy::Aggressive);
8697 assert!(dry_run);
8698 }
8699
8700 #[test]
8701 fn command_parses_uri_position_max_steps_strategy_and_dry_run_object_args() {
8702 let args = vec![json!({
8703 "uri": "inmemory:///docs.rex",
8704 "line": 4,
8705 "character": 9,
8706 "maxSteps": 99,
8707 "strategy": "conservative",
8708 "dryRun": false
8709 })];
8710 let (uri, pos, max_steps, strategy, dry_run) =
8711 command_uri_position_max_steps_strategy_and_dry_run(&args)
8712 .expect("parsed command args");
8713 assert_eq!(uri.as_str(), "inmemory:///docs.rex");
8714 assert_eq!(pos.line, 4);
8715 assert_eq!(pos.character, 9);
8716 assert_eq!(max_steps, 20, "maxSteps should clamp to upper bound");
8717 assert_eq!(strategy, BulkQuickFixStrategy::Conservative);
8718 assert!(!dry_run);
8719 }
8720
8721 #[test]
8722 fn execute_expected_type_command_returns_object() {
8723 let uri = in_memory_doc_uri();
8724 let text = "if true then 1 else 2";
8725 let out = execute_query_command_for_document(
8726 CMD_EXPECTED_TYPE_AT,
8727 &uri,
8728 text,
8729 Position {
8730 line: 0,
8731 character: 3,
8732 },
8733 )
8734 .expect("command output");
8735 let expected = out
8736 .as_object()
8737 .and_then(|o| o.get("expectedType"))
8738 .and_then(Value::as_str)
8739 .expect("expectedType");
8740 assert_eq!(expected, "bool");
8741 }
8742
8743 #[test]
8744 fn execute_functions_command_returns_items() {
8745 let uri = in_memory_doc_uri();
8746 let text = r#"
8747fn mk : i32 -> i32 = \x -> x
8748if true then 0 else 1
8749"#;
8750 let out = execute_query_command_for_document(
8751 CMD_FUNCTIONS_PRODUCING_EXPECTED_TYPE_AT,
8752 &uri,
8753 text,
8754 Position {
8755 line: 2,
8756 character: 13,
8757 },
8758 )
8759 .expect("command output");
8760 let items = out
8761 .as_object()
8762 .and_then(|o| o.get("items"))
8763 .and_then(Value::as_array)
8764 .expect("items array");
8765 assert!(
8766 items
8767 .iter()
8768 .filter_map(Value::as_str)
8769 .any(|item| item.starts_with("mk : ")),
8770 "items: {items:#?}"
8771 );
8772 }
8773
8774 #[test]
8775 fn semantic_functions_command_caps_items_count() {
8776 let uri = in_memory_doc_uri();
8777 let mut lines = Vec::new();
8778 for i in 0..200usize {
8779 lines.push(format!("fn mk_{i} : i32 -> i32 = \\x -> x"));
8780 }
8781 lines.push("let y : i32 = ? in y".to_string());
8782 let line = (lines.len() - 1) as u32;
8783 let text = lines.join("\n");
8784 let out = execute_query_command_for_document(
8785 CMD_FUNCTIONS_PRODUCING_EXPECTED_TYPE_AT,
8786 &uri,
8787 &text,
8788 Position {
8789 line,
8790 character: 14,
8791 },
8792 )
8793 .expect("command output");
8794 let items = out
8795 .as_object()
8796 .and_then(|o| o.get("items"))
8797 .and_then(Value::as_array)
8798 .expect("items array");
8799 assert!(
8800 items.len() <= MAX_SEMANTIC_CANDIDATES,
8801 "items_len={}; max={MAX_SEMANTIC_CANDIDATES}",
8802 items.len()
8803 );
8804 }
8805
8806 #[test]
8807 fn execute_functions_accepting_inferred_type_command_returns_items() {
8808 let uri = in_memory_doc_uri();
8809 let text = r#"
8810fn use_bool : bool -> i32 = \x -> 0
8811let x = true in x
8812"#;
8813 let out = execute_query_command_for_document(
8814 CMD_FUNCTIONS_ACCEPTING_INFERRED_TYPE_AT,
8815 &uri,
8816 text,
8817 Position {
8818 line: 2,
8819 character: 8,
8820 },
8821 )
8822 .expect("command output");
8823 let obj = out.as_object().expect("object");
8824 let inferred = obj
8825 .get("inferredType")
8826 .and_then(Value::as_str)
8827 .expect("inferredType");
8828 assert_eq!(inferred, "bool");
8829 let items = obj
8830 .get("items")
8831 .and_then(Value::as_array)
8832 .expect("items array");
8833 assert!(
8834 items
8835 .iter()
8836 .filter_map(Value::as_str)
8837 .any(|item| item.starts_with("use_bool : ")),
8838 "items: {items:#?}"
8839 );
8840 }
8841
8842 #[test]
8843 fn execute_adapters_from_inferred_to_expected_command_returns_items() {
8844 let uri = in_memory_doc_uri();
8845 let text = r#"
8846fn id_i32 : i32 -> i32 = \x -> x
8847let x = 1 in let y : i32 = x in y
8848"#;
8849 let out = execute_query_command_for_document(
8850 CMD_ADAPTERS_FROM_INFERRED_TO_EXPECTED_AT,
8851 &uri,
8852 text,
8853 Position {
8854 line: 2,
8855 character: 30,
8856 },
8857 )
8858 .expect("command output");
8859 let obj = out.as_object().expect("object");
8860 let inferred = obj
8861 .get("inferredType")
8862 .and_then(Value::as_str)
8863 .expect("inferredType");
8864 assert_eq!(inferred, "i32");
8865 let expected = obj
8866 .get("expectedType")
8867 .and_then(Value::as_str)
8868 .expect("expectedType");
8869 assert_eq!(expected, "i32");
8870 let items = obj
8871 .get("items")
8872 .and_then(Value::as_array)
8873 .expect("items array");
8874 assert!(
8875 items
8876 .iter()
8877 .filter_map(Value::as_str)
8878 .any(|item| item.starts_with("id_i32 : ")),
8879 "items: {items:#?}"
8880 );
8881 }
8882
8883 #[test]
8884 fn semantic_holes_command_caps_hole_count() {
8885 let uri = in_memory_doc_uri();
8886 let mut text = String::from("let ys : List i32 = [");
8887 for i in 0..160usize {
8888 if i > 0 {
8889 text.push_str(", ");
8890 }
8891 text.push('?');
8892 }
8893 text.push_str("] in ys");
8894 let out = execute_query_command_for_document_without_position(
8895 CMD_HOLES_EXPECTED_TYPES,
8896 &uri,
8897 &text,
8898 )
8899 .expect("command output");
8900 let holes = out
8901 .as_object()
8902 .and_then(|o| o.get("holes"))
8903 .and_then(Value::as_array)
8904 .expect("holes array");
8905 assert!(
8906 holes.len() <= MAX_SEMANTIC_HOLES,
8907 "holes_len={}; max={MAX_SEMANTIC_HOLES}",
8908 holes.len()
8909 );
8910 }
8911
8912 #[test]
8913 fn execute_functions_compatible_with_in_scope_values_command_returns_items() {
8914 let uri = in_memory_doc_uri();
8915 let text = r#"
8916fn to_string_i32 : i32 -> string = \x -> "ok"
8917let x = 1 in let y : string = ? in y
8918"#;
8919 let out = execute_query_command_for_document(
8920 CMD_FUNCTIONS_COMPATIBLE_WITH_IN_SCOPE_VALUES_AT,
8921 &uri,
8922 text,
8923 Position {
8924 line: 2,
8925 character: 30,
8926 },
8927 )
8928 .expect("command output");
8929 let items = out
8930 .as_object()
8931 .and_then(|o| o.get("items"))
8932 .and_then(Value::as_array)
8933 .expect("items array");
8934 assert!(
8935 items
8936 .iter()
8937 .filter_map(Value::as_str)
8938 .any(|item| item.contains("to_string_i32") && item.contains("to_string_i32 x")),
8939 "items: {items:#?}"
8940 );
8941 }
8942
8943 #[test]
8944 fn semantic_loop_step_caps_in_scope_values() {
8945 let uri = in_memory_doc_uri();
8946 let mut lines = Vec::new();
8947 for i in 0..160usize {
8948 lines.push(format!("fn x{i} : i32 -> i32 = \\v -> v"));
8949 }
8950 lines.push("let y : i32 = ? in y".to_string());
8951 let line = (lines.len() - 1) as u32;
8952 let text = lines.join("\n");
8953 let out = execute_semantic_loop_step(
8954 &uri,
8955 &text,
8956 Position {
8957 line,
8958 character: 14,
8959 },
8960 )
8961 .expect("step output");
8962 let in_scope_values = out
8963 .as_object()
8964 .and_then(|o| o.get("inScopeValues"))
8965 .and_then(Value::as_array)
8966 .expect("inScopeValues");
8967 assert!(
8968 in_scope_values.len() <= MAX_SEMANTIC_IN_SCOPE_VALUES,
8969 "in_scope_values_len={}; max={MAX_SEMANTIC_IN_SCOPE_VALUES}",
8970 in_scope_values.len()
8971 );
8972 }
8973
8974 #[test]
8975 fn hole_expected_types_detects_placeholder_vars() {
8976 let uri = in_memory_doc_uri();
8977 let text = "let y : i32 = _ in y";
8978 let holes = hole_expected_types_for_document(&uri, text);
8979 assert!(!holes.is_empty(), "holes: {holes:#?}");
8980 let expected = holes
8981 .iter()
8982 .find_map(|hole| hole.get("expectedType").and_then(Value::as_str));
8983 assert_eq!(expected, Some("i32"));
8984 }
8985
8986 #[test]
8987 fn hole_expected_types_detects_question_holes() {
8988 let uri = in_memory_doc_uri();
8989 let text = "let y : i32 = ? in y";
8990 let holes = hole_expected_types_for_document(&uri, text);
8991 assert!(!holes.is_empty(), "holes: {holes:#?}");
8992 let hole_name = holes
8993 .iter()
8994 .find_map(|hole| hole.get("name").and_then(Value::as_str));
8995 assert_eq!(hole_name, Some("?"));
8996 let expected = holes
8997 .iter()
8998 .find_map(|hole| hole.get("expectedType").and_then(Value::as_str));
8999 assert_eq!(expected, Some("i32"));
9000 }
9001
9002 #[test]
9003 fn execute_holes_command_returns_holes_array() {
9004 let uri = in_memory_doc_uri();
9005 let text = "let y : i32 = _ in y";
9006 let out = execute_query_command_for_document_without_position(
9007 CMD_HOLES_EXPECTED_TYPES,
9008 &uri,
9009 text,
9010 )
9011 .expect("command output");
9012 let holes = out
9013 .as_object()
9014 .and_then(|o| o.get("holes"))
9015 .and_then(Value::as_array)
9016 .expect("holes array");
9017 assert!(!holes.is_empty(), "holes: {holes:#?}");
9018 }
9019
9020 #[test]
9021 fn semantic_loop_step_reports_expected_type_and_candidates() {
9022 let uri = in_memory_doc_uri();
9023 let text = r#"
9024fn mk : i32 -> i32 = \x -> x
9025let y : i32 = ? in y
9026"#;
9027 let out = execute_semantic_loop_step(
9028 &uri,
9029 text,
9030 Position {
9031 line: 2,
9032 character: 14,
9033 },
9034 )
9035 .expect("command output");
9036 let obj = out.as_object().expect("object");
9037
9038 let expected = obj
9039 .get("expectedType")
9040 .and_then(Value::as_str)
9041 .expect("expectedType");
9042 assert_eq!(expected, "i32");
9043
9044 let function_candidates = obj
9045 .get("functionCandidates")
9046 .and_then(Value::as_array)
9047 .expect("functionCandidates");
9048 assert!(
9049 function_candidates
9050 .iter()
9051 .filter_map(Value::as_str)
9052 .any(|item| item.starts_with("mk : ")),
9053 "function candidates: {function_candidates:#?}"
9054 );
9055
9056 let quick_fix_titles = obj
9057 .get("quickFixTitles")
9058 .and_then(Value::as_array)
9059 .expect("quickFixTitles");
9060 assert!(
9061 quick_fix_titles
9062 .iter()
9063 .filter_map(Value::as_str)
9064 .any(|title| title == "Fill hole with `mk`"),
9065 "quick fixes: {quick_fix_titles:#?}"
9066 );
9067 let quick_fixes = obj
9068 .get("quickFixes")
9069 .and_then(Value::as_array)
9070 .expect("quickFixes");
9071 assert!(
9072 quick_fixes.iter().any(|qf| {
9073 qf.get("id").and_then(Value::as_str).is_some()
9074 && qf.get("title").and_then(Value::as_str) == Some("Fill hole with `mk`")
9075 && qf.get("kind").and_then(Value::as_str) == Some("quickfix")
9076 && qf.get("edit").is_some()
9077 }),
9078 "quickFixes: {quick_fixes:#?}"
9079 );
9080
9081 let accepting = obj
9082 .get("functionsAcceptingInferredType")
9083 .and_then(Value::as_array)
9084 .expect("functionsAcceptingInferredType");
9085 assert!(
9086 accepting
9087 .iter()
9088 .filter_map(Value::as_str)
9089 .any(|item| item.starts_with("mk : ")),
9090 "accepting: {accepting:#?}"
9091 );
9092 }
9093
9094 #[test]
9095 fn semantic_loop_step_reports_local_diagnostics_and_fixes() {
9096 let uri = in_memory_doc_uri();
9097 let text = "let y = z in y";
9098 let out = execute_semantic_loop_step(
9099 &uri,
9100 text,
9101 Position {
9102 line: 0,
9103 character: 8,
9104 },
9105 )
9106 .expect("command output");
9107 let obj = out.as_object().expect("object");
9108
9109 let local_diagnostics = obj
9110 .get("localDiagnostics")
9111 .and_then(Value::as_array)
9112 .expect("localDiagnostics");
9113 assert!(
9114 local_diagnostics.iter().any(|diag| {
9115 diag.get("message")
9116 .and_then(Value::as_str)
9117 .is_some_and(|message| message.contains("unbound variable z"))
9118 }),
9119 "diagnostics: {local_diagnostics:#?}"
9120 );
9121
9122 let quick_fix_titles = obj
9123 .get("quickFixTitles")
9124 .and_then(Value::as_array)
9125 .expect("quickFixTitles");
9126 assert!(
9127 quick_fix_titles
9128 .iter()
9129 .filter_map(Value::as_str)
9130 .any(|title| title.contains("Introduce `let z = null`")),
9131 "quick fixes: {quick_fix_titles:#?}"
9132 );
9133 let quick_fixes = obj
9134 .get("quickFixes")
9135 .and_then(Value::as_array)
9136 .expect("quickFixes");
9137 assert!(
9138 quick_fixes.iter().any(|qf| {
9139 qf.get("id").and_then(Value::as_str).is_some()
9140 && qf
9141 .get("title")
9142 .and_then(Value::as_str)
9143 .is_some_and(|title| title.contains("Introduce `let z = null`"))
9144 && qf.get("kind").and_then(Value::as_str) == Some("quickfix")
9145 }),
9146 "quickFixes: {quick_fixes:#?}"
9147 );
9148 }
9149
9150 #[test]
9151 fn semantic_loop_step_json_contract_is_stable() {
9152 let uri = in_memory_doc_uri();
9153 let text = r#"
9154fn mk : i32 -> i32 = \x -> x
9155let y : i32 = ? in y
9156"#;
9157 let out = execute_semantic_loop_step(
9158 &uri,
9159 text,
9160 Position {
9161 line: 2,
9162 character: 14,
9163 },
9164 )
9165 .expect("step output");
9166 let obj = expect_object(&out);
9167
9168 assert!(obj.contains_key("expectedType"));
9170 assert!(obj.contains_key("inferredType"));
9171 expect_array_field(obj, "inScopeValues");
9172 expect_array_field(obj, "functionCandidates");
9173 expect_array_field(obj, "holeFillCandidates");
9174 expect_array_field(obj, "functionsAcceptingInferredType");
9175 expect_array_field(obj, "adaptersFromInferredToExpectedType");
9176 expect_array_field(obj, "functionsCompatibleWithInScopeValues");
9177 expect_array_field(obj, "localDiagnostics");
9178 expect_array_field(obj, "quickFixes");
9179 expect_array_field(obj, "quickFixTitles");
9180 expect_array_field(obj, "holes");
9181
9182 let quick_fixes = expect_array_field(obj, "quickFixes");
9183 assert!(
9184 !quick_fixes.is_empty(),
9185 "quickFixes should not be empty: {obj:#?}"
9186 );
9187 let first_quick_fix = expect_object(quick_fixes.first().expect("first quick fix"));
9188 expect_string_field(first_quick_fix, "id");
9189 expect_string_field(first_quick_fix, "title");
9190 assert!(
9192 first_quick_fix.contains_key("kind"),
9193 "quickFix should include `kind`: {first_quick_fix:#?}"
9194 );
9195 assert!(
9196 first_quick_fix.contains_key("edit"),
9197 "quickFix should include `edit`: {first_quick_fix:#?}"
9198 );
9199 }
9200
9201 #[test]
9202 fn semantic_loop_apply_quick_fix_resolves_by_id() {
9203 let uri = in_memory_doc_uri();
9204 let text = "let y = z in y";
9205 let step = execute_semantic_loop_step(
9206 &uri,
9207 text,
9208 Position {
9209 line: 0,
9210 character: 8,
9211 },
9212 )
9213 .expect("step output");
9214 let quick_fix_id = step
9215 .get("quickFixes")
9216 .and_then(Value::as_array)
9217 .and_then(|arr| arr.first())
9218 .and_then(|item| item.get("id"))
9219 .and_then(Value::as_str)
9220 .expect("quick fix id")
9221 .to_string();
9222
9223 let out = execute_semantic_loop_apply_quick_fix(
9224 &uri,
9225 text,
9226 Position {
9227 line: 0,
9228 character: 8,
9229 },
9230 &quick_fix_id,
9231 )
9232 .expect("apply output");
9233 let quick_fix = out
9234 .get("quickFix")
9235 .and_then(Value::as_object)
9236 .expect("quickFix object");
9237 assert_eq!(
9238 quick_fix
9239 .get("id")
9240 .and_then(Value::as_str)
9241 .expect("quickFix.id"),
9242 quick_fix_id
9243 );
9244 assert!(quick_fix.get("edit").is_some(), "quickFix: {quick_fix:#?}");
9245 }
9246
9247 #[test]
9248 fn semantic_loop_apply_quick_fix_json_contract_is_stable() {
9249 let uri = in_memory_doc_uri();
9250 let text = "let y = z in y";
9251 let step = execute_semantic_loop_step(
9252 &uri,
9253 text,
9254 Position {
9255 line: 0,
9256 character: 8,
9257 },
9258 )
9259 .expect("step output");
9260 let quick_fix_id = step
9261 .get("quickFixes")
9262 .and_then(Value::as_array)
9263 .and_then(|arr| arr.first())
9264 .and_then(|item| item.get("id"))
9265 .and_then(Value::as_str)
9266 .expect("quick fix id")
9267 .to_string();
9268
9269 let out = execute_semantic_loop_apply_quick_fix(
9270 &uri,
9271 text,
9272 Position {
9273 line: 0,
9274 character: 8,
9275 },
9276 &quick_fix_id,
9277 )
9278 .expect("apply output");
9279 let obj = expect_object(&out);
9280 let quick_fix = expect_object(obj.get("quickFix").expect("quickFix"));
9281 expect_string_field(quick_fix, "id");
9282 expect_string_field(quick_fix, "title");
9283 assert!(quick_fix.contains_key("kind"));
9284 assert!(quick_fix.contains_key("edit"));
9285 }
9286
9287 #[test]
9288 fn semantic_loop_apply_quick_fix_unknown_id_returns_null() {
9289 let uri = in_memory_doc_uri();
9290 let text = "let y = z in y";
9291 let out = execute_semantic_loop_apply_quick_fix(
9292 &uri,
9293 text,
9294 Position {
9295 line: 0,
9296 character: 8,
9297 },
9298 "qf-does-not-exist",
9299 )
9300 .expect("apply output");
9301 assert_eq!(out, Value::Null);
9302 }
9303
9304 #[test]
9305 fn semantic_loop_apply_best_quick_fixes_updates_text_and_reduces_errors() {
9306 let uri = in_memory_doc_uri();
9307 let text = "let y = z in y";
9308 let out = execute_semantic_loop_apply_best_quick_fixes(
9309 &uri,
9310 text,
9311 Position {
9312 line: 0,
9313 character: 8,
9314 },
9315 3,
9316 BulkQuickFixStrategy::Conservative,
9317 false,
9318 )
9319 .expect("bulk output");
9320
9321 let applied_count = out
9322 .get("appliedCount")
9323 .and_then(Value::as_u64)
9324 .expect("appliedCount");
9325 assert!(applied_count >= 1, "out: {out:#?}");
9326
9327 let updated_text = out
9328 .get("updatedText")
9329 .and_then(Value::as_str)
9330 .expect("updatedText");
9331 assert_ne!(updated_text, text, "out: {out:#?}");
9332
9333 let diagnostics_after = out
9334 .get("localDiagnosticsAfter")
9335 .and_then(Value::as_array)
9336 .expect("localDiagnosticsAfter");
9337 assert!(
9338 diagnostics_after.iter().all(|diag| {
9339 !diag
9340 .get("message")
9341 .and_then(Value::as_str)
9342 .is_some_and(|m| m.contains("unbound variable z"))
9343 }),
9344 "diagnostics_after: {diagnostics_after:#?}"
9345 );
9346 let steps = out.get("steps").and_then(Value::as_array).expect("steps");
9347 assert!(!steps.is_empty(), "out: {out:#?}");
9348 let strategy = out
9349 .get("strategy")
9350 .and_then(Value::as_str)
9351 .expect("strategy");
9352 assert_eq!(strategy, "conservative");
9353 let first_step = steps
9354 .first()
9355 .and_then(Value::as_object)
9356 .expect("first step");
9357 assert!(
9358 first_step.get("quickFix").is_some(),
9359 "step: {first_step:#?}"
9360 );
9361 let before_count = first_step
9362 .get("diagnosticsBeforeCount")
9363 .and_then(Value::as_u64)
9364 .expect("diagnosticsBeforeCount");
9365 let after_count = first_step
9366 .get("diagnosticsAfterCount")
9367 .and_then(Value::as_u64)
9368 .expect("diagnosticsAfterCount");
9369 assert!(after_count <= before_count, "step: {first_step:#?}");
9370 }
9371
9372 #[test]
9373 fn semantic_loop_apply_best_quick_fixes_json_contract_is_stable() {
9374 let uri = in_memory_doc_uri();
9375 let text = "let y = z in y";
9376 let out = execute_semantic_loop_apply_best_quick_fixes(
9377 &uri,
9378 text,
9379 Position {
9380 line: 0,
9381 character: 8,
9382 },
9383 2,
9384 BulkQuickFixStrategy::Conservative,
9385 true,
9386 )
9387 .expect("bulk output");
9388 let obj = expect_object(&out);
9389
9390 assert_eq!(expect_string_field(obj, "strategy"), "conservative");
9391 obj.get("dryRun")
9392 .and_then(Value::as_bool)
9393 .expect("dryRun bool");
9394 obj.get("appliedCount")
9395 .and_then(Value::as_u64)
9396 .expect("appliedCount u64");
9397 obj.get("updatedText")
9398 .and_then(Value::as_str)
9399 .expect("updatedText string");
9400 expect_array_field(obj, "appliedQuickFixes");
9401 expect_array_field(obj, "steps");
9402 expect_array_field(obj, "localDiagnosticsAfter");
9403 expect_string_field(obj, "stoppedReason");
9404 expect_string_field(obj, "stoppedReasonDetail");
9405 obj.get("lastDiagnosticsDelta")
9406 .and_then(Value::as_i64)
9407 .expect("lastDiagnosticsDelta i64");
9408 obj.get("noImprovementStreak")
9409 .and_then(Value::as_u64)
9410 .expect("noImprovementStreak u64");
9411 obj.get("seenStatesCount")
9412 .and_then(Value::as_u64)
9413 .expect("seenStatesCount u64");
9414
9415 if let Some(first_step) = expect_array_field(obj, "steps").first() {
9416 let step_obj = expect_object(first_step);
9417 step_obj
9418 .get("index")
9419 .and_then(Value::as_u64)
9420 .expect("step.index");
9421 assert!(step_obj.contains_key("quickFix"));
9422 expect_array_field(step_obj, "diagnosticsBefore");
9423 expect_array_field(step_obj, "diagnosticsAfter");
9424 step_obj
9425 .get("diagnosticsBeforeCount")
9426 .and_then(Value::as_u64)
9427 .expect("step diagnosticsBeforeCount");
9428 step_obj
9429 .get("diagnosticsAfterCount")
9430 .and_then(Value::as_u64)
9431 .expect("step diagnosticsAfterCount");
9432 step_obj
9433 .get("diagnosticsDelta")
9434 .and_then(Value::as_i64)
9435 .expect("step diagnosticsDelta");
9436 step_obj
9437 .get("noImprovementStreak")
9438 .and_then(Value::as_u64)
9439 .expect("step noImprovementStreak");
9440 }
9441 }
9442
9443 #[test]
9444 fn semantic_loop_apply_best_quick_fixes_no_context_returns_no_quickfix() {
9445 let uri = in_memory_doc_uri();
9446 let text = "let x = 1 in x";
9447 let out = execute_semantic_loop_apply_best_quick_fixes(
9448 &uri,
9449 text,
9450 Position {
9451 line: 0,
9452 character: 4,
9453 },
9454 3,
9455 BulkQuickFixStrategy::Conservative,
9456 false,
9457 )
9458 .expect("bulk output");
9459 let obj = expect_object(&out);
9460 assert_eq!(expect_string_field(obj, "stoppedReason"), "noQuickFix");
9461 assert_eq!(
9462 obj.get("appliedCount")
9463 .and_then(Value::as_u64)
9464 .expect("appliedCount"),
9465 0
9466 );
9467 assert!(expect_array_field(obj, "steps").is_empty(), "out={out:#?}");
9468 }
9469
9470 #[test]
9471 fn semantic_loop_step_parse_error_still_returns_contract_shape() {
9472 let uri = in_memory_doc_uri();
9473 let text = "let x =";
9474 let out = execute_semantic_loop_step(
9475 &uri,
9476 text,
9477 Position {
9478 line: 0,
9479 character: 6,
9480 },
9481 )
9482 .expect("step output");
9483 let obj = expect_object(&out);
9484 expect_array_field(obj, "quickFixes");
9486 expect_array_field(obj, "quickFixTitles");
9487 expect_array_field(obj, "localDiagnostics");
9488 expect_array_field(obj, "holes");
9489 }
9490
9491 #[test]
9492 fn golden_flow_hole_to_apply_by_id_reduces_hole_count() {
9493 let uri = in_memory_doc_uri();
9494 let text = r#"
9495fn mk : i32 -> i32 = \x -> x
9496let y : i32 = ? in y
9497"#;
9498 let step = execute_semantic_loop_step(
9499 &uri,
9500 text,
9501 Position {
9502 line: 2,
9503 character: 14,
9504 },
9505 )
9506 .expect("step output");
9507 let quick_fix_id = step
9508 .get("quickFixes")
9509 .and_then(Value::as_array)
9510 .and_then(|arr| {
9511 arr.iter().find(|item| {
9512 item.get("title")
9513 .and_then(Value::as_str)
9514 .is_some_and(|title| title == "Fill hole with `mk`")
9515 })
9516 })
9517 .and_then(|item| item.get("id"))
9518 .and_then(Value::as_str)
9519 .expect("hole fill quick-fix id")
9520 .to_string();
9521 let apply = execute_semantic_loop_apply_quick_fix(
9522 &uri,
9523 text,
9524 Position {
9525 line: 2,
9526 character: 14,
9527 },
9528 &quick_fix_id,
9529 )
9530 .expect("apply output");
9531 let quick_fix = apply.get("quickFix").expect("quickFix returned").clone();
9532 let edit: WorkspaceEdit =
9533 serde_json::from_value(quick_fix.get("edit").cloned().expect("quickFix.edit"))
9534 .expect("workspace edit");
9535 let updated = apply_workspace_edit_to_text(&uri, text, &edit).expect("apply edit");
9536
9537 let holes_before = hole_expected_types_for_document(&uri, text);
9538 let holes_after = hole_expected_types_for_document(&uri, &updated);
9539 assert!(
9540 holes_after.len() < holes_before.len(),
9541 "before={holes_before:#?}; after={holes_after:#?}; updated=\n{updated}"
9542 );
9543 }
9544
9545 #[test]
9546 fn golden_flow_unknown_var_bulk_repairs_local_error() {
9547 let uri = in_memory_doc_uri();
9548 let text = "let y = z in y";
9549 let out = execute_semantic_loop_apply_best_quick_fixes(
9550 &uri,
9551 text,
9552 Position {
9553 line: 0,
9554 character: 8,
9555 },
9556 3,
9557 BulkQuickFixStrategy::Conservative,
9558 false,
9559 )
9560 .expect("bulk output");
9561 let diagnostics_after = out
9562 .get("localDiagnosticsAfter")
9563 .and_then(Value::as_array)
9564 .expect("localDiagnosticsAfter");
9565 assert!(
9566 diagnostics_after.iter().all(|diag| {
9567 !diag
9568 .get("message")
9569 .and_then(Value::as_str)
9570 .is_some_and(|msg| msg.contains("unbound variable z"))
9571 }),
9572 "out={out:#?}"
9573 );
9574 }
9575
9576 #[test]
9577 fn golden_flow_complex_bulk_preview_then_apply_same_projection() {
9578 let uri = in_memory_doc_uri();
9579 let text = r#"
9580let
9581 y = z
9582in
9583 match y when Some x -> x
9584"#;
9585 let preview = execute_semantic_loop_apply_best_quick_fixes(
9586 &uri,
9587 text,
9588 Position {
9589 line: 2,
9590 character: 6,
9591 },
9592 3,
9593 BulkQuickFixStrategy::Aggressive,
9594 true,
9595 )
9596 .expect("preview output");
9597 let apply = execute_semantic_loop_apply_best_quick_fixes(
9598 &uri,
9599 text,
9600 Position {
9601 line: 2,
9602 character: 6,
9603 },
9604 3,
9605 BulkQuickFixStrategy::Aggressive,
9606 false,
9607 )
9608 .expect("apply output");
9609
9610 let preview_text = preview
9611 .get("updatedText")
9612 .and_then(Value::as_str)
9613 .expect("preview updatedText");
9614 let apply_text = apply
9615 .get("updatedText")
9616 .and_then(Value::as_str)
9617 .expect("apply updatedText");
9618 assert_eq!(
9619 preview_text, apply_text,
9620 "preview={preview:#?}\napply={apply:#?}"
9621 );
9622
9623 let preview_steps = preview
9624 .get("steps")
9625 .and_then(Value::as_array)
9626 .expect("preview steps");
9627 let apply_steps = apply
9628 .get("steps")
9629 .and_then(Value::as_array)
9630 .expect("apply steps");
9631 assert_eq!(preview_steps.len(), apply_steps.len());
9632 }
9633
9634 #[test]
9635 fn bulk_strategy_simple_unknown_var_applies_introduce_fix() {
9636 let uri = in_memory_doc_uri();
9637 let text = "let y = z in y";
9638 let out = execute_semantic_loop_apply_best_quick_fixes(
9639 &uri,
9640 text,
9641 Position {
9642 line: 0,
9643 character: 8,
9644 },
9645 1,
9646 BulkQuickFixStrategy::Conservative,
9647 false,
9648 )
9649 .expect("bulk output");
9650 let first_title = out
9651 .get("steps")
9652 .and_then(Value::as_array)
9653 .and_then(|steps| steps.first())
9654 .and_then(|step| step.get("quickFix"))
9655 .and_then(|qf| qf.get("title"))
9656 .and_then(Value::as_str)
9657 .expect("first quick-fix title");
9658 assert!(
9659 first_title.contains("Introduce `let z = null`"),
9660 "first_title={first_title}; out={out:#?}"
9661 );
9662 }
9663
9664 #[test]
9665 fn bulk_strategy_medium_distinguishes_conservative_vs_aggressive_ranking() {
9666 let candidates = vec![
9667 json!({
9668 "id": "qf-add",
9669 "title": "Add wildcard arm to match",
9670 "kind": "quickfix",
9671 "edit": Value::Null,
9672 }),
9673 json!({
9674 "id": "qf-intro",
9675 "title": "Introduce `let z = null`",
9676 "kind": "quickfix",
9677 "edit": Value::Null,
9678 }),
9679 ];
9680 let conservative =
9681 best_quick_fix_from_candidates(&candidates, BulkQuickFixStrategy::Conservative)
9682 .expect("conservative choice");
9683 let aggressive =
9684 best_quick_fix_from_candidates(&candidates, BulkQuickFixStrategy::Aggressive)
9685 .expect("aggressive choice");
9686 assert_eq!(
9687 conservative
9688 .get("title")
9689 .and_then(Value::as_str)
9690 .expect("conservative title"),
9691 "Add wildcard arm to match"
9692 );
9693 assert_eq!(
9694 aggressive
9695 .get("title")
9696 .and_then(Value::as_str)
9697 .expect("aggressive title"),
9698 "Introduce `let z = null`"
9699 );
9700 }
9701
9702 #[test]
9703 fn bulk_strategy_complex_returns_step_telemetry_for_each_step() {
9704 let uri = in_memory_doc_uri();
9705 let text = r#"
9706let
9707 y = z
9708in
9709 match y when Some x -> x
9710"#;
9711 let out = execute_semantic_loop_apply_best_quick_fixes(
9712 &uri,
9713 text,
9714 Position {
9715 line: 2,
9716 character: 6,
9717 },
9718 3,
9719 BulkQuickFixStrategy::Aggressive,
9720 false,
9721 )
9722 .expect("bulk output");
9723
9724 let steps = out
9725 .get("steps")
9726 .and_then(Value::as_array)
9727 .expect("steps array");
9728 assert!(!steps.is_empty(), "out: {out:#?}");
9729 for (i, step) in steps.iter().enumerate() {
9730 let diagnostics_before = step
9731 .get("diagnosticsBefore")
9732 .and_then(Value::as_array)
9733 .expect("diagnosticsBefore");
9734 let diagnostics_after = step
9735 .get("diagnosticsAfter")
9736 .and_then(Value::as_array)
9737 .expect("diagnosticsAfter");
9738 let before_count = step
9739 .get("diagnosticsBeforeCount")
9740 .and_then(Value::as_u64)
9741 .expect("diagnosticsBeforeCount");
9742 let after_count = step
9743 .get("diagnosticsAfterCount")
9744 .and_then(Value::as_u64)
9745 .expect("diagnosticsAfterCount");
9746 let delta = step
9747 .get("diagnosticsDelta")
9748 .and_then(Value::as_i64)
9749 .expect("diagnosticsDelta");
9750 assert_eq!(
9751 diagnostics_before.len() as u64,
9752 before_count,
9753 "step[{i}]={step:#?}"
9754 );
9755 assert_eq!(
9756 diagnostics_after.len() as u64,
9757 after_count,
9758 "step[{i}]={step:#?}"
9759 );
9760 assert_eq!(
9761 before_count as i64 - after_count as i64,
9762 delta,
9763 "step[{i}]={step:#?}"
9764 );
9765 }
9766 }
9767
9768 #[test]
9769 fn bulk_strategy_stops_after_requested_step_limit() {
9770 let uri = in_memory_doc_uri();
9771 let text = r#"
9772let
9773 y = z
9774in
9775 match y when Some x -> x
9776"#;
9777 let out = execute_semantic_loop_apply_best_quick_fixes(
9778 &uri,
9779 text,
9780 Position {
9781 line: 2,
9782 character: 6,
9783 },
9784 1,
9785 BulkQuickFixStrategy::Aggressive,
9786 false,
9787 )
9788 .expect("bulk output");
9789 let applied_count = out
9790 .get("appliedCount")
9791 .and_then(Value::as_u64)
9792 .expect("appliedCount");
9793 let steps = out.get("steps").and_then(Value::as_array).expect("steps");
9794 let stopped_reason = out
9795 .get("stoppedReason")
9796 .and_then(Value::as_str)
9797 .expect("stoppedReason");
9798 let stopped_detail = out
9799 .get("stoppedReasonDetail")
9800 .and_then(Value::as_str)
9801 .expect("stoppedReasonDetail");
9802 let seen_states = out
9803 .get("seenStatesCount")
9804 .and_then(Value::as_u64)
9805 .expect("seenStatesCount");
9806 assert_eq!(applied_count, 1, "out: {out:#?}");
9807 assert_eq!(steps.len(), 1, "out: {out:#?}");
9808 assert_eq!(stopped_reason, "maxStepsReached", "out: {out:#?}");
9809 assert!(
9810 stopped_detail.contains("maxSteps"),
9811 "stopped_detail={stopped_detail}; out={out:#?}"
9812 );
9813 assert!(seen_states >= 2, "out: {out:#?}");
9814 }
9815
9816 #[test]
9817 fn bulk_mode_dry_run_reports_flag_and_predicted_text() {
9818 let uri = in_memory_doc_uri();
9819 let text = "let y = z in y";
9820 let out = execute_semantic_loop_apply_best_quick_fixes(
9821 &uri,
9822 text,
9823 Position {
9824 line: 0,
9825 character: 8,
9826 },
9827 2,
9828 BulkQuickFixStrategy::Conservative,
9829 true,
9830 )
9831 .expect("bulk output");
9832 let dry_run = out.get("dryRun").and_then(Value::as_bool).expect("dryRun");
9833 assert!(dry_run, "out: {out:#?}");
9834 let updated_text = out
9835 .get("updatedText")
9836 .and_then(Value::as_str)
9837 .expect("updatedText");
9838 assert_ne!(updated_text, text, "out: {out:#?}");
9839 }
9840
9841 #[test]
9842 fn bulk_progress_guard_no_improvement_streak_logic() {
9843 assert_eq!(next_no_improvement_streak(0, 1), 0);
9844 assert_eq!(next_no_improvement_streak(0, 0), 1);
9845 assert_eq!(next_no_improvement_streak(1, -1), 2);
9846 }
9847
9848 #[test]
9849 fn bulk_progress_guard_cycle_detection_hashes_equal_texts() {
9850 let a = text_state_hash("let y = z in y");
9851 let b = text_state_hash("let y = z in y");
9852 let c = text_state_hash("let y = null in y");
9853 assert_eq!(a, b);
9854 assert_ne!(a, c);
9855 }
9856}