1use std::{
2 collections::{BTreeMap, HashMap, btree_map::Entry},
3 mem,
4};
5
6use serde::Serialize;
7
8use crate::{
9 Attrs, Expr, Query, Raw, Source, SourceKind, Type, Value, error::AnalysisError, token::Operator,
10};
11
12#[derive(Debug, Clone, Serialize)]
22pub struct Typed {
23 pub project: Type,
28
29 #[serde(skip)]
34 pub scope: Scope,
35}
36
37pub type AnalysisResult<A> = std::result::Result<A, AnalysisError>;
42
43pub struct AnalysisOptions {
49 pub default_scope: Scope,
51 pub event_type_info: Type,
53}
54
55impl Default for AnalysisOptions {
56 fn default() -> Self {
57 Self {
58 default_scope: Scope {
59 entries: HashMap::from([
60 (
61 "ABS".to_owned(),
62 Type::App {
63 args: vec![Type::Number],
64 result: Box::new(Type::Number),
65 },
66 ),
67 (
68 "CEIL".to_owned(),
69 Type::App {
70 args: vec![Type::Number],
71 result: Box::new(Type::Number),
72 },
73 ),
74 (
75 "FLOOR".to_owned(),
76 Type::App {
77 args: vec![Type::Number],
78 result: Box::new(Type::Number),
79 },
80 ),
81 (
82 "ROUND".to_owned(),
83 Type::App {
84 args: vec![Type::Number],
85 result: Box::new(Type::Number),
86 },
87 ),
88 (
89 "COS".to_owned(),
90 Type::App {
91 args: vec![Type::Number],
92 result: Box::new(Type::Number),
93 },
94 ),
95 (
96 "EXP".to_owned(),
97 Type::App {
98 args: vec![Type::Number],
99 result: Box::new(Type::Number),
100 },
101 ),
102 (
103 "POW".to_owned(),
104 Type::App {
105 args: vec![Type::Number, Type::Number],
106 result: Box::new(Type::Number),
107 },
108 ),
109 (
110 "SQRT".to_owned(),
111 Type::App {
112 args: vec![Type::Number],
113 result: Box::new(Type::Number),
114 },
115 ),
116 (
117 "RAND".to_owned(),
118 Type::App {
119 args: vec![Type::Number],
120 result: Box::new(Type::Number),
121 },
122 ),
123 (
124 "PI".to_owned(),
125 Type::App {
126 args: vec![Type::Number],
127 result: Box::new(Type::Number),
128 },
129 ),
130 (
131 "LOWER".to_owned(),
132 Type::App {
133 args: vec![Type::String],
134 result: Box::new(Type::String),
135 },
136 ),
137 (
138 "UPPER".to_owned(),
139 Type::App {
140 args: vec![Type::String],
141 result: Box::new(Type::String),
142 },
143 ),
144 (
145 "TRIM".to_owned(),
146 Type::App {
147 args: vec![Type::String],
148 result: Box::new(Type::String),
149 },
150 ),
151 (
152 "LTRIM".to_owned(),
153 Type::App {
154 args: vec![Type::String],
155 result: Box::new(Type::String),
156 },
157 ),
158 (
159 "RTRIM".to_owned(),
160 Type::App {
161 args: vec![Type::String],
162 result: Box::new(Type::String),
163 },
164 ),
165 (
166 "LEN".to_owned(),
167 Type::App {
168 args: vec![Type::String],
169 result: Box::new(Type::Number),
170 },
171 ),
172 (
173 "INSTR".to_owned(),
174 Type::App {
175 args: vec![Type::String],
176 result: Box::new(Type::Number),
177 },
178 ),
179 (
180 "SUBSTRING".to_owned(),
181 Type::App {
182 args: vec![Type::String, Type::Number, Type::Number],
183 result: Box::new(Type::String),
184 },
185 ),
186 (
187 "REPLACE".to_owned(),
188 Type::App {
189 args: vec![Type::String, Type::String, Type::String],
190 result: Box::new(Type::String),
191 },
192 ),
193 (
194 "STARTSWITH".to_owned(),
195 Type::App {
196 args: vec![Type::String, Type::String],
197 result: Box::new(Type::Bool),
198 },
199 ),
200 (
201 "ENDSWITH".to_owned(),
202 Type::App {
203 args: vec![Type::String, Type::String],
204 result: Box::new(Type::Bool),
205 },
206 ),
207 (
208 "NOW".to_owned(),
209 Type::App {
210 args: vec![],
211 result: Box::new(Type::String),
212 },
213 ),
214 (
215 "YEAR".to_owned(),
216 Type::App {
217 args: vec![Type::String],
218 result: Box::new(Type::Number),
219 },
220 ),
221 (
222 "MONTH".to_owned(),
223 Type::App {
224 args: vec![Type::String],
225 result: Box::new(Type::Number),
226 },
227 ),
228 (
229 "DAY".to_owned(),
230 Type::App {
231 args: vec![Type::String],
232 result: Box::new(Type::Number),
233 },
234 ),
235 (
236 "HOUR".to_owned(),
237 Type::App {
238 args: vec![Type::String],
239 result: Box::new(Type::Number),
240 },
241 ),
242 (
243 "MINUTE".to_owned(),
244 Type::App {
245 args: vec![Type::String],
246 result: Box::new(Type::Number),
247 },
248 ),
249 (
250 "SECOND".to_owned(),
251 Type::App {
252 args: vec![Type::String],
253 result: Box::new(Type::Number),
254 },
255 ),
256 (
257 "WEEKDAY".to_owned(),
258 Type::App {
259 args: vec![Type::String],
260 result: Box::new(Type::Number),
261 },
262 ),
263 (
264 "IF".to_owned(),
265 Type::App {
266 args: vec![Type::Bool, Type::Unspecified, Type::Unspecified],
267 result: Box::new(Type::Unspecified),
268 },
269 ),
270 (
271 "COUNT".to_owned(),
272 Type::App {
273 args: vec![],
274 result: Box::new(Type::Number),
275 },
276 ),
277 (
278 "SUM".to_owned(),
279 Type::App {
280 args: vec![Type::Number],
281 result: Box::new(Type::Number),
282 },
283 ),
284 (
285 "AVG".to_owned(),
286 Type::App {
287 args: vec![Type::Number],
288 result: Box::new(Type::Number),
289 },
290 ),
291 (
292 "MIN".to_owned(),
293 Type::App {
294 args: vec![Type::Number],
295 result: Box::new(Type::Number),
296 },
297 ),
298 (
299 "MAX".to_owned(),
300 Type::App {
301 args: vec![Type::Number],
302 result: Box::new(Type::Number),
303 },
304 ),
305 (
306 "MEDIAN".to_owned(),
307 Type::App {
308 args: vec![Type::Number],
309 result: Box::new(Type::Number),
310 },
311 ),
312 (
313 "STDDEV".to_owned(),
314 Type::App {
315 args: vec![Type::Number],
316 result: Box::new(Type::Number),
317 },
318 ),
319 (
320 "VARIANCE".to_owned(),
321 Type::App {
322 args: vec![Type::Number],
323 result: Box::new(Type::Number),
324 },
325 ),
326 (
327 "UNIQUE".to_owned(),
328 Type::App {
329 args: vec![Type::Number],
330 result: Box::new(Type::Number),
331 },
332 ),
333 ]),
334 },
335 event_type_info: Type::Record(BTreeMap::from([
336 ("specversion".to_owned(), Type::String),
337 ("id".to_owned(), Type::String),
338 ("time".to_owned(), Type::String),
339 ("source".to_owned(), Type::String),
340 ("subject".to_owned(), Type::Subject),
341 ("type".to_owned(), Type::String),
342 ("datacontenttype".to_owned(), Type::String),
343 ("data".to_owned(), Type::Unspecified),
344 ("predecessorhash".to_owned(), Type::String),
345 ("hash".to_owned(), Type::String),
346 ("traceparent".to_owned(), Type::String),
347 ("tracestate".to_owned(), Type::String),
348 ("signature".to_owned(), Type::String),
349 ])),
350 }
351 }
352}
353
354pub fn static_analysis(
372 options: &AnalysisOptions,
373 query: Query<Raw>,
374) -> AnalysisResult<Query<Typed>> {
375 let mut analysis = Analysis::new(options);
376
377 analysis.analyze_query(query)
378}
379
380#[derive(Default, Serialize, Clone, Debug)]
386pub struct Scope {
387 pub entries: HashMap<String, Type>,
389}
390
391impl Scope {
392 pub fn is_empty(&self) -> bool {
394 self.entries.is_empty()
395 }
396}
397
398struct Analysis<'a> {
399 options: &'a AnalysisOptions,
400 prev_scopes: Vec<Scope>,
401 scope: Scope,
402}
403
404impl<'a> Analysis<'a> {
405 fn new(options: &'a AnalysisOptions) -> Self {
406 Self {
407 options,
408 prev_scopes: Default::default(),
409 scope: Scope::default(),
410 }
411 }
412
413 fn enter_scope(&mut self) {
414 if self.scope.is_empty() {
415 return;
416 }
417
418 let prev = mem::take(&mut self.scope);
419 self.prev_scopes.push(prev);
420 }
421
422 fn exit_scope(&mut self) -> Scope {
423 if let Some(prev) = self.prev_scopes.pop() {
424 mem::replace(&mut self.scope, prev)
425 } else {
426 mem::take(&mut self.scope)
427 }
428 }
429
430 fn analyze_query(&mut self, query: Query<Raw>) -> AnalysisResult<Query<Typed>> {
431 self.enter_scope();
432
433 let mut sources = Vec::with_capacity(query.sources.len());
434
435 for source in query.sources {
436 sources.push(self.analyze_source(source)?);
437 }
438
439 if let Some(expr) = &query.predicate {
440 self.analyze_expr(expr, Type::Bool)?;
441 }
442
443 if let Some(group_by) = &query.group_by {
444 if !matches!(&group_by.expr.value, Value::Access(_)) {
445 return Err(AnalysisError::ExpectFieldLiteral(
446 group_by.expr.attrs.pos.line,
447 group_by.expr.attrs.pos.col,
448 ));
449 }
450
451 self.analyze_expr(&group_by.expr, Type::Unspecified)?;
452
453 if let Some(expr) = &group_by.predicate {
454 self.analyze_expr(expr, Type::Bool)?;
455 }
456 }
457
458 if let Some(order_by) = &query.order_by {
459 if !matches!(&order_by.expr.value, Value::Access(_)) {
460 return Err(AnalysisError::ExpectFieldLiteral(
461 order_by.expr.attrs.pos.line,
462 order_by.expr.attrs.pos.col,
463 ));
464 }
465 self.analyze_expr(&order_by.expr, Type::Unspecified)?;
466 }
467
468 if !matches!(&query.projection.value, Value::Record(_) | Value::Id(_)) {
469 return Err(AnalysisError::ExpectRecordLiteral(
470 query.projection.attrs.pos.line,
471 query.projection.attrs.pos.col,
472 ));
473 }
474
475 let project = self.analyze_expr(&query.projection, Type::Unspecified)?;
476
477 if !matches!(&project, Type::Record(f) if !f.is_empty()) {
478 return Err(AnalysisError::ExpectRecord(
479 query.projection.attrs.pos.line,
480 query.projection.attrs.pos.col,
481 project,
482 ));
483 }
484
485 let scope = self.exit_scope();
486
487 Ok(Query {
488 attrs: query.attrs,
489 sources,
490 predicate: query.predicate,
491 group_by: query.group_by,
492 order_by: query.order_by,
493 limit: query.limit,
494 projection: query.projection,
495 distinct: query.distinct,
496 meta: Typed { project, scope },
497 })
498 }
499
500 fn analyze_source(&mut self, source: Source<Raw>) -> AnalysisResult<Source<Typed>> {
501 let kind = self.analyze_source_kind(source.kind)?;
502 let tpe = match &kind {
503 SourceKind::Name(_) | SourceKind::Subject(_) => self.options.event_type_info.clone(),
504 SourceKind::Subquery(query) => self.projection_type(query),
505 };
506
507 if self
508 .scope
509 .entries
510 .insert(source.binding.name.clone(), tpe)
511 .is_some()
512 {
513 return Err(AnalysisError::BindingAlreadyExists(
514 source.binding.pos.line,
515 source.binding.pos.col,
516 source.binding.name,
517 ));
518 }
519
520 Ok(Source {
521 binding: source.binding,
522 kind,
523 })
524 }
525
526 fn analyze_source_kind(&mut self, kind: SourceKind<Raw>) -> AnalysisResult<SourceKind<Typed>> {
527 match kind {
528 SourceKind::Name(n) => Ok(SourceKind::Name(n)),
529 SourceKind::Subject(s) => Ok(SourceKind::Subject(s)),
530 SourceKind::Subquery(query) => {
531 let query = self.analyze_query(*query)?;
532 Ok(SourceKind::Subquery(Box::new(query)))
533 }
534 }
535 }
536
537 fn analyze_expr(&mut self, expr: &Expr, expect: Type) -> AnalysisResult<Type> {
538 self.analyze_value(&expr.attrs, &expr.value, expect)
539 }
540
541 fn analyze_value(
542 &mut self,
543 attrs: &Attrs,
544 value: &Value,
545 expect: Type,
546 ) -> AnalysisResult<Type> {
547 match value {
548 Value::Number(_) => expect.check(attrs, Type::Number),
549 Value::String(_) => expect.check(attrs, Type::String),
550 Value::Bool(_) => expect.check(attrs, Type::Bool),
551
552 Value::Id(id) => {
553 if let Some(tpe) = self.options.default_scope.entries.get(id) {
554 expect.check(attrs, tpe.clone())
555 } else if let Some(tpe) = self.scope.entries.get_mut(id.as_str()) {
556 let tmp = mem::take(tpe);
557 *tpe = tmp.check(attrs, expect)?;
558
559 Ok(tpe.clone())
560 } else {
561 Err(AnalysisError::VariableUndeclared(
562 attrs.pos.line,
563 attrs.pos.col,
564 id.to_owned(),
565 ))
566 }
567 }
568
569 this @ Value::Array(exprs) => {
570 if matches!(expect, Type::Unspecified) {
571 return Ok(self.project_type(this));
572 }
573
574 match expect {
575 Type::Array(mut types) if exprs.len() == types.len() => {
576 for (expr, expect) in exprs.iter().zip(types.iter_mut()) {
577 let tmp = mem::take(expect);
578 *expect = self.analyze_expr(expr, tmp)?;
579 }
580
581 Ok(Type::Array(types))
582 }
583
584 expect => Err(AnalysisError::TypeMismatch(
585 attrs.pos.line,
586 attrs.pos.col,
587 expect,
588 self.project_type(value),
589 )),
590 }
591 }
592
593 this @ Value::Record(fields) => {
594 if matches!(expect, Type::Unspecified) {
595 return Ok(self.project_type(this));
596 }
597
598 match expect {
599 Type::Record(mut types) if fields.len() == types.len() => {
600 for field in fields {
601 if let Some(tpe) = types.remove(field.name.as_str()) {
602 types.insert(
603 field.name.clone(),
604 self.analyze_expr(&field.value, tpe)?,
605 );
606 } else {
607 return Err(AnalysisError::FieldUndeclared(
608 attrs.pos.line,
609 attrs.pos.col,
610 field.name.clone(),
611 ));
612 }
613 }
614
615 Ok(Type::Record(types))
616 }
617
618 expect => Err(AnalysisError::TypeMismatch(
619 attrs.pos.line,
620 attrs.pos.col,
621 expect,
622 self.project_type(value),
623 )),
624 }
625 }
626
627 this @ Value::Access(_) => Ok(self.analyze_access(attrs, this, expect)?),
628
629 this @ Value::App(app) => {
630 if matches!(expect, Type::Unspecified) {
631 return Ok(self.project_type(this));
632 }
633
634 match expect {
635 Type::App { args, mut result } if app.args.len() == args.len() => {
636 let mut arg_types = Vec::with_capacity(args.capacity());
637 for (arg, tpe) in app.args.iter().zip(args.into_iter()) {
638 arg_types.push(self.analyze_expr(arg, tpe)?);
639 }
640
641 if let Some(tpe) = self.options.default_scope.entries.get(app.func.as_str())
642 {
643 let tmp = mem::take(result.as_mut());
644 *result = tmp.check(attrs, tpe.clone())?;
645
646 Ok(Type::App {
647 args: arg_types,
648 result,
649 })
650 } else {
651 Err(AnalysisError::FuncUndeclared(
652 attrs.pos.line,
653 attrs.pos.col,
654 app.func.clone(),
655 ))
656 }
657 }
658
659 expect => Err(AnalysisError::TypeMismatch(
660 attrs.pos.line,
661 attrs.pos.col,
662 expect,
663 self.project_type(value),
664 )),
665 }
666 }
667
668 Value::Binary(binary) => match binary.operator {
669 Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => {
670 self.analyze_expr(&binary.lhs, Type::Number)?;
671 self.analyze_expr(&binary.rhs, Type::Number)?;
672 expect.check(attrs, Type::Number)
673 }
674
675 Operator::Eq
676 | Operator::Neq
677 | Operator::Lt
678 | Operator::Lte
679 | Operator::Gt
680 | Operator::Gte => {
681 let lhs_expect = self.analyze_expr(&binary.lhs, Type::Unspecified)?;
682 let rhs_expect = self.analyze_expr(&binary.rhs, lhs_expect.clone())?;
683
684 if matches!(lhs_expect, Type::Unspecified)
687 && !matches!(rhs_expect, Type::Unspecified)
688 {
689 self.analyze_expr(&binary.lhs, rhs_expect)?;
690 }
691
692 expect.check(attrs, Type::Bool)
693 }
694
695 Operator::And | Operator::Or | Operator::Xor => {
696 self.analyze_expr(&binary.lhs, Type::Bool)?;
697 self.analyze_expr(&binary.rhs, Type::Bool)?;
698
699 expect.check(attrs, Type::Bool)
700 }
701
702 Operator::Not => unreachable!(),
703 },
704
705 Value::Unary(unary) => match unary.operator {
706 Operator::Add | Operator::Sub => {
707 self.analyze_expr(&unary.expr, Type::Number)?;
708 expect.check(attrs, Type::Number)
709 }
710
711 Operator::Not => {
712 self.analyze_expr(&unary.expr, Type::Bool)?;
713 expect.check(attrs, Type::Bool)
714 }
715
716 _ => unreachable!(),
717 },
718
719 Value::Group(expr) => Ok(self.analyze_expr(expr.as_ref(), expect)?),
720 }
721 }
722
723 fn analyze_access(
724 &mut self,
725 attrs: &Attrs,
726 access: &Value,
727 expect: Type,
728 ) -> AnalysisResult<Type> {
729 struct State<A, B> {
730 depth: u8,
731 dynamic: bool,
733 definition: Def<A, B>,
734 }
735
736 impl<A, B> State<A, B> {
737 fn new(definition: Def<A, B>) -> Self {
738 Self {
739 depth: 0,
740 dynamic: false,
741 definition,
742 }
743 }
744 }
745
746 enum Def<A, B> {
747 User(A),
748 System(B),
749 }
750
751 fn go<'a>(
752 scope: &'a mut Scope,
753 sys: &'a AnalysisOptions,
754 attrs: &'a Attrs,
755 value: &'a Value,
756 ) -> AnalysisResult<State<&'a mut Type, &'a Type>> {
757 match value {
758 Value::Id(id) => {
759 if let Some(tpe) = sys.default_scope.entries.get(id.as_str()) {
760 Ok(State::new(Def::System(tpe)))
761 } else if let Some(tpe) = scope.entries.get_mut(id.as_str()) {
762 Ok(State::new(Def::User(tpe)))
763 } else {
764 Err(AnalysisError::VariableUndeclared(
765 attrs.pos.line,
766 attrs.pos.col,
767 id.clone(),
768 ))
769 }
770 }
771 Value::Access(access) => {
772 let mut state = go(scope, sys, &access.target.attrs, &access.target.value)?;
773
774 let is_data_field = state.depth == 0 && access.field == "data";
776
777 if !state.dynamic && is_data_field {
781 state.dynamic = true;
782 }
783
784 match state.definition {
785 Def::User(tpe) => {
786 if matches!(tpe, Type::Unspecified) && state.dynamic {
787 *tpe = Type::Record(BTreeMap::from([(
788 access.field.clone(),
789 Type::Unspecified,
790 )]));
791 return Ok(State {
792 depth: state.depth + 1,
793 definition: Def::User(
794 tpe.as_record_or_panic_mut()
795 .get_mut(access.field.as_str())
796 .unwrap(),
797 ),
798 ..state
799 });
800 }
801
802 if let Type::Record(fields) = tpe {
803 match fields.entry(access.field.clone()) {
804 Entry::Vacant(entry) => {
805 if state.dynamic || is_data_field {
806 return Ok(State {
807 depth: state.depth + 1,
808 definition: Def::User(
809 entry.insert(Type::Unspecified),
810 ),
811 ..state
812 });
813 }
814
815 return Err(AnalysisError::FieldUndeclared(
816 attrs.pos.line,
817 attrs.pos.col,
818 access.field.clone(),
819 ));
820 }
821
822 Entry::Occupied(entry) => {
823 return Ok(State {
824 depth: state.depth + 1,
825 definition: Def::User(entry.into_mut()),
826 ..state
827 });
828 }
829 }
830 }
831
832 Err(AnalysisError::ExpectRecord(
833 attrs.pos.line,
834 attrs.pos.col,
835 tpe.clone(),
836 ))
837 }
838
839 Def::System(tpe) => {
840 if matches!(tpe, Type::Unspecified) && state.dynamic {
841 return Ok(State {
842 depth: state.depth + 1,
843 definition: Def::System(&Type::Unspecified),
844 ..state
845 });
846 }
847
848 if let Type::Record(fields) = tpe {
849 if let Some(field) = fields.get(access.field.as_str()) {
850 return Ok(State {
851 depth: state.depth + 1,
852 definition: Def::System(field),
853 ..state
854 });
855 }
856
857 return Err(AnalysisError::FieldUndeclared(
858 attrs.pos.line,
859 attrs.pos.col,
860 access.field.clone(),
861 ));
862 }
863
864 Err(AnalysisError::ExpectRecord(
865 attrs.pos.line,
866 attrs.pos.col,
867 tpe.clone(),
868 ))
869 }
870 }
871 }
872 Value::Number(_)
873 | Value::String(_)
874 | Value::Bool(_)
875 | Value::Array(_)
876 | Value::Record(_)
877 | Value::App(_)
878 | Value::Binary(_)
879 | Value::Unary(_)
880 | Value::Group(_) => unreachable!(),
881 }
882 }
883
884 let state = go(&mut self.scope, self.options, attrs, access)?;
885
886 match state.definition {
887 Def::User(tpe) => {
888 let tmp = mem::take(tpe);
889 *tpe = tmp.check(attrs, expect)?;
890
891 Ok(tpe.clone())
892 }
893
894 Def::System(tpe) => tpe.clone().check(attrs, expect),
895 }
896 }
897
898 fn projection_type(&self, query: &Query<Typed>) -> Type {
899 self.project_type(&query.projection.value)
900 }
901
902 fn project_type(&self, value: &Value) -> Type {
903 match value {
904 Value::Number(_) => Type::Number,
905 Value::String(_) => Type::String,
906 Value::Bool(_) => Type::Bool,
907 Value::Id(id) => {
908 if let Some(tpe) = self.options.default_scope.entries.get(id) {
909 tpe.clone()
910 } else if let Some(tpe) = self.scope.entries.get(id) {
911 tpe.clone()
912 } else {
913 Type::Unspecified
914 }
915 }
916 Value::Array(exprs) => {
917 Type::Array(exprs.iter().map(|v| self.project_type(&v.value)).collect())
918 }
919 Value::Record(fields) => Type::Record(
920 fields
921 .iter()
922 .map(|field| (field.name.clone(), self.project_type(&field.value.value)))
923 .collect(),
924 ),
925 Value::Access(access) => {
926 let tpe = self.project_type(&access.target.value);
927 if let Type::Record(fields) = tpe {
928 fields
929 .get(access.field.as_str())
930 .cloned()
931 .unwrap_or_default()
932 } else {
933 Type::Unspecified
934 }
935 }
936 Value::App(app) => self
937 .options
938 .default_scope
939 .entries
940 .get(app.func.as_str())
941 .cloned()
942 .unwrap_or_default(),
943 Value::Binary(binary) => match binary.operator {
944 Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => Type::Number,
945 Operator::Eq
946 | Operator::Neq
947 | Operator::Lt
948 | Operator::Lte
949 | Operator::Gt
950 | Operator::Gte
951 | Operator::And
952 | Operator::Or
953 | Operator::Xor
954 | Operator::Not => Type::Bool,
955 },
956 Value::Unary(unary) => match unary.operator {
957 Operator::Add | Operator::Sub => Type::Number,
958 Operator::Mul
959 | Operator::Div
960 | Operator::Eq
961 | Operator::Neq
962 | Operator::Lt
963 | Operator::Lte
964 | Operator::Gt
965 | Operator::Gte
966 | Operator::And
967 | Operator::Or
968 | Operator::Xor
969 | Operator::Not => unreachable!(),
970 },
971 Value::Group(expr) => self.project_type(&expr.value),
972 }
973 }
974}