use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use cairo_lang_defs::patcher::{PatchBuilder, RewriteNode};
use cairo_lang_defs::plugin::{PluginDiagnostic, PluginGeneratedFile, PluginResult};
use cairo_lang_filesystem::db::Edition;
use cairo_lang_filesystem::ids::{CodeMapping, CodeOrigin};
use cairo_lang_filesystem::span::{TextOffset, TextSpan as CairoTextSpan};
use cairo_lang_macro::{AllocationContext, TextSpan, TokenStream, TokenStreamMetadata};
use cairo_lang_syntax::attribute::structured::{AttributeArgVariant, AttributeStructurize};
use cairo_lang_syntax::node::ast::{
self, Expr, ImplItem, MaybeImplBody, MaybeTraitBody, PathSegment,
};
use cairo_lang_syntax::node::helpers::QueryAttrs;
use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
use cairo_lang_syntax::node::{SyntaxNode, Terminal, TypedStablePtr, TypedSyntaxNode};
use convert_case::{Case, Casing};
use itertools::Itertools;
use salsa::Database;
use scarb_proc_macro_server_types::methods::ProcMacroResult;
use scarb_proc_macro_server_types::methods::defined_macros::MacroWithHash;
use scarb_proc_macro_server_types::methods::expand::{
ExpandAttributeParams, ExpandDeriveParams, ExpandInlineMacroParams,
};
use scarb_proc_macro_server_types::scope::ProcMacroScope;
use scarb_stable_hash::StableHasher;
use super::into_cairo_diagnostics;
use crate::lang::proc_macros::db::{
get_attribute_expansion, get_derive_expansion, get_inline_macros_expansion,
};
use crate::lang::proc_macros::plugins::scarb::child_nodes::{
ChildNodesWithoutAttributes, ItemWithAttributes,
};
use crate::lang::proc_macros::plugins::scarb::conversion::{
CallSiteLocation, code_mapping_from_proc_macro_server,
};
use crate::lang::proc_macros::plugins::scarb::types::{
AdaptedCodeMapping, AdaptedDiagnostic, AdaptedTokenStream, ExpandableAttrLocation,
TokenStreamBuilder,
};
const DERIVE_ATTR: &str = "derive";
pub fn macro_generate_code<'db>(
db: &'db dyn Database,
expansion_context: ProcMacroScope,
item_ast: ast::ModuleItem<'db>,
defined_attributes: &[MacroWithHash],
defined_derives: &[MacroWithHash],
defined_inlines: &[MacroWithHash],
metadata: &cairo_lang_defs::plugin::MacroPluginMetadata<'_>,
) -> PluginResult<'db> {
let stream_metadata = calculate_metadata(db, item_ast.clone(), metadata.edition);
if let InnerAttrExpansionResult::Some(result) =
expand_inner_attr(db, expansion_context.clone(), defined_attributes, item_ast.clone())
{
return result.into();
}
if let ast::ModuleItem::InlineMacro(inline_macro) = &item_ast
&& let Some(result) = expand_module_level_inline_macro(
db,
inline_macro,
expansion_context.clone(),
defined_inlines,
)
{
return result;
}
let ctx = AllocationContext::default();
let (input, body) = parse_attribute(db, Vec::from(defined_attributes), item_ast.clone(), &ctx);
let derives = parse_derive(db, defined_derives, item_ast.clone());
if let Some(result) = match input {
AttrExpansionFound::Last(AttrExpansionArgs {
name,
args,
call_site,
attribute_location,
fingerprint,
}) => Some((name, args, call_site, attribute_location, true, fingerprint)),
AttrExpansionFound::Some(AttrExpansionArgs {
name,
args,
call_site,
attribute_location,
fingerprint,
}) => Some((name, args, call_site, attribute_location, false, fingerprint)),
AttrExpansionFound::None => None,
}
.map(|(name, args, call_site, attribute_location, last, fingerprint)| {
let token_stream = body.with_metadata(stream_metadata.clone());
expand_attribute(
db,
expansion_context.clone(),
last && derives.is_empty(),
token_stream,
item_ast.as_syntax_node(),
AttrExpansionArgs { name, call_site, args, attribute_location, fingerprint },
)
}) {
return result.into();
}
if let Some(result) = expand_derives(
db,
expansion_context.clone(),
item_ast.clone(),
derives,
stream_metadata.clone(),
) {
return result;
}
PluginResult { code: None, diagnostics: Vec::new(), remove_original_item: false }
}
fn expand_module_level_inline_macro<'db>(
db: &'db dyn Database,
inline_macro: &ast::ItemInlineMacro<'db>,
expansion_context: ProcMacroScope,
defined_inlines: &[MacroWithHash],
) -> Option<PluginResult<'db>> {
let path = inline_macro.path(db).segments(db).elements(db).last()?;
let PathSegment::Simple(segment) = path else {
return None;
};
let inline_macro_name = segment.ident(db).text(db).to_string(db);
let call_site = CallSiteLocation::new(inline_macro, db);
let ctx = AllocationContext::default();
let arguments = inline_macro.arguments(db);
let mut token_stream_builder = TokenStreamBuilder::new(db);
token_stream_builder.add_node(arguments.as_syntax_node());
let token_stream = token_stream_builder.build(&ctx);
let fingerprint = defined_inlines.iter().find(|m| m.name == inline_macro_name)?.hash;
let result = get_inline_macros_expansion(
db,
ExpandInlineMacroParams {
context: expansion_context,
name: inline_macro_name.clone(),
args: token_stream,
call_site: call_site.span,
},
fingerprint,
);
let result_content_string = result.token_stream.to_string();
Some(PluginResult {
code: Some(PluginGeneratedFile {
name: "inline_proc_macro".into(),
code_mappings: result
.code_mappings
.map(|x| x.into_iter().map(code_mapping_from_proc_macro_server).collect())
.unwrap_or_else(|| {
vec![CodeMapping {
origin: CodeOrigin::Span(
inline_macro.as_syntax_node().span_without_trivia(db),
),
span: CairoTextSpan::from_str(&result_content_string),
}]
}),
content: result_content_string,
aux_data: None,
diagnostics_note: Some(format!(
"this error originates in the inline macro: `{}`",
inline_macro_name
)),
is_unhygienic: false,
}),
diagnostics: into_cairo_diagnostics(db, result.diagnostics, call_site.stable_ptr),
remove_original_item: true,
})
}
fn expand_inner_attr<'db>(
db: &'db dyn Database,
expansion_context: ProcMacroScope,
defined_attributes: &[MacroWithHash],
item_ast: ast::ModuleItem<'db>,
) -> InnerAttrExpansionResult<'db> {
let mut context = InnerAttrExpansionContext::new(db, &item_ast);
let mut used_attr_names: HashSet<String> = Default::default();
let mut all_none = true;
let ctx = AllocationContext::default();
let item_start_offset = item_ast.as_syntax_node().span(db).start;
match item_ast.clone() {
ast::ModuleItem::Trait(trait_ast) => {
context.add_node(trait_ast.attributes(db).as_syntax_node());
context.add_node(trait_ast.visibility(db).as_syntax_node());
context.add_node(trait_ast.trait_kw(db).as_syntax_node());
context.add_node(trait_ast.name(db).as_syntax_node());
context.add_node(trait_ast.generic_params(db).as_syntax_node());
match trait_ast.body(db) {
MaybeTraitBody::None(terminal) => {
context.add_node(terminal.as_syntax_node());
InnerAttrExpansionResult::None
}
MaybeTraitBody::Some(body) => {
context.add_node(body.lbrace(db).as_syntax_node());
let item_list = body.items(db);
for item in item_list.elements(db) {
let ast::TraitItem::Function(func) = item else {
context.add_node(item.as_syntax_node());
continue;
};
let mut token_stream_builder = TokenStreamBuilder::new(db);
let attrs = func.attributes(db).elements(db).collect_vec();
let found = parse_attrs(
db,
defined_attributes,
&mut token_stream_builder,
attrs,
item_start_offset,
&ctx,
);
if let Some(name) = found.as_name() {
used_attr_names.insert(name);
}
token_stream_builder.add_node(func.declaration(db).as_syntax_node());
token_stream_builder.add_node(func.body(db).as_syntax_node());
let token_stream = token_stream_builder.build(&ctx);
let token_stream = found.adapt_token_stream(token_stream);
all_none = all_none
&& do_expand_inner_attr(
db,
&mut context,
expansion_context.clone(),
found,
&func,
token_stream,
);
}
context.add_node(body.rbrace(db).as_syntax_node());
if all_none {
InnerAttrExpansionResult::None
} else {
InnerAttrExpansionResult::Some(
context.into_result(used_attr_names.into_iter().collect()),
)
}
}
}
}
ast::ModuleItem::Impl(impl_ast) => {
context.add_node(impl_ast.attributes(db).as_syntax_node());
context.add_node(impl_ast.visibility(db).as_syntax_node());
context.add_node(impl_ast.impl_kw(db).as_syntax_node());
context.add_node(impl_ast.name(db).as_syntax_node());
context.add_node(impl_ast.generic_params(db).as_syntax_node());
context.add_node(impl_ast.of_kw(db).as_syntax_node());
context.add_node(impl_ast.trait_path(db).as_syntax_node());
match impl_ast.body(db) {
MaybeImplBody::None(terminal) => {
context.add_node(terminal.as_syntax_node());
InnerAttrExpansionResult::None
}
MaybeImplBody::Some(body) => {
context.add_node(body.lbrace(db).as_syntax_node());
let items = body.items(db);
for item in items.elements(db) {
let ImplItem::Function(func) = item else {
context.add_node(item.as_syntax_node());
continue;
};
let mut token_stream_builder = TokenStreamBuilder::new(db);
let attrs = func.attributes(db).elements(db).collect_vec();
let found = parse_attrs(
db,
defined_attributes,
&mut token_stream_builder,
attrs,
item_start_offset,
&ctx,
);
if let Some(name) = found.as_name() {
used_attr_names.insert(name);
}
token_stream_builder.add_node(func.visibility(db).as_syntax_node());
token_stream_builder.add_node(func.declaration(db).as_syntax_node());
token_stream_builder.add_node(func.body(db).as_syntax_node());
let token_stream = token_stream_builder.build(&ctx);
let token_stream = found.adapt_token_stream(token_stream);
all_none = all_none
&& do_expand_inner_attr(
db,
&mut context,
expansion_context.clone(),
found,
&func,
token_stream,
);
}
context.add_node(body.rbrace(db).as_syntax_node());
if all_none {
InnerAttrExpansionResult::None
} else {
InnerAttrExpansionResult::Some(
context.into_result(used_attr_names.into_iter().collect()),
)
}
}
}
}
_ => InnerAttrExpansionResult::None,
}
}
fn do_expand_inner_attr<'db>(
db: &'db dyn Database,
context: &mut InnerAttrExpansionContext<'db>,
expansion_context: ProcMacroScope,
found: AttrExpansionFound<'db>,
func: &impl TypedSyntaxNode<'db>,
token_stream: AdaptedTokenStream,
) -> bool {
let mut all_none = true;
let input = match found {
AttrExpansionFound::Last(input) => {
all_none = false;
input
}
AttrExpansionFound::Some(input) => {
all_none = false;
input
}
AttrExpansionFound::None => {
context.add_node(func.as_syntax_node());
return all_none;
}
};
let result = get_attribute_expansion(
db,
ExpandAttributeParams {
context: expansion_context,
attr: input.name.clone(),
args: input.args.clone(),
item: TokenStream::from(token_stream.clone()),
adapted_call_site: input.attribute_location.adapted_call_site().into(),
},
input.fingerprint,
);
if result.code_mappings.is_some() {
context.register_result_metadata_v2(db, &input, token_stream.to_string(), result.clone());
} else {
context.register_result_metadata_v1(
result.token_stream.to_string(),
func.as_syntax_node().span(db),
);
}
all_none
}
struct InnerAttrExpansionContext<'db> {
diagnostics: Vec<PluginDiagnostic<'db>>,
any_changed: bool,
item_builder: PatchBuilder<'db>,
}
impl<'db> InnerAttrExpansionContext<'db> {
pub fn new(db: &'db dyn Database, item_ast: &ast::ModuleItem<'db>) -> Self {
Self {
diagnostics: Vec::new(),
any_changed: false,
item_builder: PatchBuilder::new(db, item_ast),
}
}
pub fn add_node(&mut self, node: SyntaxNode<'db>) {
self.item_builder.add_node(node);
}
fn register_diagnotics(
&mut self,
db: &'db dyn Database,
diagnostics: Vec<AdaptedDiagnostic>,
stable_ptr: SyntaxStablePtrId<'db>,
) {
let diagnostics = diagnostics.into_iter().map(Into::into).collect();
self.diagnostics.extend(into_cairo_diagnostics(db, diagnostics, stable_ptr));
}
pub fn register_result_metadata_v2(
&mut self,
db: &'db dyn Database,
input: &AttrExpansionArgs<'db>,
original: String,
result: ProcMacroResult,
) {
let expanded = result.token_stream.to_string();
let changed = expanded.as_str() != original;
let diagnostics = input.attribute_location.adapt_diagnostics(result.diagnostics);
self.register_diagnotics(db, diagnostics, input.call_site.stable_ptr);
self.any_changed = self.any_changed || changed;
let code_mappings = result.code_mappings.unwrap_or_default();
let code_mappings =
code_mappings.into_iter().map(code_mapping_from_proc_macro_server).collect();
let adapted_code_mappings = input.attribute_location.adapt_code_mappings(code_mappings);
self.item_builder.add_modified(rewrite_node_patch_from_expansion_result(
adapted_code_mappings,
result.token_stream.to_string(),
));
}
pub fn register_result_metadata_v1(&mut self, result: String, origin_span: CairoTextSpan) {
self.item_builder.add_modified(RewriteNode::Mapped {
origin: origin_span,
node: Box::new(RewriteNode::Text(result)),
});
}
pub fn into_result(self, attr_names: Vec<String>) -> AttributePluginResult<'db> {
let msg = if attr_names.len() == 1 {
"the attribute macro"
} else {
"one of the attribute macros"
};
let derive_names = attr_names.iter().join("`, `");
let note = format!("this error originates in {msg}: `{derive_names}`");
AttributePluginResult::new()
.with_remove_original_item(true)
.with_plugin_diagnostics(self.diagnostics)
.with_generated_file(
AttributeGeneratedFile::from_patch_builder("proc_attr_inner", self.item_builder)
.with_diagnostics_note(note),
)
}
}
fn rewrite_node_patch_from_expansion_result<'db>(
code_mappings: Vec<AdaptedCodeMapping>,
expanded: String,
) -> RewriteNode<'db> {
let code_mappings = code_mappings.into_iter().map(Into::into).collect_vec();
RewriteNode::TextAndMapping(expanded, code_mappings)
}
enum InnerAttrExpansionResult<'db> {
None,
Some(AttributePluginResult<'db>),
}
pub enum AttrExpansionFound<'db> {
Some(AttrExpansionArgs<'db>),
Last(AttrExpansionArgs<'db>),
None,
}
pub struct AttrExpansionArgs<'db> {
pub name: String,
pub args: TokenStream,
pub call_site: CallSiteLocation<'db>,
pub attribute_location: ExpandableAttrLocation,
pub fingerprint: u64,
}
impl<'db> AttrExpansionFound<'db> {
pub fn as_name(&self) -> Option<String> {
match self {
AttrExpansionFound::Some(AttrExpansionArgs { name, .. })
| AttrExpansionFound::Last(AttrExpansionArgs { name, .. }) => Some(name.clone()),
AttrExpansionFound::None => None,
}
}
}
pub(crate) fn parse_attribute<'db>(
db: &'db dyn Database,
defined_attributes: Vec<MacroWithHash>,
item_ast: ast::ModuleItem<'db>,
ctx: &AllocationContext,
) -> (AttrExpansionFound<'db>, AdaptedTokenStream) {
let mut token_stream_builder = TokenStreamBuilder::new(db);
let input = match item_ast.clone() {
ast::ModuleItem::Trait(ast) => {
parse_item(&ast, db, &mut token_stream_builder, ctx, defined_attributes)
}
ast::ModuleItem::Impl(ast) => {
parse_item(&ast, db, &mut token_stream_builder, ctx, defined_attributes)
}
ast::ModuleItem::Module(ast) => {
parse_item(&ast, db, &mut token_stream_builder, ctx, defined_attributes)
}
ast::ModuleItem::FreeFunction(ast) => {
parse_item(&ast, db, &mut token_stream_builder, ctx, defined_attributes)
}
ast::ModuleItem::ExternFunction(ast) => {
parse_item(&ast, db, &mut token_stream_builder, ctx, defined_attributes)
}
ast::ModuleItem::ExternType(ast) => {
parse_item(&ast, db, &mut token_stream_builder, ctx, defined_attributes)
}
ast::ModuleItem::Struct(ast) => {
parse_item(&ast, db, &mut token_stream_builder, ctx, defined_attributes)
}
ast::ModuleItem::Enum(ast) => {
parse_item(&ast, db, &mut token_stream_builder, ctx, defined_attributes)
}
ast::ModuleItem::Constant(ast) => {
parse_item(&ast, db, &mut token_stream_builder, ctx, defined_attributes)
}
ast::ModuleItem::Use(ast) => {
parse_item(&ast, db, &mut token_stream_builder, ctx, defined_attributes)
}
ast::ModuleItem::ImplAlias(ast) => {
parse_item(&ast, db, &mut token_stream_builder, ctx, defined_attributes)
}
ast::ModuleItem::TypeAlias(ast) => {
parse_item(&ast, db, &mut token_stream_builder, ctx, defined_attributes)
}
ast::ModuleItem::HeaderDoc(_) => AttrExpansionFound::None,
ast::ModuleItem::Missing(_) => AttrExpansionFound::None,
ast::ModuleItem::MacroDeclaration(_) => AttrExpansionFound::None,
ast::ModuleItem::InlineMacro(_) => AttrExpansionFound::None,
};
let token_stream = input.adapt_token_stream(token_stream_builder.build(ctx));
(input, token_stream)
}
fn parse_item<'db, T: ItemWithAttributes<'db> + ChildNodesWithoutAttributes<'db>>(
ast: &T,
db: &'db dyn Database,
token_stream_builder: &mut TokenStreamBuilder<'db>,
ctx: &AllocationContext,
defined_attributes: Vec<MacroWithHash>,
) -> AttrExpansionFound<'db> {
let span = ast.span_with_trivia(db);
let attrs = ast.item_attributes(db);
let expansion =
parse_attrs(db, &defined_attributes, token_stream_builder, attrs, span.start, ctx);
token_stream_builder.extend(ast.child_nodes_without_attributes(db));
expansion
}
fn parse_attrs<'db>(
db: &'db dyn Database,
defined_attributes: &[MacroWithHash],
builder: &mut TokenStreamBuilder<'db>,
item_attrs: Vec<ast::Attribute<'db>>,
item_start_offset: TextOffset,
ctx: &AllocationContext,
) -> AttrExpansionFound<'db> {
let mut expansion = None;
let mut last = true;
for attr in item_attrs {
if last {
let structured_attr = attr.clone().structurize(db);
let attr_name = structured_attr.id.long(db);
let found = defined_attributes.iter().find(|m| m.name.as_str() == attr_name.as_str());
if let Some(m) = found {
if expansion.is_none() {
let mut args_builder = TokenStreamBuilder::new(db);
args_builder.add_node(attr.arguments(db).as_syntax_node());
let args = args_builder.build(ctx);
expansion = Some(AttrExpansionArgs {
name: attr
.attr(db)
.as_syntax_node()
.get_text_without_trivia(db)
.to_string(db),
args,
call_site: CallSiteLocation::new(&attr, db),
attribute_location: ExpandableAttrLocation::new(
&attr,
item_start_offset,
db,
),
fingerprint: m.hash,
});
continue;
} else {
last = false;
}
}
}
builder.add_node(attr.as_syntax_node());
}
match (expansion, last) {
(Some(args), true) => AttrExpansionFound::Last(args),
(Some(args), false) => AttrExpansionFound::Some(args),
(None, _) => AttrExpansionFound::None,
}
}
fn parse_derive<'db>(
db: &'db dyn Database,
defined_derives: &[MacroWithHash],
item_ast: ast::ModuleItem<'db>,
) -> Vec<(MacroWithHash, CallSiteLocation<'db>)> {
let attrs = match item_ast {
ast::ModuleItem::Struct(struct_ast) => {
Some(struct_ast.query_attr(db, DERIVE_ATTR).collect_vec())
}
ast::ModuleItem::Enum(enum_ast) => Some(enum_ast.query_attr(db, DERIVE_ATTR).collect_vec()),
_ => None,
};
attrs
.unwrap_or_default()
.iter()
.map(|attr| attr.clone().structurize(db))
.flat_map(|attr| attr.args.into_iter())
.filter_map(|attr| {
let AttributeArgVariant::Unnamed(value) = attr.clone().variant else {
return None;
};
let Expr::Path(path) = value else {
return None;
};
let path = path.segments(db).elements(db);
let path = path.last()?;
let PathSegment::Simple(segment) = path else {
return None;
};
let ident = segment.ident(db);
let value = ident.text(db).to_string(db);
let matching_derive = defined_derives
.iter()
.find(|derive| derive.name.to_case(Case::Pascal) == value)
.cloned()?;
Some((matching_derive, CallSiteLocation::new(&segment, db)))
})
.collect()
}
fn expand_derives<'db>(
db: &'db dyn Database,
expansion_context: ProcMacroScope,
item_ast: ast::ModuleItem<'db>,
derives: Vec<(MacroWithHash, CallSiteLocation<'db>)>,
stream_metadata: TokenStreamMetadata,
) -> Option<PluginResult<'db>> {
let mut token_stream_builder = TokenStreamBuilder::new(db);
token_stream_builder.add_node(item_ast.as_syntax_node());
token_stream_builder.with_metadata(stream_metadata.clone());
let ctx = AllocationContext::default();
let token_stream = token_stream_builder.build(&ctx);
if derives.is_empty() {
return None;
}
let stable_ptr = derives[0].1.stable_ptr;
let span_db = stable_ptr.lookup(db).span(db);
let call_site = TextSpan { start: span_db.start.as_u32(), end: span_db.end.as_u32() };
let mut derives = derives;
derives.sort_by_key(|(m, _)| m.name.clone());
let mut hasher = StableHasher::new();
derives.iter().for_each(|(m, _)| {
m.hash.hash(&mut hasher);
});
let derive_names: Vec<String> = derives.into_iter().map(|a| a.0.name).collect();
let result = get_derive_expansion(
db,
ExpandDeriveParams {
context: expansion_context,
derives: derive_names.clone(),
item: token_stream,
call_site,
},
hasher.finish(),
);
Some(PluginResult {
code: if result.token_stream.is_empty() {
None
} else {
let content = result.token_stream.to_string();
let msg = if derive_names.len() == 1 {
"the derive macro"
} else {
"one of the derive macros"
};
let derive_names = derive_names.iter().join("`, `");
let note = format!("this error originates in {msg}: `{derive_names}`");
let code_mappings = result
.code_mappings
.map(|x| x.into_iter().map(code_mapping_from_proc_macro_server).collect())
.unwrap_or_default();
Some(PluginGeneratedFile {
name: "proc_macro_derive".into(),
code_mappings,
content,
aux_data: None,
diagnostics_note: Some(note),
is_unhygienic: false,
})
},
diagnostics: into_cairo_diagnostics(db, result.diagnostics, stable_ptr),
remove_original_item: false,
})
}
#[allow(clippy::too_many_arguments)]
fn expand_attribute<'db>(
db: &'db dyn Database,
expansion_context: ProcMacroScope,
last: bool,
token_stream: AdaptedTokenStream,
original_node: SyntaxNode<'db>,
input: AttrExpansionArgs<'db>,
) -> AttributePluginResult<'db> {
let result = get_attribute_expansion(
db,
ExpandAttributeParams {
context: expansion_context,
args: input.args.clone(),
attr: input.name.clone(),
item: token_stream.clone().into(),
adapted_call_site: input.attribute_location.adapted_call_site().into(),
},
input.fingerprint,
);
let diagnostics =
input.attribute_location.adapt_diagnostics(result.diagnostics).into_iter().collect();
if result.token_stream.is_empty() {
return AttributePluginResult::new().with_remove_original_item(true).with_diagnostics(
db,
input.call_site.stable_ptr,
diagnostics,
);
}
if last && token_stream.to_string() == result.token_stream.to_string() {
return AttributePluginResult::new().with_diagnostics(
db,
input.call_site.stable_ptr,
diagnostics,
);
}
let file_name = format!("proc_macro_{}", input.name);
let content = result.token_stream.to_string();
let mappings: Vec<CodeMapping> = result
.code_mappings
.map(|mappings| {
input
.attribute_location
.adapt_code_mappings(
mappings.into_iter().map(code_mapping_from_proc_macro_server).collect(),
)
.into_iter()
.map(Into::into)
.collect()
})
.unwrap_or_else(|| {
vec![CodeMapping {
origin: CodeOrigin::Span(original_node.span_without_trivia(db)),
span: CairoTextSpan::from_str(&content),
}]
});
AttributePluginResult::new()
.with_remove_original_item(true)
.with_diagnostics(db, input.call_site.stable_ptr, diagnostics)
.with_generated_file(
AttributeGeneratedFile::new(file_name)
.with_content(content)
.with_code_mappings(mappings)
.with_diagnostics_note(format!(
"this error originates in the attribute macro: `{}`",
input.name
)),
)
}
fn calculate_metadata(
db: &dyn Database,
item_ast: ast::ModuleItem,
edition: Edition,
) -> TokenStreamMetadata {
fn short_hash(hashable: impl std::hash::Hash) -> String {
let mut hasher = StableHasher::new();
hashable.hash(&mut hasher);
hasher.finish_as_short_hash()
}
let stable_ptr = item_ast.clone().stable_ptr(db).untyped();
let file_path = stable_ptr.file_id(db).full_path(db);
let file_id = short_hash(file_path.clone());
let edition = serde_json::to_value(edition).unwrap();
TokenStreamMetadata::new(file_path, file_id, edition)
}
#[derive(Default)]
pub struct AttributePluginResult<'db> {
diagnostics: Vec<PluginDiagnostic<'db>>,
remove_original_item: bool,
code: Option<PluginGeneratedFile>,
}
impl<'db> AttributePluginResult<'db> {
pub fn new() -> Self {
Self::default()
}
pub fn with_diagnostics(
mut self,
db: &'db dyn Database,
call_site_stable_ptr: SyntaxStablePtrId<'db>,
diagnostics: Vec<AdaptedDiagnostic>,
) -> Self {
let diagnostics = diagnostics.into_iter().map(Into::into).collect();
self.diagnostics = into_cairo_diagnostics(db, diagnostics, call_site_stable_ptr);
self
}
pub fn with_plugin_diagnostics(mut self, diagnostics: Vec<PluginDiagnostic<'db>>) -> Self {
self.diagnostics = diagnostics;
self
}
pub fn with_remove_original_item(mut self, remove: bool) -> Self {
self.remove_original_item = remove;
self
}
pub fn with_generated_file(mut self, generated_file: AttributeGeneratedFile) -> Self {
self.code = Some(generated_file.into());
self
}
}
impl<'db> From<AttributePluginResult<'db>> for PluginResult<'db> {
fn from(value: AttributePluginResult<'db>) -> Self {
PluginResult {
diagnostics: value.diagnostics,
remove_original_item: value.remove_original_item,
code: value.code,
}
}
}
pub struct AttributeGeneratedFile {
name: String,
content: String,
code_mappings: Vec<CodeMapping>,
diagnostics_note: Option<String>,
}
impl AttributeGeneratedFile {
pub fn new(name: impl ToString) -> Self {
Self {
name: name.to_string(),
content: Default::default(),
code_mappings: Default::default(),
diagnostics_note: Default::default(),
}
}
pub fn from_patch_builder(name: impl ToString, item_builder: PatchBuilder<'_>) -> Self {
let (expanded, mut code_mappings) = item_builder.build();
code_mappings.pop();
Self {
name: name.to_string(),
content: expanded,
code_mappings,
diagnostics_note: Default::default(),
}
}
pub fn with_content(mut self, content: impl ToString) -> Self {
self.content = content.to_string();
self
}
pub fn with_code_mappings(mut self, code_mappings: Vec<CodeMapping>) -> Self {
self.code_mappings = code_mappings;
self
}
pub fn with_diagnostics_note(mut self, diagnostics_note: impl ToString) -> Self {
self.diagnostics_note = Some(diagnostics_note.to_string());
self
}
}
impl From<AttributeGeneratedFile> for PluginGeneratedFile {
fn from(value: AttributeGeneratedFile) -> Self {
PluginGeneratedFile {
name: value.name,
content: value.content,
code_mappings: value.code_mappings,
aux_data: None,
diagnostics_note: value.diagnostics_note,
is_unhygienic: false,
}
}
}