1use 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
15pub fn check(file: &ast::SourceFile, definitions: &dyn Definitions) -> Vec<Diagnostic> {
17 analyze(file, definitions).diagnostics
18}
19
20pub 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
36const 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
70const 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 binding: Option<usize>,
85}
86
87struct Checker<'a> {
88 file: &'a ast::SourceFile,
89 definitions: &'a dyn Definitions,
90 type_names: HashMap<String, String>,
92 groups: HashMap<String, ast::Group>,
93 has_imports: bool,
94 checked: HashSet<(String, Vec<String>)>,
96 reported: HashSet<(TextRange, String)>,
97 diagnostics: Vec<Diagnostic>,
98 bindings: Vec<Binding>,
99 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 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 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(¶meter))
237 .collect();
238 self.check_group(group, types, 0);
239 }
240 }
241
242 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 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 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 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 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 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 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 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 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 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 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 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 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 fn created_type(&mut self, type_name: &str, element: &Type, at: &Expr) -> Type {
798 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 (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 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 fn check_expression(&mut self, expression: &Expr, focus: &Type, scope: &Scope) {
904 self.expression_type(expression.syntax(), focus, scope);
905 }
906
907 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 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 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 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 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 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
1099const 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#[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
1153fn 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
1163fn 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
1174fn 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
1185fn 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}