use {
super::resolver::{
normalize_type,
type_uses_self_assoc,
},
crate::{
analysis::get_all_parameters,
core::{
config::Config,
constants::{
attributes::{
DOCUMENT_DEFAULT,
DOCUMENT_TYPE_PARAMETERS,
},
macros::IMPL_KIND_MACRO,
},
error_handling::{
CollectErrors,
ErrorCollector,
},
},
hkt::{
ImplKindInput,
canonicalizer::hash_assoc_signature,
},
resolution::{
ImplKey,
ProjectionKey,
},
support::{
attributes::has_attribute,
documentation_parameters::{
DocumentationParameter,
DocumentationParameters,
},
},
},
quote::ToTokens,
syn::{
Error,
ImplItem,
Item,
Result,
spanned::Spanned,
},
};
type ScopedDefaultsTracker =
std::collections::HashMap<(String, String), Vec<(String, proc_macro2::Span)>>;
fn register_module_default(
config: &mut Config,
brand_path: String,
assoc_name: String,
span: proc_macro2::Span,
errors: &mut ErrorCollector,
) {
if let Some(prev) = config.module_defaults.insert(brand_path.clone(), assoc_name.clone()) {
errors.push(Error::new(
span,
format!("Conflicting module default for {brand_path}: {prev} and {assoc_name}"),
));
}
}
fn track_scoped_default(
scoped_defaults_tracker: &mut ScopedDefaultsTracker,
self_ty_path: &str,
trait_path: &str,
assoc_name: String,
span: proc_macro2::Span,
) {
let key = (self_ty_path.to_string(), trait_path.to_string());
scoped_defaults_tracker.entry(key).or_default().push((assoc_name, span));
}
fn process_impl_kind_macro(
item_macro: &syn::ItemMacro,
config: &mut Config,
errors: &mut ErrorCollector,
) {
let has_cfg = item_macro.attrs.iter().any(|attr| attr.path().is_ident("cfg"));
if let Some(impl_kind) = errors.collect(|| item_macro.mac.parse_body::<ImplKindInput>()) {
let brand_path = impl_kind.brand.to_token_stream().to_string();
for def in &impl_kind.definitions {
let assoc_name = def.signature.name.to_string();
if type_uses_self_assoc(&def.target_type) {
errors.push(Error::new(
def.target_type.span(),
"Self:: reference forbidden in associated type definition",
));
}
let signature_hash = match hash_assoc_signature(&def.signature) {
Ok(h) => h,
Err(e) => {
errors.push(e.into());
continue;
}
};
let canonical_sig =
match crate::hkt::canonicalizer::generate_assoc_signature(&def.signature) {
Ok(s) => s,
Err(e) => {
errors.push(e.into());
continue;
}
};
if let Some((prev_brand, prev_assoc, prev_sig)) =
config.signature_hashes.get(&signature_hash)
{
if prev_sig != &canonical_sig {
errors.push(Error::new(
def.signature.name.span(),
format!(
"Hash collision detected (extremely rare!): {brand_path}.{assoc_name} and {prev_brand}.{prev_assoc} have the same hash. Please report this as a bug."
),
));
continue;
}
} else {
config.signature_hashes.insert(
signature_hash,
(brand_path.clone(), assoc_name.clone(), canonical_sig),
);
}
let key =
ProjectionKey::new(&brand_path, &assoc_name).with_signature_hash(signature_hash);
if !has_cfg {
let normalized_target =
normalize_type(def.target_type.clone(), &def.signature.generics);
if let Some((prev_generics, prev_type)) = config.projections.get(&key) {
let prev_normalized = normalize_type(prev_type.clone(), prev_generics);
if prev_normalized != normalized_target {
errors.push(Error::new(
def.signature.name.span(),
format!(
"Conflicting implementation for {assoc_name}: already defined with different type"
),
));
}
}
}
config
.projections
.insert(key, (def.signature.generics.clone(), def.target_type.clone()));
if has_attribute(&def.signature.attributes, DOCUMENT_DEFAULT) {
register_module_default(
config,
brand_path.clone(),
assoc_name.clone(),
def.signature.name.span(),
errors,
);
}
}
}
}
fn process_impl_type_parameter_documentation(
item_impl: &syn::ItemImpl,
self_ty_path: &str,
trait_path: Option<&str>,
config: &mut Config,
errors: &mut ErrorCollector,
) {
for attr in &item_impl.attrs {
if attr.path().is_ident(DOCUMENT_TYPE_PARAMETERS) {
let targets = get_all_parameters(&item_impl.generics);
if targets.is_empty() {
errors.push(Error::new(
attr.span(),
format!(
"{DOCUMENT_TYPE_PARAMETERS} cannot be used on impl blocks with no type parameters"
),
));
continue;
}
if let Some(args) = errors.collect(|| attr.parse_args::<DocumentationParameters>()) {
let entries: Vec<_> = args.entries.iter().collect();
if entries.len() != targets.len() {
errors.push(Error::new(
attr.span(),
format!(
"Expected exactly {} description arguments for impl generics, found {}.",
targets.len(),
entries.len()
),
));
} else {
let mut docs = Vec::new();
for (name_from_target, entry) in targets.iter().zip(entries) {
let (_name, desc) = match entry {
DocumentationParameter::Override(n, d) => (n.value(), d.value()),
DocumentationParameter::Description(d) =>
(name_from_target.clone(), d.value()),
};
docs.push((name_from_target.clone(), desc));
}
let impl_key = ImplKey::from_paths(self_ty_path, trait_path);
config.impl_type_param_docs.insert(impl_key, docs);
}
}
}
}
}
fn process_impl_associated_types(
item_impl: &syn::ItemImpl,
self_ty_path: &str,
trait_path: Option<&str>,
config: &mut Config,
scoped_defaults_tracker: &mut ScopedDefaultsTracker,
) {
for item in &item_impl.items {
if let ImplItem::Type(assoc_type) = item {
let assoc_name = assoc_type.ident.to_string();
let key = if let Some(t_path) = trait_path {
ProjectionKey::scoped(self_ty_path, t_path, &assoc_name)
} else {
ProjectionKey::new(self_ty_path, &assoc_name)
};
config.projections.insert(key, (assoc_type.generics.clone(), assoc_type.ty.clone()));
if has_attribute(&assoc_type.attrs, DOCUMENT_DEFAULT)
&& let Some(t_path) = trait_path
{
track_scoped_default(
scoped_defaults_tracker,
self_ty_path,
t_path,
assoc_name.clone(),
assoc_type.ident.span(),
);
}
}
}
}
fn process_impl_block(
item_impl: &syn::ItemImpl,
config: &mut Config,
scoped_defaults_tracker: &mut ScopedDefaultsTracker,
errors: &mut ErrorCollector,
) {
let self_ty_path = item_impl.self_ty.to_token_stream().to_string();
let trait_path =
item_impl.trait_.as_ref().map(|(_, path, _)| path.to_token_stream().to_string());
process_impl_type_parameter_documentation(
item_impl,
&self_ty_path,
trait_path.as_deref(),
config,
errors,
);
process_impl_associated_types(
item_impl,
&self_ty_path,
trait_path.as_deref(),
config,
scoped_defaults_tracker,
);
}
fn validate_scoped_defaults(
scoped_defaults_tracker: ScopedDefaultsTracker,
config: &mut Config,
errors: &mut ErrorCollector,
) {
for ((self_ty, trait_path), defaults) in scoped_defaults_tracker {
if defaults.len() > 1 {
let names: Vec<_> = defaults.iter().map(|(n, _)| n.as_str()).collect();
for (_name, span) in &defaults {
errors.push(Error::new(
*span,
format!(
"Multiple #[{DOCUMENT_DEFAULT}] annotations for ({self_ty}, {trait_path}): {}",
names.join(", ")
),
));
}
} else if let Some((name, _)) = defaults.first() {
config.scoped_defaults.insert((self_ty, trait_path), name.clone());
}
}
}
pub fn get_context(
items: &[Item],
config: &mut Config,
) -> Result<()> {
let mut errors = ErrorCollector::new();
let mut scoped_defaults_tracker: ScopedDefaultsTracker = std::collections::HashMap::new();
for item in items {
match item {
Item::Macro(m) if m.mac.path.is_ident(IMPL_KIND_MACRO) => {
process_impl_kind_macro(m, config, &mut errors);
}
Item::Impl(item_impl) => {
process_impl_block(item_impl, config, &mut scoped_defaults_tracker, &mut errors);
}
_ => {}
}
}
validate_scoped_defaults(scoped_defaults_tracker, config, &mut errors);
errors.finish()
}