src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs RUST 1,013 lines View on github.com → Search inside
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 {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.