Skip to main content

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::{ErrorCode, 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    /// An optional error code associated with the diagnostic.
115    pub error_code: Option<ErrorCode>,
116}
117impl<'db> PluginDiagnostic<'db> {
118    pub fn error(
119        stable_ptr: impl Into<SyntaxStablePtrId<'db>>,
120        message: String,
121    ) -> PluginDiagnostic<'db> {
122        PluginDiagnostic {
123            stable_ptr: stable_ptr.into(),
124            message,
125            severity: Severity::Error,
126            inner_span: None,
127            error_code: None,
128        }
129    }
130
131    /// Creates a diagnostic, pointing to an inner span inside the given stable pointer.
132    pub fn error_with_inner_span(
133        db: &dyn Database,
134        stable_ptr: impl Into<SyntaxStablePtrId<'db>>,
135        inner_span: SyntaxNode<'_>,
136        message: String,
137    ) -> PluginDiagnostic<'db> {
138        let stable_ptr = stable_ptr.into();
139        let offset = inner_span.offset(db) - stable_ptr.lookup(db).offset(db);
140        let width = inner_span.width(db);
141        PluginDiagnostic {
142            stable_ptr,
143            message,
144            severity: Severity::Error,
145            inner_span: Some((offset, width)),
146            error_code: None,
147        }
148    }
149
150    pub fn warning(
151        stable_ptr: impl Into<SyntaxStablePtrId<'db>>,
152        message: String,
153    ) -> PluginDiagnostic<'db> {
154        PluginDiagnostic {
155            stable_ptr: stable_ptr.into(),
156            message,
157            severity: Severity::Warning,
158            inner_span: None,
159            error_code: None,
160        }
161    }
162
163    /// Updates the diagnostic with an error code.
164    pub fn with_error_code(self, error_code: ErrorCode) -> PluginDiagnostic<'db> {
165        PluginDiagnostic { error_code: Some(error_code), ..self }
166    }
167}
168
169/// A structure containing additional info about the current module item on which macro plugin
170/// operates.
171pub struct MacroPluginMetadata<'a> {
172    /// Config set of the crate to which the current item belongs.
173    pub cfg_set: &'a CfgSet,
174    /// The possible derives declared by any plugin.
175    pub declared_derives: &'a OrderedHashSet<SmolStrId<'a>>,
176    /// The allowed features at the macro activation site.
177    pub allowed_features: &'a OrderedHashSet<SmolStrId<'a>>,
178    /// The edition of the crate to which the current item belongs.
179    pub edition: Edition,
180}
181
182/// A trait for a macro plugin: an external plugin that generates additional code for items.
183pub trait MacroPlugin: std::fmt::Debug + Sync + Send + Any {
184    /// Generates code for an item. If no code should be generated returns None.
185    /// Otherwise, returns `PluginResult` with the generated virtual submodule.
186    fn generate_code<'db>(
187        &self,
188        db: &'db dyn Database,
189        item_ast: ast::ModuleItem<'db>,
190        metadata: &MacroPluginMetadata<'_>,
191    ) -> PluginResult<'db>;
192
193    /// Attributes this plugin uses.
194    /// Attributes the plugin uses without declaring here are likely to cause a compilation error
195    /// for unknown attributes.
196    /// Note: They may not cause a diagnostic if some other plugin declares such attribute, but
197    /// plugin writers should not rely on that.
198    fn declared_attributes<'db>(&self, db: &'db dyn Database) -> Vec<SmolStrId<'db>>;
199
200    /// Derives this plugin supplies.
201    /// Any derived classes the plugin supplies without declaring here are likely to cause a
202    /// compilation error for unknown derive.
203    /// Note: They may not cause a diagnostic if some other plugin declares such derive, but
204    /// plugin writers should not rely on that.
205    fn declared_derives<'db>(&self, _db: &'db dyn Database) -> Vec<SmolStrId<'db>> {
206        Vec::new()
207    }
208
209    /// Attributes that should mark the function as an executable.
210    /// Functions marked with executable attributes will be listed
211    /// in a dedicated field in the generated program.
212    /// Must return a subset of `declared_attributes`.
213    /// This mechanism is optional.
214    fn executable_attributes<'db>(&self, _db: &'db dyn Database) -> Vec<SmolStrId<'db>> {
215        Vec::new()
216    }
217
218    /// Attributes that mark a type as a phantom type. Must return a subset of
219    /// `declared_attributes`.
220    /// This mechanism is optional.
221    fn phantom_type_attributes<'db>(&self, _db: &'db dyn Database) -> Vec<SmolStrId<'db>> {
222        Vec::new()
223    }
224
225    /// A `TypeId` of the plugin, used to compare the concrete types
226    /// of plugins given as trait objects.
227    fn plugin_type_id(&self) -> any::TypeId {
228        self.type_id()
229    }
230}
231
232/// Result of plugin code generation.
233#[derive(Default)]
234pub struct InlinePluginResult<'db> {
235    pub code: Option<PluginGeneratedFile>,
236    /// Diagnostics.
237    pub diagnostics: Vec<PluginDiagnostic<'db>>,
238}
239
240pub trait InlineMacroExprPlugin: std::fmt::Debug + Sync + Send + Any {
241    /// Generates code for an item. If no code should be generated returns None.
242    /// Otherwise, returns (virtual_module_name, module_content), and a virtual submodule
243    /// with that name and content should be created.
244    fn generate_code<'db>(
245        &self,
246        db: &'db dyn Database,
247        item_ast: &ast::ExprInlineMacro<'db>,
248        metadata: &MacroPluginMetadata<'_>,
249    ) -> InlinePluginResult<'db>;
250
251    /// Allows for the plugin to provide documentation for an inline macro.
252    fn documentation(&self) -> Option<String> {
253        None
254    }
255
256    /// A `TypeId` of the plugin, used to compare the concrete types
257    /// of plugins given as trait objects.
258    fn plugin_type_id(&self) -> any::TypeId {
259        self.type_id()
260    }
261}
262
263/// A trait for easier addition of macro plugins.
264pub trait NamedPlugin: Default + 'static {
265    const NAME: &'static str;
266}