1use bumpalo::collections::Vec as BumpVec;
10use nash_region::{Located, Position, Region};
11use nash_source::{FieldType, Type};
12
13use crate::Parser;
14use crate::error::{self, TRecord, TTuple};
15
16enum ForeignUpper<'a> {
18 Unqualified(&'a str),
19 Qualified(&'a str, &'a str), }
21
22impl<'a> Parser<'a> {
23 pub fn type_expr(&mut self) -> Result<(&'a Located<Type<'a>>, Position), error::Type<'a>> {
40 let start = self.get_position();
41
42 let (tipe1, end1) = self.one_of(
44 error::Type::Start,
45 vec![
46 Box::new(|p: &mut Parser<'a>| p.type_app(start)),
48 Box::new(|p: &mut Parser<'a>| {
50 let term = p.type_term()?;
51 let end = p.get_position();
52 p.chomp(error::Type::Space)?;
53 Ok((term, end))
54 }),
55 ],
56 )?;
57
58 self.one_of_with_fallback(
60 vec![Box::new(|p: &mut Parser<'a>| {
61 p.check_indent(end1.line, end1.column, error::Type::IndentStart)?;
62 p.word2(0x2D, 0x3E, error::Type::Start)?; p.chomp_and_check_indent(error::Type::Space, error::Type::IndentStart)?;
64
65 let (tipe2, end2) = p.type_expr()?;
66 let tipe = p.alloc(Located::at(
67 Region::new(start, end2),
68 Type::Lambda {
69 from: tipe1,
70 to: tipe2,
71 },
72 ));
73 Ok((tipe, end2))
74 })],
75 (tipe1, end1),
76 )
77 }
78
79 fn type_app(
87 &mut self,
88 start: Position,
89 ) -> Result<(&'a Located<Type<'a>>, Position), error::Type<'a>> {
90 let upper = self.foreign_upper(error::Type::Start)?;
91 let upper_end = self.get_position();
92 self.chomp(error::Type::Space)?;
93
94 let (args, end) = self.type_chomp_args(upper_end)?;
95
96 let region = Region::new(start, upper_end);
97 let tipe = match upper {
98 ForeignUpper::Unqualified(name) => Type::Type { region, name, args },
99 ForeignUpper::Qualified(module, name) => Type::TypeQual {
100 region,
101 module,
102 name,
103 args,
104 },
105 };
106
107 Ok((self.alloc(Located::at(Region::new(start, end), tipe)), end))
108 }
109
110 fn type_chomp_args(
114 &mut self,
115 mut end: Position,
116 ) -> Result<(&'a [&'a Located<Type<'a>>], Position), error::Type<'a>> {
117 let mut args: BumpVec<'a, &'a Located<Type<'a>>> = BumpVec::new_in(self.bump);
118
119 loop {
120 let result = self.one_of_with_fallback(
121 vec![Box::new(|p: &mut Parser<'a>| {
122 let (row, col) = p.position();
124 p.check_indent(row, col, error::Type::IndentStart)?;
125 let arg = p.type_term()?;
126 let new_end = p.get_position();
127 p.chomp(error::Type::Space)?;
128 Ok(Some((arg, new_end)))
129 })],
130 None,
131 )?;
132
133 match result {
134 Some((arg, new_end)) => {
135 args.push(arg);
136 end = new_end;
137 }
138 None => break,
139 }
140 }
141
142 Ok((args.into_bump_slice(), end))
143 }
144
145 pub fn type_term(&mut self) -> Result<&'a Located<Type<'a>>, error::Type<'a>> {
157 let start = self.get_position();
158
159 self.one_of(
160 error::Type::Start,
161 vec![
162 Box::new(|p: &mut Parser<'a>| {
164 let upper = p.foreign_upper(error::Type::Start)?;
165 let end = p.get_position();
166 let region = Region::new(start, end);
167
168 let tipe = match upper {
169 ForeignUpper::Unqualified(name) => {
170 let empty: &'a [&'a Located<Type<'a>>] = &[];
171 Type::Type {
172 region,
173 name,
174 args: empty,
175 }
176 }
177 ForeignUpper::Qualified(module, name) => {
178 let empty: &'a [&'a Located<Type<'a>>] = &[];
179 Type::TypeQual {
180 region,
181 module,
182 name,
183 args: empty,
184 }
185 }
186 };
187
188 Ok(p.add_end(start, tipe))
189 }),
190 Box::new(|p: &mut Parser<'a>| {
192 let var = p.lower_name(error::Type::Start)?;
193 Ok(p.add_end(start, Type::Var(var)))
194 }),
195 Box::new(|p: &mut Parser<'a>| p.type_tuple(start)),
197 Box::new(|p: &mut Parser<'a>| p.type_record(start)),
199 ],
200 )
201 }
202
203 fn type_tuple(&mut self, start: Position) -> Result<&'a Located<Type<'a>>, error::Type<'a>> {
209 self.in_context(
210 |bump, tuple_err, row, col| error::Type::Tuple(bump.alloc(tuple_err), row, col),
211 |p| p.word1(0x28, error::Type::Start), |p| p.type_tuple_body(start),
213 )
214 }
215
216 fn type_tuple_body(&mut self, start: Position) -> Result<&'a Located<Type<'a>>, TTuple<'a>> {
218 self.chomp_and_check_indent(TTuple::Space, TTuple::IndentType1)?;
219
220 self.one_of(
221 TTuple::Open,
222 vec![
223 Box::new(|p: &mut Parser<'a>| {
225 p.word1(0x29, TTuple::Open)?; Ok(p.add_end(start, Type::Unit))
227 }),
228 Box::new(|p: &mut Parser<'a>| {
230 let (first, end) = p.type_tuple_entry()?;
231 p.check_indent(end.line, end.column, TTuple::IndentEnd)?;
232 p.type_tuple_help(start, first)
233 }),
234 ],
235 )
236 }
237
238 fn type_tuple_entry(&mut self) -> Result<(&'a Located<Type<'a>>, Position), TTuple<'a>> {
240 self.specialize(
241 |bump, type_err, row, col| TTuple::Type(bump.alloc(type_err), row, col),
242 |p| p.type_expr(),
243 )
244 }
245
246 fn type_tuple_help(
248 &mut self,
249 start: Position,
250 first: &'a Located<Type<'a>>,
251 ) -> Result<&'a Located<Type<'a>>, TTuple<'a>> {
252 let mut rest: BumpVec<'a, &'a Located<Type<'a>>> = BumpVec::new_in(self.bump);
253
254 loop {
255 self.chomp(TTuple::Space)?;
256
257 let done = self.one_of(
258 TTuple::End,
259 vec![
260 Box::new(|p: &mut Parser<'a>| {
262 p.word1(0x2C, TTuple::End)?; p.chomp_and_check_indent(TTuple::Space, TTuple::IndentTypeN)?;
264
265 let (tipe, end) = p.type_tuple_entry()?;
266 rest.push(tipe);
267
268 p.check_indent(end.line, end.column, TTuple::IndentEnd)?;
269 Ok(false)
270 }),
271 Box::new(|p: &mut Parser<'a>| {
273 p.word1(0x29, TTuple::End)?; Ok(true)
275 }),
276 ],
277 )?;
278
279 if done {
280 break;
281 }
282 }
283
284 if rest.is_empty() {
285 Ok(first)
287 } else {
288 let second = rest.remove(0);
290 let others = rest.into_bump_slice();
291 Ok(self.add_end(
292 start,
293 Type::Tuple {
294 first,
295 second,
296 rest: others,
297 },
298 ))
299 }
300 }
301
302 fn type_record(&mut self, start: Position) -> Result<&'a Located<Type<'a>>, error::Type<'a>> {
308 self.in_context(
309 |bump, record_err, row, col| error::Type::Record(bump.alloc(record_err), row, col),
310 |p| p.word1(0x7B, error::Type::Start), |p| p.type_record_body(start),
312 )
313 }
314
315 fn type_record_body(&mut self, start: Position) -> Result<&'a Located<Type<'a>>, TRecord<'a>> {
317 self.chomp_and_check_indent(TRecord::Space, TRecord::IndentOpen)?;
318
319 self.one_of(
320 TRecord::Open,
321 vec![
322 Box::new(|p: &mut Parser<'a>| {
324 p.word1(0x7D, TRecord::Open)?; let empty: &'a [&'a FieldType<'a>] = &[];
326 Ok(p.add_end(
327 start,
328 Type::Record {
329 fields: empty,
330 ext: None,
331 },
332 ))
333 }),
334 Box::new(|p: &mut Parser<'a>| {
336 let name_start = p.get_position();
337 let name = p.lower_name(TRecord::Field)?;
338 let name_loc = p.add_end(name_start, name);
339
340 p.chomp_and_check_indent(TRecord::Space, TRecord::IndentColon)?;
341
342 p.one_of(
343 TRecord::Colon,
344 vec![
345 Box::new(|p: &mut Parser<'a>| {
347 p.word1(0x7C, TRecord::Colon)?; p.chomp_and_check_indent(TRecord::Space, TRecord::IndentField)?;
349
350 let field = p.type_record_field()?;
351 let fields = p.type_record_end(field)?;
352 Ok(p.add_end(
353 start,
354 Type::Record {
355 fields,
356 ext: Some(name_loc),
357 },
358 ))
359 }),
360 Box::new(|p: &mut Parser<'a>| {
362 p.word1(0x3A, TRecord::Colon)?; p.chomp_and_check_indent(TRecord::Space, TRecord::IndentType)?;
364
365 let (tipe, end) = p.type_record_type_entry()?;
366 p.check_indent(end.line, end.column, TRecord::IndentEnd)?;
367
368 let field = p.alloc(FieldType {
369 field: name_loc,
370 typ: tipe,
371 });
372 let fields = p.type_record_end(field)?;
373 Ok(p.add_end(start, Type::Record { fields, ext: None }))
374 }),
375 ],
376 )
377 }),
378 ],
379 )
380 }
381
382 fn type_record_type_entry(&mut self) -> Result<(&'a Located<Type<'a>>, Position), TRecord<'a>> {
384 self.specialize(
385 |bump, type_err, row, col| TRecord::Type(bump.alloc(type_err), row, col),
386 |p| p.type_expr(),
387 )
388 }
389
390 fn type_record_field(&mut self) -> Result<&'a FieldType<'a>, TRecord<'a>> {
392 let name_start = self.get_position();
393 let name = self.lower_name(TRecord::Field)?;
394 let name_loc = self.add_end(name_start, name);
395
396 self.chomp_and_check_indent(TRecord::Space, TRecord::IndentColon)?;
397 self.word1(0x3A, TRecord::Colon)?; self.chomp_and_check_indent(TRecord::Space, TRecord::IndentType)?;
399
400 let (tipe, end) = self.type_record_type_entry()?;
401 self.check_indent(end.line, end.column, TRecord::IndentEnd)?;
402
403 Ok(self.alloc(FieldType {
404 field: name_loc,
405 typ: tipe,
406 }))
407 }
408
409 fn type_record_end(
411 &mut self,
412 first: &'a FieldType<'a>,
413 ) -> Result<&'a [&'a FieldType<'a>], TRecord<'a>> {
414 let mut fields: BumpVec<'a, &'a FieldType<'a>> = BumpVec::new_in(self.bump);
415 fields.push(first);
416
417 loop {
418 self.chomp(TRecord::Space)?;
419
420 let done = self.one_of(
421 TRecord::End,
422 vec![
423 Box::new(|p: &mut Parser<'a>| {
425 p.word1(0x2C, TRecord::End)?; p.chomp_and_check_indent(TRecord::Space, TRecord::IndentField)?;
427
428 let field = p.type_record_field()?;
429 fields.push(field);
430 Ok(false)
431 }),
432 Box::new(|p: &mut Parser<'a>| {
434 p.word1(0x7D, TRecord::End)?; Ok(true)
436 }),
437 ],
438 )?;
439
440 if done {
441 break;
442 }
443 }
444
445 Ok(fields.into_bump_slice())
446 }
447
448 fn foreign_upper<E>(
456 &mut self,
457 to_error: impl FnOnce(u16, u16) -> E,
458 ) -> Result<ForeignUpper<'a>, E> {
459 let (row, col) = self.position();
460 let start_pos = self.pos;
461
462 match self.peek() {
463 Some(b) if b.is_ascii_uppercase() => {
464 self.advance();
465 self.chomp_inner_chars();
466
467 if self.is_dot_upper() {
469 self.chomp_qualified_upper_for_type(start_pos)
470 } else if self.is_dot_lower() {
471 Err(to_error(row, col))
473 } else {
474 let name = self.slice_from(start_pos);
475 Ok(ForeignUpper::Unqualified(name))
476 }
477 }
478 _ => Err(to_error(row, col)),
479 }
480 }
481
482 fn chomp_qualified_upper_for_type<E>(
484 &mut self,
485 start_pos: usize,
486 ) -> Result<ForeignUpper<'a>, E> {
487 loop {
488 if self.is_dot_upper() {
489 self.advance(); self.advance(); self.chomp_inner_chars();
492 } else {
493 let full = self.slice_from(start_pos);
495 if let Some(last_dot) = full.rfind('.') {
496 let module = &full[..last_dot];
497 let name = &full[last_dot + 1..];
498 return Ok(ForeignUpper::Qualified(module, name));
499 } else {
500 return Ok(ForeignUpper::Unqualified(full));
501 }
502 }
503 }
504 }
505}
506
507#[cfg(test)]
512macro_rules! assert_type_snapshot {
513 ($code:expr) => {{
514 let bump = bumpalo::Bump::new();
515 let src = bump.alloc_str(indoc::indoc!($code));
516 let mut parser = $crate::Parser::new(&bump, src.as_bytes());
517 let (result, _end) = parser.type_expr().expect("expected successful parse");
518
519 insta::with_settings!({
520 description => format!("Code:\n\n{}", indoc::indoc!($code)),
521 omit_expression => true,
522 }, {
523 insta::assert_debug_snapshot!(result);
524 });
525 }};
526}
527
528#[cfg(test)]
529mod tests {
530 #[test]
532 fn type_var_simple() {
533 assert_type_snapshot!("a");
534 }
535
536 #[test]
537 fn type_var_msg() {
538 assert_type_snapshot!("msg");
539 }
540
541 #[test]
543 fn named_type_int() {
544 assert_type_snapshot!("Int");
545 }
546
547 #[test]
548 fn named_type_string() {
549 assert_type_snapshot!("String");
550 }
551
552 #[test]
553 fn named_type_qualified() {
554 assert_type_snapshot!("Dict.Dict");
555 }
556
557 #[test]
558 fn named_type_multi_qualified() {
559 assert_type_snapshot!("Data.Map.Map");
560 }
561
562 #[test]
564 fn type_app_maybe() {
565 assert_type_snapshot!("Maybe Int");
566 }
567
568 #[test]
569 fn type_app_result() {
570 assert_type_snapshot!("Result String Int");
571 }
572
573 #[test]
574 fn type_app_nested() {
575 assert_type_snapshot!("Maybe (List Int)");
576 }
577
578 #[test]
579 fn type_app_qualified() {
580 assert_type_snapshot!("Dict.Dict String Int");
581 }
582
583 #[test]
585 fn function_simple() {
586 assert_type_snapshot!("a -> b");
587 }
588
589 #[test]
590 fn function_multi() {
591 assert_type_snapshot!("a -> b -> c");
592 }
593
594 #[test]
595 fn function_with_types() {
596 assert_type_snapshot!("Int -> String -> Bool");
597 }
598
599 #[test]
600 fn function_with_app() {
601 assert_type_snapshot!("Maybe a -> Result e a");
602 }
603
604 #[test]
606 fn unit() {
607 assert_type_snapshot!("()");
608 }
609
610 #[test]
612 fn tuple_pair() {
613 assert_type_snapshot!("(Int, String)");
614 }
615
616 #[test]
617 fn tuple_triple() {
618 assert_type_snapshot!("(Int, String, Bool)");
619 }
620
621 #[test]
622 fn tuple_nested() {
623 assert_type_snapshot!("((Int, String), Bool)");
624 }
625
626 #[test]
627 fn tuple_with_function() {
628 assert_type_snapshot!("(a -> b, c)");
629 }
630
631 #[test]
632 fn tuple_multiline() {
633 assert_type_snapshot!(
634 "(
635 Int,
636 String,
637 Bool
638 )"
639 );
640 }
641
642 #[test]
644 fn record_empty() {
645 assert_type_snapshot!("{}");
646 }
647
648 #[test]
649 fn record_single() {
650 assert_type_snapshot!("{ name : String }");
651 }
652
653 #[test]
654 fn record_multiple() {
655 assert_type_snapshot!("{ name : String, age : Int }");
656 }
657
658 #[test]
659 fn record_with_function() {
660 assert_type_snapshot!("{ onClick : msg -> Cmd msg }");
661 }
662
663 #[test]
664 fn record_extension() {
665 assert_type_snapshot!("{ a | name : String }");
666 }
667
668 #[test]
669 fn record_extension_multiple() {
670 assert_type_snapshot!("{ a | name : String, age : Int }");
671 }
672
673 #[test]
674 fn record_multiline() {
675 assert_type_snapshot!(
676 "{
677 name : String,
678 age : Int,
679 active : Bool
680 }"
681 );
682 }
683
684 #[test]
686 fn parenthesized() {
687 assert_type_snapshot!("(Int)");
688 }
689
690 #[test]
691 fn parenthesized_function() {
692 assert_type_snapshot!("(a -> b) -> List a -> List b");
693 }
694
695 #[test]
697 fn complex_model_msg() {
698 assert_type_snapshot!("{ model : Model, update : Msg -> Model -> Model }");
699 }
700}