fbs_build/ir/
types.rs

1//! IR types.
2//!
3//! Intermediate representations for `fbs-build` where types are semantically organized (e.g by
4//! namespace) and resolved so that type dependencies are known.
5//!
6//! IR types can also implement `ToTokens` because they contain all the required information to be
7//! transformed into code.
8
9use crate::ast::types as ast;
10use derive_more::{AsRef, From};
11use std::{borrow::Cow, fmt::Display};
12use typed_builder::TypedBuilder;
13
14/// A single IR node.
15#[derive(Debug, Clone, PartialEq)]
16pub enum Node<'a> {
17    Namespace(Namespace<'a>),
18    Table(Table<'a>),
19    Struct(Struct<'a>),
20    Enum(Enum<'a>),
21    Union(Union<'a>),
22    Rpc(Rpc<'a>),
23}
24
25impl<'a> Node<'a> {
26    pub fn ident(&self) -> &QualifiedIdent<'a> {
27        match self {
28            Node::Namespace(ns) => &ns.ident,
29            Node::Table(t) => &t.ident,
30            Node::Struct(s) => &s.ident,
31            Node::Enum(e) => &e.ident,
32            Node::Union(u) => &u.ident,
33            Node::Rpc(r) => &r.ident,
34        }
35    }
36
37    pub fn namespace(&self) -> Option<QualifiedIdent<'a>> {
38        self.ident().namespace()
39    }
40
41    pub fn is_namespace(&self) -> bool {
42        self.as_namespace().is_some()
43    }
44
45    pub fn as_namespace(&self) -> Option<&Namespace<'a>> {
46        match self {
47            Node::Namespace(ns) => Some(ns),
48            _ => None,
49        }
50    }
51
52    pub fn as_namespace_mut(&mut self) -> Option<&mut Namespace<'a>> {
53        match self {
54            Node::Namespace(ns) => Some(ns),
55            _ => None,
56        }
57    }
58
59    pub fn into_namespace(self) -> Option<Namespace<'a>> {
60        match self {
61            Node::Namespace(ns) => Some(ns),
62            _ => None,
63        }
64    }
65}
66
67/// The root of the intermediate representation.
68///
69/// *Not* to be confused with a flatbuffers `root_type`
70#[derive(Debug, Clone, PartialEq, TypedBuilder)]
71pub struct Root<'a> {
72    /// Top-level IR nodes
73    pub nodes: Vec<Node<'a>>,
74}
75
76#[derive(Debug, Clone, PartialEq, TypedBuilder)]
77pub struct Namespace<'a> {
78    // The namespace's fully-qualified name
79    pub ident: QualifiedIdent<'a>,
80
81    /// IR namespace nodes
82    #[builder(default)]
83    pub nodes: Vec<Node<'a>>,
84}
85
86impl Namespace<'_> {
87    pub fn depth(&self) -> usize {
88        self.nodes.len()
89    }
90}
91
92#[derive(Debug, Clone, PartialEq, TypedBuilder)]
93pub struct Table<'a> {
94    /// The table's ident
95    pub ident: QualifiedIdent<'a>,
96    #[builder(default)]
97    pub fields: Vec<Field<'a>>,
98    #[builder(default)]
99    pub root_type: bool,
100    #[builder(default)]
101    pub doc: ast::Comment<'a>,
102}
103
104#[derive(Debug, Clone, PartialEq, TypedBuilder)]
105pub struct Struct<'a> {
106    /// The table's ident
107    pub ident: QualifiedIdent<'a>,
108    #[builder(default)]
109    pub fields: Vec<Field<'a>>,
110
111    #[builder(default)]
112    pub doc: ast::Comment<'a>,
113}
114
115#[derive(Debug, Clone, PartialEq, TypedBuilder)]
116pub struct Field<'a> {
117    /// The fields's ident
118    pub ident: Ident<'a>,
119    pub ty: Type<'a>,
120    #[builder(default)]
121    pub default_value: Option<ast::DefaultValue<'a>>,
122    #[builder(default)]
123    pub metadata: FieldMetadata,
124
125    #[builder(default)]
126    pub doc: ast::Comment<'a>,
127}
128
129#[derive(Clone, Copy, Debug, Default, PartialEq, TypedBuilder)]
130pub struct FieldMetadata {
131    #[builder(default)]
132    pub required: bool,
133}
134
135#[derive(Debug, Clone, PartialEq)]
136pub enum Type<'a> {
137    Bool,
138    Byte,
139    UByte,
140    Short,
141    UShort,
142    Int,
143    UInt,
144    Float,
145    Long,
146    ULong,
147    Double,
148    Int8,
149    UInt8,
150    Int16,
151    UInt16,
152    Int32,
153    UInt32,
154    Int64,
155    UInt64,
156    Float32,
157    Float64,
158    String,
159    Array(Box<Type<'a>>),
160    Custom(CustomTypeRef<'a>),
161}
162
163impl<'a> Type<'a> {
164    pub fn is_scalar(&self) -> bool {
165        match self {
166            // Apparently there are fixed sized arrays
167            // which are considered scalar if their contents
168            // is scalar
169
170            // However they're not currently supported by fbs's parser
171            Type::String | Type::Array(..) => false,
172            Type::Custom(CustomTypeRef { ty, .. }) => match ty {
173                // Apparently only C++ impl supports union in structs
174                // TODO check if unions containing only structs are considered scalar?
175                CustomType::Table | CustomType::Union { .. } => false,
176                CustomType::Struct { .. } | CustomType::Enum { .. } => true,
177            },
178            _ => true,
179        }
180    }
181
182    pub fn is_union(&self) -> bool {
183        match self {
184            Type::Custom(CustomTypeRef { ty, .. }) => match ty {
185                CustomType::Union { .. } => true,
186                _ => false,
187            },
188            _ => false,
189        }
190    }
191
192    pub fn is_enum(&self) -> bool {
193        match self {
194            Type::Custom(CustomTypeRef { ty, .. }) => match ty {
195                CustomType::Enum { .. } => true,
196                _ => false,
197            },
198            _ => false,
199        }
200    }
201
202    pub fn is_array(&self) -> bool {
203        match self {
204            Type::Array(..) => true,
205            _ => false,
206        }
207    }
208
209    // Is it a complex type that will trigger Clippy warnings
210    pub fn is_complex(&self) -> bool {
211        self.is_array()
212    }
213
214    // For unions, we generate an enum type "companion" that's just a simple
215    // ubyte based enum with all the union variants, + None (default 0)
216    //
217    // This method lets us generate the companion type name from the union.
218    pub fn make_union_enum_type_companion(&self) -> Option<&QualifiedIdent<'a>> {
219        match self {
220            Type::Custom(CustomTypeRef { ty, .. }) => match ty {
221                CustomType::Union { ref enum_ident, .. } => Some(enum_ident),
222                _ => None,
223            },
224            _ => None,
225        }
226    }
227}
228
229impl Display for Type<'_> {
230    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231        match self {
232            Type::Bool => write!(f, "bool"),
233            Type::Byte => write!(f, "i8"),
234            Type::UByte => write!(f, "u8"),
235            Type::Short => write!(f, "i16"),
236            Type::UShort => write!(f, "u16"),
237            Type::Int => write!(f, "i32"),
238            Type::UInt => write!(f, "u32"),
239            Type::Float => write!(f, "f32"),
240            Type::Long => write!(f, "i64"),
241            Type::ULong => write!(f, "u64"),
242            Type::Double => write!(f, "f64"),
243            Type::Int8 => write!(f, "i8"),
244            Type::UInt8 => write!(f, "u8"),
245            Type::Int16 => write!(f, "i16"),
246            Type::UInt16 => write!(f, "u16"),
247            Type::Int32 => write!(f, "i32"),
248            Type::UInt32 => write!(f, "u32"),
249            Type::Int64 => write!(f, "i64"),
250            Type::UInt64 => write!(f, "u64"),
251            Type::Float32 => write!(f, "f32"),
252            Type::Float64 => write!(f, "f64"),
253            Type::String => write!(f, "&str"),
254            Type::Array(component) => write!(f, "[{}]", component),
255            Type::Custom(CustomTypeRef { ident, .. }) => write!(f, "{}", ident),
256        }
257    }
258}
259
260#[derive(Debug, Clone, PartialEq, TypedBuilder)]
261pub struct CustomTypeRef<'a> {
262    pub ident: QualifiedIdent<'a>,
263    pub ty: CustomType<'a>,
264}
265
266#[derive(Debug, Clone, PartialEq)]
267pub enum CustomType<'a> {
268    Table,
269    Struct {
270        fields: Vec<Field<'a>>,
271    },
272    Enum {
273        variants: Vec<EnumVariant<'a>>,
274        base_type: EnumBaseType,
275    },
276    Union {
277        enum_ident: QualifiedIdent<'a>,
278        variants: Vec<UnionVariant<'a>>,
279    },
280}
281
282impl<'a> CustomType<'a> {
283    pub fn is_scalar(&self) -> bool {
284        match self {
285            CustomType::Struct { .. } | CustomType::Enum { .. } => true,
286            _ => false,
287        }
288    }
289}
290
291#[derive(Debug, Clone, PartialEq, TypedBuilder)]
292pub struct Enum<'a> {
293    pub ident: QualifiedIdent<'a>,
294    pub variants: Vec<EnumVariant<'a>>,
295    pub base_type: EnumBaseType,
296
297    #[builder(default)]
298    pub doc: ast::Comment<'a>,
299}
300
301#[derive(Debug, Copy, Clone, PartialEq, Eq)]
302pub enum EnumBaseType {
303    Byte,
304    UByte,
305    Short,
306    UShort,
307    Int,
308    UInt,
309    Long,
310    ULong,
311    Int8,
312    UInt8,
313    Int16,
314    UInt16,
315    Int32,
316    UInt32,
317    Int64,
318    UInt64,
319}
320
321impl<'a> std::convert::TryFrom<&ast::Type<'a>> for EnumBaseType {
322    type Error = ();
323    fn try_from(ty: &ast::Type<'a>) -> Result<Self, Self::Error> {
324        Ok(match ty {
325            ast::Type::Byte => EnumBaseType::Byte,
326            ast::Type::UByte => EnumBaseType::UByte,
327            ast::Type::Short => EnumBaseType::Short,
328            ast::Type::UShort => EnumBaseType::UShort,
329            ast::Type::Int => EnumBaseType::Int,
330            ast::Type::UInt => EnumBaseType::UInt,
331            ast::Type::Long => EnumBaseType::Long,
332            ast::Type::ULong => EnumBaseType::ULong,
333            ast::Type::Int8 => EnumBaseType::Int8,
334            ast::Type::UInt8 => EnumBaseType::UInt8,
335            ast::Type::Int16 => EnumBaseType::Int16,
336            ast::Type::UInt16 => EnumBaseType::UInt16,
337            ast::Type::Int32 => EnumBaseType::Int32,
338            ast::Type::UInt32 => EnumBaseType::UInt32,
339            ast::Type::Int64 => EnumBaseType::Int64,
340            ast::Type::UInt64 => EnumBaseType::UInt64,
341            _ => return Err(()),
342        })
343    }
344}
345
346#[derive(Debug, Clone, PartialEq, TypedBuilder)]
347pub struct Union<'a> {
348    pub ident: QualifiedIdent<'a>,
349    // Ident of the enum "companion"
350    pub enum_ident: QualifiedIdent<'a>,
351    pub variants: Vec<UnionVariant<'a>>,
352
353    #[builder(default)]
354    pub doc: ast::Comment<'a>,
355}
356
357/// An RPC service.
358#[derive(Debug, Clone, PartialEq, TypedBuilder)]
359pub struct Rpc<'a> {
360    pub ident: QualifiedIdent<'a>,
361    pub methods: Vec<RpcMethod<'a>>,
362
363    #[builder(default)]
364    pub doc: ast::Comment<'a>,
365}
366
367/// A method in an RPC service.
368#[derive(Debug, Clone, PartialEq, TypedBuilder)]
369pub struct RpcMethod<'a> {
370    /// The name of the method.
371    pub ident: Ident<'a>,
372
373    /// The snake name of the method.
374    pub snake_ident: Ident<'a>,
375
376    /// The request type of the method.
377    pub request_type: QualifiedIdent<'a>,
378
379    /// The response type of the method.
380    pub response_type: QualifiedIdent<'a>,
381
382    /// Method metadata.
383    #[builder(default)]
384    pub metadata: RpcMethodMetadata,
385
386    #[builder(default)]
387    pub doc: ast::Comment<'a>,
388}
389
390#[derive(Clone, Copy, Debug, Default, PartialEq, TypedBuilder)]
391pub struct RpcMethodMetadata {
392    pub streaming: RpcStreaming,
393}
394
395#[derive(Clone, Copy, Debug, PartialEq)]
396pub enum RpcStreaming {
397    None,
398    Client,
399    Server,
400    Bidi,
401}
402
403impl Default for RpcStreaming {
404    fn default() -> Self {
405        Self::None
406    }
407}
408
409#[derive(Debug, Clone, PartialEq, Eq)]
410pub struct RootType<'a> {
411    _phantom: &'a (),
412}
413
414/// Type for `Enum` values.
415#[derive(Debug, Clone, PartialEq, Hash, Eq, From, TypedBuilder)]
416pub struct EnumVariant<'a> {
417    /// The name of the enum value.
418    pub ident: Ident<'a>,
419
420    /// An optional enum value.
421    #[builder(default)]
422    pub value: Option<ast::IntegerConstant>,
423
424    #[builder(default)]
425    pub doc: ast::Comment<'a>,
426}
427
428/// Type for `Union` variants.
429#[derive(Debug, Clone, PartialEq, From, TypedBuilder)]
430pub struct UnionVariant<'a> {
431    /// The type of the variant
432    pub ty: Type<'a>,
433    /// The ident of the variant, matching the companion enum's
434    pub ident: Ident<'a>,
435
436    #[builder(default)]
437    pub doc: ast::Comment<'a>,
438}
439
440/// An identifier
441///
442/// Can be either taken from a schema, or generated synthetically,
443/// in which case an owned version of the ident is used.
444#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, AsRef)]
445pub struct Ident<'a> {
446    pub raw: Cow<'a, str>,
447}
448
449impl<'a> Ident<'a> {
450    // Utility function to fix identifiers that may be invalid,
451    // e.g that contain Rust keywords and would generate invalid code
452    pub fn fix_identifier(ident: Cow<'_, str>) -> Cow<'_, str> {
453        if let Some(substitute) = super::keywords::substitute_keyword(ident.as_ref()) {
454            Cow::Borrowed(substitute)
455        } else {
456            ident
457        }
458    }
459
460    // Purposefully create an ident named after a keyword
461    // e.g to use `crate` or `super` for a type path
462    fn keyword(keyword: &str) -> Ident<'_> {
463        Ident {
464            raw: Cow::Borrowed(keyword),
465        }
466    }
467}
468
469impl<'a> From<ast::Ident<'a>> for Ident<'a> {
470    fn from(ident: ast::Ident<'a>) -> Self {
471        Ident::from(Cow::Borrowed(ident.raw))
472    }
473}
474
475impl<'a> From<&ast::Ident<'a>> for Ident<'a> {
476    fn from(ident: &ast::Ident<'a>) -> Self {
477        Ident::from(Cow::Borrowed(ident.raw))
478    }
479}
480impl<'a> From<&'a str> for Ident<'a> {
481    fn from(s: &'a str) -> Self {
482        Ident::from(Cow::Borrowed(s))
483    }
484}
485
486impl<'a> From<String> for Ident<'a> {
487    fn from(s: String) -> Self {
488        Ident::from(Cow::Owned(s))
489    }
490}
491
492impl<'a> From<Cow<'a, str>> for Ident<'a> {
493    fn from(raw: Cow<'a, str>) -> Self {
494        let raw = Ident::fix_identifier(raw);
495        Self { raw }
496    }
497}
498
499impl<'a> Display for Ident<'a> {
500    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
501        write!(f, "{}", self.raw.as_ref())
502    }
503}
504
505/// An identifier composed of `Ident`s separated by dots.
506#[derive(Default, Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, From)]
507pub struct QualifiedIdent<'a> {
508    pub parts: Vec<Ident<'a>>,
509}
510
511// Clippy wants `is_empty` if there's `len` but it makes no sense here since we never have an
512// empty `QualifiedIdent`.
513#[allow(clippy::len_without_is_empty)]
514impl<'a> QualifiedIdent<'a> {
515    pub fn len(&self) -> usize {
516        self.parts.len()
517    }
518
519    pub fn namespace(&self) -> Option<QualifiedIdent<'a>> {
520        if self.parts.len() > 1 {
521            Some(Self {
522                parts: self.parts[..self.parts.len() - 1].to_vec(),
523            })
524        } else {
525            None
526        }
527    }
528
529    pub fn relative(&self, namespace: Option<&QualifiedIdent<'a>>) -> QualifiedIdent<'a> {
530        if let Some(ns) = namespace {
531            let common_prefix_len = self
532                .parts
533                .iter()
534                .zip(ns.parts.iter())
535                .take_while(|(a, b)| a == b)
536                .count();
537
538            let parts = std::iter::repeat(Ident::keyword("super"))
539                .take(ns.len() - common_prefix_len)
540                .chain(self.parts[common_prefix_len..].to_vec())
541                .collect();
542            QualifiedIdent { parts }
543        } else {
544            self.clone()
545        }
546    }
547
548    pub fn simple(&self) -> &Ident<'a> {
549        &self.parts[self.parts.len() - 1]
550    }
551
552    // Assumes correctly formed ident.
553    // Keywords are allowed
554    // Use only for tests
555    #[cfg(test)]
556    pub fn parse_str(s: &'a str) -> Self {
557        QualifiedIdent {
558            parts: s
559                .split('.')
560                .map(|p| Ident {
561                    raw: Cow::Borrowed(p),
562                })
563                .collect(),
564        }
565    }
566}
567
568impl<'a> From<&'a str> for QualifiedIdent<'a> {
569    fn from(s: &'a str) -> Self {
570        Self {
571            parts: vec![s.into()],
572        }
573    }
574}
575
576impl<'a> From<ast::QualifiedIdent<'a>> for QualifiedIdent<'a> {
577    fn from(ident: ast::QualifiedIdent<'a>) -> Self {
578        Self {
579            parts: ident.parts.into_iter().map(Into::into).collect(),
580        }
581    }
582}
583
584impl<'a> From<&ast::QualifiedIdent<'a>> for QualifiedIdent<'a> {
585    fn from(ident: &ast::QualifiedIdent<'a>) -> Self {
586        Self {
587            parts: ident.parts.iter().map(Into::into).collect(),
588        }
589    }
590}
591
592impl<'a> Display for QualifiedIdent<'a> {
593    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
594        use itertools::Itertools;
595        write!(f, "{}", self.parts.iter().join("."))
596    }
597}
598
599#[cfg(test)]
600mod tests {
601    use super::*;
602
603    #[test]
604    pub fn test_relative_ident_from_same_namespace() {
605        let id = QualifiedIdent::parse_str("foo.bar.Baz");
606        let ns = id.namespace(); // ns = "foo.bar"
607        let expected = QualifiedIdent::parse_str("Baz");
608        let actual = id.relative(ns.as_ref());
609
610        assert_eq!(expected, actual);
611    }
612
613    #[test]
614    pub fn test_relative_ident_from_root_namespace() {
615        let id = QualifiedIdent::parse_str("foo.bar.Baz");
616        let ns = None; // "root" namespace
617        let expected = id.clone(); // absolute id is unchanged
618        let actual = id.relative(ns.as_ref());
619
620        assert_eq!(expected, actual);
621    }
622
623    #[test]
624    pub fn test_relative_ident_from_parent_namespace() {
625        let id = QualifiedIdent::parse_str("foo.bar.Baz");
626        let ns = Some(QualifiedIdent::parse_str("foo"));
627        let expected = QualifiedIdent::parse_str("bar.Baz");
628        let actual = id.relative(ns.as_ref());
629
630        assert_eq!(expected, actual);
631    }
632
633    #[test]
634    pub fn test_relative_ident_from_child_namespace() {
635        let id = QualifiedIdent::parse_str("foo.bar.Baz");
636        let ns = Some(QualifiedIdent::parse_str("foo.bar.bim"));
637        let expected = QualifiedIdent::parse_str("super.Baz");
638        let actual = id.relative(ns.as_ref());
639
640        assert_eq!(expected, actual);
641    }
642
643    #[test]
644    pub fn test_relative_ident_from_sibling_namespace() {
645        let id = QualifiedIdent::parse_str("foo.bar.bam.Baz");
646        let ns = Some(QualifiedIdent::parse_str("foo.bar.bim"));
647        let expected = QualifiedIdent::parse_str("super.bam.Baz");
648        let actual = id.relative(ns.as_ref());
649
650        assert_eq!(expected, actual);
651    }
652}