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 RESERVED: &[&str] = &[
25 "map",
26 "uses",
27 "as",
28 "alias",
29 "imports",
30 "group",
31 "extends",
32 "default",
33 "where",
34 "check",
35 "log",
36 "then",
37 "true",
38 "false",
39 "types",
40 "type",
41 "first",
42 "not_first",
43 "last",
44 "not_last",
45 "only_one",
46 "share",
47 "single",
48 "source",
49 "target",
50 "queried",
51 "produced",
52 "conceptmap",
53 "prefix",
54 "let",
55];
56
57const MAX_CALL_DEPTH: usize = 32;
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61enum Mode {
62 Source,
63 Target,
64}
65
66#[derive(Debug, Clone)]
67struct Variable {
68 ty: Type,
69 mode: Mode,
70}
71
72struct Checker<'a> {
73 file: &'a ast::SourceFile,
74 definitions: &'a dyn Definitions,
75 type_names: HashMap<String, String>,
77 groups: HashMap<String, ast::Group>,
78 has_imports: bool,
79 checked: HashSet<(String, Vec<String>)>,
81 reported: HashSet<(TextRange, String)>,
82 diagnostics: Vec<Diagnostic>,
83}
84
85impl<'a> Checker<'a> {
86 fn types(&self) -> Types<'a> {
87 Types::new(self.definitions)
88 }
89
90 fn new(file: &'a ast::SourceFile, definitions: &'a dyn Definitions) -> Self {
91 Self {
92 file,
93 definitions,
94 type_names: HashMap::new(),
95 groups: HashMap::new(),
96 has_imports: file.imports().next().is_some(),
97 checked: HashSet::new(),
98 reported: HashSet::new(),
99 diagnostics: Vec::new(),
100 }
101 }
102
103 fn report(&mut self, severity: Severity, range: TextRange, message: String) {
104 if self.reported.insert((range, message.clone())) {
105 self.diagnostics.push(Diagnostic {
106 severity,
107 range,
108 message,
109 });
110 }
111 }
112
113 fn error(&mut self, node: &SyntaxNode, message: impl Into<String>) {
114 self.report(Severity::Error, trimmed_range(node), message.into());
115 }
116
117 fn warning(&mut self, node: &SyntaxNode, message: impl Into<String>) {
118 self.report(Severity::Warning, trimmed_range(node), message.into());
119 }
120
121 fn error_at(&mut self, range: TextRange, message: impl Into<String>) {
122 self.report(Severity::Error, range, message.into());
123 }
124
125 fn run(&mut self) {
126 self.declarations();
127 self.identity();
128 self.names();
129 self.group_signatures();
130 self.rule_names();
131 let groups: Vec<ast::Group> = self.file.groups().collect();
132 for group in &groups {
133 let types = group
134 .parameters()
135 .map(|parameter| self.parameter_type(¶meter))
136 .collect();
137 self.check_group(group, types, 0);
138 }
139 }
140
141 fn declarations(&mut self) {
144 for uses in self.file.uses() {
145 let Some(url) = uses.url().and_then(|u| u.value()) else {
146 continue;
147 };
148 match self.definitions.structure(&url) {
149 Some(structure) => {
150 let name = uses.alias().unwrap_or_else(|| structure.name.clone());
151 self.type_names.insert(name, url);
152 }
153 None => {
154 if self.definitions.covers(&url) {
155 let at = uses.url().unwrap();
156 self.error(at.syntax(), format!("unknown structure `{url}`"));
157 }
158 if let Some(alias) = uses.alias() {
159 self.type_names.insert(alias, url);
160 }
161 }
162 }
163 if uses.mode().is_none() {
164 self.error(
165 uses.syntax(),
166 "`uses` needs a mode: `as source` or `as target`",
167 );
168 }
169 }
170 for group in self.file.groups() {
171 let Some(name) = group.name() else { continue };
172 match self.groups.entry(name) {
173 Entry::Occupied(entry) => {
174 let message = format!("group `{}` is declared twice", entry.key());
175 self.error(group.syntax(), message);
176 }
177 Entry::Vacant(entry) => {
178 entry.insert(group);
179 }
180 }
181 }
182 for group in self.file.groups() {
183 if let Some(extends) = group.extends()
184 && !self.groups.contains_key(&extends)
185 && !self.has_imports
186 {
187 self.error(
188 group.syntax(),
189 format!("group `{extends}` is not declared and nothing is imported"),
190 );
191 }
192 }
193 }
194
195 fn names(&mut self) {
198 let mut constants: HashSet<String> = HashSet::new();
199 for constant in self.file.constants() {
200 let Some(name) = constant.name() else {
201 continue;
202 };
203 if !constants.insert(name.clone()) {
204 self.error(
205 constant.syntax(),
206 format!("constant `{name}` is declared twice"),
207 );
208 }
209 if RESERVED.contains(&name.as_str()) {
210 self.warning(constant.syntax(), format!("`{name}` is a reserved keyword"));
211 }
212 }
213 for group in self.file.groups() {
214 if let Some(name) = group.name()
215 && RESERVED.contains(&name.as_str())
216 {
217 self.warning(group.syntax(), format!("`{name}` is a reserved keyword"));
218 }
219 for parameter in group.parameters() {
220 if let Some(name) = parameter.name()
221 && RESERVED.contains(&name.as_str())
222 {
223 self.warning(
224 parameter.syntax(),
225 format!("`{name}` is a reserved keyword"),
226 );
227 }
228 }
229 }
230 }
231
232 fn identity(&mut self) {
234 let declaration = self.file.map_declaration();
235 let metadata: Vec<String> = self
236 .file
237 .metadata()
238 .filter_map(|m| m.key().map(|k| k.text()))
239 .collect();
240 let has = |key: &str| metadata.iter().any(|k| k == key);
241 let url = has("url") || declaration.as_ref().is_some_and(|d| d.url().is_some());
242 let name = has("name") || declaration.as_ref().is_some_and(|d| d.name().is_some());
243 let at = TextRange::empty(0.into());
244 if !url {
245 self.error_at(at, "the map has no `url`: add `/// url = '…'`");
246 }
247 if !name {
248 self.error_at(at, "the map has no `name`: add `/// name = '…'`");
249 }
250 }
251
252 fn signature(group: &ast::Group) -> Vec<(Mode, String, Option<String>)> {
254 group
255 .parameters()
256 .map(|parameter| {
257 let mode = match parameter.mode() {
258 Some(ParameterMode::Target) => Mode::Target,
259 _ => Mode::Source,
260 };
261 let type_name = parameter.type_annotation().and_then(|t| t.type_name());
262 (mode, parameter.name().unwrap_or_default(), type_name)
263 })
264 .collect()
265 }
266
267 fn group_signatures(&mut self) {
271 let groups: Vec<ast::Group> = self.file.groups().collect();
272 for group in &groups {
273 let own = Self::signature(group);
274 if let Some(base) = group
275 .extends()
276 .and_then(|name| self.groups.get(&name).cloned())
277 {
278 let base_name = base.name().unwrap_or_default();
279 for (mode, name, type_name) in Self::signature(&base) {
280 let matching = own.iter().find(|(_, own_name, _)| *own_name == name);
281 let problem = match matching {
282 None => Some(format!("has no parameter `{name}`")),
283 Some((own_mode, _, _)) if *own_mode != mode => {
284 Some(format!("has `{name}` with another mode"))
285 }
286 Some((_, _, own_type))
287 if type_name.is_some()
288 && own_type.is_some()
289 && *own_type != type_name =>
290 {
291 Some(format!(
292 "has `{name}` as `{}`, not `{}`",
293 own_type.as_deref().unwrap_or_default(),
294 type_name.as_deref().unwrap_or_default()
295 ))
296 }
297 _ => None,
298 };
299 if let Some(problem) = problem {
300 self.error(
301 group.syntax(),
302 format!(
303 "group `{}` extends `{base_name}` but {problem}; it needs the parameters of `{base_name}`",
304 group.name().unwrap_or_default()
305 ),
306 );
307 }
308 }
309 }
310 if group.type_mode().is_some() {
311 let valid = matches!(
312 own.as_slice(),
313 [(Mode::Source, _, Some(_)), (Mode::Target, _, Some(_))]
314 );
315 if !valid {
316 self.warning(
317 group.syntax(),
318 format!(
319 "default group `{}` needs two typed parameters, a source and then a target; it is not used as a default",
320 group.name().unwrap_or_default()
321 ),
322 );
323 }
324 }
325 }
326 }
327
328 fn rule_names(&mut self) {
330 let mut rules: Vec<ast::Rule> = Vec::new();
331 let mut pending: Vec<ast::Rule> = self
332 .file
333 .groups()
334 .flat_map(|g| g.rules().collect::<Vec<_>>())
335 .collect();
336 while let Some(rule) = pending.pop() {
337 if let Some(block) = rule.dependent().and_then(|d| d.rules()) {
338 pending.extend(block.rules());
339 }
340 rules.push(rule);
341 }
342 rules.sort_by_key(|rule| rule.syntax().text_range().start());
343 let mut seen: HashSet<String> = HashSet::new();
344 for rule in rules {
345 let Some(name) = rule.name() else {
346 continue;
347 };
348 if !seen.insert(name.clone()) {
349 self.warning(
350 rule.syntax(),
351 format!("rule name `{name}` is used twice in the map"),
352 );
353 }
354 }
355 }
356
357 fn parameter_type(&mut self, parameter: &ast::Parameter) -> Option<Type> {
360 let annotation = parameter.type_annotation()?;
361 let name = annotation.type_name()?;
362 if let Some(url) = self.type_names.get(&name).cloned() {
363 return Some(self.types().structure(&url));
364 }
365 let url = type_url(&name, "http://hl7.org/fhir/StructureDefinition/");
366 if let Some(structure) = self.definitions.structure(&url) {
367 return Some(Type::Node {
368 structure,
369 path: Vec::new(),
370 });
371 }
372 if self.definitions.covers(&url) {
373 self.error(annotation.syntax(), format!("unknown type `{name}`"));
374 }
375 Some(Type::Unknown)
376 }
377
378 fn check_group(&mut self, group: &ast::Group, arguments: Vec<Option<Type>>, depth: usize) {
383 let name = group.name().unwrap_or_default();
384 let parameters: Vec<ast::Parameter> = group.parameters().collect();
385 let mut scope = Scope::default();
386 for constant in self.file.constants() {
387 if let Some(constant_name) = constant.name() {
388 scope.define(
389 constant_name,
390 Variable {
391 ty: Type::Unknown,
392 mode: Mode::Source,
393 },
394 );
395 }
396 }
397 scope.push();
398 let mut key = Vec::new();
399 for (index, parameter) in parameters.iter().enumerate() {
400 let declared = self.parameter_type(parameter);
401 let ty = declared
402 .or_else(|| arguments.get(index).cloned().flatten())
403 .unwrap_or(Type::Unknown);
404 key.push(ty.key());
405 let mode = match parameter.mode() {
406 Some(ParameterMode::Target) => Mode::Target,
407 _ => Mode::Source,
408 };
409 if let Some(parameter_name) = parameter.name() {
410 scope.define(parameter_name, Variable { ty, mode });
411 }
412 }
413 if !self.checked.insert((name, key)) || depth > MAX_CALL_DEPTH {
414 return;
415 }
416 if let Some(body) = group.body() {
417 for rule in body.rules() {
418 self.check_rule(&rule, &mut scope, depth);
419 }
420 }
421 }
422
423 fn check_rule(&mut self, rule: &ast::Rule, outer: &mut Scope, depth: usize) {
426 outer.push();
427 for source in rule.sources() {
428 self.check_source(&source, outer);
429 }
430 let fields = rule.identity_fields();
431 let targets: Vec<ast::Target> = rule.targets().collect();
432 if !fields.is_empty() {
433 self.check_identity(rule, &fields, outer);
434 }
435 for target in &targets {
436 self.check_target(target, outer);
437 }
438 if let Some(dependent) = rule.dependent() {
439 for invocation in dependent.invocations() {
440 self.check_call(&invocation, outer, depth);
441 }
442 if let Some(block) = dependent.rules() {
443 for nested in block.rules() {
444 self.check_rule(&nested, outer, depth);
445 }
446 }
447 }
448 outer.pop();
449 }
450
451 fn check_source(&mut self, source: &ast::Source, scope: &mut Scope) {
452 let Some(path) = source.path() else { return };
453 let segments = path.segments();
454 let Some(context) = segments.first() else {
455 return;
456 };
457 let Some(variable) = scope.get(context).cloned() else {
458 self.error_at(
459 segment_range(&path, 0),
460 format!("unknown variable `{context}`"),
461 );
462 return;
463 };
464 let annotation = source.type_annotation();
465 let annotated = annotation.as_ref().and_then(|a| a.type_name());
466 let ty = self.walk(
467 &path,
468 variable.ty,
469 annotated.as_deref(),
470 annotation.as_ref(),
471 );
472 if let Some(default) = source.default_value().and_then(|d| d.expression()) {
473 self.check_expression(&default, &Type::Unknown, scope);
474 }
475 if let Some(alias) = source.alias() {
476 scope.define(
477 alias,
478 Variable {
479 ty: ty.clone(),
480 mode: Mode::Source,
481 },
482 );
483 }
484 for clause in [
486 source.where_clause(),
487 source.check_clause(),
488 source.log_clause(),
489 ]
490 .into_iter()
491 .flatten()
492 {
493 self.check_expression(&clause, &ty, scope);
494 }
495 }
496
497 fn check_identity(&mut self, rule: &ast::Rule, fields: &[String], scope: &Scope) {
499 let source = rule.sources().next().and_then(|s| s.path());
500 let target = rule.targets().next().and_then(|t| t.path());
501 for path in [source, target].into_iter().flatten() {
502 let Some(variable) = path.segments().first().and_then(|c| scope.get(c)).cloned() else {
503 continue;
504 };
505 for field in fields {
506 if let Lookup::Missing = self.types().lookup(&variable.ty, field) {
507 let message = format!("`{}` has no element `{field}`", variable.ty.display());
508 self.error(rule.syntax(), message);
509 }
510 }
511 }
512 }
513
514 fn check_target(&mut self, target: &ast::Target, scope: &mut Scope) {
515 let mut ty = Type::Unknown;
516 if let Some(path) = target.path() {
517 let segments = path.segments();
518 let context = &segments[0];
519 match scope.get(context).cloned() {
520 None => {
521 self.error_at(
522 segment_range(&path, 0),
523 format!("unknown variable `{context}`"),
524 );
525 }
526 Some(variable) => {
527 if variable.mode == Mode::Source {
528 self.error_at(
529 segment_range(&path, 0),
530 format!("`{context}` is a source variable; a target writes into a target variable"),
531 );
532 }
533 ty = self.walk(&path, variable.ty, None, None);
534 }
535 }
536 }
537 if let Some(transform) = target.transform() {
538 ty = self.check_transform(&transform, ty, scope);
539 }
540 if let Some(alias) = target.alias() {
541 scope.define(
542 alias,
543 Variable {
544 ty,
545 mode: Mode::Target,
546 },
547 );
548 }
549 }
550
551 fn check_transform(
553 &mut self,
554 transform: &TransformValue,
555 element: Type,
556 scope: &Scope,
557 ) -> Type {
558 match transform {
559 TransformValue::Name(name) => {
560 let segments = name.segments();
561 match scope.get(&segments[0]).cloned() {
562 Some(variable) if segments.len() == 1 => {
563 if matches!(element, Type::Unknown) {
564 variable.ty
565 } else {
566 element
567 }
568 }
569 Some(variable) => {
570 self.walk(name, variable.ty, None, None);
571 element
572 }
573 None => {
574 self.error_at(
575 segment_range(name, 0),
576 format!("unknown variable `{}`", segments[0]),
577 );
578 element
579 }
580 }
581 }
582 TransformValue::Invocation(invocation) => {
583 self.check_invocation(invocation, element, scope)
584 }
585 TransformValue::Expression(expression) => {
586 self.check_expression(expression, &Type::Unknown, scope);
587 element
588 }
589 TransformValue::Literal(_) => element,
590 }
591 }
592
593 fn check_invocation(&mut self, invocation: &Invocation, element: Type, scope: &Scope) -> Type {
594 let name = invocation.name().unwrap_or_default();
595 let arguments: Vec<Expr> = invocation.arguments().collect();
596 let Some(&(_, min, max)) = TRANSFORMS.iter().find(|(n, _, _)| *n == name) else {
597 self.error(invocation.syntax(), format!("unknown transform `{name}`"));
598 return element;
599 };
600 if arguments.len() < min || max.is_some_and(|max| arguments.len() > max) {
601 let expected = match max {
602 Some(max) if max == min => format!("{min}"),
603 Some(max) => format!("{min} to {max}"),
604 None => format!("at least {min}"),
605 };
606 self.error(
607 invocation.syntax(),
608 format!(
609 "`{name}` takes {expected} argument(s), found {}",
610 arguments.len()
611 ),
612 );
613 }
614 if name == "evaluate"
615 && let [focus, expression] = arguments.as_slice()
616 {
617 let focus = scope
618 .get(&focus.text())
619 .map(|v| v.ty.clone())
620 .unwrap_or(Type::Unknown);
621 self.check_expression(expression, &focus, scope);
622 }
623 for (index, argument) in arguments.iter().enumerate() {
624 let is_expression = name == "evaluate" && index == 1;
625 if !is_expression && argument.syntax().kind() == SyntaxKind::NameRef {
626 let variable = argument.text();
627 if scope.get(&variable).is_none() {
628 self.error(argument.syntax(), format!("unknown variable `{variable}`"));
629 }
630 }
631 }
632 if name == "create"
633 && let Some(type_name) = arguments.first().and_then(string_literal)
634 {
635 return self.created_type(&type_name, &element, &arguments[0]);
636 }
637 element
638 }
639
640 fn created_type(&mut self, type_name: &str, element: &Type, at: &Expr) -> Type {
642 let url = match (self.type_names.get(type_name), element) {
643 (Some(url), _) => url.clone(),
644 (None, Type::Node { structure, .. }) => type_url(type_name, &structure.url),
645 (None, Type::Unknown) => return Type::Unknown,
647 };
648 match self.definitions.structure(&url) {
649 Some(structure) => {
650 if structure.is_abstract {
651 self.error(
652 at.syntax(),
653 format!("`{type_name}` is abstract and cannot be created"),
654 );
655 }
656 Type::Node {
657 structure,
658 path: Vec::new(),
659 }
660 }
661 None => {
662 if self.definitions.covers(&url) {
663 self.error(at.syntax(), format!("unknown type `{type_name}`"));
664 }
665 Type::Unknown
666 }
667 }
668 }
669
670 fn check_call(&mut self, invocation: &Invocation, scope: &Scope, depth: usize) {
673 let name = invocation.name().unwrap_or_default();
674 let arguments: Vec<Expr> = invocation.arguments().collect();
675 let mut types = Vec::new();
676 for argument in &arguments {
677 if argument.syntax().kind() == SyntaxKind::NameRef {
678 let variable = argument.text();
679 match scope.get(&variable) {
680 Some(v) => types.push(Some(v.ty.clone())),
681 None => {
682 self.error(argument.syntax(), format!("unknown variable `{variable}`"));
683 types.push(None);
684 }
685 }
686 } else {
687 types.push(None);
688 }
689 }
690 let Some(group) = self.groups.get(&name).cloned() else {
691 if !self.has_imports {
692 self.error(
693 invocation.syntax(),
694 format!("group `{name}` is not declared and nothing is imported"),
695 );
696 }
697 return;
698 };
699 let parameters = group.parameters().count();
700 if parameters != arguments.len() {
701 self.error(
702 invocation.syntax(),
703 format!(
704 "group `{name}` takes {parameters} argument(s), found {}",
705 arguments.len()
706 ),
707 );
708 return;
709 }
710 self.check_group(&group, types, depth + 1);
711 }
712
713 fn check_expression(&mut self, expression: &Expr, focus: &Type, scope: &Scope) {
721 self.expression_type(expression.syntax(), focus, scope);
722 }
723
724 fn expression_type(&mut self, node: &SyntaxNode, focus: &Type, scope: &Scope) -> Type {
726 match node.kind() {
727 SyntaxKind::NameRef => {
728 let Some(token) = node.first_token() else {
729 return Type::Unknown;
730 };
731 if token.kind() == SyntaxKind::DollarIdent {
732 return if token.text() == "$this" {
733 focus.clone()
734 } else {
735 Type::Unknown
736 };
737 }
738 let name = ast::name_text(&token);
739 if let Some(variable) = scope.get(&name) {
740 return variable.ty.clone();
741 }
742 if focus.fhir_type().as_deref() == Some(name.as_str()) {
744 return focus.clone();
745 }
746 self.element_of(focus, &name, node)
747 }
748 SyntaxKind::MemberExpr => {
749 let mut parts = node.children();
750 let (Some(left), Some(right)) = (parts.next(), parts.next()) else {
751 return Type::Unknown;
752 };
753 let input = self.expression_type(&left, focus, scope);
754 match right.kind() {
755 SyntaxKind::NameRef => match right.first_token() {
756 Some(token) if token.kind() != SyntaxKind::DollarIdent => {
757 self.element_of(&input, &ast::name_text(&token), &right)
758 }
759 _ => Type::Unknown,
760 },
761 SyntaxKind::CallExpr => self.call_type(&right, &input, scope),
762 _ => Type::Unknown,
763 }
764 }
765 SyntaxKind::CallExpr => self.call_type(node, focus, scope),
766 SyntaxKind::ParenExpr => match node.children().next() {
767 Some(inner) => self.expression_type(&inner, focus, scope),
768 None => Type::Unknown,
769 },
770 SyntaxKind::IndexExpr => {
771 let mut parts = node.children();
772 let input = match parts.next() {
773 Some(left) => self.expression_type(&left, focus, scope),
774 None => Type::Unknown,
775 };
776 if let Some(index) = parts.next() {
777 self.expression_type(&index, focus, scope);
778 }
779 input
780 }
781 SyntaxKind::BinaryExpr | SyntaxKind::PrefixExpr | SyntaxKind::TypeExpr => {
782 for child in node.children().filter(|c| Expr::can_cast(c.kind())) {
783 self.expression_type(&child, focus, scope);
784 }
785 Type::Unknown
786 }
787 _ => Type::Unknown,
788 }
789 }
790
791 fn element_of(&mut self, ty: &Type, name: &str, at: &SyntaxNode) -> Type {
792 match self.types().lookup(ty, name) {
793 Lookup::Found(found) => self.types().element_type(&found, None),
794 Lookup::Missing => {
795 let message = format!("`{}` has no element `{name}`", ty.display());
796 self.report(Severity::Warning, trimmed_range(at), message);
797 Type::Unknown
798 }
799 Lookup::Unknown => Type::Unknown,
800 }
801 }
802
803 fn call_type(&mut self, call: &SyntaxNode, input: &Type, scope: &Scope) -> Type {
806 let name = call
807 .first_token()
808 .map(|t| t.text().to_owned())
809 .unwrap_or_default();
810 let arguments: Vec<SyntaxNode> = call
811 .children()
812 .find(|n| n.kind() == SyntaxKind::ArgumentList)
813 .map(|list| {
814 list.children()
815 .filter(|c| Expr::can_cast(c.kind()))
816 .collect()
817 })
818 .unwrap_or_default();
819 let iterating = matches!(
820 name.as_str(),
821 "where" | "select" | "exists" | "all" | "repeat" | "sort" | "aggregate"
822 );
823 let argument_focus = if iterating {
824 input.clone()
825 } else {
826 Type::Unknown
827 };
828 let mut last = Type::Unknown;
829 for argument in &arguments {
830 last = self.expression_type(argument, &argument_focus, scope);
831 }
832 match name.as_str() {
833 "where" | "first" | "last" | "tail" | "skip" | "take" | "single" | "distinct"
834 | "trace" | "repeat" | "sort" => input.clone(),
835 "select" => last,
836 _ => Type::Unknown,
837 }
838 }
839
840 fn walk(
844 &mut self,
845 path: &QualifiedName,
846 mut ty: Type,
847 annotation: Option<&str>,
848 annotation_node: Option<&ast::TypeAnnotation>,
849 ) -> Type {
850 let segments = path.segments();
851 for (index, segment) in segments.iter().enumerate().skip(1) {
852 let last = index == segments.len() - 1;
853 let segment = segment.trim_end_matches("[x]");
854 ty = match self.types().lookup(&ty, segment) {
855 Lookup::Unknown => return Type::Unknown,
856 Lookup::Missing => {
857 let message = format!("`{}` has no element `{segment}`", ty.display());
858 self.error_at(segment_range(path, index), message);
859 return Type::Unknown;
860 }
861 Lookup::Found(found) => {
862 let chosen = if last { annotation } else { None };
863 if let (Some(chosen), Some(node)) = (chosen, annotation_node)
864 && !found.element.types.is_empty()
865 && !self
866 .types()
867 .is_one_of(chosen, &found.element.types, &found.owner.url)
868 {
869 self.error(
870 node.syntax(),
871 format!(
872 "`{chosen}` is not a type of `{}.{segment}`; it is {}",
873 ty.display(),
874 found.element.types.join(", ")
875 ),
876 );
877 }
878 self.types().element_type(&found, chosen)
879 }
880 };
881 }
882 ty
883 }
884}
885
886const TRANSFORMS: &[(&str, usize, Option<usize>)] = &[
888 ("create", 0, Some(1)),
889 ("copy", 1, Some(1)),
890 ("truncate", 2, Some(2)),
891 ("escape", 2, Some(3)),
892 ("cast", 1, Some(2)),
893 ("append", 1, None),
894 ("translate", 3, Some(3)),
895 ("reference", 1, Some(1)),
896 ("dateOp", 2, Some(3)),
897 ("uuid", 0, Some(0)),
898 ("pointer", 1, Some(1)),
899 ("evaluate", 1, Some(2)),
900 ("cc", 1, Some(3)),
901 ("c", 2, Some(3)),
902 ("qty", 1, Some(4)),
903 ("id", 2, Some(3)),
904 ("cp", 1, Some(2)),
905 ("toDateTime", 1, Some(2)),
906 ("unixToDateTime", 1, Some(2)),
907 ("toDate", 1, Some(2)),
908 ("unixToDate", 1, Some(2)),
909 ("toTime", 1, Some(2)),
910 ("unixToTime", 1, Some(2)),
911];
912
913#[derive(Debug, Default)]
915struct Scope {
916 frames: Vec<HashMap<String, Variable>>,
917}
918
919impl Scope {
920 fn push(&mut self) {
921 self.frames.push(HashMap::new());
922 }
923
924 fn pop(&mut self) {
925 self.frames.pop();
926 }
927
928 fn define(&mut self, name: String, variable: Variable) {
929 if self.frames.is_empty() {
930 self.frames.push(HashMap::new());
931 }
932 self.frames.last_mut().unwrap().insert(name, variable);
933 }
934
935 fn get(&self, name: &str) -> Option<&Variable> {
936 self.frames.iter().rev().find_map(|frame| frame.get(name))
937 }
938}
939
940fn string_literal(expression: &Expr) -> Option<String> {
942 let literal = ast::Literal::cast(expression.syntax().clone())?;
943 matches!(
944 literal.kind()?,
945 SyntaxKind::SingleQuotedString | SyntaxKind::DoubleQuotedString
946 )
947 .then(|| literal.value())
948 .flatten()
949}
950
951fn segment_range(path: &QualifiedName, index: usize) -> TextRange {
953 path.syntax()
954 .children_with_tokens()
955 .filter_map(|e| e.into_token())
956 .filter(|t| matches!(t.kind(), SyntaxKind::Ident | SyntaxKind::DelimitedIdent))
957 .nth(index)
958 .map(|t| t.text_range())
959 .unwrap_or_else(|| trimmed_range(path.syntax()))
960}
961
962fn trimmed_range(node: &SyntaxNode) -> TextRange {
963 let mut tokens = node
964 .descendants_with_tokens()
965 .filter_map(|e| e.into_token())
966 .filter(|t| !t.kind().is_trivia());
967 match tokens.next() {
968 Some(first) => {
969 let end = tokens
970 .last()
971 .map_or(first.text_range().end(), |t| t.text_range().end());
972 TextRange::new(first.text_range().start(), end)
973 }
974 None => node.text_range(),
975 }
976}