Skip to main content

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}