1//! An NFA-based parser, which is porting from rustc mbe parsing code2//!3//! See <https://github.com/rust-lang/rust/blob/70b18bc2cbac4712020019f5bf57c00905373205/compiler/rustc_expand/src/mbe/macro_parser.rs>4//! Here is a quick intro to how the parser works, copied from rustc:5//!6//! A 'position' is a dot in the middle of a matcher, usually represented as a7//! dot. For example `· a $( a )* a b` is a position, as is `a $( · a )* a b`.8//!9//! The parser walks through the input a character at a time, maintaining a list10//! of threads consistent with the current position in the input string: `cur_items`.11//!12//! As it processes them, it fills up `eof_items` with threads that would be valid if13//! the macro invocation is now over, `bb_items` with threads that are waiting on14//! a Rust non-terminal like `$e:expr`, and `next_items` with threads that are waiting15//! on a particular token. Most of the logic concerns moving the · through the16//! repetitions indicated by Kleene stars. The rules for moving the · without17//! consuming any input are called epsilon transitions. It only advances or calls18//! out to the real Rust parser when no `cur_items` threads remain.19//!20//! Example:21//!22//! ```text, ignore23//! Start parsing a a a a b against [· a $( a )* a b].24//!25//! Remaining input: a a a a b26//! next: [· a $( a )* a b]27//!28//! - - - Advance over an a. - - -29//!30//! Remaining input: a a a b31//! cur: [a · $( a )* a b]32//! Descend/Skip (first item).33//! next: [a $( · a )* a b] [a $( a )* · a b].34//!35//! - - - Advance over an a. - - -36//!37//! Remaining input: a a b38//! cur: [a $( a · )* a b] [a $( a )* a · b]39//! Follow epsilon transition: Finish/Repeat (first item)40//! next: [a $( a )* · a b] [a $( · a )* a b] [a $( a )* a · b]41//!42//! - - - Advance over an a. - - - (this looks exactly like the last step)43//!44//! Remaining input: a b45//! cur: [a $( a · )* a b] [a $( a )* a · b]46//! Follow epsilon transition: Finish/Repeat (first item)47//! next: [a $( a )* · a b] [a $( · a )* a b] [a $( a )* a · b]48//!49//! - - - Advance over an a. - - - (this looks exactly like the last step)50//!51//! Remaining input: b52//! cur: [a $( a · )* a b] [a $( a )* a · b]53//! Follow epsilon transition: Finish/Repeat (first item)54//! next: [a $( a )* · a b] [a $( · a )* a b] [a $( a )* a · b]55//!56//! - - - Advance over a b. - - -57//!58//! Remaining input: ''59//! eof: [a $( a )* a b ·]60//! ```6162use std::{rc::Rc, sync::Arc};6364use intern::{Symbol, sym};65use smallvec::{SmallVec, smallvec};66use tt::{67 DelimSpan,68 iter::{TtElement, TtIter},69};7071use crate::{72 ExpandError, ExpandErrorKind, MetaTemplate, ValueResult,73 expander::{Binding, Bindings, ExpandResult, Fragment, TokensOrigin},74 expect_fragment,75 parser::{ExprKind, MetaVarKind, Op, RepeatKind, Separator},76};7778impl<'a> Bindings<'a> {79 fn push_optional(&mut self, name: Symbol) {80 self.inner.insert(name, Binding::Fragment(Fragment::Empty));81 }8283 fn push_empty(&mut self, name: Symbol) {84 self.inner.insert(name, Binding::Empty);85 }8687 fn bindings(&self) -> impl Iterator<Item = &Binding<'a>> {88 self.inner.values()89 }90}9192#[derive(Clone, Default, Debug)]93pub(super) struct Match<'a> {94 pub(super) bindings: Bindings<'a>,95 /// We currently just keep the first error and count the rest to compare matches.96 pub(super) err: Option<ExpandError>,97 pub(super) err_count: usize,98 /// How many top-level token trees were left to match.99 pub(super) unmatched_tts: usize,100 /// The number of bound variables101 pub(super) bound_count: usize,102}103104impl Match<'_> {105 fn add_err(&mut self, err: ExpandError) {106 let prev_err = self.err.take();107 self.err = prev_err.or(Some(err));108 self.err_count += 1;109 }110}111112/// Matching errors are added to the `Match`.113pub(super) fn match_<'t>(114 db: &dyn salsa::Database,115 pattern: &'t MetaTemplate,116 input: &'t tt::TopSubtree,117) -> Match<'t> {118 let mut res = match_loop(db, pattern, input);119 res.bound_count = count(res.bindings.bindings());120 return res;121122 fn count<'a>(bindings: impl Iterator<Item = &'a Binding<'a>>) -> usize {123 bindings124 .map(|it| match it {125 Binding::Fragment(_) => 1,126 Binding::Empty => 1,127 Binding::Missing(_) => 1,128 Binding::Nested(it) => count(it.iter()),129 })130 .sum()131 }132}133134#[derive(Debug, Clone)]135enum BindingKind<'a> {136 Empty(Symbol),137 Optional(Symbol),138 Fragment(Symbol, Fragment<'a>),139 Missing(Symbol, MetaVarKind),140 Nested(usize, usize),141}142143#[derive(Debug, Clone)]144struct BindingsIdx(usize, usize);145146#[derive(Debug, Clone)]147enum LinkNode<T> {148 Node(T),149 Parent { idx: usize, len: usize },150}151152#[derive(Default)]153struct BindingsBuilder<'a> {154 nodes: Vec<Vec<LinkNode<Rc<BindingKind<'a>>>>>,155 nested: Vec<Vec<LinkNode<usize>>>,156}157158impl<'a> BindingsBuilder<'a> {159 fn alloc(&mut self) -> BindingsIdx {160 let idx = self.nodes.len();161 self.nodes.push(Vec::new());162 let nidx = self.nested.len();163 self.nested.push(Vec::new());164 BindingsIdx(idx, nidx)165 }166167 fn copy(&mut self, bindings: &BindingsIdx) -> BindingsIdx {168 let idx = copy_parent(bindings.0, &mut self.nodes);169 let nidx = copy_parent(bindings.1, &mut self.nested);170 return BindingsIdx(idx, nidx);171172 fn copy_parent<T>(idx: usize, target: &mut Vec<Vec<LinkNode<T>>>) -> usize173 where174 T: Clone,175 {176 let new_idx = target.len();177 let len = target[idx].len();178 if len < 4 {179 target.push(target[idx].clone())180 } else {181 target.push(vec![LinkNode::Parent { idx, len }]);182 }183 new_idx184 }185 }186187 fn push_empty(&mut self, idx: &mut BindingsIdx, var: &Symbol) {188 self.nodes[idx.0].push(LinkNode::Node(Rc::new(BindingKind::Empty(var.clone()))));189 }190191 fn push_optional(&mut self, idx: &mut BindingsIdx, var: &Symbol) {192 self.nodes[idx.0].push(LinkNode::Node(Rc::new(BindingKind::Optional(var.clone()))));193 }194195 fn push_fragment(&mut self, idx: &mut BindingsIdx, var: &Symbol, fragment: Fragment<'a>) {196 self.nodes[idx.0]197 .push(LinkNode::Node(Rc::new(BindingKind::Fragment(var.clone(), fragment))));198 }199200 fn push_missing(&mut self, idx: &mut BindingsIdx, var: &Symbol, kind: MetaVarKind) {201 self.nodes[idx.0].push(LinkNode::Node(Rc::new(BindingKind::Missing(var.clone(), kind))));202 }203204 fn push_nested(&mut self, parent: &mut BindingsIdx, child: &BindingsIdx) {205 let BindingsIdx(idx, nidx) = self.copy(child);206 self.nodes[parent.0].push(LinkNode::Node(Rc::new(BindingKind::Nested(idx, nidx))));207 }208209 fn push_default(&mut self, idx: &mut BindingsIdx) {210 self.nested[idx.1].push(LinkNode::Node(idx.0));211 let new_idx = self.nodes.len();212 self.nodes.push(Vec::new());213 idx.0 = new_idx;214 }215216 fn build(self, idx: &BindingsIdx) -> Bindings<'a> {217 self.build_inner(&self.nodes[idx.0])218 }219220 fn build_inner(&self, link_nodes: &[LinkNode<Rc<BindingKind<'a>>>]) -> Bindings<'a> {221 let mut bindings = Bindings::default();222 let mut nodes = Vec::new();223 self.collect_nodes(link_nodes, &mut nodes);224225 for cmd in nodes {226 match cmd {227 BindingKind::Empty(name) => {228 bindings.push_empty(name.clone());229 }230 BindingKind::Optional(name) => {231 bindings.push_optional(name.clone());232 }233 BindingKind::Fragment(name, fragment) => {234 bindings.inner.insert(name.clone(), Binding::Fragment(fragment.clone()));235 }236 BindingKind::Missing(name, kind) => {237 bindings.inner.insert(name.clone(), Binding::Missing(*kind));238 }239 BindingKind::Nested(idx, nested_idx) => {240 let mut nested_nodes = Vec::new();241 self.collect_nested(*idx, *nested_idx, &mut nested_nodes);242243 for (idx, iter) in nested_nodes.into_iter().enumerate() {244 for (key, value) in &iter.inner {245 let bindings = bindings246 .inner247 .entry(key.clone())248 .or_insert_with(|| Binding::Nested(Vec::new()));249250 if let Binding::Nested(it) = bindings {251 // insert empty nested bindings before this one252 while it.len() < idx {253 it.push(Binding::Nested(Vec::new()));254 }255 it.push(value.clone());256 }257 }258 }259 }260 }261 }262263 bindings264 }265266 fn collect_nested_ref<'b>(267 &'b self,268 id: usize,269 len: usize,270 nested_refs: &mut Vec<&'b [LinkNode<Rc<BindingKind<'a>>>]>,271 ) {272 self.nested[id].iter().take(len).for_each(|it| match it {273 LinkNode::Node(id) => nested_refs.push(&self.nodes[*id]),274 LinkNode::Parent { idx, len } => self.collect_nested_ref(*idx, *len, nested_refs),275 });276 }277278 fn collect_nested(&self, idx: usize, nested_idx: usize, nested: &mut Vec<Bindings<'a>>) {279 let last = &self.nodes[idx];280 let mut nested_refs: Vec<&[_]> = Vec::new();281 self.nested[nested_idx].iter().for_each(|it| match *it {282 LinkNode::Node(idx) => nested_refs.push(&self.nodes[idx]),283 LinkNode::Parent { idx, len } => self.collect_nested_ref(idx, len, &mut nested_refs),284 });285 nested_refs.push(last);286 nested.extend(nested_refs.into_iter().map(|iter| self.build_inner(iter)));287 }288289 fn collect_nodes_ref<'b>(290 &'b self,291 id: usize,292 len: usize,293 nodes: &mut Vec<&'b BindingKind<'a>>,294 ) {295 self.nodes[id].iter().take(len).for_each(|it| match it {296 LinkNode::Node(it) => nodes.push(it),297 LinkNode::Parent { idx, len } => self.collect_nodes_ref(*idx, *len, nodes),298 });299 }300301 fn collect_nodes<'b>(302 &'b self,303 link_nodes: &'b [LinkNode<Rc<BindingKind<'a>>>],304 nodes: &mut Vec<&'b BindingKind<'a>>,305 ) {306 link_nodes.iter().for_each(|it| match it {307 LinkNode::Node(it) => nodes.push(it),308 LinkNode::Parent { idx, len } => self.collect_nodes_ref(*idx, *len, nodes),309 });310 }311}312313#[derive(Debug, Clone)]314struct MatchState<'t> {315 /// The position of the "dot" in this matcher316 dot: OpDelimitedIter<'t>,317318 /// Token subtree stack319 /// When matching against matchers with nested delimited submatchers (e.g., `pat ( pat ( .. )320 /// pat ) pat`), we need to keep track of the matchers we are descending into. This stack does321 /// that where the bottom of the stack is the outermost matcher.322 stack: SmallVec<[OpDelimitedIter<'t>; 4]>,323324 /// The "parent" matcher position if we are in a repetition. That is, the matcher position just325 /// before we enter the repetition.326 up: Option<Box<MatchState<'t>>>,327328 /// The separator if we are in a repetition.329 sep: Option<Arc<Separator>>,330331 /// The KleeneOp of this sequence if we are in a repetition.332 sep_kind: Option<RepeatKind>,333334 /// Whether we already matched separator token.335 sep_matched: bool,336337 /// Matched meta variables bindings338 bindings: BindingsIdx,339340 /// Cached result of meta variable parsing341 meta_result: Option<(TtIter<'t>, ExpandResult<Option<Fragment<'t>>>)>,342343 /// Is error occurred in this state, will `poised` to "parent"344 is_error: bool,345}346347/// Process the matcher positions of `cur_items` until it is empty. In the process, this will348/// produce more items in `next_items`, `eof_items`, and `bb_items`.349///350/// For more info about how this happens, see the module-level doc comments and the inline351/// comments of this function.352///353/// # Parameters354///355/// - `src`: the current token of the parser.356/// - `stack`: the "parent" frames of the token tree357/// - `res`: the match result to store errors358/// - `cur_items`: the set of current items to be processed. This should be empty by the end of a359/// successful execution of this function.360/// - `next_items`: the set of newly generated items. These are used to replenish `cur_items` in361/// the function `parse`.362/// - `eof_items`: the set of items that would be valid if this was the EOF.363/// - `bb_items`: the set of items that are waiting for the black-box parser.364/// - `error_items`: the set of items in errors, used for error-resilient parsing365#[inline]366fn match_loop_inner<'t>(367 db: &dyn salsa::Database,368 src: TtIter<'t>,369 stack: &[TtIter<'t>],370 res: &mut Match<'t>,371 bindings_builder: &mut BindingsBuilder<'t>,372 cur_items: &mut SmallVec<[MatchState<'t>; 1]>,373 bb_items: &mut SmallVec<[MatchState<'t>; 1]>,374 next_items: &mut Vec<MatchState<'t>>,375 eof_items: &mut SmallVec<[MatchState<'t>; 1]>,376 error_items: &mut SmallVec<[MatchState<'t>; 1]>,377 delim_span: tt::DelimSpan,378) {379 macro_rules! try_push {380 ($items: expr, $it:expr) => {381 if $it.is_error {382 error_items.push($it);383 } else {384 $items.push($it);385 }386 };387 }388389 while let Some(mut item) = cur_items.pop() {390 while item.dot.is_eof() {391 match item.stack.pop() {392 Some(frame) => {393 item.dot = frame;394 item.dot.next();395 }396 None => break,397 }398 }399 let op = match item.dot.peek() {400 None => {401 // We are at or past the end of the matcher of `item`.402 if let Some(up) = &item.up {403 if !item.sep_matched {404 // Get the `up` matcher405 let mut new_pos = (**up).clone();406 new_pos.bindings = bindings_builder.copy(&new_pos.bindings);407 // Add matches from this repetition to the `matches` of `up`408 bindings_builder.push_nested(&mut new_pos.bindings, &item.bindings);409410 // Move the "dot" past the repetition in `up`411 new_pos.dot.next();412 new_pos.is_error = new_pos.is_error || item.is_error;413 cur_items.push(new_pos);414 }415416 // Check if we need a separator.417 if let Some(sep) = &item.sep418 && !item.sep_matched419 {420 let mut fork = src.clone();421 if expect_separator(&mut fork, sep) {422 // HACK: here we use `meta_result` to pass `TtIter` back to caller because423 // it might have been advanced multiple times. `ValueResult` is424 // insignificant.425 item.meta_result = Some((fork, ValueResult::ok(None)));426 item.dot.next();427 // item.sep_parsed = Some(sep_len);428 item.sep_matched = true;429 try_push!(next_items, item);430 }431 }432 // We don't need a separator. Move the "dot" back to the beginning of the matcher433 // and try to match again UNLESS we are only allowed to have _one_ repetition.434 else if item.sep_kind != Some(RepeatKind::ZeroOrOne) {435 item.dot = item.dot.reset();436 item.sep_matched = false;437 bindings_builder.push_default(&mut item.bindings);438 cur_items.push(item);439 }440 } else {441 // If we are not in a repetition, then being at the end of a matcher means that we have442 // reached the potential end of the input.443 try_push!(eof_items, item);444 }445 continue;446 }447 Some(it) => it,448 };449450 // We are in the middle of a matcher.451 match op {452 OpDelimited::Op(Op::Repeat { tokens, kind, separator }) => {453 if matches!(kind, RepeatKind::ZeroOrMore | RepeatKind::ZeroOrOne) {454 let mut new_item = item.clone();455 new_item.bindings = bindings_builder.copy(&new_item.bindings);456 new_item.dot.next();457 collect_vars(458 &mut |s| {459 bindings_builder.push_empty(&mut new_item.bindings, &s);460 },461 tokens,462 );463 cur_items.push(new_item);464 }465 cur_items.push(MatchState {466 dot: tokens.iter_delimited(delim_span),467 stack: Default::default(),468 up: Some(Box::new(item)),469 sep: separator.clone(),470 sep_kind: Some(*kind),471 sep_matched: false,472 bindings: bindings_builder.alloc(),473 meta_result: None,474 is_error: false,475 })476 }477 OpDelimited::Op(Op::Subtree { tokens, delimiter }) => {478 if let Ok((subtree, _)) = src.clone().expect_subtree()479 && subtree.delimiter.kind == delimiter.kind480 {481 item.stack.push(item.dot);482 item.dot = tokens.iter_delimited_with(*delimiter);483 cur_items.push(item);484 }485 }486 OpDelimited::Op(Op::Var { kind, name, .. }) => {487 if let &Some(kind) = kind {488 let mut fork = src.clone();489 let match_res = match_meta_var(db, kind, &mut fork, delim_span);490 match match_res.err {491 None => {492 // Some meta variables are optional (e.g. vis)493 if !match_res.value.is_empty() {494 item.meta_result = Some((fork, match_res.map(Some)));495 try_push!(bb_items, item);496 } else {497 bindings_builder.push_optional(&mut item.bindings, name);498 item.dot.next();499 cur_items.push(item);500 }501 }502 Some(err) => {503 res.add_err(err);504 if !match_res.value.is_empty() {505 bindings_builder.push_fragment(506 &mut item.bindings,507 name,508 match_res.value,509 )510 } else {511 bindings_builder.push_missing(&mut item.bindings, name, kind)512 }513 item.is_error = true;514 error_items.push(item);515 }516 }517 }518 }519 OpDelimited::Op(Op::Literal(lhs)) => {520 if let Ok(rhs) = src.clone().expect_leaf() {521 if matches!(&rhs, tt::Leaf::Literal(it) if it.text_and_suffix == lhs.text_and_suffix)522 {523 item.dot.next();524 } else {525 res.add_err(ExpandError::new(526 *rhs.span(),527 ExpandErrorKind::UnexpectedToken,528 ));529 item.is_error = true;530 }531 } else {532 res.add_err(ExpandError::binding_error(533 src.clone().next().map_or(delim_span.close, |it| it.first_span()),534 format!("expected literal: `{lhs}`"),535 ));536 item.is_error = true;537 }538 try_push!(next_items, item);539 }540 OpDelimited::Op(Op::Ident(lhs)) => {541 if let Ok(rhs) = src.clone().expect_leaf() {542 if matches!(&rhs, tt::Leaf::Ident(it) if it.sym == lhs.sym) {543 item.dot.next();544 } else {545 res.add_err(ExpandError::new(546 *rhs.span(),547 ExpandErrorKind::UnexpectedToken,548 ));549 item.is_error = true;550 }551 } else {552 res.add_err(ExpandError::binding_error(553 src.clone().next().map_or(delim_span.close, |it| it.first_span()),554 format!("expected ident: `{lhs}`"),555 ));556 item.is_error = true;557 }558 try_push!(next_items, item);559 }560 OpDelimited::Op(Op::Punct(lhs)) => {561 let mut fork = src.clone();562 let error = if let Ok(rhs) = fork.expect_glued_punct() {563 let first_is_single_quote = rhs[0].char == '\'';564 let lhs = lhs.iter().map(|it| it.char);565 let rhs_ = rhs.iter().map(|it| it.char);566 if lhs.clone().eq(rhs_) {567 // HACK: here we use `meta_result` to pass `TtIter` back to caller because568 // it might have been advanced multiple times. `ValueResult` is569 // insignificant.570 item.meta_result = Some((fork, ValueResult::ok(None)));571 item.dot.next();572 next_items.push(item);573 continue;574 }575576 if first_is_single_quote {577 // If the first punct token is a single quote, that's a part of a lifetime578 // ident, not a punct.579 ExpandError::new(580 rhs.get(1).map_or(rhs[0].span, |it| it.span),581 ExpandErrorKind::UnexpectedToken,582 )583 } else {584 let lhs = lhs.collect::<String>();585 ExpandError::binding_error(rhs[0].span, format!("expected punct: `{lhs}`"))586 }587 } else {588 ExpandError::new(589 src.clone().next().map_or(delim_span.close, |it| it.first_span()),590 ExpandErrorKind::UnexpectedToken,591 )592 };593594 res.add_err(error);595 item.is_error = true;596 error_items.push(item);597 }598 OpDelimited::Op(599 Op::Ignore { .. }600 | Op::Index { .. }601 | Op::Count { .. }602 | Op::Len { .. }603 | Op::Concat { .. },604 ) => {605 stdx::never!("metavariable expression in lhs found");606 }607 OpDelimited::Open => {608 if matches!(src.peek(), Some(TtElement::Subtree(..))) {609 item.dot.next();610 try_push!(next_items, item);611 }612 }613 OpDelimited::Close => {614 let is_delim_closed = src.is_empty() && !stack.is_empty();615 if is_delim_closed {616 item.dot.next();617 try_push!(next_items, item);618 }619 }620 }621 }622}623624fn match_loop<'t>(625 db: &dyn salsa::Database,626 pattern: &'t MetaTemplate,627 src: &'t tt::TopSubtree,628) -> Match<'t> {629 let span = src.top_subtree().delimiter.delim_span();630 let mut src = src.iter();631 let mut stack: SmallVec<[TtIter<'_>; 1]> = SmallVec::new();632 let mut res = Match::default();633 let mut error_recover_item = None;634635 let mut bindings_builder = BindingsBuilder::default();636637 let mut cur_items = smallvec![MatchState {638 dot: pattern.iter_delimited(span),639 stack: Default::default(),640 up: None,641 sep: None,642 sep_kind: None,643 sep_matched: false,644 bindings: bindings_builder.alloc(),645 is_error: false,646 meta_result: None,647 }];648649 let mut next_items = vec![];650651 loop {652 let mut bb_items = SmallVec::new();653 let mut eof_items = SmallVec::new();654 let mut error_items = SmallVec::new();655656 stdx::always!(next_items.is_empty());657658 match_loop_inner(659 db,660 src.clone(),661 &stack,662 &mut res,663 &mut bindings_builder,664 &mut cur_items,665 &mut bb_items,666 &mut next_items,667 &mut eof_items,668 &mut error_items,669 span,670 );671 stdx::always!(cur_items.is_empty());672673 if !error_items.is_empty() {674 error_recover_item = error_items.pop().map(|it| it.bindings);675 } else if let [state, ..] = &*eof_items {676 error_recover_item = Some(state.bindings.clone());677 }678679 // We need to do some post processing after the `match_loop_inner`.680 // If we reached the EOF, check that there is EXACTLY ONE possible matcher. Otherwise,681 // either the parse is ambiguous (which should never happen) or there is a syntax error.682 if src.is_empty() && stack.is_empty() {683 if let [state] = &*eof_items {684 // remove all errors, because it is the correct answer !685 res = Match::default();686 res.bindings = bindings_builder.build(&state.bindings);687 } else {688 // Error recovery689 if let Some(item) = error_recover_item {690 res.bindings = bindings_builder.build(&item);691 }692 res.add_err(ExpandError::new(span.open, ExpandErrorKind::UnexpectedToken));693 }694 return res;695 }696697 // If there are no possible next positions AND we aren't waiting for the black-box parser,698 // then there is a syntax error.699 //700 // Another possibility is that we need to call out to parse some rust nonterminal701 // (black-box) parser. However, if there is not EXACTLY ONE of these, something is wrong.702 let has_leftover_tokens = (bb_items.is_empty() && next_items.is_empty())703 || !(bb_items.is_empty() || next_items.is_empty())704 || bb_items.len() > 1;705 if has_leftover_tokens {706 res.unmatched_tts += src.remaining().len();707 res.add_err(ExpandError::new(span.open, ExpandErrorKind::LeftoverTokens));708709 if let Some(error_recover_item) = error_recover_item {710 res.bindings = bindings_builder.build(&error_recover_item);711 }712 return res;713 }714 // Dump all possible `next_items` into `cur_items` for the next iteration.715 else if !next_items.is_empty() {716 if let Some((iter, _)) = next_items[0].meta_result.take() {717 // We've matched a possibly "glued" punct. The matched punct (hence718 // `meta_result` also) must be the same for all items.719 // FIXME: If there are multiple items, it's definitely redundant (and it's hacky!720 // `meta_result` isn't supposed to be used this way).721722 // We already bumped, so no need to call `.next()` like in the other branch.723 src = iter;724 for item in next_items.iter_mut() {725 item.meta_result = None;726 }727 } else {728 match src.next() {729 Some(TtElement::Subtree(_, subtree_iter)) => {730 stack.push(src.clone());731 src = subtree_iter;732 }733 None => {734 if let Some(iter) = stack.pop() {735 src = iter;736 }737 }738 _ => (),739 }740 }741 // Now process the next token742 cur_items.extend(next_items.drain(..));743 }744 // Finally, we have the case where we need to call the black-box parser to get some745 // nonterminal.746 else {747 stdx::always!(bb_items.len() == 1);748 let mut item = bb_items.pop().unwrap();749750 if let Some(OpDelimited::Op(Op::Var { name, .. })) = item.dot.peek() {751 let (iter, match_res) = item.meta_result.take().unwrap();752 match match_res.value {753 Some(fragment) => {754 bindings_builder.push_fragment(&mut item.bindings, name, fragment);755 }756 None if match_res.err.is_none() => {757 bindings_builder.push_optional(&mut item.bindings, name);758 }759 None => {}760 }761 if let Some(err) = match_res.err {762 res.add_err(err);763 }764 src = iter.clone();765 item.dot.next();766 } else {767 unreachable!()768 }769 cur_items.push(item);770 }771 stdx::always!(!cur_items.is_empty());772 }773}774775fn match_meta_var<'t>(776 db: &dyn salsa::Database,777 kind: MetaVarKind,778 input: &mut TtIter<'t>,779 delim_span: DelimSpan,780) -> ExpandResult<Fragment<'t>> {781 let fragment = match kind {782 MetaVarKind::Path => {783 return expect_fragment(db, input, parser::PrefixEntryPoint::Path, delim_span)784 .map(Fragment::Path);785 }786 MetaVarKind::Expr(expr) => {787 // `expr_2021` should not match underscores, let expressions, or inline const.788 // The latter two are for [backwards compatibility][0].789 // And `expr` also should not contain let expressions but may contain the other two790 // since `Edition2024`.791 // HACK: Macro expansion should not be done using "rollback and try another alternative".792 // rustc [explicitly checks the next token][1].793 // [0]: https://github.com/rust-lang/rust/issues/86730794 // [1]: https://github.com/rust-lang/rust/blob/f0c4da499/compiler/rustc_expand/src/mbe/macro_parser.rs#L576795 match input.peek() {796 Some(TtElement::Leaf(tt::Leaf::Ident(it))) => {797 let is_err = if it.is_raw.no() && matches!(expr, ExprKind::Expr2021) {798 it.sym == sym::underscore || it.sym == sym::let_ || it.sym == sym::const_799 } else {800 it.sym == sym::let_801 };802 if is_err {803 return ExpandResult::only_err(ExpandError::new(804 it.span,805 ExpandErrorKind::NoMatchingRule,806 ));807 }808 }809 _ => {}810 };811 return expect_fragment(db, input, parser::PrefixEntryPoint::Expr, delim_span)812 .map(Fragment::Expr);813 }814 MetaVarKind::Ident | MetaVarKind::Tt | MetaVarKind::Lifetime | MetaVarKind::Literal => {815 let span = input.next_span();816 let savepoint = input.savepoint();817 let err = match kind {818 MetaVarKind::Ident => input.expect_ident().map(drop).map_err(|()| {819 ExpandError::binding_error(span.unwrap_or(delim_span.close), "expected ident")820 }),821 MetaVarKind::Tt => expect_tt(input).map_err(|()| {822 ExpandError::binding_error(823 span.unwrap_or(delim_span.close),824 "expected token tree",825 )826 }),827 MetaVarKind::Lifetime => expect_lifetime(input).map(drop).map_err(|()| {828 ExpandError::binding_error(829 span.unwrap_or(delim_span.close),830 "expected lifetime",831 )832 }),833 MetaVarKind::Literal => {834 eat_char(input, '-');835 input.expect_literal().map(drop).map_err(|()| {836 ExpandError::binding_error(837 span.unwrap_or(delim_span.close),838 "expected literal",839 )840 })841 }842 _ => unreachable!(),843 }844 .err();845 let value = match err {846 Some(_) => Fragment::Empty,847 None => Fragment::Tokens {848 tree: input.from_savepoint(savepoint),849 origin: TokensOrigin::Raw,850 },851 };852 return ValueResult { value, err };853 }854 MetaVarKind::Ty => (parser::PrefixEntryPoint::Ty, TokensOrigin::Ast),855 MetaVarKind::Pat => (parser::PrefixEntryPoint::PatTop, TokensOrigin::Ast),856 MetaVarKind::PatParam => (parser::PrefixEntryPoint::Pat, TokensOrigin::Ast),857 MetaVarKind::Stmt => (parser::PrefixEntryPoint::Stmt, TokensOrigin::Ast),858 MetaVarKind::Block => (parser::PrefixEntryPoint::Block, TokensOrigin::Ast),859 MetaVarKind::Meta => (parser::PrefixEntryPoint::MetaItem, TokensOrigin::Ast),860 MetaVarKind::Item => (parser::PrefixEntryPoint::Item, TokensOrigin::Ast),861 MetaVarKind::Vis => (parser::PrefixEntryPoint::Vis, TokensOrigin::Ast),862 };863 let (entry_point, origin) = fragment;864 expect_fragment(db, input, entry_point, delim_span)865 .map(|tree| Fragment::Tokens { tree, origin })866}867868fn collect_vars(collector_fun: &mut impl FnMut(Symbol), pattern: &MetaTemplate) {869 for op in pattern.iter() {870 match op {871 Op::Var { name, .. } => collector_fun(name.clone()),872 Op::Subtree { tokens, .. } => collect_vars(collector_fun, tokens),873 Op::Repeat { tokens, .. } => collect_vars(collector_fun, tokens),874 Op::Literal(_) | Op::Ident(_) | Op::Punct(_) => {}875 Op::Ignore { .. }876 | Op::Index { .. }877 | Op::Count { .. }878 | Op::Len { .. }879 | Op::Concat { .. } => {880 stdx::never!("metavariable expression in lhs found");881 }882 }883 }884}885impl MetaTemplate {886 fn iter_delimited_with(&self, delimiter: tt::Delimiter) -> OpDelimitedIter<'_> {887 OpDelimitedIter { inner: &self.0, idx: 0, delimited: delimiter }888 }889 fn iter_delimited(&self, span: tt::DelimSpan) -> OpDelimitedIter<'_> {890 OpDelimitedIter {891 inner: &self.0,892 idx: 0,893 delimited: tt::Delimiter::invisible_delim_spanned(span),894 }895 }896}897898#[derive(Debug, Clone, Copy)]899enum OpDelimited<'a> {900 Op(&'a Op),901 Open,902 Close,903}904905#[derive(Debug, Clone, Copy)]906struct OpDelimitedIter<'a> {907 inner: &'a [Op],908 delimited: tt::Delimiter,909 idx: usize,910}911912impl<'a> OpDelimitedIter<'a> {913 fn is_eof(&self) -> bool {914 let len = self.inner.len()915 + if self.delimited.kind != tt::DelimiterKind::Invisible { 2 } else { 0 };916 self.idx >= len917 }918919 fn peek(&self) -> Option<OpDelimited<'a>> {920 match self.delimited.kind {921 tt::DelimiterKind::Invisible => self.inner.get(self.idx).map(OpDelimited::Op),922 _ => match self.idx {923 0 => Some(OpDelimited::Open),924 i if i == self.inner.len() + 1 => Some(OpDelimited::Close),925 i => self.inner.get(i - 1).map(OpDelimited::Op),926 },927 }928 }929930 fn reset(&self) -> Self {931 Self { inner: self.inner, idx: 0, delimited: self.delimited }932 }933}934935impl<'a> Iterator for OpDelimitedIter<'a> {936 type Item = OpDelimited<'a>;937938 fn next(&mut self) -> Option<Self::Item> {939 let res = self.peek();940 self.idx += 1;941 res942 }943944 fn size_hint(&self) -> (usize, Option<usize>) {945 let len = self.inner.len()946 + if self.delimited.kind != tt::DelimiterKind::Invisible { 2 } else { 0 };947 let remain = len.saturating_sub(self.idx);948 (remain, Some(remain))949 }950}951952fn expect_separator(iter: &mut TtIter<'_>, separator: &Separator) -> bool {953 let mut fork = iter.clone();954 let ok = match separator {955 Separator::Ident(lhs) => match fork.expect_ident_or_underscore() {956 Ok(rhs) => rhs.sym == lhs.sym,957 Err(_) => false,958 },959 Separator::Literal(lhs) => match fork.expect_literal() {960 Ok(rhs) => match rhs {961 tt::Leaf::Literal(rhs) => rhs.text_and_suffix == lhs.text_and_suffix,962 tt::Leaf::Ident(rhs) => rhs.sym == lhs.text_and_suffix,963 tt::Leaf::Punct(_) => false,964 },965 Err(_) => false,966 },967 Separator::Puncts(lhs) => match fork.expect_glued_punct() {968 Ok(rhs) => {969 let lhs = lhs.iter().map(|it| it.char);970 let rhs = rhs.iter().map(|it| it.char);971 lhs.eq(rhs)972 }973 Err(_) => false,974 },975 Separator::Lifetime(_punct, ident) => match expect_lifetime(&mut fork) {976 Ok(lifetime) => lifetime.sym == ident.sym,977 Err(_) => false,978 },979 };980 if ok {981 *iter = fork;982 }983 ok984}985986fn expect_tt(iter: &mut TtIter<'_>) -> Result<(), ()> {987 if let Some(TtElement::Leaf(tt::Leaf::Punct(punct))) = iter.peek() {988 if punct.char == '\'' {989 expect_lifetime(iter)?;990 } else {991 iter.expect_glued_punct()?;992 }993 } else {994 iter.next().ok_or(())?;995 }996 Ok(())997}998999fn expect_lifetime<'a>(iter: &mut TtIter<'a>) -> Result<tt::Ident, ()> {1000 let punct = iter.expect_single_punct()?;1001 if punct.char != '\'' {1002 return Err(());1003 }1004 iter.expect_ident_or_underscore()1005}10061007fn eat_char(iter: &mut TtIter<'_>, c: char) {1008 if matches!(iter.peek(), Some(TtElement::Leaf(tt::Leaf::Punct(tt::Punct { char, .. }))) if char == c)1009 {1010 iter.next().expect("already peeked");1011 }1012}
Code quality findings 54
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
//! Start parsing a a a a b against [· a $( a )* a b].
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
//! next: [a $( · a )* a b] [a $( a )* · a b].
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
//! cur: [a $( a · )* a b] [a $( a )* a · b]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
//! next: [a $( a )* · a b] [a $( · a )* a b] [a $( a )* a · b]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
//! cur: [a $( a · )* a b] [a $( a )* a · b]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
//! next: [a $( a )* · a b] [a $( · a )* a b] [a $( a )* a · b]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
//! cur: [a $( a · )* a b] [a $( a )* a · b]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
//! next: [a $( a )* · a b] [a $( · a )* a b] [a $( a )* a · b]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let len = target[idx].len();
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
target.push(target[idx].clone())
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
self.nodes[idx.0].push(LinkNode::Node(Rc::new(BindingKind::Empty(var.clone()))));
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
self.nodes[idx.0].push(LinkNode::Node(Rc::new(BindingKind::Optional(var.clone()))));
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
self.nodes[idx.0]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
self.nodes[idx.0].push(LinkNode::Node(Rc::new(BindingKind::Missing(var.clone(), kind))));
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
self.nodes[parent.0].push(LinkNode::Node(Rc::new(BindingKind::Nested(idx, nidx))));
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
self.nested[idx.1].push(LinkNode::Node(idx.0));
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
self.build_inner(&self.nodes[idx.0])
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
nested_refs: &mut Vec<&'b [LinkNode<Rc<BindingKind<'a>>>]>,
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
self.nested[id].iter().take(len).for_each(|it| match it {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
LinkNode::Node(id) => nested_refs.push(&self.nodes[*id]),
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let last = &self.nodes[idx];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
self.nested[nested_idx].iter().for_each(|it| match *it {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
LinkNode::Node(idx) => nested_refs.push(&self.nodes[idx]),
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
self.nodes[id].iter().take(len).for_each(|it| match it {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
link_nodes: &'b [LinkNode<Rc<BindingKind<'a>>>],
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
let first_is_single_quote = rhs[0].char == '\'';
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
rhs.get(1).map_or(rhs[0].span, |it| it.span),
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
ExpandError::binding_error(rhs[0].span, format!("expected punct: `{lhs}`"))
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
} else if let [state, ..] = &*eof_items {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
if let [state] = &*eof_items {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
if let Some((iter, _)) = next_items[0].meta_result.take() {
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
let mut item = bb_items.pop().unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning
correctness
unwrap-usage
let (iter, match_res) = item.meta_result.take().unwrap();
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
// The latter two are for [backwards compatibility][0].
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
// rustc [explicitly checks the next token][1].
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning
correctness
unchecked-indexing
inner: &'a [Op],
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning
correctness
expect-usage
iter.next().expect("already peeked");
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
bindings.push_empty(name.clone());
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
bindings.push_optional(name.clone());
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
bindings.inner.insert(name.clone(), Binding::Fragment(fragment.clone()));
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
bindings.inner.insert(name.clone(), Binding::Missing(*kind));
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
.entry(key.clone())
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info
performance
push-without-reserve
it.push(Binding::Nested(Vec::new()));
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
it.push(value.clone());
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info
performance
push-without-reserve
it.push(value.clone());
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info
performance
push-without-reserve
error_items.push($it);
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info
performance
push-without-reserve
$items.push($it);
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
src.clone(),
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info
correctness
match-wildcard
match src.next() {
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info
performance
push-without-reserve
stack.push(src.clone());
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
stack.push(src.clone());
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info
performance
clone-in-loop
Op::Var { name, .. } => collector_fun(name.clone()),
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info
correctness
match-wildcard
match self.delimited.kind {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info
correctness
match-wildcard
_ => match self.idx {