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