use cairo_lang_defs::patcher::{PatchBuilder, RewriteNode};
use cairo_lang_defs::plugin::{
MacroPlugin, MacroPluginMetadata, PluginDiagnostic, PluginGeneratedFile, PluginResult,
};
use cairo_lang_filesystem::ids::SmolStrId;
use cairo_lang_syntax::attribute::structured::{
AttributeArg, AttributeArgVariant, AttributeStructurize,
};
use cairo_lang_syntax::node::helpers::QueryAttrs;
use cairo_lang_syntax::node::{TypedSyntaxNode, ast};
use salsa::Database;
use super::utils::PluginTypeInfo;
use crate::plugins::DOC_ATTR;
mod clone;
mod debug;
mod default;
mod destruct;
mod hash;
mod panic_destruct;
mod partial_eq;
mod serde;
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct DerivePlugin;
const DERIVE_ATTR: &str = "derive";
impl MacroPlugin for DerivePlugin {
fn generate_code<'db>(
&self,
db: &'db dyn Database,
item_ast: ast::ModuleItem<'db>,
metadata: &MacroPluginMetadata<'_>,
) -> PluginResult<'db> {
generate_derive_code_for_type(
db,
metadata,
match PluginTypeInfo::new(db, &item_ast) {
Some(info) => info,
None => {
let maybe_error = item_ast.find_attr(db, DERIVE_ATTR).map(|derive_attr| {
vec![PluginDiagnostic::error(
derive_attr.as_syntax_node().stable_ptr(db),
"`derive` may only be applied to `struct`s and `enum`s".to_string(),
)]
});
return PluginResult {
diagnostics: maybe_error.unwrap_or_default(),
..PluginResult::default()
};
}
},
)
}
fn declared_attributes<'db>(&self, db: &'db dyn Database) -> Vec<SmolStrId<'db>> {
vec![SmolStrId::from(db, DERIVE_ATTR), SmolStrId::from(db, default::DEFAULT_ATTR)]
}
fn declared_derives<'db>(&self, db: &'db dyn Database) -> Vec<SmolStrId<'db>> {
vec![
SmolStrId::from(db, "Copy"),
SmolStrId::from(db, "Drop"),
SmolStrId::from(db, "Clone"),
SmolStrId::from(db, "Debug"),
SmolStrId::from(db, "Default"),
SmolStrId::from(db, "Destruct"),
SmolStrId::from(db, "Hash"),
SmolStrId::from(db, "PanicDestruct"),
SmolStrId::from(db, "PartialEq"),
SmolStrId::from(db, "Serde"),
]
}
}
fn generate_derive_code_for_type<'db>(
db: &'db dyn Database,
metadata: &MacroPluginMetadata<'_>,
info: PluginTypeInfo<'db>,
) -> PluginResult<'db> {
let mut diagnostics = vec![];
let mut builder = PatchBuilder::new(db, &info.attributes);
for attr in info.attributes.query_attr(db, DERIVE_ATTR) {
let attr = attr.structurize(db);
if attr.args.is_empty() {
diagnostics
.push(PluginDiagnostic::error(attr.args_stable_ptr, "Expected args.".into()));
continue;
}
let member_access_desnaps = metadata.edition.member_access_desnaps();
for arg in attr.args {
let AttributeArg {
variant: AttributeArgVariant::Unnamed(ast::Expr::Path(derived_path)),
..
} = arg
else {
diagnostics
.push(PluginDiagnostic::error(arg.arg.stable_ptr(db), "Expected path.".into()));
continue;
};
let derived = derived_path.as_syntax_node().get_text_without_trivia(db);
let derived_text = derived.long(db).as_str();
if let Some(mut code) = match derived_text {
"Copy" | "Drop" => Some(get_empty_impl(derived_text, &info)),
"Clone" => Some(clone::handle_clone(&info, member_access_desnaps)),
"Debug" => Some(debug::handle_debug(&info, member_access_desnaps)),
"Default" => default::handle_default(db, &info, &derived_path, &mut diagnostics),
"Destruct" => Some(destruct::handle_destruct(&info)),
"Hash" => Some(hash::handle_hash(&info)),
"PanicDestruct" => Some(panic_destruct::handle_panic_destruct(&info)),
"PartialEq" => Some(partial_eq::handle_partial_eq(&info, member_access_desnaps)),
"Serde" => Some(serde::handle_serde(&info, member_access_desnaps)),
_ => {
if !metadata.declared_derives.contains(&derived) {
diagnostics.push(PluginDiagnostic::error(
derived_path.stable_ptr(db),
format!(
"Unknown derive `{derived_text}` - a plugin might be missing.",
),
));
}
None
}
} {
if let Some(doc_attr) = info.attributes.find_attr(db, DOC_ATTR) {
code = format!(
"{}\n{code}",
doc_attr.as_syntax_node().get_text_without_trivia(db).long(db)
)
}
builder.add_modified(RewriteNode::mapped_text(code, db, &derived_path));
}
}
}
let (content, code_mappings) = builder.build();
PluginResult {
code: (!content.is_empty()).then(|| PluginGeneratedFile {
name: "impls".into(),
code_mappings,
content,
aux_data: None,
diagnostics_note: Default::default(),
is_unhygienic: false,
}),
diagnostics,
remove_original_item: false,
}
}
fn get_empty_impl(derived_trait: &str, info: &PluginTypeInfo<'_>) -> String {
let derive_trait = format!("core::traits::{derived_trait}");
format!("{};\n", info.impl_header(&derive_trait, &[&derive_trait]))
}