Skip to main content

fml_check/
analysis.rs

1//! What the checker learns about a map besides its problems: the variables, where each is
2//! declared and visible, and what each name in the map refers to. Editors use it for hover,
3//! completion, go to definition, find references and rename.
4
5use fml_syntax::{Diagnostic, TextRange, TextSize};
6
7use crate::types::{ElementRef, Type};
8
9/// The result of [`analyze`](crate::analyze).
10#[derive(Debug, Clone, Default)]
11pub struct Analysis {
12    /// The problems [`check`](crate::check) reports.
13    pub diagnostics: Vec<Diagnostic>,
14    /// Constants, group parameters and rule aliases, in the order they are declared.
15    pub bindings: Vec<Binding>,
16    /// The names that refer to something, ordered by position.
17    pub references: Vec<Reference>,
18}
19
20/// How a variable came to be.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum BindingKind {
23    /// `let name = …;`
24    Constant,
25    /// `source src : Type` or `target tgt : Type` in a group's parameters.
26    SourceParameter,
27    TargetParameter,
28    /// `src.name as n`
29    Source,
30    /// `tgt.name as n`
31    Target,
32}
33
34impl BindingKind {
35    /// Whether the variable is a target, which rules write into.
36    pub fn is_target(self) -> bool {
37        matches!(self, BindingKind::TargetParameter | BindingKind::Target)
38    }
39}
40
41/// A variable.
42#[derive(Debug, Clone)]
43pub struct Binding {
44    pub name: String,
45    pub kind: BindingKind,
46    /// The name where it is declared.
47    pub definition: TextRange,
48    /// Where it can be used: the group of a parameter, the rest of the rule of an alias, the
49    /// whole map for a constant.
50    pub visible: TextRange,
51    /// The type, where known. An untyped group parameter has the type of the first call that
52    /// gives one.
53    pub ty: Type,
54}
55
56/// What a name refers to.
57#[derive(Debug, Clone)]
58pub enum Referent {
59    /// A variable: an index into [`Analysis::bindings`].
60    Binding(usize),
61    /// An element of a type.
62    Element(Box<ElementRef>),
63    /// A group, by name.
64    Group(String),
65    /// A transform, such as `create` or `translate`.
66    Transform(String),
67    /// A type: a parameter or source type, or what `create('Type')` makes.
68    Type(Type),
69}
70
71/// A name in the map and what it refers to.
72#[derive(Debug, Clone)]
73pub struct Reference {
74    pub range: TextRange,
75    pub referent: Referent,
76    /// The type of the value the name gives, where known.
77    pub ty: Type,
78}
79
80impl Analysis {
81    /// The reference whose name covers `offset`, or ends at it (the cursor right after a name).
82    pub fn reference_at(&self, offset: TextSize) -> Option<&Reference> {
83        let index = self.references.partition_point(|r| r.range.end() < offset);
84        self.references[index..]
85            .iter()
86            .take_while(|r| r.range.start() <= offset)
87            .find(|r| r.range.contains_inclusive(offset))
88    }
89
90    /// The variable declared at `offset`.
91    pub fn binding_at(&self, offset: TextSize) -> Option<(usize, &Binding)> {
92        self.bindings
93            .iter()
94            .enumerate()
95            .find(|(_, b)| b.definition.contains_inclusive(offset))
96    }
97
98    /// The variables that can be used at `offset`, the innermost one for each name.
99    pub fn bindings_at(&self, offset: TextSize) -> Vec<&Binding> {
100        let mut visible: Vec<&Binding> = Vec::new();
101        for binding in self
102            .bindings
103            .iter()
104            .filter(|b| b.visible.contains_inclusive(offset))
105        {
106            match visible.iter_mut().find(|b| b.name == binding.name) {
107                Some(seen) if binding.visible.len() < seen.visible.len() => *seen = binding,
108                Some(_) => {}
109                None => visible.push(binding),
110            }
111        }
112        visible
113    }
114
115    /// The references to the variable `binding`.
116    pub fn references_to(&self, binding: usize) -> impl Iterator<Item = &Reference> {
117        self.references
118            .iter()
119            .filter(move |r| matches!(r.referent, Referent::Binding(b) if b == binding))
120    }
121}