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 if matches!(&tpe, Type::Record(f) if !f.is_empty()) {
722 Ok(tpe)
723 } else {
724 Err(AnalysisError::ExpectRecord(
725 expr.attrs.pos.line,
726 expr.attrs.pos.col,
727 tpe,
728 ))
729 }
730 } else {
731 Err(AnalysisError::VariableUndeclared(
732 expr.attrs.pos.line,
733 expr.attrs.pos.col,
734 id.clone(),
735 ))
736 }
737 }
738
739 _ => Err(AnalysisError::ExpectRecord(
740 expr.attrs.pos.line,
741 expr.attrs.pos.col,
742 self.project_type(&expr.value),
743 )),
744 }
745 }
746
747 fn check_projection_on_record(
748 &mut self,
749 ctx: &mut CheckContext,
750 record: &[Field],
751 ) -> AnalysisResult<()> {
752 for field in record {
753 self.check_projection_on_field(ctx, field)?;
754 }
755
756 Ok(())
757 }
758
759 fn check_projection_on_field(
760 &mut self,
761 ctx: &mut CheckContext,
762 field: &Field,
763 ) -> AnalysisResult<()> {
764 self.check_projection_on_field_expr(ctx, &field.value)
765 }
766
767 fn check_projection_on_field_expr(
768 &mut self,
769 ctx: &mut CheckContext,
770 expr: &Expr,
771 ) -> AnalysisResult<()> {
772 match &expr.value {
773 Value::Number(_) | Value::String(_) | Value::Bool(_) => Ok(()),
774
775 Value::Id(id) => {
776 if self.scope.entries.contains_key(id) {
777 if ctx.use_agg_func {
778 return Err(AnalysisError::UnallowedAggFuncUsageWithSrcField(
779 expr.attrs.pos.line,
780 expr.attrs.pos.col,
781 ));
782 }
783
784 ctx.use_source_based = true;
785 }
786
787 Ok(())
788 }
789
790 Value::Array(exprs) => {
791 for expr in exprs {
792 self.check_projection_on_field_expr(ctx, expr)?;
793 }
794
795 Ok(())
796 }
797
798 Value::Record(fields) => {
799 for field in fields {
800 self.check_projection_on_field(ctx, field)?;
801 }
802
803 Ok(())
804 }
805
806 Value::Access(access) => self.check_projection_on_field_expr(ctx, &access.target),
807
808 Value::App(app) => {
809 if let Some(Type::App { aggregate, .. }) =
810 self.options.default_scope.entries.get(app.func.as_str())
811 {
812 ctx.use_agg_func |= *aggregate;
813
814 if ctx.use_agg_func && ctx.use_source_based {
815 return Err(AnalysisError::UnallowedAggFuncUsageWithSrcField(
816 expr.attrs.pos.line,
817 expr.attrs.pos.col,
818 ));
819 }
820
821 for arg in &app.args {
822 if *aggregate {
823 self.ensure_agg_param_is_source_bound(arg)?;
824 }
825
826 self.invalidate_agg_func_usage(arg)?;
827 }
828 }
829
830 Ok(())
831 }
832
833 Value::Binary(binary) => {
834 self.check_projection_on_field_expr(ctx, &binary.lhs)?;
835 self.check_projection_on_field_expr(ctx, &binary.rhs)
836 }
837
838 Value::Unary(unary) => self.check_projection_on_field_expr(ctx, &unary.expr),
839 Value::Group(expr) => self.check_projection_on_field_expr(ctx, expr),
840 }
841 }
842
843 fn ensure_agg_param_is_source_bound(&mut self, expr: &Expr) -> AnalysisResult<()> {
844 match &expr.value {
845 Value::Id(id) if !self.options.default_scope.entries.contains_key(id) => Ok(()),
846 Value::Access(access) => self.ensure_agg_param_is_source_bound(&access.target),
847 Value::Binary(binary) => self.ensure_agg_binary_op_is_source_bound(&expr.attrs, binary),
848 Value::Unary(unary) => self.ensure_agg_param_is_source_bound(&unary.expr),
849
850 _ => Err(AnalysisError::ExpectSourceBoundProperty(
851 expr.attrs.pos.line,
852 expr.attrs.pos.col,
853 )),
854 }
855 }
856
857 fn ensure_agg_binary_op_is_source_bound(
858 &mut self,
859 attrs: &Attrs,
860 binary: &Binary,
861 ) -> AnalysisResult<()> {
862 if !self.ensure_agg_binary_op_branch_is_source_bound(&binary.lhs)
863 && !self.ensure_agg_binary_op_branch_is_source_bound(&binary.rhs)
864 {
865 return Err(AnalysisError::ExpectSourceBoundProperty(
866 attrs.pos.line,
867 attrs.pos.col,
868 ));
869 }
870
871 Ok(())
872 }
873
874 fn ensure_agg_binary_op_branch_is_source_bound(&mut self, expr: &Expr) -> bool {
875 match &expr.value {
876 Value::Id(id) => !self.options.default_scope.entries.contains_key(id),
877 Value::Array(exprs) => {
878 if exprs.is_empty() {
879 return false;
880 }
881
882 exprs
883 .iter()
884 .all(|expr| self.ensure_agg_binary_op_branch_is_source_bound(expr))
885 }
886 Value::Record(fields) => {
887 if fields.is_empty() {
888 return false;
889 }
890
891 fields
892 .iter()
893 .all(|field| self.ensure_agg_binary_op_branch_is_source_bound(&field.value))
894 }
895 Value::Access(access) => {
896 self.ensure_agg_binary_op_branch_is_source_bound(&access.target)
897 }
898
899 Value::Binary(binary) => self
900 .ensure_agg_binary_op_is_source_bound(&expr.attrs, binary)
901 .is_ok(),
902 Value::Unary(unary) => self.ensure_agg_binary_op_branch_is_source_bound(&unary.expr),
903 Value::Group(expr) => self.ensure_agg_binary_op_branch_is_source_bound(expr),
904
905 Value::Number(_) | Value::String(_) | Value::Bool(_) | Value::App(_) => false,
906 }
907 }
908
909 fn invalidate_agg_func_usage(&mut self, expr: &Expr) -> AnalysisResult<()> {
910 match &expr.value {
911 Value::Number(_)
912 | Value::String(_)
913 | Value::Bool(_)
914 | Value::Id(_)
915 | Value::Access(_) => Ok(()),
916
917 Value::Array(exprs) => {
918 for expr in exprs {
919 self.invalidate_agg_func_usage(expr)?;
920 }
921
922 Ok(())
923 }
924
925 Value::Record(fields) => {
926 for field in fields {
927 self.invalidate_agg_func_usage(&field.value)?;
928 }
929
930 Ok(())
931 }
932
933 Value::App(app) => {
934 if let Some(Type::App { aggregate, .. }) =
935 self.options.default_scope.entries.get(&app.func)
936 && *aggregate
937 {
938 return Err(AnalysisError::WrongAggFunUsage(
939 expr.attrs.pos.line,
940 expr.attrs.pos.col,
941 app.func.clone(),
942 ));
943 }
944
945 for arg in &app.args {
946 self.invalidate_agg_func_usage(arg)?;
947 }
948
949 Ok(())
950 }
951
952 Value::Binary(binary) => {
953 self.invalidate_agg_func_usage(&binary.lhs)?;
954 self.invalidate_agg_func_usage(&binary.rhs)
955 }
956
957 Value::Unary(unary) => self.invalidate_agg_func_usage(&unary.expr),
958 Value::Group(expr) => self.invalidate_agg_func_usage(expr),
959 }
960 }
961
962 pub fn analyze_expr(
992 &mut self,
993 ctx: &AnalysisContext,
994 expr: &Expr,
995 mut expect: Type,
996 ) -> AnalysisResult<Type> {
997 match &expr.value {
998 Value::Number(_) => expect.check(&expr.attrs, Type::Number),
999 Value::String(_) => expect.check(&expr.attrs, Type::String),
1000 Value::Bool(_) => expect.check(&expr.attrs, Type::Bool),
1001
1002 Value::Id(id) => {
1003 if let Some(tpe) = self.options.default_scope.entries.get(id) {
1004 expect.check(&expr.attrs, tpe.clone())
1005 } else if let Some(tpe) = self.scope.entries.get_mut(id.as_str()) {
1006 let tmp = mem::take(tpe);
1007 *tpe = tmp.check(&expr.attrs, expect)?;
1008
1009 Ok(tpe.clone())
1010 } else {
1011 Err(AnalysisError::VariableUndeclared(
1012 expr.attrs.pos.line,
1013 expr.attrs.pos.col,
1014 id.to_owned(),
1015 ))
1016 }
1017 }
1018
1019 Value::Array(exprs) => {
1020 if matches!(expect, Type::Unspecified) {
1021 for expr in exprs {
1022 expect = self.analyze_expr(ctx, expr, expect)?;
1023 }
1024
1025 return Ok(Type::Array(Box::new(expect)));
1026 }
1027
1028 match expect {
1029 Type::Array(mut expect) => {
1030 for expr in exprs {
1031 *expect = self.analyze_expr(ctx, expr, expect.as_ref().clone())?;
1032 }
1033
1034 Ok(Type::Array(expect))
1035 }
1036
1037 expect => Err(AnalysisError::TypeMismatch(
1038 expr.attrs.pos.line,
1039 expr.attrs.pos.col,
1040 expect,
1041 self.project_type(&expr.value),
1042 )),
1043 }
1044 }
1045
1046 Value::Record(fields) => {
1047 if matches!(expect, Type::Unspecified) {
1048 let mut record = BTreeMap::new();
1049
1050 for field in fields {
1051 record.insert(
1052 field.name.clone(),
1053 self.analyze_expr(ctx, &field.value, Type::Unspecified)?,
1054 );
1055 }
1056
1057 return Ok(Type::Record(record));
1058 }
1059
1060 match expect {
1061 Type::Record(mut types) if fields.len() == types.len() => {
1062 for field in fields {
1063 if let Some(tpe) = types.remove(field.name.as_str()) {
1064 types.insert(
1065 field.name.clone(),
1066 self.analyze_expr(ctx, &field.value, tpe)?,
1067 );
1068 } else {
1069 return Err(AnalysisError::FieldUndeclared(
1070 expr.attrs.pos.line,
1071 expr.attrs.pos.col,
1072 field.name.clone(),
1073 ));
1074 }
1075 }
1076
1077 Ok(Type::Record(types))
1078 }
1079
1080 expect => Err(AnalysisError::TypeMismatch(
1081 expr.attrs.pos.line,
1082 expr.attrs.pos.col,
1083 expect,
1084 self.project_type(&expr.value),
1085 )),
1086 }
1087 }
1088
1089 this @ Value::Access(_) => Ok(self.analyze_access(&expr.attrs, this, expect)?),
1090
1091 Value::App(app) => {
1092 if let Some(tpe) = self.options.default_scope.entries.get(app.func.as_str())
1093 && let Type::App {
1094 args,
1095 result,
1096 aggregate,
1097 } = tpe
1098 {
1099 if !args.match_arg_count(app.args.len()) {
1100 return Err(AnalysisError::FunWrongArgumentCount(
1101 expr.attrs.pos.line,
1102 expr.attrs.pos.col,
1103 app.func.clone(),
1104 ));
1105 }
1106
1107 if *aggregate && !ctx.allow_agg_func {
1108 return Err(AnalysisError::WrongAggFunUsage(
1109 expr.attrs.pos.line,
1110 expr.attrs.pos.col,
1111 app.func.clone(),
1112 ));
1113 }
1114
1115 for (arg, tpe) in app.args.iter().zip(args.values.iter().cloned()) {
1116 self.analyze_expr(ctx, arg, tpe)?;
1117 }
1118
1119 if matches!(expect, Type::Unspecified) {
1120 Ok(result.as_ref().clone())
1121 } else {
1122 expect.check(&expr.attrs, result.as_ref().clone())
1123 }
1124 } else {
1125 Err(AnalysisError::FuncUndeclared(
1126 expr.attrs.pos.line,
1127 expr.attrs.pos.col,
1128 app.func.clone(),
1129 ))
1130 }
1131 }
1132
1133 Value::Binary(binary) => match binary.operator {
1134 Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => {
1135 self.analyze_expr(ctx, &binary.lhs, Type::Number)?;
1136 self.analyze_expr(ctx, &binary.rhs, Type::Number)?;
1137 expect.check(&expr.attrs, Type::Number)
1138 }
1139
1140 Operator::Eq
1141 | Operator::Neq
1142 | Operator::Lt
1143 | Operator::Lte
1144 | Operator::Gt
1145 | Operator::Gte => {
1146 let lhs_expect = self.analyze_expr(ctx, &binary.lhs, Type::Unspecified)?;
1147 let rhs_expect = self.analyze_expr(ctx, &binary.rhs, lhs_expect.clone())?;
1148
1149 if matches!(lhs_expect, Type::Unspecified)
1152 && !matches!(rhs_expect, Type::Unspecified)
1153 {
1154 self.analyze_expr(ctx, &binary.lhs, rhs_expect)?;
1155 }
1156
1157 expect.check(&expr.attrs, Type::Bool)
1158 }
1159
1160 Operator::Contains => {
1161 let lhs_expect = self.analyze_expr(
1162 ctx,
1163 &binary.lhs,
1164 Type::Array(Box::new(Type::Unspecified)),
1165 )?;
1166
1167 let lhs_assumption = match lhs_expect {
1168 Type::Array(inner) => *inner,
1169 other => {
1170 return Err(AnalysisError::ExpectArray(
1171 expr.attrs.pos.line,
1172 expr.attrs.pos.col,
1173 other,
1174 ));
1175 }
1176 };
1177
1178 let rhs_expect = self.analyze_expr(ctx, &binary.rhs, lhs_assumption.clone())?;
1179
1180 if matches!(lhs_assumption, Type::Unspecified)
1183 && !matches!(rhs_expect, Type::Unspecified)
1184 {
1185 self.analyze_expr(ctx, &binary.lhs, Type::Array(Box::new(rhs_expect)))?;
1186 }
1187
1188 expect.check(&expr.attrs, Type::Bool)
1189 }
1190
1191 Operator::And | Operator::Or | Operator::Xor => {
1192 self.analyze_expr(ctx, &binary.lhs, Type::Bool)?;
1193 self.analyze_expr(ctx, &binary.rhs, Type::Bool)?;
1194
1195 expect.check(&expr.attrs, Type::Bool)
1196 }
1197
1198 Operator::As => {
1199 if let Value::Id(name) = &binary.rhs.value {
1200 if let Some(tpe) = name_to_type(self.options, name) {
1201 return Ok(tpe);
1203 } else {
1204 return Err(AnalysisError::UnsupportedCustomType(
1205 expr.attrs.pos.line,
1206 expr.attrs.pos.col,
1207 name.clone(),
1208 ));
1209 }
1210 }
1211
1212 unreachable!(
1213 "we already made sure during parsing that we can only have an ID symbol at this point"
1214 )
1215 }
1216
1217 Operator::Not => unreachable!(),
1218 },
1219
1220 Value::Unary(unary) => match unary.operator {
1221 Operator::Add | Operator::Sub => {
1222 self.analyze_expr(ctx, &unary.expr, Type::Number)?;
1223 expect.check(&expr.attrs, Type::Number)
1224 }
1225
1226 Operator::Not => {
1227 self.analyze_expr(ctx, &unary.expr, Type::Bool)?;
1228 expect.check(&expr.attrs, Type::Bool)
1229 }
1230
1231 _ => unreachable!(),
1232 },
1233
1234 Value::Group(expr) => Ok(self.analyze_expr(ctx, expr.as_ref(), expect)?),
1235 }
1236 }
1237
1238 fn analyze_access(
1239 &mut self,
1240 attrs: &Attrs,
1241 access: &Value,
1242 expect: Type,
1243 ) -> AnalysisResult<Type> {
1244 struct State<A, B> {
1245 depth: u8,
1246 dynamic: bool,
1248 definition: Def<A, B>,
1249 }
1250
1251 impl<A, B> State<A, B> {
1252 fn new(definition: Def<A, B>) -> Self {
1253 Self {
1254 depth: 0,
1255 dynamic: false,
1256 definition,
1257 }
1258 }
1259 }
1260
1261 enum Def<A, B> {
1262 User(A),
1263 System(B),
1264 }
1265
1266 fn go<'a>(
1267 scope: &'a mut Scope,
1268 sys: &'a AnalysisOptions,
1269 attrs: &'a Attrs,
1270 value: &'a Value,
1271 ) -> AnalysisResult<State<&'a mut Type, &'a Type>> {
1272 match value {
1273 Value::Id(id) => {
1274 if let Some(tpe) = sys.default_scope.entries.get(id.as_str()) {
1275 Ok(State::new(Def::System(tpe)))
1276 } else if let Some(tpe) = scope.entries.get_mut(id.as_str()) {
1277 Ok(State::new(Def::User(tpe)))
1278 } else {
1279 Err(AnalysisError::VariableUndeclared(
1280 attrs.pos.line,
1281 attrs.pos.col,
1282 id.clone(),
1283 ))
1284 }
1285 }
1286 Value::Access(access) => {
1287 let mut state = go(scope, sys, &access.target.attrs, &access.target.value)?;
1288
1289 let is_data_field = state.depth == 0 && access.field == "data";
1291
1292 if !state.dynamic && is_data_field {
1296 state.dynamic = true;
1297 }
1298
1299 match state.definition {
1300 Def::User(tpe) => {
1301 if matches!(tpe, Type::Unspecified) && state.dynamic {
1302 *tpe = Type::Record(BTreeMap::from([(
1303 access.field.clone(),
1304 Type::Unspecified,
1305 )]));
1306 return Ok(State {
1307 depth: state.depth + 1,
1308 definition: Def::User(
1309 tpe.as_record_or_panic_mut()
1310 .get_mut(access.field.as_str())
1311 .unwrap(),
1312 ),
1313 ..state
1314 });
1315 }
1316
1317 if let Type::Record(fields) = tpe {
1318 match fields.entry(access.field.clone()) {
1319 Entry::Vacant(entry) => {
1320 if state.dynamic || is_data_field {
1321 return Ok(State {
1322 depth: state.depth + 1,
1323 definition: Def::User(
1324 entry.insert(Type::Unspecified),
1325 ),
1326 ..state
1327 });
1328 }
1329
1330 return Err(AnalysisError::FieldUndeclared(
1331 attrs.pos.line,
1332 attrs.pos.col,
1333 access.field.clone(),
1334 ));
1335 }
1336
1337 Entry::Occupied(entry) => {
1338 return Ok(State {
1339 depth: state.depth + 1,
1340 definition: Def::User(entry.into_mut()),
1341 ..state
1342 });
1343 }
1344 }
1345 }
1346
1347 Err(AnalysisError::ExpectRecord(
1348 attrs.pos.line,
1349 attrs.pos.col,
1350 tpe.clone(),
1351 ))
1352 }
1353
1354 Def::System(tpe) => {
1355 if matches!(tpe, Type::Unspecified) && state.dynamic {
1356 return Ok(State {
1357 depth: state.depth + 1,
1358 definition: Def::System(&Type::Unspecified),
1359 ..state
1360 });
1361 }
1362
1363 if let Type::Record(fields) = tpe {
1364 if let Some(field) = fields.get(access.field.as_str()) {
1365 return Ok(State {
1366 depth: state.depth + 1,
1367 definition: Def::System(field),
1368 ..state
1369 });
1370 }
1371
1372 return Err(AnalysisError::FieldUndeclared(
1373 attrs.pos.line,
1374 attrs.pos.col,
1375 access.field.clone(),
1376 ));
1377 }
1378
1379 Err(AnalysisError::ExpectRecord(
1380 attrs.pos.line,
1381 attrs.pos.col,
1382 tpe.clone(),
1383 ))
1384 }
1385 }
1386 }
1387 Value::Number(_)
1388 | Value::String(_)
1389 | Value::Bool(_)
1390 | Value::Array(_)
1391 | Value::Record(_)
1392 | Value::App(_)
1393 | Value::Binary(_)
1394 | Value::Unary(_)
1395 | Value::Group(_) => unreachable!(),
1396 }
1397 }
1398
1399 let state = go(&mut self.scope, self.options, attrs, access)?;
1400
1401 match state.definition {
1402 Def::User(tpe) => {
1403 let tmp = mem::take(tpe);
1404 *tpe = tmp.check(attrs, expect)?;
1405
1406 Ok(tpe.clone())
1407 }
1408
1409 Def::System(tpe) => tpe.clone().check(attrs, expect),
1410 }
1411 }
1412
1413 fn projection_type(&self, query: &Query<Typed>) -> Type {
1414 self.project_type(&query.projection.value)
1415 }
1416
1417 fn project_type(&self, value: &Value) -> Type {
1418 match value {
1419 Value::Number(_) => Type::Number,
1420 Value::String(_) => Type::String,
1421 Value::Bool(_) => Type::Bool,
1422 Value::Id(id) => {
1423 if let Some(tpe) = self.options.default_scope.entries.get(id) {
1424 tpe.clone()
1425 } else if let Some(tpe) = self.scope.entries.get(id) {
1426 tpe.clone()
1427 } else {
1428 Type::Unspecified
1429 }
1430 }
1431 Value::Array(exprs) => {
1432 let mut project = Type::Unspecified;
1433
1434 for expr in exprs {
1435 let tmp = self.project_type(&expr.value);
1436
1437 if !matches!(tmp, Type::Unspecified) {
1438 project = tmp;
1439 break;
1440 }
1441 }
1442
1443 Type::Array(Box::new(project))
1444 }
1445 Value::Record(fields) => Type::Record(
1446 fields
1447 .iter()
1448 .map(|field| (field.name.clone(), self.project_type(&field.value.value)))
1449 .collect(),
1450 ),
1451 Value::Access(access) => {
1452 let tpe = self.project_type(&access.target.value);
1453 if let Type::Record(fields) = tpe {
1454 fields
1455 .get(access.field.as_str())
1456 .cloned()
1457 .unwrap_or_default()
1458 } else {
1459 Type::Unspecified
1460 }
1461 }
1462 Value::App(app) => self
1463 .options
1464 .default_scope
1465 .entries
1466 .get(app.func.as_str())
1467 .cloned()
1468 .unwrap_or_default(),
1469 Value::Binary(binary) => match binary.operator {
1470 Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => Type::Number,
1471 Operator::As => {
1472 if let Value::Id(n) = &binary.rhs.as_ref().value
1473 && let Some(tpe) = name_to_type(self.options, n.as_str())
1474 {
1475 tpe
1476 } else {
1477 Type::Unspecified
1478 }
1479 }
1480 Operator::Eq
1481 | Operator::Neq
1482 | Operator::Lt
1483 | Operator::Lte
1484 | Operator::Gt
1485 | Operator::Gte
1486 | Operator::And
1487 | Operator::Or
1488 | Operator::Xor
1489 | Operator::Not
1490 | Operator::Contains => Type::Bool,
1491 },
1492 Value::Unary(unary) => match unary.operator {
1493 Operator::Add | Operator::Sub => Type::Number,
1494 Operator::Mul
1495 | Operator::Div
1496 | Operator::Eq
1497 | Operator::Neq
1498 | Operator::Lt
1499 | Operator::Lte
1500 | Operator::Gt
1501 | Operator::Gte
1502 | Operator::And
1503 | Operator::Or
1504 | Operator::Xor
1505 | Operator::Not
1506 | Operator::Contains
1507 | Operator::As => unreachable!(),
1508 },
1509 Value::Group(expr) => self.project_type(&expr.value),
1510 }
1511 }
1512}
1513
1514fn name_to_type(opts: &AnalysisOptions, name: &str) -> Option<Type> {
1515 if name.eq_ignore_ascii_case("string") {
1516 Some(Type::String)
1517 } else if name.eq_ignore_ascii_case("int") || name.eq_ignore_ascii_case("float64") {
1518 Some(Type::Number)
1519 } else if name.eq_ignore_ascii_case("boolean") {
1520 Some(Type::Bool)
1521 } else if name.eq_ignore_ascii_case("date") {
1522 Some(Type::Date)
1523 } else if name.eq_ignore_ascii_case("time") {
1524 Some(Type::Time)
1525 } else if name.eq_ignore_ascii_case("datetime") {
1526 Some(Type::DateTime)
1527 } else if opts.custom_types.contains(&Ascii::new(name.to_owned())) {
1528 Some(Type::Custom(name.to_owned()))
1530 } else {
1531 None
1532 }
1533}