use std::collections::BTreeMap;
use std::sync::Arc;
use brink_analyzer::{AnalysisResult, ExternalCheckSeverity, SymbolMeta, TypePolicy};
use brink_format::DefinitionId;
use brink_ir::{
Diagnostic, DocBlock, FileId, HirFile, ResolutionMap, SymbolIndex, SymbolKind, SymbolManifest,
};
use crate::determinism::LookupSet;
use super::{
DefKey, ProjectInput, SourceFile, effects_query, file_import_scope_query,
inference_index_query, is_source_file, lowered_query, module_map_query, raw_lowered_query,
resolution_index_query, resolve_query, symbol_index_query, type_inference_query,
};
#[derive(Debug, Clone, PartialEq)]
pub struct ResolvedProject {
pub index: Arc<SymbolIndex>,
pub resolutions: ResolutionMap,
}
#[salsa::tracked]
pub(crate) fn resolutions_index_query(
db: &dyn salsa::Database,
project: ProjectInput,
) -> Arc<ResolvedProject> {
let (index, _diags) = symbol_index_query(db, project).clone();
let mut resolutions = ResolutionMap::new();
for file in project.files(db) {
if !is_source_file(file.path(db)) {
continue;
}
let (file_map, _file_diags) = resolve_query(db, project, *file);
resolutions.extend(file_map.iter().cloned());
}
Arc::new(ResolvedProject { index, resolutions })
}
#[salsa::tracked(lru = 4096)]
pub(crate) fn per_file_diagnostics_query(
db: &dyn salsa::Database,
project: ProjectInput,
file: SourceFile,
) -> Arc<Vec<Diagnostic>> {
if !is_source_file(file.path(db)) {
return Arc::new(Vec::new());
}
let file_id = file.file_id(db);
let hir = &lowered_query(db, project, file).hir;
let (file_resolutions, _diags) = resolve_query(db, project, file);
let index = resolution_index_query(db, project);
let opts = project.analysis_options(db);
let is_native = super::file_language(file.path(db)) == super::Language::Native;
let scope = file_import_scope_query(db, project, file);
let mut diagnostics = brink_analyzer::per_file_diagnostics(
file_id,
hir,
file_resolutions,
index,
opts.dialect,
is_native,
opts.host_manifest.as_ref(),
scope,
);
if is_native {
diagnostics.extend(brink_analyzer::native_strict_only_error(
file_id, opts.types,
));
}
Arc::new(diagnostics)
}
#[salsa::tracked(returns(ref))]
pub(crate) fn contributor_diagnostics_query(
db: &dyn salsa::Database,
project: ProjectInput,
) -> Vec<Diagnostic> {
let mut out = Vec::new();
for file in project.files(db) {
if !is_source_file(file.path(db)) {
continue;
}
out.extend(
per_file_diagnostics_query(db, project, *file)
.iter()
.cloned(),
);
}
out
}
#[salsa::tracked(returns(ref))]
pub(crate) fn inline_docs_query(
db: &dyn salsa::Database,
project: ProjectInput,
) -> Arc<BTreeMap<(SymbolKind, String), DocBlock>> {
let manifest_inputs: Vec<(FileId, &SymbolManifest)> = project
.files(db)
.iter()
.filter(|f| is_source_file(f.path(db)))
.map(|f| (f.file_id(db), &lowered_query(db, project, *f).manifest))
.collect();
Arc::new(brink_analyzer::project_inline_docs(&manifest_inputs))
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct ExternalMeta {
pub symbol_meta: BTreeMap<DefinitionId, SymbolMeta>,
pub diagnostics: Vec<Diagnostic>,
}
#[salsa::tracked(returns(ref))]
pub(crate) fn external_meta_query(db: &dyn salsa::Database, project: ProjectInput) -> ExternalMeta {
let (index, _diags) = symbol_index_query(db, project);
let inline_docs = inline_docs_query(db, project);
let opts = project.analysis_options(db);
let (symbol_meta, diagnostics) =
brink_analyzer::external_meta_diagnostics(index, inline_docs, opts);
ExternalMeta {
symbol_meta,
diagnostics,
}
}
#[salsa::tracked]
pub(crate) fn call_site_metas_query(
db: &dyn salsa::Database,
project: ProjectInput,
) -> Arc<BTreeMap<String, SymbolMeta>> {
let (index, _diags) = symbol_index_query(db, project);
let ext = external_meta_query(db, project);
Arc::new(brink_analyzer::call_site_metas(index, &ext.symbol_meta))
}
#[salsa::tracked(lru = 4096)]
pub(crate) fn value_meta_query(
db: &dyn salsa::Database,
project: ProjectInput,
file: SourceFile,
) -> Arc<BTreeMap<DefinitionId, SymbolMeta>> {
let hir = &lowered_query(db, project, file).hir;
let index = inference_index_query(db, project);
let inline_docs = inline_docs_query(db, project);
Arc::new(brink_analyzer::file_value_meta(
file.file_id(db),
hir,
index,
inline_docs,
))
}
#[salsa::tracked(lru = 4096)]
pub(crate) fn call_site_diagnostics_query(
db: &dyn salsa::Database,
project: ProjectInput,
file: SourceFile,
) -> Arc<Vec<Diagnostic>> {
if project.analysis_options(db).external_check == ExternalCheckSeverity::Off {
return Arc::new(Vec::new());
}
let metas = call_site_metas_query(db, project);
let hir = &lowered_query(db, project, file).hir;
Arc::new(brink_analyzer::file_call_site_diagnostics(
file.file_id(db),
hir,
&metas,
))
}
#[salsa::tracked(lru = 4096)]
pub(crate) fn effects_assertion_diagnostics_query(
db: &dyn salsa::Database,
project: ProjectInput,
file: SourceFile,
) -> Arc<Vec<Diagnostic>> {
if project.analysis_options(db).dialect != brink_analyzer::Dialect::Brink {
return Arc::new(Vec::new());
}
let file_id = file.file_id(db);
let hir = &lowered_query(db, project, file).hir;
let index = resolution_index_query(db, project);
let def_ids = brink_analyzer::effects_assertion_defs(hir, index, file_id);
if def_ids.is_empty() {
return Arc::new(Vec::new());
}
let mut rows = BTreeMap::new();
for id in def_ids {
if let Some(row) = effects_query(db, project, DefKey::new(db, id)) {
rows.insert(id, (*row).clone());
}
}
let (module_map, _module_diags) = module_map_query(db, project);
let file_module = module_map
.get(&file_id)
.filter(|m| m.declared)
.map(|m| m.name.clone());
let scope = brink_analyzer::ImportScope::new(file_module, &hir.imports);
Arc::new(brink_analyzer::effects_assertion_diagnostics(
file_id, hir, index, &scope, &rows,
))
}
#[salsa::tracked(lru = 4096)]
pub(crate) fn await_purity_diagnostics_query(
db: &dyn salsa::Database,
project: ProjectInput,
file: SourceFile,
) -> Arc<Vec<Diagnostic>> {
if project.analysis_options(db).dialect != brink_analyzer::Dialect::Brink {
return Arc::new(Vec::new());
}
let file_id = file.file_id(db);
let hir = &lowered_query(db, project, file).hir;
if !brink_analyzer::hir_has_await(hir) {
return Arc::new(Vec::new());
}
let (file_resolutions, _diags) = resolve_query(db, project, file);
let index = resolution_index_query(db, project);
let callee_defs = brink_analyzer::await_condition_callees(file_id, hir, file_resolutions);
let mut rows = BTreeMap::new();
for id in callee_defs {
if let Some(row) = effects_query(db, project, DefKey::new(db, id)) {
rows.insert(id, (*row).clone());
}
}
Arc::new(brink_analyzer::await_purity_diagnostics(
file_id,
hir,
index,
file_resolutions,
&rows,
))
}
fn expected_conventions_module(db: &dyn salsa::Database, project: ProjectInput) -> Option<String> {
let opts = project.analysis_options(db);
let pointer = opts.conventions.as_deref()?;
if !brink_analyzer::is_path_shaped_conventions_pointer(pointer) {
return None;
}
Some(crate::modules::native_module_path(
&crate::modules::conventions_pointer_key(project.native_root(db).as_deref(), pointer),
))
}
#[salsa::tracked(lru = 4096)]
pub(crate) fn conventions_confinement_diagnostics_query(
db: &dyn salsa::Database,
project: ProjectInput,
file: SourceFile,
) -> Arc<Vec<Diagnostic>> {
let hir = &lowered_query(db, project, file).hir;
if hir.claim_handlers.is_empty() {
return Arc::new(Vec::new());
}
let file_id = file.file_id(db);
let (module_map, _module_diags) = module_map_query(db, project);
if module_map
.get(&file_id)
.is_some_and(|m| brink_ir::symbols::is_reserved_root_module(&m.name))
{
return Arc::new(Vec::new());
}
let opts = project.analysis_options(db);
let Some(pointer) = opts.conventions.as_deref() else {
return Arc::new(brink_analyzer::conventions_unconfigured_diagnostics(
file_id, hir,
));
};
let Some(expected_module) = expected_conventions_module(db, project) else {
return Arc::new(Vec::new());
};
let Some(this_module) = module_map.get(&file_id).map(|m| m.name.as_str()) else {
return Arc::new(Vec::new());
};
if !module_map.values().any(|m| m.name == expected_module) {
tracing::warn!(
"[project] conventions = \"{pointer}\" does not match any file in the project \
(expected module `{expected_module}`) — conventions-module confinement (E169) \
is skipped until this is fixed"
);
return Arc::new(
brink_analyzer::conventions_pointer_unresolvable_diagnostics(file_id, hir, pointer),
);
}
let is_conventions_module = this_module == expected_module;
Arc::new(brink_analyzer::conventions_module_diagnostics(
file_id,
hir,
is_conventions_module,
pointer,
))
}
#[salsa::tracked(returns(ref))]
pub(crate) fn external_claim_handlers_query(
db: &dyn salsa::Database,
project: ProjectInput,
) -> Arc<Option<(FileId, Vec<brink_ir::ClaimHandlerDecl>)>> {
let opts = project.analysis_options(db);
let Some(pointer) = opts.conventions.as_deref() else {
return Arc::new(None);
};
let Some(expected_module) = expected_conventions_module(db, project) else {
return Arc::new(None);
};
let (module_map, _module_diags) = module_map_query(db, project);
let Some(conventions_file) = project
.files(db)
.iter()
.filter(|f| {
module_map
.get(&f.file_id(db))
.is_some_and(|m| m.name == expected_module)
})
.min_by_key(|f| f.path(db).clone())
.copied()
else {
tracing::warn!(
"[project] conventions = \"{pointer}\" does not match any file in the project \
(expected module `{expected_module}`) — cross-file claiming is skipped until \
this is fixed"
);
return Arc::new(None);
};
let hir = &raw_lowered_query(db, conventions_file).hir;
Arc::new(Some((
conventions_file.file_id(db),
hir.claim_handlers.clone(),
)))
}
#[salsa::tracked(returns(ref))]
pub(crate) fn import_closure_query(
db: &dyn salsa::Database,
project: ProjectInput,
entry: SourceFile,
) -> Arc<Vec<SourceFile>> {
let (module_map, _module_diags) = module_map_query(db, project);
let mut files_by_path: Vec<SourceFile> = project.files(db).clone();
files_by_path.sort_by_key(|f| f.path(db).clone());
let mut by_name: BTreeMap<&str, SourceFile> = BTreeMap::new();
for f in &files_by_path {
if let Some(m) = module_map.get(&f.file_id(db)) {
by_name.entry(m.name.as_str()).or_insert(*f);
}
}
let mut seen = std::collections::BTreeSet::new();
seen.insert(entry.file_id(db));
let mut stack = vec![entry];
let mut closure = Vec::new();
while let Some(file) = stack.pop() {
closure.push(file);
let hir = &lowered_query(db, project, file).hir;
for import in &hir.imports {
if let Some(target) = by_name.get(import.module.as_str()).copied()
&& seen.insert(target.file_id(db))
{
stack.push(target);
}
}
}
closure.sort_by_key(|f| f.path(db).clone());
Arc::new(closure)
}
#[salsa::tracked(returns(ref))]
pub(crate) fn conventions_projection_query(
db: &dyn salsa::Database,
project: ProjectInput,
) -> Arc<brink_ir::ConventionsProjection> {
let opts = project.analysis_options(db);
let Some(pointer) = opts.conventions.as_deref() else {
return Arc::new(brink_ir::ConventionsProjection::default());
};
let Some(expected_module) = expected_conventions_module(db, project) else {
return Arc::new(brink_ir::ConventionsProjection::default());
};
let (module_map, _module_diags) = module_map_query(db, project);
let Some(conventions_file) = project
.files(db)
.iter()
.filter(|f| {
module_map
.get(&f.file_id(db))
.is_some_and(|m| m.name == expected_module)
})
.min_by_key(|f| f.path(db).clone())
else {
tracing::warn!(
"[project] conventions = \"{pointer}\" does not match any file in the project \
(expected module `{expected_module}`) — the conventions projection is empty \
until this is fixed"
);
return Arc::new(brink_ir::ConventionsProjection::default());
};
let closure = import_closure_query(db, project, *conventions_file);
let mut structs: BTreeMap<String, Vec<brink_ir::ConventionAttachField>> = BTreeMap::new();
for file in closure.iter() {
let hir = &lowered_query(db, project, *file).hir;
for s in &hir.structs {
structs.entry(s.name.text.clone()).or_insert_with(|| {
s.fields
.iter()
.map(|f| brink_ir::ConventionAttachField {
name: f.name.text.clone(),
ty: brink_ir::SchemaTypeShape::from(&f.ty),
})
.collect()
});
}
}
let hir = &lowered_query(db, project, *conventions_file).hir;
let projection = brink_ir::ConventionsProjection::from_decls(
&hir.claim_handlers,
&hir.dispatch_handlers,
&structs,
);
for entry in &projection.entries {
if let Some(brink_ir::ConventionAttachSchema::Unresolved(name)) = &entry.attach {
tracing::warn!(
"`@[convention(…, attach = {name})]` on `{}` does not resolve to any struct \
declared in the conventions module `{expected_module}` or its import closure \
— the projection carries this attach clause as `Unresolved` rather than \
dropping it",
entry.name.text
);
}
}
Arc::new(projection)
}
#[salsa::tracked(lru = 4096)]
pub(crate) fn comparator_contract_diagnostics_query(
db: &dyn salsa::Database,
project: ProjectInput,
file: SourceFile,
) -> Arc<Vec<Diagnostic>> {
if project.analysis_options(db).dialect != brink_analyzer::Dialect::Brink {
return Arc::new(Vec::new());
}
let file_id = file.file_id(db);
let hir = &lowered_query(db, project, file).hir;
if !brink_analyzer::hir_has_comparator_site(hir) {
return Arc::new(Vec::new());
}
let (file_resolutions, _diags) = resolve_query(db, project, file);
let index = resolution_index_query(db, project);
let callee_defs = brink_analyzer::comparator_callees(file_id, hir, index, file_resolutions);
let mut rows = BTreeMap::new();
for id in callee_defs {
if let Some(row) = effects_query(db, project, DefKey::new(db, id)) {
rows.insert(id, (*row).clone());
}
}
Arc::new(brink_analyzer::comparator_contract_diagnostics(
file_id,
hir,
index,
file_resolutions,
&rows,
))
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct WholeProjectDiagnostics {
pub diagnostics: Vec<Diagnostic>,
pub symbol_meta: BTreeMap<DefinitionId, SymbolMeta>,
}
fn for_each_source_file(
db: &dyn salsa::Database,
project: ProjectInput,
mut per_file: impl FnMut(SourceFile),
) {
for file in project.files(db) {
if is_source_file(file.path(db)) {
per_file(*file);
}
}
}
#[salsa::tracked(returns(ref))]
pub(crate) fn whole_project_diagnostics_query(
db: &dyn salsa::Database,
project: ProjectInput,
) -> WholeProjectDiagnostics {
let opts = project.analysis_options(db);
let resolved = resolutions_index_query(db, project);
let hir_refs: Vec<(FileId, &HirFile)> = project
.files(db)
.iter()
.filter(|f| is_source_file(f.path(db)))
.map(|f| (f.file_id(db), &lowered_query(db, project, *f).hir))
.collect();
let mut diagnostics =
brink_analyzer::module_diagnostics(&hir_refs, &resolved.index, &resolved.resolutions);
if opts.type_policy() == TypePolicy::Strict {
let strict_inference = (opts.dialect == brink_analyzer::Dialect::Brink)
.then(|| type_inference_query(db, project).as_ref());
let inline_docs = inline_docs_query(db, project);
diagnostics.extend(brink_analyzer::strict_diagnostics(
&hir_refs,
&resolved.index,
&resolved.resolutions,
opts,
super::project_is_all_native(db, project),
strict_inference,
inline_docs,
));
}
let ext = external_meta_query(db, project);
diagnostics.extend(ext.diagnostics.iter().cloned());
let mut symbol_meta = ext.symbol_meta.clone();
for_each_source_file(db, project, |file| {
symbol_meta.extend(
value_meta_query(db, project, file)
.iter()
.map(|(k, v)| (*k, v.clone())),
);
});
for_each_source_file(db, project, |file| {
diagnostics.extend(
call_site_diagnostics_query(db, project, file)
.iter()
.cloned(),
);
});
for_each_source_file(db, project, |file| {
diagnostics.extend(
effects_assertion_diagnostics_query(db, project, file)
.iter()
.cloned(),
);
});
for_each_source_file(db, project, |file| {
diagnostics.extend(
await_purity_diagnostics_query(db, project, file)
.iter()
.cloned(),
);
});
for_each_source_file(db, project, |file| {
diagnostics.extend(
comparator_contract_diagnostics_query(db, project, file)
.iter()
.cloned(),
);
});
for_each_source_file(db, project, |file| {
diagnostics.extend(
conventions_confinement_diagnostics_query(db, project, file)
.iter()
.cloned(),
);
});
for file in project.files(db) {
let hir = &lowered_query(db, project, *file).hir;
diagnostics.extend(brink_analyzer::no_world_reads_diagnostics(
file.file_id(db),
hir,
&hir_refs,
&resolved.index,
&resolved.resolutions,
&symbol_meta,
));
}
diagnostics.extend(
ufcs_resolution_query(db, project)
.diagnostics
.iter()
.cloned(),
);
WholeProjectDiagnostics {
diagnostics,
symbol_meta,
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct UfcsResolution {
pub table: brink_ir::lir::UfcsLookup,
pub diagnostics: Vec<Diagnostic>,
}
#[salsa::tracked(returns(ref))]
pub(crate) fn ufcs_resolution_query(
db: &dyn salsa::Database,
project: ProjectInput,
) -> UfcsResolution {
let resolved = resolutions_index_query(db, project);
let hir_refs: Vec<(FileId, &HirFile)> = project
.files(db)
.iter()
.filter(|f| is_source_file(f.path(db)))
.map(|f| (f.file_id(db), &lowered_query(db, project, *f).hir))
.collect();
if !hir_refs
.iter()
.any(|&(_, hir)| brink_analyzer::project_has_ufcs_call(hir))
{
return UfcsResolution {
table: brink_ir::lir::UfcsLookup::new(),
diagnostics: Vec::new(),
};
}
let inference = type_inference_query(db, project);
let (table, diagnostics) = brink_analyzer::ufcs_resolution(
&hir_refs,
&resolved.index,
&resolved.resolutions,
inference.as_ref(),
);
UfcsResolution {
table: brink_analyzer::ufcs_lir_lookup(&table),
diagnostics,
}
}
#[salsa::tracked(returns(ref))]
pub(crate) fn coalesce_types_query(
db: &dyn salsa::Database,
project: ProjectInput,
) -> brink_ir::lir::CoalesceLookup {
let resolved = resolutions_index_query(db, project);
let hir_refs: Vec<(FileId, &HirFile)> = project
.files(db)
.iter()
.filter(|f| is_source_file(f.path(db)))
.map(|f| (f.file_id(db), &lowered_query(db, project, *f).hir))
.collect();
if !hir_refs
.iter()
.any(|&(_, hir)| brink_analyzer::project_has_coalesce(hir))
{
return brink_ir::lir::CoalesceLookup::new();
}
let inference = type_inference_query(db, project);
let (table, _strict_only_diagnostics) = brink_analyzer::coalesce_types(
&hir_refs,
&resolved.index,
inference.as_ref(),
&resolved.resolutions,
);
brink_analyzer::coalesce_lir_lookup(&table)
}
#[salsa::tracked(returns(ref))]
pub(crate) fn analysis_diagnostics_query(
db: &dyn salsa::Database,
project: ProjectInput,
) -> Vec<Diagnostic> {
let (_index, mut diagnostics) = symbol_index_query(db, project).clone();
for file in project.files(db) {
if !is_source_file(file.path(db)) {
continue;
}
let (_file_map, file_diags) = resolve_query(db, project, *file);
diagnostics.extend(file_diags.iter().cloned());
}
diagnostics.extend(contributor_diagnostics_query(db, project).iter().cloned());
diagnostics.extend(
whole_project_diagnostics_query(db, project)
.diagnostics
.iter()
.cloned(),
);
diagnostics
}
#[salsa::tracked(returns(ref))]
pub(crate) fn analysis_query(db: &dyn salsa::Database, project: ProjectInput) -> AnalysisResult {
let resolved = resolutions_index_query(db, project);
let diagnostics = analysis_diagnostics_query(db, project).clone();
let whole = whole_project_diagnostics_query(db, project);
AnalysisResult {
index: Arc::clone(&resolved.index),
resolutions: resolved.resolutions.clone(),
diagnostics,
symbol_meta: whole.symbol_meta.clone(),
}
}
#[salsa::tracked(returns(ref), lru = 4096)]
pub(crate) fn diagnostics_query(
db: &dyn salsa::Database,
project: ProjectInput,
file: SourceFile,
) -> Vec<Diagnostic> {
if !is_source_file(file.path(db)) {
return Vec::new();
}
let file_id = file.file_id(db);
let mut out = lowered_query(db, project, file).diagnostics.clone();
out.extend(
analysis_diagnostics_query(db, project)
.iter()
.filter(|d| d.file == file_id)
.cloned(),
);
out
}
#[salsa::tracked]
pub(crate) fn has_errors_query(db: &dyn salsa::Database, project: ProjectInput) -> bool {
let files = project.files(db);
let Some(entry) = project.entry(db) else {
return false;
};
let disable_all = files
.iter()
.find(|f| f.file_id(db) == entry)
.is_some_and(|f| super::suppressions_query(db, *f).disable_all);
let inputs: Vec<super::FileDiagnostics<'_>> = files
.iter()
.filter(|f| is_source_file(f.path(db)))
.map(|f| super::FileDiagnostics {
file: f.file_id(db),
source: f.text(db),
suppressions: super::suppressions_query(db, *f),
lowering: &lowered_query(db, project, *f).diagnostics,
})
.collect();
let opts = project.analysis_options(db);
let types = opts.type_policy();
let diagnostics = analysis_diagnostics_query(db, project);
let (errors, _warnings) =
super::partition_diagnostics(&inputs, diagnostics, disable_all, types, &opts.lints);
!errors.is_empty()
}
#[salsa::tracked]
pub(crate) fn has_errors_in_closure_query(db: &dyn salsa::Database, project: ProjectInput) -> bool {
let Some(entry) = project.entry(db) else {
return false;
};
let files = project.files(db);
let closure: LookupSet<FileId> = super::compilation_closure_files(db, project)
.into_iter()
.collect();
let disable_all = files
.iter()
.find(|f| f.file_id(db) == entry)
.is_some_and(|f| super::suppressions_query(db, *f).disable_all);
let inputs: Vec<super::FileDiagnostics<'_>> = files
.iter()
.filter(|f| closure.contains(&f.file_id(db)))
.map(|f| super::FileDiagnostics {
file: f.file_id(db),
source: f.text(db),
suppressions: super::suppressions_query(db, *f),
lowering: &lowered_query(db, project, *f).diagnostics,
})
.collect();
let opts = project.analysis_options(db);
let types = opts.type_policy();
let diagnostics: Vec<Diagnostic> = analysis_diagnostics_query(db, project)
.iter()
.filter(|d| closure.contains(&d.file))
.cloned()
.collect();
let (errors, _warnings) =
super::partition_diagnostics(&inputs, &diagnostics, disable_all, types, &opts.lints);
!errors.is_empty()
}
#[salsa::interned]
pub(crate) struct MemberSet<'db> {
#[returns(ref)]
pub members: Vec<FileId>,
}
fn members_all_native(db: &dyn salsa::Database, project: ProjectInput, members: &[FileId]) -> bool {
let mut any = false;
for file in project.files(db) {
let id = file.file_id(db);
if members.binary_search(&id).is_err() {
continue;
}
let path = file.path(db);
if !is_source_file(path) {
continue;
}
match super::file_language(path) {
super::Language::Native => any = true,
super::Language::Ink => return false,
}
}
any
}
#[salsa::tracked(returns(ref))]
pub(crate) fn subset_analysis_query<'db>(
db: &'db dyn salsa::Database,
project: ProjectInput,
set: MemberSet<'db>,
) -> AnalysisResult {
let members = set.members(db);
let opts = project.analysis_options(db);
let (module_map, module_diags) = module_map_query(db, project);
let is_native = members_all_native(db, project, members);
let lowered: Vec<(FileId, &Arc<super::LoweredFile>)> = project
.files(db)
.iter()
.filter(|f| is_source_file(f.path(db)))
.filter(|f| members.binary_search(&f.file_id(db)).is_ok())
.map(|f| (f.file_id(db), lowered_query(db, project, *f)))
.collect();
let files: Vec<(FileId, &brink_ir::HirFile, &brink_ir::SymbolManifest)> = lowered
.iter()
.map(|(id, l)| (*id, &l.hir, &l.manifest))
.collect();
let manifest_inputs: Vec<(FileId, &brink_ir::SymbolManifest)> =
files.iter().map(|&(id, _hir, m)| (id, m)).collect();
let (index, mut diagnostics) = brink_analyzer::symbol_index_with_modules(
&manifest_inputs,
module_map,
opts.dialect,
is_native,
);
diagnostics.extend(
module_diags
.iter()
.filter(|d| members.binary_search(&d.file).is_ok())
.cloned(),
);
let mut resolutions = brink_ir::ResolutionMap::new();
let mut scopes: BTreeMap<FileId, brink_analyzer::ImportScope> = BTreeMap::new();
for &(file_id, hir, manifest) in &files {
let declared_module = match module_map.get(&file_id) {
Some(resolved) => resolved.declared.then(|| resolved.name.clone()),
None => hir.module.as_ref().map(|m| m.name.clone()),
};
let scope = brink_analyzer::ImportScope::new(declared_module, &hir.imports);
let (file_map, file_diags) = brink_analyzer::resolve(file_id, manifest, &index, &scope);
resolutions.extend(Arc::unwrap_or_clone(file_map));
diagnostics.extend(file_diags);
scopes.insert(file_id, scope);
}
let hir_files: Vec<(FileId, &brink_ir::HirFile)> =
files.iter().map(|&(id, hir, _)| (id, hir)).collect();
diagnostics.extend(brink_analyzer::conventions_confinement_diagnostics(
&hir_files,
module_map,
opts.conventions.as_deref(),
));
brink_analyzer::finish_analysis(
&files,
index,
resolutions,
diagnostics,
opts,
is_native,
None,
&scopes,
)
}