1use 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
106struct 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
121impl 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
152impl 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#[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 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#[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; 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, 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
684impl 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 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 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, used_captures: scope_result.used_captures,
810 })
811 }
812}
813
814#[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
831impl 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}