Fixed module system

This commit is contained in:
2026-06-22 00:48:13 +02:00
parent b69168f135
commit 2718a5028a
2 changed files with 122 additions and 14 deletions
+111 -1
View File
@@ -346,6 +346,8 @@ impl Backend {
let src_root = ws.join("src");
let package_mist = read_mist_package(&ws);
self.ensure_implicit_packages(&src_root).await;
// Collect sources first to avoid holding locks during transpile
let sources: Vec<(PathBuf, String, String)> = {
let docs = self.documents.lock().await;
@@ -366,6 +368,13 @@ impl Backend {
}
}
/// Create synthetic `package.mist` documents for subdirectories that contain
/// .mist files but no real package.mist (implicit packages).
async fn ensure_implicit_packages(&self, src_root: &Path) {
let mut docs = self.documents.lock().await;
ensure_implicit_packages_impl(&mut *docs, src_root);
}
async fn publish_diagnostics(&self, uri: Url, diagnostics: Vec<Diagnostic>) {
let key = uri.clone();
let prev = self.previous_diagnostics.lock().await.get(&key).cloned();
@@ -471,6 +480,8 @@ impl LanguageServer for Backend {
eprintln!("Loaded {} mist files", files.len());
ensure_implicit_packages_impl(&mut *documents.lock().await, &src_root);
// Compute mod_decls before any transpile so the root file gets its
// child module declarations from the very first didOpen.
let package_mist = read_mist_package(root);
@@ -510,6 +521,41 @@ impl LanguageServer for Backend {
}
}
// Also transpile synthetic package.mist files (implicit packages)
// so rust-analyzer has their content from the start.
let synthetic_paths: Vec<PathBuf> = documents
.lock()
.await
.keys()
.filter(|p| {
!files.contains(p)
&& p.file_name().and_then(|n| n.to_str()) == Some("package.mist")
})
.cloned()
.collect();
for syn_path in &synthetic_paths {
let decl = mod_decls.get(syn_path).map(String::as_str).unwrap_or("");
let source = documents
.lock()
.await
.get(syn_path)
.map(|r| r.to_string())
.unwrap_or_default();
if let Ok(transpiled) = transpile_mist(syn_path, &source, decl) {
if let Some(rust_uri) = clean_lsp_url(&transpiled.rust_path) {
let _ = ra
.lock()
.await
.did_open(rust_uri, &transpiled.rust_content)
.await;
mapping
.lock()
.await
.insert(transpiled.rust_path, transpiled.mapping);
}
}
}
if let Some(rx) = notification_rx.lock().await.take() {
tokio::spawn(handle_ra_notifications(
rx,
@@ -1099,10 +1145,15 @@ fn compute_mod_decls(
Some(s) => s,
None => continue,
};
// Skip the root package file itself
// Skip the root package entry file (e.g. main.mist)
if *file == package_path {
continue;
}
// Skip any subdirectory package.mist — it's the directory's own
// entry file, not a sibling submodule.
if stem == "package" {
continue;
}
mod_decl.push_str(&format!("pub mod {};\n", stem));
}
@@ -1118,7 +1169,66 @@ fn compute_mod_decls(
}
}
// For each subdirectory that is a Mist module, add pub mod <dirname>;
// to the parent directory's declarations so the parent's transpiled Rust
// output includes the child module declaration.
for dir in dirs.keys() {
if dir == src_root {
continue;
}
let dirname = match dir.file_name().and_then(|n| n.to_str()) {
Some(n) => n,
None => continue,
};
let parent = dir.parent().unwrap();
let parent_target = if parent == src_root {
package_path.clone()
} else {
let pkg = parent.join("package.mist");
if !documents.contains_key(&pkg) {
continue;
}
pkg
};
result
.entry(parent_target)
.or_default()
.push_str(&format!("pub mod {};\n", dirname));
}
result
}
fn ensure_implicit_packages_impl(
docs: &mut HashMap<PathBuf, Rope>,
src_root: &Path,
) {
let mut dirs_with_mist: HashMap<PathBuf, Vec<PathBuf>> = HashMap::new();
for mist_path in docs.keys() {
if let Ok(rel) = mist_path.strip_prefix(src_root) {
if let Some(parent) = rel.parent() {
if !parent.as_os_str().is_empty() {
dirs_with_mist
.entry(src_root.join(parent))
.or_default()
.push(mist_path.clone());
}
}
}
}
for dir in dirs_with_mist.keys() {
let pkg_path = dir.join("package.mist");
if !docs.contains_key(&pkg_path) {
let dirname = dir
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("module")
.to_string();
let content = format!("pub module {dirname};\n");
docs.insert(pkg_path, Rope::from_str(&content));
}
}
}
fn clean_completion_item(mut item: CompletionItem) -> CompletionItem {
+11 -13
View File
@@ -1,8 +1,8 @@
use std::path::{Path, PathBuf};
use mist_codegen::{GetRust, RustCodegen};
use mist_codegen::RustCodegen;
use mist_parser::rev_mapper::Mapping;
use mist_parser::{parse, parse_module};
use mist_parser::parse;
pub struct TranspiledFile {
pub mist_path: PathBuf,
@@ -16,8 +16,13 @@ pub fn transpile_mist(
source: &str,
extra_mod_decl: &str,
) -> Result<TranspiledFile, String> {
let module_decl = parse_module(source).map_err(|e| format!("parse module error: {e:?}"))?;
let rust_path = crate::from_mist_to_rust(mist_path.to_path_buf());
let mut rust_path = crate::from_mist_to_rust(mist_path.to_path_buf());
// Package files (package.mist) must output as <dir>/mod.rs so the Rust module
// hierarchy resolves correctly (pub mod <child>; declarations look for sibling
// .rs files, and the parent module declaration looks for <dir>/mod.rs).
if mist_path.file_name().and_then(|n| n.to_str()) == Some("package.mist") {
rust_path.set_file_name("mod.rs");
}
let parsed = parse(source).map_err(|e| format!("parse error: {e:?}"))?;
@@ -29,19 +34,12 @@ pub fn transpile_mist(
let mut codegen = RustCodegen::new(mist_path.to_path_buf());
let output = codegen.generate(parsed);
let self_mod_prefix = module_decl
.as_ref()
.map(|(vis, name)| format!("{}mod {};\n", vis.get_rust(), name.get_rust()))
.unwrap_or_default();
let combined_prefix = format!("{}{}", extra_mod_decl, self_mod_prefix);
codegen.mapping.shift_rust(combined_prefix.lines().count() as isize, 0);
codegen.mapping.shift_rust(extra_mod_decl.lines().count() as isize, 0);
Ok(TranspiledFile {
mist_path: mist_path.to_path_buf(),
rust_path,
rust_content: format!("{}{}", combined_prefix, output),
rust_content: format!("{}{}", extra_mod_decl, output),
mapping: codegen.mapping,
})
}