Skip to main content

efmt_core/items/
types.rs

1//! Erlang types.
2//!
3//! <https://www.erlang.org/doc/reference_manual/typespec.html>
4use self::components::{BinaryOp, BitstringItem, UnaryOp};
5use crate::format::{Format, Formatter};
6use crate::items::Type;
7use crate::items::components::{
8    Args, BitstringLike, Either, Element, ListLike, MapLike, Maybe, NonEmptyItems, Params,
9    Parenthesized, RecordLike, TupleLike,
10};
11use crate::items::keywords::FunKeyword;
12use crate::items::symbols::{
13    ColonSymbol, DoubleColonSymbol, RightArrowSymbol, SharpSymbol, TripleDotSymbol,
14    VerticalBarSymbol,
15};
16use crate::items::tokens::{AtomToken, CharToken, IntegerToken, VariableToken};
17use crate::parse::{self, Parse, ResumeParse};
18use crate::span::Span;
19
20pub mod components;
21
22/// [Type] `|` [Type]
23#[derive(Debug, Clone, Span, Parse, Format)]
24pub struct UnionType(NonEmptyItems<NonUnionType, UnionDelimiter>);
25
26impl UnionType {
27    pub(crate) fn items(&self) -> &[NonUnionType] {
28        self.0.items()
29    }
30}
31
32#[derive(Debug, Clone, Span, Parse)]
33struct UnionDelimiter(VerticalBarSymbol);
34
35impl Format for UnionDelimiter {
36    fn format(&self, fmt: &mut Formatter) {
37        fmt.write_space();
38        self.0.format(fmt);
39    }
40}
41
42#[derive(Debug, Clone, Span, Format)]
43pub enum NonUnionType {
44    Base(BaseType),
45    BinaryOp(Box<BinaryOpType>),
46}
47
48impl Parse for NonUnionType {
49    fn parse(ts: &mut parse::TokenStream) -> parse::Result<Self> {
50        let expr: BaseType = ts.parse()?;
51        if ts.peek::<BinaryOp>().is_some() {
52            ts.resume_parse(expr).map(Self::BinaryOp)
53        } else {
54            Ok(Self::Base(expr))
55        }
56    }
57}
58
59// Non left-recursive type.
60#[derive(Debug, Clone, Span, Parse, Format)]
61pub enum BaseType {
62    Mfargs(Box<MfargsType>),
63    List(Box<ListType>),
64    Tuple(Box<TupleType>),
65    Map(Box<MapType>),
66    Record(Box<RecordType>),
67    Bitstring(Box<BitstringType>),
68    Function(Box<FunctionType>),
69    UnaryOp(Box<UnaryOpType>),
70    Parenthesized(Box<Parenthesized<Type>>),
71    Annotated(Box<AnnotatedVariableType>),
72    Literal(LiteralType),
73}
74
75/// [VariableToken] `::` [Type]
76#[derive(Debug, Clone, Span, Parse)]
77pub struct AnnotatedVariableType {
78    variable: VariableToken,
79    colon: DoubleColonSymbol,
80    ty: Type,
81}
82
83impl AnnotatedVariableType {
84    pub fn variable(&self) -> &VariableToken {
85        &self.variable
86    }
87
88    pub fn ty(&self) -> &Type {
89        &self.ty
90    }
91}
92
93impl Format for AnnotatedVariableType {
94    fn format(&self, fmt: &mut Formatter) {
95        self.variable.format(fmt);
96        fmt.write_space();
97        self.colon.format(fmt);
98        fmt.write_space();
99        self.ty.format(fmt);
100    }
101}
102
103/// [Type] [BinaryOp] [Type]
104#[derive(Debug, Clone, Span, Parse)]
105pub struct BinaryOpType {
106    left: BaseType,
107    op: BinaryOp,
108    right: Type,
109}
110
111impl ResumeParse<BaseType> for BinaryOpType {
112    fn resume_parse(ts: &mut parse::TokenStream, left: BaseType) -> parse::Result<Self> {
113        Ok(Self {
114            left,
115            op: ts.parse()?,
116            right: ts.parse()?,
117        })
118    }
119}
120
121impl Format for BinaryOpType {
122    fn format(&self, fmt: &mut Formatter) {
123        let needs_space = !matches!(self.op, BinaryOp::Range(_));
124
125        self.left.format(fmt);
126        if needs_space {
127            fmt.write_space();
128        }
129        self.op.format(fmt);
130        if needs_space {
131            fmt.write_space();
132        }
133        self.right.format(fmt);
134    }
135}
136
137/// [UnaryOp] [Type]
138#[derive(Debug, Clone, Span, Parse)]
139pub struct UnaryOpType {
140    op: UnaryOp,
141    ty: BaseType,
142}
143
144impl Format for UnaryOpType {
145    fn format(&self, fmt: &mut Formatter) {
146        let last = fmt.last_char().unwrap_or('\n');
147        if !matches!(last, '\n' | ' ' | '.') {
148            fmt.write_space();
149        }
150
151        self.op.format(fmt);
152        if matches!(self.op, UnaryOp::Bnot(_)) {
153            fmt.write_space();
154        }
155        self.ty.format(fmt);
156    }
157}
158
159/// `fun` `(` (`$PARAMS` `->` `$RETURN`)? `)`
160///
161/// - $PARAMS: `(` `...` `)` | `(` ([Type] `,`?)* `)`
162/// - $RETURN: [Type]
163#[derive(Debug, Clone, Span, Parse)]
164pub struct FunctionType {
165    fun: FunKeyword,
166    params_and_return: Parenthesized<Maybe<FunctionParamsAndReturn>>,
167}
168
169impl FunctionType {
170    pub fn params(&self) -> impl Iterator<Item = &Type> {
171        self.params_and_return
172            .get()
173            .get()
174            .and_then(|x| match &x.params {
175                FunctionParams::Any(_) => None,
176                FunctionParams::Params(x) => Some(x.get()),
177            })
178            .into_iter()
179            .flat_map(|x| x.iter())
180    }
181
182    pub fn return_type(&self) -> Option<&Type> {
183        self.params_and_return.get().get().map(|x| &x.ty)
184    }
185}
186
187impl Format for FunctionType {
188    fn format(&self, fmt: &mut Formatter) {
189        self.fun.format(fmt);
190        self.params_and_return.format(fmt);
191    }
192}
193
194#[derive(Debug, Clone, Span, Parse)]
195struct FunctionParamsAndReturn {
196    params: FunctionParams,
197    arrow: RightArrowSymbol,
198    ty: Type,
199}
200
201impl Format for FunctionParamsAndReturn {
202    fn format(&self, fmt: &mut Formatter) {
203        fmt.with_scoped_indent(|fmt| {
204            // 'Params'
205            self.params.format(fmt);
206            fmt.write_space();
207
208            // '->'
209            let multiline = fmt.has_newline_until(&self.ty);
210            self.arrow.format(fmt);
211            if multiline {
212                fmt.set_indent(fmt.indent() + 8);
213                fmt.write_newline();
214            } else {
215                fmt.write_space();
216            }
217
218            // 'Type'
219            self.ty.format(fmt);
220        });
221    }
222}
223
224#[derive(Debug, Clone, Span, Parse, Format)]
225enum FunctionParams {
226    Any(Parenthesized<TripleDotSymbol>),
227    Params(Params<Type>),
228}
229
230/// [AtomToken] | [CharToken] | [IntegerToken] | [VariableToken]
231#[derive(Debug, Clone, Span, Parse, Format)]
232pub enum LiteralType {
233    Atom(AtomToken),
234    Char(CharToken),
235    Integer(IntegerToken),
236    Variable(VariableToken),
237}
238
239/// (`$MODULE` `:`)? `$NAME` `(` (`$ARG` `,`)* `)`
240///
241/// - $MODULE: [AtomToken]
242/// - $NAME: [AtomToken]
243/// - $ARG: [Type]
244#[derive(Debug, Clone, Span, Parse, Format)]
245pub struct MfargsType {
246    module: Maybe<(AtomToken, ColonSymbol)>,
247    name: AtomToken,
248    args: Args<Type>,
249}
250
251impl MfargsType {
252    pub fn module_name(&self) -> Option<&AtomToken> {
253        self.module.get().map(|(name, _)| name)
254    }
255
256    pub fn type_name(&self) -> &AtomToken {
257        &self.name
258    }
259
260    pub fn args(&self) -> &[Type] {
261        self.args.get()
262    }
263}
264
265/// `[` (`$ITEM` `,`?)* `]`
266///
267/// - $ITEM: [Type] | `...`
268#[derive(Debug, Clone, Span, Parse, Format)]
269pub struct ListType(ListLike<ListItem>);
270
271impl ListType {
272    pub fn item_type(&self) -> Option<&Type> {
273        self.0.items().first().and_then(|item| match &item.0 {
274            Either::A(x) => Some(x),
275            Either::B(_) => None,
276        })
277    }
278}
279
280#[derive(Debug, Clone, Span, Parse, Format)]
281struct ListItem(Either<Type, TripleDotSymbol>);
282
283impl Element for ListItem {
284    fn is_packable(&self) -> bool {
285        false
286    }
287}
288
289/// `{` ([Type] `,`)* `}`
290#[derive(Debug, Clone, Span, Parse, Format)]
291pub struct TupleType(TupleLike<TupleItem>);
292
293impl TupleType {
294    pub fn items(&self) -> (Option<&AtomToken>, impl Iterator<Item = &Type>) {
295        let (tag, items) = self.0.items();
296        (tag, items.iter().map(|item| &item.0))
297    }
298}
299
300#[derive(Debug, Clone, Span, Parse, Format, Element)]
301struct TupleItem(Type);
302
303/// `#` `{` ([Type] (`:=` | `=>`) [Type] `,`?)* `}`
304#[derive(Debug, Clone, Span, Parse, Format)]
305pub struct MapType(MapLike<SharpSymbol, Type>);
306
307impl MapType {
308    pub fn items(&self) -> impl Iterator<Item = (&Type, &Type)> {
309        self.0.items()
310    }
311}
312
313/// `#` `$NAME` `{` (`$FIELD` `,`?)* `}`
314///
315/// - $NAME: [AtomToken]
316/// - $FIELD: [AtomToken] `::` [Type]
317#[derive(Debug, Clone, Span, Parse, Format)]
318pub struct RecordType {
319    record: RecordLike<(SharpSymbol, AtomToken), RecordItem>,
320}
321
322impl RecordType {
323    pub fn name(&self) -> &AtomToken {
324        &self.record.prefix().1
325    }
326
327    pub fn fields(&self) -> impl Iterator<Item = (&AtomToken, &Type)> {
328        self.record.fields().map(|item| (&item.name, &item.ty))
329    }
330}
331
332#[derive(Debug, Clone, Span, Parse, Element)]
333struct RecordItem {
334    name: AtomToken,
335    colon: DoubleColonSymbol,
336    ty: Type,
337}
338
339impl Format for RecordItem {
340    fn format(&self, fmt: &mut Formatter) {
341        self.name.format(fmt);
342        fmt.write_space();
343        self.colon.format(fmt);
344        fmt.write_space();
345        self.ty.format(fmt);
346    }
347}
348
349/// `<<` `$BITS_SIZE`? `,`? `$UNIT_SIZE`? `>>`
350///
351/// - $BITS_SIZE: `_` `:` [Type]
352/// - $UNIT_SIZE: `_` `:` `_` `*` [Type]
353#[derive(Debug, Clone, Span, Parse, Format)]
354pub struct BitstringType(BitstringLike<BitstringItem>);
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359
360    #[test]
361    fn mfargs_works() {
362        let texts = [
363            "foo()",
364            "foo:bar(A, 1)",
365            indoc::indoc! {"
366            foo:bar(A,
367                    BB,
368                    baz())"},
369            indoc::indoc! {"
370            foo:bar(A,
371                    B,
372                    baz(12, 34),
373                    qux())"},
374        ];
375        for text in texts {
376            crate::assert_format!(text, Type);
377        }
378    }
379
380    #[test]
381    fn list_works() {
382        let texts = [
383            "[]",
384            "[foo()]",
385            "[10, ...]",
386            indoc::indoc! {"
387            [fooooooooooo(),
388             ...]"},
389        ];
390        for text in texts {
391            crate::assert_format!(text, Type);
392        }
393    }
394
395    #[test]
396    fn tuple_works() {
397        let texts = [
398            "{}",
399            "{foo()}",
400            "{atom, 1}",
401            indoc::indoc! {"
402            {foo(),
403             bar(),
404             [baz()]}"},
405            indoc::indoc! {"
406            {foo(),
407             bar(),
408             [baz(A, B)]}"},
409            indoc::indoc! {"
410            {foo, bar,
411                  [baz(A, B)]}"},
412        ];
413        for text in texts {
414            crate::assert_format!(text, Type);
415        }
416    }
417
418    #[test]
419    fn map_works() {
420        let expected = [
421            "#{}",
422            "#{a => b, 1 := 2}",
423            indoc::indoc! {"
424            #{
425              atom() :=
426                  integer()
427             }"},
428            indoc::indoc! {"
429            #{
430              atom() := {Aaa,
431                         bbb,
432                         ccc}
433             }"},
434            indoc::indoc! {"
435            #{
436              a => b,
437              1 := 2,
438              atom() := atom()
439             }"},
440            indoc::indoc! {"
441            #{a => b, 1 := 2, atom() := atom()}"},
442        ];
443        for text in expected {
444            crate::assert_format!(text, Type);
445        }
446    }
447
448    #[test]
449    fn record_works() {
450        let texts = [
451            "#foo{}",
452            indoc::indoc! {"
453            #foo{
454              bar :: integer()
455             }"},
456            indoc::indoc! {"
457            #foo{
458              bar :: b,
459              baz :: 2
460             }"},
461            indoc::indoc! {"
462            #foo{bar :: b, baz :: 2}"},
463            indoc::indoc! {"
464            #foo{
465              bar :: b,
466              baz :: bb()
467             }"},
468        ];
469        for text in texts {
470            crate::assert_format!(text, Type);
471        }
472    }
473
474    #[test]
475    fn function_works() {
476        let texts = [
477            "fun()",
478            "fun(() -> integer())",
479            indoc::indoc! {"
480            fun((...) -> atom())"},
481            indoc::indoc! {"
482            fun((A, b, $c) ->
483                        tuple())"},
484            indoc::indoc! {"
485            fun((A,
486                 b,
487                 $c,
488                 {foo()}) ->
489                        tuple())"},
490        ];
491        for text in texts {
492            crate::assert_format!(text, Type);
493        }
494    }
495
496    #[test]
497    fn unary_op_works() {
498        let texts = ["-10", "+10", "bnot 100", "- - + +3"];
499        for text in texts {
500            crate::assert_format!(text, Type);
501        }
502    }
503
504    #[test]
505    fn binary_op_works() {
506        let texts = [
507            "-10 + 20 rem 3",
508            indoc::indoc! {"
509            foo |
510            (3 + 10) |
511            -1..+20"},
512        ];
513        for text in texts {
514            crate::assert_format!(text, Type);
515        }
516    }
517
518    #[test]
519    fn bitstring_works() {
520        let texts = [
521            "<<>>",
522            "<<_:10>>",
523            "<<_:_*8>>",
524            "<<_:8, _:_*4>>",
525            indoc::indoc! {"
526            <<_:(1 + 3 + 4),
527              _:_*4>>"},
528        ];
529        for text in texts {
530            crate::assert_format!(text, Type);
531        }
532    }
533
534    #[test]
535    fn annotated_variable_works() {
536        let texts = [
537            "Foo :: atom()",
538            indoc::indoc! {"
539            Foo :: [bar:baz(qux)]"},
540            indoc::indoc! {"
541            Foo :: atom() |
542                   integer() |
543                   bar"},
544        ];
545        for text in texts {
546            crate::assert_format!(text, Type);
547        }
548    }
549}