1use crate::metaprogramming::metaprogrammation;
12use nom_locate::LocatedSpan;
13use std::path::Path;
14use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, Position, Range};
15use typr_core::components::context::config::Environment;
16use typr_core::components::context::Context;
17use typr_core::components::language::var::Var;
18use typr_core::components::language::Lang;
19use typr_core::components::r#type::type_system::TypeSystem;
20use typr_core::components::r#type::Type;
21use typr_core::processes::parsing::parse;
22use typr_core::typing;
23use typr_core::utils::builder;
24
25type Span<'a> = LocatedSpan<&'a str, String>;
26
27#[derive(Debug, Clone)]
29pub struct HoverInfo {
30 pub type_display: String,
32 pub range: Range,
34}
35
36#[derive(Debug, Clone)]
38pub struct DefinitionInfo {
39 pub range: Range,
41 pub file_path: Option<String>,
43}
44
45pub fn find_type_at(content: &str, line: u32, character: u32) -> Option<HoverInfo> {
53 let (word, word_range) = extract_word_at(content, line, character)?;
55
56 let span: Span = LocatedSpan::new_extra(content, String::new());
58 let parse_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| parse(span)));
59 let ast = parse_result.ok()?.ast;
60
61 let context = Context::default();
63 let type_context =
64 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| typing(&context, &ast)));
65 let type_context = type_context.ok()?;
66 let final_context = type_context.context;
67
68 let types = final_context.get_types_from_name(&word);
70
71 let typ = if types.is_empty() {
72 infer_literal_type(&word)?
74 } else {
75 types.last().unwrap().clone()
77 };
78
79 let highlighted = highlight_type(&typ.pretty());
81 let markdown = format!(
82 "**`{}`** : {}\n\n```\n{}\n```",
83 word, highlighted, typ.pretty() );
87
88 Some(HoverInfo {
89 type_display: markdown,
90 range: word_range,
91 })
92}
93
94fn detect_environment(file_path: &str) -> Environment {
99 let path = Path::new(file_path);
100 let mut dir = path.parent();
101
102 while let Some(d) = dir {
103 let description = d.join("DESCRIPTION");
104 let namespace = d.join("NAMESPACE");
105 if description.exists() && namespace.exists() {
106 return Environment::Project;
107 }
108 dir = d.parent();
109 }
110
111 Environment::StandAlone
112}
113
114pub fn find_definition_at(
116 content: &str,
117 line: u32,
118 character: u32,
119 file_path: &str,
120) -> Option<DefinitionInfo> {
121 let (word, _word_range) = extract_word_at(content, line, character)?;
123
124 let span: Span = LocatedSpan::new_extra(content, file_path.to_string());
126 let parse_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| parse(span)));
127 let ast = parse_result.ok()?.ast;
128
129 let environment = detect_environment(file_path);
131 let ast = metaprogrammation(ast, environment);
132
133 let context = Context::default();
135 let type_context =
136 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| typing(&context, &ast)));
137 let type_context = type_context.ok()?;
138 let final_context = type_context.context;
139
140 let definition_var = final_context
142 .variables()
143 .find(|(var, _)| var.get_name() == word)
144 .map(|(var, _)| var.clone());
145
146 let definition_var = definition_var.or_else(|| {
147 final_context
148 .aliases()
149 .find(|(var, _)| var.get_name() == word)
150 .map(|(var, _)| var.clone())
151 });
152
153 let var = definition_var?;
154 let help_data = var.get_help_data();
155 let offset = help_data.get_offset();
156 let definition_file = help_data.get_file_name();
157
158 let (source_content, file_path_result) =
160 if definition_file.is_empty() || definition_file == file_path {
161 (content.to_string(), None)
162 } else {
163 match std::fs::read_to_string(&definition_file) {
164 Ok(external_content) => (external_content, Some(definition_file)),
165 Err(_) => (content.to_string(), None),
166 }
167 };
168
169 let pos = offset_to_position(offset, &source_content);
171 let end_col = pos.character + word.len() as u32;
172
173 Some(DefinitionInfo {
174 range: Range::new(pos, Position::new(pos.line, end_col)),
175 file_path: file_path_result,
176 })
177}
178
179fn offset_to_position(offset: usize, content: &str) -> Position {
181 let mut line = 0u32;
182 let mut col = 0u32;
183
184 for (i, ch) in content.chars().enumerate() {
185 if i >= offset {
186 break;
187 }
188 if ch == '\n' {
189 line += 1;
190 col = 0;
191 } else {
192 col += 1;
193 }
194 }
195
196 Position::new(line, col)
197}
198
199fn extract_word_at(content: &str, line: u32, character: u32) -> Option<(String, Range)> {
204 let source_line = content.lines().nth(line as usize)?;
205
206 if (character as usize) > source_line.len() {
207 return None;
208 }
209
210 let bytes = source_line.as_bytes();
211 let col = character as usize;
212
213 if col >= bytes.len() || !is_word_char(bytes[col]) {
214 if col == 0 {
215 return None;
216 }
217 if !is_word_char(bytes[col - 1]) {
218 return None;
219 }
220 }
221
222 let anchor = if col < bytes.len() && is_word_char(bytes[col]) {
223 col
224 } else {
225 col - 1
226 };
227
228 let start = {
229 let mut i = anchor;
230 while i > 0 && is_word_char(bytes[i - 1]) {
231 i -= 1;
232 }
233 i
234 };
235
236 let end = {
237 let mut i = anchor;
238 while i + 1 < bytes.len() && is_word_char(bytes[i + 1]) {
239 i += 1;
240 }
241 i + 1
242 };
243
244 let word = &source_line[start..end];
245 if word.is_empty() {
246 return None;
247 }
248
249 Some((
250 word.to_string(),
251 Range {
252 start: Position::new(line, start as u32),
253 end: Position::new(line, end as u32),
254 },
255 ))
256}
257
258fn is_word_char(b: u8) -> bool {
260 b.is_ascii_alphanumeric() || b == b'_' || b == b'.'
261}
262
263fn infer_literal_type(word: &str) -> Option<Type> {
267 if let Ok(i) = word.parse::<i32>() {
268 return Some(builder::integer_type(i));
269 }
270 if let Ok(_f) = word.parse::<f32>() {
271 return Some(builder::number_type());
272 }
273 None
274}
275
276const PRIMITIVE_TYPES: &[&str] = &["int", "num", "bool", "char", "any", "Empty"];
280
281const TYPE_KEYWORDS: &[&str] = &["fn", "Module", "interface", "class"];
283
284pub fn highlight_type(type_str: &str) -> String {
286 let mut out = String::with_capacity(type_str.len() * 2);
287 let chars: Vec<char> = type_str.chars().collect();
288 let len = chars.len();
289 let mut i = 0;
290
291 while i < len {
292 let ch = chars[i];
293
294 if (ch == '#' || ch == '%')
296 && i + 1 < len
297 && (chars[i + 1].is_alphanumeric() || chars[i + 1] == '_')
298 {
299 let start = i;
300 i += 1;
301 while i < len && (chars[i].is_alphanumeric() || chars[i] == '_') {
302 i += 1;
303 }
304 let token: String = chars[start..i].iter().collect();
305 out.push_str(&format!("*{}*", token));
306 continue;
307 }
308
309 if ch == '"' || ch == '\'' {
311 let delim = ch;
312 let start = i;
313 i += 1;
314 while i < len && chars[i] != delim {
315 i += 1;
316 }
317 if i < len {
318 i += 1;
319 }
320 let token: String = chars[start..i].iter().collect();
321 out.push_str(&format!("`{}`", token));
322 continue;
323 }
324
325 if ch.is_alphabetic() || ch == '_' {
327 let start = i;
328 while i < len && (chars[i].is_alphanumeric() || chars[i] == '_' || chars[i] == '.') {
329 i += 1;
330 }
331 let word: String = chars[start..i].iter().collect();
332 out.push_str(&colorize_word(&word));
333 continue;
334 }
335
336 if ch.is_ascii_digit() {
338 let start = i;
339 while i < len && (chars[i].is_ascii_digit() || chars[i] == '.') {
340 i += 1;
341 }
342 let token: String = chars[start..i].iter().collect();
343 out.push_str(&format!("*{}*", token));
344 continue;
345 }
346
347 if ch == '.' && i + 1 < len && chars[i + 1].is_alphabetic() {
349 out.push('.');
350 i += 1;
351 let start = i;
352 while i < len && (chars[i].is_alphanumeric() || chars[i] == '_') {
353 i += 1;
354 }
355 let word: String = chars[start..i].iter().collect();
356 out.push_str(&format!("**{}**", word));
357 continue;
358 }
359
360 if ch == '-' && i + 1 < len && chars[i + 1] == '>' {
362 out.push_str(" → ");
363 i += 2;
364 continue;
365 }
366
367 out.push(ch);
369 i += 1;
370 }
371
372 out
373}
374
375fn colorize_word(word: &str) -> String {
377 if TYPE_KEYWORDS.contains(&word) {
378 format!("***{}***", word)
379 } else if PRIMITIVE_TYPES.contains(&word) {
380 format!("**{}**", word)
381 } else if is_generic_name(word) {
382 format!("*{}*", word)
383 } else if word.chars().next().map_or(false, |c| c.is_uppercase()) {
384 format!("**{}**", word)
385 } else {
386 word.to_string()
387 }
388}
389
390fn is_generic_name(word: &str) -> bool {
392 let mut chars = word.chars();
393 match chars.next() {
394 Some(c) if c.is_ascii_uppercase() => chars.all(|c| c.is_ascii_digit()),
395 _ => false,
396 }
397}
398
399pub fn get_completions_at(content: &str, line: u32, character: u32) -> Vec<CompletionItem> {
405 let final_context = match parse_document_without_cursor_line(content, line) {
407 Some(ctx) => ctx,
408 None => {
409 let span: Span = LocatedSpan::new_extra(content, String::new());
410 let parse_result =
411 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| parse(span)));
412 let context = Context::default();
413 match parse_result {
414 Ok(result) => {
415 let ast = result.ast;
416 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
417 typing(&context, &ast)
418 })) {
419 Ok(tc) => tc.context,
420 Err(_) => return get_fallback_completions(),
421 }
422 }
423 Err(_) => return get_fallback_completions(),
424 }
425 }
426 };
427
428 let prefix = extract_multiline_prefix(content, line, character);
430
431 let ctx = detect_completion_context(&prefix);
433
434 match ctx {
436 CompletionCtx::Type => get_type_completions(&final_context),
437 CompletionCtx::Module(name) => get_module_completions(&final_context, &name),
438 CompletionCtx::Pipe(expr) => get_pipe_completions(&final_context, &expr),
439 CompletionCtx::RecordField(expr) => get_record_field_completions(&final_context, &expr),
440 CompletionCtx::DotAccess(expr) => get_dot_completions(&final_context, &expr),
441 CompletionCtx::Expression => get_expression_completions(&final_context),
442 }
443}
444
445fn parse_document_without_cursor_line(content: &str, cursor_line: u32) -> Option<Context> {
447 let lines: Vec<&str> = content.lines().collect();
448
449 let mut filtered_lines = Vec::new();
450 for (idx, line) in lines.iter().enumerate() {
451 if idx != cursor_line as usize {
452 filtered_lines.push(*line);
453 }
454 }
455
456 let filtered_content = filtered_lines.join("\n");
457 let span: Span = LocatedSpan::new_extra(&filtered_content, String::new());
458
459 let parse_result =
460 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| parse(span))).ok()?;
461 let ast = parse_result.ast;
462
463 let context = Context::default();
464
465 let final_context = if let Lang::Lines(exprs, _) = &ast {
466 let mut ctx = context.clone();
467 for expr in exprs {
468 if let Ok(tc) =
469 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| typing(&ctx, expr)))
470 {
471 ctx = tc.context;
472 }
473 }
474 ctx
475 } else {
476 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| typing(&context, &ast)))
477 .ok()?
478 .context
479 };
480
481 Some(final_context)
482}
483
484#[derive(Debug, Clone)]
487enum CompletionCtx {
488 Type,
489 Module(String),
490 Pipe(String),
491 RecordField(String),
492 DotAccess(String),
493 Expression,
494}
495
496fn extract_multiline_prefix(content: &str, line: u32, character: u32) -> String {
497 let lines: Vec<&str> = content.lines().collect();
498 let current_line_idx = line as usize;
499
500 if current_line_idx >= lines.len() {
501 return String::new();
502 }
503
504 let current_line_part = lines[current_line_idx]
505 .get(..character as usize)
506 .unwrap_or("");
507
508 let lookback_lines = 10;
509 let start_line = current_line_idx.saturating_sub(lookback_lines);
510
511 let mut context_lines = Vec::new();
512 for i in start_line..current_line_idx {
513 context_lines.push(lines[i]);
514 }
515 context_lines.push(current_line_part);
516
517 context_lines.join("\n")
518}
519
520fn detect_completion_context(prefix: &str) -> CompletionCtx {
521 let trimmed = prefix.trim_end();
522
523 if trimmed.ends_with("|>") {
524 let before_pipe = trimmed[..trimmed.len() - 2].trim();
525 return CompletionCtx::Pipe(extract_expression_before(before_pipe));
526 }
527
528 if let Some(dollar_pos) = trimmed.rfind('$') {
529 let after_dollar = &trimmed[dollar_pos + 1..];
530 if after_dollar.is_empty() || after_dollar.chars().all(|c| c.is_whitespace()) {
531 let before_dollar = trimmed[..dollar_pos].trim_end();
532 if !before_dollar.is_empty() {
533 let expr = extract_last_expression(before_dollar);
534 return CompletionCtx::RecordField(expr);
535 }
536 }
537 }
538
539 if let Some(dot_pos) = trimmed.rfind('.') {
540 let after_dot = &trimmed[dot_pos + 1..];
541 if after_dot.is_empty() || after_dot.chars().all(|c| c.is_whitespace()) {
542 let before_dot = trimmed[..dot_pos].trim_end();
543 if !before_dot.is_empty() {
544 let expr = extract_last_expression(before_dot);
545
546 if expr.chars().next().map_or(false, |c| c.is_uppercase()) {
547 return CompletionCtx::Module(expr);
548 } else {
549 return CompletionCtx::DotAccess(expr);
550 }
551 }
552 }
553 }
554
555 if let Some(colon_pos) = trimmed.rfind(':') {
556 let after_colon = &trimmed[colon_pos + 1..];
557 if !after_colon.contains('=') && !after_colon.contains(';') {
558 return CompletionCtx::Type;
559 }
560 }
561
562 if trimmed.trim_start().starts_with("type ") && trimmed.contains('=') {
563 return CompletionCtx::Type;
564 }
565
566 CompletionCtx::Expression
567}
568
569fn extract_last_expression(s: &str) -> String {
570 let trimmed = s.trim_end();
571
572 let parts: Vec<&str> = trimmed
573 .split(|c| c == ';' || c == '\n')
574 .filter(|p| !p.trim().is_empty())
575 .collect();
576
577 let last_statement = parts.last().unwrap_or(&"").trim();
578
579 if last_statement.is_empty() {
580 return String::new();
581 }
582
583 let mut depth_paren = 0;
584 let mut depth_bracket = 0;
585 let mut depth_brace = 0;
586 let mut start = 0;
587
588 for (i, ch) in last_statement.char_indices().rev() {
589 match ch {
590 ')' => depth_paren += 1,
591 '(' => {
592 depth_paren -= 1;
593 if depth_paren < 0 {
594 start = i + 1;
595 break;
596 }
597 }
598 ']' => depth_bracket += 1,
599 '[' => {
600 depth_bracket -= 1;
601 if depth_bracket < 0 {
602 start = i + 1;
603 break;
604 }
605 }
606 '}' => depth_brace += 1,
607 '{' => {
608 depth_brace -= 1;
609 if depth_brace < 0 {
610 start = i + 1;
611 break;
612 }
613 }
614 ',' if depth_paren == 0 && depth_bracket == 0 && depth_brace == 0 => {
615 start = i + 1;
616 break;
617 }
618 '<' | '>' if depth_paren == 0 && depth_bracket == 0 && depth_brace == 0 => {
619 if i > 0 && last_statement.as_bytes().get(i - 1) == Some(&b'-') {
620 continue;
621 }
622 start = i + 1;
623 break;
624 }
625 _ => {}
626 }
627 }
628
629 last_statement[start..].trim().to_string()
630}
631
632fn extract_expression_before(s: &str) -> String {
633 let trimmed = s.trim_end();
634
635 let mut depth = 0;
636 let mut start = trimmed.len();
637
638 for (i, ch) in trimmed.char_indices().rev() {
639 match ch {
640 ')' | ']' | '}' => depth += 1,
641 '(' | '[' | '{' => {
642 depth -= 1;
643 if depth < 0 {
644 start = i + 1;
645 break;
646 }
647 }
648 ';' | ',' if depth == 0 => {
649 start = i + 1;
650 break;
651 }
652 _ => {}
653 }
654 }
655
656 trimmed[start..].trim().to_string()
657}
658
659fn get_type_completions(context: &Context) -> Vec<CompletionItem> {
662 let mut items = Vec::new();
663
664 let primitives = [
665 ("int", builder::integer_type_default()),
666 ("num", builder::number_type()),
667 ("bool", builder::boolean_type()),
668 ("char", builder::character_type_default()),
669 ("any", builder::any_type()),
670 ];
671
672 for (name, typ) in &primitives {
673 items.push(CompletionItem {
674 label: name.to_string(),
675 insert_text: Some(format!(" {}", name)),
676 kind: Some(CompletionItemKind::KEYWORD),
677 detail: Some(typ.pretty()),
678 ..Default::default()
679 });
680 }
681
682 for (var, typ) in context.aliases() {
683 if var.is_alias() {
684 items.push(CompletionItem {
685 label: var.get_name(),
686 insert_text: Some(format!(" {}", var.get_name())),
687 kind: Some(CompletionItemKind::INTERFACE),
688 detail: Some(typ.pretty()),
689 ..Default::default()
690 });
691 }
692 }
693
694 for (var, typ) in context.module_aliases() {
695 items.push(CompletionItem {
696 label: var.get_name(),
697 insert_text: Some(format!(" {}", var.get_name())),
698 kind: Some(CompletionItemKind::INTERFACE),
699 detail: Some(typ.pretty()),
700 ..Default::default()
701 });
702 }
703
704 items
705}
706
707fn get_module_completions(context: &Context, module_name: &str) -> Vec<CompletionItem> {
708 let module_context = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
709 context.extract_module_as_vartype(module_name)
710 }));
711
712 let module_ctx = match module_context {
713 Ok(ctx) => ctx,
714 Err(_) => return Vec::new(),
715 };
716
717 let mut items = Vec::new();
718
719 for (var, typ) in module_ctx.variables() {
720 let kind = if typ.is_function() {
721 CompletionItemKind::FUNCTION
722 } else {
723 CompletionItemKind::VARIABLE
724 };
725 items.push(var_to_completion_item(var, typ, kind));
726 }
727
728 for (var, typ) in module_ctx.aliases() {
729 items.push(var_to_completion_item(
730 var,
731 typ,
732 CompletionItemKind::INTERFACE,
733 ));
734 }
735
736 items
737}
738
739fn get_pipe_completions(context: &Context, expr: &str) -> Vec<CompletionItem> {
740 let expr_type = infer_expression_type(context, expr);
741
742 let mut items = Vec::new();
743
744 let all_functions: Vec<_> = context
745 .get_all_generic_functions()
746 .into_iter()
747 .chain(
748 context
749 .typing_context
750 .standard_library()
751 .into_iter()
752 .filter(|(_, typ)| typ.is_function())
753 .map(|(v, t)| (v.clone(), t.clone())),
754 )
755 .collect();
756
757 for (var, typ) in all_functions {
758 if let Some(first_param_type) = get_first_parameter_type(&typ) {
759 if expr_type.is_subtype(&first_param_type, context).0 {
760 items.push(CompletionItem {
761 label: var.get_name(),
762 insert_text: Some(format!(" {}", var.get_name())),
763 kind: Some(CompletionItemKind::FUNCTION),
764 detail: Some(typ.pretty()),
765 ..Default::default()
766 });
767 }
768 }
769 }
770
771 items
772}
773
774fn get_record_field_completions(context: &Context, expr: &str) -> Vec<CompletionItem> {
775 let record_type = infer_expression_type(context, expr);
776
777 match record_type {
778 Type::Record(fields, _) => fields
779 .iter()
780 .map(|arg_type| CompletionItem {
781 label: arg_type.get_argument_str(),
782 kind: Some(CompletionItemKind::FIELD),
783 detail: Some(arg_type.get_type().pretty()),
784 ..Default::default()
785 })
786 .collect(),
787 _ => Vec::new(),
788 }
789}
790
791fn get_dot_completions(context: &Context, expr: &str) -> Vec<CompletionItem> {
792 let mut items = Vec::new();
793
794 let expr_type = infer_expression_type(context, expr);
795
796 if let Type::Record(fields, _) = &expr_type {
797 for arg_type in fields {
798 items.push(CompletionItem {
799 label: arg_type.get_argument_str(),
800 kind: Some(CompletionItemKind::FIELD),
801 detail: Some(arg_type.get_type().pretty()),
802 ..Default::default()
803 });
804 }
805 }
806
807 let all_functions: Vec<_> = context
808 .get_all_generic_functions()
809 .into_iter()
810 .chain(
811 context
812 .typing_context
813 .standard_library()
814 .into_iter()
815 .filter(|(_, typ)| typ.is_function())
816 .map(|(v, t)| (v.clone(), t.clone())),
817 )
818 .collect();
819
820 for (var, typ) in all_functions {
821 if let Some(first_param_type) = get_first_parameter_type(&typ) {
822 if expr_type.is_subtype(&first_param_type, context).0 {
823 items.push(var_to_completion_item(
824 &var,
825 &typ,
826 CompletionItemKind::FUNCTION,
827 ));
828 }
829 }
830 }
831
832 items
833}
834
835fn get_expression_completions(context: &Context) -> Vec<CompletionItem> {
836 let mut items = Vec::new();
837
838 for (var, typ) in context.variables() {
839 if !typ.is_function() && !var.is_alias() {
840 items.push(var_to_completion_item(
841 var,
842 typ,
843 CompletionItemKind::VARIABLE,
844 ));
845 }
846 }
847
848 for (var, typ) in context.get_all_generic_functions() {
849 items.push(var_to_completion_item(
850 &var,
851 &typ,
852 CompletionItemKind::FUNCTION,
853 ));
854 }
855
856 for (var, typ) in &context.typing_context.standard_library() {
857 if typ.is_function() {
858 items.push(var_to_completion_item(
859 var,
860 typ,
861 CompletionItemKind::FUNCTION,
862 ));
863 }
864 }
865
866 items
867}
868
869fn get_fallback_completions() -> Vec<CompletionItem> {
870 let mut items = Vec::new();
871
872 let primitives = [
873 ("int", builder::integer_type_default()),
874 ("num", builder::number_type()),
875 ("bool", builder::boolean_type()),
876 ("char", builder::character_type_default()),
877 ("any", builder::any_type()),
878 ];
879
880 for (name, typ) in &primitives {
881 items.push(CompletionItem {
882 label: name.to_string(),
883 kind: Some(CompletionItemKind::KEYWORD),
884 detail: Some(typ.pretty()),
885 ..Default::default()
886 });
887 }
888
889 items
890}
891
892fn infer_expression_type(context: &Context, expr: &str) -> Type {
895 let trimmed = expr.trim();
896
897 if trimmed.is_empty() {
898 return builder::any_type();
899 }
900
901 let types = context.get_types_from_name(trimmed);
902 if !types.is_empty() {
903 return types.last().unwrap().clone();
904 }
905
906 if let Some(typ) = infer_literal_type(trimmed) {
907 return typ;
908 }
909
910 let result = parse_and_infer_expression_type(context, trimmed);
911
912 result.unwrap_or_else(|| builder::any_type())
913}
914
915fn parse_and_infer_expression_type(context: &Context, expr: &str) -> Option<Type> {
916 let normalized_expr = normalize_dot_calls(context, expr);
917
918 let wrapped = format!("__temp <- {};", normalized_expr);
919 let span: Span = LocatedSpan::new_extra(&wrapped, "<lsp-inference>".to_string());
920
921 let ast = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| parse(span)))
922 .ok()?
923 .ast;
924
925 let type_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
926 typr_core::typing_with_errors(context, &ast)
927 }))
928 .ok()?;
929
930 Some(type_result.type_context.value.clone())
931}
932
933fn normalize_dot_calls(context: &Context, expr: &str) -> String {
934 let trimmed = expr.trim();
935
936 let mut result = trimmed.to_string();
937 let mut changed = true;
938
939 while changed {
940 changed = false;
941 if let Some(transformed) = try_normalize_rightmost_dot_call(context, &result) {
942 result = transformed;
943 changed = true;
944 }
945 }
946
947 result
948}
949
950fn try_normalize_rightmost_dot_call(context: &Context, expr: &str) -> Option<String> {
951 let chars: Vec<char> = expr.chars().collect();
952 let len = chars.len();
953
954 let mut paren_depth = 0;
955 let mut bracket_depth = 0;
956 let mut i = len;
957
958 while i > 0 {
959 i -= 1;
960 match chars[i] {
961 ')' => paren_depth += 1,
962 '(' => {
963 if paren_depth > 0 {
964 paren_depth -= 1;
965 }
966 }
967 ']' => bracket_depth += 1,
968 '[' => {
969 if bracket_depth > 0 {
970 bracket_depth -= 1;
971 }
972 }
973 '.' if paren_depth == 0 && bracket_depth == 0 => {
974 if i + 1 < len && (chars[i + 1].is_alphabetic() || chars[i + 1] == '_') {
975 let mut method_end = i + 1;
976 while method_end < len
977 && (chars[method_end].is_alphanumeric() || chars[method_end] == '_')
978 {
979 method_end += 1;
980 }
981 let method_name: String = chars[i + 1..method_end].iter().collect();
982
983 let types = context.get_types_from_name(&method_name);
984 let is_known_function = types.iter().any(|t| t.is_function());
985
986 let is_std_function = context
987 .typing_context
988 .standard_library()
989 .iter()
990 .any(|(v, t)| v.get_name() == method_name && t.is_function());
991
992 if is_known_function || is_std_function {
993 let receiver: String = chars[..i].iter().collect();
994 let after_method: String = chars[method_end..].iter().collect();
995
996 if after_method.starts_with('(') {
997 let after_chars: Vec<char> = after_method.chars().collect();
998 let mut depth = 0;
999 let mut close_idx = 0;
1000 for (j, &c) in after_chars.iter().enumerate() {
1001 match c {
1002 '(' => depth += 1,
1003 ')' => {
1004 depth -= 1;
1005 if depth == 0 {
1006 close_idx = j;
1007 break;
1008 }
1009 }
1010 _ => {}
1011 }
1012 }
1013
1014 let args_content: String = after_chars[1..close_idx].iter().collect();
1015 let rest: String = after_chars[close_idx + 1..].iter().collect();
1016
1017 if args_content.trim().is_empty() {
1018 return Some(format!(
1019 "{}({}){}",
1020 method_name,
1021 receiver.trim(),
1022 rest
1023 ));
1024 } else {
1025 return Some(format!(
1026 "{}({}, {}){}",
1027 method_name,
1028 receiver.trim(),
1029 args_content.trim(),
1030 rest
1031 ));
1032 }
1033 } else {
1034 return None;
1035 }
1036 }
1037 }
1038 }
1039 _ => {}
1040 }
1041 }
1042
1043 None
1044}
1045
1046fn get_first_parameter_type(typ: &Type) -> Option<Type> {
1047 match typ {
1048 Type::Function(params, _, _) => params.first().cloned(),
1049 _ => None,
1050 }
1051}
1052
1053fn var_to_completion_item(var: &Var, typ: &Type, kind: CompletionItemKind) -> CompletionItem {
1054 CompletionItem {
1055 label: var.get_name(),
1056 kind: Some(kind),
1057 detail: Some(typ.pretty()),
1058 ..Default::default()
1059 }
1060}