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 name == "dateOp" {
742            self.warning(
743                invocation.syntax(),
744                "`dateOp` is not defined: neither the Mapping Language IG nor the StructureMap transform codes say what its parameters are or what it does, so no engine can run it; use toDate, toDateTime, toTime or a FHIRPath expression",
745            );
746        }
747        if arguments.len() < min || max.is_some_and(|max| arguments.len() > max) {
748            let expected = match max {
749                Some(max) if max == min => format!("{min}"),
750                Some(max) => format!("{min} to {max}"),
751                None => format!("at least {min}"),
752            };
753            self.error(
754                invocation.syntax(),
755                format!(
756                    "`{name}` takes {expected} argument(s), found {}",
757                    arguments.len()
758                ),
759            );
760        }
761        if name == "evaluate"
762            && let [focus, expression] = arguments.as_slice()
763        {
764            let focus = scope
765                .get(&focus.text())
766                .map(|v| v.ty.clone())
767                .unwrap_or(Type::Unknown);
768            self.check_expression(expression, &focus, scope);
769        }
770        for (index, argument) in arguments.iter().enumerate() {
771            let is_expression = name == "evaluate" && index == 1;
772            if !is_expression && argument.syntax().kind() == SyntaxKind::NameRef {
773                let name = argument.text();
774                match scope.get(&name).cloned() {
775                    Some(variable) => {
776                        self.refer_variable(trimmed_range(argument.syntax()), &variable)
777                    }
778                    None => self.error(argument.syntax(), format!("unknown variable `{name}`")),
779                }
780            }
781        }
782        if name == "create"
783            && let Some(type_name) = arguments.first().and_then(string_literal)
784        {
785            let created = self.created_type(&type_name, &element, &arguments[0]);
786            self.refer(
787                trimmed_range(arguments[0].syntax()),
788                Referent::Type(created.clone()),
789                &created,
790            );
791            return created;
792        }
793        element
794    }
795
796    /// The type `create('Type')` makes, checked against the element it is assigned to.
797    fn created_type(&mut self, type_name: &str, element: &Type, at: &Expr) -> Type {
798        // In a logical model, a type next to the element's own (`create('CE')` into a `CE`
799        // element, or a subtype of it), named as the engine names it.
800        if let (None, Type::Node { structure, .. }) = (self.type_names.get(type_name), element)
801            && structure.kind == Kind::Logical
802            && let Some((prefix, _)) = structure.url.rsplit_once('/')
803        {
804            for id in [type_name.to_owned(), type_name.replace('_', "-")] {
805                if let Some(found) = self.definitions.structure(&format!("{prefix}/{id}"))
806                    && found.name == type_name
807                {
808                    return Type::Node {
809                        structure: found,
810                        path: Vec::new(),
811                    };
812                }
813            }
814        }
815        let url = match (self.type_names.get(type_name), element) {
816            (Some(url), _) => url.clone(),
817            (None, Type::Node { structure, .. }) => type_url(type_name, &structure.url),
818            // The element's type, and so the FHIR release the created type belongs to, is unknown.
819            (None, Type::Unknown) => return Type::Unknown,
820        };
821        match self.definitions.structure(&url) {
822            Some(structure) => {
823                if structure.is_abstract {
824                    self.error(
825                        at.syntax(),
826                        format!("`{type_name}` is abstract and cannot be created"),
827                    );
828                }
829                Type::Node {
830                    structure,
831                    path: Vec::new(),
832                }
833            }
834            None => {
835                if self.definitions.covers(&url) {
836                    self.error(at.syntax(), format!("unknown type `{type_name}`"));
837                }
838                Type::Unknown
839            }
840        }
841    }
842
843    /// `then Group(a, b)`: the group exists, takes that many arguments, and is checked with the
844    /// argument types when its parameters are untyped.
845    fn check_call(&mut self, invocation: &Invocation, scope: &Scope, depth: usize) {
846        let name = invocation.name().unwrap_or_default();
847        let arguments: Vec<Expr> = invocation.arguments().collect();
848        let mut types = Vec::new();
849        for argument in &arguments {
850            if argument.syntax().kind() == SyntaxKind::NameRef {
851                let name = argument.text();
852                match scope.get(&name).cloned() {
853                    Some(variable) => {
854                        self.refer_variable(trimmed_range(argument.syntax()), &variable);
855                        types.push(Some(variable.ty));
856                    }
857                    None => {
858                        self.error(argument.syntax(), format!("unknown variable `{name}`"));
859                        types.push(None);
860                    }
861                }
862            } else {
863                types.push(None);
864            }
865        }
866        if let Some(token) = invocation.name_token() {
867            self.refer(
868                token.text_range(),
869                Referent::Group(name.clone()),
870                &Type::Unknown,
871            );
872        }
873        let Some(group) = self.groups.get(&name).cloned() else {
874            if !self.has_imports {
875                self.error(
876                    invocation.syntax(),
877                    format!("group `{name}` is not declared and nothing is imported"),
878                );
879            }
880            return;
881        };
882        let parameters = group.parameters().count();
883        if parameters != arguments.len() {
884            self.error(
885                invocation.syntax(),
886                format!(
887                    "group `{name}` takes {parameters} argument(s), found {}",
888                    arguments.len()
889                ),
890            );
891            return;
892        }
893        self.check_group(&group, types, depth + 1);
894    }
895
896    // ---- Element paths ----------------------------------------------------------------------
897
898    // ---- FHIRPath ---------------------------------------------------------------------------
899
900    /// Checks the element paths of a FHIRPath expression: a name that is not a variable is an
901    /// element of `focus`. Problems are warnings, since a wrong path gives an empty result rather
902    /// than an error, but it is nearly always a mistake.
903    fn check_expression(&mut self, expression: &Expr, focus: &Type, scope: &Scope) {
904        self.expression_type(expression.syntax(), focus, scope);
905    }
906
907    /// The type an expression gives, where it is known, reporting missing elements on the way.
908    fn expression_type(&mut self, node: &SyntaxNode, focus: &Type, scope: &Scope) -> Type {
909        match node.kind() {
910            SyntaxKind::NameRef => {
911                let Some(token) = node.first_token() else {
912                    return Type::Unknown;
913                };
914                if token.kind() == SyntaxKind::DollarIdent {
915                    return if token.text() == "$this" {
916                        focus.clone()
917                    } else {
918                        Type::Unknown
919                    };
920                }
921                let name = ast::name_text(&token);
922                if let Some(variable) = scope.get(&name).cloned() {
923                    self.refer_variable(trimmed_range(node), &variable);
924                    // A variable hides an element of the focus with the same name: almost always
925                    // a slip (`src.code as code where (code.exists())` tests the variable).
926                    // A primitive's own `value` is the primitive: no difference there.
927                    if !(focus.is_primitive() && name == "value")
928                        && matches!(self.types().lookup(focus, &name), Lookup::Found(_))
929                    {
930                        let message = format!(
931                            "`{name}` here is the variable `{name}`, not the element `{name}` of the focus; rename the variable, or write `$this.{name}` for the element"
932                        );
933                        self.report(Severity::Warning, trimmed_range(node), message);
934                    }
935                    return variable.ty;
936                }
937                // `Patient.name` on a Patient: the type name selects the focus itself.
938                if focus.fhir_type().as_deref() == Some(name.as_str()) {
939                    return focus.clone();
940                }
941                self.element_of(focus, &name, node)
942            }
943            SyntaxKind::MemberExpr => {
944                let mut parts = node.children();
945                let (Some(left), Some(right)) = (parts.next(), parts.next()) else {
946                    return Type::Unknown;
947                };
948                let input = self.expression_type(&left, focus, scope);
949                match right.kind() {
950                    SyntaxKind::NameRef => match right.first_token() {
951                        Some(token) if token.kind() != SyntaxKind::DollarIdent => {
952                            self.element_of(&input, &ast::name_text(&token), &right)
953                        }
954                        _ => Type::Unknown,
955                    },
956                    SyntaxKind::CallExpr => self.call_type(&right, &input, scope),
957                    _ => Type::Unknown,
958                }
959            }
960            SyntaxKind::CallExpr => self.call_type(node, focus, scope),
961            SyntaxKind::ParenExpr => match node.children().next() {
962                Some(inner) => self.expression_type(&inner, focus, scope),
963                None => Type::Unknown,
964            },
965            SyntaxKind::IndexExpr => {
966                let mut parts = node.children();
967                let input = match parts.next() {
968                    Some(left) => self.expression_type(&left, focus, scope),
969                    None => Type::Unknown,
970                };
971                if let Some(index) = parts.next() {
972                    self.expression_type(&index, focus, scope);
973                }
974                input
975            }
976            SyntaxKind::BinaryExpr | SyntaxKind::PrefixExpr | SyntaxKind::TypeExpr => {
977                for child in node.children().filter(|c| Expr::can_cast(c.kind())) {
978                    self.expression_type(&child, focus, scope);
979                }
980                Type::Unknown
981            }
982            _ => Type::Unknown,
983        }
984    }
985
986    fn element_of(&mut self, ty: &Type, name: &str, at: &SyntaxNode) -> Type {
987        match self.types().lookup(ty, name) {
988            Lookup::Found(found) => {
989                let element = self.types().element_type(&found, None);
990                self.refer(trimmed_range(at), Referent::Element(found), &element);
991                element
992            }
993            Lookup::Missing => {
994                let message = format!("`{}` has no element `{name}`", ty.display());
995                self.report(Severity::Warning, trimmed_range(at), message);
996                Type::Unknown
997            }
998            Lookup::Unknown => Type::Unknown,
999        }
1000    }
1001
1002    /// A function call on `input`. The iterating functions see each input item as the focus of
1003    /// their argument; the arguments of the others are left unchecked.
1004    fn call_type(&mut self, call: &SyntaxNode, input: &Type, scope: &Scope) -> Type {
1005        let name = call
1006            .first_token()
1007            .map(|t| t.text().to_owned())
1008            .unwrap_or_default();
1009        let arguments: Vec<SyntaxNode> = call
1010            .children()
1011            .find(|n| n.kind() == SyntaxKind::ArgumentList)
1012            .map(|list| {
1013                list.children()
1014                    .filter(|c| Expr::can_cast(c.kind()))
1015                    .collect()
1016            })
1017            .unwrap_or_default();
1018        let iterating = matches!(
1019            name.as_str(),
1020            "where" | "select" | "exists" | "all" | "repeat" | "sort" | "aggregate"
1021        );
1022        let argument_focus = if iterating {
1023            input.clone()
1024        } else {
1025            Type::Unknown
1026        };
1027        let mut last = Type::Unknown;
1028        for argument in &arguments {
1029            last = self.expression_type(argument, &argument_focus, scope);
1030        }
1031        match name.as_str() {
1032            "where" | "first" | "last" | "tail" | "skip" | "take" | "single" | "distinct"
1033            | "trace" | "repeat" | "sort" => input.clone(),
1034            "select" => last,
1035            _ => Type::Unknown,
1036        }
1037    }
1038
1039    /// Follows `context.a.b` from the context variable's type, reporting missing elements, and
1040    /// returns the type at the end. `annotation` is the source's `: Type`, which chooses a type of
1041    /// a choice element.
1042    fn walk(
1043        &mut self,
1044        path: &QualifiedName,
1045        mut ty: Type,
1046        annotation: Option<&str>,
1047        annotation_node: Option<&ast::TypeAnnotation>,
1048    ) -> Type {
1049        let segments = path.segments();
1050        for (index, segment) in segments.iter().enumerate().skip(1) {
1051            let last = index == segments.len() - 1;
1052            let segment = segment.trim_end_matches("[x]");
1053            ty = match self.types().lookup(&ty, segment) {
1054                Lookup::Unknown => return Type::Unknown,
1055                Lookup::Missing => {
1056                    let message = format!("`{}` has no element `{segment}`", ty.display());
1057                    self.error_at(segment_range(path, index), message);
1058                    return Type::Unknown;
1059                }
1060                Lookup::Found(found) => {
1061                    // A `uses` alias names its structure (a profile, say), as in the engine.
1062                    let resolved = annotation.map(|name| {
1063                        self.type_names
1064                            .get(name)
1065                            .cloned()
1066                            .unwrap_or_else(|| name.to_owned())
1067                    });
1068                    let chosen = if last { resolved.as_deref() } else { None };
1069                    if let (Some(chosen), Some(node)) = (chosen, annotation_node)
1070                        && !found.element.types.is_empty()
1071                        && !self
1072                            .types()
1073                            .is_one_of(chosen, &found.element.types, &found.owner.url)
1074                    {
1075                        self.error(
1076                            node.syntax(),
1077                            format!(
1078                                "`{}` is not a type of `{}.{segment}`; it is {}",
1079                                annotation.unwrap_or(chosen),
1080                                ty.display(),
1081                                found.element.types.join(", ")
1082                            ),
1083                        );
1084                    }
1085                    let element = self.types().element_type(&found, chosen);
1086                    self.refer(
1087                        segment_range(path, index),
1088                        Referent::Element(found),
1089                        &element,
1090                    );
1091                    element
1092                }
1093            };
1094        }
1095        ty
1096    }
1097}
1098
1099/// Transforms with their argument counts (minimum, maximum).
1100const TRANSFORMS: &[(&str, usize, Option<usize>)] = &[
1101    ("create", 0, Some(1)),
1102    ("copy", 1, Some(1)),
1103    ("truncate", 2, Some(2)),
1104    ("escape", 2, Some(3)),
1105    ("cast", 1, Some(2)),
1106    ("append", 1, None),
1107    ("translate", 3, Some(3)),
1108    ("reference", 1, Some(1)),
1109    ("dateOp", 2, Some(3)),
1110    ("uuid", 0, Some(0)),
1111    ("pointer", 1, Some(1)),
1112    ("evaluate", 1, Some(2)),
1113    ("cc", 1, Some(3)),
1114    ("c", 2, Some(3)),
1115    ("qty", 1, Some(4)),
1116    ("id", 2, Some(3)),
1117    ("cp", 1, Some(2)),
1118    ("toDateTime", 1, Some(2)),
1119    ("unixToDateTime", 1, Some(2)),
1120    ("toDate", 1, Some(2)),
1121    ("unixToDate", 1, Some(2)),
1122    ("toTime", 1, Some(2)),
1123    ("unixToTime", 1, Some(2)),
1124];
1125
1126/// Variables by name, in nested frames: group parameters, then one frame per rule.
1127#[derive(Debug, Default)]
1128struct Scope {
1129    frames: Vec<HashMap<String, Variable>>,
1130}
1131
1132impl Scope {
1133    fn push(&mut self) {
1134        self.frames.push(HashMap::new());
1135    }
1136
1137    fn pop(&mut self) {
1138        self.frames.pop();
1139    }
1140
1141    fn define(&mut self, name: String, variable: Variable) {
1142        if self.frames.is_empty() {
1143            self.frames.push(HashMap::new());
1144        }
1145        self.frames.last_mut().unwrap().insert(name, variable);
1146    }
1147
1148    fn get(&self, name: &str) -> Option<&Variable> {
1149        self.frames.iter().rev().find_map(|frame| frame.get(name))
1150    }
1151}
1152
1153/// The type name of a `: Type` annotation, without the colon.
1154fn annotation_range(annotation: &ast::TypeAnnotation) -> TextRange {
1155    annotation
1156        .syntax()
1157        .children()
1158        .next()
1159        .map(|name| trimmed_range(&name))
1160        .unwrap_or_else(|| trimmed_range(annotation.syntax()))
1161}
1162
1163/// Where an alias declared by `token` can be used: from the alias to the end of its rule.
1164fn visible_until(token: Option<&SyntaxToken>, rule_end: TextSize) -> TextRange {
1165    match token {
1166        Some(token) => TextRange::new(
1167            token.text_range().end(),
1168            rule_end.max(token.text_range().end()),
1169        ),
1170        None => TextRange::empty(rule_end),
1171    }
1172}
1173
1174/// A string literal argument's value.
1175fn string_literal(expression: &Expr) -> Option<String> {
1176    let literal = ast::Literal::cast(expression.syntax().clone())?;
1177    matches!(
1178        literal.kind()?,
1179        SyntaxKind::SingleQuotedString | SyntaxKind::DoubleQuotedString
1180    )
1181    .then(|| literal.value())
1182    .flatten()
1183}
1184
1185/// The range of the `index`-th name in `a.b.c`.
1186fn segment_range(path: &QualifiedName, index: usize) -> TextRange {
1187    path.syntax()
1188        .children_with_tokens()
1189        .filter_map(|e| e.into_token())
1190        .filter(|t| matches!(t.kind(), SyntaxKind::Ident | SyntaxKind::DelimitedIdent))
1191        .nth(index)
1192        .map(|t| t.text_range())
1193        .unwrap_or_else(|| trimmed_range(path.syntax()))
1194}
1195
1196fn trimmed_range(node: &SyntaxNode) -> TextRange {
1197    let mut tokens = node
1198        .descendants_with_tokens()
1199        .filter_map(|e| e.into_token())
1200        .filter(|t| !t.kind().is_trivia());
1201    match tokens.next() {
1202        Some(first) => {
1203            let end = tokens
1204                .last()
1205                .map_or(first.text_range().end(), |t| t.text_range().end());
1206            TextRange::new(first.text_range().start(), end)
1207        }
1208        None => node.text_range(),
1209    }
1210}