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