cairo_lang_defs/plugin.rs
1use std::any::{self, Any};
2use std::ops::Deref;
3use std::sync::Arc;
4
5use cairo_lang_diagnostics::Severity;
6use cairo_lang_filesystem::cfg::CfgSet;
7use cairo_lang_filesystem::db::Edition;
8use cairo_lang_filesystem::ids::CodeMapping;
9use cairo_lang_filesystem::span::TextWidth;
10use cairo_lang_syntax::node::db::SyntaxGroup;
11use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
12use cairo_lang_syntax::node::{SyntaxNode, ast};
13use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
14use serde::{Deserialize, Serialize};
15use smol_str::SmolStr;
16
17/// A trait for arbitrary data that a macro generates along with the generated file.
18#[typetag::serde]
19pub trait GeneratedFileAuxData: std::fmt::Debug + Sync + Send {
20 fn as_any(&self) -> &dyn Any;
21 fn eq(&self, other: &dyn GeneratedFileAuxData) -> bool;
22}
23
24#[derive(Clone, Debug, Serialize, Deserialize)]
25pub struct DynGeneratedFileAuxData(pub Arc<dyn GeneratedFileAuxData>);
26impl DynGeneratedFileAuxData {
27 pub fn new<T: GeneratedFileAuxData + 'static>(aux_data: T) -> Self {
28 DynGeneratedFileAuxData(Arc::new(aux_data))
29 }
30}
31impl Deref for DynGeneratedFileAuxData {
32 type Target = Arc<dyn GeneratedFileAuxData>;
33
34 fn deref(&self) -> &Self::Target {
35 &self.0
36 }
37}
38impl PartialEq for DynGeneratedFileAuxData {
39 fn eq(&self, that: &DynGeneratedFileAuxData) -> bool {
40 GeneratedFileAuxData::eq(&*self.0, &*that.0)
41 }
42}
43impl Eq for DynGeneratedFileAuxData {}
44
45/// Virtual code file generated by a plugin.
46pub struct PluginGeneratedFile {
47 /// Name for the virtual file. Will appear in diagnostics.
48 pub name: SmolStr,
49 /// Code content for the file.
50 pub content: String,
51 /// A code mapper, to allow more readable diagnostics that originate in plugin generated
52 /// virtual files.
53 pub code_mappings: Vec<CodeMapping>,
54 /// Arbitrary data that the plugin generates along with the file.
55 pub aux_data: Option<DynGeneratedFileAuxData>,
56 /// Diagnostic note for the plugin generated file.
57 /// This will be used as [`cairo_lang_diagnostics::DiagnosticNote`] on diagnostics originating
58 /// from this file.
59 pub diagnostics_note: Option<String>,
60 /// This needs to be set to true if the plugin is unhygienic, i.e. it does not preserve
61 /// variable hygiene.
62 pub is_unhygienic: bool,
63}
64
65/// Result of plugin code generation.
66#[derive(Default)]
67pub struct PluginResult {
68 /// Filename, content.
69 pub code: Option<PluginGeneratedFile>,
70 /// Diagnostics.
71 pub diagnostics: Vec<PluginDiagnostic>,
72 /// If true - the original item should be removed, if false - it should remain as is.
73 pub remove_original_item: bool,
74}
75
76/// A diagnostic generated by a plugin.
77#[derive(Clone, Debug, Eq, Hash, PartialEq)]
78pub struct PluginDiagnostic {
79 /// The stable pointer of the syntax node that caused the diagnostic.
80 pub stable_ptr: SyntaxStablePtrId,
81 pub message: String,
82 /// The severity of the diagnostic.
83 pub severity: Severity,
84 /// An optional inner span inside the stable pointer that caused the diagnostic. Useful for
85 /// diagnostics caused by inline macros, since the syntax of the arguments is a token tree and
86 /// is not segmented into each argument.
87 /// The tuple is (offset, width).
88 pub inner_span: Option<(TextWidth, TextWidth)>,
89}
90impl PluginDiagnostic {
91 pub fn error(stable_ptr: impl Into<SyntaxStablePtrId>, message: String) -> PluginDiagnostic {
92 PluginDiagnostic {
93 stable_ptr: stable_ptr.into(),
94 message,
95 severity: Severity::Error,
96 inner_span: None,
97 }
98 }
99
100 /// Creates a diagnostic, pointing to an inner span inside the given stable pointer.
101 pub fn error_with_inner_span(
102 db: &dyn SyntaxGroup,
103 stable_ptr: impl Into<SyntaxStablePtrId>,
104 inner_span: SyntaxNode,
105 message: String,
106 ) -> PluginDiagnostic {
107 let stable_ptr = stable_ptr.into();
108 let offset = inner_span.offset(db) - stable_ptr.lookup(db).offset(db);
109 let width = inner_span.width(db);
110 PluginDiagnostic {
111 stable_ptr,
112 message,
113 severity: Severity::Error,
114 inner_span: Some((offset, width)),
115 }
116 }
117
118 pub fn warning(stable_ptr: impl Into<SyntaxStablePtrId>, message: String) -> PluginDiagnostic {
119 PluginDiagnostic {
120 stable_ptr: stable_ptr.into(),
121 message,
122 severity: Severity::Warning,
123 inner_span: None,
124 }
125 }
126}
127
128/// A structure containing additional info about the current module item on which macro plugin
129/// operates.
130pub struct MacroPluginMetadata<'a> {
131 /// Config set of the crate to which the current item belongs.
132 pub cfg_set: &'a CfgSet,
133 /// The possible derives declared by any plugin.
134 pub declared_derives: &'a OrderedHashSet<String>,
135 /// The allowed features at the macro activation site.
136 pub allowed_features: &'a OrderedHashSet<SmolStr>,
137 /// The edition of the crate to which the current item belongs.
138 pub edition: Edition,
139}
140
141// TODO(spapini): Move to another place.
142/// A trait for a macro plugin: external plugin that generates additional code for items.
143pub trait MacroPlugin: std::fmt::Debug + Sync + Send + Any {
144 /// Generates code for an item. If no code should be generated returns None.
145 /// Otherwise, returns (virtual_module_name, module_content), and a virtual submodule
146 /// with that name and content should be created.
147 fn generate_code(
148 &self,
149 db: &dyn SyntaxGroup,
150 item_ast: ast::ModuleItem,
151 metadata: &MacroPluginMetadata<'_>,
152 ) -> PluginResult;
153
154 /// Attributes this plugin uses.
155 /// Attributes the plugin uses without declaring here are likely to cause a compilation error
156 /// for unknown attribute.
157 /// Note: They may not cause a diagnostic if some other plugin declares such attribute, but
158 /// plugin writers should not rely on that.
159 fn declared_attributes(&self) -> Vec<String>;
160
161 /// Derives this plugin supplies.
162 /// Any derived classes the plugin supplies without declaring here are likely to cause a
163 /// compilation error for unknown derive.
164 /// Note: They may not cause a diagnostic if some other plugin declares such derive, but
165 /// plugin writers should not rely on that.
166 fn declared_derives(&self) -> Vec<String> {
167 Vec::new()
168 }
169
170 /// Attributes that should mark the function as an executable.
171 /// Functions marked with executable attributes will be listed
172 /// in a dedicated field in the generated program.
173 /// Must return a subset of `declared_attributes`.
174 /// This mechanism is optional.
175 fn executable_attributes(&self) -> Vec<String> {
176 Vec::new()
177 }
178
179 /// Attributes that mark a type as a phantom type. Must return a subset of
180 /// `declared_attributes`.
181 /// This mechanism is optional.
182 fn phantom_type_attributes(&self) -> Vec<String> {
183 Vec::new()
184 }
185
186 /// A `TypeId` of the plugin, used to compare the concrete types
187 /// of plugins given as trait objects.
188 fn plugin_type_id(&self) -> any::TypeId {
189 self.type_id()
190 }
191}
192
193/// Result of plugin code generation.
194#[derive(Default)]
195pub struct InlinePluginResult {
196 pub code: Option<PluginGeneratedFile>,
197 /// Diagnostics.
198 pub diagnostics: Vec<PluginDiagnostic>,
199}
200
201pub trait InlineMacroExprPlugin: std::fmt::Debug + Sync + Send + Any {
202 /// Generates code for an item. If no code should be generated returns None.
203 /// Otherwise, returns (virtual_module_name, module_content), and a virtual submodule
204 /// with that name and content should be created.
205 fn generate_code(
206 &self,
207 db: &dyn SyntaxGroup,
208 item_ast: &ast::ExprInlineMacro,
209 metadata: &MacroPluginMetadata<'_>,
210 ) -> InlinePluginResult;
211
212 /// Allows for the plugin to provide documentation for an inline macro.
213 fn documentation(&self) -> Option<String> {
214 None
215 }
216
217 /// A `TypeId` of the plugin, used to compare the concrete types
218 /// of plugins given as trait objects.
219 fn plugin_type_id(&self) -> any::TypeId {
220 self.type_id()
221 }
222}
223
224/// A trait for easier addition of macro plugins.
225pub trait NamedPlugin: Default + 'static {
226 const NAME: &'static str;
227}