use std::collections::HashMap;
use crate::ast::{
AstErrorEnumDeclaration, AstErrorVariant, AstInlineErrorVariantField, Disabled, RefError,
};
use crate::expand::{ErrorEnum, ErrorVariant, Named, SourceStruct, SourceTuple, Struct};
use quote::ToTokens;
use syn::{Attribute, Ident, TypeParam, Visibility};
pub(crate) fn resolve(
error_enum_decls: Vec<AstErrorEnumDeclaration>,
) -> syn::Result<Vec<ErrorEnum>> {
let mut error_enum_builders: Vec<ErrorEnumBuilder> = Vec::new();
for declaration in error_enum_decls.into_iter() {
let AstErrorEnumDeclaration {
attributes,
vis, error_name,
generics,
disabled,
parts,
} = declaration;
let mut error_enum_builder =
ErrorEnumBuilder::new(error_name, attributes, vis, generics, disabled);
for part in parts.into_iter() {
match part {
crate::ast::AstInlineOrRefError::Inline(inline_part) => {
error_enum_builder
.error_variants
.extend(inline_part.error_variants.into_iter());
}
crate::ast::AstInlineOrRefError::Ref(ref_part) => {
error_enum_builder.add_ref_part(ref_part);
}
}
}
error_enum_builders.push(error_enum_builder);
}
let error_enums = resolve_builders(error_enum_builders)?;
Ok(error_enums)
}
fn resolve_builders(mut error_enum_builders: Vec<ErrorEnumBuilder>) -> syn::Result<Vec<ErrorEnum>> {
for index in 0..error_enum_builders.len() {
if !error_enum_builders[index].ref_parts_to_resolve.is_empty() {
resolve_builders_helper(index, &mut *error_enum_builders, &mut Vec::new())?;
}
}
let error_enums = error_enum_builders
.into_iter()
.map(Into::into)
.collect::<Vec<ErrorEnum>>();
Ok(error_enums)
}
fn resolve_builders_helper<'a>(
index: usize,
error_enum_builders: &'a mut [ErrorEnumBuilder],
visited: &mut Vec<Ident>,
) -> syn::Result<Vec<AstErrorVariant>> {
let error_enum_builder = &error_enum_builders[index];
let error_name = &error_enum_builder.error_name;
if visited.contains(error_name) {
visited.push(error_name.clone());
if let Some(pos) = visited.iter().position(|e| e == error_name) {
visited.drain(0..pos);
}
return Err(syn::parse::Error::new_spanned(
error_name.clone(),
format!(
"Cycle Detected: {}",
visited
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join("->")
),
));
}
let ref_parts_to_resolve = error_enum_builder.ref_parts_to_resolve.clone();
if !ref_parts_to_resolve.is_empty() {
for ref_part in ref_parts_to_resolve {
let ref_error_enum_index = error_enum_builders
.iter()
.position(|e| e.error_name == ref_part.name);
let ref_error_enum_index = match ref_error_enum_index {
Some(e) => e,
None => {
return Err(syn::parse::Error::new_spanned(
&ref_part.name,
"Not a declared error set.",
));
}
};
if !error_enum_builders[ref_error_enum_index]
.ref_parts_to_resolve
.is_empty()
{
visited.push(error_enum_builders[index].error_name.clone());
resolve_builders_helper(ref_error_enum_index, error_enum_builders, visited)?;
visited.pop();
}
let [this_error_enum_builder, ref_error_enum_builder] = error_enum_builders
.get_disjoint_mut([index, ref_error_enum_index])
.unwrap();
if ref_part.generic_refs.len() != ref_error_enum_builder.generics.len() {
Err(syn::parse::Error::new_spanned(
&ref_part.name,
format!(
"A reference to {} was declared with {} generic param(s), but the original definition takes {}.",
ref_part.name,
ref_part.generic_refs.len(),
ref_error_enum_builder.generics.len()
),
))?;
}
let mut error_variants = Vec::new();
let error_variants = if ref_part.generic_refs.is_empty() {
&ref_error_enum_builder.error_variants
} else {
fn ident_to_type(ident: Ident) -> syn::Type {
let segment = syn::PathSegment {
ident,
arguments: syn::PathArguments::None, };
let path = syn::Path {
leading_colon: None,
segments: {
let mut punctuated = syn::punctuated::Punctuated::new();
punctuated.push(segment);
punctuated
},
};
let type_path = syn::TypePath { qself: None, path };
syn::Type::Path(type_path)
}
let mut generic_type_to_new_generic_type = HashMap::<syn::Type, syn::Type>::new();
let mut generic_type_to_new_generic_type_str = HashMap::<String, String>::new();
let mut generic_type_str_to_regex = HashMap::<String, regex::Regex>::new();
for (ref_part_generic, ref_error_enum_generic) in ref_part
.generic_refs
.iter()
.zip(ref_error_enum_builder.generics.iter())
{
let old = ref_error_enum_generic.ident.to_string();
let generic_identification_pattern = format!(
r"(?P<before>[^\w\d]){}(?P<after>[^\w\d])",
regex::escape(&old)
);
let re = regex::Regex::new(&generic_identification_pattern).unwrap();
generic_type_str_to_regex.insert(old.clone(), re);
let new = ref_part_generic.to_string();
generic_type_to_new_generic_type_str.insert(old, new);
generic_type_to_new_generic_type.insert(
ident_to_type(ref_error_enum_generic.ident.clone()),
ident_to_type(ref_part_generic.clone()),
);
}
for error_variant in ref_error_enum_builder.error_variants.iter() {
let new_fields = if let Some(fields) = &error_variant.fields {
let mut new_fields = Vec::new();
for field in fields.iter() {
new_fields.push(replace_generics_in_fields(
field,
&generic_type_to_new_generic_type,
&generic_type_to_new_generic_type_str,
&generic_type_str_to_regex,
));
}
Some(new_fields)
} else {
None
};
error_variants.push(AstErrorVariant {
attributes: error_variant.attributes.clone(),
cfg_attributes: error_variant.cfg_attributes.clone(),
display: error_variant.display.clone(),
name: error_variant.name.clone(),
fields: new_fields,
source_type: error_variant.source_type.clone(),
});
}
&error_variants
};
for variant in error_variants {
let this_error_variants = &mut this_error_enum_builder.error_variants;
let is_variant_already_in_enum = this_error_variants
.iter()
.any(|e| does_occupy_the_same_space(e, &variant));
if !is_variant_already_in_enum {
this_error_variants.push(variant.clone());
}
}
}
error_enum_builders[index].ref_parts_to_resolve.clear();
}
Ok(error_enum_builders[index].error_variants.clone())
}
pub(crate) fn does_occupy_the_same_space(this: &AstErrorVariant, other: &AstErrorVariant) -> bool {
return this.name == other.name;
}
struct ErrorEnumBuilder {
pub attributes: Vec<Attribute>,
pub vis: Visibility,
pub error_name: Ident,
pub generics: Vec<TypeParam>,
pub disabled: Disabled,
pub error_variants: Vec<AstErrorVariant>,
pub ref_parts_to_resolve: Vec<RefError>,
}
impl ErrorEnumBuilder {
fn new(
error_name: Ident,
attributes: Vec<Attribute>,
vis: Visibility,
generics: Vec<TypeParam>,
disabled: Disabled,
) -> Self {
Self {
attributes,
vis,
error_name,
generics,
disabled,
error_variants: Vec::new(),
ref_parts_to_resolve: Vec::new(),
}
}
fn add_ref_part(&mut self, ref_part: RefError) {
self.ref_parts_to_resolve.push(ref_part);
}
}
impl From<ErrorEnumBuilder> for ErrorEnum {
fn from(value: ErrorEnumBuilder) -> Self {
assert!(
value.ref_parts_to_resolve.is_empty(),
"All references should be resolved when converting to an error enum."
);
ErrorEnum {
attributes: value.attributes,
vis: value.vis,
error_name: value.error_name,
generics: value.generics,
disabled: value.disabled,
error_variants: value
.error_variants
.into_iter()
.map(|v| reshape(v))
.collect::<Vec<_>>(),
}
}
}
impl PartialEq for ErrorEnumBuilder {
fn eq(&self, other: &Self) -> bool {
self.error_name == other.error_name
}
}
impl std::hash::Hash for ErrorEnumBuilder {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.error_name.hash(state);
}
}
impl Eq for ErrorEnumBuilder {}
fn reshape(this: AstErrorVariant) -> ErrorVariant {
let AstErrorVariant {
attributes,
cfg_attributes,
display,
name,
fields,
source_type,
} = this;
match (fields, source_type) {
(Some(fields), Some(source_type)) => {
return ErrorVariant::SourceStruct(SourceStruct {
attributes,
cfg_attributes,
display,
name,
source_type,
fields,
});
}
(Some(fields), None) => {
return ErrorVariant::Struct(Struct {
attributes,
cfg_attributes,
display,
name,
fields,
});
}
(None, Some(source_type)) => {
return ErrorVariant::SourceTuple(SourceTuple {
attributes,
cfg_attributes,
display,
name,
source_type,
});
}
(None, None) => {
return ErrorVariant::Named(Named {
attributes,
cfg_attributes,
display,
name,
});
}
}
}
fn replace_generics_in_fields(
field: &AstInlineErrorVariantField,
old_to_new: &HashMap<syn::Type, syn::Type>,
old_to_new_str: &HashMap<String, String>,
old_to_match_regex: &HashMap<String, regex::Regex>,
) -> AstInlineErrorVariantField {
if old_to_new.contains_key(&field.r#type) {
let new_type = old_to_new.get(&field.r#type).unwrap().clone();
return AstInlineErrorVariantField {
attributes: field.attributes.clone(),
name: field.name.clone(),
r#type: new_type.clone(),
};
}
let field_type_str = field.r#type.to_token_stream().to_string();
for (original_type, new_type) in old_to_new_str {
let regex = &old_to_match_regex[original_type];
let replaced = replace_part(&field_type_str, new_type, regex);
if field_type_str != replaced {
let new_type = syn::parse_str::<syn::Type>(&replaced)
.expect("Failed to parse replaced type back into type");
return AstInlineErrorVariantField {
attributes: field.attributes.clone(),
name: field.name.clone(),
r#type: new_type.clone(),
};
}
}
return field.clone();
}
fn replace_part(input: &str, replacement: &str, re: ®ex::Regex) -> String {
re.replace_all(input, |caps: ®ex::Captures| {
format!("{}{}{}", &caps["before"], replacement, &caps["after"])
})
.to_string()
}