eventql_parser/
analysis.rs

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, Expr, Query, Raw, Source, SourceKind, Type, Value, error::AnalysisError, token::Operator,
12};
13
14/// Represents the state of a query that has been statically analyzed.
15///
16/// This type is used as a marker to indicate that a query has successfully
17/// passed static analysis. It contains metadata about the query's type
18/// information and variable scope after type checking.
19///
20/// All variables in a typed query are guaranteed to be:
21/// - Properly declared and in scope
22/// - Type-safe with sound type assignments
23#[derive(Debug, Clone, Serialize)]
24pub struct Typed {
25    /// The inferred type of the query's projection (PROJECT INTO clause).
26    ///
27    /// This represents the shape and types of the data that will be
28    /// returned by the query.
29    pub project: Type,
30
31    /// The variable scope after static analysis.
32    ///
33    /// Contains all variables that were in scope during type checking,
34    /// including bindings from FROM clauses and their associated types.
35    #[serde(skip)]
36    pub scope: Scope,
37}
38
39/// Result type for static analysis operations.
40///
41/// This is a convenience type alias for `Result<A, AnalysisError>` used throughout
42/// the static analysis module.
43pub type AnalysisResult<A> = std::result::Result<A, AnalysisError>;
44
45/// Configuration options for static analysis.
46///
47/// This structure contains the type information needed to perform static analysis
48/// on EventQL queries, including the default scope with built-in functions and
49/// the type information for event records.
50pub struct AnalysisOptions {
51    /// The default scope containing built-in functions and their type signatures.
52    pub default_scope: Scope,
53    /// Type information for event records being queried.
54    pub event_type_info: Type,
55    /// Custom types that are not defined in the EventQL reference.
56    ///
57    /// This set allows users to register custom type names that can be used
58    /// in type conversion expressions (e.g., `field AS CustomType`). Custom
59    /// type names are case-insensitive.
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use eventql_parser::prelude::AnalysisOptions;
65    ///
66    /// let options = AnalysisOptions::default()
67    ///     .add_custom_type("Foobar");
68    /// ```
69    pub custom_types: HashSet<Ascii<String>>,
70}
71
72impl AnalysisOptions {
73    /// Adds a custom type name to the analysis options.
74    ///
75    /// Custom types allow you to use type conversion syntax with types that are
76    /// not part of the standard EventQL type system. The type name is stored
77    /// case-insensitively.
78    ///
79    /// # Arguments
80    ///
81    /// * `value` - The custom type name to register
82    ///
83    /// # Returns
84    ///
85    /// Returns `self` to allow for method chaining.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use eventql_parser::prelude::AnalysisOptions;
91    ///
92    /// let options = AnalysisOptions::default()
93    ///     .add_custom_type("Timestamp")
94    ///     .add_custom_type("UUID");
95    /// ```
96    pub fn add_custom_type<'a>(mut self, value: impl Into<Cow<'a, str>>) -> Self {
97        match value.into() {
98            Cow::Borrowed(t) => self.custom_types.insert(Ascii::new(t.to_owned())),
99            Cow::Owned(t) => self.custom_types.insert(Ascii::new(t)),
100        };
101
102        self
103    }
104}
105
106impl Default for AnalysisOptions {
107    fn default() -> Self {
108        Self {
109            default_scope: Scope {
110                entries: HashMap::from([
111                    (
112                        "ABS".to_owned(),
113                        Type::App {
114                            args: vec![Type::Number],
115                            result: Box::new(Type::Number),
116                        },
117                    ),
118                    (
119                        "CEIL".to_owned(),
120                        Type::App {
121                            args: vec![Type::Number],
122                            result: Box::new(Type::Number),
123                        },
124                    ),
125                    (
126                        "FLOOR".to_owned(),
127                        Type::App {
128                            args: vec![Type::Number],
129                            result: Box::new(Type::Number),
130                        },
131                    ),
132                    (
133                        "ROUND".to_owned(),
134                        Type::App {
135                            args: vec![Type::Number],
136                            result: Box::new(Type::Number),
137                        },
138                    ),
139                    (
140                        "COS".to_owned(),
141                        Type::App {
142                            args: vec![Type::Number],
143                            result: Box::new(Type::Number),
144                        },
145                    ),
146                    (
147                        "EXP".to_owned(),
148                        Type::App {
149                            args: vec![Type::Number],
150                            result: Box::new(Type::Number),
151                        },
152                    ),
153                    (
154                        "POW".to_owned(),
155                        Type::App {
156                            args: vec![Type::Number, Type::Number],
157                            result: Box::new(Type::Number),
158                        },
159                    ),
160                    (
161                        "SQRT".to_owned(),
162                        Type::App {
163                            args: vec![Type::Number],
164                            result: Box::new(Type::Number),
165                        },
166                    ),
167                    (
168                        "RAND".to_owned(),
169                        Type::App {
170                            args: vec![Type::Number],
171                            result: Box::new(Type::Number),
172                        },
173                    ),
174                    (
175                        "PI".to_owned(),
176                        Type::App {
177                            args: vec![Type::Number],
178                            result: Box::new(Type::Number),
179                        },
180                    ),
181                    (
182                        "LOWER".to_owned(),
183                        Type::App {
184                            args: vec![Type::String],
185                            result: Box::new(Type::String),
186                        },
187                    ),
188                    (
189                        "UPPER".to_owned(),
190                        Type::App {
191                            args: vec![Type::String],
192                            result: Box::new(Type::String),
193                        },
194                    ),
195                    (
196                        "TRIM".to_owned(),
197                        Type::App {
198                            args: vec![Type::String],
199                            result: Box::new(Type::String),
200                        },
201                    ),
202                    (
203                        "LTRIM".to_owned(),
204                        Type::App {
205                            args: vec![Type::String],
206                            result: Box::new(Type::String),
207                        },
208                    ),
209                    (
210                        "RTRIM".to_owned(),
211                        Type::App {
212                            args: vec![Type::String],
213                            result: Box::new(Type::String),
214                        },
215                    ),
216                    (
217                        "LEN".to_owned(),
218                        Type::App {
219                            args: vec![Type::String],
220                            result: Box::new(Type::Number),
221                        },
222                    ),
223                    (
224                        "INSTR".to_owned(),
225                        Type::App {
226                            args: vec![Type::String],
227                            result: Box::new(Type::Number),
228                        },
229                    ),
230                    (
231                        "SUBSTRING".to_owned(),
232                        Type::App {
233                            args: vec![Type::String, Type::Number, Type::Number],
234                            result: Box::new(Type::String),
235                        },
236                    ),
237                    (
238                        "REPLACE".to_owned(),
239                        Type::App {
240                            args: vec![Type::String, Type::String, Type::String],
241                            result: Box::new(Type::String),
242                        },
243                    ),
244                    (
245                        "STARTSWITH".to_owned(),
246                        Type::App {
247                            args: vec![Type::String, Type::String],
248                            result: Box::new(Type::Bool),
249                        },
250                    ),
251                    (
252                        "ENDSWITH".to_owned(),
253                        Type::App {
254                            args: vec![Type::String, Type::String],
255                            result: Box::new(Type::Bool),
256                        },
257                    ),
258                    (
259                        "NOW".to_owned(),
260                        Type::App {
261                            args: vec![],
262                            result: Box::new(Type::String),
263                        },
264                    ),
265                    (
266                        "YEAR".to_owned(),
267                        Type::App {
268                            args: vec![Type::String],
269                            result: Box::new(Type::Number),
270                        },
271                    ),
272                    (
273                        "MONTH".to_owned(),
274                        Type::App {
275                            args: vec![Type::String],
276                            result: Box::new(Type::Number),
277                        },
278                    ),
279                    (
280                        "DAY".to_owned(),
281                        Type::App {
282                            args: vec![Type::String],
283                            result: Box::new(Type::Number),
284                        },
285                    ),
286                    (
287                        "HOUR".to_owned(),
288                        Type::App {
289                            args: vec![Type::String],
290                            result: Box::new(Type::Number),
291                        },
292                    ),
293                    (
294                        "MINUTE".to_owned(),
295                        Type::App {
296                            args: vec![Type::String],
297                            result: Box::new(Type::Number),
298                        },
299                    ),
300                    (
301                        "SECOND".to_owned(),
302                        Type::App {
303                            args: vec![Type::String],
304                            result: Box::new(Type::Number),
305                        },
306                    ),
307                    (
308                        "WEEKDAY".to_owned(),
309                        Type::App {
310                            args: vec![Type::String],
311                            result: Box::new(Type::Number),
312                        },
313                    ),
314                    (
315                        "IF".to_owned(),
316                        Type::App {
317                            args: vec![Type::Bool, Type::Unspecified, Type::Unspecified],
318                            result: Box::new(Type::Unspecified),
319                        },
320                    ),
321                    (
322                        "COUNT".to_owned(),
323                        Type::App {
324                            args: vec![],
325                            result: Box::new(Type::Number),
326                        },
327                    ),
328                    (
329                        "SUM".to_owned(),
330                        Type::App {
331                            args: vec![Type::Number],
332                            result: Box::new(Type::Number),
333                        },
334                    ),
335                    (
336                        "AVG".to_owned(),
337                        Type::App {
338                            args: vec![Type::Number],
339                            result: Box::new(Type::Number),
340                        },
341                    ),
342                    (
343                        "MIN".to_owned(),
344                        Type::App {
345                            args: vec![Type::Number],
346                            result: Box::new(Type::Number),
347                        },
348                    ),
349                    (
350                        "MAX".to_owned(),
351                        Type::App {
352                            args: vec![Type::Number],
353                            result: Box::new(Type::Number),
354                        },
355                    ),
356                    (
357                        "MEDIAN".to_owned(),
358                        Type::App {
359                            args: vec![Type::Number],
360                            result: Box::new(Type::Number),
361                        },
362                    ),
363                    (
364                        "STDDEV".to_owned(),
365                        Type::App {
366                            args: vec![Type::Number],
367                            result: Box::new(Type::Number),
368                        },
369                    ),
370                    (
371                        "VARIANCE".to_owned(),
372                        Type::App {
373                            args: vec![Type::Number],
374                            result: Box::new(Type::Number),
375                        },
376                    ),
377                    (
378                        "UNIQUE".to_owned(),
379                        Type::App {
380                            args: vec![Type::Number],
381                            result: Box::new(Type::Number),
382                        },
383                    ),
384                ]),
385            },
386            event_type_info: Type::Record(BTreeMap::from([
387                ("specversion".to_owned(), Type::String),
388                ("id".to_owned(), Type::String),
389                ("time".to_owned(), Type::String),
390                ("source".to_owned(), Type::String),
391                ("subject".to_owned(), Type::Subject),
392                ("type".to_owned(), Type::String),
393                ("datacontenttype".to_owned(), Type::String),
394                ("data".to_owned(), Type::Unspecified),
395                ("predecessorhash".to_owned(), Type::String),
396                ("hash".to_owned(), Type::String),
397                ("traceparent".to_owned(), Type::String),
398                ("tracestate".to_owned(), Type::String),
399                ("signature".to_owned(), Type::String),
400            ])),
401            custom_types: HashSet::default(),
402        }
403    }
404}
405
406/// Performs static analysis on an EventQL query.
407///
408/// This function takes a raw (untyped) query and performs type checking and
409/// variable scoping analysis. It validates that:
410/// - All variables are properly declared
411/// - Types match expected types in expressions and operations
412/// - Field accesses are valid for their record types
413/// - Function calls have the correct argument types
414///
415/// # Arguments
416///
417/// * `options` - Configuration containing type information and default scope
418/// * `query` - The raw query to analyze
419///
420/// # Returns
421///
422/// Returns a typed query on success, or an `AnalysisError` if type checking fails.
423pub fn static_analysis(
424    options: &AnalysisOptions,
425    query: Query<Raw>,
426) -> AnalysisResult<Query<Typed>> {
427    let mut analysis = Analysis::new(options);
428
429    analysis.analyze_query(query)
430}
431
432/// Represents a variable scope during static analysis.
433///
434/// A scope tracks the variables and their types that are currently in scope
435/// during type checking. This is used to resolve variable references and
436/// ensure type correctness.
437#[derive(Default, Serialize, Clone, Debug)]
438pub struct Scope {
439    /// Map of variable names to their types.
440    pub entries: HashMap<String, Type>,
441}
442
443impl Scope {
444    /// Checks if the scope contains no entries.
445    pub fn is_empty(&self) -> bool {
446        self.entries.is_empty()
447    }
448}
449
450struct Analysis<'a> {
451    options: &'a AnalysisOptions,
452    prev_scopes: Vec<Scope>,
453    scope: Scope,
454}
455
456impl<'a> Analysis<'a> {
457    fn new(options: &'a AnalysisOptions) -> Self {
458        Self {
459            options,
460            prev_scopes: Default::default(),
461            scope: Scope::default(),
462        }
463    }
464
465    fn enter_scope(&mut self) {
466        if self.scope.is_empty() {
467            return;
468        }
469
470        let prev = mem::take(&mut self.scope);
471        self.prev_scopes.push(prev);
472    }
473
474    fn exit_scope(&mut self) -> Scope {
475        if let Some(prev) = self.prev_scopes.pop() {
476            mem::replace(&mut self.scope, prev)
477        } else {
478            mem::take(&mut self.scope)
479        }
480    }
481
482    fn analyze_query(&mut self, query: Query<Raw>) -> AnalysisResult<Query<Typed>> {
483        self.enter_scope();
484
485        let mut sources = Vec::with_capacity(query.sources.len());
486
487        for source in query.sources {
488            sources.push(self.analyze_source(source)?);
489        }
490
491        if let Some(expr) = &query.predicate {
492            self.analyze_expr(expr, Type::Bool)?;
493        }
494
495        if let Some(group_by) = &query.group_by {
496            if !matches!(&group_by.expr.value, Value::Access(_)) {
497                return Err(AnalysisError::ExpectFieldLiteral(
498                    group_by.expr.attrs.pos.line,
499                    group_by.expr.attrs.pos.col,
500                ));
501            }
502
503            self.analyze_expr(&group_by.expr, Type::Unspecified)?;
504
505            if let Some(expr) = &group_by.predicate {
506                self.analyze_expr(expr, Type::Bool)?;
507            }
508        }
509
510        if let Some(order_by) = &query.order_by {
511            if !matches!(&order_by.expr.value, Value::Access(_)) {
512                return Err(AnalysisError::ExpectFieldLiteral(
513                    order_by.expr.attrs.pos.line,
514                    order_by.expr.attrs.pos.col,
515                ));
516            }
517            self.analyze_expr(&order_by.expr, Type::Unspecified)?;
518        }
519
520        if !matches!(&query.projection.value, Value::Record(_) | Value::Id(_)) {
521            return Err(AnalysisError::ExpectRecordLiteral(
522                query.projection.attrs.pos.line,
523                query.projection.attrs.pos.col,
524            ));
525        }
526
527        let project = self.analyze_expr(&query.projection, Type::Unspecified)?;
528
529        if !matches!(&project, Type::Record(f) if !f.is_empty()) {
530            return Err(AnalysisError::ExpectRecord(
531                query.projection.attrs.pos.line,
532                query.projection.attrs.pos.col,
533                project,
534            ));
535        }
536
537        let scope = self.exit_scope();
538
539        Ok(Query {
540            attrs: query.attrs,
541            sources,
542            predicate: query.predicate,
543            group_by: query.group_by,
544            order_by: query.order_by,
545            limit: query.limit,
546            projection: query.projection,
547            distinct: query.distinct,
548            meta: Typed { project, scope },
549        })
550    }
551
552    fn analyze_source(&mut self, source: Source<Raw>) -> AnalysisResult<Source<Typed>> {
553        let kind = self.analyze_source_kind(source.kind)?;
554        let tpe = match &kind {
555            SourceKind::Name(_) | SourceKind::Subject(_) => self.options.event_type_info.clone(),
556            SourceKind::Subquery(query) => self.projection_type(query),
557        };
558
559        if self
560            .scope
561            .entries
562            .insert(source.binding.name.clone(), tpe)
563            .is_some()
564        {
565            return Err(AnalysisError::BindingAlreadyExists(
566                source.binding.pos.line,
567                source.binding.pos.col,
568                source.binding.name,
569            ));
570        }
571
572        Ok(Source {
573            binding: source.binding,
574            kind,
575        })
576    }
577
578    fn analyze_source_kind(&mut self, kind: SourceKind<Raw>) -> AnalysisResult<SourceKind<Typed>> {
579        match kind {
580            SourceKind::Name(n) => Ok(SourceKind::Name(n)),
581            SourceKind::Subject(s) => Ok(SourceKind::Subject(s)),
582            SourceKind::Subquery(query) => {
583                let query = self.analyze_query(*query)?;
584                Ok(SourceKind::Subquery(Box::new(query)))
585            }
586        }
587    }
588
589    fn analyze_expr(&mut self, expr: &Expr, expect: Type) -> AnalysisResult<Type> {
590        self.analyze_value(&expr.attrs, &expr.value, expect)
591    }
592
593    fn analyze_value(
594        &mut self,
595        attrs: &Attrs,
596        value: &Value,
597        mut expect: Type,
598    ) -> AnalysisResult<Type> {
599        match value {
600            Value::Number(_) => expect.check(attrs, Type::Number),
601            Value::String(_) => expect.check(attrs, Type::String),
602            Value::Bool(_) => expect.check(attrs, Type::Bool),
603
604            Value::Id(id) => {
605                if let Some(tpe) = self.options.default_scope.entries.get(id) {
606                    expect.check(attrs, tpe.clone())
607                } else if let Some(tpe) = self.scope.entries.get_mut(id.as_str()) {
608                    let tmp = mem::take(tpe);
609                    *tpe = tmp.check(attrs, expect)?;
610
611                    Ok(tpe.clone())
612                } else {
613                    Err(AnalysisError::VariableUndeclared(
614                        attrs.pos.line,
615                        attrs.pos.col,
616                        id.to_owned(),
617                    ))
618                }
619            }
620
621            Value::Array(exprs) => {
622                if matches!(expect, Type::Unspecified) {
623                    for expr in exprs {
624                        expect = self.analyze_expr(expr, expect)?;
625                    }
626
627                    return Ok(Type::Array(Box::new(expect)));
628                }
629
630                match expect {
631                    Type::Array(mut expect) => {
632                        for expr in exprs {
633                            *expect = self.analyze_expr(expr, expect.as_ref().clone())?;
634                        }
635
636                        Ok(Type::Array(expect))
637                    }
638
639                    expect => Err(AnalysisError::TypeMismatch(
640                        attrs.pos.line,
641                        attrs.pos.col,
642                        expect,
643                        self.project_type(value),
644                    )),
645                }
646            }
647
648            Value::Record(fields) => {
649                if matches!(expect, Type::Unspecified) {
650                    let mut record = BTreeMap::new();
651
652                    for field in fields {
653                        record.insert(
654                            field.name.clone(),
655                            self.analyze_value(
656                                &field.value.attrs,
657                                &field.value.value,
658                                Type::Unspecified,
659                            )?,
660                        );
661                    }
662
663                    return Ok(Type::Record(record));
664                }
665
666                match expect {
667                    Type::Record(mut types) if fields.len() == types.len() => {
668                        for field in fields {
669                            if let Some(tpe) = types.remove(field.name.as_str()) {
670                                types.insert(
671                                    field.name.clone(),
672                                    self.analyze_expr(&field.value, tpe)?,
673                                );
674                            } else {
675                                return Err(AnalysisError::FieldUndeclared(
676                                    attrs.pos.line,
677                                    attrs.pos.col,
678                                    field.name.clone(),
679                                ));
680                            }
681                        }
682
683                        Ok(Type::Record(types))
684                    }
685
686                    expect => Err(AnalysisError::TypeMismatch(
687                        attrs.pos.line,
688                        attrs.pos.col,
689                        expect,
690                        self.project_type(value),
691                    )),
692                }
693            }
694
695            this @ Value::Access(_) => Ok(self.analyze_access(attrs, this, expect)?),
696
697            this @ Value::App(app) => {
698                if matches!(expect, Type::Unspecified) {
699                    return Ok(self.project_type(this));
700                }
701
702                match expect {
703                    Type::App { args, mut result } if app.args.len() == args.len() => {
704                        let mut arg_types = Vec::with_capacity(args.capacity());
705                        for (arg, tpe) in app.args.iter().zip(args.into_iter()) {
706                            arg_types.push(self.analyze_expr(arg, tpe)?);
707                        }
708
709                        if let Some(tpe) = self.options.default_scope.entries.get(app.func.as_str())
710                        {
711                            let tmp = mem::take(result.as_mut());
712                            *result = tmp.check(attrs, tpe.clone())?;
713
714                            Ok(Type::App {
715                                args: arg_types,
716                                result,
717                            })
718                        } else {
719                            Err(AnalysisError::FuncUndeclared(
720                                attrs.pos.line,
721                                attrs.pos.col,
722                                app.func.clone(),
723                            ))
724                        }
725                    }
726
727                    expect => Err(AnalysisError::TypeMismatch(
728                        attrs.pos.line,
729                        attrs.pos.col,
730                        expect,
731                        self.project_type(value),
732                    )),
733                }
734            }
735
736            Value::Binary(binary) => match binary.operator {
737                Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => {
738                    self.analyze_expr(&binary.lhs, Type::Number)?;
739                    self.analyze_expr(&binary.rhs, Type::Number)?;
740                    expect.check(attrs, Type::Number)
741                }
742
743                Operator::Eq
744                | Operator::Neq
745                | Operator::Lt
746                | Operator::Lte
747                | Operator::Gt
748                | Operator::Gte => {
749                    let lhs_expect = self.analyze_expr(&binary.lhs, Type::Unspecified)?;
750                    let rhs_expect = self.analyze_expr(&binary.rhs, lhs_expect.clone())?;
751
752                    // If the left side didn't have enough type information while the other did,
753                    // we replay another typecheck pass on the left side if the right side was conclusive
754                    if matches!(lhs_expect, Type::Unspecified)
755                        && !matches!(rhs_expect, Type::Unspecified)
756                    {
757                        self.analyze_expr(&binary.lhs, rhs_expect)?;
758                    }
759
760                    expect.check(attrs, Type::Bool)
761                }
762
763                Operator::Contains => {
764                    let lhs_expect =
765                        self.analyze_expr(&binary.lhs, Type::Array(Box::new(Type::Unspecified)))?;
766
767                    let lhs_assumption = match lhs_expect {
768                        Type::Array(inner) => *inner,
769                        other => {
770                            return Err(AnalysisError::ExpectArray(
771                                attrs.pos.line,
772                                attrs.pos.col,
773                                other,
774                            ));
775                        }
776                    };
777
778                    let rhs_expect = self.analyze_expr(&binary.rhs, lhs_assumption.clone())?;
779
780                    // If the left side didn't have enough type information while the other did,
781                    // we replay another typecheck pass on the left side if the right side was conclusive
782                    if matches!(lhs_assumption, Type::Unspecified)
783                        && !matches!(rhs_expect, Type::Unspecified)
784                    {
785                        self.analyze_expr(&binary.lhs, Type::Array(Box::new(rhs_expect)))?;
786                    }
787
788                    expect.check(attrs, Type::Bool)
789                }
790
791                Operator::And | Operator::Or | Operator::Xor => {
792                    self.analyze_expr(&binary.lhs, Type::Bool)?;
793                    self.analyze_expr(&binary.rhs, Type::Bool)?;
794
795                    expect.check(attrs, Type::Bool)
796                }
797
798                Operator::As => {
799                    if let Value::Id(name) = &binary.rhs.value {
800                        if let Some(tpe) = name_to_type(self.options, name) {
801                            // NOTE - we could check if it's safe to convert the left branch to that type
802                            return Ok(tpe);
803                        } else {
804                            return Err(AnalysisError::UnsupportedCustomType(
805                                attrs.pos.line,
806                                attrs.pos.col,
807                                name.clone(),
808                            ));
809                        }
810                    }
811
812                    unreachable!(
813                        "we already made sure during parsing that we can only have an ID symbol at this point"
814                    )
815                }
816
817                Operator::Not => unreachable!(),
818            },
819
820            Value::Unary(unary) => match unary.operator {
821                Operator::Add | Operator::Sub => {
822                    self.analyze_expr(&unary.expr, Type::Number)?;
823                    expect.check(attrs, Type::Number)
824                }
825
826                Operator::Not => {
827                    self.analyze_expr(&unary.expr, Type::Bool)?;
828                    expect.check(attrs, Type::Bool)
829                }
830
831                _ => unreachable!(),
832            },
833
834            Value::Group(expr) => Ok(self.analyze_expr(expr.as_ref(), expect)?),
835        }
836    }
837
838    fn analyze_access(
839        &mut self,
840        attrs: &Attrs,
841        access: &Value,
842        expect: Type,
843    ) -> AnalysisResult<Type> {
844        struct State<A, B> {
845            depth: u8,
846            /// When true means we are into dynamically type object.
847            dynamic: bool,
848            definition: Def<A, B>,
849        }
850
851        impl<A, B> State<A, B> {
852            fn new(definition: Def<A, B>) -> Self {
853                Self {
854                    depth: 0,
855                    dynamic: false,
856                    definition,
857                }
858            }
859        }
860
861        enum Def<A, B> {
862            User(A),
863            System(B),
864        }
865
866        fn go<'a>(
867            scope: &'a mut Scope,
868            sys: &'a AnalysisOptions,
869            attrs: &'a Attrs,
870            value: &'a Value,
871        ) -> AnalysisResult<State<&'a mut Type, &'a Type>> {
872            match value {
873                Value::Id(id) => {
874                    if let Some(tpe) = sys.default_scope.entries.get(id.as_str()) {
875                        Ok(State::new(Def::System(tpe)))
876                    } else if let Some(tpe) = scope.entries.get_mut(id.as_str()) {
877                        Ok(State::new(Def::User(tpe)))
878                    } else {
879                        Err(AnalysisError::VariableUndeclared(
880                            attrs.pos.line,
881                            attrs.pos.col,
882                            id.clone(),
883                        ))
884                    }
885                }
886                Value::Access(access) => {
887                    let mut state = go(scope, sys, &access.target.attrs, &access.target.value)?;
888
889                    // TODO - we should consider make that field and depth configurable.
890                    let is_data_field = state.depth == 0 && access.field == "data";
891
892                    // TODO - we should consider make that behavior configurable.
893                    // the `data` property is where the JSON payload is located, which means
894                    // we should be lax if a property is not defined yet.
895                    if !state.dynamic && is_data_field {
896                        state.dynamic = true;
897                    }
898
899                    match state.definition {
900                        Def::User(tpe) => {
901                            if matches!(tpe, Type::Unspecified) && state.dynamic {
902                                *tpe = Type::Record(BTreeMap::from([(
903                                    access.field.clone(),
904                                    Type::Unspecified,
905                                )]));
906                                return Ok(State {
907                                    depth: state.depth + 1,
908                                    definition: Def::User(
909                                        tpe.as_record_or_panic_mut()
910                                            .get_mut(access.field.as_str())
911                                            .unwrap(),
912                                    ),
913                                    ..state
914                                });
915                            }
916
917                            if let Type::Record(fields) = tpe {
918                                match fields.entry(access.field.clone()) {
919                                    Entry::Vacant(entry) => {
920                                        if state.dynamic || is_data_field {
921                                            return Ok(State {
922                                                depth: state.depth + 1,
923                                                definition: Def::User(
924                                                    entry.insert(Type::Unspecified),
925                                                ),
926                                                ..state
927                                            });
928                                        }
929
930                                        return Err(AnalysisError::FieldUndeclared(
931                                            attrs.pos.line,
932                                            attrs.pos.col,
933                                            access.field.clone(),
934                                        ));
935                                    }
936
937                                    Entry::Occupied(entry) => {
938                                        return Ok(State {
939                                            depth: state.depth + 1,
940                                            definition: Def::User(entry.into_mut()),
941                                            ..state
942                                        });
943                                    }
944                                }
945                            }
946
947                            Err(AnalysisError::ExpectRecord(
948                                attrs.pos.line,
949                                attrs.pos.col,
950                                tpe.clone(),
951                            ))
952                        }
953
954                        Def::System(tpe) => {
955                            if matches!(tpe, Type::Unspecified) && state.dynamic {
956                                return Ok(State {
957                                    depth: state.depth + 1,
958                                    definition: Def::System(&Type::Unspecified),
959                                    ..state
960                                });
961                            }
962
963                            if let Type::Record(fields) = tpe {
964                                if let Some(field) = fields.get(access.field.as_str()) {
965                                    return Ok(State {
966                                        depth: state.depth + 1,
967                                        definition: Def::System(field),
968                                        ..state
969                                    });
970                                }
971
972                                return Err(AnalysisError::FieldUndeclared(
973                                    attrs.pos.line,
974                                    attrs.pos.col,
975                                    access.field.clone(),
976                                ));
977                            }
978
979                            Err(AnalysisError::ExpectRecord(
980                                attrs.pos.line,
981                                attrs.pos.col,
982                                tpe.clone(),
983                            ))
984                        }
985                    }
986                }
987                Value::Number(_)
988                | Value::String(_)
989                | Value::Bool(_)
990                | Value::Array(_)
991                | Value::Record(_)
992                | Value::App(_)
993                | Value::Binary(_)
994                | Value::Unary(_)
995                | Value::Group(_) => unreachable!(),
996            }
997        }
998
999        let state = go(&mut self.scope, self.options, attrs, access)?;
1000
1001        match state.definition {
1002            Def::User(tpe) => {
1003                let tmp = mem::take(tpe);
1004                *tpe = tmp.check(attrs, expect)?;
1005
1006                Ok(tpe.clone())
1007            }
1008
1009            Def::System(tpe) => tpe.clone().check(attrs, expect),
1010        }
1011    }
1012
1013    fn projection_type(&self, query: &Query<Typed>) -> Type {
1014        self.project_type(&query.projection.value)
1015    }
1016
1017    fn project_type(&self, value: &Value) -> Type {
1018        match value {
1019            Value::Number(_) => Type::Number,
1020            Value::String(_) => Type::String,
1021            Value::Bool(_) => Type::Bool,
1022            Value::Id(id) => {
1023                if let Some(tpe) = self.options.default_scope.entries.get(id) {
1024                    tpe.clone()
1025                } else if let Some(tpe) = self.scope.entries.get(id) {
1026                    tpe.clone()
1027                } else {
1028                    Type::Unspecified
1029                }
1030            }
1031            Value::Array(exprs) => {
1032                let mut project = Type::Unspecified;
1033
1034                for expr in exprs {
1035                    let tmp = self.project_type(&expr.value);
1036
1037                    if !matches!(tmp, Type::Unspecified) {
1038                        project = tmp;
1039                        break;
1040                    }
1041                }
1042
1043                Type::Array(Box::new(project))
1044            }
1045            Value::Record(fields) => Type::Record(
1046                fields
1047                    .iter()
1048                    .map(|field| (field.name.clone(), self.project_type(&field.value.value)))
1049                    .collect(),
1050            ),
1051            Value::Access(access) => {
1052                let tpe = self.project_type(&access.target.value);
1053                if let Type::Record(fields) = tpe {
1054                    fields
1055                        .get(access.field.as_str())
1056                        .cloned()
1057                        .unwrap_or_default()
1058                } else {
1059                    Type::Unspecified
1060                }
1061            }
1062            Value::App(app) => self
1063                .options
1064                .default_scope
1065                .entries
1066                .get(app.func.as_str())
1067                .cloned()
1068                .unwrap_or_default(),
1069            Value::Binary(binary) => match binary.operator {
1070                Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => Type::Number,
1071                Operator::As => {
1072                    if let Value::Id(n) = &binary.rhs.as_ref().value
1073                        && let Some(tpe) = name_to_type(self.options, n.as_str())
1074                    {
1075                        tpe
1076                    } else {
1077                        Type::Unspecified
1078                    }
1079                }
1080                Operator::Eq
1081                | Operator::Neq
1082                | Operator::Lt
1083                | Operator::Lte
1084                | Operator::Gt
1085                | Operator::Gte
1086                | Operator::And
1087                | Operator::Or
1088                | Operator::Xor
1089                | Operator::Not
1090                | Operator::Contains => Type::Bool,
1091            },
1092            Value::Unary(unary) => match unary.operator {
1093                Operator::Add | Operator::Sub => Type::Number,
1094                Operator::Mul
1095                | Operator::Div
1096                | Operator::Eq
1097                | Operator::Neq
1098                | Operator::Lt
1099                | Operator::Lte
1100                | Operator::Gt
1101                | Operator::Gte
1102                | Operator::And
1103                | Operator::Or
1104                | Operator::Xor
1105                | Operator::Not
1106                | Operator::Contains
1107                | Operator::As => unreachable!(),
1108            },
1109            Value::Group(expr) => self.project_type(&expr.value),
1110        }
1111    }
1112}
1113
1114fn name_to_type(opts: &AnalysisOptions, name: &str) -> Option<Type> {
1115    if name.eq_ignore_ascii_case("string") {
1116        Some(Type::String)
1117    } else if name.eq_ignore_ascii_case("int") || name.eq_ignore_ascii_case("float64") {
1118        Some(Type::Number)
1119    } else if name.eq_ignore_ascii_case("boolean") {
1120        Some(Type::Bool)
1121    } else if name.eq_ignore_ascii_case("date") {
1122        Some(Type::Date)
1123    } else if name.eq_ignore_ascii_case("time") {
1124        Some(Type::Time)
1125    } else if name.eq_ignore_ascii_case("datetime") {
1126        Some(Type::DateTime)
1127    } else if opts.custom_types.contains(&Ascii::new(name.to_owned())) {
1128        // ^ Sad we have to allocate here for no reason
1129        Some(Type::Custom(name.to_owned()))
1130    } else {
1131        None
1132    }
1133}