tree_sitter_graph/
checker.rs

1// -*- coding: utf-8 -*-
2// ------------------------------------------------------------------------------------------------
3// Copyright © 2022, tree-sitter authors.
4// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
5// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6// ------------------------------------------------------------------------------------------------
7
8use std::collections::HashSet;
9use std::path::Path;
10
11use thiserror::Error;
12use tree_sitter::CaptureQuantifier;
13use tree_sitter::CaptureQuantifier::One;
14use tree_sitter::CaptureQuantifier::OneOrMore;
15use tree_sitter::CaptureQuantifier::ZeroOrMore;
16use tree_sitter::CaptureQuantifier::ZeroOrOne;
17use tree_sitter::Query;
18
19use crate::ast;
20use crate::parse_error::Excerpt;
21use crate::parser::FULL_MATCH;
22use crate::variables::MutVariables;
23use crate::variables::VariableError;
24use crate::variables::VariableMap;
25use crate::variables::Variables;
26use crate::Identifier;
27use crate::Location;
28
29#[derive(Debug, Error)]
30pub enum CheckError {
31    #[error("Cannot hide global variable {0} at {1}")]
32    CannotHideGlobalVariable(String, Location),
33    #[error("Cannot set global variable {0} at {1}")]
34    CannotSetGlobalVariable(String, Location),
35    #[error("Duplicate global variable {0} at {1}")]
36    DuplicateGlobalVariable(String, Location),
37    #[error("Expected list value at {0}")]
38    ExpectedListValue(Location),
39    #[error("Expected local value at {0}")]
40    ExpectedLocalValue(Location),
41    #[error("Expected optional value at {0}")]
42    ExpectedOptionalValue(Location),
43    #[error("Nullable regular expression /{0}/ at {1}")]
44    NullableRegex(String, Location),
45    #[error("Undefined syntax capture @{0} at {1}")]
46    UndefinedSyntaxCapture(String, Location),
47    #[error("Undefined variable {0} at {1}")]
48    UndefinedVariable(String, Location),
49    #[error("Unused capture(s) {0} at {1}. Remove or prefix with _.")]
50    UnusedCaptures(String, Location),
51    #[error("{0}: {1} at {2}")]
52    Variable(VariableError, String, Location),
53}
54
55impl CheckError {
56    pub fn display_pretty<'a>(
57        &'a self,
58        path: &'a Path,
59        source: &'a str,
60    ) -> impl std::fmt::Display + 'a {
61        DisplayCheckErrorPretty {
62            error: self,
63            path,
64            source,
65        }
66    }
67}
68
69struct DisplayCheckErrorPretty<'a> {
70    error: &'a CheckError,
71    path: &'a Path,
72    source: &'a str,
73}
74
75impl std::fmt::Display for DisplayCheckErrorPretty<'_> {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        let location = match self.error {
78            CheckError::CannotHideGlobalVariable(_, location) => *location,
79            CheckError::CannotSetGlobalVariable(_, location) => *location,
80            CheckError::DuplicateGlobalVariable(_, location) => *location,
81            CheckError::ExpectedListValue(location) => *location,
82            CheckError::ExpectedLocalValue(location) => *location,
83            CheckError::ExpectedOptionalValue(location) => *location,
84            CheckError::NullableRegex(_, location) => *location,
85            CheckError::UndefinedSyntaxCapture(_, location) => *location,
86            CheckError::UndefinedVariable(_, location) => *location,
87            CheckError::UnusedCaptures(_, location) => *location,
88            CheckError::Variable(_, _, location) => *location,
89        };
90        writeln!(f, "{}", self.error)?;
91        write!(
92            f,
93            "{}",
94            Excerpt::from_source(
95                self.path,
96                self.source,
97                location.row,
98                location.to_column_range(),
99                0
100            )
101        )?;
102        Ok(())
103    }
104}
105
106/// Checker context
107struct CheckContext<'a> {
108    globals: &'a dyn Variables<VariableResult>,
109    file_query: &'a Query,
110    stanza_index: usize,
111    stanza_query: &'a Query,
112    locals: &'a mut dyn MutVariables<VariableResult>,
113}
114
115#[derive(Clone, Debug)]
116struct VariableResult {
117    is_local: bool,
118    quantifier: CaptureQuantifier,
119}
120
121//-----------------------------------------------------------------------------
122// File
123
124impl ast::File {
125    pub fn check(&mut self) -> Result<(), CheckError> {
126        let mut globals = VariableMap::new();
127        for global in &self.globals {
128            globals
129                .add(
130                    global.name.clone(),
131                    VariableResult {
132                        quantifier: global.quantifier,
133                        is_local: true,
134                    },
135                    false,
136                )
137                .map_err(|_| {
138                    CheckError::DuplicateGlobalVariable(
139                        global.name.as_str().to_string(),
140                        global.location,
141                    )
142                })?;
143        }
144        let file_query = self.query.as_ref().unwrap();
145        for (index, stanza) in self.stanzas.iter_mut().enumerate() {
146            stanza.check(&globals, file_query, index)?;
147        }
148        Ok(())
149    }
150}
151
152//-----------------------------------------------------------------------------
153// Stanza
154
155impl ast::Stanza {
156    fn check(
157        &mut self,
158        globals: &dyn Variables<VariableResult>,
159        file_query: &Query,
160        stanza_index: usize,
161    ) -> Result<(), CheckError> {
162        let mut locals = VariableMap::new();
163        let mut ctx = CheckContext {
164            globals,
165            file_query,
166            stanza_index,
167            stanza_query: &self.query,
168            locals: &mut locals,
169        };
170        self.full_match_file_capture_index =
171            ctx.file_query
172                .capture_index_for_name(FULL_MATCH)
173                .expect("missing capture index for full match") as usize;
174
175        let mut used_captures = HashSet::new();
176        for statement in &mut self.statements {
177            let stmt_result = statement.check(&mut ctx)?;
178            used_captures.extend(stmt_result.used_captures);
179        }
180
181        let all_captures = self
182            .query
183            .capture_names()
184            .into_iter()
185            .filter(|cn| {
186                self.query
187                    .capture_index_for_name(cn)
188                    .expect("capture should have index")
189                    != self.full_match_stanza_capture_index as u32
190            })
191            .map(|cn| Identifier::from(*cn))
192            .collect::<HashSet<_>>();
193        let unused_captures = all_captures
194            .difference(&used_captures)
195            .filter(|i| !i.starts_with("_"))
196            .map(|i| format!("@{}", i))
197            .collect::<Vec<_>>();
198        if !unused_captures.is_empty() {
199            return Err(CheckError::UnusedCaptures(
200                unused_captures.join(" "),
201                self.range.start,
202            ));
203        }
204
205        Ok(())
206    }
207}
208
209//-----------------------------------------------------------------------------
210// Statements
211
212#[derive(Clone, Debug)]
213struct StatementResult {
214    used_captures: HashSet<Identifier>,
215}
216
217impl ast::Statement {
218    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
219        match self {
220            Self::DeclareImmutable(stmt) => stmt.check(ctx),
221            Self::DeclareMutable(stmt) => stmt.check(ctx),
222            Self::Assign(stmt) => stmt.check(ctx),
223            Self::CreateGraphNode(stmt) => stmt.check(ctx),
224            Self::AddGraphNodeAttribute(stmt) => stmt.check(ctx),
225            Self::CreateEdge(stmt) => stmt.check(ctx),
226            Self::AddEdgeAttribute(stmt) => stmt.check(ctx),
227            Self::Scan(stmt) => stmt.check(ctx),
228            Self::Print(stmt) => stmt.check(ctx),
229            Self::If(stmt) => stmt.check(ctx),
230            Self::ForIn(stmt) => stmt.check(ctx),
231        }
232    }
233}
234
235impl ast::DeclareImmutable {
236    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
237        let mut used_captures = HashSet::new();
238        let value = self.value.check(ctx)?;
239        used_captures.extend(value.used_captures.iter().cloned());
240        let var_result = self.variable.check_add(ctx, value.into(), false)?;
241        used_captures.extend(var_result.used_captures);
242        Ok(StatementResult { used_captures })
243    }
244}
245
246impl ast::DeclareMutable {
247    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
248        let mut used_captures = HashSet::new();
249        let value = self.value.check(ctx)?;
250        used_captures.extend(value.used_captures.iter().cloned());
251        let var_result = self.variable.check_add(ctx, value.into(), true)?;
252        used_captures.extend(var_result.used_captures);
253        Ok(StatementResult { used_captures })
254    }
255}
256
257impl ast::Assign {
258    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
259        let mut used_captures = HashSet::new();
260        let value = self.value.check(ctx)?;
261        used_captures.extend(value.used_captures.iter().cloned());
262        let var_result = self.variable.check_set(ctx, value.into())?;
263        used_captures.extend(var_result.used_captures);
264        Ok(StatementResult { used_captures })
265    }
266}
267
268impl ast::CreateGraphNode {
269    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
270        let node_result = self.node.check_add(
271            ctx,
272            VariableResult {
273                is_local: true,
274                quantifier: One,
275            },
276            false,
277        )?;
278        Ok(StatementResult {
279            used_captures: node_result.used_captures,
280        })
281    }
282}
283
284impl ast::AddGraphNodeAttribute {
285    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
286        let mut used_captures = HashSet::new();
287        let node_result = self.node.check(ctx)?;
288        used_captures.extend(node_result.used_captures);
289        for attribute in &mut self.attributes {
290            let attr_result = attribute.check(ctx)?;
291            used_captures.extend(attr_result.used_captures);
292        }
293        Ok(StatementResult { used_captures })
294    }
295}
296
297impl ast::CreateEdge {
298    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
299        let mut used_captures = HashSet::new();
300        let source_result = self.source.check(ctx)?;
301        used_captures.extend(source_result.used_captures);
302        let sink_result = self.sink.check(ctx)?;
303        used_captures.extend(sink_result.used_captures);
304        Ok(StatementResult { used_captures })
305    }
306}
307
308impl ast::AddEdgeAttribute {
309    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
310        let mut used_captures = HashSet::new();
311        let source_result = self.source.check(ctx)?;
312        used_captures.extend(source_result.used_captures);
313        let sink_result = self.sink.check(ctx)?;
314        used_captures.extend(sink_result.used_captures);
315        for attribute in &mut self.attributes {
316            let attr_result = attribute.check(ctx)?;
317            used_captures.extend(attr_result.used_captures);
318        }
319        Ok(StatementResult { used_captures })
320    }
321}
322
323impl ast::Scan {
324    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
325        let mut used_captures = HashSet::new();
326
327        let value_result = self.value.check(ctx)?;
328        if !value_result.is_local {
329            return Err(CheckError::ExpectedLocalValue(self.location));
330        }
331        used_captures.extend(value_result.used_captures);
332
333        for arm in &mut self.arms {
334            // Be aware that this check is not complete, as it does not rule out
335            // all regular expressions that admit empty matches. For example, th
336            // regex "\b" matches empty strings within a larger non-empty one.
337            // Therefore, there is also a runtime check that checks that a match was
338            // non-empty. This is all to prevent non-termination of scan.
339            if let Some(_) = arm.regex.captures("") {
340                return Err(CheckError::NullableRegex(
341                    arm.regex.to_string(),
342                    arm.location,
343                ));
344            }
345
346            let mut arm_locals = VariableMap::nested(ctx.locals);
347            let mut arm_ctx = CheckContext {
348                globals: ctx.globals,
349                file_query: ctx.file_query,
350                stanza_index: ctx.stanza_index,
351                stanza_query: ctx.stanza_query,
352                locals: &mut arm_locals,
353            };
354
355            for statement in &mut arm.statements {
356                let stmt_result = statement.check(&mut arm_ctx)?;
357                used_captures.extend(stmt_result.used_captures);
358            }
359        }
360        Ok(StatementResult { used_captures })
361    }
362}
363
364impl ast::Print {
365    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
366        let mut used_captures = HashSet::new();
367        for value in &mut self.values {
368            let value_result = value.check(ctx)?;
369            used_captures.extend(value_result.used_captures);
370        }
371        Ok(StatementResult { used_captures })
372    }
373}
374
375impl ast::If {
376    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
377        let mut used_captures = HashSet::new();
378
379        for arm in &mut self.arms {
380            for condition in &mut arm.conditions {
381                let condition_result = condition.check(ctx)?;
382                used_captures.extend(condition_result.used_captures);
383            }
384
385            let mut arm_locals = VariableMap::nested(ctx.locals);
386            let mut arm_ctx = CheckContext {
387                globals: ctx.globals,
388                file_query: ctx.file_query,
389                stanza_index: ctx.stanza_index,
390                stanza_query: ctx.stanza_query,
391                locals: &mut arm_locals,
392            };
393
394            for statement in &mut arm.statements {
395                let stmt_result = statement.check(&mut arm_ctx)?;
396                used_captures.extend(stmt_result.used_captures);
397            }
398        }
399        Ok(StatementResult { used_captures })
400    }
401}
402
403impl ast::Condition {
404    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
405        let mut used_captures = HashSet::new();
406        match self {
407            Self::None { value, location } | Self::Some { value, location } => {
408                let value_result = value.check(ctx)?;
409                if !value_result.is_local {
410                    return Err(CheckError::ExpectedLocalValue(*location));
411                }
412                if value_result.quantifier != ZeroOrOne {
413                    return Err(CheckError::ExpectedOptionalValue(*location));
414                }
415                used_captures.extend(value_result.used_captures);
416            }
417            Self::Bool { value, location } => {
418                let value_result = value.check(ctx)?;
419                if !value_result.is_local {
420                    return Err(CheckError::ExpectedLocalValue(*location));
421                }
422                used_captures.extend(value_result.used_captures);
423            }
424        }
425        Ok(StatementResult { used_captures })
426    }
427}
428
429impl ast::ForIn {
430    fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
431        let mut used_captures = HashSet::new();
432
433        let value_result = self.value.check(ctx)?;
434        if !value_result.is_local {
435            return Err(CheckError::ExpectedLocalValue(self.location));
436        }
437        if value_result.quantifier != ZeroOrMore && value_result.quantifier != OneOrMore {
438            return Err(CheckError::ExpectedListValue(self.location));
439        }
440        used_captures.extend(value_result.used_captures.iter().cloned());
441
442        let mut loop_locals = VariableMap::nested(ctx.locals);
443        let mut loop_ctx = CheckContext {
444            globals: ctx.globals,
445            file_query: ctx.file_query,
446            stanza_index: ctx.stanza_index,
447            stanza_query: ctx.stanza_query,
448            locals: &mut loop_locals,
449        };
450        let var_result = self
451            .variable
452            .check_add(&mut loop_ctx, value_result.into(), false)?;
453        used_captures.extend(var_result.used_captures);
454
455        for statement in &mut self.statements {
456            let stmt_result = statement.check(&mut loop_ctx)?;
457            used_captures.extend(stmt_result.used_captures);
458        }
459
460        Ok(StatementResult { used_captures })
461    }
462}
463
464//-----------------------------------------------------------------------------
465// Expressions
466
467/// Expression checking result
468#[derive(Clone, Debug)]
469struct ExpressionResult {
470    is_local: bool,
471    quantifier: CaptureQuantifier,
472    used_captures: HashSet<Identifier>,
473}
474
475impl ast::Expression {
476    fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
477        match self {
478            Self::FalseLiteral => Ok(ExpressionResult {
479                is_local: true,
480                quantifier: One,
481                used_captures: HashSet::default(),
482            }),
483            Self::NullLiteral => Ok(ExpressionResult {
484                is_local: true,
485                quantifier: One,
486                used_captures: HashSet::default(),
487            }),
488            Self::TrueLiteral => Ok(ExpressionResult {
489                is_local: true,
490                quantifier: One,
491                used_captures: HashSet::default(),
492            }),
493            Self::IntegerConstant(expr) => expr.check(ctx),
494            Self::StringConstant(expr) => expr.check(ctx),
495            Self::ListLiteral(expr) => expr.check(ctx),
496            Self::SetLiteral(expr) => expr.check(ctx),
497            Self::ListComprehension(expr) => expr.check(ctx),
498            Self::SetComprehension(expr) => expr.check(ctx),
499            Self::Capture(expr) => expr.check(ctx),
500            Self::Variable(expr) => expr.check_get(ctx),
501            Self::Call(expr) => expr.check(ctx),
502            Self::RegexCapture(expr) => expr.check(ctx),
503        }
504    }
505}
506
507impl ast::IntegerConstant {
508    fn check(&mut self, _ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
509        Ok(ExpressionResult {
510            is_local: true,
511            quantifier: One,
512            used_captures: HashSet::default(),
513        })
514    }
515}
516
517impl ast::StringConstant {
518    fn check(&mut self, _ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
519        Ok(ExpressionResult {
520            is_local: true,
521            quantifier: One,
522            used_captures: HashSet::default(),
523        })
524    }
525}
526
527impl ast::ListLiteral {
528    fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
529        let mut is_local = true;
530        let mut used_captures = HashSet::new();
531        for element in &mut self.elements {
532            let element_result = element.check(ctx)?;
533            is_local &= element_result.is_local;
534            used_captures.extend(element_result.used_captures);
535        }
536        Ok(ExpressionResult {
537            is_local,
538            quantifier: ZeroOrMore,
539            used_captures,
540        })
541    }
542}
543
544impl ast::SetLiteral {
545    fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
546        let mut is_local = true;
547        let mut used_captures = HashSet::new();
548        for element in &mut self.elements {
549            let element_result = element.check(ctx)?;
550            is_local &= element_result.is_local;
551            used_captures.extend(element_result.used_captures);
552        }
553        Ok(ExpressionResult {
554            is_local,
555            quantifier: ZeroOrMore,
556            used_captures,
557        })
558    }
559}
560
561impl ast::ListComprehension {
562    fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
563        let mut used_captures = HashSet::new();
564
565        let value_result = self.value.check(ctx)?;
566        if !value_result.is_local {
567            return Err(CheckError::ExpectedLocalValue(self.location));
568        }
569        if value_result.quantifier != ZeroOrMore && value_result.quantifier != OneOrMore {
570            return Err(CheckError::ExpectedListValue(self.location));
571        }
572        used_captures.extend(value_result.used_captures.iter().cloned());
573
574        let mut loop_locals = VariableMap::nested(ctx.locals);
575        let mut loop_ctx = CheckContext {
576            globals: ctx.globals,
577            file_query: ctx.file_query,
578            stanza_index: ctx.stanza_index,
579            stanza_query: ctx.stanza_query,
580            locals: &mut loop_locals,
581        };
582        let var_result = self
583            .variable
584            .check_add(&mut loop_ctx, value_result.into(), false)?;
585        used_captures.extend(var_result.used_captures);
586
587        let element_result = self.element.check(&mut loop_ctx)?;
588        used_captures.extend(element_result.used_captures);
589
590        Ok(ExpressionResult {
591            is_local: element_result.is_local,
592            quantifier: ZeroOrMore,
593            used_captures,
594        })
595    }
596}
597
598impl ast::SetComprehension {
599    fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
600        let mut used_captures = HashSet::new();
601
602        let value_result = self.value.check(ctx)?;
603        if !value_result.is_local {
604            return Err(CheckError::ExpectedLocalValue(self.location));
605        }
606        if value_result.quantifier != ZeroOrMore && value_result.quantifier != OneOrMore {
607            return Err(CheckError::ExpectedListValue(self.location));
608        }
609        used_captures.extend(value_result.used_captures.iter().cloned());
610
611        let mut loop_locals = VariableMap::nested(ctx.locals);
612        let mut loop_ctx = CheckContext {
613            globals: ctx.globals,
614            file_query: ctx.file_query,
615            stanza_index: ctx.stanza_index,
616            stanza_query: ctx.stanza_query,
617            locals: &mut loop_locals,
618        };
619        let var_result = self
620            .variable
621            .check_add(&mut loop_ctx, value_result.into(), false)?;
622        used_captures.extend(var_result.used_captures);
623
624        let element_result = self.element.check(&mut loop_ctx)?;
625        used_captures.extend(element_result.used_captures);
626
627        Ok(ExpressionResult {
628            is_local: element_result.is_local,
629            quantifier: ZeroOrMore,
630            used_captures,
631        })
632    }
633}
634
635impl ast::Capture {
636    fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
637        let name = self.name.to_string();
638        self.stanza_capture_index = ctx
639            .stanza_query
640            .capture_index_for_name(&name)
641            .ok_or_else(|| CheckError::UndefinedSyntaxCapture(name.clone(), self.location))?
642            as usize;
643        self.file_capture_index = ctx
644            .file_query
645            .capture_index_for_name(&name)
646            .expect("missing capture index for name") as usize; // if the previous lookup succeeded, this one should succeed as well
647        self.quantifier =
648            ctx.file_query.capture_quantifiers(ctx.stanza_index)[self.file_capture_index];
649        Ok(ExpressionResult {
650            is_local: true,
651            quantifier: self.quantifier,
652            used_captures: HashSet::from([self.name.clone()]),
653        })
654    }
655}
656
657impl ast::Call {
658    fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
659        let mut is_local = true;
660        let mut used_captures = HashSet::new();
661        for parameter in &mut self.parameters {
662            let parameter_result = parameter.check(ctx)?;
663            is_local &= parameter_result.is_local;
664            used_captures.extend(parameter_result.used_captures);
665        }
666        Ok(ExpressionResult {
667            is_local,
668            quantifier: One, // FIXME we don't really know
669            used_captures,
670        })
671    }
672}
673
674impl ast::RegexCapture {
675    fn check(&mut self, _ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
676        Ok(ExpressionResult {
677            is_local: true,
678            quantifier: One,
679            used_captures: HashSet::default(),
680        })
681    }
682}
683
684//-----------------------------------------------------------------------------
685// Variables
686
687impl ast::Variable {
688    fn check_add(
689        &mut self,
690        ctx: &mut CheckContext,
691        value: VariableResult,
692        mutable: bool,
693    ) -> Result<StatementResult, CheckError> {
694        match self {
695            Self::Unscoped(v) => v.check_add(ctx, value, mutable),
696            Self::Scoped(v) => v.check_add(ctx, value, mutable),
697        }
698    }
699
700    fn check_set(
701        &mut self,
702        ctx: &mut CheckContext,
703        value: VariableResult,
704    ) -> Result<StatementResult, CheckError> {
705        match self {
706            Self::Unscoped(v) => v.check_set(ctx, value),
707            Self::Scoped(v) => v.check_set(ctx, value),
708        }
709    }
710
711    fn check_get(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
712        match self {
713            Self::Unscoped(v) => v.check_get(ctx),
714            Self::Scoped(v) => v.check_get(ctx),
715        }
716    }
717}
718
719impl ast::UnscopedVariable {
720    fn check_add(
721        &mut self,
722        ctx: &mut CheckContext,
723        value: VariableResult,
724        mutable: bool,
725    ) -> Result<StatementResult, CheckError> {
726        if ctx.globals.get(&self.name).is_some() {
727            return Err(CheckError::CannotHideGlobalVariable(
728                self.name.as_str().to_string(),
729                self.location,
730            ));
731        }
732        let mut value = value;
733        // Mutable variables are not considered local, because a non-local
734        // assignment in a loop could invalidate an earlier local assignment.
735        // Since we process all statement in order, we don't have info on later
736        // assignments, and can assume non-local to be sound.
737        if mutable {
738            value.is_local = false;
739        }
740        ctx.locals
741            .add(self.name.clone(), value, mutable)
742            .map_err(|e| CheckError::Variable(e, format!("{}", self.name), self.location))?;
743        Ok(StatementResult {
744            used_captures: HashSet::default(),
745        })
746    }
747
748    fn check_set(
749        &mut self,
750        ctx: &mut CheckContext,
751        value: VariableResult,
752    ) -> Result<StatementResult, CheckError> {
753        if ctx.globals.get(&self.name).is_some() {
754            return Err(CheckError::CannotSetGlobalVariable(
755                self.name.as_str().to_string(),
756                self.location,
757            ));
758        }
759        let mut value = value;
760        // Mutable variables are not considered local, because a non-local
761        // assignment in a loop could invalidate an earlier local assignment.
762        // Since we process all statement in order, we don't have info on later
763        // assignments, and can assume non-local to be sound.
764        value.is_local = false;
765        ctx.locals
766            .set(self.name.clone(), value)
767            .map_err(|e| CheckError::Variable(e, format!("{}", self.name), self.location))?;
768        Ok(StatementResult {
769            used_captures: HashSet::default(),
770        })
771    }
772
773    fn check_get(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
774        if let Some(result) = ctx.globals.get(&self.name) {
775            Some(result)
776        } else {
777            ctx.locals.get(&self.name)
778        }
779        .map(|value| value.into())
780        .ok_or_else(|| CheckError::UndefinedVariable(self.name.as_str().to_string(), self.location))
781    }
782}
783
784impl ast::ScopedVariable {
785    fn check_add(
786        &mut self,
787        ctx: &mut CheckContext,
788        _value: VariableResult,
789        _mutable: bool,
790    ) -> Result<StatementResult, CheckError> {
791        let scope_result = self.scope.check(ctx)?;
792        Ok(scope_result.into())
793    }
794
795    fn check_set(
796        &mut self,
797        ctx: &mut CheckContext,
798        _value: VariableResult,
799    ) -> Result<StatementResult, CheckError> {
800        let scope_result = self.scope.check(ctx)?;
801        Ok(scope_result.into())
802    }
803
804    fn check_get(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
805        let scope_result = self.scope.check(ctx)?;
806        Ok(ExpressionResult {
807            is_local: false,
808            quantifier: One, // FIXME we don't really know
809            used_captures: scope_result.used_captures,
810        })
811    }
812}
813
814//-----------------------------------------------------------------------------
815// Attributes
816
817#[derive(Clone, Debug)]
818struct AttributeResult {
819    used_captures: HashSet<Identifier>,
820}
821
822impl ast::Attribute {
823    fn check(&mut self, ctx: &mut CheckContext) -> Result<AttributeResult, CheckError> {
824        let value_result = self.value.check(ctx)?;
825        Ok(AttributeResult {
826            used_captures: value_result.used_captures,
827        })
828    }
829}
830
831//-----------------------------------------------------------------------------
832// Result Conversions
833
834impl Into<StatementResult> for ExpressionResult {
835    fn into(self) -> StatementResult {
836        StatementResult {
837            used_captures: self.used_captures,
838        }
839    }
840}
841
842impl Into<ExpressionResult> for &VariableResult {
843    fn into(self) -> ExpressionResult {
844        ExpressionResult {
845            is_local: self.is_local,
846            quantifier: self.quantifier,
847            used_captures: HashSet::default(),
848        }
849    }
850}
851
852impl Into<VariableResult> for ExpressionResult {
853    fn into(self) -> VariableResult {
854        VariableResult {
855            is_local: self.is_local,
856            quantifier: self.quantifier,
857        }
858    }
859}