Skip to main content

mproto_codegen/codegen/
codegen_cx.rs

1use crate::{
2    ast::{PrimitiveType, QualifiedIdentifier, Type, TypeDef},
3    codegen::{MprotoJs, MprotoLang, MprotoRust},
4    Database,
5};
6
7pub struct CodegenCx<'a> {
8    pub db: &'a Database,
9    pub local_def_source: Option<&'a str>,
10    pub is_package: bool,
11    pub type_param_bindings: TypeParamBindings<'a>,
12}
13
14impl<'a> CodegenCx<'a> {
15    pub fn new(db: &'a Database, local_def_source: Option<&'a str>, is_package: bool) -> Self {
16        Self {
17            db,
18            local_def_source,
19            is_package,
20            type_param_bindings: TypeParamBindings::empty(),
21        }
22    }
23
24    pub fn new_with_type_params(
25        db: &'a Database,
26        local_def_source: Option<&'a str>,
27        is_package: bool,
28        type_params: &'a [impl AsRef<str>],
29    ) -> Self {
30        Self {
31            db,
32            local_def_source,
33            is_package,
34            type_param_bindings: TypeParamBindings::from_type_params(type_params),
35        }
36    }
37
38    pub fn rust_import_qualified(
39        &self,
40        qualified_identifier: &QualifiedIdentifier,
41    ) -> genco::lang::rust::Tokens {
42        MprotoRust::import_qualified(self.db, self.local_def_source, qualified_identifier)
43    }
44
45    pub fn js_import_qualified(
46        &self,
47        qualified_identifier: &QualifiedIdentifier,
48    ) -> genco::lang::js::Tokens {
49        MprotoJs::import_qualified(self.db, self.local_def_source, qualified_identifier)
50    }
51
52    pub fn with_type_param_bindings(&self, type_param_bindings: &TypeParamBindings<'a>) -> Self {
53        Self {
54            db: self.db,
55            local_def_source: self.local_def_source,
56            is_package: self.is_package,
57            type_param_bindings: type_param_bindings.clone(),
58        }
59    }
60
61    pub fn with_type_params(&self, type_params: &'a [impl AsRef<str>]) -> Self {
62        Self {
63            db: self.db,
64            local_def_source: self.local_def_source,
65            is_package: self.is_package,
66            type_param_bindings: TypeParamBindings::from_type_params(type_params),
67        }
68    }
69
70    pub fn with_type_args(
71        &'a self,
72        type_params: &'a [impl AsRef<str>],
73        type_args: &'a [Type],
74    ) -> Self {
75        Self {
76            db: self.db,
77            local_def_source: self.local_def_source,
78            is_package: self.is_package,
79            type_param_bindings: TypeParamBindings::from_type_args(
80                &self.type_param_bindings,
81                type_params,
82                type_args,
83            ),
84        }
85    }
86
87    pub fn resolve_type_param_binding(&self, type_name: &str) -> Option<TypeParamBinding<'a>> {
88        self.type_param_bindings.resolve(type_name)
89    }
90
91    pub fn resolve_type(&self, ident: &QualifiedIdentifier) -> Option<ResolvedType<'a>> {
92        if ident.module.is_none() {
93            if let Some(type_param_binding) = self.resolve_type_param_binding(&ident.name) {
94                return match type_param_binding {
95                    TypeParamBinding::Unbound => Some(ResolvedType::UnboundParam),
96                    TypeParamBinding::Bound { value, binding_cx } => {
97                        Some(ResolvedType::BoundParam { value, binding_cx })
98                    }
99                };
100            }
101        }
102
103        if let Some(type_def) = self.db.lookup_type_def(ident) {
104            Some(ResolvedType::Defined(type_def))
105        } else {
106            None
107        }
108    }
109}
110
111pub enum ResolvedType<'a> {
112    /// Defined type
113    Defined(&'a TypeDef),
114    /// Unbound type parameter
115    UnboundParam,
116    /// Bound type parameter
117    BoundParam {
118        value: &'a Type,
119        binding_cx: &'a TypeParamBindings<'a>,
120    },
121}
122
123#[derive(Copy, Clone)]
124pub enum TypeParamBinding<'a> {
125    Unbound,
126    Bound {
127        value: &'a Type,
128        binding_cx: &'a TypeParamBindings<'a>,
129    },
130}
131
132#[derive(Clone)]
133pub struct TypeParamBindings<'a> {
134    bindings: Vec<(&'a str, TypeParamBinding<'a>)>,
135}
136
137impl<'a> TypeParamBindings<'a> {
138    pub fn empty() -> Self {
139        Self {
140            bindings: Vec::new(),
141        }
142    }
143
144    pub fn from_type_params(type_params: &'a [impl AsRef<str>]) -> Self {
145        Self {
146            bindings: type_params
147                .iter()
148                .map(|x| (x.as_ref(), TypeParamBinding::Unbound))
149                .collect(),
150        }
151    }
152
153    pub fn from_type_args(
154        parent_bindings: &'a TypeParamBindings<'a>,
155        type_params: &'a [impl AsRef<str>],
156        type_args: &'a [Type],
157    ) -> Self {
158        Self {
159            bindings: type_params
160                .iter()
161                .map(|x| x.as_ref())
162                .zip(type_args.iter().map(|type_arg| TypeParamBinding::Bound {
163                    value: type_arg,
164                    binding_cx: parent_bindings,
165                }))
166                .collect(),
167        }
168    }
169
170    pub fn resolve(&self, type_name: &str) -> Option<TypeParamBinding<'a>> {
171        for &(binding_name, binding) in &self.bindings {
172            if binding_name == type_name {
173                return Some(binding);
174            }
175        }
176
177        None
178    }
179}
180
181pub fn type_uses_param(cx: &CodegenCx, ty: &Type, param_name: &str) -> bool {
182    match ty {
183        Type::Primitive(PrimitiveType::Box(inner_ty)) => type_uses_param(cx, inner_ty, param_name),
184        Type::Primitive(PrimitiveType::List(item_ty)) => type_uses_param(cx, item_ty, param_name),
185        Type::Primitive(PrimitiveType::Option(inner_ty)) => {
186            type_uses_param(cx, inner_ty, param_name)
187        }
188        Type::Primitive(PrimitiveType::Result(ok_ty, err_ty)) => {
189            type_uses_param(cx, ok_ty, param_name) || type_uses_param(cx, err_ty, param_name)
190        }
191        Type::Defined { ident, args } => match cx.resolve_type(ident) {
192            Some(ResolvedType::Defined(_)) => {
193                for arg in args {
194                    if type_uses_param(cx, arg, param_name) {
195                        return true;
196                    }
197                }
198
199                false
200            }
201            Some(ResolvedType::UnboundParam) => ident == &QualifiedIdentifier::local(param_name),
202            Some(ResolvedType::BoundParam { .. }) => false,
203            None => {
204                panic!("type_uses_type_param failed to resolve type: {:?}", ident);
205            }
206        },
207        _ => false,
208    }
209}
210
211pub fn type_uses_type_param(cx: &CodegenCx, ty: &Type) -> bool {
212    match ty {
213        Type::Primitive(PrimitiveType::Box(inner_ty)) => type_uses_type_param(cx, inner_ty),
214        Type::Primitive(PrimitiveType::List(item_ty)) => type_uses_type_param(cx, item_ty),
215        Type::Primitive(PrimitiveType::Option(inner_ty)) => type_uses_type_param(cx, inner_ty),
216        Type::Primitive(PrimitiveType::Result(ok_ty, err_ty)) => {
217            type_uses_type_param(cx, ok_ty) || type_uses_type_param(cx, err_ty)
218        }
219        Type::Defined { ident, args } => match cx.resolve_type(ident) {
220            Some(ResolvedType::Defined(_)) => {
221                for arg in args {
222                    if type_uses_type_param(cx, arg) {
223                        return true;
224                    }
225                }
226
227                false
228            }
229            Some(ResolvedType::UnboundParam) => true,
230            Some(ResolvedType::BoundParam { .. }) => false,
231            None => {
232                panic!("type_uses_type_param failed to resolve type: {:?}", ident);
233            }
234        },
235        _ => false,
236    }
237}