use alloc::vec::Vec;
use hir_lang::{
DefId, Hir, HirError, ItemId, ItemKind, Name, NodeRef, Pat, PatId, PathId, Res, UnitId, Vis,
};
use intern_lang::Lookup;
use crate::{
collect::{Bases, collect},
diag::{DiagKind, Diagnostic},
env::{Env, NoEnv},
error::{Budget, ResolveError},
imports::resolve_imports,
index::{Index, RawRef, Target, UnitInput},
members::{Members, finish_deferred},
model::{Fold, Model, ScopeKind, Unit, ix},
pass::{Pass, UnitOut, entry_gate},
policy::{Namespace, Policy},
suggest::Suggester,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ClassMember {
pub name: Name,
pub namespace: Namespace,
pub res: Res,
pub vis: Vis,
pub mixin: Option<DefId>,
}
#[derive(Clone, Debug)]
pub struct ResolvedUnit {
name: Option<Name>,
hir: Hir,
members: Vec<(ItemId, Vec<ClassMember>)>,
ident_matches: Vec<PatId>,
}
impl ResolvedUnit {
#[must_use]
pub fn id(&self) -> UnitId {
self.hir.unit()
}
#[must_use]
pub fn name(&self) -> Option<Name> {
self.name
}
#[must_use]
pub fn hir(&self) -> &Hir {
&self.hir
}
#[must_use]
pub fn into_hir(self) -> Hir {
self.hir
}
#[must_use]
pub fn members(&self, class: ItemId) -> Option<&[ClassMember]> {
let i = self
.members
.binary_search_by_key(&class, |(c, _)| *c)
.ok()?;
self.members.get(i).map(|(_, m)| m.as_slice())
}
#[must_use]
pub fn ident_binds(&self, pat: PatId) -> Option<bool> {
matches!(self.hir.get_pat(pat), Some(Pat::Ident { .. }))
.then(|| self.ident_matches.binary_search(&pat).is_err())
}
}
#[derive(Clone, Debug)]
pub struct Resolution {
units: Vec<ResolvedUnit>,
diagnostics: Vec<Diagnostic>,
index: Index,
}
impl Resolution {
#[must_use]
pub fn units(&self) -> &[ResolvedUnit] {
&self.units
}
#[must_use]
pub fn unit(&self, id: UnitId) -> Option<&ResolvedUnit> {
self.units.iter().find(|u| u.id() == id)
}
#[must_use]
pub fn hir(&self, id: UnitId) -> Option<&Hir> {
self.unit(id).map(ResolvedUnit::hir)
}
#[must_use]
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
#[must_use]
pub fn is_clean(&self) -> bool {
self.diagnostics.is_empty()
}
#[must_use]
pub fn index(&self) -> &Index {
&self.index
}
#[must_use]
pub fn into_parts(self) -> (Vec<ResolvedUnit>, Vec<Diagnostic>, Index) {
(self.units, self.diagnostics, self.index)
}
}
#[derive(Clone, Copy)]
pub struct Resolver<'e> {
policy: Policy,
env: &'e dyn Env,
budget: Budget,
}
impl core::fmt::Debug for Resolver<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Resolver")
.field("policy", &self.policy)
.field("budget", &self.budget)
.finish_non_exhaustive()
}
}
impl Resolver<'static> {
#[must_use]
pub fn new(policy: Policy) -> Self {
Self {
policy,
env: &NoEnv,
budget: Budget::default(),
}
}
}
impl Resolver<'_> {
#[must_use]
pub fn with_env(self, env: &dyn Env) -> Resolver<'_> {
Resolver {
policy: self.policy,
env,
budget: self.budget,
}
}
#[must_use]
pub fn with_budget(mut self, budget: Budget) -> Self {
self.budget = budget;
self
}
pub fn resolve<L: Lookup>(self, hir: Hir, names: &L) -> Result<Resolution, ResolveError> {
let mut program = Program::new(self.policy)
.with_env(self.env)
.with_budget(self.budget);
program.add_unit(None, hir);
program.resolve(names)
}
}
pub struct Program<'e> {
policy: Policy,
env: &'e dyn Env,
budget: Budget,
units: Vec<(Option<Name>, u32, Hir)>,
}
impl core::fmt::Debug for Program<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Program")
.field("policy", &self.policy)
.field("budget", &self.budget)
.field("units", &self.units.len())
.finish_non_exhaustive()
}
}
impl Program<'static> {
#[must_use]
pub fn new(policy: Policy) -> Self {
Self {
policy,
env: &NoEnv,
budget: Budget::default(),
units: Vec::new(),
}
}
}
impl Program<'_> {
#[must_use]
pub fn with_env(self, env: &dyn Env) -> Program<'_> {
Program {
policy: self.policy,
env,
budget: self.budget,
units: self.units,
}
}
#[must_use]
pub fn with_budget(mut self, budget: Budget) -> Self {
self.budget = budget;
self
}
pub fn add_unit(&mut self, name: Option<Name>, hir: Hir) {
self.units.push((name, 0, hir));
}
pub fn add_unit_in(&mut self, package: u32, name: Option<Name>, hir: Hir) {
self.units.push((name, package, hir));
}
#[must_use]
pub fn len(&self) -> usize {
self.units.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.units.is_empty()
}
pub fn resolve<L: Lookup>(self, names: &L) -> Result<Resolution, ResolveError> {
let mut unit_ix: Vec<(UnitId, u32)> = self
.units
.iter()
.enumerate()
.map(|(i, (_, _, h))| (h.unit(), ix(i)))
.collect();
unit_ix.sort();
if let Some(w) = unit_ix
.windows(2)
.find(|w| w.first().map(|x| x.0) == w.get(1).map(|x| x.0))
{
if let Some(&(unit, _)) = w.first() {
return Err(ResolveError::DuplicateUnit { unit });
}
}
let mut m = Model {
policy: self.policy,
fold: Fold::build(&self.policy, names),
env: self.env,
units: Vec::with_capacity(self.units.len()),
unit_ix,
roots: Vec::new(),
scopes: Vec::new(),
bindings: Vec::new(),
imports: Vec::new(),
sums: Vec::new(),
diags: Vec::new(),
};
for (u, (name, package, hir)) in self.units.into_iter().enumerate() {
let bases = Bases {
unit: ix(u),
scope: ix(m.scopes.len()),
binding: ix(m.bindings.len()),
import: ix(m.imports.len()),
sum: ix(m.sums.len()),
};
let c = collect(&hir, &m.policy, &m.fold, bases);
m.scopes.extend(c.scopes);
m.bindings.extend(c.bindings);
m.imports.extend(c.imports);
m.sums.extend(c.sums);
if let Some(name) = name {
let key = m.fold.key_ns(&m.policy, Namespace::Module, name);
m.roots.push((key, ix(u)));
}
m.units.push(Unit {
id: hir.unit(),
name,
package,
hir,
root_scope: c.root_scope,
item_scope: c.item_scope,
block_scope: c.block_scope,
sum_table: c.sum_table,
import_of: c.import_of,
after_decl: Vec::new(),
});
for (kind, span, node) in c.diags {
m.report(ix(u), kind, span, Some(node));
}
}
m.roots.sort_by_key(|(n, u)| (*n, *u));
m.roots.dedup_by_key(|(n, _)| *n);
let mut sugg = Suggester::new(self.budget.suggestion_cells());
resolve_imports(&mut m, names, &mut sugg, self.budget.glob_bindings())?;
let mut gated: Vec<Vec<(u32, u32, u32)>> = m.units.iter().map(|_| Vec::new()).collect();
for (s, scope) in m.scopes.iter().enumerate() {
for (e, entry) in scope.table.iter().enumerate() {
if let Some(item) = entry_gate(&m, entry.state) {
if let Some(list) = gated.get_mut(scope.unit as usize) {
list.push((ix(item.index()), ix(s), ix(e)));
}
}
}
}
for (unit, mut list) in m.units.iter_mut().zip(gated) {
list.sort_unstable();
unit.after_decl = list;
}
let mut outs: Vec<UnitOut> = Vec::with_capacity(m.units.len());
for u in 0..m.units.len() {
if let Some(pass) = Pass::new(&m, names, &mut sugg, ix(u)) {
outs.push(pass.run());
}
}
let mut members = Members::build(&mut m, &outs, self.budget.member_steps())?;
finish_deferred(&m, &mut members, &mut outs)?;
for (unit, out) in m.units.iter_mut().zip(outs.iter_mut()) {
apply(unit, out);
}
Ok(assemble(m, outs, &members))
}
}
fn apply(unit: &mut Unit, out: &mut UnitOut) {
let id = unit.id;
for i in 0..out.plan.len() {
let Some((res, unresolved)) = out.plan.get(i).copied().flatten() else {
continue;
};
let Some(p) = PathId::from_index(i) else {
continue;
};
let Err(error) = unit.hir.resolve_partial(p, res, unresolved) else {
continue;
};
let _always_ok = unit.hir.resolve(p, Res::Err);
if let Some(slot) = out.plan.get_mut(i) {
*slot = Some((Res::Err, 0));
}
let span = unit
.hir
.list(unit.hir.path(p).segments)
.first()
.map_or(unit.hir.origin(NodeRef::Path(p)).span, |s| s.origin.span);
let kind = match error {
HirError::OutOfScope { binder, .. } | HirError::NotCapturable { binder, .. } => {
let name = unit.hir.binder(binder).map(|b| b.name);
match name {
Some(name) => DiagKind::CannotCapture { name, binder },
None => DiagKind::Rejected { error },
}
}
_ => DiagKind::Rejected { error },
};
out.diag(id, p, kind, span, false);
}
}
fn assemble(m: Model<'_>, outs: Vec<UnitOut>, members: &Members) -> Resolution {
let mut diagnostics = m.diags.clone();
for out in &outs {
diagnostics.extend(out.diags.iter().copied());
}
let order: Vec<UnitId> = m.units.iter().map(|u| u.id).collect();
let pos = |id: UnitId| order.iter().position(|u| *u == id).unwrap_or(usize::MAX);
diagnostics.sort_by_key(|d| (pos(d.unit), d.span.start(), d.span.end()));
let mut inputs_refs: Vec<(Vec<RawRef>, Vec<hir_lang::BinderId>, Vec<ItemId>)> = Vec::new();
for (u, out) in outs.iter().enumerate() {
let Some(unit) = m.units.get(u) else { continue };
let mut refs = Vec::with_capacity(out.segs.len());
for s in &out.segs {
if unit.hir.path(s.path).res == Res::Err {
continue;
}
let Some(target) = target_of(unit, s.res) else {
continue;
};
let via = m
.imports
.get(s.via as usize)
.filter(|imp| is_alias(&m, imp.unit, imp.item))
.and_then(|imp| {
m.units
.get(imp.unit as usize)
.map(|x| Target::Import(x.id, imp.item))
});
refs.push(RawRef {
path: s.path,
seg: s.seg,
target,
via,
});
}
let dead: Vec<hir_lang::BinderId> = out
.ident_matches
.iter()
.filter_map(|p| match unit.hir.get_pat(*p) {
Some(Pat::Ident { binder, .. }) => Some(*binder),
_ => None,
})
.collect();
let aliases: Vec<ItemId> = m
.imports
.iter()
.filter(|imp| imp.unit == ix(u) && is_alias(&m, imp.unit, imp.item))
.map(|imp| imp.item)
.collect();
inputs_refs.push((refs, dead, aliases));
}
let inputs: Vec<UnitInput<'_>> = m
.units
.iter()
.zip(inputs_refs)
.map(|(unit, (refs, dead_binders, mut aliases))| {
aliases.sort();
UnitInput {
hir: &unit.hir,
refs,
dead_binders,
aliases,
}
})
.collect();
let index = Index::build(&inputs, &m.fold);
drop(inputs);
let mut class_members: Vec<Vec<(ItemId, Vec<ClassMember>)>> =
m.units.iter().map(|_| Vec::new()).collect();
for (s, scope) in m.scopes.iter().enumerate() {
let ScopeKind::Type(item, _) = scope.kind else {
continue;
};
let list: Vec<ClassMember> = members
.tables
.get(s)
.into_iter()
.flatten()
.filter_map(|mem| {
let b = m.bindings.get(mem.binding as usize)?;
let mixin = match mem.from {
Some(Res::Def(d)) => Some(d),
_ => None,
};
Some(ClassMember {
name: b.name,
namespace: Namespace::from_index(mem.ns as usize),
res: b.res,
vis: b.vis,
mixin,
})
})
.collect();
if let Some(v) = class_members.get_mut(scope.unit as usize) {
v.push((item, list));
}
}
let mut units = Vec::with_capacity(m.units.len());
for ((unit, out), mut cm) in m.units.into_iter().zip(outs).zip(class_members) {
cm.sort_by_key(|(i, _)| *i);
let mut ident_matches = out.ident_matches;
ident_matches.sort();
ident_matches.dedup();
units.push(ResolvedUnit {
name: unit.name,
hir: unit.hir,
members: cm,
ident_matches,
});
}
Resolution {
units,
diagnostics,
index,
}
}
fn is_alias(m: &Model<'_>, u: u32, item: ItemId) -> bool {
let Some(unit) = m.units.get(u as usize) else {
return false;
};
let it = unit.hir.item(item);
let ItemKind::Import { path, glob: false } = it.kind else {
return false;
};
let last = unit
.hir
.list(unit.hir.path(path).segments)
.last()
.map(|s| s.name);
it.name.is_some() && it.name != last
}
fn target_of(unit: &Unit, res: Res) -> Option<Target> {
match res {
Res::Def(d) => Some(Target::Def(d)),
Res::Local(b) => Some(Target::Local(unit.id, b)),
Res::Extern(s) => Some(Target::Extern(s)),
Res::Prim(_) | Res::Err | Res::Unresolved => None,
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use hir_lang::{BinderKind, Expr, Stmt};
use super::*;
use crate::model::fixture::*;
#[test]
fn test_apply_turns_refused_resolutions_into_diagnostics() {
let mut f = Fixture::new();
let (x, g, fname) = (f.name("x"), f.name("g"), f.name("f"));
let bx = f.b.new_binder(x, BinderKind::Local);
let pat = f.b.bind(bx);
let one = f.b.int(1);
let s = f.b.let_stmt(pat, Some(one));
let use_x = f.b.name_expr(x);
let gb = f.b.block(&[], Some(use_x));
let gi = f.b.func(g, &[], gb);
let si = f.b.stmt(Stmt::Item(gi));
let body = f.b.block(&[s, si], None);
let fi = f.b.func(fname, &[], body);
let root = f.b.module(None, &[fi]);
let hir = f.finish(root);
let p = PathId::from_index(0).unwrap();
assert_eq!(*hir.expr(use_x), Expr::Path(p));
let (mut m, mut outs) = walked(hir, &f.names, Policy::new());
outs[0].plan[p.index()] = Some((Res::Local(bx), 0));
outs[0].diagnosed[p.index()] = false;
outs[0].diags.clear();
apply(&mut m.units[0], &mut outs[0]);
assert_eq!(m.units[0].hir.path(p).res, Res::Err);
assert!(matches!(
outs[0].diags[..],
[Diagnostic {
kind: DiagKind::CannotCapture { .. },
..
}]
));
assert!(m.units[0].hir.validate().is_ok());
}
#[test]
fn test_ident_binds_only_for_ident_patterns() {
let mut f = Fixture::new();
let root = f.b.module(None, &[]);
let hir = f.finish(root);
let unit = ResolvedUnit {
name: None,
hir,
members: Vec::new(),
ident_matches: Vec::new(),
};
assert_eq!(unit.ident_binds(PatId::from_index(0).unwrap()), None);
assert!(unit.members(ItemId::from_index(0).unwrap()).is_none());
}
}