Skip to main content

fml_check/
check.rs

1//! The checker: walks a map with the types of its variables and reports what cannot work.
2
3use 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
14/// Checks a parsed map. Syntax errors are not repeated; see [`fml_syntax::Parse::diagnostics`].
15pub 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
23/// How deep untyped groups are followed into the groups they call.
24const 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    /// `uses` aliases and the names of unaliased structures, to their URLs.
42    type_names: HashMap<String, String>,
43    groups: HashMap<String, ast::Group>,
44    has_imports: bool,
45    /// Groups already checked with given argument types.
46    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(&parameter))
94                .collect();
95            self.check_group(group, types, 0);
96        }
97    }
98
99    // ---- Declarations -----------------------------------------------------------------------
100
101    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    /// The declared type of a group parameter: a `uses` alias, the name of a used structure, or a
154    /// core type name.
155    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    // ---- Groups -----------------------------------------------------------------------------
175
176    /// Checks a group with argument types: declared types, or for untyped parameters the types
177    /// inferred at a call site (`None` when not known).
178    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    // ---- Rules ------------------------------------------------------------------------------
220
221    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        // `where`, `check` and `log` see each item as the focus, and its variable.
281        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    /// `src -> tgt : a, b;`: each field exists on both sides.
294    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    /// Checks a transform; returns the type of the value it produces where that is known.
348    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    /// The type `create('Type')` makes, checked against the element it is assigned to.
437    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            // The element's type, and so the FHIR release the created type belongs to, is unknown.
442            (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    /// `then Group(a, b)`: the group exists, takes that many arguments, and is checked with the
467    /// argument types when its parameters are untyped.
468    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    // ---- Element paths ----------------------------------------------------------------------
510
511    // ---- FHIRPath ---------------------------------------------------------------------------
512
513    /// Checks the element paths of a FHIRPath expression: a name that is not a variable is an
514    /// element of `focus`. Problems are warnings, since a wrong path gives an empty result rather
515    /// than an error, but it is nearly always a mistake.
516    fn check_expression(&mut self, expression: &Expr, focus: &Type, scope: &Scope) {
517        self.expression_type(expression.syntax(), focus, scope);
518    }
519
520    /// The type an expression gives, where it is known, reporting missing elements on the way.
521    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                // `Patient.name` on a Patient: the type name selects the focus itself.
539                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    /// A function call on `input`. The iterating functions see each input item as the focus of
600    /// their argument; the arguments of the others are left unchecked.
601    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    /// Follows `context.a.b` from the context variable's type, reporting missing elements, and
637    /// returns the type at the end. `annotation` is the source's `: Type`, which chooses a type of
638    /// a choice element.
639    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
682/// Transforms with their argument counts (minimum, maximum).
683const 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/// Variables by name, in nested frames: group parameters, then one frame per rule.
710#[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
736/// A string literal argument's value.
737fn 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
747/// The range of the `index`-th name in `a.b.c`.
748fn 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}