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 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 fn created_type(&mut self, type_name: &str, element: &Type, at: &Expr) -> Type {
792 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 (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 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 fn check_expression(&mut self, expression: &Expr, focus: &Type, scope: &Scope) {
898 self.expression_type(expression.syntax(), focus, scope);
899 }
900
901 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 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 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 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 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 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
1093const 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#[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
1147fn 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
1157fn 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
1168fn 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
1179fn 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}