fix(items_after_statements): handle cfg_select! arms - #17508
fix(items_after_statements): handle cfg_select! arms#17508saberoueslati wants to merge 3 commits into
Conversation
|
Thanks for the pull request, and welcome! You should hear from one of our reviewers after this PR gets at least 2 reviews from the community. Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (
|
|
No changes for c4442e5 |
|
Documentation cut from the PR, but useful for reviewing:
This deliberately gives up on a few true positives, e.g. an item after a statement within the |
| /// `cfg_select!` splices the tokens of the selected arm into the enclosing block without applying | ||
| /// any expansion marker, so both the span and the syntax context of the resulting items are | ||
| /// indistinguishable from items written directly in the block. | ||
| /// | ||
| /// This deliberately gives up on a few true positives, e.g. an item after a statement within the | ||
| /// same `cfg_select!` arm, or `some_macro! { fn f() {} }` in statement position, as a false | ||
| /// negative is preferable to a false positive here. |
There was a problem hiding this comment.
Thanks for documenting this function so much, but it might be a little too verbose 😅 , I'll paste the removed text into a PR comment.
| /// `cfg_select!` splices the tokens of the selected arm into the enclosing block without applying | |
| /// any expansion marker, so both the span and the syntax context of the resulting items are | |
| /// indistinguishable from items written directly in the block. | |
| /// | |
| /// This deliberately gives up on a few true positives, e.g. an item after a statement within the | |
| /// same `cfg_select!` arm, or `some_macro! { fn f() {} }` in statement position, as a false | |
| /// negative is preferable to a false positive here. | |
| /// `cfg_select!` splices the tokens of the selected arm into the enclosing block without applying | |
| /// any expansion marker, so both the span and the syntax context of the resulting items are | |
| /// indistinguishable from items written directly in the block. |
| let mut direct_children = vec![false; item_spans.len()]; | ||
| if item_spans.is_empty() { |
There was a problem hiding this comment.
This could be made a bit simpler :)
| let mut direct_children = vec![false; item_spans.len()]; | |
| if item_spans.is_empty() { | |
| if item_spans.is_empty() { | |
| return vec![]; | |
| }; | |
| let mut direct_children = vec![false; item_spans.len()]; |
| block.span.with_source_text(cx, |src| { | ||
| let mut item_offsets = item_spans | ||
| .iter() | ||
| .enumerate() |
There was a problem hiding this comment.
We don't really need the enumerate be so early, we can wait until later to enumerate this Vec
There was a problem hiding this comment.
Done, the revised code no longer needs enumerate at all.
| block.span.with_source_text(cx, |src| { | ||
| let mut item_offsets = item_spans | ||
| .iter() | ||
| .enumerate() | ||
| .filter(|(_, item_span)| item_span.lo() >= block.span.lo() && item_span.lo() <= block.span.hi()) | ||
| .map(|(index, item_span)| (index, (item_span.lo() - block.span.lo()).to_usize())) | ||
| .filter(|&(_, offset)| offset <= src.len()) | ||
| .collect::<Vec<_>>(); | ||
| item_offsets.sort_unstable_by_key(|&(_, offset)| offset); | ||
|
|
||
| let mut depth = 0i32; | ||
| let mut offset = 0; | ||
| let mut next_item = 0; | ||
| for token in tokenize(src, FrontmatterAllowed::No) { | ||
| while let Some(&(index, item_offset)) = item_offsets.get(next_item) | ||
| && item_offset <= offset | ||
| { | ||
| direct_children[index] = depth == 1; | ||
| next_item += 1; | ||
| } | ||
|
|
||
| match token.kind { | ||
| TokenKind::OpenParen | TokenKind::OpenBrace | TokenKind::OpenBracket => depth += 1, | ||
| TokenKind::CloseParen | TokenKind::CloseBrace | TokenKind::CloseBracket => depth -= 1, | ||
| _ => {}, | ||
| } | ||
| offset += token.len as usize; | ||
| } | ||
|
|
||
| while let Some(&(index, _)) = item_offsets.get(next_item) { | ||
| direct_children[index] = depth == 1; | ||
| next_item += 1; | ||
| } | ||
| }); |
There was a problem hiding this comment.
Spans and source text is frail, it gets shifted, proc-macro'ed, replaced by include_str!, and all manners of other modifications. Is there any chance that we could be made stronger?
What about, instead of operating with spans, we query to tcx one of its parent functions? Like tcx.hir_parent_iter
There was a problem hiding this comment.
I looked into this. cfg_select! splices the selected arm's tokens into the enclosing block during expansion, keeping the original spans and root syntax context, so by the time we reach HIR, the resulting items are indistinguishable direct children of that block.
I instrumented the lint to compare an item inside a cfg_select! arm with a regular item after a statement:
cfg_select item ctxt == block ctxt: true from_expansion: false outer_expn: Root
genuine item ctxt == block ctxt: true from_expansion: false outer_expn: Root
Their hir_parent_iter chains are identical too. I couldn't find a tcx query that separates them; if there's one I've missed, I'm happy to switch to it.
Given that, source nesting seems to be the only available signal. I've made that path fail open:
- it now uses the full statement span instead of each leaf item's span; and
- if the source text or spans can't be mapped, it keeps the lint rather than suppressing it.
The first change also restores the grouped-use diagnostics that lintcheck flagged as removed.
Known tradeoff: this still misses an item after a statement within the same cfg_select! arm. I'd rather take that false negative than the false positive.
|
Reminder, once the PR becomes ready for a review, use |
|
@blyxyas I investigated the I therefore kept source nesting as the discriminator, but made it fail open: it uses full statement spans, and unmappable source/spans retain the lint instead of suppressing it. This also restores the grouped- |
|
@DanielEScherzer Thank you, the two removals were the leaf imports in a grouped |
|
I see, I'm not sure if this is even intended behavior. I think that this is a bug in |
Fix false positives in
items_after_statementsfor items insidecfg_select!arms by checking token nesting once per block.fixes #17498
changelog: [
items_after_statements]: handlecfg_select!arms without false positives