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, TextRange};
10
11use crate::definitions::{Definitions, type_url};
12use crate::types::{Lookup, Type, Types};
13
14pub fn check(file: &ast::SourceFile, definitions: &dyn Definitions) -> Vec<Diagnostic> {
16 let mut checker = Checker::new(file, definitions);
17 checker.run();
18 let mut diagnostics = checker.diagnostics;
19 diagnostics.sort_by_key(|d| (d.range.start(), d.range.end()));
20 diagnostics
21}
22
23const MAX_CALL_DEPTH: usize = 32;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27enum Mode {
28 Source,
29 Target,
30}
31
32#[derive(Debug, Clone)]
33struct Variable {
34 ty: Type,
35 mode: Mode,
36}
37
38struct Checker<'a> {
39 file: &'a ast::SourceFile,
40 definitions: &'a dyn Definitions,
41 type_names: HashMap<String, String>,
43 groups: HashMap<String, ast::Group>,
44 has_imports: bool,
45 checked: HashSet<(String, Vec<String>)>,
47 reported: HashSet<(TextRange, String)>,
48 diagnostics: Vec<Diagnostic>,
49}
50
51impl<'a> Checker<'a> {
52 fn types(&self) -> Types<'a> {
53 Types::new(self.definitions)
54 }
55
56 fn new(file: &'a ast::SourceFile, definitions: &'a dyn Definitions) -> Self {
57 Self {
58 file,
59 definitions,
60 type_names: HashMap::new(),
61 groups: HashMap::new(),
62 has_imports: file.imports().next().is_some(),
63 checked: HashSet::new(),
64 reported: HashSet::new(),
65 diagnostics: Vec::new(),
66 }
67 }
68
69 fn report(&mut self, severity: Severity, range: TextRange, message: String) {
70 if self.reported.insert((range, message.clone())) {
71 self.diagnostics.push(Diagnostic {
72 severity,
73 range,
74 message,
75 });
76 }
77 }
78
79 fn error(&mut self, node: &SyntaxNode, message: impl Into<String>) {
80 self.report(Severity::Error, trimmed_range(node), message.into());
81 }
82
83 fn error_at(&mut self, range: TextRange, message: impl Into<String>) {
84 self.report(Severity::Error, range, message.into());
85 }
86
87 fn run(&mut self) {
88 self.declarations();
89 let groups: Vec<ast::Group> = self.file.groups().collect();
90 for group in &groups {
91 let types = group
92 .parameters()
93 .map(|parameter| self.parameter_type(¶meter))
94 .collect();
95 self.check_group(group, types, 0);
96 }
97 }
98
99 fn declarations(&mut self) {
102 for uses in self.file.uses() {
103 let Some(url) = uses.url().and_then(|u| u.value()) else {
104 continue;
105 };
106 match self.definitions.structure(&url) {
107 Some(structure) => {
108 let name = uses.alias().unwrap_or_else(|| structure.name.clone());
109 self.type_names.insert(name, url);
110 }
111 None => {
112 if self.definitions.covers(&url) {
113 let at = uses.url().unwrap();
114 self.error(at.syntax(), format!("unknown structure `{url}`"));
115 }
116 if let Some(alias) = uses.alias() {
117 self.type_names.insert(alias, url);
118 }
119 }
120 }
121 if uses.mode().is_none() {
122 self.error(
123 uses.syntax(),
124 "`uses` needs a mode: `as source` or `as target`",
125 );
126 }
127 }
128 for group in self.file.groups() {
129 let Some(name) = group.name() else { continue };
130 match self.groups.entry(name) {
131 Entry::Occupied(entry) => {
132 let message = format!("group `{}` is declared twice", entry.key());
133 self.error(group.syntax(), message);
134 }
135 Entry::Vacant(entry) => {
136 entry.insert(group);
137 }
138 }
139 }
140 for group in self.file.groups() {
141 if let Some(extends) = group.extends()
142 && !self.groups.contains_key(&extends)
143 && !self.has_imports
144 {
145 self.error(
146 group.syntax(),
147 format!("group `{extends}` is not declared and nothing is imported"),
148 );
149 }
150 }
151 }
152
153 fn parameter_type(&mut self, parameter: &ast::Parameter) -> Option<Type> {
156 let annotation = parameter.type_annotation()?;
157 let name = annotation.type_name()?;
158 if let Some(url) = self.type_names.get(&name).cloned() {
159 return Some(self.types().structure(&url));
160 }
161 let url = type_url(&name, "http://hl7.org/fhir/StructureDefinition/");
162 if let Some(structure) = self.definitions.structure(&url) {
163 return Some(Type::Node {
164 structure,
165 path: Vec::new(),
166 });
167 }
168 if self.definitions.covers(&url) {
169 self.error(annotation.syntax(), format!("unknown type `{name}`"));
170 }
171 Some(Type::Unknown)
172 }
173
174 fn check_group(&mut self, group: &ast::Group, arguments: Vec<Option<Type>>, depth: usize) {
179 let name = group.name().unwrap_or_default();
180 let parameters: Vec<ast::Parameter> = group.parameters().collect();
181 let mut scope = Scope::default();
182 for constant in self.file.constants() {
183 if let Some(constant_name) = constant.name() {
184 scope.define(
185 constant_name,
186 Variable {
187 ty: Type::Unknown,
188 mode: Mode::Source,
189 },
190 );
191 }
192 }
193 scope.push();
194 let mut key = Vec::new();
195 for (index, parameter) in parameters.iter().enumerate() {
196 let declared = self.parameter_type(parameter);
197 let ty = declared
198 .or_else(|| arguments.get(index).cloned().flatten())
199 .unwrap_or(Type::Unknown);
200 key.push(ty.key());
201 let mode = match parameter.mode() {
202 Some(ParameterMode::Target) => Mode::Target,
203 _ => Mode::Source,
204 };
205 if let Some(parameter_name) = parameter.name() {
206 scope.define(parameter_name, Variable { ty, mode });
207 }
208 }
209 if !self.checked.insert((name, key)) || depth > MAX_CALL_DEPTH {
210 return;
211 }
212 if let Some(body) = group.body() {
213 for rule in body.rules() {
214 self.check_rule(&rule, &mut scope, depth);
215 }
216 }
217 }
218
219 fn check_rule(&mut self, rule: &ast::Rule, outer: &mut Scope, depth: usize) {
222 outer.push();
223 for source in rule.sources() {
224 self.check_source(&source, outer);
225 }
226 let fields = rule.identity_fields();
227 let targets: Vec<ast::Target> = rule.targets().collect();
228 if !fields.is_empty() {
229 self.check_identity(rule, &fields, outer);
230 }
231 for target in &targets {
232 self.check_target(target, outer);
233 }
234 if let Some(dependent) = rule.dependent() {
235 for invocation in dependent.invocations() {
236 self.check_call(&invocation, outer, depth);
237 }
238 if let Some(block) = dependent.rules() {
239 for nested in block.rules() {
240 self.check_rule(&nested, outer, depth);
241 }
242 }
243 }
244 outer.pop();
245 }
246
247 fn check_source(&mut self, source: &ast::Source, scope: &mut Scope) {
248 let Some(path) = source.path() else { return };
249 let segments = path.segments();
250 let Some(context) = segments.first() else {
251 return;
252 };
253 let Some(variable) = scope.get(context).cloned() else {
254 self.error_at(
255 segment_range(&path, 0),
256 format!("unknown variable `{context}`"),
257 );
258 return;
259 };
260 let annotation = source.type_annotation();
261 let annotated = annotation.as_ref().and_then(|a| a.type_name());
262 let ty = self.walk(
263 &path,
264 variable.ty,
265 annotated.as_deref(),
266 annotation.as_ref(),
267 );
268 if let Some(default) = source.default_value().and_then(|d| d.expression()) {
269 self.check_expression(&default, &Type::Unknown, scope);
270 }
271 if let Some(alias) = source.alias() {
272 scope.define(
273 alias,
274 Variable {
275 ty: ty.clone(),
276 mode: Mode::Source,
277 },
278 );
279 }
280 for clause in [
282 source.where_clause(),
283 source.check_clause(),
284 source.log_clause(),
285 ]
286 .into_iter()
287 .flatten()
288 {
289 self.check_expression(&clause, &ty, scope);
290 }
291 }
292
293 fn check_identity(&mut self, rule: &ast::Rule, fields: &[String], scope: &Scope) {
295 let source = rule.sources().next().and_then(|s| s.path());
296 let target = rule.targets().next().and_then(|t| t.path());
297 for path in [source, target].into_iter().flatten() {
298 let Some(variable) = path.segments().first().and_then(|c| scope.get(c)).cloned() else {
299 continue;
300 };
301 for field in fields {
302 if let Lookup::Missing = self.types().lookup(&variable.ty, field) {
303 let message = format!("`{}` has no element `{field}`", variable.ty.display());
304 self.error(rule.syntax(), message);
305 }
306 }
307 }
308 }
309
310 fn check_target(&mut self, target: &ast::Target, scope: &mut Scope) {
311 let mut ty = Type::Unknown;
312 if let Some(path) = target.path() {
313 let segments = path.segments();
314 let context = &segments[0];
315 match scope.get(context).cloned() {
316 None => {
317 self.error_at(
318 segment_range(&path, 0),
319 format!("unknown variable `{context}`"),
320 );
321 }
322 Some(variable) => {
323 if variable.mode == Mode::Source {
324 self.error_at(
325 segment_range(&path, 0),
326 format!("`{context}` is a source variable; a target writes into a target variable"),
327 );
328 }
329 ty = self.walk(&path, variable.ty, None, None);
330 }
331 }
332 }
333 if let Some(transform) = target.transform() {
334 ty = self.check_transform(&transform, ty, scope);
335 }
336 if let Some(alias) = target.alias() {
337 scope.define(
338 alias,
339 Variable {
340 ty,
341 mode: Mode::Target,
342 },
343 );
344 }
345 }
346
347 fn check_transform(
349 &mut self,
350 transform: &TransformValue,
351 element: Type,
352 scope: &Scope,
353 ) -> Type {
354 match transform {
355 TransformValue::Name(name) => {
356 let segments = name.segments();
357 match scope.get(&segments[0]).cloned() {
358 Some(variable) if segments.len() == 1 => {
359 if matches!(element, Type::Unknown) {
360 variable.ty
361 } else {
362 element
363 }
364 }
365 Some(variable) => {
366 self.walk(name, variable.ty, None, None);
367 element
368 }
369 None => {
370 self.error_at(
371 segment_range(name, 0),
372 format!("unknown variable `{}`", segments[0]),
373 );
374 element
375 }
376 }
377 }
378 TransformValue::Invocation(invocation) => {
379 self.check_invocation(invocation, element, scope)
380 }
381 TransformValue::Expression(expression) => {
382 self.check_expression(expression, &Type::Unknown, scope);
383 element
384 }
385 TransformValue::Literal(_) => element,
386 }
387 }
388
389 fn check_invocation(&mut self, invocation: &Invocation, element: Type, scope: &Scope) -> Type {
390 let name = invocation.name().unwrap_or_default();
391 let arguments: Vec<Expr> = invocation.arguments().collect();
392 let Some(&(_, min, max)) = TRANSFORMS.iter().find(|(n, _, _)| *n == name) else {
393 self.error(invocation.syntax(), format!("unknown transform `{name}`"));
394 return element;
395 };
396 if arguments.len() < min || max.is_some_and(|max| arguments.len() > max) {
397 let expected = match max {
398 Some(max) if max == min => format!("{min}"),
399 Some(max) => format!("{min} to {max}"),
400 None => format!("at least {min}"),
401 };
402 self.error(
403 invocation.syntax(),
404 format!(
405 "`{name}` takes {expected} argument(s), found {}",
406 arguments.len()
407 ),
408 );
409 }
410 if name == "evaluate"
411 && let [focus, expression] = arguments.as_slice()
412 {
413 let focus = scope
414 .get(&focus.text())
415 .map(|v| v.ty.clone())
416 .unwrap_or(Type::Unknown);
417 self.check_expression(expression, &focus, scope);
418 }
419 for (index, argument) in arguments.iter().enumerate() {
420 let is_expression = name == "evaluate" && index == 1;
421 if !is_expression && argument.syntax().kind() == SyntaxKind::NameRef {
422 let variable = argument.text();
423 if scope.get(&variable).is_none() {
424 self.error(argument.syntax(), format!("unknown variable `{variable}`"));
425 }
426 }
427 }
428 if name == "create"
429 && let Some(type_name) = arguments.first().and_then(string_literal)
430 {
431 return self.created_type(&type_name, &element, &arguments[0]);
432 }
433 element
434 }
435
436 fn created_type(&mut self, type_name: &str, element: &Type, at: &Expr) -> Type {
438 let url = match (self.type_names.get(type_name), element) {
439 (Some(url), _) => url.clone(),
440 (None, Type::Node { structure, .. }) => type_url(type_name, &structure.url),
441 (None, Type::Unknown) => return Type::Unknown,
443 };
444 match self.definitions.structure(&url) {
445 Some(structure) => {
446 if structure.is_abstract {
447 self.error(
448 at.syntax(),
449 format!("`{type_name}` is abstract and cannot be created"),
450 );
451 }
452 Type::Node {
453 structure,
454 path: Vec::new(),
455 }
456 }
457 None => {
458 if self.definitions.covers(&url) {
459 self.error(at.syntax(), format!("unknown type `{type_name}`"));
460 }
461 Type::Unknown
462 }
463 }
464 }
465
466 fn check_call(&mut self, invocation: &Invocation, scope: &Scope, depth: usize) {
469 let name = invocation.name().unwrap_or_default();
470 let arguments: Vec<Expr> = invocation.arguments().collect();
471 let mut types = Vec::new();
472 for argument in &arguments {
473 if argument.syntax().kind() == SyntaxKind::NameRef {
474 let variable = argument.text();
475 match scope.get(&variable) {
476 Some(v) => types.push(Some(v.ty.clone())),
477 None => {
478 self.error(argument.syntax(), format!("unknown variable `{variable}`"));
479 types.push(None);
480 }
481 }
482 } else {
483 types.push(None);
484 }
485 }
486 let Some(group) = self.groups.get(&name).cloned() else {
487 if !self.has_imports {
488 self.error(
489 invocation.syntax(),
490 format!("group `{name}` is not declared and nothing is imported"),
491 );
492 }
493 return;
494 };
495 let parameters = group.parameters().count();
496 if parameters != arguments.len() {
497 self.error(
498 invocation.syntax(),
499 format!(
500 "group `{name}` takes {parameters} argument(s), found {}",
501 arguments.len()
502 ),
503 );
504 return;
505 }
506 self.check_group(&group, types, depth + 1);
507 }
508
509 fn check_expression(&mut self, expression: &Expr, focus: &Type, scope: &Scope) {
517 self.expression_type(expression.syntax(), focus, scope);
518 }
519
520 fn expression_type(&mut self, node: &SyntaxNode, focus: &Type, scope: &Scope) -> Type {
522 match node.kind() {
523 SyntaxKind::NameRef => {
524 let Some(token) = node.first_token() else {
525 return Type::Unknown;
526 };
527 if token.kind() == SyntaxKind::DollarIdent {
528 return if token.text() == "$this" {
529 focus.clone()
530 } else {
531 Type::Unknown
532 };
533 }
534 let name = ast::name_text(&token);
535 if let Some(variable) = scope.get(&name) {
536 return variable.ty.clone();
537 }
538 if focus.fhir_type().as_deref() == Some(name.as_str()) {
540 return focus.clone();
541 }
542 self.element_of(focus, &name, node)
543 }
544 SyntaxKind::MemberExpr => {
545 let mut parts = node.children();
546 let (Some(left), Some(right)) = (parts.next(), parts.next()) else {
547 return Type::Unknown;
548 };
549 let input = self.expression_type(&left, focus, scope);
550 match right.kind() {
551 SyntaxKind::NameRef => match right.first_token() {
552 Some(token) if token.kind() != SyntaxKind::DollarIdent => {
553 self.element_of(&input, &ast::name_text(&token), &right)
554 }
555 _ => Type::Unknown,
556 },
557 SyntaxKind::CallExpr => self.call_type(&right, &input, scope),
558 _ => Type::Unknown,
559 }
560 }
561 SyntaxKind::CallExpr => self.call_type(node, focus, scope),
562 SyntaxKind::ParenExpr => match node.children().next() {
563 Some(inner) => self.expression_type(&inner, focus, scope),
564 None => Type::Unknown,
565 },
566 SyntaxKind::IndexExpr => {
567 let mut parts = node.children();
568 let input = match parts.next() {
569 Some(left) => self.expression_type(&left, focus, scope),
570 None => Type::Unknown,
571 };
572 if let Some(index) = parts.next() {
573 self.expression_type(&index, focus, scope);
574 }
575 input
576 }
577 SyntaxKind::BinaryExpr | SyntaxKind::PrefixExpr | SyntaxKind::TypeExpr => {
578 for child in node.children().filter(|c| Expr::can_cast(c.kind())) {
579 self.expression_type(&child, focus, scope);
580 }
581 Type::Unknown
582 }
583 _ => Type::Unknown,
584 }
585 }
586
587 fn element_of(&mut self, ty: &Type, name: &str, at: &SyntaxNode) -> Type {
588 match self.types().lookup(ty, name) {
589 Lookup::Found(found) => self.types().element_type(&found, None),
590 Lookup::Missing => {
591 let message = format!("`{}` has no element `{name}`", ty.display());
592 self.report(Severity::Warning, trimmed_range(at), message);
593 Type::Unknown
594 }
595 Lookup::Unknown => Type::Unknown,
596 }
597 }
598
599 fn call_type(&mut self, call: &SyntaxNode, input: &Type, scope: &Scope) -> Type {
602 let name = call
603 .first_token()
604 .map(|t| t.text().to_owned())
605 .unwrap_or_default();
606 let arguments: Vec<SyntaxNode> = call
607 .children()
608 .find(|n| n.kind() == SyntaxKind::ArgumentList)
609 .map(|list| {
610 list.children()
611 .filter(|c| Expr::can_cast(c.kind()))
612 .collect()
613 })
614 .unwrap_or_default();
615 let iterating = matches!(
616 name.as_str(),
617 "where" | "select" | "exists" | "all" | "repeat" | "sort" | "aggregate"
618 );
619 let argument_focus = if iterating {
620 input.clone()
621 } else {
622 Type::Unknown
623 };
624 let mut last = Type::Unknown;
625 for argument in &arguments {
626 last = self.expression_type(argument, &argument_focus, scope);
627 }
628 match name.as_str() {
629 "where" | "first" | "last" | "tail" | "skip" | "take" | "single" | "distinct"
630 | "trace" | "repeat" | "sort" => input.clone(),
631 "select" => last,
632 _ => Type::Unknown,
633 }
634 }
635
636 fn walk(
640 &mut self,
641 path: &QualifiedName,
642 mut ty: Type,
643 annotation: Option<&str>,
644 annotation_node: Option<&ast::TypeAnnotation>,
645 ) -> Type {
646 let segments = path.segments();
647 for (index, segment) in segments.iter().enumerate().skip(1) {
648 let last = index == segments.len() - 1;
649 let segment = segment.trim_end_matches("[x]");
650 ty = match self.types().lookup(&ty, segment) {
651 Lookup::Unknown => return Type::Unknown,
652 Lookup::Missing => {
653 let message = format!("`{}` has no element `{segment}`", ty.display());
654 self.error_at(segment_range(path, index), message);
655 return Type::Unknown;
656 }
657 Lookup::Found(found) => {
658 let chosen = if last { annotation } else { None };
659 if let (Some(chosen), Some(node)) = (chosen, annotation_node)
660 && !found.element.types.is_empty()
661 && !self
662 .types()
663 .is_one_of(chosen, &found.element.types, &found.owner.url)
664 {
665 self.error(
666 node.syntax(),
667 format!(
668 "`{chosen}` is not a type of `{}.{segment}`; it is {}",
669 ty.display(),
670 found.element.types.join(", ")
671 ),
672 );
673 }
674 self.types().element_type(&found, chosen)
675 }
676 };
677 }
678 ty
679 }
680}
681
682const TRANSFORMS: &[(&str, usize, Option<usize>)] = &[
684 ("create", 0, Some(1)),
685 ("copy", 1, Some(1)),
686 ("truncate", 2, Some(2)),
687 ("escape", 2, Some(3)),
688 ("cast", 1, Some(2)),
689 ("append", 1, None),
690 ("translate", 3, Some(3)),
691 ("reference", 1, Some(1)),
692 ("dateOp", 2, Some(3)),
693 ("uuid", 0, Some(0)),
694 ("pointer", 1, Some(1)),
695 ("evaluate", 1, Some(2)),
696 ("cc", 1, Some(3)),
697 ("c", 2, Some(3)),
698 ("qty", 1, Some(4)),
699 ("id", 2, Some(3)),
700 ("cp", 1, Some(2)),
701 ("toDateTime", 1, Some(2)),
702 ("unixToDateTime", 1, Some(2)),
703 ("toDate", 1, Some(2)),
704 ("unixToDate", 1, Some(2)),
705 ("toTime", 1, Some(2)),
706 ("unixToTime", 1, Some(2)),
707];
708
709#[derive(Debug, Default)]
711struct Scope {
712 frames: Vec<HashMap<String, Variable>>,
713}
714
715impl Scope {
716 fn push(&mut self) {
717 self.frames.push(HashMap::new());
718 }
719
720 fn pop(&mut self) {
721 self.frames.pop();
722 }
723
724 fn define(&mut self, name: String, variable: Variable) {
725 if self.frames.is_empty() {
726 self.frames.push(HashMap::new());
727 }
728 self.frames.last_mut().unwrap().insert(name, variable);
729 }
730
731 fn get(&self, name: &str) -> Option<&Variable> {
732 self.frames.iter().rev().find_map(|frame| frame.get(name))
733 }
734}
735
736fn string_literal(expression: &Expr) -> Option<String> {
738 let literal = ast::Literal::cast(expression.syntax().clone())?;
739 matches!(
740 literal.kind()?,
741 SyntaxKind::SingleQuotedString | SyntaxKind::DoubleQuotedString
742 )
743 .then(|| literal.value())
744 .flatten()
745}
746
747fn segment_range(path: &QualifiedName, index: usize) -> TextRange {
749 path.syntax()
750 .children_with_tokens()
751 .filter_map(|e| e.into_token())
752 .filter(|t| matches!(t.kind(), SyntaxKind::Ident | SyntaxKind::DelimitedIdent))
753 .nth(index)
754 .map(|t| t.text_range())
755 .unwrap_or_else(|| trimmed_range(path.syntax()))
756}
757
758fn trimmed_range(node: &SyntaxNode) -> TextRange {
759 let mut tokens = node
760 .descendants_with_tokens()
761 .filter_map(|e| e.into_token())
762 .filter(|t| !t.kind().is_trivia());
763 match tokens.next() {
764 Some(first) => {
765 let end = tokens
766 .last()
767 .map_or(first.text_range().end(), |t| t.text_range().end());
768 TextRange::new(first.text_range().start(), end)
769 }
770 None => node.text_range(),
771 }
772}