Skip to main content

cairo_lang_plugins/plugins/
utils.rs

1use cairo_lang_filesystem::ids::SmolStrId;
2use cairo_lang_syntax::node::helpers::{GenericParamEx, IsDependentType};
3use cairo_lang_syntax::node::{Terminal, TypedSyntaxNode, ast};
4use itertools::{Itertools, chain};
5use salsa::Database;
6
7/// Information on struct members or enum variants.
8pub struct MemberInfo<'a> {
9    pub name: &'a str,
10    pub ty: &'a str,
11    pub attributes: ast::AttributeList<'a>,
12    pub is_generics_dependent: bool,
13}
14impl<'a> MemberInfo<'a> {
15    pub fn impl_name(&self, trt: &str) -> String {
16        if self.is_generics_dependent {
17            let short_name = trt.split("::").last().unwrap_or(trt);
18            format!("__MEMBER_IMPL_{}_{short_name}", self.name)
19        } else {
20            format!("{}::<{}>", trt, self.ty)
21        }
22    }
23    pub fn drop_with(&self) -> String {
24        if self.is_generics_dependent {
25            format!("core::internal::DropWith::<{}, {}>", self.ty, self.impl_name("Drop"))
26        } else {
27            format!("core::internal::InferDrop::<{}>", self.ty)
28        }
29    }
30    pub fn destruct_with(&self) -> String {
31        if self.is_generics_dependent {
32            format!("core::internal::DestructWith::<{}, {}>", self.ty, self.impl_name("Destruct"))
33        } else {
34            format!("core::internal::InferDestruct::<{}>", self.ty)
35        }
36    }
37}
38
39/// Information on the type being derived.
40pub enum TypeVariant {
41    Enum,
42    Struct,
43}
44
45/// Information on generic params.
46pub struct GenericParamsInfo<'a> {
47    /// All the generic param names, at the original order.
48    pub param_names: Vec<&'a str>,
49    /// The full generic params, including keywords and definitions.
50    pub full_params: Vec<&'a str>,
51}
52impl<'a> GenericParamsInfo<'a> {
53    /// Extracts the information on generic params.
54    pub fn new(
55        db: &'a dyn Database,
56        generic_params: ast::OptionWrappedGenericParamList<'a>,
57    ) -> Self {
58        let ast::OptionWrappedGenericParamList::WrappedGenericParamList(gens) = generic_params
59        else {
60            return Self { param_names: Default::default(), full_params: Default::default() };
61        };
62        let (param_names, full_params) = gens
63            .generic_params(db)
64            .elements(db)
65            .map(|param| {
66                let name =
67                    param.name(db).map(|n| n.text(db).long(db).as_str()).unwrap_or_else(|| "_");
68                let full_param =
69                    param.as_syntax_node().get_text_without_trivia(db).long(db).as_str();
70                (name, full_param)
71            })
72            .unzip();
73        Self { param_names, full_params }
74    }
75}
76
77/// Information for the type being processed by a plugin.
78pub struct PluginTypeInfo<'a> {
79    pub name: &'a str,
80    pub attributes: ast::AttributeList<'a>,
81    pub generics: GenericParamsInfo<'a>,
82    pub members_info: Vec<MemberInfo<'a>>,
83    pub type_variant: TypeVariant,
84}
85impl<'a> PluginTypeInfo<'a> {
86    /// Extracts the information on the type being derived.
87    pub fn new(db: &'a dyn Database, item_ast: &ast::ModuleItem<'a>) -> Option<Self> {
88        match item_ast {
89            ast::ModuleItem::Struct(struct_ast) => {
90                let generics = GenericParamsInfo::new(db, struct_ast.generic_params(db));
91                let interned =
92                    generics.param_names.iter().map(|s| SmolStrId::from(db, *s)).collect_vec();
93                let members_info = extract_members(db, struct_ast.members(db), &interned);
94                Some(Self {
95                    name: struct_ast.name(db).text(db).long(db).as_str(),
96                    attributes: struct_ast.attributes(db),
97                    generics,
98                    members_info,
99                    type_variant: TypeVariant::Struct,
100                })
101            }
102            ast::ModuleItem::Enum(enum_ast) => {
103                let generics = GenericParamsInfo::new(db, enum_ast.generic_params(db));
104                let interned =
105                    generics.param_names.iter().map(|s| SmolStrId::from(db, *s)).collect_vec();
106                let members_info = extract_variants(db, enum_ast.variants(db), &interned);
107                Some(Self {
108                    name: enum_ast.name(db).text(db).long(db).as_str(),
109                    attributes: enum_ast.attributes(db),
110                    generics,
111                    members_info,
112                    type_variant: TypeVariant::Enum,
113                })
114            }
115            _ => None,
116        }
117    }
118
119    /// Returns a full derived impl header - given `derived_trait` - and the `dependent_traits`
120    /// required for all its members.
121    pub fn impl_header(&self, derived_trait: &str, dependent_traits: &[&str]) -> String {
122        let derived_trait_name = derived_trait.split("::").last().unwrap_or(derived_trait);
123        format!(
124            "impl {name}{derived_trait_name}<{generics}> of {derived_trait}::<{full_typename}>",
125            name = self.name,
126            generics = self
127                .impl_generics(dependent_traits, |trt, ty| format!("{trt}<{ty}>"))
128                .iter()
129                .format(", "),
130            full_typename = self.full_typename(),
131        )
132    }
133
134    /// Returns the expected generics parameters for a derived impl definition.
135    ///
136    /// `dep_req` - is the formatting of a trait and the type as a concrete trait.
137    pub fn impl_generics(
138        &self,
139        dependent_traits: &[&str],
140        dep_req: fn(&str, &str) -> String,
141    ) -> Vec<String> {
142        chain!(
143            self.generics.full_params.iter().map(ToString::to_string),
144            self.members_info.iter().filter(|m| m.is_generics_dependent).flat_map(|m| {
145                dependent_traits
146                    .iter()
147                    .cloned()
148                    .map(move |trt| format!("impl {}: {}", m.impl_name(trt), dep_req(trt, m.ty)))
149            })
150        )
151        .collect()
152    }
153
154    /// Formats the full typename of the type, including generic args.
155    pub fn full_typename(&self) -> String {
156        if self.generics.param_names.is_empty() {
157            self.name.to_string()
158        } else {
159            format!("{}<{}>", self.name, self.generics.param_names.iter().format(", "))
160        }
161    }
162}
163
164/// Extracts the information on the members of the struct.
165fn extract_members<'a>(
166    db: &'a dyn Database,
167    members: ast::MemberList<'a>,
168    generics: &[SmolStrId<'a>],
169) -> Vec<MemberInfo<'a>> {
170    members
171        .elements(db)
172        .map(|member| MemberInfo {
173            name: member.name(db).text(db).long(db).as_str(),
174            ty: member
175                .type_clause(db)
176                .ty(db)
177                .as_syntax_node()
178                .get_text_without_trivia(db)
179                .long(db)
180                .as_str(),
181            attributes: member.attributes(db),
182            is_generics_dependent: member.type_clause(db).ty(db).is_dependent_type(db, generics),
183        })
184        .collect()
185}
186
187/// Extracts the information on the variants of the enum.
188fn extract_variants<'a>(
189    db: &'a dyn Database,
190    variants: ast::VariantList<'a>,
191    generics: &[SmolStrId<'a>],
192) -> Vec<MemberInfo<'a>> {
193    variants
194        .elements(db)
195        .map(|variant| MemberInfo {
196            name: variant.name(db).text(db).long(db).as_str(),
197            ty: match variant.type_clause(db) {
198                ast::OptionTypeClause::Empty(_) => "()",
199                ast::OptionTypeClause::TypeClause(t) => {
200                    t.ty(db).as_syntax_node().get_text_without_trivia(db).long(db).as_str()
201                }
202            },
203            attributes: variant.attributes(db),
204            is_generics_dependent: match variant.type_clause(db) {
205                ast::OptionTypeClause::Empty(_) => false,
206                ast::OptionTypeClause::TypeClause(t) => t.ty(db).is_dependent_type(db, generics),
207            },
208        })
209        .collect()
210}