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, Kind, 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 if variable.mode == Mode::Target {
467 self.error_at(
468 segment_range(&path, 0),
469 format!("`{context}` is a target variable; a source reads from a source variable"),
470 );
471 }
472 let annotation = source.type_annotation();
473 let annotated = annotation.as_ref().and_then(|a| a.type_name());
474 let ty = self.walk(
475 &path,
476 variable.ty,
477 annotated.as_deref(),
478 annotation.as_ref(),
479 );
480 if let Some(default) = source.default_value().and_then(|d| d.expression()) {
481 self.check_expression(&default, &Type::Unknown, scope);
482 }
483 if let Some(alias) = source.alias() {
484 scope.define(
485 alias,
486 Variable {
487 ty: ty.clone(),
488 mode: Mode::Source,
489 },
490 );
491 }
492 for clause in [
494 source.where_clause(),
495 source.check_clause(),
496 source.log_clause(),
497 ]
498 .into_iter()
499 .flatten()
500 {
501 self.check_expression(&clause, &ty, scope);
502 }
503 }
504
505 fn check_identity(&mut self, rule: &ast::Rule, fields: &[String], scope: &Scope) {
507 let source = rule.sources().next().and_then(|s| s.path());
508 let target = rule.targets().next().and_then(|t| t.path());
509 for path in [source, target].into_iter().flatten() {
510 let Some(variable) = path.segments().first().and_then(|c| scope.get(c)).cloned() else {
511 continue;
512 };
513 for field in fields {
514 if let Lookup::Missing = self.types().lookup(&variable.ty, field) {
515 let message = format!("`{}` has no element `{field}`", variable.ty.display());
516 self.error(rule.syntax(), message);
517 }
518 }
519 }
520 }
521
522 fn check_target(&mut self, target: &ast::Target, scope: &mut Scope) {
523 let mut ty = Type::Unknown;
524 if let Some(path) = target.path() {
525 let segments = path.segments();
526 let context = &segments[0];
527 match scope.get(context).cloned() {
528 None => {
529 self.error_at(
530 segment_range(&path, 0),
531 format!("unknown variable `{context}`"),
532 );
533 }
534 Some(variable) => {
535 if variable.mode == Mode::Source {
536 self.error_at(
537 segment_range(&path, 0),
538 format!("`{context}` is a source variable; a target writes into a target variable"),
539 );
540 }
541 ty = self.walk(&path, variable.ty, None, None);
542 }
543 }
544 }
545 if let Some(transform) = target.transform() {
546 ty = self.check_transform(&transform, ty, scope);
547 }
548 if let Some(alias) = target.alias() {
549 scope.define(
550 alias,
551 Variable {
552 ty,
553 mode: Mode::Target,
554 },
555 );
556 }
557 }
558
559 fn check_transform(
561 &mut self,
562 transform: &TransformValue,
563 element: Type,
564 scope: &Scope,
565 ) -> Type {
566 match transform {
567 TransformValue::Name(name) => {
568 let segments = name.segments();
569 match scope.get(&segments[0]).cloned() {
570 Some(variable) if segments.len() == 1 => {
571 if matches!(element, Type::Unknown) {
572 variable.ty
573 } else {
574 element
575 }
576 }
577 Some(variable) => {
578 self.walk(name, variable.ty, None, None);
579 element
580 }
581 None => {
582 self.error_at(
583 segment_range(name, 0),
584 format!("unknown variable `{}`", segments[0]),
585 );
586 element
587 }
588 }
589 }
590 TransformValue::Invocation(invocation) => {
591 self.check_invocation(invocation, element, scope)
592 }
593 TransformValue::Expression(expression) => {
594 self.check_expression(expression, &Type::Unknown, scope);
595 element
596 }
597 TransformValue::Literal(_) => element,
598 }
599 }
600
601 fn check_invocation(&mut self, invocation: &Invocation, element: Type, scope: &Scope) -> Type {
602 let name = invocation.name().unwrap_or_default();
603 let arguments: Vec<Expr> = invocation.arguments().collect();
604 let Some(&(_, min, max)) = TRANSFORMS.iter().find(|(n, _, _)| *n == name) else {
605 self.error(invocation.syntax(), format!("unknown transform `{name}`"));
606 return element;
607 };
608 if arguments.len() < min || max.is_some_and(|max| arguments.len() > max) {
609 let expected = match max {
610 Some(max) if max == min => format!("{min}"),
611 Some(max) => format!("{min} to {max}"),
612 None => format!("at least {min}"),
613 };
614 self.error(
615 invocation.syntax(),
616 format!(
617 "`{name}` takes {expected} argument(s), found {}",
618 arguments.len()
619 ),
620 );
621 }
622 if name == "evaluate"
623 && let [focus, expression] = arguments.as_slice()
624 {
625 let focus = scope
626 .get(&focus.text())
627 .map(|v| v.ty.clone())
628 .unwrap_or(Type::Unknown);
629 self.check_expression(expression, &focus, scope);
630 }
631 for (index, argument) in arguments.iter().enumerate() {
632 let is_expression = name == "evaluate" && index == 1;
633 if !is_expression && argument.syntax().kind() == SyntaxKind::NameRef {
634 let variable = argument.text();
635 if scope.get(&variable).is_none() {
636 self.error(argument.syntax(), format!("unknown variable `{variable}`"));
637 }
638 }
639 }
640 if name == "create"
641 && let Some(type_name) = arguments.first().and_then(string_literal)
642 {
643 return self.created_type(&type_name, &element, &arguments[0]);
644 }
645 element
646 }
647
648 fn created_type(&mut self, type_name: &str, element: &Type, at: &Expr) -> Type {
650 if let (None, Type::Node { structure, .. }) = (self.type_names.get(type_name), element)
653 && structure.kind == Kind::Logical
654 && let Some((prefix, _)) = structure.url.rsplit_once('/')
655 {
656 for id in [type_name.to_owned(), type_name.replace('_', "-")] {
657 if let Some(found) = self.definitions.structure(&format!("{prefix}/{id}"))
658 && found.name == type_name
659 {
660 return Type::Node {
661 structure: found,
662 path: Vec::new(),
663 };
664 }
665 }
666 }
667 let url = match (self.type_names.get(type_name), element) {
668 (Some(url), _) => url.clone(),
669 (None, Type::Node { structure, .. }) => type_url(type_name, &structure.url),
670 (None, Type::Unknown) => return Type::Unknown,
672 };
673 match self.definitions.structure(&url) {
674 Some(structure) => {
675 if structure.is_abstract {
676 self.error(
677 at.syntax(),
678 format!("`{type_name}` is abstract and cannot be created"),
679 );
680 }
681 Type::Node {
682 structure,
683 path: Vec::new(),
684 }
685 }
686 None => {
687 if self.definitions.covers(&url) {
688 self.error(at.syntax(), format!("unknown type `{type_name}`"));
689 }
690 Type::Unknown
691 }
692 }
693 }
694
695 fn check_call(&mut self, invocation: &Invocation, scope: &Scope, depth: usize) {
698 let name = invocation.name().unwrap_or_default();
699 let arguments: Vec<Expr> = invocation.arguments().collect();
700 let mut types = Vec::new();
701 for argument in &arguments {
702 if argument.syntax().kind() == SyntaxKind::NameRef {
703 let variable = argument.text();
704 match scope.get(&variable) {
705 Some(v) => types.push(Some(v.ty.clone())),
706 None => {
707 self.error(argument.syntax(), format!("unknown variable `{variable}`"));
708 types.push(None);
709 }
710 }
711 } else {
712 types.push(None);
713 }
714 }
715 let Some(group) = self.groups.get(&name).cloned() else {
716 if !self.has_imports {
717 self.error(
718 invocation.syntax(),
719 format!("group `{name}` is not declared and nothing is imported"),
720 );
721 }
722 return;
723 };
724 let parameters = group.parameters().count();
725 if parameters != arguments.len() {
726 self.error(
727 invocation.syntax(),
728 format!(
729 "group `{name}` takes {parameters} argument(s), found {}",
730 arguments.len()
731 ),
732 );
733 return;
734 }
735 self.check_group(&group, types, depth + 1);
736 }
737
738 fn check_expression(&mut self, expression: &Expr, focus: &Type, scope: &Scope) {
746 self.expression_type(expression.syntax(), focus, scope);
747 }
748
749 fn expression_type(&mut self, node: &SyntaxNode, focus: &Type, scope: &Scope) -> Type {
751 match node.kind() {
752 SyntaxKind::NameRef => {
753 let Some(token) = node.first_token() else {
754 return Type::Unknown;
755 };
756 if token.kind() == SyntaxKind::DollarIdent {
757 return if token.text() == "$this" {
758 focus.clone()
759 } else {
760 Type::Unknown
761 };
762 }
763 let name = ast::name_text(&token);
764 if let Some(variable) = scope.get(&name) {
765 return variable.ty.clone();
766 }
767 if focus.fhir_type().as_deref() == Some(name.as_str()) {
769 return focus.clone();
770 }
771 self.element_of(focus, &name, node)
772 }
773 SyntaxKind::MemberExpr => {
774 let mut parts = node.children();
775 let (Some(left), Some(right)) = (parts.next(), parts.next()) else {
776 return Type::Unknown;
777 };
778 let input = self.expression_type(&left, focus, scope);
779 match right.kind() {
780 SyntaxKind::NameRef => match right.first_token() {
781 Some(token) if token.kind() != SyntaxKind::DollarIdent => {
782 self.element_of(&input, &ast::name_text(&token), &right)
783 }
784 _ => Type::Unknown,
785 },
786 SyntaxKind::CallExpr => self.call_type(&right, &input, scope),
787 _ => Type::Unknown,
788 }
789 }
790 SyntaxKind::CallExpr => self.call_type(node, focus, scope),
791 SyntaxKind::ParenExpr => match node.children().next() {
792 Some(inner) => self.expression_type(&inner, focus, scope),
793 None => Type::Unknown,
794 },
795 SyntaxKind::IndexExpr => {
796 let mut parts = node.children();
797 let input = match parts.next() {
798 Some(left) => self.expression_type(&left, focus, scope),
799 None => Type::Unknown,
800 };
801 if let Some(index) = parts.next() {
802 self.expression_type(&index, focus, scope);
803 }
804 input
805 }
806 SyntaxKind::BinaryExpr | SyntaxKind::PrefixExpr | SyntaxKind::TypeExpr => {
807 for child in node.children().filter(|c| Expr::can_cast(c.kind())) {
808 self.expression_type(&child, focus, scope);
809 }
810 Type::Unknown
811 }
812 _ => Type::Unknown,
813 }
814 }
815
816 fn element_of(&mut self, ty: &Type, name: &str, at: &SyntaxNode) -> Type {
817 match self.types().lookup(ty, name) {
818 Lookup::Found(found) => self.types().element_type(&found, None),
819 Lookup::Missing => {
820 let message = format!("`{}` has no element `{name}`", ty.display());
821 self.report(Severity::Warning, trimmed_range(at), message);
822 Type::Unknown
823 }
824 Lookup::Unknown => Type::Unknown,
825 }
826 }
827
828 fn call_type(&mut self, call: &SyntaxNode, input: &Type, scope: &Scope) -> Type {
831 let name = call
832 .first_token()
833 .map(|t| t.text().to_owned())
834 .unwrap_or_default();
835 let arguments: Vec<SyntaxNode> = call
836 .children()
837 .find(|n| n.kind() == SyntaxKind::ArgumentList)
838 .map(|list| {
839 list.children()
840 .filter(|c| Expr::can_cast(c.kind()))
841 .collect()
842 })
843 .unwrap_or_default();
844 let iterating = matches!(
845 name.as_str(),
846 "where" | "select" | "exists" | "all" | "repeat" | "sort" | "aggregate"
847 );
848 let argument_focus = if iterating {
849 input.clone()
850 } else {
851 Type::Unknown
852 };
853 let mut last = Type::Unknown;
854 for argument in &arguments {
855 last = self.expression_type(argument, &argument_focus, scope);
856 }
857 match name.as_str() {
858 "where" | "first" | "last" | "tail" | "skip" | "take" | "single" | "distinct"
859 | "trace" | "repeat" | "sort" => input.clone(),
860 "select" => last,
861 _ => Type::Unknown,
862 }
863 }
864
865 fn walk(
869 &mut self,
870 path: &QualifiedName,
871 mut ty: Type,
872 annotation: Option<&str>,
873 annotation_node: Option<&ast::TypeAnnotation>,
874 ) -> Type {
875 let segments = path.segments();
876 for (index, segment) in segments.iter().enumerate().skip(1) {
877 let last = index == segments.len() - 1;
878 let segment = segment.trim_end_matches("[x]");
879 ty = match self.types().lookup(&ty, segment) {
880 Lookup::Unknown => return Type::Unknown,
881 Lookup::Missing => {
882 let message = format!("`{}` has no element `{segment}`", ty.display());
883 self.error_at(segment_range(path, index), message);
884 return Type::Unknown;
885 }
886 Lookup::Found(found) => {
887 let resolved = annotation.map(|name| {
889 self.type_names
890 .get(name)
891 .cloned()
892 .unwrap_or_else(|| name.to_owned())
893 });
894 let chosen = if last { resolved.as_deref() } else { None };
895 if let (Some(chosen), Some(node)) = (chosen, annotation_node)
896 && !found.element.types.is_empty()
897 && !self
898 .types()
899 .is_one_of(chosen, &found.element.types, &found.owner.url)
900 {
901 self.error(
902 node.syntax(),
903 format!(
904 "`{}` is not a type of `{}.{segment}`; it is {}",
905 annotation.unwrap_or(chosen),
906 ty.display(),
907 found.element.types.join(", ")
908 ),
909 );
910 }
911 self.types().element_type(&found, chosen)
912 }
913 };
914 }
915 ty
916 }
917}
918
919const TRANSFORMS: &[(&str, usize, Option<usize>)] = &[
921 ("create", 0, Some(1)),
922 ("copy", 1, Some(1)),
923 ("truncate", 2, Some(2)),
924 ("escape", 2, Some(3)),
925 ("cast", 1, Some(2)),
926 ("append", 1, None),
927 ("translate", 3, Some(3)),
928 ("reference", 1, Some(1)),
929 ("dateOp", 2, Some(3)),
930 ("uuid", 0, Some(0)),
931 ("pointer", 1, Some(1)),
932 ("evaluate", 1, Some(2)),
933 ("cc", 1, Some(3)),
934 ("c", 2, Some(3)),
935 ("qty", 1, Some(4)),
936 ("id", 2, Some(3)),
937 ("cp", 1, Some(2)),
938 ("toDateTime", 1, Some(2)),
939 ("unixToDateTime", 1, Some(2)),
940 ("toDate", 1, Some(2)),
941 ("unixToDate", 1, Some(2)),
942 ("toTime", 1, Some(2)),
943 ("unixToTime", 1, Some(2)),
944];
945
946#[derive(Debug, Default)]
948struct Scope {
949 frames: Vec<HashMap<String, Variable>>,
950}
951
952impl Scope {
953 fn push(&mut self) {
954 self.frames.push(HashMap::new());
955 }
956
957 fn pop(&mut self) {
958 self.frames.pop();
959 }
960
961 fn define(&mut self, name: String, variable: Variable) {
962 if self.frames.is_empty() {
963 self.frames.push(HashMap::new());
964 }
965 self.frames.last_mut().unwrap().insert(name, variable);
966 }
967
968 fn get(&self, name: &str) -> Option<&Variable> {
969 self.frames.iter().rev().find_map(|frame| frame.get(name))
970 }
971}
972
973fn string_literal(expression: &Expr) -> Option<String> {
975 let literal = ast::Literal::cast(expression.syntax().clone())?;
976 matches!(
977 literal.kind()?,
978 SyntaxKind::SingleQuotedString | SyntaxKind::DoubleQuotedString
979 )
980 .then(|| literal.value())
981 .flatten()
982}
983
984fn segment_range(path: &QualifiedName, index: usize) -> TextRange {
986 path.syntax()
987 .children_with_tokens()
988 .filter_map(|e| e.into_token())
989 .filter(|t| matches!(t.kind(), SyntaxKind::Ident | SyntaxKind::DelimitedIdent))
990 .nth(index)
991 .map(|t| t.text_range())
992 .unwrap_or_else(|| trimmed_range(path.syntax()))
993}
994
995fn trimmed_range(node: &SyntaxNode) -> TextRange {
996 let mut tokens = node
997 .descendants_with_tokens()
998 .filter_map(|e| e.into_token())
999 .filter(|t| !t.kind().is_trivia());
1000 match tokens.next() {
1001 Some(first) => {
1002 let end = tokens
1003 .last()
1004 .map_or(first.text_range().end(), |t| t.text_range().end());
1005 TextRange::new(first.text_range().start(), end)
1006 }
1007 None => node.text_range(),
1008 }
1009}