use std::collections::BTreeSet;
use rustc_hash::FxHashMap;
use super::checker::TypeChecker;
use super::problems::TypeProblems;
use super::selection::Selection;
use super::ty::{Bound, Type};
use super::typed::TypedTraitImpl;
use crate::core::{BindingId, CoreModule, ItemKind, RustTraitDeclaration, StructuralClause};
use crate::diagnostic::Diagnostic;
use crate::source::Span;
#[derive(Clone)]
pub struct StructuralTrait {
pub binding: BindingId,
pub method: BindingId,
pub parameters: Vec<Type>,
pub result: Type,
pub clause: StructuralClause,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StructuralUse {
pub trait_binding: BindingId,
pub head: BindingId,
}
#[derive(Clone)]
struct ImplBoundUse {
definitions: Vec<BindingId>,
bound: Bound,
implementor: Type,
span: Span,
}
#[derive(Default)]
pub struct StructuralUses {
by_definition: FxHashMap<BindingId, BTreeSet<StructuralUse>>,
through_impls: Vec<ImplBoundUse>,
}
impl StructuralUses {
pub fn of_definitions<'u>(
&'u self,
definitions: impl IntoIterator<Item = &'u BindingId>,
) -> BTreeSet<StructuralUse> {
definitions
.into_iter()
.filter_map(|definition| self.by_definition.get(definition))
.flatten()
.copied()
.collect()
}
fn record(&mut self, definitions: &[BindingId], used: StructuralUse) {
for definition in definitions {
self.by_definition
.entry(*definition)
.or_default()
.insert(used);
}
}
}
impl TypeChecker {
const RECEIVER: &'static str = "self";
pub(super) fn record_structural_traits(&mut self, modules: &[CoreModule]) {
for item in modules.iter().flat_map(|module| &module.items) {
let ItemKind::RustTrait(declaration) = &item.kind else {
continue;
};
if let Err(diagnostic) = self.record_structural_trait(declaration) {
self.diagnostics.push(diagnostic);
}
}
}
fn record_structural_trait(
&mut self,
declaration: &RustTraitDeclaration,
) -> Result<(), Diagnostic> {
let (Some(clause), Some(method)) = (&declaration.structural, declaration.methods.first())
else {
return Ok(());
};
let receiver = self.new_generic(Self::RECEIVER, Vec::new());
let scope = FxHashMap::from_iter([(declaration.self_variable, receiver.id)]);
let Type::Function(_, parameters, result) = self.lower_type(&method.signature, &scope)?
else {
return Ok(());
};
self.structural_traits.push(StructuralTrait {
binding: declaration.binding,
method: method.binding,
parameters: parameters.into_iter().skip(1).collect(),
result: *result,
clause: clause.clone(),
});
Ok(())
}
pub(super) fn shows_trait_help(
&self,
trait_binding: BindingId,
implementor: &Type,
program_type: bool,
) -> bool {
if !self.is_structural_trait(trait_binding) {
return program_type || !self.declares_methods(trait_binding);
}
self.lacks_source_form(implementor)
|| self
.blocking_types(implementor)
.any(|blocking| self.lacks_source_form(blocking))
}
fn blocking_types<'t>(&'t self, implementor: &Type) -> impl Iterator<Item = &'t Type> + 't {
let blocked = match implementor {
Type::Named(binding, _) => match self.structs.get(*binding) {
Some(definition) => definition.blocked.as_slice(),
None => self
.enums
.get(*binding)
.map_or(&[][..], |definition| definition.blocked.as_slice()),
},
_ => &[],
};
blocked.iter().map(|(_, _, ty)| ty)
}
fn lacks_source_form(&self, ty: &Type) -> bool {
let opaque = matches!(ty, Type::Named(binding, _) if self.rust_types.is_opaque(*binding));
opaque
|| matches!(ty, Type::Function(..))
|| ty
.parts()
.into_iter()
.any(|part| self.lacks_source_form(part))
}
pub(super) fn with_replacing_impl(
&self,
diagnostic: Diagnostic,
head: BindingId,
trait_binding: BindingId,
) -> Diagnostic {
let replacing = self
.is_structural_trait(trait_binding)
.then(|| self.written_impl(trait_binding, head))
.flatten();
let Some(replacing) = replacing else {
return diagnostic;
};
let trait_name = self.bindings.get(trait_binding).name();
let head_name = self.bindings.get(head).name();
diagnostic.with_label(TypeProblems::replacing_impl(
replacing.implementor_span,
trait_name,
head_name,
))
}
fn declares_methods(&self, trait_binding: BindingId) -> bool {
self.trait_methods
.values()
.any(|method| method.trait_binding == trait_binding)
}
pub(super) fn is_structural_trait(&self, trait_binding: BindingId) -> bool {
self.structural_traits
.iter()
.any(|structural| structural.binding == trait_binding)
}
pub(super) fn record_structural_use(&mut self, bound: &Bound, implementor: &Type, span: Span) {
let definitions = self.recursive_group.clone();
if self.has_generic_program_impl(bound.binding) {
self.structural_uses.through_impls.push(ImplBoundUse {
definitions: definitions.clone(),
bound: bound.clone(),
implementor: implementor.clone(),
span,
});
}
self.record_use_in(&definitions, bound.binding, implementor, span);
}
fn has_generic_program_impl(&self, trait_binding: BindingId) -> bool {
self.trait_impls.iter().any(|implementation| {
implementation.trait_binding == trait_binding && !implementation.params.is_empty()
})
}
pub(super) fn record_uses_through_impls(&mut self) {
for position in 0..self.structural_uses.through_impls.len() {
let pending = self.structural_uses.through_impls[position].clone();
self.record_requirements(&pending);
}
}
fn record_requirements(&mut self, pending: &ImplBoundUse) {
let implementor = self.unifier.resolve(&pending.implementor);
let Selection::Implemented { requirements, .. } =
self.traits.select(&pending.bound, &implementor)
else {
return;
};
for requirement in requirements {
let required = self.unifier.resolve(&requirement.implementor);
self.record_use_in(
&pending.definitions,
requirement.bound.binding,
&required,
pending.span,
);
self.record_requirements(&ImplBoundUse {
definitions: pending.definitions.clone(),
bound: requirement.bound,
implementor: required,
span: pending.span,
});
}
}
fn record_use_in(
&mut self,
definitions: &[BindingId],
trait_binding: BindingId,
implementor: &Type,
span: Span,
) {
let Type::Named(head, _) = implementor else {
return;
};
if !self.is_structural_trait(trait_binding) {
return;
}
self.report_path_clash(trait_binding, *head, span);
let used = StructuralUse {
trait_binding,
head: *head,
};
self.structural_uses.record(definitions, used);
}
fn report_path_clash(&mut self, trait_binding: BindingId, head: BindingId, span: Span) {
let Some(written) = self.written_impl_of_path(trait_binding, head) else {
return;
};
let (written_binding, written_span) = (written.trait_binding, written.implementor_span);
let clash = StructuralUse {
trait_binding,
head,
};
if !self.reported_path_clashes.insert(clash) {
return;
}
let path = self.rust_trait_paths[&trait_binding].to_string();
let written_trait = self.bindings.get(written_binding).name().to_string();
let trait_name = self.bindings.get(trait_binding).name().to_string();
let implementor = self.bindings.get(head).name().to_string();
let diagnostic = TypeProblems::inferred_impl_clash(
&implementor,
&trait_name,
&written_trait,
&path,
span,
written_span,
);
self.diagnostics.push(diagnostic);
}
fn written_impl_of_path(
&self,
trait_binding: BindingId,
head: BindingId,
) -> Option<&TypedTraitImpl> {
let path = self.rust_trait_paths.get(&trait_binding)?;
self.trait_impls.iter().find(|implementation| {
implementation.trait_binding != trait_binding
&& self.rust_trait_paths.get(&implementation.trait_binding) == Some(path)
&& matches!(implementation.implementor, Type::Named(binding, _) if binding == head)
})
}
}
#[cfg(test)]
mod tests {
use crate::types::TypedSource;
struct UsesFixture {
source: TypedSource,
}
impl UsesFixture {
const SOURCE: &str = r#"
(rust-type out "std::fmt::Formatter<'_>")
(rust-type written "std::fmt::Result")
(rust-trait octal "std::fmt::Octal"
(method write-octal (fn ((ref self) (mut out)) written))
(structural :struct "" :field "" :variant "" :part "" :bare "")
u32)
(def show-octal (rust "{0}" (where ((a octal)) (fn ((ref a)) unit))))
(def copied (rust "{0}.clone()" (where ((a clone)) (fn ((ref a)) a))))
(defstruct mode :owner u32)
(defn main () (show-octal (->mode 1)) (copied (->mode 2)))"#;
fn new() -> Self {
Self {
source: TypedSource::expand(Self::SOURCE),
}
}
fn recorded_traits(&self) -> Vec<String> {
let typed = self.source.typed();
let definitions: Vec<_> = self
.source
.program()
.modules
.iter()
.flat_map(|module| module.definitions().map(|(binding, _)| binding))
.collect();
typed
.structural_uses
.of_definitions(&definitions)
.into_iter()
.map(|used| typed.bindings.get(used.trait_binding).name().to_string())
.collect()
}
}
#[test]
fn only_a_trait_with_a_generic_program_impl_waits_for_its_bounds() {
let source = TypedSource::expand(
r#"
(trait describe (describe-it (fn ((ref self)) string)))
(trait label (label-it (fn ((ref self)) string)))
(impl describe (vec a) (describe-it (v) (pr-str v)))
(impl label i64 (label-it (n) (str n)))
(defn main () (println (describe-it (vec 1)) (label-it 2)))"#,
);
assert_eq!(source.typed().structural_uses.through_impls.len(), 1);
}
#[test]
fn only_a_structural_trait_records_its_uses() {
assert_eq!(UsesFixture::new().recorded_traits(), vec!["octal"]);
}
}