use std::rc::Rc;
use rustc_hash::FxHashMap;
use super::checker::TypeChecker;
use super::enums::PayloadPosition;
use super::problems::TypeProblems;
use super::selection::Selection;
use super::traits::Implementor;
use super::ty::{Bound, GenericId, GenericParam, Scheme, Type};
use super::typed::TypedTraitImpl;
use crate::core::{BindingId, CoreModule, FunctionFieldTraits, ItemKind, StructDerives};
use crate::modules::ModuleSources;
use crate::source::Span;
struct WrittenDerive {
span: Span,
traits: Vec<(BindingId, Span)>,
}
#[derive(Clone, Copy)]
enum DeriveSource {
Inferred,
Listed(Span),
}
#[derive(Clone, Copy)]
enum DefinitionKind {
Struct,
Enum,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum DerivedTrait {
Written(&'static str),
Structural,
}
#[derive(Clone, Copy)]
struct DeriveCandidate {
binding: BindingId,
kind: DefinitionKind,
trait_binding: BindingId,
source: DeriveSource,
}
impl TypeChecker {
pub(super) fn register_derived_traits(&mut self, modules: &[CoreModule]) {
let types = Self::type_arities(modules);
let derivable = self.derivable_traits(modules);
let written = self.written_derives(modules, &derivable);
let mut candidates = self.add_derive_candidates(&types, &derivable, &written);
self.remove_blocked_derives(&mut candidates);
self.record_derives(&candidates, &derivable);
}
fn type_arities(modules: &[CoreModule]) -> Vec<(BindingId, usize, DefinitionKind)> {
modules
.iter()
.flat_map(|module| &module.items)
.filter_map(|item| match &item.kind {
ItemKind::Struct(binding, params, _) => {
Some((*binding, params.len(), DefinitionKind::Struct))
}
ItemKind::Enum(binding, params, ..) => {
Some((*binding, params.len(), DefinitionKind::Enum))
}
_ => None,
})
.collect()
}
fn derivable_traits(&self, modules: &[CoreModule]) -> Vec<(BindingId, DerivedTrait)> {
let prelude_traits: Vec<(BindingId, &str)> = modules
.iter()
.filter(|module| ModuleSources::is_prelude(&module.path))
.flat_map(|module| &module.items)
.filter_map(|item| match &item.kind {
ItemKind::RustTrait(declaration) => {
Some((declaration.binding, declaration.path.as_ref()))
}
_ => None,
})
.collect();
let written = StructDerives::DERIVABLE.iter().flat_map(|derived| {
prelude_traits
.iter()
.filter(move |(_, path)| StructDerives::derive_name(path) == *derived)
.map(move |(binding, _)| (*binding, DerivedTrait::Written(derived)))
});
let structural = self
.structural_traits
.iter()
.map(|structural| (structural.binding, DerivedTrait::Structural));
written.chain(structural).collect()
}
fn written_derives(
&mut self,
modules: &[CoreModule],
derivable: &[(BindingId, DerivedTrait)],
) -> FxHashMap<BindingId, WrittenDerive> {
let mut written: FxHashMap<BindingId, WrittenDerive> = FxHashMap::default();
for item in modules.iter().flat_map(|module| &module.items) {
let ItemKind::Derive(struct_binding, name_span, traits) = &item.kind else {
continue;
};
if self.is_rust_enum(*struct_binding) {
let enum_name = self.bindings.get(*struct_binding).name().to_string();
self.diagnostics
.push(TypeProblems::derive_on_rust_type(&enum_name, *name_span));
continue;
}
if let Some(first) = written.get(struct_binding) {
let struct_name = self.bindings.get(*struct_binding).name().to_string();
self.diagnostics.push(TypeProblems::duplicate_derive(
&struct_name,
item.span,
first.span,
));
continue;
}
let listed = traits
.iter()
.copied()
.filter(|(trait_binding, span)| {
self.accepts_derive(*trait_binding, *span, derivable)
&& !self.report_derived_written_impl(*struct_binding, *trait_binding, *span)
})
.collect();
if let Some(definition) = self.structs.get_mut(*struct_binding) {
definition.written_derive = Some(item.span);
}
if let Some(definition) = self.enums.get_mut(*struct_binding) {
definition.written_derive = Some(item.span);
}
written.insert(
*struct_binding,
WrittenDerive {
span: item.span,
traits: listed,
},
);
}
written
}
fn accepts_derive(
&mut self,
trait_binding: BindingId,
span: Span,
derivable: &[(BindingId, DerivedTrait)],
) -> bool {
if derivable
.iter()
.any(|(binding, _)| *binding == trait_binding)
{
return true;
}
let names: Vec<String> = derivable
.iter()
.map(|(binding, _)| self.bindings.get(*binding).name().to_string())
.collect();
let trait_name = self.bindings.get(trait_binding).name().to_string();
self.diagnostics
.push(TypeProblems::not_derivable(&trait_name, &names, span));
false
}
fn add_derive_candidates(
&mut self,
types: &[(BindingId, usize, DefinitionKind)],
derivable: &[(BindingId, DerivedTrait)],
written: &FxHashMap<BindingId, WrittenDerive>,
) -> Vec<DeriveCandidate> {
let mut candidates = Vec::new();
for (trait_binding, derived) in derivable {
for (binding, arity, kind) in types {
if self.is_rust_enum(*binding) {
continue;
}
let Some(source) =
self.derive_source(*binding, *trait_binding, *derived, written, derivable)
else {
continue;
};
let implementor = self.derived_implementor(*binding, *arity, *trait_binding);
self.traits.add_implementor(*trait_binding, implementor);
candidates.push(DeriveCandidate {
binding: *binding,
kind: *kind,
trait_binding: *trait_binding,
source,
});
}
}
candidates
}
fn is_rust_enum(&self, binding: BindingId) -> bool {
self.enums
.get(binding)
.is_some_and(|definition| definition.rust.is_some())
}
fn derive_source(
&self,
struct_binding: BindingId,
trait_binding: BindingId,
derived: DerivedTrait,
written: &FxHashMap<BindingId, WrittenDerive>,
derivable: &[(BindingId, DerivedTrait)],
) -> Option<DeriveSource> {
match (written.get(&struct_binding), derived) {
(Some(derive), _) => Self::listing_span(derive, trait_binding, derived, derivable)
.map(DeriveSource::Listed),
(None, DerivedTrait::Structural) => (!self
.has_written_impl(trait_binding, struct_binding))
.then_some(DeriveSource::Inferred),
(None, DerivedTrait::Written(name))
if name == StructDerives::COPY
&& self.changed_structs.contains(&struct_binding) =>
{
None
}
(None, DerivedTrait::Written(name)) => StructDerives::INFERRED
.contains(&name)
.then_some(DeriveSource::Inferred),
}
}
fn has_written_impl(&self, trait_binding: BindingId, head: BindingId) -> bool {
self.written_impl(trait_binding, head).is_some()
}
pub(super) fn written_impl(
&self,
trait_binding: BindingId,
head: BindingId,
) -> Option<&TypedTraitImpl> {
self.trait_impls.iter().find(|implementation| {
implementation.trait_binding == trait_binding
&& matches!(implementation.implementor, Type::Named(binding, _) if binding == head)
})
}
fn report_derived_written_impl(
&mut self,
head: BindingId,
trait_binding: BindingId,
listed: Span,
) -> bool {
let Some(written) = self.written_impl(trait_binding, head) else {
return false;
};
let trait_name = self.bindings.get(trait_binding).name().to_string();
let implementor = self.display().show(&written.implementor);
let diagnostic = TypeProblems::second_impl(
&implementor,
&trait_name,
written.implementor_span,
TypeProblems::derive_lists(listed, &trait_name),
);
self.diagnostics.push(diagnostic);
true
}
fn listing_span(
derive: &WrittenDerive,
trait_binding: BindingId,
derived: DerivedTrait,
derivable: &[(BindingId, DerivedTrait)],
) -> Option<Span> {
derive
.traits
.iter()
.find(|(listed, _)| {
*listed == trait_binding || Self::brings(*listed, derived, derivable)
})
.map(|(_, span)| *span)
}
fn brings(
listed: BindingId,
derived: DerivedTrait,
derivable: &[(BindingId, DerivedTrait)],
) -> bool {
let listed = derivable
.iter()
.find(|(binding, _)| *binding == listed)
.map(|(_, listed)| *listed);
match (listed, derived) {
(Some(DerivedTrait::Written(listed)), DerivedTrait::Written(name)) => {
StructDerives::brings(listed, name)
}
_ => false,
}
}
fn remove_blocked_derives(&mut self, candidates: &mut Vec<DeriveCandidate>) {
loop {
let blocked = self.blocked_derives(candidates);
if blocked.is_empty() {
break;
}
for (candidate, field, ty) in blocked {
self.traits
.remove_struct_implementor(candidate.trait_binding, candidate.binding);
candidates.retain(|kept| {
(kept.binding, kept.trait_binding)
!= (candidate.binding, candidate.trait_binding)
});
self.report_blocked_derive(candidate, field, ty);
}
}
}
fn blocked_derives(
&self,
candidates: &[DeriveCandidate],
) -> Vec<(DeriveCandidate, Rc<str>, Type)> {
candidates
.iter()
.filter_map(|candidate| {
self.blocking_payload(candidate.binding, candidate.kind, candidate.trait_binding)
.map(|(field, ty)| (*candidate, field, ty))
})
.collect()
}
fn report_blocked_derive(&mut self, candidate: DeriveCandidate, field: Rc<str>, ty: Type) {
match candidate.source {
DeriveSource::Listed(span) => self.report_listed_block(candidate, &field, &ty, span),
DeriveSource::Inferred => self.record_inferred_block(candidate, field, ty),
}
}
fn report_listed_block(
&mut self,
candidate: DeriveCandidate,
field: &Rc<str>,
ty: &Type,
span: Span,
) {
let display = self.display();
let name = self.bindings.get(candidate.binding).name();
let trait_name = self.bindings.get(candidate.trait_binding).name();
let diagnostic = match candidate.kind {
DefinitionKind::Struct => {
TypeProblems::blocked_derive(name, trait_name, field, &display.show(ty), span)
}
DefinitionKind::Enum => {
TypeProblems::blocked_enum_derive(name, trait_name, field, &display.show(ty), span)
}
};
self.diagnostics.push(diagnostic);
}
fn record_inferred_block(&mut self, candidate: DeriveCandidate, field: Rc<str>, ty: Type) {
match candidate.kind {
DefinitionKind::Struct => {
if let Some(definition) = self.structs.get_mut(candidate.binding) {
definition
.blocked
.push((candidate.trait_binding, field, ty));
}
}
DefinitionKind::Enum => {
if let Some(definition) = self.enums.get_mut(candidate.binding) {
definition
.blocked
.push((candidate.trait_binding, field, ty));
}
}
}
}
fn record_derives(
&mut self,
candidates: &[DeriveCandidate],
derivable: &[(BindingId, DerivedTrait)],
) {
for candidate in candidates {
let written_name = derivable
.iter()
.find(|(binding, _)| *binding == candidate.trait_binding)
.and_then(|(_, derived)| match derived {
DerivedTrait::Written(name) => Some(*name),
DerivedTrait::Structural => None,
});
let (derives, derived_traits) = match candidate.kind {
DefinitionKind::Struct => {
let Some(definition) = self.structs.get_mut(candidate.binding) else {
continue;
};
(&mut definition.derives, &mut definition.derived_traits)
}
DefinitionKind::Enum => {
let Some(definition) = self.enums.get_mut(candidate.binding) else {
continue;
};
(&mut definition.derives, &mut definition.derived_traits)
}
};
derives.extend(written_name);
derived_traits.push(candidate.trait_binding);
}
}
fn blocking_payload(
&self,
binding: BindingId,
kind: DefinitionKind,
trait_binding: BindingId,
) -> Option<(Rc<str>, Type)> {
match kind {
DefinitionKind::Struct => self.blocking_field(binding, trait_binding),
DefinitionKind::Enum => self.blocking_enum_payload(binding, trait_binding),
}
}
pub(super) fn blocking_enum_payload(
&self,
binding: BindingId,
trait_binding: BindingId,
) -> Option<(Rc<str>, Type)> {
let definition = self.enums.get(binding)?;
let params: Vec<GenericId> = definition.params.iter().map(|param| param.id).collect();
definition.blocking_variant(&|position, payload| {
let written = self.written_payload(binding, position, payload, trait_binding);
self.payload_satisfies(trait_binding, written, ¶ms)
})
}
fn written_payload<'p>(
&self,
binding: BindingId,
position: PayloadPosition,
payload: &'p Type,
trait_binding: BindingId,
) -> &'p Type {
let inferred_box =
self.inferred_boxes
.contains(&(binding, position.variant, position.payload));
match payload {
Type::Named(_, arguments)
if inferred_box && self.is_structural_trait(trait_binding) =>
{
&arguments[0]
}
_ => payload,
}
}
pub(super) fn blocking_field(
&self,
struct_binding: BindingId,
trait_binding: BindingId,
) -> Option<(Rc<str>, Type)> {
let definition = self.structs.get(struct_binding)?;
let params: Vec<GenericId> = definition.params.iter().map(|param| param.id).collect();
definition
.fields
.iter()
.find(|(_, ty)| !self.payload_satisfies(trait_binding, ty, ¶ms))
.map(|(name, ty)| (name.clone(), ty.clone()))
}
fn payload_satisfies(
&self,
trait_binding: BindingId,
payload: &Type,
params: &[GenericId],
) -> bool {
self.field_type_satisfies(&Bound::plain(trait_binding), payload, params)
}
fn field_type_satisfies(&self, bound: &Bound, field_type: &Type, params: &[GenericId]) -> bool {
match field_type {
Type::Function(..) => self.function_field_trait_allowed(bound.binding),
Type::Generic(generic) => params.contains(generic),
_ => match self.traits.select(bound, field_type) {
Selection::Implemented { requirements, .. } => {
requirements.iter().all(|requirement| {
self.field_type_satisfies(
&requirement.bound,
&requirement.implementor,
params,
)
})
}
Selection::Undecided | Selection::Missing => false,
},
}
}
fn function_field_trait_allowed(&self, trait_binding: BindingId) -> bool {
self.traits.get(trait_binding).is_some_and(|rust_trait| {
FunctionFieldTraits::ALLOWED.contains(&StructDerives::derive_name(&rust_trait.path))
})
}
fn derived_implementor(
&mut self,
binding: BindingId,
arity: usize,
derived: BindingId,
) -> Implementor {
let params: Vec<GenericParam> = (0..arity)
.map(|position| {
self.new_generic(&Self::parameter_name(position), vec![Bound::plain(derived)])
})
.collect();
let arguments = params.iter().map(|param| Type::Generic(param.id)).collect();
Implementor {
scheme: Scheme {
lifetimes: Vec::new(),
params,
body: Type::Named(binding, arguments),
},
argument: None,
}
}
}