···4646 UndefinedSyntaxCapture(String, Location),
4747 #[error("Undefined variable {0} at {1}")]
4848 UndefinedVariable(String, Location),
4949+ #[error("Unused capture(s) {0} at {1}. Remove or prefix with _.")]
5050+ UnusedCaptures(String, Location),
4951 #[error("{0}: {1} at {2}")]
5052 Variable(VariableError, String, Location),
5153}
···8284 CheckError::NullableRegex(_, location) => *location,
8385 CheckError::UndefinedSyntaxCapture(_, location) => *location,
8486 CheckError::UndefinedVariable(_, location) => *location,
8787+ CheckError::UnusedCaptures(_, location) => *location,
8588 CheckError::Variable(_, _, location) => *location,
8689 };
8790 writeln!(f, "{}", self.error)?;
···163166 ctx.file_query
164167 .capture_index_for_name(FULL_MATCH)
165168 .expect("missing capture index for full match") as usize;
169169+170170+ let mut used_captures = HashSet::new();
166171 for statement in &mut self.statements {
167167- statement.check(&mut ctx)?;
172172+ let stmt_result = statement.check(&mut ctx)?;
173173+ used_captures.extend(stmt_result.used_captures);
174174+ }
175175+176176+ let all_captures = self
177177+ .query
178178+ .capture_names()
179179+ .into_iter()
180180+ .filter(|cn| {
181181+ self.query
182182+ .capture_index_for_name(cn)
183183+ .expect("capture should have index")
184184+ != self.full_match_stanza_capture_index as u32
185185+ })
186186+ .map(|cn| Identifier::from(cn.as_str()))
187187+ .collect::<HashSet<_>>();
188188+ let unused_captures = all_captures
189189+ .difference(&used_captures)
190190+ .filter(|i| !i.starts_with("_"))
191191+ .map(|i| format!("@{}", i))
192192+ .collect::<Vec<_>>();
193193+ if !unused_captures.is_empty() {
194194+ return Err(CheckError::UnusedCaptures(
195195+ unused_captures.join(" "),
196196+ self.location,
197197+ ));
168198 }
199199+169200 Ok(())
170201 }
171202}
···198229199230impl ast::DeclareImmutable {
200231 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
232232+ let mut used_captures = HashSet::new();
201233 let value = self.value.check(ctx)?;
202202- let used_captures = value.used_captures.clone();
203203- self.variable.check_add(ctx, value, false)?;
234234+ used_captures.extend(value.used_captures.iter().cloned());
235235+ let var_result = self.variable.check_add(ctx, value, false)?;
236236+ used_captures.extend(var_result.used_captures);
204237 Ok(StatementResult { used_captures })
205238 }
206239}
207240208241impl ast::DeclareMutable {
209242 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
243243+ let mut used_captures = HashSet::new();
210244 let value = self.value.check(ctx)?;
211211- let used_captures = value.used_captures.clone();
212212- self.variable.check_add(ctx, value, true)?;
245245+ used_captures.extend(value.used_captures.iter().cloned());
246246+ let var_result = self.variable.check_add(ctx, value, true)?;
247247+ used_captures.extend(var_result.used_captures);
213248 Ok(StatementResult { used_captures })
214249 }
215250}
216251217252impl ast::Assign {
218253 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
254254+ let mut used_captures = HashSet::new();
219255 let value = self.value.check(ctx)?;
220220- let used_captures = value.used_captures.clone();
221221- self.variable.check_set(ctx, value)?;
256256+ used_captures.extend(value.used_captures.iter().cloned());
257257+ let var_result = self.variable.check_set(ctx, value)?;
258258+ used_captures.extend(var_result.used_captures);
222259 Ok(StatementResult { used_captures })
223260 }
224261}
225262226263impl ast::CreateGraphNode {
227264 fn check(&mut self, ctx: &mut CheckContext) -> Result<StatementResult, CheckError> {
228228- self.node.check_add(
265265+ let node_result = self.node.check_add(
229266 ctx,
230267 ExpressionResult {
231268 is_local: true,
···235272 false,
236273 )?;
237274 Ok(StatementResult {
238238- used_captures: HashSet::default(),
275275+ used_captures: node_result.used_captures,
239276 })
240277 }
241278}
···406443 stanza_query: ctx.stanza_query,
407444 locals: &mut loop_locals,
408445 };
409409- self.variable
446446+ let var_result = self
447447+ .variable
410448 .check_add(&mut loop_ctx, value_result, false)?;
449449+ used_captures.extend(var_result.used_captures);
450450+411451 for statement in &mut self.statements {
412452 let stmt_result = statement.check(&mut loop_ctx)?;
413453 used_captures.extend(stmt_result.used_captures);
···535575 stanza_query: ctx.stanza_query,
536576 locals: &mut loop_locals,
537577 };
538538- self.variable
578578+ let var_result = self
579579+ .variable
539580 .check_add(&mut loop_ctx, value_result, false)?;
581581+ used_captures.extend(var_result.used_captures);
540582541583 let element_result = self.element.check(&mut loop_ctx)?;
542584 used_captures.extend(element_result.used_captures);
···570612 stanza_query: ctx.stanza_query,
571613 locals: &mut loop_locals,
572614 };
573573- self.variable
615615+ let var_result = self
616616+ .variable
574617 .check_add(&mut loop_ctx, value_result, false)?;
618618+ used_captures.extend(var_result.used_captures);
575619576620 let element_result = self.element.check(&mut loop_ctx)?;
577621 used_captures.extend(element_result.used_captures);
···636680//-----------------------------------------------------------------------------
637681// Variables
638682683683+#[derive(Clone, Debug)]
684684+struct VariableResult {
685685+ used_captures: HashSet<Identifier>,
686686+}
687687+639688impl ast::Variable {
640689 fn check_add(
641690 &mut self,
642691 ctx: &mut CheckContext,
643692 value: ExpressionResult,
644693 mutable: bool,
645645- ) -> Result<(), CheckError> {
694694+ ) -> Result<VariableResult, CheckError> {
646695 match self {
647696 Self::Unscoped(v) => v.check_add(ctx, value, mutable),
648697 Self::Scoped(v) => v.check_add(ctx, value, mutable),
···653702 &mut self,
654703 ctx: &mut CheckContext,
655704 value: ExpressionResult,
656656- ) -> Result<(), CheckError> {
705705+ ) -> Result<VariableResult, CheckError> {
657706 match self {
658707 Self::Unscoped(v) => v.check_set(ctx, value),
659708 Self::Scoped(v) => v.check_set(ctx, value),
···674723 ctx: &mut CheckContext,
675724 value: ExpressionResult,
676725 mutable: bool,
677677- ) -> Result<(), CheckError> {
726726+ ) -> Result<VariableResult, CheckError> {
678727 if ctx.globals.get(&self.name).is_some() {
679728 return Err(CheckError::CannotHideGlobalVariable(
680729 self.name.as_str().to_string(),
···689738 if mutable {
690739 value.is_local = false;
691740 }
741741+ let used_captures = value.used_captures.clone();
742742+ value.used_captures.clear(); // prevent used captures from escaping
743743+ // we may want to separate quantifier/is_local from
744744+ // the used_captures and only store the former in the
745745+ // future for a cleaner solution
692746 ctx.locals
693747 .add(self.name.clone(), value, mutable)
694694- .map_err(|e| CheckError::Variable(e, format!("{}", self.name), self.location))
748748+ .map_err(|e| CheckError::Variable(e, format!("{}", self.name), self.location))?;
749749+ Ok(VariableResult { used_captures })
695750 }
696751697752 fn check_set(
698753 &mut self,
699754 ctx: &mut CheckContext,
700755 value: ExpressionResult,
701701- ) -> Result<(), CheckError> {
756756+ ) -> Result<VariableResult, CheckError> {
702757 if ctx.globals.get(&self.name).is_some() {
703758 return Err(CheckError::CannotSetGlobalVariable(
704759 self.name.as_str().to_string(),
···711766 // Since we process all statement in order, we don't have info on later
712767 // assignments, and can assume non-local to be sound.
713768 value.is_local = false;
769769+ let used_captures = value.used_captures.clone();
770770+ value.used_captures.clear(); // prevent used captures from escaping
771771+ // we may want to separate quantifier/is_local from
772772+ // the used_captures and only store the former in the
773773+ // future for a cleaner solution
714774 ctx.locals
715775 .set(self.name.clone(), value)
716716- .map_err(|e| CheckError::Variable(e, format!("{}", self.name), self.location))
776776+ .map_err(|e| CheckError::Variable(e, format!("{}", self.name), self.location))?;
777777+ Ok(VariableResult { used_captures })
717778 }
718779719780 fn check_get(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
···733794 ctx: &mut CheckContext,
734795 _value: ExpressionResult,
735796 _mutable: bool,
736736- ) -> Result<(), CheckError> {
737737- self.scope.check(ctx)?;
738738- Ok(())
797797+ ) -> Result<VariableResult, CheckError> {
798798+ let scope_result = self.scope.check(ctx)?;
799799+ Ok(VariableResult {
800800+ used_captures: scope_result.used_captures,
801801+ })
739802 }
740803741804 fn check_set(
742805 &mut self,
743806 ctx: &mut CheckContext,
744807 _value: ExpressionResult,
745745- ) -> Result<(), CheckError> {
746746- self.scope.check(ctx)?;
747747- Ok(())
808808+ ) -> Result<VariableResult, CheckError> {
809809+ let scope_result = self.scope.check(ctx)?;
810810+ Ok(VariableResult {
811811+ used_captures: scope_result.used_captures,
812812+ })
748813 }
749814750815 fn check_get(&mut self, ctx: &mut CheckContext) -> Result<ExpressionResult, CheckError> {
751751- self.scope.check(ctx)?;
816816+ let scope_result = self.scope.check(ctx)?;
752817 Ok(ExpressionResult {
753818 is_local: false,
754819 quantifier: One, // FIXME we don't really know
755755- used_captures: HashSet::new(),
820820+ used_captures: scope_result.used_captures,
756821 })
757822 }
758823}
+3
src/reference/mod.rs
···197197//! example stanza, whose query is `(identifier) @id`, `@id` would refer to the `identifier` syntax
198198//! node that the stanza matched against.
199199//!
200200+//! Unused query captures are considered errors, unless they start with an underscode. For example,
201201+//! a capture `@id` must be used within the stanza, but `@_id` does not.
202202+//!
200203//! # Variables
201204//!
202205//! You can use variables to pass information between different stanzas and statements in a graph
+25-25
tests/it/execution.rs
···9292 check_execution(
9393 "pass",
9494 indoc! {r#"
9595- (module) @root
9595+ (module)
9696 {
9797 var new_node = #null
9898 var current_node = (node)
···138138 check_execution(
139139 "pass",
140140 indoc! {r#"
141141- (module) @root
141141+ (module)
142142 {
143143 var current_node = (node)
144144···257257 indoc! {r#"
258258 global filename
259259260260- (module) @root
260260+ (module)
261261 {
262262 node n
263263 attr (n) filename = filename
···277277 indoc! {r#"
278278 global pkgname = ""
279279280280- (module) @root
280280+ (module)
281281 {
282282 node n
283283 attr (n) pkgname = pkgname
···320320 check_execution(
321321 "pass",
322322 indoc! {r#"
323323- (module) @root
323323+ (module)
324324 {
325325 let x = (node)
326326 let y = x
···338338 check_execution(
339339 "pass",
340340 indoc! {r#"
341341- (module) @root
341341+ (module)
342342 {
343343 node node0
344344 attr (node0) val = (replace "accacc" (replace "abc" "b" "c") (replace "abc" "a" "b"))
···356356 fail_execution(
357357 "pass",
358358 indoc! {r#"
359359- (module) @root
359359+ (module)
360360 {
361361 scan "abc" {
362362 "^\\b" {
···450450 check_execution(
451451 "pass",
452452 indoc! {r#"
453453- (module (pass_statement)? @x) @root
453453+ (module (pass_statement)? @x)
454454 {
455455 node node0
456456 if some @x {
···472472 check_execution(
473473 "pass",
474474 indoc! {r#"
475475- (module (import_statement)? @x) @root
475475+ (module (import_statement)? @x)
476476 {
477477 node node0
478478 if none @x {
···494494 check_execution(
495495 "pass",
496496 indoc! {r#"
497497- (module (import_statement)? @x (pass_statement)? @y) @root
497497+ (module (import_statement)? @x (pass_statement)? @y)
498498 {
499499 node node0
500500 if none @x, some @y {
···516516 check_execution(
517517 "pass",
518518 indoc! {r#"
519519- (module (import_statement)? @x (pass_statement)? @y) @root
519519+ (module (import_statement)? @x (pass_statement)? @y)
520520 {
521521 node node0
522522 if some @x {
···538538 check_execution(
539539 "pass",
540540 indoc! {r#"
541541- (module (import_statement)? @x) @root
541541+ (module (import_statement)? @x)
542542 {
543543 node node0
544544 if some @x {
···560560 check_execution(
561561 "pass",
562562 indoc! {r#"
563563- (module (import_statement)? @x) @root
563563+ (module (import_statement)?)
564564 {
565565 node node0
566566 if #true {
···582582 check_execution(
583583 "pass",
584584 indoc! {r#"
585585- (module (import_statement)? @x (import_statement)? @y) @root
585585+ (module (import_statement)? @x (import_statement)? @y)
586586 {
587587 node node0
588588 if some @x {
···605605 pass
606606 "#,
607607 indoc! {r#"
608608- (module (pass_statement)? @x) @root
608608+ (module (pass_statement)? @x)
609609 {
610610 let n = 1
611611 if some @x {
···629629 pass
630630 "#,
631631 indoc! {r#"
632632- (module (pass_statement)? @x) @root
632632+ (module (pass_statement)? @x)
633633 {
634634 var n = 1
635635 if some @x {
···653653 pass
654654 "#,
655655 indoc! {r#"
656656- (module (pass_statement)? @x) @root
656656+ (module (pass_statement)? @x)
657657 {
658658 var n = 1
659659 if some @x {
···679679 pass
680680 "#,
681681 indoc! {r#"
682682- (module (pass_statement)* @xs) @root
682682+ (module (pass_statement)* @xs)
683683 {
684684 var n = 0
685685 for x in @xs {
···703703 pass
704704 "#,
705705 indoc! {r#"
706706- (module (import_statement)* @xs) @root
706706+ (module (import_statement)* @xs)
707707 {
708708 var n = 0
709709 for x in @xs {
···727727 pass
728728 "#,
729729 indoc! {r#"
730730- (module) @root
730730+ (module)
731731 {
732732 var n = 0
733733 for x in [#null, #null, #null] {
···751751 pass
752752 "#,
753753 indoc! {r#"
754754- (module (pass_statement)* @xs) @root
754754+ (module (pass_statement)* @xs)
755755 {
756756 let n = 1
757757 for x in @xs {
···775775 pass
776776 "#,
777777 indoc! {r#"
778778- (module (pass_statement)* @xs) @root
778778+ (module (pass_statement)* @xs)
779779 {
780780 var n = 1
781781 for x in @xs {
···801801 pass
802802 "#,
803803 indoc! {r#"
804804- (module (pass_statement)+ @xs) @root
804804+ (module (pass_statement)+ @xs)
805805 {
806806 var n = 0
807807 for x in @xs {
···827827 pass
828828 "#,
829829 indoc! {r#"
830830- (module (pass_statement)* @xs) @root
830830+ (module (pass_statement)* @xs)
831831 {
832832 node node0
833833 attr (node0) val = [ (named-child-index x) for x in @xs ]
···849849 pass
850850 "#,
851851 indoc! {r#"
852852- (module (pass_statement)* @xs) @root
852852+ (module (pass_statement)* @xs)
853853 {
854854 node node0
855855 attr (node0) val = { (source-text x) for x in @xs }