1use std::{
2 borrow::Cow,
3 collections::{BTreeMap, HashMap, HashSet, btree_map::Entry},
4 mem,
5};
6
7use serde::Serialize;
8use unicase::Ascii;
9
10use crate::{
11 Attrs, Binary, Expr, Field, FunArgs, Query, Raw, Source, SourceKind, Type, Value,
12 error::AnalysisError, token::Operator,
13};
14
15#[derive(Debug, Clone, Serialize)]
25pub struct Typed {
26 pub project: Type,
31
32 #[serde(skip)]
37 pub scope: Scope,
38}
39
40pub type AnalysisResult<A> = std::result::Result<A, AnalysisError>;
45
46pub struct AnalysisOptions {
52 pub default_scope: Scope,
54 pub event_type_info: Type,
56 pub custom_types: HashSet<Ascii<String>>,
71}
72
73impl AnalysisOptions {
74 pub fn add_custom_type<'a>(mut self, value: impl Into<Cow<'a, str>>) -> Self {
98 match value.into() {
99 Cow::Borrowed(t) => self.custom_types.insert(Ascii::new(t.to_owned())),
100 Cow::Owned(t) => self.custom_types.insert(Ascii::new(t)),
101 };
102
103 self
104 }
105}
106
107impl Default for AnalysisOptions {
108 fn default() -> Self {
109 Self {
110 default_scope: Scope {
111 entries: HashMap::from([
112 (
113 "ABS".to_owned(),
114 Type::App {
115 args: vec![Type::Number].into(),
116 result: Box::new(Type::Number),
117 aggregate: false,
118 },
119 ),
120 (
121 "CEIL".to_owned(),
122 Type::App {
123 args: vec![Type::Number].into(),
124 result: Box::new(Type::Number),
125 aggregate: false,
126 },
127 ),
128 (
129 "FLOOR".to_owned(),
130 Type::App {
131 args: vec![Type::Number].into(),
132 result: Box::new(Type::Number),
133 aggregate: false,
134 },
135 ),
136 (
137 "ROUND".to_owned(),
138 Type::App {
139 args: vec![Type::Number].into(),
140 result: Box::new(Type::Number),
141 aggregate: false,
142 },
143 ),
144 (
145 "COS".to_owned(),
146 Type::App {
147 args: vec![Type::Number].into(),
148 result: Box::new(Type::Number),
149 aggregate: false,
150 },
151 ),
152 (
153 "EXP".to_owned(),
154 Type::App {
155 args: vec![Type::Number].into(),
156 result: Box::new(Type::Number),
157 aggregate: false,
158 },
159 ),
160 (
161 "POW".to_owned(),
162 Type::App {
163 args: vec![Type::Number, Type::Number].into(),
164 result: Box::new(Type::Number),
165 aggregate: false,
166 },
167 ),
168 (
169 "SQRT".to_owned(),
170 Type::App {
171 args: vec![Type::Number].into(),
172 result: Box::new(Type::Number),
173 aggregate: false,
174 },
175 ),
176 (
177 "RAND".to_owned(),
178 Type::App {
179 args: vec![].into(),
180 result: Box::new(Type::Number),
181 aggregate: false,
182 },
183 ),
184 (
185 "PI".to_owned(),
186 Type::App {
187 args: vec![Type::Number].into(),
188 result: Box::new(Type::Number),
189 aggregate: false,
190 },
191 ),
192 (
193 "LOWER".to_owned(),
194 Type::App {
195 args: vec![Type::String].into(),
196 result: Box::new(Type::String),
197 aggregate: false,
198 },
199 ),
200 (
201 "UPPER".to_owned(),
202 Type::App {
203 args: vec![Type::String].into(),
204 result: Box::new(Type::String),
205 aggregate: false,
206 },
207 ),
208 (
209 "TRIM".to_owned(),
210 Type::App {
211 args: vec![Type::String].into(),
212 result: Box::new(Type::String),
213 aggregate: false,
214 },
215 ),
216 (
217 "LTRIM".to_owned(),
218 Type::App {
219 args: vec![Type::String].into(),
220 result: Box::new(Type::String),
221 aggregate: false,
222 },
223 ),
224 (
225 "RTRIM".to_owned(),
226 Type::App {
227 args: vec![Type::String].into(),
228 result: Box::new(Type::String),
229 aggregate: false,
230 },
231 ),
232 (
233 "LEN".to_owned(),
234 Type::App {
235 args: vec![Type::String].into(),
236 result: Box::new(Type::Number),
237 aggregate: false,
238 },
239 ),
240 (
241 "INSTR".to_owned(),
242 Type::App {
243 args: vec![Type::String].into(),
244 result: Box::new(Type::Number),
245 aggregate: false,
246 },
247 ),
248 (
249 "SUBSTRING".to_owned(),
250 Type::App {
251 args: vec![Type::String, Type::Number, Type::Number].into(),
252 result: Box::new(Type::String),
253 aggregate: false,
254 },
255 ),
256 (
257 "REPLACE".to_owned(),
258 Type::App {
259 args: vec![Type::String, Type::String, Type::String].into(),
260 result: Box::new(Type::String),
261 aggregate: false,
262 },
263 ),
264 (
265 "STARTSWITH".to_owned(),
266 Type::App {
267 args: vec![Type::String, Type::String].into(),
268 result: Box::new(Type::Bool),
269 aggregate: false,
270 },
271 ),
272 (
273 "ENDSWITH".to_owned(),
274 Type::App {
275 args: vec![Type::String, Type::String].into(),
276 result: Box::new(Type::Bool),
277 aggregate: false,
278 },
279 ),
280 (
281 "NOW".to_owned(),
282 Type::App {
283 args: vec![].into(),
284 result: Box::new(Type::DateTime),
285 aggregate: false,
286 },
287 ),
288 (
289 "YEAR".to_owned(),
290 Type::App {
291 args: vec![Type::Date].into(),
292 result: Box::new(Type::Number),
293 aggregate: false,
294 },
295 ),
296 (
297 "MONTH".to_owned(),
298 Type::App {
299 args: vec![Type::Date].into(),
300 result: Box::new(Type::Number),
301 aggregate: false,
302 },
303 ),
304 (
305 "DAY".to_owned(),
306 Type::App {
307 args: vec![Type::Date].into(),
308 result: Box::new(Type::Number),
309 aggregate: false,
310 },
311 ),
312 (
313 "HOUR".to_owned(),
314 Type::App {
315 args: vec![Type::Time].into(),
316 result: Box::new(Type::Number),
317 aggregate: false,
318 },
319 ),
320 (
321 "MINUTE".to_owned(),
322 Type::App {
323 args: vec![Type::Time].into(),
324 result: Box::new(Type::Number),
325 aggregate: false,
326 },
327 ),
328 (
329 "SECOND".to_owned(),
330 Type::App {
331 args: vec![Type::Time].into(),
332 result: Box::new(Type::Number),
333 aggregate: false,
334 },
335 ),
336 (
337 "WEEKDAY".to_owned(),
338 Type::App {
339 args: vec![Type::Date].into(),
340 result: Box::new(Type::Number),
341 aggregate: false,
342 },
343 ),
344 (
345 "IF".to_owned(),
346 Type::App {
347 args: vec![Type::Bool, Type::Unspecified, Type::Unspecified].into(),
348 result: Box::new(Type::Unspecified),
349 aggregate: false,
350 },
351 ),
352 (
353 "COUNT".to_owned(),
354 Type::App {
355 args: FunArgs {
356 values: vec![Type::Bool],
357 needed: 0,
358 },
359 result: Box::new(Type::Number),
360 aggregate: true,
361 },
362 ),
363 (
364 "SUM".to_owned(),
365 Type::App {
366 args: vec![Type::Number].into(),
367 result: Box::new(Type::Number),
368 aggregate: true,
369 },
370 ),
371 (
372 "AVG".to_owned(),
373 Type::App {
374 args: vec![Type::Number].into(),
375 result: Box::new(Type::Number),
376 aggregate: true,
377 },
378 ),
379 (
380 "MIN".to_owned(),
381 Type::App {
382 args: vec![Type::Number].into(),
383 result: Box::new(Type::Number),
384 aggregate: true,
385 },
386 ),
387 (
388 "MAX".to_owned(),
389 Type::App {
390 args: vec![Type::Number].into(),
391 result: Box::new(Type::Number),
392 aggregate: true,
393 },
394 ),
395 (
396 "MEDIAN".to_owned(),
397 Type::App {
398 args: vec![Type::Number].into(),
399 result: Box::new(Type::Number),
400 aggregate: true,
401 },
402 ),
403 (
404 "STDDEV".to_owned(),
405 Type::App {
406 args: vec![Type::Number].into(),
407 result: Box::new(Type::Number),
408 aggregate: true,
409 },
410 ),
411 (
412 "VARIANCE".to_owned(),
413 Type::App {
414 args: vec![Type::Number].into(),
415 result: Box::new(Type::Number),
416 aggregate: true,
417 },
418 ),
419 (
420 "UNIQUE".to_owned(),
421 Type::App {
422 args: vec![Type::Unspecified].into(),
423 result: Box::new(Type::Unspecified),
424 aggregate: true,
425 },
426 ),
427 ]),
428 },
429 event_type_info: Type::Record(BTreeMap::from([
430 ("specversion".to_owned(), Type::String),
431 ("id".to_owned(), Type::String),
432 ("time".to_owned(), Type::DateTime),
433 ("source".to_owned(), Type::String),
434 ("subject".to_owned(), Type::Subject),
435 ("type".to_owned(), Type::String),
436 ("datacontenttype".to_owned(), Type::String),
437 ("data".to_owned(), Type::Unspecified),
438 ("predecessorhash".to_owned(), Type::String),
439 ("hash".to_owned(), Type::String),
440 ("traceparent".to_owned(), Type::String),
441 ("tracestate".to_owned(), Type::String),
442 ("signature".to_owned(), Type::String),
443 ])),
444 custom_types: HashSet::default(),
445 }
446 }
447}
448
449pub fn static_analysis(
471 options: &AnalysisOptions,
472 query: Query<Raw>,
473) -> AnalysisResult<Query<Typed>> {
474 let mut analysis = Analysis::new(options);
475
476 analysis.analyze_query(query)
477}
478
479#[derive(Default, Serialize, Clone, Debug)]
485pub struct Scope {
486 pub entries: HashMap<String, Type>,
488}
489
490impl Scope {
491 pub fn is_empty(&self) -> bool {
493 self.entries.is_empty()
494 }
495}
496
497#[derive(Default)]
498struct CheckContext {
499 use_agg_func: bool,
500 use_source_based: bool,
501}
502
503#[derive(Default)]
508pub struct AnalysisContext {
509 pub allow_agg_func: bool,
515}
516
517pub struct Analysis<'a> {
522 options: &'a AnalysisOptions,
524 prev_scopes: Vec<Scope>,
526 scope: Scope,
528}
529
530impl<'a> Analysis<'a> {
531 pub fn new(options: &'a AnalysisOptions) -> Self {
533 Self {
534 options,
535 prev_scopes: Default::default(),
536 scope: Scope::default(),
537 }
538 }
539
540 pub fn scope(&self) -> &Scope {
548 &self.scope
549 }
550
551 pub fn scope_mut(&mut self) -> &mut Scope {
559 &mut self.scope
560 }
561
562 fn enter_scope(&mut self) {
563 if self.scope.is_empty() {
564 return;
565 }
566
567 let prev = mem::take(&mut self.scope);
568 self.prev_scopes.push(prev);
569 }
570
571 fn exit_scope(&mut self) -> Scope {
572 if let Some(prev) = self.prev_scopes.pop() {
573 mem::replace(&mut self.scope, prev)
574 } else {
575 mem::take(&mut self.scope)
576 }
577 }
578
579 pub fn analyze_query(&mut self, query: Query<Raw>) -> AnalysisResult<Query<Typed>> {
608 self.enter_scope();
609
610 let mut sources = Vec::with_capacity(query.sources.len());
611 let mut ctx = AnalysisContext::default();
612
613 for source in query.sources {
614 sources.push(self.analyze_source(source)?);
615 }
616
617 if let Some(expr) = &query.predicate {
618 self.analyze_expr(&ctx, expr, Type::Bool)?;
619 }
620
621 if let Some(group_by) = &query.group_by {
622 if !matches!(&group_by.expr.value, Value::Access(_)) {
623 return Err(AnalysisError::ExpectFieldLiteral(
624 group_by.expr.attrs.pos.line,
625 group_by.expr.attrs.pos.col,
626 ));
627 }
628
629 self.analyze_expr(&ctx, &group_by.expr, Type::Unspecified)?;
630
631 if let Some(expr) = &group_by.predicate {
632 self.analyze_expr(&ctx, expr, Type::Bool)?;
633 }
634 }
635
636 if let Some(order_by) = &query.order_by {
637 if !matches!(&order_by.expr.value, Value::Access(_)) {
638 return Err(AnalysisError::ExpectFieldLiteral(
639 order_by.expr.attrs.pos.line,
640 order_by.expr.attrs.pos.col,
641 ));
642 }
643 self.analyze_expr(&ctx, &order_by.expr, Type::Unspecified)?;
644 }
645
646 let project = self.analyze_projection(&mut ctx, &query.projection)?;
647 let scope = self.exit_scope();
648
649 Ok(Query {
650 attrs: query.attrs,
651 sources,
652 predicate: query.predicate,
653 group_by: query.group_by,
654 order_by: query.order_by,
655 limit: query.limit,
656 projection: query.projection,
657 distinct: query.distinct,
658 meta: Typed { project, scope },
659 })
660 }
661
662 fn analyze_source(&mut self, source: Source<Raw>) -> AnalysisResult<Source<Typed>> {
663 let kind = self.analyze_source_kind(source.kind)?;
664 let tpe = match &kind {
665 SourceKind::Name(_) | SourceKind::Subject(_) => self.options.event_type_info.clone(),
666 SourceKind::Subquery(query) => self.projection_type(query),
667 };
668
669 if self
670 .scope
671 .entries
672 .insert(source.binding.name.clone(), tpe)
673 .is_some()
674 {
675 return Err(AnalysisError::BindingAlreadyExists(
676 source.binding.pos.line,
677 source.binding.pos.col,
678 source.binding.name,
679 ));
680 }
681
682 Ok(Source {
683 binding: source.binding,
684 kind,
685 })
686 }
687
688 fn analyze_source_kind(&mut self, kind: SourceKind<Raw>) -> AnalysisResult<SourceKind<Typed>> {
689 match kind {
690 SourceKind::Name(n) => Ok(SourceKind::Name(n)),
691 SourceKind::Subject(s) => Ok(SourceKind::Subject(s)),
692 SourceKind::Subquery(query) => {
693 let query = self.analyze_query(*query)?;
694 Ok(SourceKind::Subquery(Box::new(query)))
695 }
696 }
697 }
698
699 fn analyze_projection(
700 &mut self,
701 ctx: &mut AnalysisContext,
702 expr: &Expr,
703 ) -> AnalysisResult<Type> {
704 match &expr.value {
705 Value::Record(record) => {
706 if record.is_empty() {
707 return Err(AnalysisError::EmptyRecord(
708 expr.attrs.pos.line,
709 expr.attrs.pos.col,
710 ));
711 }
712
713 ctx.allow_agg_func = true;
714 let tpe = self.analyze_expr(ctx, expr, Type::Unspecified)?;
715 self.check_projection_on_record(&mut CheckContext::default(), record.as_slice())?;
716 Ok(tpe)
717 }
718
719 Value::Id(id) => {
720 if let Some(tpe) = self.scope.entries.get(id).cloned() {
721 Ok(tpe)
722 } else {
723 Err(AnalysisError::VariableUndeclared(
724 expr.attrs.pos.line,
725 expr.attrs.pos.col,
726 id.clone(),
727 ))
728 }
729 }
730
731 Value::Access(access) => {
732 let mut current = &access.target.value;
733
734 loop {
735 match current {
736 Value::Id(name) => {
737 if !self.scope.entries.contains_key(name) {
738 return Err(AnalysisError::VariableUndeclared(
739 expr.attrs.pos.line,
740 expr.attrs.pos.col,
741 name.clone(),
742 ));
743 }
744
745 break;
746 }
747
748 Value::Access(next) => current = &next.target.value,
749 _ => unreachable!(),
750 }
751 }
752
753 self.analyze_expr(ctx, expr, Type::Unspecified)
754 }
755
756 _ => Err(AnalysisError::ExpectRecordOrSourcedProperty(
757 expr.attrs.pos.line,
758 expr.attrs.pos.col,
759 self.project_type(&expr.value),
760 )),
761 }
762 }
763
764 fn check_projection_on_record(
765 &mut self,
766 ctx: &mut CheckContext,
767 record: &[Field],
768 ) -> AnalysisResult<()> {
769 for field in record {
770 self.check_projection_on_field(ctx, field)?;
771 }
772
773 Ok(())
774 }
775
776 fn check_projection_on_field(
777 &mut self,
778 ctx: &mut CheckContext,
779 field: &Field,
780 ) -> AnalysisResult<()> {
781 self.check_projection_on_field_expr(ctx, &field.value)
782 }
783
784 fn check_projection_on_field_expr(
785 &mut self,
786 ctx: &mut CheckContext,
787 expr: &Expr,
788 ) -> AnalysisResult<()> {
789 match &expr.value {
790 Value::Number(_) | Value::String(_) | Value::Bool(_) => Ok(()),
791
792 Value::Id(id) => {
793 if self.scope.entries.contains_key(id) {
794 if ctx.use_agg_func {
795 return Err(AnalysisError::UnallowedAggFuncUsageWithSrcField(
796 expr.attrs.pos.line,
797 expr.attrs.pos.col,
798 ));
799 }
800
801 ctx.use_source_based = true;
802 }
803
804 Ok(())
805 }
806
807 Value::Array(exprs) => {
808 for expr in exprs {
809 self.check_projection_on_field_expr(ctx, expr)?;
810 }
811
812 Ok(())
813 }
814
815 Value::Record(fields) => {
816 for field in fields {
817 self.check_projection_on_field(ctx, field)?;
818 }
819
820 Ok(())
821 }
822
823 Value::Access(access) => self.check_projection_on_field_expr(ctx, &access.target),
824
825 Value::App(app) => {
826 if let Some(Type::App { aggregate, .. }) =
827 self.options.default_scope.entries.get(app.func.as_str())
828 {
829 ctx.use_agg_func |= *aggregate;
830
831 if ctx.use_agg_func && ctx.use_source_based {
832 return Err(AnalysisError::UnallowedAggFuncUsageWithSrcField(
833 expr.attrs.pos.line,
834 expr.attrs.pos.col,
835 ));
836 }
837
838 for arg in &app.args {
839 if *aggregate {
840 self.ensure_agg_param_is_source_bound(arg)?;
841 }
842
843 self.invalidate_agg_func_usage(arg)?;
844 }
845 }
846
847 Ok(())
848 }
849
850 Value::Binary(binary) => {
851 self.check_projection_on_field_expr(ctx, &binary.lhs)?;
852 self.check_projection_on_field_expr(ctx, &binary.rhs)
853 }
854
855 Value::Unary(unary) => self.check_projection_on_field_expr(ctx, &unary.expr),
856 Value::Group(expr) => self.check_projection_on_field_expr(ctx, expr),
857 }
858 }
859
860 fn ensure_agg_param_is_source_bound(&mut self, expr: &Expr) -> AnalysisResult<()> {
861 match &expr.value {
862 Value::Id(id) if !self.options.default_scope.entries.contains_key(id) => Ok(()),
863 Value::Access(access) => self.ensure_agg_param_is_source_bound(&access.target),
864 Value::Binary(binary) => self.ensure_agg_binary_op_is_source_bound(&expr.attrs, binary),
865 Value::Unary(unary) => self.ensure_agg_param_is_source_bound(&unary.expr),
866
867 _ => Err(AnalysisError::ExpectSourceBoundProperty(
868 expr.attrs.pos.line,
869 expr.attrs.pos.col,
870 )),
871 }
872 }
873
874 fn ensure_agg_binary_op_is_source_bound(
875 &mut self,
876 attrs: &Attrs,
877 binary: &Binary,
878 ) -> AnalysisResult<()> {
879 if !self.ensure_agg_binary_op_branch_is_source_bound(&binary.lhs)
880 && !self.ensure_agg_binary_op_branch_is_source_bound(&binary.rhs)
881 {
882 return Err(AnalysisError::ExpectSourceBoundProperty(
883 attrs.pos.line,
884 attrs.pos.col,
885 ));
886 }
887
888 Ok(())
889 }
890
891 fn ensure_agg_binary_op_branch_is_source_bound(&mut self, expr: &Expr) -> bool {
892 match &expr.value {
893 Value::Id(id) => !self.options.default_scope.entries.contains_key(id),
894 Value::Array(exprs) => {
895 if exprs.is_empty() {
896 return false;
897 }
898
899 exprs
900 .iter()
901 .all(|expr| self.ensure_agg_binary_op_branch_is_source_bound(expr))
902 }
903 Value::Record(fields) => {
904 if fields.is_empty() {
905 return false;
906 }
907
908 fields
909 .iter()
910 .all(|field| self.ensure_agg_binary_op_branch_is_source_bound(&field.value))
911 }
912 Value::Access(access) => {
913 self.ensure_agg_binary_op_branch_is_source_bound(&access.target)
914 }
915
916 Value::Binary(binary) => self
917 .ensure_agg_binary_op_is_source_bound(&expr.attrs, binary)
918 .is_ok(),
919 Value::Unary(unary) => self.ensure_agg_binary_op_branch_is_source_bound(&unary.expr),
920 Value::Group(expr) => self.ensure_agg_binary_op_branch_is_source_bound(expr),
921
922 Value::Number(_) | Value::String(_) | Value::Bool(_) | Value::App(_) => false,
923 }
924 }
925
926 fn invalidate_agg_func_usage(&mut self, expr: &Expr) -> AnalysisResult<()> {
927 match &expr.value {
928 Value::Number(_)
929 | Value::String(_)
930 | Value::Bool(_)
931 | Value::Id(_)
932 | Value::Access(_) => Ok(()),
933
934 Value::Array(exprs) => {
935 for expr in exprs {
936 self.invalidate_agg_func_usage(expr)?;
937 }
938
939 Ok(())
940 }
941
942 Value::Record(fields) => {
943 for field in fields {
944 self.invalidate_agg_func_usage(&field.value)?;
945 }
946
947 Ok(())
948 }
949
950 Value::App(app) => {
951 if let Some(Type::App { aggregate, .. }) =
952 self.options.default_scope.entries.get(&app.func)
953 && *aggregate
954 {
955 return Err(AnalysisError::WrongAggFunUsage(
956 expr.attrs.pos.line,
957 expr.attrs.pos.col,
958 app.func.clone(),
959 ));
960 }
961
962 for arg in &app.args {
963 self.invalidate_agg_func_usage(arg)?;
964 }
965
966 Ok(())
967 }
968
969 Value::Binary(binary) => {
970 self.invalidate_agg_func_usage(&binary.lhs)?;
971 self.invalidate_agg_func_usage(&binary.rhs)
972 }
973
974 Value::Unary(unary) => self.invalidate_agg_func_usage(&unary.expr),
975 Value::Group(expr) => self.invalidate_agg_func_usage(expr),
976 }
977 }
978
979 pub fn analyze_expr(
1009 &mut self,
1010 ctx: &AnalysisContext,
1011 expr: &Expr,
1012 mut expect: Type,
1013 ) -> AnalysisResult<Type> {
1014 match &expr.value {
1015 Value::Number(_) => expect.check(&expr.attrs, Type::Number),
1016 Value::String(_) => expect.check(&expr.attrs, Type::String),
1017 Value::Bool(_) => expect.check(&expr.attrs, Type::Bool),
1018
1019 Value::Id(id) => {
1020 if let Some(tpe) = self.options.default_scope.entries.get(id) {
1021 expect.check(&expr.attrs, tpe.clone())
1022 } else if let Some(tpe) = self.scope.entries.get_mut(id.as_str()) {
1023 let tmp = mem::take(tpe);
1024 *tpe = tmp.check(&expr.attrs, expect)?;
1025
1026 Ok(tpe.clone())
1027 } else {
1028 Err(AnalysisError::VariableUndeclared(
1029 expr.attrs.pos.line,
1030 expr.attrs.pos.col,
1031 id.to_owned(),
1032 ))
1033 }
1034 }
1035
1036 Value::Array(exprs) => {
1037 if matches!(expect, Type::Unspecified) {
1038 for expr in exprs {
1039 expect = self.analyze_expr(ctx, expr, expect)?;
1040 }
1041
1042 return Ok(Type::Array(Box::new(expect)));
1043 }
1044
1045 match expect {
1046 Type::Array(mut expect) => {
1047 for expr in exprs {
1048 *expect = self.analyze_expr(ctx, expr, expect.as_ref().clone())?;
1049 }
1050
1051 Ok(Type::Array(expect))
1052 }
1053
1054 expect => Err(AnalysisError::TypeMismatch(
1055 expr.attrs.pos.line,
1056 expr.attrs.pos.col,
1057 expect,
1058 self.project_type(&expr.value),
1059 )),
1060 }
1061 }
1062
1063 Value::Record(fields) => {
1064 if matches!(expect, Type::Unspecified) {
1065 let mut record = BTreeMap::new();
1066
1067 for field in fields {
1068 record.insert(
1069 field.name.clone(),
1070 self.analyze_expr(ctx, &field.value, Type::Unspecified)?,
1071 );
1072 }
1073
1074 return Ok(Type::Record(record));
1075 }
1076
1077 match expect {
1078 Type::Record(mut types) if fields.len() == types.len() => {
1079 for field in fields {
1080 if let Some(tpe) = types.remove(field.name.as_str()) {
1081 types.insert(
1082 field.name.clone(),
1083 self.analyze_expr(ctx, &field.value, tpe)?,
1084 );
1085 } else {
1086 return Err(AnalysisError::FieldUndeclared(
1087 expr.attrs.pos.line,
1088 expr.attrs.pos.col,
1089 field.name.clone(),
1090 ));
1091 }
1092 }
1093
1094 Ok(Type::Record(types))
1095 }
1096
1097 expect => Err(AnalysisError::TypeMismatch(
1098 expr.attrs.pos.line,
1099 expr.attrs.pos.col,
1100 expect,
1101 self.project_type(&expr.value),
1102 )),
1103 }
1104 }
1105
1106 this @ Value::Access(_) => Ok(self.analyze_access(&expr.attrs, this, expect)?),
1107
1108 Value::App(app) => {
1109 if let Some(tpe) = self.options.default_scope.entries.get(app.func.as_str())
1110 && let Type::App {
1111 args,
1112 result,
1113 aggregate,
1114 } = tpe
1115 {
1116 if !args.match_arg_count(app.args.len()) {
1117 return Err(AnalysisError::FunWrongArgumentCount(
1118 expr.attrs.pos.line,
1119 expr.attrs.pos.col,
1120 app.func.clone(),
1121 ));
1122 }
1123
1124 if *aggregate && !ctx.allow_agg_func {
1125 return Err(AnalysisError::WrongAggFunUsage(
1126 expr.attrs.pos.line,
1127 expr.attrs.pos.col,
1128 app.func.clone(),
1129 ));
1130 }
1131
1132 for (arg, tpe) in app.args.iter().zip(args.values.iter().cloned()) {
1133 self.analyze_expr(ctx, arg, tpe)?;
1134 }
1135
1136 if matches!(expect, Type::Unspecified) {
1137 Ok(result.as_ref().clone())
1138 } else {
1139 expect.check(&expr.attrs, result.as_ref().clone())
1140 }
1141 } else {
1142 Err(AnalysisError::FuncUndeclared(
1143 expr.attrs.pos.line,
1144 expr.attrs.pos.col,
1145 app.func.clone(),
1146 ))
1147 }
1148 }
1149
1150 Value::Binary(binary) => match binary.operator {
1151 Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => {
1152 self.analyze_expr(ctx, &binary.lhs, Type::Number)?;
1153 self.analyze_expr(ctx, &binary.rhs, Type::Number)?;
1154 expect.check(&expr.attrs, Type::Number)
1155 }
1156
1157 Operator::Eq
1158 | Operator::Neq
1159 | Operator::Lt
1160 | Operator::Lte
1161 | Operator::Gt
1162 | Operator::Gte => {
1163 let lhs_expect = self.analyze_expr(ctx, &binary.lhs, Type::Unspecified)?;
1164 let rhs_expect = self.analyze_expr(ctx, &binary.rhs, lhs_expect.clone())?;
1165
1166 if matches!(lhs_expect, Type::Unspecified)
1169 && !matches!(rhs_expect, Type::Unspecified)
1170 {
1171 self.analyze_expr(ctx, &binary.lhs, rhs_expect)?;
1172 }
1173
1174 expect.check(&expr.attrs, Type::Bool)
1175 }
1176
1177 Operator::Contains => {
1178 let lhs_expect = self.analyze_expr(
1179 ctx,
1180 &binary.lhs,
1181 Type::Array(Box::new(Type::Unspecified)),
1182 )?;
1183
1184 let lhs_assumption = match lhs_expect {
1185 Type::Array(inner) => *inner,
1186 other => {
1187 return Err(AnalysisError::ExpectArray(
1188 expr.attrs.pos.line,
1189 expr.attrs.pos.col,
1190 other,
1191 ));
1192 }
1193 };
1194
1195 let rhs_expect = self.analyze_expr(ctx, &binary.rhs, lhs_assumption.clone())?;
1196
1197 if matches!(lhs_assumption, Type::Unspecified)
1200 && !matches!(rhs_expect, Type::Unspecified)
1201 {
1202 self.analyze_expr(ctx, &binary.lhs, Type::Array(Box::new(rhs_expect)))?;
1203 }
1204
1205 expect.check(&expr.attrs, Type::Bool)
1206 }
1207
1208 Operator::And | Operator::Or | Operator::Xor => {
1209 self.analyze_expr(ctx, &binary.lhs, Type::Bool)?;
1210 self.analyze_expr(ctx, &binary.rhs, Type::Bool)?;
1211
1212 expect.check(&expr.attrs, Type::Bool)
1213 }
1214
1215 Operator::As => {
1216 if let Value::Id(name) = &binary.rhs.value {
1217 if let Some(tpe) = name_to_type(self.options, name) {
1218 return Ok(tpe);
1220 } else {
1221 return Err(AnalysisError::UnsupportedCustomType(
1222 expr.attrs.pos.line,
1223 expr.attrs.pos.col,
1224 name.clone(),
1225 ));
1226 }
1227 }
1228
1229 unreachable!(
1230 "we already made sure during parsing that we can only have an ID symbol at this point"
1231 )
1232 }
1233
1234 Operator::Not => unreachable!(),
1235 },
1236
1237 Value::Unary(unary) => match unary.operator {
1238 Operator::Add | Operator::Sub => {
1239 self.analyze_expr(ctx, &unary.expr, Type::Number)?;
1240 expect.check(&expr.attrs, Type::Number)
1241 }
1242
1243 Operator::Not => {
1244 self.analyze_expr(ctx, &unary.expr, Type::Bool)?;
1245 expect.check(&expr.attrs, Type::Bool)
1246 }
1247
1248 _ => unreachable!(),
1249 },
1250
1251 Value::Group(expr) => Ok(self.analyze_expr(ctx, expr.as_ref(), expect)?),
1252 }
1253 }
1254
1255 fn analyze_access(
1256 &mut self,
1257 attrs: &Attrs,
1258 access: &Value,
1259 expect: Type,
1260 ) -> AnalysisResult<Type> {
1261 struct State<A, B> {
1262 depth: u8,
1263 dynamic: bool,
1265 definition: Def<A, B>,
1266 }
1267
1268 impl<A, B> State<A, B> {
1269 fn new(definition: Def<A, B>) -> Self {
1270 Self {
1271 depth: 0,
1272 dynamic: false,
1273 definition,
1274 }
1275 }
1276 }
1277
1278 enum Def<A, B> {
1279 User(A),
1280 System(B),
1281 }
1282
1283 fn go<'a>(
1284 scope: &'a mut Scope,
1285 sys: &'a AnalysisOptions,
1286 attrs: &'a Attrs,
1287 value: &'a Value,
1288 ) -> AnalysisResult<State<&'a mut Type, &'a Type>> {
1289 match value {
1290 Value::Id(id) => {
1291 if let Some(tpe) = sys.default_scope.entries.get(id.as_str()) {
1292 Ok(State::new(Def::System(tpe)))
1293 } else if let Some(tpe) = scope.entries.get_mut(id.as_str()) {
1294 Ok(State::new(Def::User(tpe)))
1295 } else {
1296 Err(AnalysisError::VariableUndeclared(
1297 attrs.pos.line,
1298 attrs.pos.col,
1299 id.clone(),
1300 ))
1301 }
1302 }
1303 Value::Access(access) => {
1304 let mut state = go(scope, sys, &access.target.attrs, &access.target.value)?;
1305
1306 let is_data_field = state.depth == 0 && access.field == "data";
1308
1309 if !state.dynamic && is_data_field {
1313 state.dynamic = true;
1314 }
1315
1316 match state.definition {
1317 Def::User(tpe) => {
1318 if matches!(tpe, Type::Unspecified) && state.dynamic {
1319 *tpe = Type::Record(BTreeMap::from([(
1320 access.field.clone(),
1321 Type::Unspecified,
1322 )]));
1323 return Ok(State {
1324 depth: state.depth + 1,
1325 definition: Def::User(
1326 tpe.as_record_or_panic_mut()
1327 .get_mut(access.field.as_str())
1328 .unwrap(),
1329 ),
1330 ..state
1331 });
1332 }
1333
1334 if let Type::Record(fields) = tpe {
1335 match fields.entry(access.field.clone()) {
1336 Entry::Vacant(entry) => {
1337 if state.dynamic || is_data_field {
1338 return Ok(State {
1339 depth: state.depth + 1,
1340 definition: Def::User(
1341 entry.insert(Type::Unspecified),
1342 ),
1343 ..state
1344 });
1345 }
1346
1347 return Err(AnalysisError::FieldUndeclared(
1348 attrs.pos.line,
1349 attrs.pos.col,
1350 access.field.clone(),
1351 ));
1352 }
1353
1354 Entry::Occupied(entry) => {
1355 return Ok(State {
1356 depth: state.depth + 1,
1357 definition: Def::User(entry.into_mut()),
1358 ..state
1359 });
1360 }
1361 }
1362 }
1363
1364 Err(AnalysisError::ExpectRecord(
1365 attrs.pos.line,
1366 attrs.pos.col,
1367 tpe.clone(),
1368 ))
1369 }
1370
1371 Def::System(tpe) => {
1372 if matches!(tpe, Type::Unspecified) && state.dynamic {
1373 return Ok(State {
1374 depth: state.depth + 1,
1375 definition: Def::System(&Type::Unspecified),
1376 ..state
1377 });
1378 }
1379
1380 if let Type::Record(fields) = tpe {
1381 if let Some(field) = fields.get(access.field.as_str()) {
1382 return Ok(State {
1383 depth: state.depth + 1,
1384 definition: Def::System(field),
1385 ..state
1386 });
1387 }
1388
1389 return Err(AnalysisError::FieldUndeclared(
1390 attrs.pos.line,
1391 attrs.pos.col,
1392 access.field.clone(),
1393 ));
1394 }
1395
1396 Err(AnalysisError::ExpectRecord(
1397 attrs.pos.line,
1398 attrs.pos.col,
1399 tpe.clone(),
1400 ))
1401 }
1402 }
1403 }
1404 Value::Number(_)
1405 | Value::String(_)
1406 | Value::Bool(_)
1407 | Value::Array(_)
1408 | Value::Record(_)
1409 | Value::App(_)
1410 | Value::Binary(_)
1411 | Value::Unary(_)
1412 | Value::Group(_) => unreachable!(),
1413 }
1414 }
1415
1416 let state = go(&mut self.scope, self.options, attrs, access)?;
1417
1418 match state.definition {
1419 Def::User(tpe) => {
1420 let tmp = mem::take(tpe);
1421 *tpe = tmp.check(attrs, expect)?;
1422
1423 Ok(tpe.clone())
1424 }
1425
1426 Def::System(tpe) => tpe.clone().check(attrs, expect),
1427 }
1428 }
1429
1430 fn projection_type(&self, query: &Query<Typed>) -> Type {
1431 self.project_type(&query.projection.value)
1432 }
1433
1434 fn project_type(&self, value: &Value) -> Type {
1435 match value {
1436 Value::Number(_) => Type::Number,
1437 Value::String(_) => Type::String,
1438 Value::Bool(_) => Type::Bool,
1439 Value::Id(id) => {
1440 if let Some(tpe) = self.options.default_scope.entries.get(id) {
1441 tpe.clone()
1442 } else if let Some(tpe) = self.scope.entries.get(id) {
1443 tpe.clone()
1444 } else {
1445 Type::Unspecified
1446 }
1447 }
1448 Value::Array(exprs) => {
1449 let mut project = Type::Unspecified;
1450
1451 for expr in exprs {
1452 let tmp = self.project_type(&expr.value);
1453
1454 if !matches!(tmp, Type::Unspecified) {
1455 project = tmp;
1456 break;
1457 }
1458 }
1459
1460 Type::Array(Box::new(project))
1461 }
1462 Value::Record(fields) => Type::Record(
1463 fields
1464 .iter()
1465 .map(|field| (field.name.clone(), self.project_type(&field.value.value)))
1466 .collect(),
1467 ),
1468 Value::Access(access) => {
1469 let tpe = self.project_type(&access.target.value);
1470 if let Type::Record(fields) = tpe {
1471 fields
1472 .get(access.field.as_str())
1473 .cloned()
1474 .unwrap_or_default()
1475 } else {
1476 Type::Unspecified
1477 }
1478 }
1479 Value::App(app) => self
1480 .options
1481 .default_scope
1482 .entries
1483 .get(app.func.as_str())
1484 .cloned()
1485 .unwrap_or_default(),
1486 Value::Binary(binary) => match binary.operator {
1487 Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => Type::Number,
1488 Operator::As => {
1489 if let Value::Id(n) = &binary.rhs.as_ref().value
1490 && let Some(tpe) = name_to_type(self.options, n.as_str())
1491 {
1492 tpe
1493 } else {
1494 Type::Unspecified
1495 }
1496 }
1497 Operator::Eq
1498 | Operator::Neq
1499 | Operator::Lt
1500 | Operator::Lte
1501 | Operator::Gt
1502 | Operator::Gte
1503 | Operator::And
1504 | Operator::Or
1505 | Operator::Xor
1506 | Operator::Not
1507 | Operator::Contains => Type::Bool,
1508 },
1509 Value::Unary(unary) => match unary.operator {
1510 Operator::Add | Operator::Sub => Type::Number,
1511 Operator::Mul
1512 | Operator::Div
1513 | Operator::Eq
1514 | Operator::Neq
1515 | Operator::Lt
1516 | Operator::Lte
1517 | Operator::Gt
1518 | Operator::Gte
1519 | Operator::And
1520 | Operator::Or
1521 | Operator::Xor
1522 | Operator::Not
1523 | Operator::Contains
1524 | Operator::As => unreachable!(),
1525 },
1526 Value::Group(expr) => self.project_type(&expr.value),
1527 }
1528 }
1529}
1530
1531pub fn name_to_type(opts: &AnalysisOptions, name: &str) -> Option<Type> {
1560 if name.eq_ignore_ascii_case("string") {
1561 Some(Type::String)
1562 } else if name.eq_ignore_ascii_case("int") || name.eq_ignore_ascii_case("float64") {
1563 Some(Type::Number)
1564 } else if name.eq_ignore_ascii_case("boolean") {
1565 Some(Type::Bool)
1566 } else if name.eq_ignore_ascii_case("date") {
1567 Some(Type::Date)
1568 } else if name.eq_ignore_ascii_case("time") {
1569 Some(Type::Time)
1570 } else if name.eq_ignore_ascii_case("datetime") {
1571 Some(Type::DateTime)
1572 } else if opts.custom_types.contains(&Ascii::new(name.to_owned())) {
1573 Some(Type::Custom(name.to_owned()))
1575 } else {
1576 None
1577 }
1578}