use std::collections::HashSet;
use std::path::{Path, PathBuf};
use nova_diagnostics::{Diagnostic, FileDb, FileId, Severity};
use nova_pm::{Graph, PackageId};
use nova_resolver::{Definitions, ModuleSource};
use nova_typeck::{CheckOptions, ProbePoint, ProbeResult};
use crate::program::{a_dependencys_warning, load_program, registry_files, Load, Program};
pub trait Sources {
fn read(&self, path: &Path) -> std::io::Result<String>;
fn same_file(&self, a: &Path, b: &Path) -> bool {
a == b
}
}
pub struct DiskSources;
impl Sources for DiskSources {
fn read(&self, path: &Path) -> std::io::Result<String> {
std::fs::read_to_string(path)
}
}
#[derive(Debug, Clone, Default)]
pub struct Options {
pub keep_going: bool,
pub tests: bool,
pub module_only: bool,
pub probe: Option<Probe>,
pub index: bool,
}
#[derive(Debug, Clone)]
pub struct Probe {
pub path: PathBuf,
pub offset: u32,
}
pub struct Analysis {
pub db: FileDb,
pub diagnostics: Vec<Diagnostic>,
pub modules: Vec<(FileId, PathBuf)>,
pub module_packages: Vec<Option<PackageId>>,
pub graph: Option<Graph>,
pub manifest: Option<FileId>,
pub definitions: Option<Definitions>,
pub file: Option<nova_ast::File>,
pub module: Option<nova_hir::Module>,
pub probe: ProbeResult,
pub index: Option<nova_resolver::Index>,
}
pub fn analyze(
entry: &Path,
sources: &dyn Sources,
options: &Options,
) -> std::io::Result<Analysis> {
analyze_program(Program::for_file(entry), sources, options)
}
pub fn analyze_program(
program: Program,
sources: &dyn Sources,
options: &Options,
) -> std::io::Result<Analysis> {
let manifest = program.manifest();
let Program {
mut db,
graph,
roots,
runs,
mut diagnostics,
} = program;
let stop = |diags: &[Diagnostic]| !options.keep_going && has_error(diags);
let load = if stop(&diagnostics) {
Load::default()
} else {
load_program(graph.as_ref(), &roots, sources, &mut db)?
};
diagnostics.extend(load.diagnostics);
let dropped = load.dropped;
let registry = registry_files(graph.as_ref(), &load.modules);
let mut modules = load.modules;
let mut analysis = Analysis {
db,
diagnostics: Vec::new(),
modules: modules.iter().map(|m| (m.file, m.path.clone())).collect(),
module_packages: modules.iter().map(|m| m.package).collect(),
graph,
manifest,
definitions: None,
file: None,
module: None,
probe: ProbeResult::default(),
index: None,
};
if stop(&diagnostics) {
analysis.diagnostics = shown(diagnostics, ®istry);
return Ok(analysis);
}
crate::strip_test_functions(&mut modules, options.tests);
let std_files: Vec<FileId> = nova_resolver::STD_MODULES
.iter()
.map(|&(name, src)| {
let short = name.strip_prefix("$std.").unwrap_or(name);
analysis.db.add(format!("<std/{short}>"), src)
})
.collect();
let extra_std = options.tests.then(|| {
let (name, src) = nova_resolver::STD_TEST_MODULE;
let short = name.strip_prefix("$std.").unwrap_or(name);
let file = analysis.db.add(format!("<std/{short}>"), src);
(nova_resolver::STD_TEST_MODULE, file)
});
let module_sources: Vec<ModuleSource> = modules
.iter()
.map(|m| ModuleSource {
name: m.name.clone(),
file: &m.ast,
imports: m.imports.clone(),
text: analysis.db.get_source(m.file),
package: m.package.map(|p| p.0),
importable: m.importable.clone(),
})
.collect();
let resolved = nova_resolver::resolve_program(&module_sources, &std_files, extra_std);
diagnostics.extend(resolved.diagnostics);
let imports = resolved.imports;
if stop(&diagnostics) {
analysis.diagnostics = shown(diagnostics, ®istry);
analysis.definitions = Some(resolved.definitions);
analysis.file = Some(resolved.file);
return Ok(analysis);
}
let probe = options.probe.as_ref().and_then(|p| {
analysis
.modules
.iter()
.find(|(_, path)| sources.same_file(path, &p.path))
.map(|(file, _)| ProbePoint {
file: *file,
offset: p.offset,
})
});
let checked = nova_typeck::check_with(
&resolved.file,
&resolved.definitions,
&CheckOptions {
probe,
index: options.index,
sources: Some(&analysis.db),
},
);
diagnostics.extend(checked.diagnostics);
let module_only = options.module_only || !runs;
let mut module = checked.module;
if !module_only && !has_error(&diagnostics) {
crate::keep_the_entry_main(&mut module, &resolved.definitions);
if let Err(mir) = nova_mir::lower_module(&module) {
diagnostics.extend(mir);
}
}
if options.keep_going {
diagnostics.retain(|d| !about_a_dropped_name(d, &dropped));
}
analysis.diagnostics = shown(diagnostics, ®istry);
analysis.definitions = Some(resolved.definitions);
analysis.module = Some(module);
analysis.probe = checked.probe;
if let Some(mut index) = checked.index {
index.extend(imports);
analysis.index = Some(index);
}
analysis.file = Some(resolved.file);
Ok(analysis)
}
fn has_error(diagnostics: &[Diagnostic]) -> bool {
diagnostics.iter().any(|d| d.severity == Severity::Error)
}
fn shown(mut diagnostics: Vec<Diagnostic>, registry: &HashSet<FileId>) -> Vec<Diagnostic> {
diagnostics.retain(|d| !a_dependencys_warning(d, registry));
diagnostics
}
fn about_a_dropped_name(d: &Diagnostic, dropped: &[String]) -> bool {
if d.code != "E0001" {
return false;
}
let mut parts = d.message.split('`');
parts.next();
parts
.next()
.is_some_and(|name| dropped.iter().any(|n| n == name))
}