Skip to main content

fml_check/
check.rs

1//! The checker: walks a map with the types of its variables and reports what cannot work.
2
3use std::collections::hash_map::Entry;
4use std::collections::{HashMap, HashSet};
5
6use fml_syntax::ast::{
7    self, AstNode, Expr, Invocation, ParameterMode, QualifiedName, TransformValue,
8};
9use fml_syntax::{Diagnostic, Severity, SyntaxKind, SyntaxNode, TextRange};
10
11use crate::definitions::{Definitions, type_url};
12use crate::types::{Lookup, Type, Types};
13
14/// Checks a parsed map. Syntax errors are not repeated; see [`fml_syntax::Parse::diagnostics`].
15pub fn check(file: &ast::SourceFile, definitions: &dyn Definitions) -> Vec<Diagnostic> {
16    let mut checker = Checker::new(file, definitions);
17    checker.run();
18    let mut diagnostics = checker.diagnostics;
19    diagnostics.sort_by_key(|d| (d.range.start(), d.range.end()));
20    diagnostics
21}
22
23/// The reserved keywords of the mapping language (fml.md, "Reserved keywords").
24const RESERVED: &[&str] = &[
25    "map",
26    "uses",
27    "as",
28    "alias",
29    "imports",
30    "group",
31    "extends",
32    "default",
33    "where",
34    "check",
35    "log",
36    "then",
37    "true",
38    "false",
39    "types",
40    "type",
41    "first",
42    "not_first",
43    "last",
44    "not_last",
45    "only_one",
46    "share",
47    "single",
48    "source",
49    "target",
50    "queried",
51    "produced",
52    "conceptmap",
53    "prefix",
54    "let",
55];
56
57/// How deep untyped groups are followed into the groups they call.
58const MAX_CALL_DEPTH: usize = 32;
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61enum Mode {
62    Source,
63    Target,
64}
65
66#[derive(Debug, Clone)]
67struct Variable {
68    ty: Type,
69    mode: Mode,
70}
71
72struct Checker<'a> {
73    file: &'a ast::SourceFile,
74    definitions: &'a dyn Definitions,
75    /// `uses` aliases and the names of unaliased structures, to their URLs.
76    type_names: HashMap<String, String>,
77    groups: HashMap<String, ast::Group>,
78    has_imports: bool,
79    /// Groups already checked with given argument types.
80    checked: HashSet<(String, Vec<String>)>,
81    reported: HashSet<(TextRange, String)>,
82    diagnostics: Vec<Diagnostic>,
83}
84
85impl<'a> Checker<'a> {
86    fn types(&self) -> Types<'a> {
87        Types::new(self.definitions)
88    }
89
90    fn new(file: &'a ast::SourceFile, definitions: &'a dyn Definitions) -> Self {
91        Self {
92            file,
93            definitions,
94            type_names: HashMap::new(),
95            groups: HashMap::new(),
96            has_imports: file.imports().next().is_some(),
97            checked: HashSet::new(),
98            reported: HashSet::new(),
99            diagnostics: Vec::new(),
100        }
101    }
102
103    fn report(&mut self, severity: Severity, range: TextRange, message: String) {
104        if self.reported.insert((range, message.clone())) {
105            self.diagnostics.push(Diagnostic {
106                severity,
107                range,
108                message,
109            });
110        }
111    }
112
113    fn error(&mut self, node: &SyntaxNode, message: impl Into<String>) {
114        self.report(Severity::Error, trimmed_range(node), message.into());
115    }
116
117    fn warning(&mut self, node: &SyntaxNode, message: impl Into<String>) {
118        self.report(Severity::Warning, trimmed_range(node), message.into());
119    }
120
121    fn error_at(&mut self, range: TextRange, message: impl Into<String>) {
122        self.report(Severity::Error, range, message.into());
123    }
124
125    fn run(&mut self) {
126        self.declarations();
127        self.identity();
128        self.names();
129        self.group_signatures();
130        self.rule_names();
131        let groups: Vec<ast::Group> = self.file.groups().collect();
132        for group in &groups {
133            let types = group
134                .parameters()
135                .map(|parameter| self.parameter_type(&parameter))
136                .collect();
137            self.check_group(group, types, 0);
138        }
139    }
140
141    // ---- Declarations -----------------------------------------------------------------------
142
143    fn declarations(&mut self) {
144        for uses in self.file.uses() {
145            let Some(url) = uses.url().and_then(|u| u.value()) else {
146                continue;
147            };
148            match self.definitions.structure(&url) {
149                Some(structure) => {
150                    let name = uses.alias().unwrap_or_else(|| structure.name.clone());
151                    self.type_names.insert(name, url);
152                }
153                None => {
154                    if self.definitions.covers(&url) {
155                        let at = uses.url().unwrap();
156                        self.error(at.syntax(), format!("unknown structure `{url}`"));
157                    }
158                    if let Some(alias) = uses.alias() {
159                        self.type_names.insert(alias, url);
160                    }
161                }
162            }
163            if uses.mode().is_none() {
164                self.error(
165                    uses.syntax(),
166                    "`uses` needs a mode: `as source` or `as target`",
167                );
168            }
169        }
170        for group in self.file.groups() {
171            let Some(name) = group.name() else { continue };
172            match self.groups.entry(name) {
173                Entry::Occupied(entry) => {
174                    let message = format!("group `{}` is declared twice", entry.key());
175                    self.error(group.syntax(), message);
176                }
177                Entry::Vacant(entry) => {
178                    entry.insert(group);
179                }
180            }
181        }
182        for group in self.file.groups() {
183            if let Some(extends) = group.extends()
184                && !self.groups.contains_key(&extends)
185                && !self.has_imports
186            {
187                self.error(
188                    group.syntax(),
189                    format!("group `{extends}` is not declared and nothing is imported"),
190                );
191            }
192        }
193    }
194
195    /// Constant names must be unique in the file, and no name the map defines may be a reserved
196    /// keyword (fml.md, "Constants" and "Reserved keywords").
197    fn names(&mut self) {
198        let mut constants: HashSet<String> = HashSet::new();
199        for constant in self.file.constants() {
200            let Some(name) = constant.name() else {
201                continue;
202            };
203            if !constants.insert(name.clone()) {
204                self.error(
205                    constant.syntax(),
206                    format!("constant `{name}` is declared twice"),
207                );
208            }
209            if RESERVED.contains(&name.as_str()) {
210                self.warning(constant.syntax(), format!("`{name}` is a reserved keyword"));
211            }
212        }
213        for group in self.file.groups() {
214            if let Some(name) = group.name()
215                && RESERVED.contains(&name.as_str())
216            {
217                self.warning(group.syntax(), format!("`{name}` is a reserved keyword"));
218            }
219            for parameter in group.parameters() {
220                if let Some(name) = parameter.name()
221                    && RESERVED.contains(&name.as_str())
222                {
223                    self.warning(
224                        parameter.syntax(),
225                        format!("`{name}` is a reserved keyword"),
226                    );
227                }
228            }
229        }
230    }
231
232    /// `url` and `name` SHALL be given (fml.md, "Metadata"), in metadata or a `map` line.
233    fn identity(&mut self) {
234        let declaration = self.file.map_declaration();
235        let metadata: Vec<String> = self
236            .file
237            .metadata()
238            .filter_map(|m| m.key().map(|k| k.text()))
239            .collect();
240        let has = |key: &str| metadata.iter().any(|k| k == key);
241        let url = has("url") || declaration.as_ref().is_some_and(|d| d.url().is_some());
242        let name = has("name") || declaration.as_ref().is_some_and(|d| d.name().is_some());
243        let at = TextRange::empty(0.into());
244        if !url {
245            self.error_at(at, "the map has no `url`: add `/// url = '…'`");
246        }
247        if !name {
248            self.error_at(at, "the map has no `name`: add `/// name = '…'`");
249        }
250    }
251
252    /// The parameters a group declares, as (mode, name, type).
253    fn signature(group: &ast::Group) -> Vec<(Mode, String, Option<String>)> {
254        group
255            .parameters()
256            .map(|parameter| {
257                let mode = match parameter.mode() {
258                    Some(ParameterMode::Target) => Mode::Target,
259                    _ => Mode::Source,
260                };
261                let type_name = parameter.type_annotation().and_then(|t| t.type_name());
262                (mode, parameter.name().unwrap_or_default(), type_name)
263            })
264            .collect()
265    }
266
267    /// A group that extends another SHALL have the other's parameters, by mode, name and type
268    /// (fml.md, "Groups"); a default group SHALL have a typed source and a typed target, in that
269    /// order.
270    fn group_signatures(&mut self) {
271        let groups: Vec<ast::Group> = self.file.groups().collect();
272        for group in &groups {
273            let own = Self::signature(group);
274            if let Some(base) = group
275                .extends()
276                .and_then(|name| self.groups.get(&name).cloned())
277            {
278                let base_name = base.name().unwrap_or_default();
279                for (mode, name, type_name) in Self::signature(&base) {
280                    let matching = own.iter().find(|(_, own_name, _)| *own_name == name);
281                    let problem = match matching {
282                        None => Some(format!("has no parameter `{name}`")),
283                        Some((own_mode, _, _)) if *own_mode != mode => {
284                            Some(format!("has `{name}` with another mode"))
285                        }
286                        Some((_, _, own_type))
287                            if type_name.is_some()
288                                && own_type.is_some()
289                                && *own_type != type_name =>
290                        {
291                            Some(format!(
292                                "has `{name}` as `{}`, not `{}`",
293                                own_type.as_deref().unwrap_or_default(),
294                                type_name.as_deref().unwrap_or_default()
295                            ))
296                        }
297                        _ => None,
298                    };
299                    if let Some(problem) = problem {
300                        self.error(
301                            group.syntax(),
302                            format!(
303                                "group `{}` extends `{base_name}` but {problem}; it needs the parameters of `{base_name}`",
304                                group.name().unwrap_or_default()
305                            ),
306                        );
307                    }
308                }
309            }
310            if group.type_mode().is_some() {
311                let valid = matches!(
312                    own.as_slice(),
313                    [(Mode::Source, _, Some(_)), (Mode::Target, _, Some(_))]
314                );
315                if !valid {
316                    self.warning(
317                        group.syntax(),
318                        format!(
319                            "default group `{}` needs two typed parameters, a source and then a target; it is not used as a default",
320                            group.name().unwrap_or_default()
321                        ),
322                    );
323                }
324            }
325        }
326    }
327
328    /// Rule names given in the map must be unique within it (fml.md, "Transform Rules").
329    fn rule_names(&mut self) {
330        let mut rules: Vec<ast::Rule> = Vec::new();
331        let mut pending: Vec<ast::Rule> = self
332            .file
333            .groups()
334            .flat_map(|g| g.rules().collect::<Vec<_>>())
335            .collect();
336        while let Some(rule) = pending.pop() {
337            if let Some(block) = rule.dependent().and_then(|d| d.rules()) {
338                pending.extend(block.rules());
339            }
340            rules.push(rule);
341        }
342        rules.sort_by_key(|rule| rule.syntax().text_range().start());
343        let mut seen: HashSet<String> = HashSet::new();
344        for rule in rules {
345            let Some(name) = rule.name() else {
346                continue;
347            };
348            if !seen.insert(name.clone()) {
349                self.warning(
350                    rule.syntax(),
351                    format!("rule name `{name}` is used twice in the map"),
352                );
353            }
354        }
355    }
356
357    /// The declared type of a group parameter: a `uses` alias, the name of a used structure, or a
358    /// core type name.
359    fn parameter_type(&mut self, parameter: &ast::Parameter) -> Option<Type> {
360        let annotation = parameter.type_annotation()?;
361        let name = annotation.type_name()?;
362        if let Some(url) = self.type_names.get(&name).cloned() {
363            return Some(self.types().structure(&url));
364        }
365        let url = type_url(&name, "http://hl7.org/fhir/StructureDefinition/");
366        if let Some(structure) = self.definitions.structure(&url) {
367            return Some(Type::Node {
368                structure,
369                path: Vec::new(),
370            });
371        }
372        if self.definitions.covers(&url) {
373            self.error(annotation.syntax(), format!("unknown type `{name}`"));
374        }
375        Some(Type::Unknown)
376    }
377
378    // ---- Groups -----------------------------------------------------------------------------
379
380    /// Checks a group with argument types: declared types, or for untyped parameters the types
381    /// inferred at a call site (`None` when not known).
382    fn check_group(&mut self, group: &ast::Group, arguments: Vec<Option<Type>>, depth: usize) {
383        let name = group.name().unwrap_or_default();
384        let parameters: Vec<ast::Parameter> = group.parameters().collect();
385        let mut scope = Scope::default();
386        for constant in self.file.constants() {
387            if let Some(constant_name) = constant.name() {
388                scope.define(
389                    constant_name,
390                    Variable {
391                        ty: Type::Unknown,
392                        mode: Mode::Source,
393                    },
394                );
395            }
396        }
397        scope.push();
398        let mut key = Vec::new();
399        for (index, parameter) in parameters.iter().enumerate() {
400            let declared = self.parameter_type(parameter);
401            let ty = declared
402                .or_else(|| arguments.get(index).cloned().flatten())
403                .unwrap_or(Type::Unknown);
404            key.push(ty.key());
405            let mode = match parameter.mode() {
406                Some(ParameterMode::Target) => Mode::Target,
407                _ => Mode::Source,
408            };
409            if let Some(parameter_name) = parameter.name() {
410                scope.define(parameter_name, Variable { ty, mode });
411            }
412        }
413        if !self.checked.insert((name, key)) || depth > MAX_CALL_DEPTH {
414            return;
415        }
416        if let Some(body) = group.body() {
417            for rule in body.rules() {
418                self.check_rule(&rule, &mut scope, depth);
419            }
420        }
421    }
422
423    // ---- Rules ------------------------------------------------------------------------------
424
425    fn check_rule(&mut self, rule: &ast::Rule, outer: &mut Scope, depth: usize) {
426        outer.push();
427        for source in rule.sources() {
428            self.check_source(&source, outer);
429        }
430        let fields = rule.identity_fields();
431        let targets: Vec<ast::Target> = rule.targets().collect();
432        if !fields.is_empty() {
433            self.check_identity(rule, &fields, outer);
434        }
435        for target in &targets {
436            self.check_target(target, outer);
437        }
438        if let Some(dependent) = rule.dependent() {
439            for invocation in dependent.invocations() {
440                self.check_call(&invocation, outer, depth);
441            }
442            if let Some(block) = dependent.rules() {
443                for nested in block.rules() {
444                    self.check_rule(&nested, outer, depth);
445                }
446            }
447        }
448        outer.pop();
449    }
450
451    fn check_source(&mut self, source: &ast::Source, scope: &mut Scope) {
452        let Some(path) = source.path() else { return };
453        let segments = path.segments();
454        let Some(context) = segments.first() else {
455            return;
456        };
457        let Some(variable) = scope.get(context).cloned() else {
458            self.error_at(
459                segment_range(&path, 0),
460                format!("unknown variable `{context}`"),
461            );
462            return;
463        };
464        let annotation = source.type_annotation();
465        let annotated = annotation.as_ref().and_then(|a| a.type_name());
466        let ty = self.walk(
467            &path,
468            variable.ty,
469            annotated.as_deref(),
470            annotation.as_ref(),
471        );
472        if let Some(default) = source.default_value().and_then(|d| d.expression()) {
473            self.check_expression(&default, &Type::Unknown, scope);
474        }
475        if let Some(alias) = source.alias() {
476            scope.define(
477                alias,
478                Variable {
479                    ty: ty.clone(),
480                    mode: Mode::Source,
481                },
482            );
483        }
484        // `where`, `check` and `log` see each item as the focus, and its variable.
485        for clause in [
486            source.where_clause(),
487            source.check_clause(),
488            source.log_clause(),
489        ]
490        .into_iter()
491        .flatten()
492        {
493            self.check_expression(&clause, &ty, scope);
494        }
495    }
496
497    /// `src -> tgt : a, b;`: each field exists on both sides.
498    fn check_identity(&mut self, rule: &ast::Rule, fields: &[String], scope: &Scope) {
499        let source = rule.sources().next().and_then(|s| s.path());
500        let target = rule.targets().next().and_then(|t| t.path());
501        for path in [source, target].into_iter().flatten() {
502            let Some(variable) = path.segments().first().and_then(|c| scope.get(c)).cloned() else {
503                continue;
504            };
505            for field in fields {
506                if let Lookup::Missing = self.types().lookup(&variable.ty, field) {
507                    let message = format!("`{}` has no element `{field}`", variable.ty.display());
508                    self.error(rule.syntax(), message);
509                }
510            }
511        }
512    }
513
514    fn check_target(&mut self, target: &ast::Target, scope: &mut Scope) {
515        let mut ty = Type::Unknown;
516        if let Some(path) = target.path() {
517            let segments = path.segments();
518            let context = &segments[0];
519            match scope.get(context).cloned() {
520                None => {
521                    self.error_at(
522                        segment_range(&path, 0),
523                        format!("unknown variable `{context}`"),
524                    );
525                }
526                Some(variable) => {
527                    if variable.mode == Mode::Source {
528                        self.error_at(
529                            segment_range(&path, 0),
530                            format!("`{context}` is a source variable; a target writes into a target variable"),
531                        );
532                    }
533                    ty = self.walk(&path, variable.ty, None, None);
534                }
535            }
536        }
537        if let Some(transform) = target.transform() {
538            ty = self.check_transform(&transform, ty, scope);
539        }
540        if let Some(alias) = target.alias() {
541            scope.define(
542                alias,
543                Variable {
544                    ty,
545                    mode: Mode::Target,
546                },
547            );
548        }
549    }
550
551    /// Checks a transform; returns the type of the value it produces where that is known.
552    fn check_transform(
553        &mut self,
554        transform: &TransformValue,
555        element: Type,
556        scope: &Scope,
557    ) -> Type {
558        match transform {
559            TransformValue::Name(name) => {
560                let segments = name.segments();
561                match scope.get(&segments[0]).cloned() {
562                    Some(variable) if segments.len() == 1 => {
563                        if matches!(element, Type::Unknown) {
564                            variable.ty
565                        } else {
566                            element
567                        }
568                    }
569                    Some(variable) => {
570                        self.walk(name, variable.ty, None, None);
571                        element
572                    }
573                    None => {
574                        self.error_at(
575                            segment_range(name, 0),
576                            format!("unknown variable `{}`", segments[0]),
577                        );
578                        element
579                    }
580                }
581            }
582            TransformValue::Invocation(invocation) => {
583                self.check_invocation(invocation, element, scope)
584            }
585            TransformValue::Expression(expression) => {
586                self.check_expression(expression, &Type::Unknown, scope);
587                element
588            }
589            TransformValue::Literal(_) => element,
590        }
591    }
592
593    fn check_invocation(&mut self, invocation: &Invocation, element: Type, scope: &Scope) -> Type {
594        let name = invocation.name().unwrap_or_default();
595        let arguments: Vec<Expr> = invocation.arguments().collect();
596        let Some(&(_, min, max)) = TRANSFORMS.iter().find(|(n, _, _)| *n == name) else {
597            self.error(invocation.syntax(), format!("unknown transform `{name}`"));
598            return element;
599        };
600        if arguments.len() < min || max.is_some_and(|max| arguments.len() > max) {
601            let expected = match max {
602                Some(max) if max == min => format!("{min}"),
603                Some(max) => format!("{min} to {max}"),
604                None => format!("at least {min}"),
605            };
606            self.error(
607                invocation.syntax(),
608                format!(
609                    "`{name}` takes {expected} argument(s), found {}",
610                    arguments.len()
611                ),
612            );
613        }
614        if name == "evaluate"
615            && let [focus, expression] = arguments.as_slice()
616        {
617            let focus = scope
618                .get(&focus.text())
619                .map(|v| v.ty.clone())
620                .unwrap_or(Type::Unknown);
621            self.check_expression(expression, &focus, scope);
622        }
623        for (index, argument) in arguments.iter().enumerate() {
624            let is_expression = name == "evaluate" && index == 1;
625            if !is_expression && argument.syntax().kind() == SyntaxKind::NameRef {
626                let variable = argument.text();
627                if scope.get(&variable).is_none() {
628                    self.error(argument.syntax(), format!("unknown variable `{variable}`"));
629                }
630            }
631        }
632        if name == "create"
633            && let Some(type_name) = arguments.first().and_then(string_literal)
634        {
635            return self.created_type(&type_name, &element, &arguments[0]);
636        }
637        element
638    }
639
640    /// The type `create('Type')` makes, checked against the element it is assigned to.
641    fn created_type(&mut self, type_name: &str, element: &Type, at: &Expr) -> Type {
642        let url = match (self.type_names.get(type_name), element) {
643            (Some(url), _) => url.clone(),
644            (None, Type::Node { structure, .. }) => type_url(type_name, &structure.url),
645            // The element's type, and so the FHIR release the created type belongs to, is unknown.
646            (None, Type::Unknown) => return Type::Unknown,
647        };
648        match self.definitions.structure(&url) {
649            Some(structure) => {
650                if structure.is_abstract {
651                    self.error(
652                        at.syntax(),
653                        format!("`{type_name}` is abstract and cannot be created"),
654                    );
655                }
656                Type::Node {
657                    structure,
658                    path: Vec::new(),
659                }
660            }
661            None => {
662                if self.definitions.covers(&url) {
663                    self.error(at.syntax(), format!("unknown type `{type_name}`"));
664                }
665                Type::Unknown
666            }
667        }
668    }
669
670    /// `then Group(a, b)`: the group exists, takes that many arguments, and is checked with the
671    /// argument types when its parameters are untyped.
672    fn check_call(&mut self, invocation: &Invocation, scope: &Scope, depth: usize) {
673        let name = invocation.name().unwrap_or_default();
674        let arguments: Vec<Expr> = invocation.arguments().collect();
675        let mut types = Vec::new();
676        for argument in &arguments {
677            if argument.syntax().kind() == SyntaxKind::NameRef {
678                let variable = argument.text();
679                match scope.get(&variable) {
680                    Some(v) => types.push(Some(v.ty.clone())),
681                    None => {
682                        self.error(argument.syntax(), format!("unknown variable `{variable}`"));
683                        types.push(None);
684                    }
685                }
686            } else {
687                types.push(None);
688            }
689        }
690        let Some(group) = self.groups.get(&name).cloned() else {
691            if !self.has_imports {
692                self.error(
693                    invocation.syntax(),
694                    format!("group `{name}` is not declared and nothing is imported"),
695                );
696            }
697            return;
698        };
699        let parameters = group.parameters().count();
700        if parameters != arguments.len() {
701            self.error(
702                invocation.syntax(),
703                format!(
704                    "group `{name}` takes {parameters} argument(s), found {}",
705                    arguments.len()
706                ),
707            );
708            return;
709        }
710        self.check_group(&group, types, depth + 1);
711    }
712
713    // ---- Element paths ----------------------------------------------------------------------
714
715    // ---- FHIRPath ---------------------------------------------------------------------------
716
717    /// Checks the element paths of a FHIRPath expression: a name that is not a variable is an
718    /// element of `focus`. Problems are warnings, since a wrong path gives an empty result rather
719    /// than an error, but it is nearly always a mistake.
720    fn check_expression(&mut self, expression: &Expr, focus: &Type, scope: &Scope) {
721        self.expression_type(expression.syntax(), focus, scope);
722    }
723
724    /// The type an expression gives, where it is known, reporting missing elements on the way.
725    fn expression_type(&mut self, node: &SyntaxNode, focus: &Type, scope: &Scope) -> Type {
726        match node.kind() {
727            SyntaxKind::NameRef => {
728                let Some(token) = node.first_token() else {
729                    return Type::Unknown;
730                };
731                if token.kind() == SyntaxKind::DollarIdent {
732                    return if token.text() == "$this" {
733                        focus.clone()
734                    } else {
735                        Type::Unknown
736                    };
737                }
738                let name = ast::name_text(&token);
739                if let Some(variable) = scope.get(&name) {
740                    return variable.ty.clone();
741                }
742                // `Patient.name` on a Patient: the type name selects the focus itself.
743                if focus.fhir_type().as_deref() == Some(name.as_str()) {
744                    return focus.clone();
745                }
746                self.element_of(focus, &name, node)
747            }
748            SyntaxKind::MemberExpr => {
749                let mut parts = node.children();
750                let (Some(left), Some(right)) = (parts.next(), parts.next()) else {
751                    return Type::Unknown;
752                };
753                let input = self.expression_type(&left, focus, scope);
754                match right.kind() {
755                    SyntaxKind::NameRef => match right.first_token() {
756                        Some(token) if token.kind() != SyntaxKind::DollarIdent => {
757                            self.element_of(&input, &ast::name_text(&token), &right)
758                        }
759                        _ => Type::Unknown,
760                    },
761                    SyntaxKind::CallExpr => self.call_type(&right, &input, scope),
762                    _ => Type::Unknown,
763                }
764            }
765            SyntaxKind::CallExpr => self.call_type(node, focus, scope),
766            SyntaxKind::ParenExpr => match node.children().next() {
767                Some(inner) => self.expression_type(&inner, focus, scope),
768                None => Type::Unknown,
769            },
770            SyntaxKind::IndexExpr => {
771                let mut parts = node.children();
772                let input = match parts.next() {
773                    Some(left) => self.expression_type(&left, focus, scope),
774                    None => Type::Unknown,
775                };
776                if let Some(index) = parts.next() {
777                    self.expression_type(&index, focus, scope);
778                }
779                input
780            }
781            SyntaxKind::BinaryExpr | SyntaxKind::PrefixExpr | SyntaxKind::TypeExpr => {
782                for child in node.children().filter(|c| Expr::can_cast(c.kind())) {
783                    self.expression_type(&child, focus, scope);
784                }
785                Type::Unknown
786            }
787            _ => Type::Unknown,
788        }
789    }
790
791    fn element_of(&mut self, ty: &Type, name: &str, at: &SyntaxNode) -> Type {
792        match self.types().lookup(ty, name) {
793            Lookup::Found(found) => self.types().element_type(&found, None),
794            Lookup::Missing => {
795                let message = format!("`{}` has no element `{name}`", ty.display());
796                self.report(Severity::Warning, trimmed_range(at), message);
797                Type::Unknown
798            }
799            Lookup::Unknown => Type::Unknown,
800        }
801    }
802
803    /// A function call on `input`. The iterating functions see each input item as the focus of
804    /// their argument; the arguments of the others are left unchecked.
805    fn call_type(&mut self, call: &SyntaxNode, input: &Type, scope: &Scope) -> Type {
806        let name = call
807            .first_token()
808            .map(|t| t.text().to_owned())
809            .unwrap_or_default();
810        let arguments: Vec<SyntaxNode> = call
811            .children()
812            .find(|n| n.kind() == SyntaxKind::ArgumentList)
813            .map(|list| {
814                list.children()
815                    .filter(|c| Expr::can_cast(c.kind()))
816                    .collect()
817            })
818            .unwrap_or_default();
819        let iterating = matches!(
820            name.as_str(),
821            "where" | "select" | "exists" | "all" | "repeat" | "sort" | "aggregate"
822        );
823        let argument_focus = if iterating {
824            input.clone()
825        } else {
826            Type::Unknown
827        };
828        let mut last = Type::Unknown;
829        for argument in &arguments {
830            last = self.expression_type(argument, &argument_focus, scope);
831        }
832        match name.as_str() {
833            "where" | "first" | "last" | "tail" | "skip" | "take" | "single" | "distinct"
834            | "trace" | "repeat" | "sort" => input.clone(),
835            "select" => last,
836            _ => Type::Unknown,
837        }
838    }
839
840    /// Follows `context.a.b` from the context variable's type, reporting missing elements, and
841    /// returns the type at the end. `annotation` is the source's `: Type`, which chooses a type of
842    /// a choice element.
843    fn walk(
844        &mut self,
845        path: &QualifiedName,
846        mut ty: Type,
847        annotation: Option<&str>,
848        annotation_node: Option<&ast::TypeAnnotation>,
849    ) -> Type {
850        let segments = path.segments();
851        for (index, segment) in segments.iter().enumerate().skip(1) {
852            let last = index == segments.len() - 1;
853            let segment = segment.trim_end_matches("[x]");
854            ty = match self.types().lookup(&ty, segment) {
855                Lookup::Unknown => return Type::Unknown,
856                Lookup::Missing => {
857                    let message = format!("`{}` has no element `{segment}`", ty.display());
858                    self.error_at(segment_range(path, index), message);
859                    return Type::Unknown;
860                }
861                Lookup::Found(found) => {
862                    let chosen = if last { annotation } else { None };
863                    if let (Some(chosen), Some(node)) = (chosen, annotation_node)
864                        && !found.element.types.is_empty()
865                        && !self
866                            .types()
867                            .is_one_of(chosen, &found.element.types, &found.owner.url)
868                    {
869                        self.error(
870                            node.syntax(),
871                            format!(
872                                "`{chosen}` is not a type of `{}.{segment}`; it is {}",
873                                ty.display(),
874                                found.element.types.join(", ")
875                            ),
876                        );
877                    }
878                    self.types().element_type(&found, chosen)
879                }
880            };
881        }
882        ty
883    }
884}
885
886/// Transforms with their argument counts (minimum, maximum).
887const TRANSFORMS: &[(&str, usize, Option<usize>)] = &[
888    ("create", 0, Some(1)),
889    ("copy", 1, Some(1)),
890    ("truncate", 2, Some(2)),
891    ("escape", 2, Some(3)),
892    ("cast", 1, Some(2)),
893    ("append", 1, None),
894    ("translate", 3, Some(3)),
895    ("reference", 1, Some(1)),
896    ("dateOp", 2, Some(3)),
897    ("uuid", 0, Some(0)),
898    ("pointer", 1, Some(1)),
899    ("evaluate", 1, Some(2)),
900    ("cc", 1, Some(3)),
901    ("c", 2, Some(3)),
902    ("qty", 1, Some(4)),
903    ("id", 2, Some(3)),
904    ("cp", 1, Some(2)),
905    ("toDateTime", 1, Some(2)),
906    ("unixToDateTime", 1, Some(2)),
907    ("toDate", 1, Some(2)),
908    ("unixToDate", 1, Some(2)),
909    ("toTime", 1, Some(2)),
910    ("unixToTime", 1, Some(2)),
911];
912
913/// Variables by name, in nested frames: group parameters, then one frame per rule.
914#[derive(Debug, Default)]
915struct Scope {
916    frames: Vec<HashMap<String, Variable>>,
917}
918
919impl Scope {
920    fn push(&mut self) {
921        self.frames.push(HashMap::new());
922    }
923
924    fn pop(&mut self) {
925        self.frames.pop();
926    }
927
928    fn define(&mut self, name: String, variable: Variable) {
929        if self.frames.is_empty() {
930            self.frames.push(HashMap::new());
931        }
932        self.frames.last_mut().unwrap().insert(name, variable);
933    }
934
935    fn get(&self, name: &str) -> Option<&Variable> {
936        self.frames.iter().rev().find_map(|frame| frame.get(name))
937    }
938}
939
940/// A string literal argument's value.
941fn string_literal(expression: &Expr) -> Option<String> {
942    let literal = ast::Literal::cast(expression.syntax().clone())?;
943    matches!(
944        literal.kind()?,
945        SyntaxKind::SingleQuotedString | SyntaxKind::DoubleQuotedString
946    )
947    .then(|| literal.value())
948    .flatten()
949}
950
951/// The range of the `index`-th name in `a.b.c`.
952fn segment_range(path: &QualifiedName, index: usize) -> TextRange {
953    path.syntax()
954        .children_with_tokens()
955        .filter_map(|e| e.into_token())
956        .filter(|t| matches!(t.kind(), SyntaxKind::Ident | SyntaxKind::DelimitedIdent))
957        .nth(index)
958        .map(|t| t.text_range())
959        .unwrap_or_else(|| trimmed_range(path.syntax()))
960}
961
962fn trimmed_range(node: &SyntaxNode) -> TextRange {
963    let mut tokens = node
964        .descendants_with_tokens()
965        .filter_map(|e| e.into_token())
966        .filter(|t| !t.kind().is_trivia());
967    match tokens.next() {
968        Some(first) => {
969            let end = tokens
970                .last()
971                .map_or(first.text_range().end(), |t| t.text_range().end());
972            TextRange::new(first.text_range().start(), end)
973        }
974        None => node.text_range(),
975    }
976}