eventql_parser/
analysis.rs

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