mproto_codegen/codegen/rust/
mod.rs

1use genco::prelude::*;
2
3use self::{common::lazy_type_requires_lifetime, rust_enum::rust_enum, rust_struct::rust_struct};
4use crate::{
5    ast,
6    codegen::{CodegenCx, ResolvedType},
7};
8
9pub use package::{rust_module_gen, rust_package_gen};
10
11mod common;
12mod package;
13mod rust_enum;
14mod rust_struct;
15
16pub fn rust_type_def(cx: &CodegenCx, type_def: &ast::TypeDef) -> rust::Tokens {
17    let cx = &cx.with_type_params(&type_def.params);
18
19    match &type_def.body {
20        ast::TypeBody::Struct(struct_def) => {
21            rust_struct(cx, &type_def.name, &type_def.params, struct_def)
22        }
23        ast::TypeBody::Enum(enum_def) => rust_enum(cx, &type_def.name, &type_def.params, enum_def),
24    }
25}
26
27pub fn rust_type_tokens(cx: &CodegenCx, ty: &ast::Type) -> rust::Tokens {
28    match ty {
29        ast::Type::Primitive(ast::PrimitiveType::Void) => quote! { () },
30        ast::Type::Primitive(ast::PrimitiveType::U8) => quote! { u8 },
31        ast::Type::Primitive(ast::PrimitiveType::U16) => quote! { u16 },
32        ast::Type::Primitive(ast::PrimitiveType::U32) => quote! { u32 },
33        ast::Type::Primitive(ast::PrimitiveType::U64) => quote! { u64 },
34        ast::Type::Primitive(ast::PrimitiveType::U128) => quote! { u128 },
35        ast::Type::Primitive(ast::PrimitiveType::I8) => quote! { i8 },
36        ast::Type::Primitive(ast::PrimitiveType::I16) => quote! { i16 },
37        ast::Type::Primitive(ast::PrimitiveType::I32) => quote! { i32 },
38        ast::Type::Primitive(ast::PrimitiveType::I64) => quote! { i64 },
39        ast::Type::Primitive(ast::PrimitiveType::I128) => quote! { i128 },
40        ast::Type::Primitive(ast::PrimitiveType::F32) => quote! { f32 },
41        ast::Type::Primitive(ast::PrimitiveType::Bool) => quote! { bool },
42        ast::Type::Primitive(ast::PrimitiveType::String) => quote! { String },
43        ast::Type::Primitive(ast::PrimitiveType::Box(inner_ty)) => quote! {
44            Box<$(rust_type_tokens(cx, inner_ty))>
45        },
46        ast::Type::Primitive(ast::PrimitiveType::List(item_ty)) => quote! {
47            Vec<$(rust_type_tokens(cx, item_ty))>
48        },
49        ast::Type::Primitive(ast::PrimitiveType::Option(item_ty)) => quote! {
50            Option<$(rust_type_tokens(cx, item_ty))>
51        },
52        ast::Type::Primitive(ast::PrimitiveType::Result(ok_ty, err_ty)) => quote! {
53            Result<$(rust_type_tokens(cx, ok_ty)), $(rust_type_tokens(cx, err_ty))>
54        },
55        ast::Type::Defined { ident, args } => match cx.resolve_type(ident) {
56            Some(ResolvedType::Defined(_)) => {
57                let args_tokens = rust_type_arg_list(cx, args, None);
58                quote! { $(cx.rust_import_qualified(ident))$args_tokens }
59            }
60            Some(ResolvedType::UnboundParam) => {
61                quote! { $(&ident.name) }
62            }
63            Some(ResolvedType::BoundParam { value, .. }) => rust_type_tokens(cx, value),
64            None => {
65                panic!("rust_type_tokens failed to resolve type: {:?}", ident);
66            }
67        },
68    }
69}
70
71pub fn rust_type_lazy_tokens(cx: &CodegenCx, ty: &ast::Type) -> rust::Tokens {
72    match ty {
73        ast::Type::Primitive(ast::PrimitiveType::Void) => quote! { () },
74        ast::Type::Primitive(ast::PrimitiveType::U8) => quote! { u8 },
75        ast::Type::Primitive(ast::PrimitiveType::U16) => quote! { u16 },
76        ast::Type::Primitive(ast::PrimitiveType::U32) => quote! { u32 },
77        ast::Type::Primitive(ast::PrimitiveType::U64) => quote! { u64 },
78        ast::Type::Primitive(ast::PrimitiveType::U128) => quote! { u128 },
79        ast::Type::Primitive(ast::PrimitiveType::I8) => quote! { i8 },
80        ast::Type::Primitive(ast::PrimitiveType::I16) => quote! { i16 },
81        ast::Type::Primitive(ast::PrimitiveType::I32) => quote! { i32 },
82        ast::Type::Primitive(ast::PrimitiveType::I64) => quote! { i64 },
83        ast::Type::Primitive(ast::PrimitiveType::I128) => quote! { i128 },
84        ast::Type::Primitive(ast::PrimitiveType::F32) => quote! { f32 },
85        ast::Type::Primitive(ast::PrimitiveType::Bool) => quote! { bool },
86        ast::Type::Primitive(ast::PrimitiveType::String) => quote! { &'a str },
87        ast::Type::Primitive(ast::PrimitiveType::Box(inner_ty)) => quote! {
88            $(rust_type_tokens(cx, inner_ty))
89        },
90        ast::Type::Primitive(ast::PrimitiveType::List(item_ty)) => quote! {
91            $(rust::import("mproto", "ListLazy").qualified())<'a, $(rust_type_tokens(cx, item_ty))>
92        },
93        ast::Type::Primitive(ast::PrimitiveType::Option(item_ty)) => quote! {
94            Option<$(rust_type_lazy_tokens(cx, item_ty))>
95        },
96        ast::Type::Primitive(ast::PrimitiveType::Result(ok_ty, err_ty)) => quote! {
97            Result<$(rust_type_lazy_tokens(cx, ok_ty)), $(rust_type_lazy_tokens(cx, err_ty))>
98        },
99        ast::Type::Defined { ident, args } => match cx.resolve_type(ident) {
100            Some(ResolvedType::Defined(_)) => {
101                let maybe_lifetime = if lazy_type_requires_lifetime(cx.db, ty) {
102                    Some(quote! { 'a })
103                } else {
104                    None
105                };
106                let args_tokens = rust_type_arg_list(cx, args, maybe_lifetime);
107                let ref_ident = ast::QualifiedIdentifier {
108                    name: format!("{}Lazy", ident.name),
109                    module: ident.module.clone(),
110                };
111                quote! { $(cx.rust_import_qualified(&ref_ident))$(args_tokens) }
112            }
113            Some(ResolvedType::UnboundParam) => {
114                quote! { $(&ident.name)::Lazy<'a> }
115            }
116            Some(ResolvedType::BoundParam { value, .. }) => rust_type_lazy_tokens(cx, value),
117            None => {
118                panic!("rust_type_lazy_tokens failed to resolve type: {:?}", ident);
119            }
120        },
121    }
122}
123
124pub fn rust_type_arg_list(
125    cx: &CodegenCx,
126    args: &[ast::Type],
127    lifetimes: Option<rust::Tokens>,
128) -> rust::Tokens {
129    if args.len() == 0 {
130        if let Some(lifetimes) = lifetimes {
131            quote! { <$lifetimes> }
132        } else {
133            quote! {}
134        }
135    } else {
136        let lifetimes = lifetimes
137            .map(|l| quote! { $l,$(" ") })
138            .unwrap_or(rust::Tokens::new());
139
140        let mut args_items: rust::Tokens = quote! {
141            $(lifetimes)$(rust_type_tokens(cx, &args[0]))
142        };
143        for arg in &args[1..] {
144            args_items = quote! { $args_items, $(rust_type_tokens(cx, arg)) };
145        }
146
147        quote! { <$args_items> }
148    }
149}
150
151pub fn rust_type_param_list(
152    params: &[String],
153    lifetimes: Option<rust::Tokens>,
154    impl_trait: Option<rust::Tokens>,
155) -> rust::Tokens {
156    if params.len() == 0 {
157        if let Some(lifetimes) = lifetimes {
158            quote! { <$lifetimes> }
159        } else {
160            Tokens::new()
161        }
162    } else {
163        let lifetimes = lifetimes
164            .map(|l| quote! { $l,$(" ") })
165            .unwrap_or(Tokens::new());
166        let impl_trait = impl_trait.map(|i| quote! { : $i }).unwrap_or(Tokens::new());
167
168        let mut tokens = quote! { <$(lifetimes)$(&params[0])$(&impl_trait) };
169
170        for param in &params[1..] {
171            tokens = quote! { $tokens, $(param)$(&impl_trait) };
172        }
173
174        tokens = quote! { $tokens> };
175
176        tokens
177    }
178}
179
180pub fn rust_type_default_value(cx: &CodegenCx, ty: &ast::Type) -> rust::Tokens {
181    match &ty {
182        ast::Type::Primitive(ast::PrimitiveType::Void) => quote! { () },
183        ast::Type::Primitive(ast::PrimitiveType::U8) => quote! { 0 },
184        ast::Type::Primitive(ast::PrimitiveType::U16) => quote! { 0 },
185        ast::Type::Primitive(ast::PrimitiveType::U32) => quote! { 0 },
186        ast::Type::Primitive(ast::PrimitiveType::U64) => quote! { 0 },
187        ast::Type::Primitive(ast::PrimitiveType::U128) => quote! { 0 },
188        ast::Type::Primitive(ast::PrimitiveType::I8) => quote! { 0 },
189        ast::Type::Primitive(ast::PrimitiveType::I16) => quote! { 0 },
190        ast::Type::Primitive(ast::PrimitiveType::I32) => quote! { 0 },
191        ast::Type::Primitive(ast::PrimitiveType::I64) => quote! { 0 },
192        ast::Type::Primitive(ast::PrimitiveType::I128) => quote! { 0 },
193        ast::Type::Primitive(ast::PrimitiveType::F32) => quote! { 0.0 },
194        ast::Type::Primitive(ast::PrimitiveType::Bool) => quote! { false },
195        ast::Type::Primitive(ast::PrimitiveType::String) => quote! { 0 },
196        ast::Type::Primitive(ast::PrimitiveType::Box(inner_ty)) => quote! {
197            Box::new($(rust_type_default_value(cx, inner_ty)))
198        },
199        ast::Type::Primitive(ast::PrimitiveType::List(_)) => quote! { [] },
200        ast::Type::Primitive(ast::PrimitiveType::Option(_)) => quote! { None },
201        ast::Type::Primitive(ast::PrimitiveType::Result(ok_ty, _)) => quote! {
202            Ok($(rust_type_default_value(cx, ok_ty)))
203        },
204        ast::Type::Defined { ident, .. } => {
205            if let Some(type_def) = cx.db.lookup_type_def(ident) {
206                match &type_def.body {
207                    ast::TypeBody::Struct(s) => rust_struct_default_value(cx, &ident, s),
208                    ast::TypeBody::Enum(e) => rust_enum_default_value(cx, &ident, e),
209                }
210            } else {
211                quote! { todo!() }
212            }
213        }
214    }
215}
216
217pub fn rust_struct_default_value(
218    cx: &CodegenCx,
219    ident: &ast::QualifiedIdentifier,
220    s: &ast::Struct,
221) -> rust::Tokens {
222    let mut params = rust::Tokens::new();
223    for field in &s.fields {
224        quote_in! { params =>
225            $(&field.name): $(rust_type_default_value(cx, &field.ty)),
226        };
227    }
228
229    quote! {
230        $(cx.rust_import_qualified(ident)) {
231            $params
232        }
233    }
234}
235
236pub fn rust_enum_default_value(
237    cx: &CodegenCx,
238    ident: &ast::QualifiedIdentifier,
239    e: &ast::Enum,
240) -> rust::Tokens {
241    let (variant_name, variant) = &e.variants[0];
242    let enum_import = cx.rust_import_qualified(ident);
243
244    match variant {
245        ast::EnumVariant::Empty => {
246            quote! {
247                $(enum_import)::$(variant_name)
248            }
249        }
250        ast::EnumVariant::NamedFields { fields } => {
251            let mut params = rust::Tokens::new();
252            for field in fields {
253                quote_in! { params =>
254                    $(&field.name): $(rust_type_default_value(cx, &field.ty)),
255                };
256            }
257
258            quote! {
259                $(enum_import)::$(variant_name) {
260                    $params
261                }
262            }
263        }
264    }
265}