fork of https://github.com/tree-sitter/tree-sitter-graph
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::Expr(stmt) => stmt.check(ctx),
224 Self::CreateGraphNode(stmt) => stmt.check(ctx),
225 Self::AddGraphNodeAttribute(stmt) => stmt.check(ctx),
226 Self::CreateEdge(stmt) => stmt.check(ctx),
227 Self::AddEdgeAttribute(stmt) => stmt.check(ctx),
228 Self::Scan(stmt) => stmt.check(ctx),
229 Self::Print(stmt) => stmt.check(ctx),
230 Self::If(stmt) => stmt.check(ctx),
231 Self::ForIn(stmt) => stmt.check(ctx),
232 }
233 }
234}
235
236impl ast::DeclareImmutable {
237 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
238 let mut used_captures = HashSet::new();
239 let value = self.value.check(ctx)?;
240 used_captures.extend(value.used_captures.iter().cloned());
241 let var_result = self.variable.check_add(ctx, value.into(), false)?;
242 used_captures.extend(var_result.used_captures);
243 Ok(StatementResult { used_captures })
244 }
245}
246
247impl ast::DeclareMutable {
248 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
249 let mut used_captures = HashSet::new();
250 let value = self.value.check(ctx)?;
251 used_captures.extend(value.used_captures.iter().cloned());
252 let var_result = self.variable.check_add(ctx, value.into(), true)?;
253 used_captures.extend(var_result.used_captures);
254 Ok(StatementResult { used_captures })
255 }
256}
257
258impl ast::Assign {
259 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
260 let mut used_captures = HashSet::new();
261 let value = self.value.check(ctx)?;
262 used_captures.extend(value.used_captures.iter().cloned());
263 let var_result = self.variable.check_set(ctx, value.into())?;
264 used_captures.extend(var_result.used_captures);
265 Ok(StatementResult { used_captures })
266 }
267}
268
269impl ast::ExpressionStatement {
270 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
271 let mut used_captures = HashSet::new();
272 let value = self.value.check(ctx)?;
273 used_captures.extend(value.used_captures.iter().cloned());
274 Ok(StatementResult { used_captures })
275 }
276}
277
278impl ast::CreateGraphNode {
279 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
280 let node_result = self.node.check_add(
281 ctx,
282 VariableResult {
283 is_local: true,
284 quantifier: One,
285 },
286 false,
287 )?;
288 Ok(StatementResult {
289 used_captures: node_result.used_captures,
290 })
291 }
292}
293
294impl ast::AddGraphNodeAttribute {
295 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
296 let mut used_captures = HashSet::new();
297 let node_result = self.node.check(ctx)?;
298 used_captures.extend(node_result.used_captures);
299 for attribute in &mut self.attributes {
300 let attr_result = attribute.check(ctx)?;
301 used_captures.extend(attr_result.used_captures);
302 }
303 Ok(StatementResult { used_captures })
304 }
305}
306
307impl ast::CreateEdge {
308 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
309 let mut used_captures = HashSet::new();
310 let source_result = self.source.check(ctx)?;
311 used_captures.extend(source_result.used_captures);
312 let sink_result = self.sink.check(ctx)?;
313 used_captures.extend(sink_result.used_captures);
314 Ok(StatementResult { used_captures })
315 }
316}
317
318impl ast::AddEdgeAttribute {
319 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
320 let mut used_captures = HashSet::new();
321 let source_result = self.source.check(ctx)?;
322 used_captures.extend(source_result.used_captures);
323 let sink_result = self.sink.check(ctx)?;
324 used_captures.extend(sink_result.used_captures);
325 for attribute in &mut self.attributes {
326 let attr_result = attribute.check(ctx)?;
327 used_captures.extend(attr_result.used_captures);
328 }
329 Ok(StatementResult { used_captures })
330 }
331}
332
333impl ast::Scan {
334 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
335 let mut used_captures = HashSet::new();
336
337 let value_result = self.value.check(ctx)?;
338 if !value_result.is_local {
339 return Err(CheckError::ExpectedLocalValue(self.location));
340 }
341 used_captures.extend(value_result.used_captures);
342
343 for arm in &mut self.arms {
344 // Be aware that this check is not complete, as it does not rule out
345 // all regular expressions that admit empty matches. For example, th
346 // regex "\b" matches empty strings within a larger non-empty one.
347 // Therefore, there is also a runtime check that checks that a match was
348 // non-empty. This is all to prevent non-termination of scan.
349 if let Some(_) = arm.regex.captures("") {
350 return Err(CheckError::NullableRegex(
351 arm.regex.to_string(),
352 arm.location,
353 ));
354 }
355
356 let mut arm_locals = VariableMap::nested(ctx.locals);
357 let mut arm_ctx = CheckContext {
358 globals: ctx.globals,
359 file_query: ctx.file_query,
360 stanza_index: ctx.stanza_index,
361 stanza_query: ctx.stanza_query,
362 locals: &mut arm_locals,
363 };
364
365 for statement in &mut arm.statements {
366 let stmt_result = statement.check(&mut arm_ctx)?;
367 used_captures.extend(stmt_result.used_captures);
368 }
369 }
370 Ok(StatementResult { used_captures })
371 }
372}
373
374impl ast::Print {
375 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
376 let mut used_captures = HashSet::new();
377 for value in &mut self.values {
378 let value_result = value.check(ctx)?;
379 used_captures.extend(value_result.used_captures);
380 }
381 Ok(StatementResult { used_captures })
382 }
383}
384
385impl ast::If {
386 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
387 let mut used_captures = HashSet::new();
388
389 for arm in &mut self.arms {
390 for condition in &mut arm.conditions {
391 let condition_result = condition.check(ctx)?;
392 used_captures.extend(condition_result.used_captures);
393 }
394
395 let mut arm_locals = VariableMap::nested(ctx.locals);
396 let mut arm_ctx = CheckContext {
397 globals: ctx.globals,
398 file_query: ctx.file_query,
399 stanza_index: ctx.stanza_index,
400 stanza_query: ctx.stanza_query,
401 locals: &mut arm_locals,
402 };
403
404 for statement in &mut arm.statements {
405 let stmt_result = statement.check(&mut arm_ctx)?;
406 used_captures.extend(stmt_result.used_captures);
407 }
408 }
409 Ok(StatementResult { used_captures })
410 }
411}
412
413impl ast::Condition {
414 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
415 let mut used_captures = HashSet::new();
416 match self {
417 Self::None { value, location } | Self::Some { value, location } => {
418 let value_result = value.check(ctx)?;
419 if !value_result.is_local {
420 return Err(CheckError::ExpectedLocalValue(*location));
421 }
422 if value_result.quantifier != ZeroOrOne {
423 return Err(CheckError::ExpectedOptionalValue(*location));
424 }
425 used_captures.extend(value_result.used_captures);
426 }
427 Self::Bool { value, location } => {
428 let value_result = value.check(ctx)?;
429 if !value_result.is_local {
430 return Err(CheckError::ExpectedLocalValue(*location));
431 }
432 used_captures.extend(value_result.used_captures);
433 }
434 }
435 Ok(StatementResult { used_captures })
436 }
437}
438
439impl ast::ForIn {
440 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
441 let mut used_captures = HashSet::new();
442
443 let value_result = self.value.check(ctx)?;
444 if !value_result.is_local {
445 return Err(CheckError::ExpectedLocalValue(self.location));
446 }
447 if value_result.quantifier != ZeroOrMore && value_result.quantifier != OneOrMore {
448 return Err(CheckError::ExpectedListValue(self.location));
449 }
450 used_captures.extend(value_result.used_captures.iter().cloned());
451
452 let mut loop_locals = VariableMap::nested(ctx.locals);
453 let mut loop_ctx = CheckContext {
454 globals: ctx.globals,
455 file_query: ctx.file_query,
456 stanza_index: ctx.stanza_index,
457 stanza_query: ctx.stanza_query,
458 locals: &mut loop_locals,
459 };
460 let var_result = self
461 .variable
462 .check_add(&mut loop_ctx, value_result.into(), false)?;
463 used_captures.extend(var_result.used_captures);
464
465 for statement in &mut self.statements {
466 let stmt_result = statement.check(&mut loop_ctx)?;
467 used_captures.extend(stmt_result.used_captures);
468 }
469
470 Ok(StatementResult { used_captures })
471 }
472}
473
474//-----------------------------------------------------------------------------
475// Expressions
476
477/// Expression checking result
478#[derive(Clone, Debug)]
479struct ExpressionResult {
480 is_local: bool,
481 quantifier: CaptureQuantifier,
482 used_captures: HashSet<Identifier>,
483}
484
485impl ast::Expression {
486 fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
487 match self {
488 Self::FalseLiteral => Ok(ExpressionResult {
489 is_local: true,
490 quantifier: One,
491 used_captures: HashSet::default(),
492 }),
493 Self::NullLiteral => Ok(ExpressionResult {
494 is_local: true,
495 quantifier: One,
496 used_captures: HashSet::default(),
497 }),
498 Self::TrueLiteral => Ok(ExpressionResult {
499 is_local: true,
500 quantifier: One,
501 used_captures: HashSet::default(),
502 }),
503 Self::IntegerConstant(expr) => expr.check(ctx),
504 Self::StringConstant(expr) => expr.check(ctx),
505 Self::ListLiteral(expr) => expr.check(ctx),
506 Self::SetLiteral(expr) => expr.check(ctx),
507 Self::ListComprehension(expr) => expr.check(ctx),
508 Self::SetComprehension(expr) => expr.check(ctx),
509 Self::Capture(expr) => expr.check(ctx),
510 Self::Variable(expr) => expr.check_get(ctx),
511 Self::Call(expr) => expr.check(ctx),
512 Self::RegexCapture(expr) => expr.check(ctx),
513 }
514 }
515}
516
517impl ast::IntegerConstant {
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::StringConstant {
528 fn check(&mut self, _ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
529 Ok(ExpressionResult {
530 is_local: true,
531 quantifier: One,
532 used_captures: HashSet::default(),
533 })
534 }
535}
536
537impl ast::ListLiteral {
538 fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
539 let mut is_local = true;
540 let mut used_captures = HashSet::new();
541 for element in &mut self.elements {
542 let element_result = element.check(ctx)?;
543 is_local &= element_result.is_local;
544 used_captures.extend(element_result.used_captures);
545 }
546 Ok(ExpressionResult {
547 is_local,
548 quantifier: ZeroOrMore,
549 used_captures,
550 })
551 }
552}
553
554impl ast::SetLiteral {
555 fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
556 let mut is_local = true;
557 let mut used_captures = HashSet::new();
558 for element in &mut self.elements {
559 let element_result = element.check(ctx)?;
560 is_local &= element_result.is_local;
561 used_captures.extend(element_result.used_captures);
562 }
563 Ok(ExpressionResult {
564 is_local,
565 quantifier: ZeroOrMore,
566 used_captures,
567 })
568 }
569}
570
571impl ast::ListComprehension {
572 fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
573 let mut used_captures = HashSet::new();
574
575 let value_result = self.value.check(ctx)?;
576 if !value_result.is_local {
577 return Err(CheckError::ExpectedLocalValue(self.location));
578 }
579 if value_result.quantifier != ZeroOrMore && value_result.quantifier != OneOrMore {
580 return Err(CheckError::ExpectedListValue(self.location));
581 }
582 used_captures.extend(value_result.used_captures.iter().cloned());
583
584 let mut loop_locals = VariableMap::nested(ctx.locals);
585 let mut loop_ctx = CheckContext {
586 globals: ctx.globals,
587 file_query: ctx.file_query,
588 stanza_index: ctx.stanza_index,
589 stanza_query: ctx.stanza_query,
590 locals: &mut loop_locals,
591 };
592 let var_result = self
593 .variable
594 .check_add(&mut loop_ctx, value_result.into(), false)?;
595 used_captures.extend(var_result.used_captures);
596
597 let element_result = self.element.check(&mut loop_ctx)?;
598 used_captures.extend(element_result.used_captures);
599
600 Ok(ExpressionResult {
601 is_local: element_result.is_local,
602 quantifier: ZeroOrMore,
603 used_captures,
604 })
605 }
606}
607
608impl ast::SetComprehension {
609 fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
610 let mut used_captures = HashSet::new();
611
612 let value_result = self.value.check(ctx)?;
613 if !value_result.is_local {
614 return Err(CheckError::ExpectedLocalValue(self.location));
615 }
616 if value_result.quantifier != ZeroOrMore && value_result.quantifier != OneOrMore {
617 return Err(CheckError::ExpectedListValue(self.location));
618 }
619 used_captures.extend(value_result.used_captures.iter().cloned());
620
621 let mut loop_locals = VariableMap::nested(ctx.locals);
622 let mut loop_ctx = CheckContext {
623 globals: ctx.globals,
624 file_query: ctx.file_query,
625 stanza_index: ctx.stanza_index,
626 stanza_query: ctx.stanza_query,
627 locals: &mut loop_locals,
628 };
629 let var_result = self
630 .variable
631 .check_add(&mut loop_ctx, value_result.into(), false)?;
632 used_captures.extend(var_result.used_captures);
633
634 let element_result = self.element.check(&mut loop_ctx)?;
635 used_captures.extend(element_result.used_captures);
636
637 Ok(ExpressionResult {
638 is_local: element_result.is_local,
639 quantifier: ZeroOrMore,
640 used_captures,
641 })
642 }
643}
644
645impl ast::Capture {
646 fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
647 let name = self.name.to_string();
648 self.stanza_capture_index = ctx
649 .stanza_query
650 .capture_index_for_name(&name)
651 .ok_or_else(|| CheckError::UndefinedSyntaxCapture(name.clone(), self.location))?
652 as usize;
653 self.file_capture_index = ctx
654 .file_query
655 .capture_index_for_name(&name)
656 .expect("missing capture index for name") as usize; // if the previous lookup succeeded, this one should succeed as well
657 self.quantifier =
658 ctx.file_query.capture_quantifiers(ctx.stanza_index)[self.file_capture_index];
659 Ok(ExpressionResult {
660 is_local: true,
661 quantifier: self.quantifier,
662 used_captures: HashSet::from([self.name.clone()]),
663 })
664 }
665}
666
667impl ast::Call {
668 fn check(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
669 let mut is_local = true;
670 let mut used_captures = HashSet::new();
671 for parameter in &mut self.parameters {
672 let parameter_result = parameter.check(ctx)?;
673 is_local &= parameter_result.is_local;
674 used_captures.extend(parameter_result.used_captures);
675 }
676 Ok(ExpressionResult {
677 is_local,
678 quantifier: One, // FIXME we don't really know
679 used_captures,
680 })
681 }
682}
683
684impl ast::RegexCapture {
685 fn check(&mut self, _ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
686 Ok(ExpressionResult {
687 is_local: true,
688 quantifier: One,
689 used_captures: HashSet::default(),
690 })
691 }
692}
693
694//-----------------------------------------------------------------------------
695// Variables
696
697impl ast::Variable {
698 fn check_add(
699 &mut self,
700 ctx: &mut CheckContext,
701 value: VariableResult,
702 mutable: bool,
703 ) -> Result<StatementResult, CheckError> {
704 match self {
705 Self::Unscoped(v) => v.check_add(ctx, value, mutable),
706 Self::Scoped(v) => v.check_add(ctx, value, mutable),
707 }
708 }
709
710 fn check_set(
711 &mut self,
712 ctx: &mut CheckContext,
713 value: VariableResult,
714 ) -> Result<StatementResult, CheckError> {
715 match self {
716 Self::Unscoped(v) => v.check_set(ctx, value),
717 Self::Scoped(v) => v.check_set(ctx, value),
718 }
719 }
720
721 fn check_get(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
722 match self {
723 Self::Unscoped(v) => v.check_get(ctx),
724 Self::Scoped(v) => v.check_get(ctx),
725 }
726 }
727}
728
729impl ast::UnscopedVariable {
730 fn check_add(
731 &mut self,
732 ctx: &mut CheckContext,
733 value: VariableResult,
734 mutable: bool,
735 ) -> Result<StatementResult, CheckError> {
736 if ctx.globals.get(&self.name).is_some() {
737 return Err(CheckError::CannotHideGlobalVariable(
738 self.name.as_str().to_string(),
739 self.location,
740 ));
741 }
742 let mut value = value;
743 // Mutable variables are not considered local, because a non-local
744 // assignment in a loop could invalidate an earlier local assignment.
745 // Since we process all statement in order, we don't have info on later
746 // assignments, and can assume non-local to be sound.
747 if mutable {
748 value.is_local = false;
749 }
750 ctx.locals
751 .add(self.name.clone(), value, mutable)
752 .map_err(|e| CheckError::Variable(e, format!("{}", self.name), self.location))?;
753 Ok(StatementResult {
754 used_captures: HashSet::default(),
755 })
756 }
757
758 fn check_set(
759 &mut self,
760 ctx: &mut CheckContext,
761 value: VariableResult,
762 ) -> Result<StatementResult, CheckError> {
763 if ctx.globals.get(&self.name).is_some() {
764 return Err(CheckError::CannotSetGlobalVariable(
765 self.name.as_str().to_string(),
766 self.location,
767 ));
768 }
769 let mut value = value;
770 // Mutable variables are not considered local, because a non-local
771 // assignment in a loop could invalidate an earlier local assignment.
772 // Since we process all statement in order, we don't have info on later
773 // assignments, and can assume non-local to be sound.
774 value.is_local = false;
775 ctx.locals
776 .set(self.name.clone(), value)
777 .map_err(|e| CheckError::Variable(e, format!("{}", self.name), self.location))?;
778 Ok(StatementResult {
779 used_captures: HashSet::default(),
780 })
781 }
782
783 fn check_get(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
784 if let Some(result) = ctx.globals.get(&self.name) {
785 Some(result)
786 } else {
787 ctx.locals.get(&self.name)
788 }
789 .map(|value| value.into())
790 .ok_or_else(|| CheckError::UndefinedVariable(self.name.as_str().to_string(), self.location))
791 }
792}
793
794impl ast::ScopedVariable {
795 fn check_add(
796 &mut self,
797 ctx: &mut CheckContext,
798 _value: VariableResult,
799 _mutable: bool,
800 ) -> Result<StatementResult, CheckError> {
801 let scope_result = self.scope.check(ctx)?;
802 Ok(scope_result.into())
803 }
804
805 fn check_set(
806 &mut self,
807 ctx: &mut CheckContext,
808 _value: VariableResult,
809 ) -> Result<StatementResult, CheckError> {
810 let scope_result = self.scope.check(ctx)?;
811 Ok(scope_result.into())
812 }
813
814 fn check_get(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
815 let scope_result = self.scope.check(ctx)?;
816 Ok(ExpressionResult {
817 is_local: false,
818 quantifier: One, // FIXME we don't really know
819 used_captures: scope_result.used_captures,
820 })
821 }
822}
823
824//-----------------------------------------------------------------------------
825// Attributes
826
827#[derive(Clone, Debug)]
828struct AttributeResult {
829 used_captures: HashSet<Identifier>,
830}
831
832impl ast::Attribute {
833 fn check(&mut self, ctx: &mut CheckContext) -> Result<AttributeResult, CheckError> {
834 let value_result = self.value.check(ctx)?;
835 Ok(AttributeResult {
836 used_captures: value_result.used_captures,
837 })
838 }
839}
840
841//-----------------------------------------------------------------------------
842// Result Conversions
843
844impl Into<StatementResult> for ExpressionResult {
845 fn into(self) -> StatementResult {
846 StatementResult {
847 used_captures: self.used_captures,
848 }
849 }
850}
851
852impl Into<ExpressionResult> for &VariableResult {
853 fn into(self) -> ExpressionResult {
854 ExpressionResult {
855 is_local: self.is_local,
856 quantifier: self.quantifier,
857 used_captures: HashSet::default(),
858 }
859 }
860}
861
862impl Into<VariableResult> for ExpressionResult {
863 fn into(self) -> VariableResult {
864 VariableResult {
865 is_local: self.is_local,
866 quantifier: self.quantifier,
867 }
868 }
869}