1use rustc_hash::FxHashMap;
2use serde::Serialize;
3use std::{borrow::Cow, collections::HashSet, mem};
4use unicase::Ascii;
5
6use crate::arena::Arena;
7use crate::typing::{Record, Type};
8use crate::{
9 App, Attrs, Binary, ExprRef, Field, Query, Raw, RecRef, Source, SourceKind, StrRef, Value,
10 error::AnalysisError, token::Operator,
11};
12
13#[derive(Debug, Clone, Serialize)]
23pub struct Typed {
24 pub project: Type,
29
30 #[serde(skip)]
35 pub scope: Scope,
36
37 pub aggregate: bool,
39}
40
41pub type AnalysisResult<A> = Result<A, AnalysisError>;
46
47pub struct AnalysisOptions {
53 pub default_scope: Scope,
55 pub event_type_info: Type,
57 pub custom_types: HashSet<Ascii<String>>,
63}
64
65impl AnalysisOptions {
66 pub fn add_custom_type<'a>(mut self, value: impl Into<Cow<'a, str>>) -> Self {
80 match value.into() {
81 Cow::Borrowed(t) => self.custom_types.insert(Ascii::new(t.to_owned())),
82 Cow::Owned(t) => self.custom_types.insert(Ascii::new(t)),
83 };
84
85 self
86 }
87
88 pub fn empty() -> Self {
90 Self {
91 default_scope: Scope::default(),
92 event_type_info: Type::default(),
93 custom_types: HashSet::default(),
94 }
95 }
96}
97
98#[derive(Default, Clone, Serialize, Debug)]
104pub struct Scope {
105 #[serde(skip_serializing)]
106 entries: FxHashMap<StrRef, Type>,
107}
108
109impl Scope {
110 pub fn is_empty(&self) -> bool {
112 self.entries.is_empty()
113 }
114
115 pub fn declare(&mut self, name: StrRef, tpe: Type) -> bool {
120 self.entries.insert(name, tpe).is_none()
121 }
122
123 pub fn get(&self, name: StrRef) -> Option<Type> {
127 self.entries.get(&name).copied()
128 }
129
130 pub fn get_mut(&mut self, name: StrRef) -> Option<&mut Type> {
134 self.entries.get_mut(&name)
135 }
136
137 pub fn exists(&self, name: StrRef) -> bool {
139 self.entries.contains_key(&name)
140 }
141}
142
143#[derive(Default)]
144struct CheckContext {
145 use_agg_func: bool,
146 use_source_based: bool,
147}
148
149#[derive(Default)]
154pub struct AnalysisContext {
155 pub allow_agg_func: bool,
161
162 pub use_agg_funcs: bool,
164}
165
166pub struct Analysis<'a> {
171 arena: &'a mut Arena,
172 options: &'a AnalysisOptions,
174 prev_scopes: Vec<Scope>,
176 scope: Scope,
178}
179
180impl<'a> Analysis<'a> {
181 pub fn new(arena: &'a mut Arena, options: &'a AnalysisOptions) -> Self {
183 Self {
184 arena,
185 options,
186 prev_scopes: Default::default(),
187 scope: Scope::default(),
188 }
189 }
190
191 pub fn scope(&self) -> &Scope {
199 &self.scope
200 }
201
202 pub fn scope_mut(&mut self) -> &mut Scope {
210 &mut self.scope
211 }
212
213 fn enter_scope(&mut self) {
214 if self.scope.is_empty() {
215 return;
216 }
217
218 let prev = mem::take(&mut self.scope);
219 self.prev_scopes.push(prev);
220 }
221
222 fn exit_scope(&mut self) -> Scope {
223 if let Some(prev) = self.prev_scopes.pop() {
224 mem::replace(&mut self.scope, prev)
225 } else {
226 mem::take(&mut self.scope)
227 }
228 }
229
230 #[cfg(test)]
231 pub fn test_declare(&mut self, name: &str, tpe: Type) -> bool {
232 let name = self.arena.strings.alloc_no_case(name);
233 self.scope.declare(name, tpe)
234 }
235
236 pub fn analyze_query(&mut self, query: Query<Raw>) -> AnalysisResult<Query<Typed>> {
263 self.enter_scope();
264
265 let mut sources = Vec::with_capacity(query.sources.len());
266 let mut ctx = AnalysisContext::default();
267
268 for source in query.sources {
269 sources.push(self.analyze_source(source)?);
270 }
271
272 if let Some(expr) = query.predicate.as_ref().copied() {
273 self.analyze_expr(&mut ctx, expr, Type::Bool)?;
274 }
275
276 if let Some(group_by) = &query.group_by {
277 let node = self.arena.exprs.get(group_by.expr);
278 if !matches!(node.value, Value::Access(_)) {
279 return Err(AnalysisError::ExpectFieldLiteral(
280 node.attrs.pos.line,
281 node.attrs.pos.col,
282 ));
283 }
284
285 self.analyze_expr(&mut ctx, group_by.expr, Type::Unspecified)?;
286
287 if let Some(expr) = group_by.predicate.as_ref().copied() {
288 ctx.allow_agg_func = true;
289 ctx.use_agg_funcs = true;
290
291 self.analyze_expr(&mut ctx, expr, Type::Bool)?;
292
293 let node = self.arena.exprs.get(expr);
294 if !self.expect_agg_expr(expr)? {
295 return Err(AnalysisError::ExpectAggExpr(
296 node.attrs.pos.line,
297 node.attrs.pos.col,
298 ));
299 }
300 }
301
302 ctx.allow_agg_func = true;
303 ctx.use_agg_funcs = true;
304 }
305
306 let project = self.analyze_projection(&mut ctx, query.projection)?;
307
308 if let Some(order_by) = &query.order_by {
309 self.analyze_expr(&mut ctx, order_by.expr, Type::Unspecified)?;
310 let node = self.arena.exprs.get(order_by.expr);
311 if query.group_by.is_none() && !matches!(node.value, Value::Access(_)) {
312 return Err(AnalysisError::ExpectFieldLiteral(
313 node.attrs.pos.line,
314 node.attrs.pos.col,
315 ));
316 } else if query.group_by.is_some() {
317 self.expect_agg_func(order_by.expr)?;
318 }
319 }
320
321 let scope = self.exit_scope();
322
323 Ok(Query {
324 attrs: query.attrs,
325 sources,
326 predicate: query.predicate,
327 group_by: query.group_by,
328 order_by: query.order_by,
329 limit: query.limit,
330 projection: query.projection,
331 distinct: query.distinct,
332 meta: Typed {
333 project,
334 scope,
335 aggregate: ctx.use_agg_funcs,
336 },
337 })
338 }
339
340 fn analyze_source(&mut self, source: Source<Raw>) -> AnalysisResult<Source<Typed>> {
341 let kind = self.analyze_source_kind(source.kind)?;
342 let tpe = match &kind {
343 SourceKind::Name(_) | SourceKind::Subject(_) => {
344 self.arena.types.alloc_type(self.options.event_type_info)
345 }
346 SourceKind::Subquery(query) => self.projection_type(query),
347 };
348
349 if !self.scope.declare(source.binding.name, tpe) {
350 return Err(AnalysisError::BindingAlreadyExists(
351 source.binding.pos.line,
352 source.binding.pos.col,
353 self.arena.strings.get(source.binding.name).to_owned(),
354 ));
355 }
356
357 Ok(Source {
358 binding: source.binding,
359 kind,
360 })
361 }
362
363 fn analyze_source_kind(&mut self, kind: SourceKind<Raw>) -> AnalysisResult<SourceKind<Typed>> {
364 match kind {
365 SourceKind::Name(n) => Ok(SourceKind::Name(n)),
366 SourceKind::Subject(s) => Ok(SourceKind::Subject(s)),
367 SourceKind::Subquery(query) => {
368 let query = self.analyze_query(*query)?;
369 Ok(SourceKind::Subquery(Box::new(query)))
370 }
371 }
372 }
373
374 fn analyze_projection(
375 &mut self,
376 ctx: &mut AnalysisContext,
377 expr: ExprRef,
378 ) -> AnalysisResult<Type> {
379 let node = self.arena.exprs.get(expr);
380 match node.value {
381 Value::Record(record) => {
382 if self.arena.exprs.rec(record).is_empty() {
383 return Err(AnalysisError::EmptyRecord(
384 node.attrs.pos.line,
385 node.attrs.pos.col,
386 ));
387 }
388
389 ctx.allow_agg_func = true;
390 let tpe = self.analyze_expr(ctx, expr, Type::Unspecified)?;
391 let mut chk_ctx = CheckContext {
392 use_agg_func: ctx.use_agg_funcs,
393 ..Default::default()
394 };
395
396 self.check_projection_on_record(&mut chk_ctx, record)?;
397 Ok(tpe)
398 }
399
400 Value::App(app) => {
401 ctx.allow_agg_func = true;
402
403 let tpe = self.analyze_expr(ctx, expr, Type::Unspecified)?;
404
405 if ctx.use_agg_funcs {
406 let mut chk_ctx = CheckContext {
407 use_agg_func: ctx.use_agg_funcs,
408 ..Default::default()
409 };
410
411 self.check_projection_on_field_expr(&mut chk_ctx, expr)?;
412 } else {
413 self.reject_constant_func(node.attrs, &app)?;
414 }
415
416 Ok(tpe)
417 }
418
419 Value::Id(_) if ctx.use_agg_funcs => Err(AnalysisError::ExpectAggExpr(
420 node.attrs.pos.line,
421 node.attrs.pos.col,
422 )),
423
424 Value::Id(id) => {
425 if let Some(tpe) = self.scope.get(id) {
426 Ok(tpe)
427 } else {
428 Err(AnalysisError::VariableUndeclared(
429 node.attrs.pos.line,
430 node.attrs.pos.col,
431 self.arena.strings.get(id).to_owned(),
432 ))
433 }
434 }
435
436 Value::Access(_) if ctx.use_agg_funcs => Err(AnalysisError::ExpectAggExpr(
437 node.attrs.pos.line,
438 node.attrs.pos.col,
439 )),
440
441 Value::Access(access) => {
442 let mut current = self.arena.exprs.get(access.target);
443
444 loop {
445 match current.value {
446 Value::Id(name) => {
447 if !self.scope.exists(name) {
448 return Err(AnalysisError::VariableUndeclared(
449 current.attrs.pos.line,
450 current.attrs.pos.col,
451 self.arena.strings.get(name).to_owned(),
452 ));
453 }
454
455 break;
456 }
457
458 Value::Access(next) => current = self.arena.exprs.get(next.target),
459 _ => unreachable!(),
460 }
461 }
462
463 self.analyze_expr(ctx, expr, Type::Unspecified)
464 }
465
466 _ => {
467 let tpe = self.project_type(expr);
468
469 Err(AnalysisError::ExpectRecordOrSourcedProperty(
470 node.attrs.pos.line,
471 node.attrs.pos.col,
472 display_type(self.arena, tpe),
473 ))
474 }
475 }
476 }
477
478 fn check_projection_on_record(
479 &mut self,
480 ctx: &mut CheckContext,
481 record: RecRef,
482 ) -> AnalysisResult<()> {
483 for idx in 0..self.arena.exprs.rec(record).len() {
484 let field = self.arena.exprs.rec_get(record, idx);
485
486 self.check_projection_on_field(ctx, &field)?;
487 }
488
489 Ok(())
490 }
491
492 fn check_projection_on_field(
493 &mut self,
494 ctx: &mut CheckContext,
495 field: &Field,
496 ) -> AnalysisResult<()> {
497 self.check_projection_on_field_expr(ctx, field.expr)
498 }
499
500 fn check_projection_on_field_expr(
501 &mut self,
502 ctx: &mut CheckContext,
503 expr: ExprRef,
504 ) -> AnalysisResult<()> {
505 let node = self.arena.exprs.get(expr);
506 match node.value {
507 Value::Number(_) | Value::String(_) | Value::Bool(_) => Ok(()),
508
509 Value::Id(id) => {
510 if self.scope.exists(id) {
511 if ctx.use_agg_func {
512 return Err(AnalysisError::UnallowedAggFuncUsageWithSrcField(
513 node.attrs.pos.line,
514 node.attrs.pos.col,
515 ));
516 }
517
518 ctx.use_source_based = true;
519 }
520
521 Ok(())
522 }
523
524 Value::Array(exprs) => {
525 for idx in self.arena.exprs.vec_idxes(exprs) {
526 let expr = self.arena.exprs.vec_get(exprs, idx);
527
528 self.check_projection_on_field_expr(ctx, expr)?;
529 }
530
531 Ok(())
532 }
533
534 Value::Record(fields) => {
535 for idx in self.arena.exprs.rec_idxes(fields) {
536 let field = self.arena.exprs.rec_get(fields, idx);
537
538 self.check_projection_on_field(ctx, &field)?;
539 }
540
541 Ok(())
542 }
543
544 Value::Access(access) => self.check_projection_on_field_expr(ctx, access.target),
545
546 Value::App(app) => {
547 if let Some(Type::App { aggregate, .. }) = self.options.default_scope.get(app.func)
548 {
549 ctx.use_agg_func |= aggregate;
550
551 if ctx.use_agg_func && ctx.use_source_based {
552 return Err(AnalysisError::UnallowedAggFuncUsageWithSrcField(
553 node.attrs.pos.line,
554 node.attrs.pos.col,
555 ));
556 }
557
558 if aggregate {
559 return self.expect_agg_func(expr);
560 }
561
562 for idx in self.arena.exprs.vec_idxes(app.args) {
563 let arg = self.arena.exprs.vec_get(app.args, idx);
564
565 self.invalidate_agg_func_usage(arg)?;
566 }
567 }
568
569 Ok(())
570 }
571
572 Value::Binary(binary) => {
573 self.check_projection_on_field_expr(ctx, binary.lhs)?;
574 self.check_projection_on_field_expr(ctx, binary.rhs)
575 }
576
577 Value::Unary(unary) => self.check_projection_on_field_expr(ctx, unary.expr),
578 Value::Group(expr) => self.check_projection_on_field_expr(ctx, expr),
579 }
580 }
581
582 fn expect_agg_func(&self, expr: ExprRef) -> AnalysisResult<()> {
583 let node = self.arena.exprs.get(expr);
584 if let Value::App(app) = node.value
585 && let Some(Type::App {
586 aggregate: true, ..
587 }) = self.options.default_scope.get(app.func)
588 {
589 for idx in 0..self.arena.exprs.vec(app.args).len() {
590 let arg = self.arena.exprs.vec_get(app.args, idx);
591
592 self.ensure_agg_param_is_source_bound(arg)?;
593 self.invalidate_agg_func_usage(arg)?;
594 }
595
596 return Ok(());
597 }
598
599 Err(AnalysisError::ExpectAggExpr(
600 node.attrs.pos.line,
601 node.attrs.pos.col,
602 ))
603 }
604
605 fn expect_agg_expr(&self, expr: ExprRef) -> AnalysisResult<bool> {
606 let node = self.arena.exprs.get(expr);
607 match node.value {
608 Value::Id(id) => {
609 if self.scope.exists(id) {
610 return Err(AnalysisError::UnallowedAggFuncUsageWithSrcField(
611 node.attrs.pos.line,
612 node.attrs.pos.col,
613 ));
614 }
615
616 Ok(false)
617 }
618 Value::Group(expr) => self.expect_agg_expr(expr),
619 Value::Binary(binary) => {
620 let lhs = self.expect_agg_expr(binary.lhs)?;
621 let rhs = self.expect_agg_expr(binary.rhs)?;
622
623 if !lhs && !rhs {
624 return Err(AnalysisError::ExpectAggExpr(
625 node.attrs.pos.line,
626 node.attrs.pos.col,
627 ));
628 }
629
630 Ok(true)
631 }
632 Value::Unary(unary) => self.expect_agg_expr(unary.expr),
633 Value::App(_) => {
634 self.expect_agg_func(expr)?;
635 Ok(true)
636 }
637
638 _ => Ok(false),
639 }
640 }
641
642 fn ensure_agg_param_is_source_bound(&self, expr: ExprRef) -> AnalysisResult<()> {
643 let node = self.arena.exprs.get(expr);
644 match node.value {
645 Value::Id(id) if !self.options.default_scope.exists(id) => Ok(()),
646 Value::Access(access) => self.ensure_agg_param_is_source_bound(access.target),
647 Value::Binary(binary) => self.ensure_agg_binary_op_is_source_bound(node.attrs, binary),
648 Value::Unary(unary) => self.ensure_agg_param_is_source_bound(unary.expr),
649
650 _ => Err(AnalysisError::ExpectSourceBoundProperty(
651 node.attrs.pos.line,
652 node.attrs.pos.col,
653 )),
654 }
655 }
656
657 fn ensure_agg_binary_op_is_source_bound(
658 &self,
659 attrs: Attrs,
660 binary: Binary,
661 ) -> AnalysisResult<()> {
662 if !self.ensure_agg_binary_op_branch_is_source_bound(binary.lhs)
663 && !self.ensure_agg_binary_op_branch_is_source_bound(binary.rhs)
664 {
665 return Err(AnalysisError::ExpectSourceBoundProperty(
666 attrs.pos.line,
667 attrs.pos.col,
668 ));
669 }
670
671 Ok(())
672 }
673
674 fn ensure_agg_binary_op_branch_is_source_bound(&self, expr: ExprRef) -> bool {
675 let node = self.arena.exprs.get(expr);
676 match node.value {
677 Value::Id(id) => !self.options.default_scope.exists(id),
678 Value::Array(exprs) => {
679 if self.arena.exprs.vec(exprs).is_empty() {
680 return false;
681 }
682
683 for idx in 0..self.arena.exprs.vec(exprs).len() {
684 let expr = self.arena.exprs.vec_get(exprs, idx);
685
686 if !self.ensure_agg_binary_op_branch_is_source_bound(expr) {
687 return false;
688 }
689 }
690
691 true
692 }
693 Value::Record(fields) => {
694 if self.arena.exprs.rec(fields).is_empty() {
695 return false;
696 }
697
698 for idx in 0..self.arena.exprs.rec(fields).len() {
699 let field = self.arena.exprs.rec_get(fields, idx);
700
701 if !self.ensure_agg_binary_op_branch_is_source_bound(field.expr) {
702 return false;
703 }
704 }
705
706 true
707 }
708
709 Value::Access(access) => {
710 self.ensure_agg_binary_op_branch_is_source_bound(access.target)
711 }
712
713 Value::Binary(binary) => self
714 .ensure_agg_binary_op_is_source_bound(node.attrs, binary)
715 .is_ok(),
716 Value::Unary(unary) => self.ensure_agg_binary_op_branch_is_source_bound(unary.expr),
717 Value::Group(expr) => self.ensure_agg_binary_op_branch_is_source_bound(expr),
718
719 Value::Number(_) | Value::String(_) | Value::Bool(_) | Value::App(_) => false,
720 }
721 }
722
723 fn invalidate_agg_func_usage(&self, expr: ExprRef) -> AnalysisResult<()> {
724 let node = self.arena.exprs.get(expr);
725 match node.value {
726 Value::Number(_)
727 | Value::String(_)
728 | Value::Bool(_)
729 | Value::Id(_)
730 | Value::Access(_) => Ok(()),
731
732 Value::Array(exprs) => {
733 for idx in 0..self.arena.exprs.vec(exprs).len() {
734 let expr = self.arena.exprs.vec_get(exprs, idx);
735
736 self.invalidate_agg_func_usage(expr)?;
737 }
738
739 Ok(())
740 }
741
742 Value::Record(fields) => {
743 for idx in 0..self.arena.exprs.rec(fields).len() {
744 let field = self.arena.exprs.rec_get(fields, idx);
745
746 self.invalidate_agg_func_usage(field.expr)?;
747 }
748
749 Ok(())
750 }
751
752 Value::App(app) => {
753 if let Some(Type::App { aggregate, .. }) = self.options.default_scope.get(app.func)
754 && aggregate
755 {
756 return Err(AnalysisError::WrongAggFunUsage(
757 node.attrs.pos.line,
758 node.attrs.pos.col,
759 self.arena.strings.get(app.func).to_owned(),
760 ));
761 }
762
763 for idx in 0..self.arena.exprs.vec(app.args).len() {
764 let arg = self.arena.exprs.vec_get(app.args, idx);
765 self.invalidate_agg_func_usage(arg)?;
766 }
767
768 Ok(())
769 }
770
771 Value::Binary(binary) => {
772 self.invalidate_agg_func_usage(binary.lhs)?;
773 self.invalidate_agg_func_usage(binary.rhs)
774 }
775
776 Value::Unary(unary) => self.invalidate_agg_func_usage(unary.expr),
777 Value::Group(expr) => self.invalidate_agg_func_usage(expr),
778 }
779 }
780
781 fn reject_constant_func(&self, attrs: Attrs, app: &App) -> AnalysisResult<()> {
782 if self.arena.exprs.vec(app.args).is_empty() {
783 return Err(AnalysisError::ConstantExprInProjectIntoClause(
784 attrs.pos.line,
785 attrs.pos.col,
786 ));
787 }
788
789 let mut errored = None;
790 for idx in 0..self.arena.exprs.vec(app.args).len() {
791 let arg = self.arena.exprs.vec_get(app.args, idx);
792
793 if let Err(e) = self.reject_constant_expr(arg) {
794 if errored.is_none() {
795 errored = Some(e);
796 }
797
798 continue;
799 }
800
801 return Ok(());
803 }
804
805 Err(errored.expect("to be defined at that point"))
806 }
807
808 fn reject_constant_expr(&self, expr: ExprRef) -> AnalysisResult<()> {
809 let node = self.arena.exprs.get(expr);
810 match node.value {
811 Value::Id(id) if self.scope.exists(id) => Ok(()),
812 Value::Array(exprs) => {
813 let mut errored = None;
814 for idx in 0..self.arena.exprs.vec(exprs).len() {
815 let expr = self.arena.exprs.vec_get(exprs, idx);
816
817 if let Err(e) = self.reject_constant_expr(expr) {
818 if errored.is_none() {
819 errored = Some(e);
820 }
821
822 continue;
823 }
824
825 return Ok(());
827 }
828
829 Err(errored.expect("to be defined at that point"))
830 }
831
832 Value::Record(fields) => {
833 let mut errored = None;
834 for idx in 0..self.arena.exprs.rec(fields).len() {
835 let field = self.arena.exprs.rec_get(fields, idx);
836
837 if let Err(e) = self.reject_constant_expr(field.expr) {
838 if errored.is_none() {
839 errored = Some(e);
840 }
841
842 continue;
843 }
844
845 return Ok(());
847 }
848
849 Err(errored.expect("to be defined at that point"))
850 }
851
852 Value::Binary(binary) => self
853 .reject_constant_expr(binary.lhs)
854 .or_else(|e| self.reject_constant_expr(binary.rhs).map_err(|_| e)),
855
856 Value::Access(access) => self.reject_constant_expr(access.target),
857 Value::App(app) => self.reject_constant_func(node.attrs, &app),
858 Value::Unary(unary) => self.reject_constant_expr(unary.expr),
859 Value::Group(expr) => self.reject_constant_expr(expr),
860
861 _ => Err(AnalysisError::ConstantExprInProjectIntoClause(
862 node.attrs.pos.line,
863 node.attrs.pos.col,
864 )),
865 }
866 }
867
868 pub fn analyze_expr(
896 &mut self,
897 ctx: &mut AnalysisContext,
898 expr: ExprRef,
899 mut expect: Type,
900 ) -> AnalysisResult<Type> {
901 let node = self.arena.exprs.get(expr);
902 match node.value {
903 Value::Number(_) => self.arena.type_check(node.attrs, expect, Type::Number),
904 Value::String(_) => self.arena.type_check(node.attrs, expect, Type::String),
905 Value::Bool(_) => self.arena.type_check(node.attrs, expect, Type::Bool),
906
907 Value::Id(id) => {
908 if let Some(tpe) = self.options.default_scope.get(id) {
909 self.arena.type_check(node.attrs, expect, tpe)
910 } else if let Some(tpe) = self.scope.get_mut(id) {
911 *tpe = self.arena.type_check(node.attrs, mem::take(tpe), expect)?;
912
913 Ok(*tpe)
914 } else {
915 Err(AnalysisError::VariableUndeclared(
916 node.attrs.pos.line,
917 node.attrs.pos.col,
918 self.arena.strings.get(id).to_owned(),
919 ))
920 }
921 }
922
923 Value::Array(exprs) => {
924 if matches!(expect, Type::Unspecified) {
925 for idx in self.arena.exprs.vec_idxes(exprs) {
926 let expr = self.arena.exprs.vec_get(exprs, idx);
927
928 expect = self.analyze_expr(ctx, expr, expect)?;
929 }
930
931 return Ok(self.arena.types.alloc_array_of(expect));
932 }
933
934 match expect {
935 Type::Array(expect) => {
936 let mut expect = self.arena.types.get_type(expect);
937 for idx in 0..self.arena.exprs.vec(exprs).len() {
938 let expr = self.arena.exprs.vec_get(exprs, idx);
939 expect = self.analyze_expr(ctx, expr, expect)?;
940 }
941
942 Ok(self.arena.types.alloc_array_of(expect))
943 }
944
945 expect => {
946 let tpe = self.project_type(expr);
947
948 Err(AnalysisError::TypeMismatch(
949 node.attrs.pos.line,
950 node.attrs.pos.col,
951 display_type(self.arena, expect),
952 display_type(self.arena, tpe),
953 ))
954 }
955 }
956 }
957
958 Value::Record(fields) => {
959 if matches!(expect, Type::Unspecified) {
960 let mut record = FxHashMap::default();
961
962 for idx in 0..self.arena.exprs.rec(fields).len() {
963 let field = self.arena.exprs.rec_get(fields, idx);
964
965 record.insert(
966 field.name,
967 self.analyze_expr(ctx, field.expr, Type::Unspecified)?,
968 );
969 }
970
971 return Ok(Type::Record(self.arena.types.alloc_record(record)));
972 }
973
974 if let Type::Record(rec) = expect
975 && self.arena.types.record_len(rec) == self.arena.exprs.rec(fields).len()
976 {
977 for idx in self.arena.exprs.rec_idxes(fields) {
978 let field = self.arena.exprs.rec_get(fields, idx);
979
980 if let Some(tpe) = self.arena.types.record_get(rec, field.name) {
981 let new_tpe = self.analyze_expr(ctx, field.expr, tpe)?;
982 self.arena.types.record_set(rec, field.name, new_tpe);
983 continue;
984 }
985
986 return Err(AnalysisError::FieldUndeclared(
987 field.attrs.pos.line,
988 field.attrs.pos.col,
989 self.arena.strings.get(field.name).to_owned(),
990 ));
991 }
992
993 return Ok(expect);
994 }
995
996 let tpe = self.project_type(expr);
997
998 Err(AnalysisError::TypeMismatch(
999 node.attrs.pos.line,
1000 node.attrs.pos.col,
1001 display_type(self.arena, expect),
1002 display_type(self.arena, tpe),
1003 ))
1004 }
1005
1006 Value::Access(_) => Ok(self.analyze_access(node.attrs, expr, expect)?),
1007
1008 Value::App(app) => {
1009 if let Some(tpe) = self.options.default_scope.get(app.func)
1010 && let Type::App {
1011 args,
1012 result,
1013 aggregate,
1014 } = tpe
1015 {
1016 let args_actual_len = self.arena.exprs.vec(app.args).len();
1017 let args_decl_len = self.arena.types.get_args(args.values).len();
1018
1019 if !(args_actual_len >= args.needed && args_actual_len <= args_decl_len) {
1020 return Err(AnalysisError::FunWrongArgumentCount(
1021 node.attrs.pos.line,
1022 node.attrs.pos.col,
1023 self.arena.strings.get(app.func).to_owned(),
1024 ));
1025 }
1026
1027 if aggregate && !ctx.allow_agg_func {
1028 return Err(AnalysisError::WrongAggFunUsage(
1029 node.attrs.pos.line,
1030 node.attrs.pos.col,
1031 self.arena.strings.get(app.func).to_owned(),
1032 ));
1033 }
1034
1035 if aggregate && ctx.allow_agg_func {
1036 ctx.use_agg_funcs = true;
1037 }
1038
1039 let arg_types = self.arena.types.args_idxes(args.values);
1040 let args_idxes = self.arena.exprs.vec_idxes(app.args);
1041 for (val_idx, tpe_idx) in args_idxes.zip(arg_types) {
1042 let arg = self.arena.exprs.vec_get(app.args, val_idx);
1043 let tpe = self.arena.types.args_get(args.values, tpe_idx);
1044
1045 self.analyze_expr(ctx, arg, tpe)?;
1046 }
1047
1048 if matches!(expect, Type::Unspecified) {
1049 Ok(self.arena.types.get_type(result))
1050 } else {
1051 self.arena
1052 .type_check(node.attrs, expect, self.arena.types.get_type(result))
1053 }
1054 } else {
1055 Err(AnalysisError::FuncUndeclared(
1056 node.attrs.pos.line,
1057 node.attrs.pos.col,
1058 self.arena.strings.get(app.func).to_owned(),
1059 ))
1060 }
1061 }
1062
1063 Value::Binary(binary) => match binary.operator {
1064 Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => {
1065 self.analyze_expr(ctx, binary.lhs, Type::Number)?;
1066 self.analyze_expr(ctx, binary.rhs, Type::Number)?;
1067 self.arena.type_check(node.attrs, expect, Type::Number)
1068 }
1069
1070 Operator::Eq
1071 | Operator::Neq
1072 | Operator::Lt
1073 | Operator::Lte
1074 | Operator::Gt
1075 | Operator::Gte => {
1076 let lhs_expect = self.analyze_expr(ctx, binary.lhs, Type::Unspecified)?;
1077 let rhs_expect = self.analyze_expr(ctx, binary.rhs, lhs_expect)?;
1078
1079 if matches!(lhs_expect, Type::Unspecified)
1082 && !matches!(rhs_expect, Type::Unspecified)
1083 {
1084 self.analyze_expr(ctx, binary.lhs, rhs_expect)?;
1085 }
1086
1087 self.arena.type_check(node.attrs, expect, Type::Bool)
1088 }
1089
1090 Operator::Contains => {
1091 let new_expect = self.arena.types.alloc_array_of(Type::Unspecified);
1092 let lhs_expect = self.analyze_expr(ctx, binary.lhs, new_expect)?;
1093
1094 let lhs_assumption = match lhs_expect {
1095 Type::Array(inner) => self.arena.types.get_type(inner),
1096 other => {
1097 return Err(AnalysisError::ExpectArray(
1098 node.attrs.pos.line,
1099 node.attrs.pos.col,
1100 display_type(self.arena, other),
1101 ));
1102 }
1103 };
1104
1105 let rhs_expect = self.analyze_expr(ctx, binary.rhs, lhs_assumption)?;
1106
1107 if matches!(lhs_assumption, Type::Unspecified)
1110 && !matches!(rhs_expect, Type::Unspecified)
1111 {
1112 let new_expect = self.arena.types.alloc_array_of(rhs_expect);
1113 self.analyze_expr(ctx, binary.lhs, new_expect)?;
1114 }
1115
1116 self.arena.type_check(node.attrs, expect, Type::Bool)
1117 }
1118
1119 Operator::And | Operator::Or | Operator::Xor => {
1120 self.analyze_expr(ctx, binary.lhs, Type::Bool)?;
1121 self.analyze_expr(ctx, binary.rhs, Type::Bool)?;
1122 self.arena.type_check(node.attrs, expect, Type::Bool)
1123 }
1124
1125 Operator::As => {
1126 let rhs = self.arena.exprs.get(binary.rhs);
1127 if let Value::Id(name) = rhs.value {
1128 return if let Some(tpe) = name_to_type(self.arena, self.options, name) {
1129 Ok(tpe)
1131 } else {
1132 Err(AnalysisError::UnsupportedCustomType(
1133 rhs.attrs.pos.line,
1134 rhs.attrs.pos.col,
1135 self.arena.strings.get(name).to_owned(),
1136 ))
1137 };
1138 }
1139
1140 unreachable!(
1141 "we already made sure during parsing that we can only have an ID symbol at this point"
1142 )
1143 }
1144
1145 Operator::Not => unreachable!(),
1146 },
1147
1148 Value::Unary(unary) => match unary.operator {
1149 Operator::Add | Operator::Sub => {
1150 self.analyze_expr(ctx, unary.expr, Type::Number)?;
1151 self.arena.type_check(node.attrs, expect, Type::Number)
1152 }
1153
1154 Operator::Not => {
1155 self.analyze_expr(ctx, unary.expr, Type::Bool)?;
1156 self.arena.type_check(node.attrs, expect, Type::Bool)
1157 }
1158
1159 _ => unreachable!(),
1160 },
1161
1162 Value::Group(expr) => Ok(self.analyze_expr(ctx, expr, expect)?),
1163 }
1164 }
1165
1166 fn analyze_access(
1167 &mut self,
1168 attrs: Attrs,
1169 access: ExprRef,
1170 expect: Type,
1171 ) -> AnalysisResult<Type> {
1172 struct State {
1173 depth: u8,
1174 dynamic: bool,
1176 definition: Def,
1177 }
1178
1179 impl State {
1180 fn new(definition: Def) -> Self {
1181 Self {
1182 depth: 0,
1183 dynamic: false,
1184 definition,
1185 }
1186 }
1187 }
1188
1189 #[derive(Copy, Clone)]
1190 struct Parent {
1191 record: Record,
1192 field: Option<StrRef>,
1193 }
1194
1195 enum Def {
1196 User { parent: Parent, tpe: Type },
1197 System(Type),
1198 }
1199
1200 fn go<'global>(
1201 scope: &mut Scope,
1202 arena: &'global mut Arena,
1203 sys: &'global AnalysisOptions,
1204 expr: ExprRef,
1205 ) -> AnalysisResult<State> {
1206 let node = arena.exprs.get(expr);
1207 match node.value {
1208 Value::Id(id) => {
1209 if let Some(tpe) = sys.default_scope.get(id) {
1210 if matches!(tpe, Type::Record(_)) {
1211 Ok(State::new(Def::System(tpe)))
1212 } else {
1213 Err(AnalysisError::ExpectRecord(
1214 node.attrs.pos.line,
1215 node.attrs.pos.col,
1216 display_type(arena, tpe),
1217 ))
1218 }
1219 } else if let Some(tpe) = scope.get_mut(id) {
1220 if matches!(tpe, Type::Unspecified) {
1221 let record = arena.types.instantiate_record();
1222 *tpe = Type::Record(record);
1223
1224 Ok(State::new(Def::User {
1225 parent: Parent {
1226 record,
1227 field: None,
1228 },
1229 tpe: Type::Record(record),
1230 }))
1231 } else if let Type::Record(record) = *tpe {
1232 Ok(State::new(Def::User {
1233 parent: Parent {
1234 record,
1235 field: None,
1236 },
1237 tpe: *tpe,
1238 }))
1239 } else {
1240 Err(AnalysisError::ExpectRecord(
1241 node.attrs.pos.line,
1242 node.attrs.pos.col,
1243 display_type(arena, *tpe),
1244 ))
1245 }
1246 } else {
1247 Err(AnalysisError::VariableUndeclared(
1248 node.attrs.pos.line,
1249 node.attrs.pos.col,
1250 arena.strings.get(id).to_owned(),
1251 ))
1252 }
1253 }
1254 Value::Access(access) => {
1255 let mut state = go(scope, arena, sys, access.target)?;
1256
1257 let is_data_field =
1259 state.depth == 0 && arena.strings.get(access.field) == "data";
1260
1261 if !state.dynamic && is_data_field {
1265 state.dynamic = true;
1266 }
1267
1268 match state.definition {
1269 Def::User { parent, tpe } => {
1270 if matches!(tpe, Type::Unspecified) && state.dynamic {
1271 let record = arena.types.instantiate_record();
1272 arena
1273 .types
1274 .record_set(record, access.field, Type::Unspecified);
1275
1276 if let Some(field) = parent.field {
1278 arena.types.record_set(
1279 parent.record,
1280 field,
1281 Type::Record(record),
1282 );
1283 }
1284
1285 return Ok(State {
1286 depth: state.depth + 1,
1287 definition: Def::User {
1288 parent: Parent {
1289 record,
1290 field: Some(access.field),
1291 },
1292 tpe: Type::Unspecified,
1293 },
1294 ..state
1295 });
1296 } else if let Type::Record(record) = tpe {
1297 return if let Some(tpe) =
1298 arena.types.record_get(record, access.field)
1299 {
1300 Ok(State {
1301 depth: state.depth + 1,
1302 definition: Def::User {
1303 parent: Parent {
1304 record,
1305 field: Some(access.field),
1306 },
1307 tpe,
1308 },
1309 ..state
1310 })
1311 } else {
1312 if state.dynamic || is_data_field {
1314 arena.types.record_set(
1315 record,
1316 access.field,
1317 Type::Unspecified,
1318 );
1319 return Ok(State {
1320 depth: state.depth + 1,
1321 definition: Def::User {
1322 parent: Parent {
1323 record,
1324 field: Some(access.field),
1325 },
1326 tpe: Type::Unspecified,
1327 },
1328 ..state
1329 });
1330 }
1331
1332 Err(AnalysisError::FieldUndeclared(
1333 node.attrs.pos.line,
1334 node.attrs.pos.col,
1335 arena.strings.get(access.field).to_owned(),
1336 ))
1337 };
1338 }
1339
1340 Err(AnalysisError::ExpectRecord(
1341 node.attrs.pos.line,
1342 node.attrs.pos.col,
1343 display_type(arena, tpe),
1344 ))
1345 }
1346
1347 Def::System(tpe) => {
1348 if matches!(tpe, Type::Unspecified) && state.dynamic {
1349 return Ok(State {
1350 depth: state.depth + 1,
1351 definition: Def::System(Type::Unspecified),
1352 ..state
1353 });
1354 }
1355
1356 if let Type::Record(rec) = tpe {
1357 if let Some(field) = arena.types.record_get(rec, access.field) {
1358 return Ok(State {
1359 depth: state.depth + 1,
1360 definition: Def::System(field),
1361 ..state
1362 });
1363 }
1364
1365 return Err(AnalysisError::FieldUndeclared(
1366 node.attrs.pos.line,
1367 node.attrs.pos.col,
1368 arena.strings.get(access.field).to_owned(),
1369 ));
1370 }
1371
1372 Err(AnalysisError::ExpectRecord(
1373 node.attrs.pos.line,
1374 node.attrs.pos.col,
1375 display_type(arena, tpe),
1376 ))
1377 }
1378 }
1379 }
1380 Value::Number(_)
1381 | Value::String(_)
1382 | Value::Bool(_)
1383 | Value::Array(_)
1384 | Value::Record(_)
1385 | Value::App(_)
1386 | Value::Binary(_)
1387 | Value::Unary(_)
1388 | Value::Group(_) => unreachable!(),
1389 }
1390 }
1391
1392 let state = go(&mut self.scope, self.arena, self.options, access)?;
1393
1394 match state.definition {
1395 Def::User { parent, tpe } => {
1396 let new_tpe = self.arena.type_check(attrs, tpe, expect)?;
1397
1398 if let Some(field) = parent.field {
1399 self.arena.types.record_set(parent.record, field, new_tpe);
1400 }
1401
1402 Ok(new_tpe)
1403 }
1404
1405 Def::System(tpe) => self.arena.type_check(attrs, tpe, expect),
1406 }
1407 }
1408
1409 fn projection_type(&mut self, query: &Query<Typed>) -> Type {
1410 self.project_type(query.projection)
1411 }
1412
1413 fn project_type(&mut self, node: ExprRef) -> Type {
1414 match self.arena.exprs.get(node).value {
1415 Value::Number(_) => Type::Number,
1416 Value::String(_) => Type::String,
1417 Value::Bool(_) => Type::Bool,
1418 Value::Id(id) => {
1419 if let Some(tpe) = self.options.default_scope.get(id) {
1420 tpe
1421 } else if let Some(tpe) = self.scope.get(id) {
1422 tpe
1423 } else {
1424 Type::Unspecified
1425 }
1426 }
1427 Value::Array(exprs) => {
1428 let mut project = Type::Unspecified;
1429
1430 for idx in self.arena.exprs.vec_idxes(exprs) {
1431 let expr = self.arena.exprs.vec_get(exprs, idx);
1432 let tmp = self.project_type(expr);
1433
1434 if !matches!(tmp, Type::Unspecified) {
1435 project = tmp;
1436 break;
1437 }
1438 }
1439
1440 self.arena.types.alloc_array_of(project)
1441 }
1442 Value::Record(fields) => {
1443 let mut props = FxHashMap::default();
1444
1445 for idx in self.arena.exprs.rec_idxes(fields) {
1446 let field = self.arena.exprs.rec_get(fields, idx);
1447 let tpe = self.project_type(field.expr);
1448 props.insert(field.name, tpe);
1449 }
1450
1451 Type::Record(self.arena.types.alloc_record(props))
1452 }
1453 Value::Access(access) => {
1454 let tpe = self.project_type(access.target);
1455 if let Type::Record(record) = tpe {
1456 self.arena
1457 .types
1458 .record_get(record, access.field)
1459 .unwrap_or_default()
1460 } else {
1461 Type::Unspecified
1462 }
1463 }
1464 Value::App(app) => self.options.default_scope.get(app.func).unwrap_or_default(),
1465 Value::Binary(binary) => match binary.operator {
1466 Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => Type::Number,
1467 Operator::As => {
1468 if let Value::Id(n) = self.arena.exprs.get(binary.rhs).value
1469 && let Some(tpe) = name_to_type(self.arena, self.options, n)
1470 {
1471 tpe
1472 } else {
1473 Type::Unspecified
1474 }
1475 }
1476 Operator::Eq
1477 | Operator::Neq
1478 | Operator::Lt
1479 | Operator::Lte
1480 | Operator::Gt
1481 | Operator::Gte
1482 | Operator::And
1483 | Operator::Or
1484 | Operator::Xor
1485 | Operator::Not
1486 | Operator::Contains => Type::Bool,
1487 },
1488 Value::Unary(unary) => match unary.operator {
1489 Operator::Add | Operator::Sub => Type::Number,
1490 Operator::Mul
1491 | Operator::Div
1492 | Operator::Eq
1493 | Operator::Neq
1494 | Operator::Lt
1495 | Operator::Lte
1496 | Operator::Gt
1497 | Operator::Gte
1498 | Operator::And
1499 | Operator::Or
1500 | Operator::Xor
1501 | Operator::Not
1502 | Operator::Contains
1503 | Operator::As => unreachable!(),
1504 },
1505 Value::Group(expr) => self.project_type(expr),
1506 }
1507 }
1508}
1509
1510impl Arena {
1511 fn type_check(&mut self, attrs: Attrs, this: Type, other: Type) -> Result<Type, AnalysisError> {
1516 match (this, other) {
1517 (Type::Unspecified, other) => Ok(other),
1518 (this, Type::Unspecified) => Ok(this),
1519 (Type::Subject, Type::Subject) => Ok(Type::Subject),
1520
1521 (Type::Subject, Type::String) => Ok(Type::String),
1525 (Type::String, Type::Subject) => Ok(Type::String),
1526
1527 (Type::Number, Type::Number) => Ok(Type::Number),
1528 (Type::String, Type::String) => Ok(Type::String),
1529 (Type::Bool, Type::Bool) => Ok(Type::Bool),
1530 (Type::Date, Type::Date) => Ok(Type::Date),
1531 (Type::Time, Type::Time) => Ok(Type::Time),
1532 (Type::DateTime, Type::DateTime) => Ok(Type::DateTime),
1533
1534 (Type::DateTime, Type::Date) => Ok(Type::Date),
1536 (Type::Date, Type::DateTime) => Ok(Type::Date),
1537 (Type::DateTime, Type::Time) => Ok(Type::Time),
1538 (Type::Time, Type::DateTime) => Ok(Type::Time),
1539 (Type::Custom(a), Type::Custom(b)) if self.strings.eq_ignore_ascii_case(a, b) => {
1540 Ok(Type::Custom(a))
1541 }
1542 (Type::Array(a), Type::Array(b)) => {
1543 let a = self.types.get_type(a);
1544 let b = self.types.get_type(b);
1545 let tpe = self.type_check(attrs, a, b)?;
1546
1547 Ok(self.types.alloc_array_of(tpe))
1548 }
1549
1550 (Type::Record(a), Type::Record(b)) if self.types.records_have_same_keys(a, b) => {
1551 let mut map_a = mem::take(&mut self.types.records[a.0]);
1552 let mut map_b = mem::take(&mut self.types.records[b.0]);
1553
1554 for (bk, bv) in map_b.iter_mut() {
1555 let av = map_a.get_mut(bk).unwrap();
1556 let new_tpe = self.type_check(attrs, *av, *bv)?;
1557
1558 *av = new_tpe;
1559 *bv = new_tpe;
1560 }
1561
1562 self.types.records[a.0] = map_a;
1563 self.types.records[b.0] = map_b;
1564
1565 Ok(Type::Record(a))
1566 }
1567
1568 (
1569 Type::App {
1570 args: a_args,
1571 result: a_res,
1572 aggregate: a_agg,
1573 },
1574 Type::App {
1575 args: b_args,
1576 result: b_res,
1577 aggregate: b_agg,
1578 },
1579 ) if self.types.get_args(a_args.values).len()
1580 == self.types.get_args(b_args.values).len()
1581 && a_agg == b_agg =>
1582 {
1583 if self.types.get_args(a_args.values).is_empty() {
1584 let a = self.types.get_type(a_res);
1585 let b = self.types.get_type(b_res);
1586 let new_res = self.type_check(attrs, a, b)?;
1587
1588 return Ok(Type::App {
1589 args: a_args,
1590 result: self.types.register_type(new_res),
1591 aggregate: a_agg,
1592 });
1593 }
1594
1595 let mut vec_a = mem::take(&mut self.types.args[a_args.values.0]);
1596 let mut vec_b = mem::take(&mut self.types.args[b_args.values.0]);
1597
1598 for (a, b) in vec_a.iter_mut().zip(vec_b.iter_mut()) {
1599 let new_tpe = self.type_check(attrs, *a, *b)?;
1600 *a = new_tpe;
1601 *b = new_tpe;
1602 }
1603
1604 self.types.args[a_args.values.0] = vec_a;
1605 self.types.args[b_args.values.0] = vec_b;
1606
1607 let res_a = self.types.get_type(a_res);
1608 let res_b = self.types.get_type(b_res);
1609 let new_tpe = self.type_check(attrs, res_a, res_b)?;
1610
1611 Ok(Type::App {
1612 args: a_args,
1613 result: self.types.register_type(new_tpe),
1614 aggregate: a_agg,
1615 })
1616 }
1617
1618 (this, other) => Err(AnalysisError::TypeMismatch(
1619 attrs.pos.line,
1620 attrs.pos.col,
1621 display_type(self, this),
1622 display_type(self, other),
1623 )),
1624 }
1625 }
1626}
1627
1628pub(crate) fn name_to_type(
1648 arena: &Arena,
1649 opts: &AnalysisOptions,
1650 name_ref: StrRef,
1651) -> Option<Type> {
1652 let name = arena.strings.get(name_ref);
1653
1654 if name.eq_ignore_ascii_case("string") {
1655 Some(Type::String)
1656 } else if name.eq_ignore_ascii_case("int") || name.eq_ignore_ascii_case("float64") {
1657 Some(Type::Number)
1658 } else if name.eq_ignore_ascii_case("boolean") {
1659 Some(Type::Bool)
1660 } else if name.eq_ignore_ascii_case("date") {
1661 Some(Type::Date)
1662 } else if name.eq_ignore_ascii_case("time") {
1663 Some(Type::Time)
1664 } else if name.eq_ignore_ascii_case("datetime") {
1665 Some(Type::DateTime)
1666 } else if opts.custom_types.contains(&Ascii::new(name.to_owned())) {
1667 Some(Type::Custom(name_ref))
1670 } else {
1671 None
1672 }
1673}
1674
1675pub(crate) fn display_type(arena: &Arena, tpe: Type) -> String {
1676 fn go(buffer: &mut String, arena: &Arena, tpe: Type) {
1677 match tpe {
1678 Type::Unspecified => buffer.push_str("Any"),
1679 Type::Number => buffer.push_str("Number"),
1680 Type::String => buffer.push_str("String"),
1681 Type::Bool => buffer.push_str("Bool"),
1682 Type::Subject => buffer.push_str("Subject"),
1683 Type::Date => buffer.push_str("Date"),
1684 Type::Time => buffer.push_str("Time"),
1685 Type::DateTime => buffer.push_str("DateTime"),
1686 Type::Custom(n) => buffer.push_str(arena.strings.get(n)),
1687
1688 Type::Array(tpe) => {
1689 buffer.push_str("[]");
1690 go(buffer, arena, arena.types.get_type(tpe));
1691 }
1692
1693 Type::Record(map) => {
1694 let map = arena.types.get_record(map);
1695
1696 buffer.push_str("{ ");
1697
1698 for (idx, (name, value)) in map.iter().enumerate() {
1699 if idx != 0 {
1700 buffer.push_str(", ");
1701 }
1702
1703 buffer.push_str(arena.strings.get(*name));
1704 buffer.push_str(": ");
1705
1706 go(buffer, arena, *value);
1707 }
1708
1709 buffer.push_str(" }");
1710 }
1711
1712 Type::App {
1713 args,
1714 result,
1715 aggregate,
1716 } => {
1717 let fun_args = arena.types.get_args(args.values);
1718 buffer.push('(');
1719
1720 for (idx, arg) in fun_args.iter().copied().enumerate() {
1721 if idx != 0 {
1722 buffer.push_str(", ");
1723 }
1724
1725 go(buffer, arena, arg);
1726
1727 if idx + 1 > args.needed {
1728 buffer.push('?');
1729 }
1730 }
1731
1732 buffer.push(')');
1733
1734 if aggregate {
1735 buffer.push_str(" => ");
1736 } else {
1737 buffer.push_str(" -> ");
1738 }
1739
1740 go(buffer, arena, arena.types.get_type(result));
1741 }
1742 }
1743 }
1744
1745 let mut buffer = String::new();
1746 go(&mut buffer, arena, tpe);
1747
1748 buffer
1749}