Skip to content

Commit 2b62d4b

Browse files
committed
Fix some cfg censoring bugs
1 parent 5b08b17 commit 2b62d4b

File tree

4 files changed

+85
-57
lines changed

4 files changed

+85
-57
lines changed

crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,10 @@ struct Foo {
610610
field1: i32,
611611
#[cfg(never)]
612612
field2: (),
613+
#[cfg(feature = "never")]
614+
field3: (),
615+
#[cfg(not(feature = "never"))]
616+
field4: (),
613617
}
614618
#[derive(Default)]
615619
enum Bar {
@@ -618,12 +622,16 @@ enum Bar {
618622
Bar,
619623
}
620624
"#,
621-
expect![[r#"
625+
expect![[r##"
622626
#[derive(Default)]
623627
struct Foo {
624628
field1: i32,
625629
#[cfg(never)]
626630
field2: (),
631+
#[cfg(feature = "never")]
632+
field3: (),
633+
#[cfg(not(feature = "never"))]
634+
field4: (),
627635
}
628636
#[derive(Default)]
629637
enum Bar {
@@ -635,14 +643,14 @@ enum Bar {
635643
impl < > $crate::default::Default for Foo< > where {
636644
fn default() -> Self {
637645
Foo {
638-
field1: $crate::default::Default::default(),
646+
field1: $crate::default::Default::default(), field4: $crate::default::Default::default(),
639647
}
640648
}
641649
}
642650
impl < > $crate::default::Default for Bar< > where {
643651
fn default() -> Self {
644652
Bar::Bar
645653
}
646-
}"#]],
654+
}"##]],
647655
);
648656
}

crates/hir-expand/src/cfg_process.rs

+68-51
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,59 @@
11
//! Processes out #[cfg] and #[cfg_attr] attributes from the input for the derive macro
22
use std::iter::Peekable;
33

4+
use base_db::CrateId;
45
use cfg::{CfgAtom, CfgExpr};
56
use rustc_hash::FxHashSet;
67
use syntax::{
78
ast::{self, Attr, HasAttrs, Meta, VariantList},
8-
AstNode, NodeOrToken, SyntaxElement, SyntaxNode, T,
9+
AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, T,
910
};
1011
use tracing::{debug, warn};
1112
use tt::SmolStr;
1213

1314
use crate::{db::ExpandDatabase, proc_macro::ProcMacroKind, MacroCallLoc, MacroDefKind};
1415

15-
fn check_cfg_attr(attr: &Attr, loc: &MacroCallLoc, db: &dyn ExpandDatabase) -> Option<bool> {
16+
fn check_cfg(db: &dyn ExpandDatabase, attr: &Attr, krate: CrateId) -> Option<bool> {
1617
if !attr.simple_name().as_deref().map(|v| v == "cfg")? {
1718
return None;
1819
}
19-
debug!("Evaluating cfg {}", attr);
2020
let cfg = parse_from_attr_meta(attr.meta()?)?;
21-
debug!("Checking cfg {:?}", cfg);
22-
let enabled = db.crate_graph()[loc.krate].cfg_options.check(&cfg) != Some(false);
21+
let enabled = db.crate_graph()[krate].cfg_options.check(&cfg) != Some(false);
2322
Some(enabled)
2423
}
2524

26-
fn check_cfg_attr_attr(attr: &Attr, loc: &MacroCallLoc, db: &dyn ExpandDatabase) -> Option<bool> {
25+
fn check_cfg_attr(db: &dyn ExpandDatabase, attr: &Attr, krate: CrateId) -> Option<bool> {
2726
if !attr.simple_name().as_deref().map(|v| v == "cfg_attr")? {
2827
return None;
2928
}
30-
debug!("Evaluating cfg_attr {}", attr);
3129
let cfg_expr = parse_from_attr_meta(attr.meta()?)?;
32-
debug!("Checking cfg_attr {:?}", cfg_expr);
33-
let enabled = db.crate_graph()[loc.krate].cfg_options.check(&cfg_expr) != Some(false);
30+
let enabled = db.crate_graph()[krate].cfg_options.check(&cfg_expr) != Some(false);
3431
Some(enabled)
3532
}
3633

3734
fn process_has_attrs_with_possible_comma<I: HasAttrs>(
38-
items: impl Iterator<Item = I>,
39-
loc: &MacroCallLoc,
4035
db: &dyn ExpandDatabase,
36+
items: impl Iterator<Item = I>,
37+
krate: CrateId,
4138
remove: &mut FxHashSet<SyntaxElement>,
4239
) -> Option<()> {
4340
for item in items {
4441
let field_attrs = item.attrs();
4542
'attrs: for attr in field_attrs {
46-
if check_cfg_attr(&attr, loc, db).map(|enabled| !enabled).unwrap_or_default() {
47-
debug!("censoring type {:?}", item.syntax());
48-
remove.insert(item.syntax().clone().into());
49-
// We need to remove the , as well
50-
remove_possible_comma(&item, remove);
51-
break 'attrs;
43+
if let Some(enabled) = check_cfg(db, &attr, krate) {
44+
if enabled {
45+
debug!("censoring {:?}", attr.syntax());
46+
remove.insert(attr.syntax().clone().into());
47+
} else {
48+
debug!("censoring {:?}", item.syntax());
49+
remove.insert(item.syntax().clone().into());
50+
// We need to remove the , as well
51+
remove_possible_comma(&item, remove);
52+
break 'attrs;
53+
}
5254
}
5355

54-
if let Some(enabled) = check_cfg_attr_attr(&attr, loc, db) {
56+
if let Some(enabled) = check_cfg_attr(db, &attr, krate) {
5557
if enabled {
5658
debug!("Removing cfg_attr tokens {:?}", attr);
5759
let meta = attr.meta()?;
@@ -60,13 +62,13 @@ fn process_has_attrs_with_possible_comma<I: HasAttrs>(
6062
} else {
6163
debug!("censoring type cfg_attr {:?}", item.syntax());
6264
remove.insert(attr.syntax().clone().into());
63-
continue;
6465
}
6566
}
6667
}
6768
}
6869
Some(())
6970
}
71+
7072
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
7173
enum CfgExprStage {
7274
/// Stripping the CFGExpr part of the attribute
@@ -78,6 +80,7 @@ enum CfgExprStage {
7880
// Related Issue: https://github.com/rust-lang/rust-analyzer/issues/10110
7981
EverythingElse,
8082
}
83+
8184
/// This function creates its own set of tokens to remove. To help prevent malformed syntax as input.
8285
fn remove_tokens_within_cfg_attr(meta: Meta) -> Option<FxHashSet<SyntaxElement>> {
8386
let mut remove: FxHashSet<SyntaxElement> = FxHashSet::default();
@@ -131,23 +134,28 @@ fn remove_possible_comma(item: &impl AstNode, res: &mut FxHashSet<SyntaxElement>
131134
}
132135
}
133136
fn process_enum(
134-
variants: VariantList,
135-
loc: &MacroCallLoc,
136137
db: &dyn ExpandDatabase,
138+
variants: VariantList,
139+
krate: CrateId,
137140
remove: &mut FxHashSet<SyntaxElement>,
138141
) -> Option<()> {
139142
'variant: for variant in variants.variants() {
140143
for attr in variant.attrs() {
141-
if check_cfg_attr(&attr, loc, db).map(|enabled| !enabled).unwrap_or_default() {
142-
// Rustc does not strip the attribute if it is enabled. So we will leave it
143-
debug!("censoring type {:?}", variant.syntax());
144-
remove.insert(variant.syntax().clone().into());
145-
// We need to remove the , as well
146-
remove_possible_comma(&variant, remove);
147-
continue 'variant;
148-
};
144+
if let Some(enabled) = check_cfg(db, &attr, krate) {
145+
if enabled {
146+
debug!("censoring {:?}", attr.syntax());
147+
remove.insert(attr.syntax().clone().into());
148+
} else {
149+
// Rustc does not strip the attribute if it is enabled. So we will leave it
150+
debug!("censoring type {:?}", variant.syntax());
151+
remove.insert(variant.syntax().clone().into());
152+
// We need to remove the , as well
153+
remove_possible_comma(&variant, remove);
154+
continue 'variant;
155+
}
156+
}
149157

150-
if let Some(enabled) = check_cfg_attr_attr(&attr, loc, db) {
158+
if let Some(enabled) = check_cfg_attr(db, &attr, krate) {
151159
if enabled {
152160
debug!("Removing cfg_attr tokens {:?}", attr);
153161
let meta = attr.meta()?;
@@ -156,17 +164,16 @@ fn process_enum(
156164
} else {
157165
debug!("censoring type cfg_attr {:?}", variant.syntax());
158166
remove.insert(attr.syntax().clone().into());
159-
continue;
160167
}
161168
}
162169
}
163170
if let Some(fields) = variant.field_list() {
164171
match fields {
165172
ast::FieldList::RecordFieldList(fields) => {
166-
process_has_attrs_with_possible_comma(fields.fields(), loc, db, remove)?;
173+
process_has_attrs_with_possible_comma(db, fields.fields(), krate, remove)?;
167174
}
168175
ast::FieldList::TupleFieldList(fields) => {
169-
process_has_attrs_with_possible_comma(fields.fields(), loc, db, remove)?;
176+
process_has_attrs_with_possible_comma(db, fields.fields(), krate, remove)?;
170177
}
171178
}
172179
}
@@ -175,9 +182,9 @@ fn process_enum(
175182
}
176183

177184
pub(crate) fn process_cfg_attrs(
185+
db: &dyn ExpandDatabase,
178186
node: &SyntaxNode,
179187
loc: &MacroCallLoc,
180-
db: &dyn ExpandDatabase,
181188
) -> Option<FxHashSet<SyntaxElement>> {
182189
// FIXME: #[cfg_eval] is not implemented. But it is not stable yet
183190
let is_derive = match loc.def.kind {
@@ -193,36 +200,35 @@ pub(crate) fn process_cfg_attrs(
193200

194201
let item = ast::Item::cast(node.clone())?;
195202
for attr in item.attrs() {
196-
if let Some(enabled) = check_cfg_attr_attr(&attr, loc, db) {
203+
if let Some(enabled) = check_cfg_attr(db, &attr, loc.krate) {
197204
if enabled {
198205
debug!("Removing cfg_attr tokens {:?}", attr);
199206
let meta = attr.meta()?;
200207
let removes_from_cfg_attr = remove_tokens_within_cfg_attr(meta)?;
201208
remove.extend(removes_from_cfg_attr);
202209
} else {
203-
debug!("censoring type cfg_attr {:?}", item.syntax());
210+
debug!("Removing type cfg_attr {:?}", item.syntax());
204211
remove.insert(attr.syntax().clone().into());
205-
continue;
206212
}
207213
}
208214
}
209215
match item {
210216
ast::Item::Struct(it) => match it.field_list()? {
211217
ast::FieldList::RecordFieldList(fields) => {
212-
process_has_attrs_with_possible_comma(fields.fields(), loc, db, &mut remove)?;
218+
process_has_attrs_with_possible_comma(db, fields.fields(), loc.krate, &mut remove)?;
213219
}
214220
ast::FieldList::TupleFieldList(fields) => {
215-
process_has_attrs_with_possible_comma(fields.fields(), loc, db, &mut remove)?;
221+
process_has_attrs_with_possible_comma(db, fields.fields(), loc.krate, &mut remove)?;
216222
}
217223
},
218224
ast::Item::Enum(it) => {
219-
process_enum(it.variant_list()?, loc, db, &mut remove)?;
225+
process_enum(db, it.variant_list()?, loc.krate, &mut remove)?;
220226
}
221227
ast::Item::Union(it) => {
222228
process_has_attrs_with_possible_comma(
223-
it.record_field_list()?.fields(),
224-
loc,
225229
db,
230+
it.record_field_list()?.fields(),
231+
loc.krate,
226232
&mut remove,
227233
)?;
228234
}
@@ -234,10 +240,22 @@ pub(crate) fn process_cfg_attrs(
234240
/// Parses a `cfg` attribute from the meta
235241
fn parse_from_attr_meta(meta: Meta) -> Option<CfgExpr> {
236242
let tt = meta.token_tree()?;
237-
let mut iter = tt.token_trees_and_tokens().skip(1).peekable();
243+
let mut iter = tt
244+
.token_trees_and_tokens()
245+
.filter(is_not_whitespace)
246+
.skip(1)
247+
.take_while(is_not_closing_paren)
248+
.peekable();
238249
next_cfg_expr_from_syntax(&mut iter)
239250
}
240251

252+
fn is_not_closing_paren(element: &NodeOrToken<ast::TokenTree, syntax::SyntaxToken>) -> bool {
253+
!matches!(element, NodeOrToken::Token(token) if (token.kind() == syntax::T![')']))
254+
}
255+
fn is_not_whitespace(element: &NodeOrToken<ast::TokenTree, syntax::SyntaxToken>) -> bool {
256+
!matches!(element, NodeOrToken::Token(token) if (token.kind() == SyntaxKind::WHITESPACE))
257+
}
258+
241259
fn next_cfg_expr_from_syntax<I>(iter: &mut Peekable<I>) -> Option<CfgExpr>
242260
where
243261
I: Iterator<Item = NodeOrToken<ast::TokenTree, syntax::SyntaxToken>>,
@@ -256,14 +274,13 @@ where
256274
let Some(NodeOrToken::Node(tree)) = iter.next() else {
257275
return Some(CfgExpr::Invalid);
258276
};
259-
let mut tree_iter = tree.token_trees_and_tokens().skip(1).peekable();
260-
while tree_iter
261-
.peek()
262-
.filter(
263-
|element| matches!(element, NodeOrToken::Token(token) if (token.kind() != syntax::T![')'])),
264-
)
265-
.is_some()
266-
{
277+
let mut tree_iter = tree
278+
.token_trees_and_tokens()
279+
.filter(is_not_whitespace)
280+
.skip(1)
281+
.take_while(is_not_closing_paren)
282+
.peekable();
283+
while tree_iter.peek().is_some() {
267284
let pred = next_cfg_expr_from_syntax(&mut tree_iter);
268285
if let Some(pred) = pred {
269286
preds.push(pred);

crates/hir-expand/src/db.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ pub fn expand_speculative(
175175
};
176176

177177
let censor_cfg =
178-
cfg_process::process_cfg_attrs(speculative_args, &loc, db).unwrap_or_default();
178+
cfg_process::process_cfg_attrs(db, speculative_args, &loc).unwrap_or_default();
179179
let mut fixups = fixup::fixup_syntax(span_map, speculative_args, span);
180180
fixups.append.retain(|it, _| match it {
181181
syntax::NodeOrToken::Token(_) => true,
@@ -462,7 +462,7 @@ fn macro_arg(db: &dyn ExpandDatabase, id: MacroCallId) -> MacroArgResult {
462462

463463
let (mut tt, undo_info) = {
464464
let syntax = item_node.syntax();
465-
let censor_cfg = cfg_process::process_cfg_attrs(syntax, &loc, db).unwrap_or_default();
465+
let censor_cfg = cfg_process::process_cfg_attrs(db, syntax, &loc).unwrap_or_default();
466466
let mut fixups = fixup::fixup_syntax(map.as_ref(), syntax, span);
467467
fixups.append.retain(|it, _| match it {
468468
syntax::NodeOrToken::Token(_) => true,

crates/ide-completion/src/completions/postfix.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use ide_db::{
99
ty_filter::TryEnum,
1010
SnippetCap,
1111
};
12+
use stdx::never;
1213
use syntax::{
1314
ast::{self, make, AstNode, AstToken},
1415
SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR},
@@ -319,7 +320,9 @@ fn build_postfix_snippet_builder<'ctx>(
319320
) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> {
320321
let receiver_range = ctx.sema.original_range_opt(receiver.syntax())?.range;
321322
if ctx.source_range().end() < receiver_range.start() {
322-
// This shouldn't happen, yet it does. I assume this might be due to an incorrect token mapping.
323+
// This shouldn't happen, yet it does. I assume this might be due to an incorrect token
324+
// mapping.
325+
never!();
323326
return None;
324327
}
325328
let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());

0 commit comments

Comments
 (0)