cairo_lang_defs/
plugin.rs

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