|
1 | 1 | use std::collections::HashMap; |
2 | 2 | use std::sync::atomic::Ordering; |
| 3 | +use std::sync::Arc; |
3 | 4 |
|
4 | 5 | use rayon::prelude::*; |
| 6 | +use tokio::sync::RwLock; |
5 | 7 | use tower_lsp::lsp_types::*; |
| 8 | +use tower_lsp::Client; |
6 | 9 |
|
7 | 10 | use crate::build::cache; |
| 11 | +use crate::build::cache::ModuleCache; |
8 | 12 | use crate::build::BuildOptions; |
9 | 13 | use crate::cst::{self, Decl}; |
10 | 14 | use crate::interner; |
11 | 15 | use crate::lsp::utils::find_definition::DefinitionIndex; |
12 | | -use crate::lsp::{CompletionEntry, CompletionEntryKind, CompletionIndex}; |
| 16 | +use crate::lsp::{CompletionEntry, CompletionEntryKind, CompletionIndex, FileState}; |
| 17 | +use crate::typechecker::registry::ModuleRegistry; |
13 | 18 |
|
14 | 19 | use super::super::{Backend, LOAD_STATE_CACHE_LOADED, LOAD_STATE_READY}; |
15 | 20 |
|
@@ -95,6 +100,7 @@ impl Backend { |
95 | 100 | if all_snapshots_loaded { |
96 | 101 | self.load_state.store(LOAD_STATE_READY, Ordering::SeqCst); |
97 | 102 | self.info(format!("[timing] Ready from cache in {:.2?} total", total_start.elapsed())).await; |
| 103 | + self.typecheck_open_files().await; |
98 | 104 | return; |
99 | 105 | } |
100 | 106 |
|
@@ -145,6 +151,8 @@ impl Backend { |
145 | 151 | let load_state = self.load_state.clone(); |
146 | 152 | let cache_dir = self.cache_dir.clone(); |
147 | 153 | let progress_token = token.clone(); |
| 154 | + let files = self.files.clone(); |
| 155 | + let registry = self.registry.clone(); |
148 | 156 |
|
149 | 157 | let rt_handle = tokio::runtime::Handle::current(); |
150 | 158 | std::thread::Builder::new() |
@@ -355,6 +363,9 @@ impl Backend { |
355 | 363 | .await; |
356 | 364 | }); |
357 | 365 | info(format!("[timing] Phase B (index build) total: {:.2?}", build_start.elapsed())); |
| 366 | + |
| 367 | + // Typecheck any files that were opened during loading |
| 368 | + typecheck_open_files(&files, ®istry, &module_cache, &client, &rt); |
358 | 369 | }) |
359 | 370 | .expect("failed to spawn load-sources thread"); |
360 | 371 | } |
@@ -393,6 +404,7 @@ impl Backend { |
393 | 404 | let load_state = self.load_state.clone(); |
394 | 405 | let cache_dir = self.cache_dir.clone(); |
395 | 406 | let progress_token = token.clone(); |
| 407 | + let files = self.files.clone(); |
396 | 408 |
|
397 | 409 | let rt_handle = tokio::runtime::Handle::current(); |
398 | 410 | std::thread::Builder::new() |
@@ -640,11 +652,105 @@ impl Backend { |
640 | 652 | .await; |
641 | 653 | }); |
642 | 654 | info(format!("[timing] Phase B (full build) total: {:.2?}", build_start.elapsed())); |
| 655 | + |
| 656 | + // Typecheck any files that were opened during loading |
| 657 | + typecheck_open_files(&files, ®istry, &module_cache, &client, &rt); |
643 | 658 | }) |
644 | 659 | .expect("failed to spawn load-sources thread"); |
645 | 660 | } |
646 | 661 | } |
647 | 662 |
|
| 663 | +/// Typecheck any files that were opened in the editor during loading. |
| 664 | +/// Called from spawned threads after setting READY. |
| 665 | +fn typecheck_open_files( |
| 666 | + files: &Arc<RwLock<HashMap<String, FileState>>>, |
| 667 | + registry: &Arc<RwLock<ModuleRegistry>>, |
| 668 | + module_cache: &Arc<RwLock<ModuleCache>>, |
| 669 | + client: &Client, |
| 670 | + rt: &tokio::runtime::Handle, |
| 671 | +) { |
| 672 | + let open_files: Vec<(String, String)> = rt.block_on(async { |
| 673 | + let f = files.read().await; |
| 674 | + f.iter().map(|(uri, fs)| (uri.clone(), fs.source.clone())).collect() |
| 675 | + }); |
| 676 | + if open_files.is_empty() { |
| 677 | + return; |
| 678 | + } |
| 679 | + rt.block_on(client.log_message( |
| 680 | + MessageType::INFO, |
| 681 | + format!("[lsp] typechecking {} open file(s) after init", open_files.len()), |
| 682 | + )); |
| 683 | + for (uri_str, source) in open_files { |
| 684 | + let uri = match Url::parse(&uri_str) { |
| 685 | + Ok(u) => u, |
| 686 | + Err(_) => continue, |
| 687 | + }; |
| 688 | + let module = match crate::parser::parse(&source) { |
| 689 | + Ok(m) => m, |
| 690 | + Err(err) => { |
| 691 | + let range = super::diagnostics::error_to_range(&err, &source); |
| 692 | + let diagnostics = vec![Diagnostic { |
| 693 | + range, |
| 694 | + severity: Some(DiagnosticSeverity::ERROR), |
| 695 | + code: Some(NumberOrString::String(err.code())), |
| 696 | + source: Some("pfc".to_string()), |
| 697 | + message: err.get_message(), |
| 698 | + ..Default::default() |
| 699 | + }]; |
| 700 | + rt.block_on(client.publish_diagnostics(uri, diagnostics, None)); |
| 701 | + continue; |
| 702 | + } |
| 703 | + }; |
| 704 | + |
| 705 | + let module_name = interner::resolve_module_name(&module.name.value.parts); |
| 706 | + let module_parts: Vec<crate::interner::Symbol> = module.name.value.parts.clone(); |
| 707 | + |
| 708 | + rt.block_on(async { |
| 709 | + // Update file state with module name |
| 710 | + { |
| 711 | + let mut f = files.write().await; |
| 712 | + if let Some(fs) = f.get_mut(&uri_str) { |
| 713 | + fs.module_name = Some(module_name.clone()); |
| 714 | + } |
| 715 | + } |
| 716 | + |
| 717 | + let mut reg = registry.write().await; |
| 718 | + |
| 719 | + // Lazy-load imports from cache |
| 720 | + for import_decl in &module.imports { |
| 721 | + let import_parts = &import_decl.module.parts; |
| 722 | + if reg.lookup(import_parts).is_some() { |
| 723 | + continue; |
| 724 | + } |
| 725 | + let import_name = interner::resolve_module_name(import_parts); |
| 726 | + let exports = { |
| 727 | + let mut mc = module_cache.write().await; |
| 728 | + mc.get_exports(&import_name).cloned() |
| 729 | + }; |
| 730 | + if let Some(exports) = exports { |
| 731 | + reg.register(import_parts, exports); |
| 732 | + } |
| 733 | + } |
| 734 | + |
| 735 | + let check_result = crate::typechecker::check_module_with_registry(&module, ®); |
| 736 | + reg.register(&module_parts, check_result.exports.clone()); |
| 737 | + |
| 738 | + let source_hash = ModuleCache::content_hash(&source); |
| 739 | + let import_names: Vec<String> = module.imports.iter() |
| 740 | + .map(|imp| interner::resolve_module_name(&imp.module.parts)) |
| 741 | + .collect(); |
| 742 | + let mut mc = module_cache.write().await; |
| 743 | + if check_result.errors.is_empty() { |
| 744 | + mc.update(module_name.clone(), source_hash, check_result.exports, import_names); |
| 745 | + } |
| 746 | + drop(mc); |
| 747 | + |
| 748 | + let diagnostics = super::diagnostics::type_errors_to_diagnostics(&check_result.errors, &source); |
| 749 | + client.publish_diagnostics(uri, diagnostics, None).await; |
| 750 | + }); |
| 751 | + } |
| 752 | +} |
| 753 | + |
648 | 754 | /// Extract completion entries from a module's CST declarations and source text. |
649 | 755 | fn extract_completion_entries(module: &cst::Module, source: &str) -> Vec<CompletionEntry> { |
650 | 756 | let mut entries = Vec::new(); |
|
0 commit comments