Unnamed repository; edit this file 'description' to name the repository.
Parse `cfg_attr` and `cfg` specially
This allows us to simplify expansion of cfg_attr in `expand_cfg_attr()`, and also fixes a bunch of bugs like expansion of `doc = macro!()` inside `cfg_attr`.
98 files changed, 1957 insertions, 1344 deletions
diff --git a/Cargo.lock b/Cargo.lock index 770f8cb940..da530b3a93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,7 +234,6 @@ dependencies = [ "intern", "oorandom", "rustc-hash 2.1.1", - "span", "syntax", "syntax-bridge", "tracing", @@ -846,7 +845,6 @@ dependencies = [ name = "hir-expand" version = "0.0.0" dependencies = [ - "arrayvec", "base-db", "cfg", "cov-mark", @@ -2733,6 +2731,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustc-literal-escaper 0.0.4", "rustc_apfloat", + "smallvec", "smol_str 0.3.2", "stdx", "test-utils", diff --git a/crates/cfg/Cargo.toml b/crates/cfg/Cargo.toml index cf2a7607b0..15de1f3293 100644 --- a/crates/cfg/Cargo.toml +++ b/crates/cfg/Cargo.toml @@ -19,7 +19,6 @@ tracing.workspace = true # locals deps tt = { workspace = true, optional = true } syntax = { workspace = true, optional = true } -span = { path = "../span", version = "0.0", optional = true } intern.workspace = true [dev-dependencies] @@ -36,7 +35,7 @@ cfg = { path = ".", default-features = false, features = ["tt"] } [features] default = [] -syntax = ["dep:syntax", "dep:span"] +syntax = ["dep:syntax"] tt = ["dep:tt"] in-rust-tree = [] diff --git a/crates/cfg/src/cfg_expr.rs b/crates/cfg/src/cfg_expr.rs index d253f6f492..7af3ed5dc9 100644 --- a/crates/cfg/src/cfg_expr.rs +++ b/crates/cfg/src/cfg_expr.rs @@ -106,10 +106,54 @@ impl CfgExpr { } #[cfg(feature = "syntax")] - pub fn parse_from_ast( - ast: &mut std::iter::Peekable<syntax::ast::TokenTreeChildren>, - ) -> CfgExpr { - next_cfg_expr_from_ast(ast).unwrap_or(CfgExpr::Invalid) + pub fn parse_from_ast(ast: syntax::ast::CfgPredicate) -> CfgExpr { + use intern::sym; + use syntax::ast::{self, AstToken}; + + match ast { + ast::CfgPredicate::CfgAtom(atom) => { + let atom = match atom.key() { + Some(ast::CfgAtomKey::True) => CfgAtom::Flag(sym::true_), + Some(ast::CfgAtomKey::False) => CfgAtom::Flag(sym::false_), + Some(ast::CfgAtomKey::Ident(key)) => { + let key = Symbol::intern(key.text()); + match atom.string_token().and_then(ast::String::cast) { + Some(value) => { + if let Ok(value) = value.value() { + CfgAtom::KeyValue { key, value: Symbol::intern(&value) } + } else { + return CfgExpr::Invalid; + } + } + None => CfgAtom::Flag(key), + } + } + None => return CfgExpr::Invalid, + }; + CfgExpr::Atom(atom) + } + ast::CfgPredicate::CfgComposite(composite) => { + let Some(keyword) = composite.keyword() else { + return CfgExpr::Invalid; + }; + match keyword.text() { + "all" => CfgExpr::All( + composite.cfg_predicates().map(CfgExpr::parse_from_ast).collect(), + ), + "any" => CfgExpr::Any( + composite.cfg_predicates().map(CfgExpr::parse_from_ast).collect(), + ), + "not" => { + let mut inner = composite.cfg_predicates(); + let (Some(inner), None) = (inner.next(), inner.next()) else { + return CfgExpr::Invalid; + }; + CfgExpr::Not(Box::new(CfgExpr::parse_from_ast(inner))) + } + _ => CfgExpr::Invalid, + } + } + } } /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates. @@ -128,65 +172,6 @@ impl CfgExpr { } } -#[cfg(feature = "syntax")] -fn next_cfg_expr_from_ast( - it: &mut std::iter::Peekable<syntax::ast::TokenTreeChildren>, -) -> Option<CfgExpr> { - use intern::sym; - use syntax::{NodeOrToken, SyntaxKind, T, ast}; - - let name = match it.next() { - None => return None, - Some(NodeOrToken::Token(ident)) if ident.kind().is_any_identifier() => { - Symbol::intern(ident.text()) - } - Some(_) => return Some(CfgExpr::Invalid), - }; - - let ret = match it.peek() { - Some(NodeOrToken::Token(eq)) if eq.kind() == T![=] => { - it.next(); - if let Some(NodeOrToken::Token(literal)) = it.peek() - && matches!(literal.kind(), SyntaxKind::STRING) - { - let dummy_span = span::Span { - range: span::TextRange::empty(span::TextSize::new(0)), - anchor: span::SpanAnchor { - file_id: span::EditionedFileId::from_raw(0), - ast_id: span::FIXUP_ERASED_FILE_AST_ID_MARKER, - }, - ctx: span::SyntaxContext::root(span::Edition::Edition2015), - }; - let literal = - Symbol::intern(tt::token_to_literal(literal.text(), dummy_span).text()); - it.next(); - CfgAtom::KeyValue { key: name, value: literal.clone() }.into() - } else { - return Some(CfgExpr::Invalid); - } - } - Some(NodeOrToken::Node(subtree)) => { - let mut subtree_iter = ast::TokenTreeChildren::new(subtree).peekable(); - it.next(); - let mut subs = std::iter::from_fn(|| next_cfg_expr_from_ast(&mut subtree_iter)); - match name { - s if s == sym::all => CfgExpr::All(subs.collect()), - s if s == sym::any => CfgExpr::Any(subs.collect()), - s if s == sym::not => { - CfgExpr::Not(Box::new(subs.next().unwrap_or(CfgExpr::Invalid))) - } - _ => CfgExpr::Invalid, - } - } - _ => CfgAtom::Flag(name).into(), - }; - - // Eat comma separator - while it.next().is_some_and(|it| it.as_token().is_none_or(|it| it.kind() != T![,])) {} - - Some(ret) -} - #[cfg(feature = "tt")] fn next_cfg_expr(it: &mut tt::iter::TtIter<'_>) -> Option<CfgExpr> { use intern::sym; diff --git a/crates/cfg/src/tests.rs b/crates/cfg/src/tests.rs index 52c581dbbd..bfc9220a05 100644 --- a/crates/cfg/src/tests.rs +++ b/crates/cfg/src/tests.rs @@ -1,10 +1,7 @@ use arbitrary::{Arbitrary, Unstructured}; use expect_test::{Expect, expect}; use intern::Symbol; -use syntax::{ - AstNode, Edition, - ast::{self, TokenTreeChildren}, -}; +use syntax::{AstNode, Edition, ast}; use syntax_bridge::{ DocCommentDesugarMode, dummy_test_span_utils::{DUMMY, DummyTestSpanMap}, @@ -14,32 +11,32 @@ use syntax_bridge::{ use crate::{CfgAtom, CfgExpr, CfgOptions, DnfExpr}; #[track_caller] -fn parse_ast_cfg(tt: &ast::TokenTree) -> CfgExpr { - CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(tt).peekable()) +fn parse_ast_cfg(pred: ast::CfgPredicate) -> CfgExpr { + CfgExpr::parse_from_ast(pred) } #[track_caller] fn assert_parse_result(input: &str, expected: CfgExpr) { - let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap(); - let tt_ast = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + let source_file = ast::SourceFile::parse(input, Edition::CURRENT).syntax_node(); + let pred_ast = source_file.descendants().find_map(ast::CfgPredicate::cast).unwrap(); let tt = syntax_node_to_token_tree( - tt_ast.syntax(), + pred_ast.syntax(), DummyTestSpanMap, DUMMY, DocCommentDesugarMode::ProcMacro, ); let cfg = CfgExpr::parse(&tt); assert_eq!(cfg, expected); - let cfg = parse_ast_cfg(&tt_ast); + let cfg = parse_ast_cfg(pred_ast); assert_eq!(cfg, expected); } #[track_caller] fn check_dnf(input: &str, expect: Expect) { let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap(); - let tt_ast = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + let pred_ast = source_file.syntax().descendants().find_map(ast::CfgPredicate::cast).unwrap(); let tt = syntax_node_to_token_tree( - tt_ast.syntax(), + pred_ast.syntax(), DummyTestSpanMap, DUMMY, DocCommentDesugarMode::ProcMacro, @@ -47,7 +44,7 @@ fn check_dnf(input: &str, expect: Expect) { let cfg = CfgExpr::parse(&tt); let actual = format!("#![cfg({})]", DnfExpr::new(&cfg)); expect.assert_eq(&actual); - let cfg = parse_ast_cfg(&tt_ast); + let cfg = parse_ast_cfg(pred_ast); let actual = format!("#![cfg({})]", DnfExpr::new(&cfg)); expect.assert_eq(&actual); } @@ -55,9 +52,9 @@ fn check_dnf(input: &str, expect: Expect) { #[track_caller] fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) { let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap(); - let tt_ast = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + let pred_ast = source_file.syntax().descendants().find_map(ast::CfgPredicate::cast).unwrap(); let tt = syntax_node_to_token_tree( - tt_ast.syntax(), + pred_ast.syntax(), DummyTestSpanMap, DUMMY, DocCommentDesugarMode::ProcMacro, @@ -66,7 +63,7 @@ fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) { let dnf = DnfExpr::new(&cfg); let why_inactive = dnf.why_inactive(opts).unwrap().to_string(); expect.assert_eq(&why_inactive); - let cfg = parse_ast_cfg(&tt_ast); + let cfg = parse_ast_cfg(pred_ast); let dnf = DnfExpr::new(&cfg); let why_inactive = dnf.why_inactive(opts).unwrap().to_string(); expect.assert_eq(&why_inactive); @@ -75,9 +72,9 @@ fn check_why_inactive(input: &str, opts: &CfgOptions, expect: Expect) { #[track_caller] fn check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str]) { let source_file = ast::SourceFile::parse(input, Edition::CURRENT).ok().unwrap(); - let tt_ast = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + let pred_ast = source_file.syntax().descendants().find_map(ast::CfgPredicate::cast).unwrap(); let tt = syntax_node_to_token_tree( - tt_ast.syntax(), + pred_ast.syntax(), DummyTestSpanMap, DUMMY, DocCommentDesugarMode::ProcMacro, @@ -86,7 +83,7 @@ fn check_enable_hints(input: &str, opts: &CfgOptions, expected_hints: &[&str]) { let dnf = DnfExpr::new(&cfg); let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::<Vec<_>>(); assert_eq!(hints, expected_hints); - let cfg = parse_ast_cfg(&tt_ast); + let cfg = parse_ast_cfg(pred_ast); let dnf = DnfExpr::new(&cfg); let hints = dnf.compute_enable_hints(opts).map(|diff| diff.to_string()).collect::<Vec<_>>(); assert_eq!(hints, expected_hints); @@ -119,20 +116,6 @@ fn test_cfg_expr_parser() { .into_boxed_slice(), ), ); - - assert_parse_result( - r#"#![cfg(any(not(), all(), , bar = "baz",))]"#, - CfgExpr::Any( - vec![ - CfgExpr::Not(Box::new(CfgExpr::Invalid)), - CfgExpr::All(Box::new([])), - CfgExpr::Invalid, - CfgAtom::KeyValue { key: Symbol::intern("bar"), value: Symbol::intern("baz") } - .into(), - ] - .into_boxed_slice(), - ), - ); } #[test] diff --git a/crates/hir-def/src/attrs.rs b/crates/hir-def/src/attrs.rs index dddfe8cefd..b560d08492 100644 --- a/crates/hir-def/src/attrs.rs +++ b/crates/hir-def/src/attrs.rs @@ -22,7 +22,7 @@ use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ InFile, Lookup, - attrs::{Meta, expand_cfg_attr}, + attrs::{AstKeyValueMetaExt, AstPathExt, expand_cfg_attr}, }; use intern::Symbol; use itertools::Itertools; @@ -128,63 +128,89 @@ fn extract_rustc_skip_during_method_dispatch(attr_flags: &mut AttrFlags, tt: ast } #[inline] -fn match_attr_flags(attr_flags: &mut AttrFlags, attr: Meta) -> ControlFlow<Infallible> { +fn match_attr_flags(attr_flags: &mut AttrFlags, attr: ast::Meta) -> ControlFlow<Infallible> { match attr { - Meta::NamedKeyValue { name: Some(name), value, .. } => match name.text() { - "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED), - "ignore" => attr_flags.insert(AttrFlags::IS_IGNORE), - "lang" => attr_flags.insert(AttrFlags::LANG_ITEM), - "path" => attr_flags.insert(AttrFlags::HAS_PATH), - "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE), - "export_name" => { - if let Some(value) = value - && let Some(value) = ast::String::cast(value) - && let Ok(value) = value.value() - && *value == *"main" - { - attr_flags.insert(AttrFlags::IS_EXPORT_NAME_MAIN); - } - } - _ => {} - }, - Meta::TokenTree { path, tt } => match path.segments.len() { - 1 => match path.segments[0].text() { + ast::Meta::CfgMeta(_) => attr_flags.insert(AttrFlags::HAS_CFG), + ast::Meta::KeyValueMeta(attr) => { + let Some(key) = attr.path().as_one_segment() else { return ControlFlow::Continue(()) }; + match &*key { "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED), - "cfg" => attr_flags.insert(AttrFlags::HAS_CFG), - "doc" => extract_doc_tt_attr(attr_flags, tt), - "repr" => attr_flags.insert(AttrFlags::HAS_REPR), - "target_feature" => attr_flags.insert(AttrFlags::HAS_TARGET_FEATURE), - "proc_macro_derive" | "rustc_builtin_macro" => { - attr_flags.insert(AttrFlags::IS_DERIVE_OR_BUILTIN_MACRO) - } + "ignore" => attr_flags.insert(AttrFlags::IS_IGNORE), + "lang" => attr_flags.insert(AttrFlags::LANG_ITEM), + "path" => attr_flags.insert(AttrFlags::HAS_PATH), "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE), - "rustc_layout_scalar_valid_range_start" | "rustc_layout_scalar_valid_range_end" => { - attr_flags.insert(AttrFlags::RUSTC_LAYOUT_SCALAR_VALID_RANGE) - } - "rustc_legacy_const_generics" => { - attr_flags.insert(AttrFlags::HAS_LEGACY_CONST_GENERICS) - } - "rustc_skip_during_method_dispatch" => { - extract_rustc_skip_during_method_dispatch(attr_flags, tt) - } - "rustc_deprecated_safe_2024" => { - attr_flags.insert(AttrFlags::RUSTC_DEPRECATED_SAFE_2024) + "export_name" => { + if let Some(value) = attr.value_string() + && *value == *"main" + { + attr_flags.insert(AttrFlags::IS_EXPORT_NAME_MAIN); + } } _ => {} - }, - 2 => match path.segments[0].text() { - "rust_analyzer" => match path.segments[1].text() { - "completions" => extract_ra_completions(attr_flags, tt), - "macro_style" => extract_ra_macro_style(attr_flags, tt), + } + } + ast::Meta::TokenTreeMeta(attr) => { + let (Some((first_segment, second_segment)), Some(tt)) = + (attr.path().as_up_to_two_segment(), attr.token_tree()) + else { + return ControlFlow::Continue(()); + }; + match second_segment { + None => match &*first_segment { + "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED), + "doc" => extract_doc_tt_attr(attr_flags, tt), + "repr" => attr_flags.insert(AttrFlags::HAS_REPR), + "target_feature" => attr_flags.insert(AttrFlags::HAS_TARGET_FEATURE), + "proc_macro_derive" | "rustc_builtin_macro" => { + attr_flags.insert(AttrFlags::IS_DERIVE_OR_BUILTIN_MACRO) + } + "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE), + "rustc_layout_scalar_valid_range_start" + | "rustc_layout_scalar_valid_range_end" => { + attr_flags.insert(AttrFlags::RUSTC_LAYOUT_SCALAR_VALID_RANGE) + } + "rustc_legacy_const_generics" => { + attr_flags.insert(AttrFlags::HAS_LEGACY_CONST_GENERICS) + } + "rustc_skip_during_method_dispatch" => { + extract_rustc_skip_during_method_dispatch(attr_flags, tt) + } + "rustc_deprecated_safe_2024" => { + attr_flags.insert(AttrFlags::RUSTC_DEPRECATED_SAFE_2024) + } _ => {} }, - _ => {} - }, - _ => {} - }, - Meta::Path { path } => { - match path.segments.len() { - 1 => match path.segments[0].text() { + Some(second_segment) => match &*first_segment { + "rust_analyzer" => match &*second_segment { + "completions" => extract_ra_completions(attr_flags, tt), + "macro_style" => extract_ra_macro_style(attr_flags, tt), + _ => {} + }, + _ => {} + }, + } + } + ast::Meta::PathMeta(attr) => { + let is_test = attr.path().is_some_and(|path| { + let Some(segment1) = (|| path.segment()?.name_ref())() else { return false }; + let segment2 = path.qualifier(); + let segment3 = segment2.as_ref().and_then(|it| it.qualifier()); + let segment4 = segment3.as_ref().and_then(|it| it.qualifier()); + let segment3 = segment3.and_then(|it| it.segment()?.name_ref()); + let segment4 = segment4.and_then(|it| it.segment()?.name_ref()); + segment1.text() == "test" + && segment3.is_none_or(|it| it.text() == "prelude") + && segment4.is_none_or(|it| it.text() == "core") + }); + if is_test { + attr_flags.insert(AttrFlags::IS_TEST); + } + + let Some((first_segment, second_segment)) = attr.path().as_up_to_two_segment() else { + return ControlFlow::Continue(()); + }; + match second_segment { + None => match &*first_segment { "rustc_has_incoherent_inherent_impls" => { attr_flags.insert(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) } @@ -228,18 +254,13 @@ fn match_attr_flags(attr_flags: &mut AttrFlags, attr: Meta) -> ControlFlow<Infal } _ => {} }, - 2 => match path.segments[0].text() { - "rust_analyzer" => match path.segments[1].text() { + Some(second_segment) => match &*first_segment { + "rust_analyzer" => match &*second_segment { "skip" => attr_flags.insert(AttrFlags::RUST_ANALYZER_SKIP), _ => {} }, _ => {} }, - _ => {} - } - - if path.is_test { - attr_flags.insert(AttrFlags::IS_TEST); } } _ => {} @@ -420,7 +441,7 @@ fn resolver_for_attr_def_id(db: &dyn DefDatabase, owner: AttrDefId) -> Resolver< fn collect_attrs<BreakValue>( db: &dyn DefDatabase, owner: AttrDefId, - mut callback: impl FnMut(Meta) -> ControlFlow<BreakValue>, + mut callback: impl FnMut(ast::Meta) -> ControlFlow<BreakValue>, ) -> Option<BreakValue> { let (source, outer_mod_decl, extra_crate_attrs, krate) = attrs_source(db, owner); let extra_attrs = extra_crate_attrs @@ -432,7 +453,7 @@ fn collect_attrs<BreakValue>( expand_cfg_attr( extra_attrs.chain(ast::attrs_including_inner(&source.value)), || cfg_options.get_or_insert_with(|| krate.cfg_options(db)), - move |meta, _, _, _| callback(meta), + move |meta, _| callback(meta), ) } @@ -500,9 +521,10 @@ pub struct DeriveInfo { pub helpers: Box<[Symbol]>, } -fn extract_doc_aliases(result: &mut Vec<Symbol>, attr: Meta) -> ControlFlow<Infallible> { - if let Meta::TokenTree { path, tt } = attr - && path.is1("doc") +fn extract_doc_aliases(result: &mut Vec<Symbol>, attr: ast::Meta) -> ControlFlow<Infallible> { + if let ast::Meta::TokenTreeMeta(attr) = attr + && attr.path().is1("doc") + && let Some(tt) = attr.token_tree() { for atom in DocAtom::parse(tt) { match atom { @@ -519,11 +541,11 @@ fn extract_doc_aliases(result: &mut Vec<Symbol>, attr: Meta) -> ControlFlow<Infa ControlFlow::Continue(()) } -fn extract_cfgs(result: &mut Vec<CfgExpr>, attr: Meta) -> ControlFlow<Infallible> { - if let Meta::TokenTree { path, tt } = attr - && path.is1("cfg") +fn extract_cfgs(result: &mut Vec<CfgExpr>, attr: ast::Meta) -> ControlFlow<Infallible> { + if let ast::Meta::CfgMeta(attr) = attr + && let Some(cfg_predicate) = attr.cfg_predicate() { - result.push(CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(&tt).peekable())); + result.push(CfgExpr::parse_from_ast(cfg_predicate)); } ControlFlow::Continue(()) } @@ -554,7 +576,7 @@ impl AttrFlags { expand_cfg_attr( field.value.attrs(), || cfg_options, - |attr, _, _, _| match_attr_flags(&mut attr_flags, attr), + |attr, _| match_attr_flags(&mut attr_flags, attr), ); attr_flags }) @@ -591,7 +613,7 @@ impl AttrFlags { let lifetimes_source = HasChildSource::<LocalLifetimeParamId>::child_source(&def, db); for (lifetime_id, lifetime) in lifetimes_source.value.iter() { let mut attr_flags = AttrFlags::empty(); - expand_cfg_attr(lifetime.attrs(), &mut cfg_options, |attr, _, _, _| { + expand_cfg_attr(lifetime.attrs(), &mut cfg_options, |attr, _| { match_attr_flags(&mut attr_flags, attr) }); if !attr_flags.is_empty() { @@ -603,7 +625,7 @@ impl AttrFlags { HasChildSource::<LocalTypeOrConstParamId>::child_source(&def, db); for (type_or_const_id, type_or_const) in type_and_consts_source.value.iter() { let mut attr_flags = AttrFlags::empty(); - expand_cfg_attr(type_or_const.attrs(), &mut cfg_options, |attr, _, _, _| { + expand_cfg_attr(type_or_const.attrs(), &mut cfg_options, |attr, _| { match_attr_flags(&mut attr_flags, attr) }); if !attr_flags.is_empty() { @@ -642,11 +664,10 @@ impl AttrFlags { let result = expand_cfg_attr( attrs, || cfg_options, - |attr, _, _, _| { - if let Meta::TokenTree { path, tt } = attr - && path.is1("cfg") - && let cfg = - CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(&tt).peekable()) + |attr, _| { + if let ast::Meta::CfgMeta(attr) = attr + && let Some(cfg_predicate) = attr.cfg_predicate() + && let cfg = CfgExpr::parse_from_ast(cfg_predicate) && cfg_options.check(&cfg) == Some(false) { ControlFlow::Break(cfg) @@ -678,10 +699,9 @@ impl AttrFlags { #[salsa::tracked] fn lang_item(db: &dyn DefDatabase, owner: AttrDefId) -> Option<Symbol> { collect_attrs(db, owner, |attr| { - if let Meta::NamedKeyValue { name: Some(name), value: Some(value), .. } = attr - && name.text() == "lang" - && let Some(value) = ast::String::cast(value) - && let Ok(value) = value.value() + if let ast::Meta::KeyValueMeta(attr) = attr + && attr.path().is1("lang") + && let Some(value) = attr.value_string() { ControlFlow::Break(Symbol::intern(&value)) } else { @@ -704,8 +724,9 @@ impl AttrFlags { fn repr(db: &dyn DefDatabase, owner: AdtId) -> Option<ReprOptions> { let mut result = None; collect_attrs::<Infallible>(db, owner.into(), |attr| { - if let Meta::TokenTree { path, tt } = attr - && path.is1("repr") + if let ast::Meta::TokenTreeMeta(attr) = attr + && attr.path().is1("repr") + && let Some(tt) = attr.token_tree() && let Some(repr) = parse_repr_tt(&tt) { match &mut result { @@ -726,8 +747,9 @@ impl AttrFlags { owner: FunctionId, ) -> Option<Box<[u32]>> { let result = collect_attrs(db, owner.into(), |attr| { - if let Meta::TokenTree { path, tt } = attr - && path.is1("rustc_legacy_const_generics") + if let ast::Meta::TokenTreeMeta(attr) = attr + && attr.path().is1("rustc_legacy_const_generics") + && let Some(tt) = attr.token_tree() { let result = parse_rustc_legacy_const_generics(tt); ControlFlow::Break(result) @@ -750,9 +772,10 @@ impl AttrFlags { expand_cfg_attr( extra_crate_attrs.chain(syntax.attrs()), || cfg_options.get_or_insert(krate.cfg_options(db)), - |attr, _, _, _| { - if let Meta::TokenTree { path, tt } = attr - && path.is1("doc") + |attr, _| { + if let ast::Meta::TokenTreeMeta(attr) = attr + && attr.path().is1("doc") + && let Some(tt) = attr.token_tree() && let Some(result) = DocAtom::parse(tt).into_iter().find_map(|atom| { if let DocAtom::KeyValue { key, value } = atom && key == "html_root_url" @@ -783,8 +806,9 @@ impl AttrFlags { fn target_features(db: &dyn DefDatabase, owner: FunctionId) -> FxHashSet<Symbol> { let mut result = FxHashSet::default(); collect_attrs::<Infallible>(db, owner.into(), |attr| { - if let Meta::TokenTree { path, tt } = attr - && path.is1("target_feature") + if let ast::Meta::TokenTreeMeta(attr) = attr + && attr.path().is1("target_feature") + && let Some(tt) = attr.token_tree() { let mut tt = TokenTreeChildren::new(&tt); while let Some(NodeOrToken::Token(enable_ident)) = tt.next() @@ -831,9 +855,11 @@ impl AttrFlags { ) -> RustcLayoutScalarValidRange { let mut result = RustcLayoutScalarValidRange::default(); collect_attrs::<Infallible>(db, owner.into(), |attr| { - if let Meta::TokenTree { path, tt } = attr + if let ast::Meta::TokenTreeMeta(attr) = attr + && let path = attr.path() && (path.is1("rustc_layout_scalar_valid_range_start") || path.is1("rustc_layout_scalar_valid_range_end")) + && let Some(tt) = attr.token_tree() && let tt = TokenTreeChildren::new(&tt) && let Ok(NodeOrToken::Token(value)) = Itertools::exactly_one(tt) && let Some(value) = ast::IntNumber::cast(value) @@ -881,7 +907,7 @@ impl AttrFlags { expand_cfg_attr( field.value.attrs(), || cfg_options, - |attr, _, _, _| extract_doc_aliases(&mut result, attr), + |attr, _| extract_doc_aliases(&mut result, attr), ); result.into_boxed_slice() }) @@ -923,7 +949,7 @@ impl AttrFlags { expand_cfg_attr( field.value.attrs(), || cfg_options, - |attr, _, _, _| extract_cfgs(&mut result, attr), + |attr, _| extract_cfgs(&mut result, attr), ); match result.len() { 0 => None, @@ -944,8 +970,9 @@ impl AttrFlags { #[salsa::tracked] fn doc_keyword(db: &dyn DefDatabase, owner: ModuleId) -> Option<Symbol> { collect_attrs(db, AttrDefId::ModuleId(owner), |attr| { - if let Meta::TokenTree { path, tt } = attr - && path.is1("doc") + if let ast::Meta::TokenTreeMeta(attr) = attr + && attr.path().is1("doc") + && let Some(tt) = attr.token_tree() { for atom in DocAtom::parse(tt) { if let DocAtom::KeyValue { key, value } = atom @@ -1015,12 +1042,10 @@ impl AttrFlags { #[salsa::tracked(returns(ref))] fn derive_info(db: &dyn DefDatabase, owner: MacroId) -> Option<DeriveInfo> { collect_attrs(db, owner.into(), |attr| { - if let Meta::TokenTree { path, tt } = attr - && path.segments.len() == 1 - && matches!( - path.segments[0].text(), - "proc_macro_derive" | "rustc_builtin_macro" - ) + if let ast::Meta::TokenTreeMeta(attr) = attr + && (attr.path().is1("proc_macro_derive") + || attr.path().is1("rustc_builtin_macro")) + && let Some(tt) = attr.token_tree() && let mut tt = TokenTreeChildren::new(&tt) && let Some(NodeOrToken::Token(trait_name)) = tt.next() && trait_name.kind().is_any_identifier() diff --git a/crates/hir-def/src/attrs/docs.rs b/crates/hir-def/src/attrs/docs.rs index 8c14808c71..9a715b1968 100644 --- a/crates/hir-def/src/attrs/docs.rs +++ b/crates/hir-def/src/attrs/docs.rs @@ -16,7 +16,7 @@ use cfg::CfgOptions; use either::Either; use hir_expand::{ AstId, ExpandTo, HirFileId, InFile, - attrs::{Meta, expand_cfg_attr_with_doc_comments}, + attrs::{AstPathExt, expand_cfg_attr_with_doc_comments}, mod_path::ModPath, span_map::SpanMap, }; @@ -182,8 +182,7 @@ impl Docs { self.extend_with_doc_str(doc, comment.syntax().text_range().start() + offset, indent); } - fn extend_with_doc_attr(&mut self, value: syntax::SyntaxToken, indent: &mut usize) { - let Some(value) = ast::String::cast(value) else { return }; + fn extend_with_doc_attr(&mut self, value: ast::String, indent: &mut usize) { let Some(value_offset) = value.text_range_between_quotes() else { return }; let value_offset = value_offset.start(); let Ok(value) = value.value() else { return }; @@ -423,10 +422,6 @@ fn extend_with_attrs<'a, 'db>( // Lazily initialised when we first encounter a `#[doc = macro!()]`. let mut expander: Option<(DocMacroExpander<'db>, DocExprSourceCtx<'db>)> = None; - // FIXME: `#[cfg_attr(..., doc = macro!())]` skips macro expansion because - // `top_attr` points to the `cfg_attr` node, not the inner `doc = macro!()`. - // Fixing this is difficult as we need an `Expr` that doesn't exist here for - // the ast id and for sanely parsing the macro call. expand_cfg_attr_with_doc_comments::<_, Infallible>( AttrDocCommentIter::from_syntax_node(node).filter(|attr| match attr { Either::Left(attr) => attr.kind().is_inner() == expect_inner_attrs, @@ -439,46 +434,38 @@ fn extend_with_attrs<'a, 'db>( |attr| { match attr { Either::Right(doc_comment) => result.extend_with_doc_comment(doc_comment, indent), - Either::Left((attr, _, _, top_attr)) => match attr { - Meta::NamedKeyValue { name: Some(name), value: Some(value), .. } - if name.text() == "doc" => - { - result.extend_with_doc_attr(value, indent); - } - Meta::NamedKeyValue { name: Some(name), value: None, .. } - if name.text() == "doc" => - { - // When the doc attribute comes from inside a `cfg_attr`, - // `top_attr` points to the `cfg_attr(...)` node, not the - // inner `doc = macro!()`. In that case `top_attr.expr()` - // would not yield the macro expression we need, so skip - // expansion (see FIXME above). - let is_from_cfg_attr = - top_attr.as_simple_call().is_some_and(|(name, _)| name == "cfg_attr"); - if !is_from_cfg_attr && let Some(expr) = top_attr.expr() { - let (exp, ctx) = expander.get_or_insert_with(|| { - let resolver = make_resolver(); - let def_map = resolver.top_level_def_map(); - let recursion_limit = def_map.recursion_limit() as usize; - ( - DocMacroExpander { - db, - krate, - recursion_depth: 0, - recursion_limit, - }, - DocExprSourceCtx { - resolver, - file_id, - ast_id_map: db.ast_id_map(file_id), - span_map: db.span_map(file_id), - }, - ) - }); - if let Some(expanded) = - expand_doc_expr_via_macro_pipeline(exp, ctx, expr) + Either::Left((attr, _)) => match attr { + ast::Meta::KeyValueMeta(attr) if attr.path().is1("doc") => { + if let Some(value) = attr.expr() { + if let ast::Expr::Literal(value) = &value + && let ast::LiteralKind::String(value) = value.kind() { - result.extend_with_unmapped_doc_str(&expanded, indent); + result.extend_with_doc_attr(value, indent); + } else { + let (exp, ctx) = expander.get_or_insert_with(|| { + let resolver = make_resolver(); + let def_map = resolver.top_level_def_map(); + let recursion_limit = def_map.recursion_limit() as usize; + ( + DocMacroExpander { + db, + krate, + recursion_depth: 0, + recursion_limit, + }, + DocExprSourceCtx { + resolver, + file_id, + ast_id_map: db.ast_id_map(file_id), + span_map: db.span_map(file_id), + }, + ) + }); + if let Some(expanded) = + expand_doc_expr_via_macro_pipeline(exp, ctx, value) + { + result.extend_with_unmapped_doc_str(&expanded, indent); + } } } } diff --git a/crates/hir-def/src/dyn_map.rs b/crates/hir-def/src/dyn_map.rs index 4308d0ef1c..c38ceccd1f 100644 --- a/crates/hir-def/src/dyn_map.rs +++ b/crates/hir-def/src/dyn_map.rs @@ -68,7 +68,7 @@ pub mod keys { pub const MACRO_CALL: Key<ast::MacroCall, MacroCallId> = Key::new(); pub const ATTR_MACRO_CALL: Key<ast::Item, MacroCallId> = Key::new(); pub const DERIVE_MACRO_CALL: Key< - ast::Attr, + ast::Meta, ( AttrId, /* derive() */ MacroCallId, diff --git a/crates/hir-def/src/item_tree/attrs.rs b/crates/hir-def/src/item_tree/attrs.rs index 7907611284..867d813e3f 100644 --- a/crates/hir-def/src/item_tree/attrs.rs +++ b/crates/hir-def/src/item_tree/attrs.rs @@ -13,12 +13,12 @@ use std::{ use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ - attrs::{Attr, AttrId, AttrInput, Meta, collect_item_tree_attrs}, + attrs::{Attr, AttrId, AttrInput, collect_item_tree_attrs}, mod_path::ModPath, name::Name, }; use intern::{Interned, Symbol, sym}; -use syntax::{AstNode, T, ast}; +use syntax::{AstNode, ast}; use syntax_bridge::DocCommentDesugarMode; use tt::token_to_literal; @@ -51,58 +51,62 @@ impl AttrsOrCfg { S: syntax_bridge::SpanMapper + Copy, { let mut attrs = Vec::new(); - let result = - collect_item_tree_attrs::<Infallible>(owner, cfg_options, |meta, container, _, _| { - // NOTE: We cannot early return from this function, *every* attribute must be pushed, otherwise we'll mess the `AttrId` - // tracking. - let (span, path_range, input) = match meta { - Meta::NamedKeyValue { path_range, name: _, value } => { - let span = span_map.span_for(path_range); - let input = value.map(|value| { - Box::new(AttrInput::Literal(token_to_literal( - value.text(), - span_map.span_for(value.text_range()), - ))) - }); - (span, path_range, input) - } - Meta::TokenTree { path, tt } => { - let span = span_map.span_for(path.range); - let tt = syntax_bridge::syntax_node_to_token_tree( - tt.syntax(), - span_map, - span, - DocCommentDesugarMode::ProcMacro, - ); - let input = Some(Box::new(AttrInput::TokenTree(tt))); - (span, path.range, input) - } - Meta::Path { path } => { - let span = span_map.span_for(path.range); - (span, path.range, None) - } - }; + let result = collect_item_tree_attrs::<Infallible>(owner, cfg_options, |meta, _| { + // NOTE: We cannot early return from this function, *every* attribute must be pushed, otherwise we'll mess the `AttrId` + // tracking. + let path = meta.path(); + let path_range = path + .as_ref() + .map(|path| path.syntax().text_range()) + .unwrap_or_else(|| meta.syntax().text_range()); + let (span, input) = match &meta { + ast::Meta::KeyValueMeta(meta) => { + let span = span_map.span_for(path_range); + let input = meta.expr().and_then(|value| { + if let ast::Expr::Literal(value) = value { + Some(Box::new(AttrInput::Literal(token_to_literal( + value.token().text(), + span_map.span_for(value.syntax().text_range()), + )))) + } else { + None + } + }); + (span, input) + } + ast::Meta::TokenTreeMeta(meta) => { + let span = span_map.span_for(path_range); + let tt = syntax_bridge::syntax_node_to_token_tree( + &meta + .token_tree() + .map(|it| it.syntax().clone()) + .unwrap_or_else(|| meta.syntax().clone()), + span_map, + span, + DocCommentDesugarMode::ProcMacro, + ); + let input = Some(Box::new(AttrInput::TokenTree(tt))); + (span, input) + } + ast::Meta::PathMeta(_) => { + let span = span_map.span_for(path_range); + (span, None) + } + ast::Meta::CfgMeta(_) | ast::Meta::CfgAttrMeta(_) | ast::Meta::UnsafeMeta(_) => { + unreachable!( + "`cfg`, `cfg_attr` and `unsafe(...)` are handled in `collect_item_tree_attrs()`" + ) + } + }; - let path = container.token_at_offset(path_range.start()).right_biased().and_then( - |first_path_token| { - let is_abs = matches!(first_path_token.kind(), T![:] | T![::]); - let segments = - std::iter::successors(Some(first_path_token), |it| it.next_token()) - .take_while(|it| it.text_range().end() <= path_range.end()) - .filter(|it| it.kind().is_any_identifier()); - ModPath::from_tokens( - db, - &mut |range| span_map.span_for(range).ctx, - is_abs, - segments, - ) - }, - ); - let path = path.unwrap_or_else(|| Name::missing().into()); - - attrs.push(Attr { path: Interned::new(path), input, ctxt: span.ctx }); - ControlFlow::Continue(()) + let path = path.and_then(|path| { + ModPath::from_src(db, path, &mut |range| span_map.span_for(range).ctx) }); + let path = path.unwrap_or_else(|| Name::missing().into()); + + attrs.push(Attr { path: Interned::new(path), input, ctxt: span.ctx }); + ControlFlow::Continue(()) + }); let attrs = AttrsOwned(attrs.into_boxed_slice()); match result { Some(Either::Right(cfg)) => AttrsOrCfg::CfgDisabled(Box::new((cfg, attrs))), diff --git a/crates/hir-def/src/macro_expansion_tests/mbe.rs b/crates/hir-def/src/macro_expansion_tests/mbe.rs index 7b5d0103e6..e75c96b630 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -1198,7 +1198,7 @@ m! { hello::world } macro_rules! m { ($m:meta) => ( #[$m] fn bar() {} ) } -#[cfg(target_os = "windows")] fn bar() {} +#[cfg (target_os = "windows")] fn bar() {} #[hello::world] fn bar() {} "#]], ); diff --git a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs index ddabb50251..cac248f47f 100644 --- a/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs +++ b/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs @@ -205,7 +205,7 @@ impl Clone for D3DVSHADERCAPS2_0 { *self } } -#[cfg(feature = "impl-default")] impl Default for D3DVSHADERCAPS2_0 { +#[cfg (feature = "impl-default")] impl Default for D3DVSHADERCAPS2_0 { #[inline] fn default() -> D3DVSHADERCAPS2_0 { unsafe { $crate::_core::mem::zeroed() @@ -215,7 +215,7 @@ impl Clone for D3DVSHADERCAPS2_0 { #[repr(C)] #[derive(Copy)] -#[cfg_attr(target_arch = "x86", repr(packed))] pub struct D3DCONTENTPROTECTIONCAPS { +#[cfg_attr (target_arch = "x86", repr(packed))] pub struct D3DCONTENTPROTECTIONCAPS { pub Caps: u8, } impl Clone for D3DCONTENTPROTECTIONCAPS { @@ -223,7 +223,7 @@ impl Clone for D3DCONTENTPROTECTIONCAPS { *self } } -#[cfg(feature = "impl-default")] impl Default for D3DCONTENTPROTECTIONCAPS { +#[cfg (feature = "impl-default")] impl Default for D3DCONTENTPROTECTIONCAPS { #[inline] fn default() -> D3DCONTENTPROTECTIONCAPS { unsafe { $crate::_core::mem::zeroed() @@ -1001,8 +1001,8 @@ macro_rules! with_std { ($($i:item)*) => ($(#[cfg(feature = "std")]$i)*) } -#[cfg(feature = "std")] mod m; -#[cfg(feature = "std")] mod f; +#[cfg (feature = "std")] mod m; +#[cfg (feature = "std")] mod f; "#]], ) } diff --git a/crates/hir-def/src/macro_expansion_tests/proc_macros.rs b/crates/hir-def/src/macro_expansion_tests/proc_macros.rs index bf04a500a5..8c91cf6793 100644 --- a/crates/hir-def/src/macro_expansion_tests/proc_macros.rs +++ b/crates/hir-def/src/macro_expansion_tests/proc_macros.rs @@ -55,8 +55,8 @@ mod foo { # ![doc = "123..."] # ![attr2] # ![attr3] - #[cfg_attr(true , cfg(false ))] fn foo() {} - #[cfg(true )] fn bar() {} + #[cfg_attr (true , cfg (false ))] fn foo() {} + #[cfg (true )] fn bar() {} }"##]], ); } diff --git a/crates/hir-expand/Cargo.toml b/crates/hir-expand/Cargo.toml index 4fa476afb6..43b0bea891 100644 --- a/crates/hir-expand/Cargo.toml +++ b/crates/hir-expand/Cargo.toml @@ -23,7 +23,6 @@ triomphe.workspace = true query-group.workspace = true salsa.workspace = true salsa-macros.workspace = true -arrayvec.workspace = true thin-vec.workspace = true # local deps diff --git a/crates/hir-expand/src/attrs.rs b/crates/hir-expand/src/attrs.rs index e3f10b2129..49baecb90c 100644 --- a/crates/hir-expand/src/attrs.rs +++ b/crates/hir-expand/src/attrs.rs @@ -4,20 +4,8 @@ //! [`expand_cfg_attr_with_doc_comments()`]. It is used to implement all attribute lowering //! in r-a. Its basic job is to list attributes; however, attributes do not necessarily map //! into [`ast::Attr`], because `cfg_attr` can map to zero, one, or more attributes -//! (`#[cfg_attr(predicate, attr1, attr2, ...)]`). To bridge this gap, this module defines -//! [`Meta`], which represents a desugared attribute. Various bits of r-a need different -//! things from [`Meta`], therefore it contains many parts. The basic idea is: -//! -//! - There are three kinds of attributes, `path = value`, `path`, and `path(token_tree)`. -//! - Most bits of rust-analyzer only need to deal with some paths. Therefore, we keep -//! the path only if it has up to 2 segments, or one segment for `path = value`. -//! We also only keep the value in `path = value` if it is a literal. However, we always -//! save the all relevant ranges of attributes (the path range, and the full attribute range) -//! for parts of r-a (e.g. name resolution) that need a faithful representation of the -//! attribute. -//! -//! [`expand_cfg_attr()`] expands `cfg_attr`s as it goes (as its name implies), to list -//! all attributes. +//! (`#[cfg_attr(predicate, attr1, attr2, ...)]`). [`expand_cfg_attr()`] expands `cfg_attr`s +//! as it goes (as its name implies), to list all attributes. //! //! Another thing to note is that we need to be able to map an attribute back to a range //! (for diagnostic purposes etc.). This is only ever needed for attributes that participate @@ -26,26 +14,18 @@ //! place (here) and one function ([`is_item_tree_filtered_attr()`]) that decides whether //! an attribute participate in name resolution. -use std::{ - borrow::Cow, cell::OnceCell, convert::Infallible, fmt, iter::Peekable, ops::ControlFlow, -}; +use std::{borrow::Cow, cell::OnceCell, convert::Infallible, fmt, ops::ControlFlow}; -use ::tt::{TextRange, TextSize}; -use arrayvec::ArrayVec; +use ::tt::TextRange; use base_db::Crate; use cfg::{CfgExpr, CfgOptions}; use either::Either; use intern::Interned; use itertools::Itertools; use mbe::{DelimiterKind, Punct}; -use parser::T; use smallvec::SmallVec; use span::{RealSpanMap, Span, SyntaxContext}; -use syntax::{ - AstNode, NodeOrToken, SyntaxNode, SyntaxToken, - ast::{self, TokenTreeChildren}, - unescape, -}; +use syntax::{AstNode, SmolStr, ast, unescape}; use syntax_bridge::DocCommentDesugarMode; use crate::{ @@ -56,207 +36,75 @@ use crate::{ tt::{self, TopSubtree}, }; -#[derive(Debug)] -pub struct AttrPath { - /// This can be empty if the path is not of 1 or 2 segments exactly. - pub segments: ArrayVec<SyntaxToken, 2>, - pub range: TextRange, - // FIXME: This shouldn't be textual, `#[test]` needs name resolution. - // And if textual, it shouldn't be here, it should be in hir-def/src/attrs.rs. But some macros - // fully qualify `test` as `core::prelude::vX::test`, and this is more than 2 segments, so hir-def - // attrs can't find it. But this will mean we have to push every up-to-4-segments path, which - // may impact perf. So it was easier to just hack it here. - pub is_test: bool, +pub trait AstPathExt { + fn is1(&self, segment: &str) -> bool; + + fn as_one_segment(&self) -> Option<SmolStr>; + + fn as_up_to_two_segment(&self) -> Option<(SmolStr, Option<SmolStr>)>; } -impl AttrPath { - #[inline] - fn extract(path: &ast::Path) -> Self { - let mut is_test = false; - let segments = (|| { - let mut segments = ArrayVec::new(); - let segment2 = path.segment()?.name_ref()?.syntax().first_token()?; - if segment2.text() == "test" { - // `#[test]` or `#[core::prelude::vX::test]`. - is_test = true; - } - let segment1 = path.qualifier(); - if let Some(segment1) = segment1 { - if segment1.qualifier().is_some() { - None - } else { - let segment1 = segment1.segment()?.name_ref()?.syntax().first_token()?; - segments.push(segment1); - segments.push(segment2); - Some(segments) - } - } else { - segments.push(segment2); - Some(segments) - } - })(); - AttrPath { - segments: segments.unwrap_or(ArrayVec::new()), - range: path.syntax().text_range(), - is_test, - } +impl AstPathExt for ast::Path { + fn is1(&self, segment: &str) -> bool { + self.as_one_segment().is_some_and(|it| it == segment) } - #[inline] - pub fn is1(&self, segment: &str) -> bool { - self.segments.len() == 1 && self.segments[0].text() == segment + fn as_one_segment(&self) -> Option<SmolStr> { + Some(self.as_single_name_ref()?.text().into()) } -} -#[derive(Debug)] -pub enum Meta { - /// `name` is `None` if not a single token. `value` is a literal or `None`. - NamedKeyValue { - path_range: TextRange, - name: Option<SyntaxToken>, - value: Option<SyntaxToken>, - }, - TokenTree { - path: AttrPath, - tt: ast::TokenTree, - }, - Path { - path: AttrPath, - }, + fn as_up_to_two_segment(&self) -> Option<(SmolStr, Option<SmolStr>)> { + let parent = self.qualifier().as_one_segment(); + let this = self.segment()?.name_ref()?.text().into(); + if let Some(parent) = parent { Some((parent, Some(this))) } else { Some((this, None)) } + } } -impl Meta { - #[inline] - pub fn path_range(&self) -> TextRange { - match self { - Meta::NamedKeyValue { path_range, .. } => *path_range, - Meta::TokenTree { path, .. } | Meta::Path { path } => path.range, - } +impl AstPathExt for Option<ast::Path> { + fn is1(&self, segment: &str) -> bool { + self.as_ref().is_some_and(|it| it.is1(segment)) } - fn extract(iter: &mut Peekable<TokenTreeChildren>) -> Option<(Self, TextSize)> { - let mut start_offset = None; - if let Some(NodeOrToken::Token(colon1)) = iter.peek() - && colon1.kind() == T![:] - { - start_offset = Some(colon1.text_range().start()); - iter.next(); - iter.next_if(|it| it.as_token().is_some_and(|it| it.kind() == T![:])); - } - let first_segment = iter - .next_if(|it| it.as_token().is_some_and(|it| it.kind().is_any_identifier()))? - .into_token()?; - let mut is_test = first_segment.text() == "test"; - let start_offset = start_offset.unwrap_or_else(|| first_segment.text_range().start()); - - let mut segments_len = 1; - let mut second_segment = None; - let mut path_range = first_segment.text_range(); - while iter.peek().and_then(NodeOrToken::as_token).is_some_and(|it| it.kind() == T![:]) - && let _ = iter.next() - && iter.peek().and_then(NodeOrToken::as_token).is_some_and(|it| it.kind() == T![:]) - && let _ = iter.next() - && let Some(NodeOrToken::Token(segment)) = iter.peek() - && segment.kind().is_any_identifier() - { - segments_len += 1; - is_test = segment.text() == "test"; - second_segment = Some(segment.clone()); - path_range = TextRange::new(path_range.start(), segment.text_range().end()); - iter.next(); - } + fn as_one_segment(&self) -> Option<SmolStr> { + self.as_ref().and_then(|it| it.as_one_segment()) + } - let segments = |first, second| { - let mut segments = ArrayVec::new(); - if segments_len <= 2 { - segments.push(first); - if let Some(second) = second { - segments.push(second); - } - } - segments - }; - let meta = match iter.peek() { - Some(NodeOrToken::Token(eq)) if eq.kind() == T![=] => { - iter.next(); - let value = match iter.peek() { - Some(NodeOrToken::Token(token)) if token.kind().is_literal() => { - // No need to consume it, it will be consumed by `extract_and_eat_comma()`. - Some(token.clone()) - } - _ => None, - }; - let name = if second_segment.is_none() { Some(first_segment) } else { None }; - Meta::NamedKeyValue { path_range, name, value } - } - Some(NodeOrToken::Node(tt)) => Meta::TokenTree { - path: AttrPath { - segments: segments(first_segment, second_segment), - range: path_range, - is_test, - }, - tt: tt.clone(), - }, - _ => Meta::Path { - path: AttrPath { - segments: segments(first_segment, second_segment), - range: path_range, - is_test, - }, - }, - }; - Some((meta, start_offset)) + fn as_up_to_two_segment(&self) -> Option<(SmolStr, Option<SmolStr>)> { + self.as_ref().and_then(|it| it.as_up_to_two_segment()) } +} + +pub trait AstKeyValueMetaExt { + fn value_string(&self) -> Option<SmolStr>; +} - fn extract_possibly_unsafe( - iter: &mut Peekable<TokenTreeChildren>, - container: &ast::TokenTree, - ) -> Option<(Self, TextRange)> { - if iter.peek().is_some_and(|it| it.as_token().is_some_and(|it| it.kind() == T![unsafe])) { - iter.next(); - let tt = iter.next()?.into_node()?; - let result = Self::extract(&mut TokenTreeChildren::new(&tt).peekable()).map( - |(meta, start_offset)| (meta, TextRange::new(start_offset, tt_end_offset(&tt))), - ); - while iter.next().is_some_and(|it| it.as_token().is_none_or(|it| it.kind() != T![,])) {} - result +impl AstKeyValueMetaExt for ast::KeyValueMeta { + fn value_string(&self) -> Option<SmolStr> { + if let Some(ast::Expr::Literal(value)) = self.expr() + && let ast::LiteralKind::String(value) = value.kind() + && let Ok(value) = value.value() + { + Some((*value).into()) } else { - Self::extract(iter).map(|(meta, start_offset)| { - let end_offset = 'find_end_offset: { - for it in iter { - if let NodeOrToken::Token(it) = it - && it.kind() == T![,] - { - break 'find_end_offset it.text_range().start(); - } - } - tt_end_offset(container) - }; - (meta, TextRange::new(start_offset, end_offset)) - }) + None } } } -fn tt_end_offset(tt: &ast::TokenTree) -> TextSize { - tt.syntax().last_token().unwrap().text_range().start() -} - -/// The callback is passed a desugared form of the attribute ([`Meta`]), a [`SyntaxNode`] fully containing it -/// (note: it may not be the direct parent), the range within the [`SyntaxNode`] bounding the attribute, -/// and the outermost `ast::Attr`. Note that one node may map to multiple [`Meta`]s due to `cfg_attr`. +/// The callback is passed the attribute and the outermost `ast::Attr`. +/// Note that one node may map to multiple [`Meta`]s due to `cfg_attr`. +/// +/// `unsafe(attr)` are passed the inner attribute for now. #[inline] pub fn expand_cfg_attr<'a, BreakValue>( attrs: impl Iterator<Item = ast::Attr>, cfg_options: impl FnMut() -> &'a CfgOptions, - mut callback: impl FnMut(Meta, &SyntaxNode, TextRange, &ast::Attr) -> ControlFlow<BreakValue>, + mut callback: impl FnMut(ast::Meta, ast::Attr) -> ControlFlow<BreakValue>, ) -> Option<BreakValue> { expand_cfg_attr_with_doc_comments::<Infallible, _>( attrs.map(Either::Left), cfg_options, - move |Either::Left((meta, container, range, top_attr))| { - callback(meta, container, range, top_attr) - }, + move |Either::Left((meta, top_attr))| callback(meta, top_attr), ) } @@ -264,66 +112,47 @@ pub fn expand_cfg_attr<'a, BreakValue>( pub fn expand_cfg_attr_with_doc_comments<'a, DocComment, BreakValue>( mut attrs: impl Iterator<Item = Either<ast::Attr, DocComment>>, mut cfg_options: impl FnMut() -> &'a CfgOptions, - mut callback: impl FnMut( - Either<(Meta, &SyntaxNode, TextRange, &ast::Attr), DocComment>, - ) -> ControlFlow<BreakValue>, + mut callback: impl FnMut(Either<(ast::Meta, ast::Attr), DocComment>) -> ControlFlow<BreakValue>, ) -> Option<BreakValue> { let mut stack = SmallVec::<[_; 1]>::new(); - let result = attrs.try_for_each(|top_attr| { - let top_attr = match top_attr { - Either::Left(it) => it, - Either::Right(comment) => return callback(Either::Right(comment)), - }; - if let Some((attr_name, tt)) = top_attr.as_simple_call() - && attr_name == "cfg_attr" - { - let mut tt_iter = TokenTreeChildren::new(&tt).peekable(); - let cfg = cfg::CfgExpr::parse_from_ast(&mut tt_iter); - if cfg_options().check(&cfg) != Some(false) { - stack.push((tt_iter, tt)); - while let Some((tt_iter, tt)) = stack.last_mut() { - let Some((attr, range)) = Meta::extract_possibly_unsafe(tt_iter, tt) else { - stack.pop(); - continue; - }; - if let Meta::TokenTree { path, tt: nested_tt } = &attr - && path.is1("cfg_attr") - { - let mut nested_tt_iter = TokenTreeChildren::new(nested_tt).peekable(); - let cfg = cfg::CfgExpr::parse_from_ast(&mut nested_tt_iter); - if cfg_options().check(&cfg) != Some(false) { - stack.push((nested_tt_iter, nested_tt.clone())); - } - } else { - callback(Either::Left((attr, tt.syntax(), range, &top_attr)))?; + loop { + let (mut meta, top_attr) = if let Some(it) = stack.pop() { + it + } else { + let attr = attrs.next()?; + match attr { + Either::Left(attr) => { + let Some(meta) = attr.meta() else { continue }; + stack.push((meta, attr)); + } + Either::Right(doc_comment) => { + if let ControlFlow::Break(break_value) = callback(Either::Right(doc_comment)) { + return Some(break_value); } } } - } else if let Some(ast_meta) = top_attr.meta() - && let Some(path) = ast_meta.path() - { - let path = AttrPath::extract(&path); - let meta = if let Some(tt) = ast_meta.token_tree() { - Meta::TokenTree { path, tt } - } else if let Some(value) = ast_meta.expr() { - let value = - if let ast::Expr::Literal(value) = value { Some(value.token()) } else { None }; - let name = - if path.segments.len() == 1 { Some(path.segments[0].clone()) } else { None }; - Meta::NamedKeyValue { name, value, path_range: path.range } - } else { - Meta::Path { path } - }; - callback(Either::Left(( - meta, - ast_meta.syntax(), - ast_meta.syntax().text_range(), - &top_attr, - )))?; + continue; + }; + + while let ast::Meta::UnsafeMeta(unsafe_meta) = &meta { + let Some(inner) = unsafe_meta.meta() else { continue }; + meta = inner; } - ControlFlow::Continue(()) - }); - result.break_value() + + if let ast::Meta::CfgAttrMeta(meta) = meta { + let Some(cfg_predicate) = meta.cfg_predicate() else { continue }; + let cfg_predicate = CfgExpr::parse_from_ast(cfg_predicate); + if cfg_options().check(&cfg_predicate) != Some(false) { + let prev_stack_len = stack.len(); + stack.extend(meta.metas().map(|meta| (meta, top_attr.clone()))); + stack[prev_stack_len..].reverse(); + } + } else { + if let ControlFlow::Break(break_value) = callback(Either::Left((meta, top_attr))) { + return Some(break_value); + } + } + } } #[inline] @@ -351,39 +180,33 @@ pub(crate) fn is_item_tree_filtered_attr(name: &str) -> bool { pub fn collect_item_tree_attrs<'a, BreakValue>( owner: &dyn ast::HasAttrs, cfg_options: impl Fn() -> &'a CfgOptions, - mut on_attr: impl FnMut(Meta, &SyntaxNode, &ast::Attr, TextRange) -> ControlFlow<BreakValue>, + mut on_attr: impl FnMut(ast::Meta, ast::Attr) -> ControlFlow<BreakValue>, ) -> Option<Either<BreakValue, CfgExpr>> { let attrs = ast::attrs_including_inner(owner); expand_cfg_attr( attrs, || cfg_options(), - |attr, container, range, top_attr| { + |attr, top_attr| { // We filter builtin attributes that we don't need for nameres, because this saves memory. // I only put the most common attributes, but if some attribute becomes common feel free to add it. // Notice, however: for an attribute to be filtered out, it *must* not be shadowable with a macro! let filter = match &attr { - Meta::NamedKeyValue { name: Some(name), .. } => { - is_item_tree_filtered_attr(name.text()) - } - Meta::TokenTree { path, tt } if path.segments.len() == 1 => { - let name = path.segments[0].text(); - if name == "cfg" { - let cfg = - CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(tt).peekable()); - if cfg_options().check(&cfg) == Some(false) { - return ControlFlow::Break(Either::Right(cfg)); - } - true - } else { - is_item_tree_filtered_attr(name) + ast::Meta::CfgMeta(attr) => { + let Some(cfg_predicate) = attr.cfg_predicate() else { + return ControlFlow::Continue(()); + }; + let cfg = CfgExpr::parse_from_ast(cfg_predicate); + if cfg_options().check(&cfg) == Some(false) { + return ControlFlow::Break(Either::Right(cfg)); } + true } - Meta::Path { path } => { - path.segments.len() == 1 && is_item_tree_filtered_attr(path.segments[0].text()) - } - _ => false, + _ => attr + .path() + .and_then(|path| path.as_one_segment()) + .is_some_and(|segment| is_item_tree_filtered_attr(&segment)), }; - if !filter && let ControlFlow::Break(v) = on_attr(attr, container, top_attr, range) { + if !filter && let ControlFlow::Break(v) = on_attr(attr, top_attr) { return ControlFlow::Break(Either::Left(v)); } ControlFlow::Continue(()) @@ -540,34 +363,32 @@ impl AttrId { } /// Returns the containing `ast::Attr` (note that it may contain other attributes as well due - /// to `cfg_attr`), a `SyntaxNode` guaranteed to contain the attribute, the full range of the - /// attribute, and its desugared [`Meta`]. + /// to `cfg_attr`) and its [`ast::Meta`]. pub fn find_attr_range<N: ast::HasAttrs>( self, db: &dyn ExpandDatabase, krate: Crate, owner: AstId<N>, - ) -> (ast::Attr, SyntaxNode, TextRange, Meta) { + ) -> (ast::Attr, ast::Meta) { self.find_attr_range_with_source(db, krate, &owner.to_node(db)) } /// Returns the containing `ast::Attr` (note that it may contain other attributes as well due - /// to `cfg_attr`), a `SyntaxNode` guaranteed to contain the attribute, the full range of the - /// attribute, and its desugared [`Meta`]. + /// to `cfg_attr`) and its [`ast::Meta`]. pub fn find_attr_range_with_source( self, db: &dyn ExpandDatabase, krate: Crate, owner: &dyn ast::HasAttrs, - ) -> (ast::Attr, SyntaxNode, TextRange, Meta) { + ) -> (ast::Attr, ast::Meta) { let cfg_options = OnceCell::new(); let mut index = 0; let result = collect_item_tree_attrs( owner, || cfg_options.get_or_init(|| krate.cfg_options(db)), - |meta, container, top_attr, range| { + |meta, top_attr| { if index == self.id { - return ControlFlow::Break((top_attr.clone(), container.clone(), range, meta)); + return ControlFlow::Break((top_attr, meta)); } index += 1; ControlFlow::Continue(()) @@ -588,9 +409,12 @@ impl AttrId { owner: AstId<ast::Adt>, derive_index: u32, ) -> TextRange { - let (_, _, derive_attr_range, derive_attr) = self.find_attr_range(db, krate, owner); - let Meta::TokenTree { tt, .. } = derive_attr else { - return derive_attr_range; + let (_, derive_attr) = self.find_attr_range(db, krate, owner); + let ast::Meta::TokenTreeMeta(derive_attr) = derive_attr else { + return derive_attr.syntax().text_range(); + }; + let Some(tt) = derive_attr.token_tree() else { + return derive_attr.syntax().text_range(); }; // Fake the span map, as we don't really need spans here, just the offsets of the node in the file. let span_map = RealSpanMap::absolute(span::EditionedFileId::current_edition( @@ -605,11 +429,11 @@ impl AttrId { let Some((_, _, derive_tts)) = parse_path_comma_token_tree(db, &tt).nth(derive_index as usize) else { - return derive_attr_range; + return derive_attr.syntax().text_range(); }; let (Some(first_span), Some(last_span)) = (derive_tts.first_span(), derive_tts.last_span()) else { - return derive_attr_range; + return derive_attr.syntax().text_range(); }; let start = first_span.range.start(); let end = last_span.range.end(); diff --git a/crates/hir-expand/src/cfg_process.rs b/crates/hir-expand/src/cfg_process.rs index ccef9168ac..6258fac0e9 100644 --- a/crates/hir-expand/src/cfg_process.rs +++ b/crates/hir-expand/src/cfg_process.rs @@ -8,12 +8,12 @@ use parser::T; use smallvec::SmallVec; use syntax::{ AstNode, PreorderWithTokens, SyntaxElement, SyntaxNode, SyntaxToken, WalkEvent, - ast::{self, HasAttrs, TokenTreeChildren}, + ast::{self, HasAttrs}, }; use syntax_bridge::DocCommentDesugarMode; use crate::{ - attrs::{AttrId, Meta, expand_cfg_attr, is_item_tree_filtered_attr}, + attrs::{AstPathExt, AttrId, expand_cfg_attr, is_item_tree_filtered_attr}, db::ExpandDatabase, fixup::{self, SyntaxFixupUndoInfo}, span_map::SpanMapRef, @@ -24,7 +24,7 @@ struct ItemIsCfgedOut; #[derive(Debug)] struct ExpandedAttrToProcess { - range: TextRange, + attr: ast::Meta, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -143,42 +143,29 @@ fn macro_input_callback( }); attrs_idx = 0; - let strip_current_item = expand_cfg_attr( - node_attrs, - &cfg_options, - |attr, _container, range, top_attr| { + let strip_current_item = + expand_cfg_attr(node_attrs, &cfg_options, |attr, top_attr| { // Find the attr. while attrs[attrs_idx].range != top_attr.syntax().text_range() { attrs_idx += 1; } let mut strip_current_attr = false; - match attr { - Meta::NamedKeyValue { name, .. } => { - if name - .is_none_or(|name| !is_item_tree_filtered_attr(name.text())) - { - strip_current_attr = should_strip_attr(); - } - } - Meta::TokenTree { path, tt } => { - if path.is1("cfg") { - let cfg_expr = CfgExpr::parse_from_ast( - &mut TokenTreeChildren::new(&tt).peekable(), - ); + match &attr { + ast::Meta::CfgMeta(attr) => { + if let Some(cfg_predicate) = attr.cfg_predicate() { + let cfg_expr = CfgExpr::parse_from_ast(cfg_predicate); if cfg_options().check(&cfg_expr) == Some(false) { return ControlFlow::Break(ItemIsCfgedOut); } strip_current_attr = true; - } else if path.segments.len() != 1 - || !is_item_tree_filtered_attr(path.segments[0].text()) - { - strip_current_attr = should_strip_attr(); } } - Meta::Path { path } => { - if path.segments.len() != 1 - || !is_item_tree_filtered_attr(path.segments[0].text()) + _ => { + if attr + .path() + .as_one_segment() + .is_none_or(|name| !is_item_tree_filtered_attr(&name)) { strip_current_attr = should_strip_attr(); } @@ -188,12 +175,11 @@ fn macro_input_callback( if !strip_current_attr { attrs[attrs_idx] .expanded_attrs - .push(ExpandedAttrToProcess { range }); + .push(ExpandedAttrToProcess { attr }); } ControlFlow::Continue(()) - }, - ); + }); attrs_idx = 0; if strip_current_item.is_some() { @@ -248,7 +234,7 @@ fn macro_input_callback( }; match ast_attr.next_expanded_attr { NextExpandedAttrState::NotStarted => { - if token_range.start() >= expanded_attr.range.start() { + if token_range.start() >= expanded_attr.attr.syntax().text_range().start() { // We started the next attribute. let mut insert_tokens = Vec::with_capacity(3); insert_tokens.push(tt::Leaf::Punct(tt::Punct { @@ -278,7 +264,7 @@ fn macro_input_callback( } } NextExpandedAttrState::InTheMiddle => { - if token_range.start() >= expanded_attr.range.end() { + if token_range.start() >= expanded_attr.attr.syntax().text_range().end() { // Finished the current attribute. let insert_tokens = vec![tt::Leaf::Punct(tt::Punct { char: ']', @@ -329,12 +315,3 @@ pub(crate) fn attr_macro_input_to_token_tree( fixups.undo_info, ) } - -pub fn check_cfg_attr_value( - db: &dyn ExpandDatabase, - attr: &ast::TokenTree, - krate: Crate, -) -> Option<bool> { - let cfg_expr = CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(attr).peekable()); - krate.cfg_options(db).check(&cfg_expr) -} diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs index 8a6b56d932..8dddddfabb 100644 --- a/crates/hir-expand/src/db.rs +++ b/crates/hir-expand/src/db.rs @@ -11,7 +11,6 @@ use crate::{ AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallInfo, EagerExpander, EditionedFileId, ExpandError, ExpandResult, ExpandTo, FileRange, HirFileId, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, - attrs::Meta, builtin::pseudo_derive_attr_expansion, cfg_process::attr_macro_input_to_token_tree, declarative::DeclarativeMacroExpander, @@ -239,8 +238,15 @@ pub fn expand_speculative( MacroCallKind::Attr { censored_attr_ids: attr_ids, .. } => { if loc.def.is_attribute_derive() { // for pseudo-derive expansion we actually pass the attribute itself only - ast::Attr::cast(speculative_args.clone()).and_then(|attr| attr.token_tree()).map( - |token_tree| { + ast::Attr::cast(speculative_args.clone()) + .and_then(|attr| { + if let ast::Meta::TokenTreeMeta(meta) = attr.meta()? { + meta.token_tree() + } else { + None + } + }) + .map(|token_tree| { let mut tree = syntax_node_to_token_tree( token_tree.syntax(), span_map, @@ -250,26 +256,26 @@ pub fn expand_speculative( tree.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); tree.set_top_subtree_delimiter_span(tt::DelimSpan::from_single(span)); tree - }, - ) + }) } else { // Attributes may have an input token tree, build the subtree and map for this as well // then try finding a token id for our token if it is inside this input subtree. let item = ast::Item::cast(speculative_args.clone())?; - let (_, _, _, meta) = + let (_, meta) = attr_ids.invoc_attr().find_attr_range_with_source(db, loc.krate, &item); - match meta { - Meta::TokenTree { tt, .. } => { - let mut attr_arg = syntax_bridge::syntax_node_to_token_tree( - tt.syntax(), - span_map, - span, - DocCommentDesugarMode::ProcMacro, - ); - attr_arg.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); - Some(attr_arg) - } - _ => None, + if let ast::Meta::TokenTreeMeta(meta) = meta + && let Some(tt) = meta.token_tree() + { + let mut attr_arg = syntax_bridge::syntax_node_to_token_tree( + tt.syntax(), + span_map, + span, + DocCommentDesugarMode::ProcMacro, + ); + attr_arg.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); + Some(attr_arg) + } else { + None } } } @@ -501,11 +507,11 @@ fn macro_arg(db: &dyn ExpandDatabase, id: MacroCallId) -> MacroArgResult { } MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { let node = ast_id.to_ptr(db).to_node(&root); - let range = attr_ids - .invoc_attr() - .find_attr_range_with_source(db, loc.krate, &node) - .3 - .path_range(); + let (_, attr) = attr_ids.invoc_attr().find_attr_range_with_source(db, loc.krate, &node); + let range = attr + .path() + .map(|path| path.syntax().text_range()) + .unwrap_or_else(|| attr.syntax().text_range()); let span = map.span_for_range(range); let is_derive = matches!(loc.def.kind, MacroDefKind::BuiltInAttr(_, expander) if expander.is_derive()); diff --git a/crates/hir-expand/src/declarative.rs b/crates/hir-expand/src/declarative.rs index 1726412275..4b2c6e7351 100644 --- a/crates/hir-expand/src/declarative.rs +++ b/crates/hir-expand/src/declarative.rs @@ -6,7 +6,7 @@ use base_db::Crate; use span::{Edition, Span, SyntaxContext}; use stdx::TupleExt; use syntax::{ - AstNode, AstToken, + AstNode, ast::{self, HasAttrs}, }; use syntax_bridge::DocCommentDesugarMode; @@ -15,7 +15,7 @@ use triomphe::Arc; use crate::{ AstId, ExpandError, ExpandErrorKind, ExpandResult, HirFileId, Lookup, MacroCallId, MacroCallStyle, - attrs::{Meta, expand_cfg_attr}, + attrs::{AstKeyValueMetaExt, AstPathExt, expand_cfg_attr}, db::ExpandDatabase, hygiene::{Transparency, apply_mark}, tt, @@ -92,11 +92,10 @@ impl DeclarativeMacroExpander { expand_cfg_attr( node.attrs(), || cfg_options.get_or_init(|| def_crate.cfg_options(db)), - |attr, _, _, _| { - if let Meta::NamedKeyValue { name: Some(name), value, .. } = attr - && name.text() == "rustc_macro_transparency" - && let Some(value) = value.and_then(ast::String::cast) - && let Ok(value) = value.value() + |attr, _| { + if let ast::Meta::KeyValueMeta(attr) = attr + && attr.path().is1("rustc_macro_transparency") + && let Some(value) = attr.value_string() { match &*value { "transparent" => ControlFlow::Break(Transparency::Transparent), diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs index 4b2c75ed38..8d42a24e2f 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -58,7 +58,6 @@ use crate::{ }; pub use crate::{ - cfg_process::check_cfg_attr_value, files::{AstId, ErasedAstId, FileRange, InFile, InMacroFile, InRealFile}, prettify_macro_expansion_::prettify_macro_expansion, }; @@ -635,14 +634,12 @@ impl MacroCallLoc { ast_id.with_value(ast_id.to_node(db).syntax().clone()) } MacroCallKind::Derive { ast_id, derive_attr_index, .. } => { - // FIXME: handle `cfg_attr` - let (attr, _, _, _) = derive_attr_index.find_attr_range(db, self.krate, *ast_id); + let (_, attr) = derive_attr_index.find_attr_range(db, self.krate, *ast_id); ast_id.with_value(attr.syntax().clone()) } MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { if self.def.is_attribute_derive() { - let (attr, _, _, _) = - attr_ids.invoc_attr().find_attr_range(db, self.krate, *ast_id); + let (_, attr) = attr_ids.invoc_attr().find_attr_range(db, self.krate, *ast_id); ast_id.with_value(attr.syntax().clone()) } else { ast_id.with_value(ast_id.to_node(db).syntax().clone()) @@ -770,11 +767,11 @@ impl MacroCallKind { } MacroCallKind::Derive { ast_id, derive_attr_index, .. } => { // FIXME: should be the range of the macro name, not the whole derive - derive_attr_index.find_attr_range(db, krate, ast_id).2 + derive_attr_index.find_attr_range(db, krate, ast_id).1.syntax().text_range() } // FIXME: handle `cfg_attr` MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { - attr_ids.invoc_attr().find_attr_range(db, krate, ast_id).2 + attr_ids.invoc_attr().find_attr_range(db, krate, ast_id).1.syntax().text_range() } }; diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 89f3cfd140..2829902035 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1239,11 +1239,15 @@ fn emit_def_diagnostic_<'db>( ); } DefDiagnosticKind::InvalidDeriveTarget { ast, id } => { - let derive = id.find_attr_range(db, krate, *ast).3.path_range(); + let (_, attr) = id.find_attr_range(db, krate, *ast); + let derive = attr + .path() + .map(|path| path.syntax().text_range()) + .unwrap_or_else(|| attr.syntax().text_range()); acc.push(InvalidDeriveTarget { range: ast.with_value(derive) }.into()); } DefDiagnosticKind::MalformedDerive { ast, id } => { - let derive = id.find_attr_range(db, krate, *ast).2; + let derive = id.find_attr_range(db, krate, *ast).1.syntax().text_range(); acc.push(MalformedDerive { range: ast.with_value(derive) }.into()); } DefDiagnosticKind::MacroDefError { ast, message } => { @@ -1283,7 +1287,8 @@ fn precise_macro_call_location( ast_id.with_value(range) } MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { - let attr_range = attr_ids.invoc_attr().find_attr_range(db, krate, *ast_id).2; + let attr_range = + attr_ids.invoc_attr().find_attr_range(db, krate, *ast_id).1.syntax().text_range(); ast_id.with_value(attr_range) } } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 9a31a08ffb..9996162485 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -24,6 +24,7 @@ use hir_def::{ }; use hir_expand::{ EditionedFileId, ExpandResult, FileRange, HirFileId, InMacroFile, MacroCallId, + attrs::AstPathExt, builtin::{BuiltinFnLikeExpander, EagerExpander}, db::ExpandDatabase, files::{FileRangeWrapper, HirFileRange, InRealFile}, @@ -298,14 +299,15 @@ impl<DB: HirDatabase + ?Sized> Semantics<'_, DB> { hir_expand::attrs::expand_cfg_attr::<Infallible>( extra_crate_attrs.chain(ast::attrs_including_inner(&item)), cfg_options, - |attr, _, _, _| { - let hir_expand::attrs::Meta::TokenTree { path, tt } = attr else { + |attr, _| { + let ast::Meta::TokenTreeMeta(attr) = attr else { return ControlFlow::Continue(()); }; - if path.segments.len() != 1 { + let (Some(segment), Some(tt)) = (attr.path().as_one_segment(), attr.token_tree()) + else { return ControlFlow::Continue(()); - } - let lint_attr = match path.segments[0].text() { + }; + let lint_attr = match &*segment { "allow" => LintAttr::Allow, "expect" => LintAttr::Expect, "warn" => LintAttr::Warn, @@ -554,17 +556,6 @@ impl<'db> SemanticsImpl<'db> { Some(InFile::new(file_id.into(), node)) } - pub fn check_cfg_attr(&self, attr: &ast::TokenTree) -> Option<bool> { - let file_id = self.find_file(attr.syntax()).file_id; - let krate = match file_id { - HirFileId::FileId(file_id) => { - self.file_to_module_defs(file_id.file_id(self.db)).next()?.krate(self.db).id - } - HirFileId::MacroFile(macro_file) => self.db.lookup_intern_macro_call(macro_file).krate, - }; - hir_expand::check_cfg_attr_value(self.db, attr, krate) - } - /// Expands the macro if it isn't one of the built-in ones that expand to custom syntax or dummy /// expansions. pub fn expand_allowed_builtins( @@ -608,8 +599,8 @@ impl<'db> SemanticsImpl<'db> { Some(self.expand(macro_call_id).map(|it| InFile::new(macro_call_id.into(), it))) } - pub fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option<SyntaxNode> { - let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; + pub fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Meta) -> Option<SyntaxNode> { + let adt = attr.parent_attr()?.syntax().parent().and_then(ast::Adt::cast)?; let src = self.wrap_node_infile(attr.clone()); let call_id = self.with_ctx(|ctx| { ctx.attr_to_derive_macro_call(src.with_value(&adt), src).map(|(_, it, _)| it) @@ -617,7 +608,7 @@ impl<'db> SemanticsImpl<'db> { Some(self.parse_or_expand(call_id.into())) } - pub fn resolve_derive_macro(&self, attr: &ast::Attr) -> Option<Vec<Option<Macro>>> { + pub fn resolve_derive_macro(&self, attr: &ast::Meta) -> Option<Vec<Option<Macro>>> { let calls = self.derive_macro_calls(attr)?; self.with_ctx(|ctx| { Some( @@ -644,7 +635,7 @@ impl<'db> SemanticsImpl<'db> { pub fn expand_derive_macro( &self, - attr: &ast::Attr, + attr: &ast::Meta, ) -> Option<Vec<Option<ExpandResult<SyntaxNode>>>> { let res: Vec<_> = self .derive_macro_calls(attr)? @@ -662,9 +653,9 @@ impl<'db> SemanticsImpl<'db> { fn derive_macro_calls( &self, - attr: &ast::Attr, + attr: &ast::Meta, ) -> Option<Vec<Option<Either<MacroCallId, BuiltinDeriveImplId>>>> { - let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; + let adt = attr.parent_attr()?.syntax().parent().and_then(ast::Adt::cast)?; let file_id = self.find_file(adt.syntax()).file_id; let adt = InFile::new(file_id, &adt); let src = InFile::new(file_id, attr.clone()); @@ -773,7 +764,11 @@ impl<'db> SemanticsImpl<'db> { let attr = self.wrap_node_infile(actual_macro_call.clone()); let adt = actual_macro_call.syntax().parent().and_then(ast::Adt::cast)?; let macro_call_id = self.with_ctx(|ctx| { - ctx.attr_to_derive_macro_call(attr.with_value(&adt), attr).map(|(_, it, _)| it) + ctx.attr_to_derive_macro_call( + attr.with_value(&adt), + attr.with_value(attr.value.meta()?), + ) + .map(|(_, it, _)| it) })?; hir_expand::db::expand_speculative( self.db, @@ -1328,7 +1323,7 @@ impl<'db> SemanticsImpl<'db> { // text ranges of the outer ones, and then all of the inner ones up // to the invoking attribute so that the inbetween is ignored. // FIXME: Should cfg_attr be handled differently? - let (attr, _, _, _) = attr_ids + let (attr, _) = attr_ids .invoc_attr() .find_attr_range_with_source(db, loc.krate, &item); let start = attr.syntax().text_range().start(); @@ -1435,7 +1430,7 @@ impl<'db> SemanticsImpl<'db> { let derive_call = ctx .attr_to_derive_macro_call( InFile::new(expansion, &adt), - InFile::new(expansion, attr.clone()), + InFile::new(expansion, meta.clone()), )? .1; diff --git a/crates/hir/src/semantics/child_by_source.rs b/crates/hir/src/semantics/child_by_source.rs index f6d1bec575..babeb35913 100644 --- a/crates/hir/src/semantics/child_by_source.rs +++ b/crates/hir/src/semantics/child_by_source.rs @@ -126,8 +126,7 @@ impl ChildBySource for ItemScope { calls.for_each(|(attr_id, call_id, calls)| { // FIXME: Is this the right crate? let krate = call_id.lookup(db).krate; - // FIXME: Fix cfg_attr handling. - let (attr, _, _, _) = attr_id.find_attr_range_with_source(db, krate, &adt); + let (_, attr) = attr_id.find_attr_range_with_source(db, krate, &adt); res[keys::DERIVE_MACRO_CALL] .insert(AstPtr::new(&attr), (attr_id, call_id, calls.into())); }); diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 59bccc22d8..d932198b43 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -398,7 +398,7 @@ impl SourceToDefCtx<'_, '_> { pub(super) fn attr_to_derive_macro_call( &mut self, item: InFile<&ast::Adt>, - src: InFile<ast::Attr>, + src: InFile<ast::Meta>, ) -> Option<(AttrId, MacroCallId, &[Option<Either<MacroCallId, BuiltinDeriveImplId>>])> { let map = self.dyn_map(item)?; map[keys::DERIVE_MACRO_CALL] @@ -423,6 +423,7 @@ impl SourceToDefCtx<'_, '_> { let dyn_map = &map[keys::DERIVE_MACRO_CALL]; adt.value .attrs() + .flat_map(|attr| attr.skip_cfg_attrs()) .filter_map(move |attr| dyn_map.get(&AstPtr::new(&attr))) .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids)) }) diff --git a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs index e022a27e51..fccc04770e 100644 --- a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs +++ b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs @@ -279,7 +279,7 @@ fn todo_fn(f: &ast::Fn, config: &AssistConfig) -> ast::Fn { } fn cfg_attrs(node: &impl HasAttrs) -> impl Iterator<Item = ast::Attr> { - node.attrs().filter(|attr| attr.as_simple_call().is_some_and(|(name, _arg)| name == "cfg")) + node.attrs().filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))) } #[cfg(test)] diff --git a/crates/ide-assists/src/handlers/generate_derive.rs b/crates/ide-assists/src/handlers/generate_derive.rs index 3ef68f06e4..7aeb5e3396 100644 --- a/crates/ide-assists/src/handlers/generate_derive.rs +++ b/crates/ide-assists/src/handlers/generate_derive.rs @@ -68,9 +68,11 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt ], ); - let delimiter = derive - .meta() - .expect("make::attr_outer was expected to have Meta") + let meta = derive.meta().expect("make::attr_outer was expected to have Meta"); + let ast::Meta::TokenTreeMeta(meta) = meta else { + unreachable!("make::attr_outer was passed a token tree meta"); + }; + let delimiter = meta .token_tree() .expect("failed to get token tree out of Meta") .r_paren_token() diff --git a/crates/ide-assists/src/handlers/generate_single_field_struct_from.rs b/crates/ide-assists/src/handlers/generate_single_field_struct_from.rs index 2fc2b9efe8..7746cdc068 100644 --- a/crates/ide-assists/src/handlers/generate_single_field_struct_from.rs +++ b/crates/ide-assists/src/handlers/generate_single_field_struct_from.rs @@ -121,9 +121,8 @@ pub(crate) fn generate_single_field_struct_from( ) .indent_with_mapping(1.into(), &make); - let cfg_attrs = strukt - .attrs() - .filter(|attr| attr.as_simple_call().is_some_and(|(name, _arg)| name == "cfg")); + let cfg_attrs = + strukt.attrs().filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))); let impl_ = make.impl_trait( cfg_attrs, diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index 04c9d8e54d..5e595218f6 100644 --- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -64,9 +64,10 @@ pub(crate) fn replace_derive_with_manual_impl( .filter_map(|attr| attr.path()) .collect::<Vec<_>>(); - let adt = value.parent().and_then(ast::Adt::cast)?; - let attr = ast::Attr::cast(value)?; - let args = attr.token_tree()?; + let attr = ast::Meta::cast(value)?.parent_attr()?; + let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; + let ast::Meta::TokenTreeMeta(meta) = attr.meta()? else { return None }; + let args = meta.token_tree()?; let current_module = ctx.sema.scope(adt.syntax())?.module(); let current_crate = current_module.krate(ctx.db()); diff --git a/crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs b/crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs index 36df4af31d..3b8988db7a 100644 --- a/crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs +++ b/crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs @@ -19,7 +19,7 @@ use crate::{AssistContext, AssistId, Assists}; // ``` // -> // ``` -// #[cfg_attr($0, derive(Debug))] +// #[cfg_attr(${0:cfg}, derive(Debug))] // struct S { // field: i32 // } @@ -147,12 +147,15 @@ pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) - } }?; match option { - WrapUnwrapOption::WrapAttr(attrs) => match &attrs[..] { - [attr] if attr.simple_name().as_deref() == Some("cfg_attr") => { - unwrap_cfg_attr(acc, attrs.into_iter().next().unwrap()) + WrapUnwrapOption::WrapAttr(attrs) => { + if let [attr] = &attrs[..] + && let Some(ast::Meta::CfgAttrMeta(meta)) = attr.meta() + { + unwrap_cfg_attr(acc, meta) + } else { + wrap_cfg_attrs(acc, ctx, attrs) } - _ => wrap_cfg_attrs(acc, ctx, attrs), - }, + } WrapUnwrapOption::WrapDerive { derive, attr } => wrap_derive(acc, ctx, attr, derive), } } @@ -164,7 +167,8 @@ fn wrap_derive( derive_element: TextRange, ) -> Option<()> { let range = attr.syntax().text_range(); - let token_tree = attr.token_tree()?; + let ast::Meta::TokenTreeMeta(meta) = attr.meta()? else { return None }; + let token_tree = meta.token_tree()?; let mut path_text = String::new(); let mut cfg_derive_tokens = Vec::new(); @@ -193,20 +197,15 @@ fn wrap_derive( let new_derive = make.attr_outer( make.meta_token_tree(make.ident_path("derive"), make.token_tree(T!['('], new_derive)), ); - let meta = make.meta_token_tree( - make.ident_path("cfg_attr"), - make.token_tree( - T!['('], - vec![ - NodeOrToken::Token(make.token(T![,])), - NodeOrToken::Token(make.whitespace(" ")), - NodeOrToken::Token(make.ident("derive")), - NodeOrToken::Node(make.token_tree(T!['('], cfg_derive_tokens)), - ], - ), + let meta = make.cfg_attr_meta( + make.cfg_flag("cfg"), + [make.meta_token_tree( + make.ident_path("derive"), + make.token_tree(T!['('], cfg_derive_tokens), + )], ); - let cfg_attr = make.attr_outer(meta); + let cfg_attr = make.attr_outer(meta.clone().into()); editor.replace_with_many( attr.syntax(), vec![ @@ -217,11 +216,10 @@ fn wrap_derive( ); if let Some(snippet_cap) = ctx.config.snippet_cap - && let Some(first_meta) = - cfg_attr.meta().and_then(|meta| meta.token_tree()).and_then(|tt| tt.l_paren_token()) + && let Some(cfg_predicate) = meta.cfg_predicate() { - let tabstop = edit.make_tabstop_after(snippet_cap); - editor.add_annotation(first_meta, tabstop); + let tabstop = edit.make_placeholder_snippet(snippet_cap); + editor.add_annotation(cfg_predicate.syntax(), tabstop); } editor.add_mappings(make.finish_with_mappings()); @@ -236,58 +234,29 @@ fn wrap_derive( ); Some(()) } + fn wrap_cfg_attrs(acc: &mut Assists, ctx: &AssistContext<'_>, attrs: Vec<ast::Attr>) -> Option<()> { let (first_attr, last_attr) = (attrs.first()?, attrs.last()?); let range = first_attr.syntax().text_range().cover(last_attr.syntax().text_range()); - let path_attrs = - attrs.iter().map(|attr| Some((attr.path()?, attr.clone()))).collect::<Option<Vec<_>>>()?; let handle_source_change = |edit: &mut SourceChangeBuilder| { let make = SyntaxFactory::with_mappings(); let mut editor = edit.make_editor(first_attr.syntax()); - let mut raw_tokens = vec![]; - for (path, attr) in path_attrs { - raw_tokens.extend([ - NodeOrToken::Token(make.token(T![,])), - NodeOrToken::Token(make.whitespace(" ")), - ]); - path.syntax().descendants_with_tokens().for_each(|it| { - if let NodeOrToken::Token(token) = it { - raw_tokens.push(NodeOrToken::Token(token)); - } - }); - if let Some(meta) = attr.meta() { - if let (Some(eq), Some(expr)) = (meta.eq_token(), meta.expr()) { - raw_tokens.push(NodeOrToken::Token(make.whitespace(" "))); - raw_tokens.push(NodeOrToken::Token(eq)); - raw_tokens.push(NodeOrToken::Token(make.whitespace(" "))); - - expr.syntax().descendants_with_tokens().for_each(|it| { - if let NodeOrToken::Token(token) = it { - raw_tokens.push(NodeOrToken::Token(token)); - } - }); - } else if let Some(tt) = meta.token_tree() { - raw_tokens.extend(tt.token_trees_and_tokens()); - } - } - } let meta = - make.meta_token_tree(make.ident_path("cfg_attr"), make.token_tree(T!['('], raw_tokens)); + make.cfg_attr_meta(make.cfg_flag("cfg"), attrs.iter().filter_map(|attr| attr.meta())); let cfg_attr = if first_attr.excl_token().is_some() { - make.attr_inner(meta) + make.attr_inner(meta.clone().into()) } else { - make.attr_outer(meta) + make.attr_outer(meta.clone().into()) }; let syntax_range = first_attr.syntax().clone().into()..=last_attr.syntax().clone().into(); editor.replace_all(syntax_range, vec![cfg_attr.syntax().clone().into()]); if let Some(snippet_cap) = ctx.config.snippet_cap - && let Some(first_meta) = - cfg_attr.meta().and_then(|meta| meta.token_tree()).and_then(|tt| tt.l_paren_token()) + && let Some(cfg_flag) = meta.cfg_predicate() { - let tabstop = edit.make_tabstop_after(snippet_cap); - editor.add_annotation(first_meta, tabstop); + let tabstop = edit.make_placeholder_snippet(snippet_cap); + editor.add_annotation(cfg_flag.syntax(), tabstop); } editor.add_mappings(make.finish_with_mappings()); @@ -301,66 +270,28 @@ fn wrap_cfg_attrs(acc: &mut Assists, ctx: &AssistContext<'_>, attrs: Vec<ast::At ); Some(()) } -fn unwrap_cfg_attr(acc: &mut Assists, attr: ast::Attr) -> Option<()> { - let range = attr.syntax().text_range(); - let meta = attr.meta()?; - let meta_tt = meta.token_tree()?; - let mut inner_attrs = Vec::with_capacity(1); - let mut found_comma = false; - let mut iter = meta_tt.token_trees_and_tokens().skip(1).peekable(); - while let Some(tt) = iter.next() { - if let NodeOrToken::Token(token) = &tt { - if token.kind() == T![')'] { - break; - } - if token.kind() == T![,] { - found_comma = true; - continue; - } - } - if !found_comma { - continue; - } - let Some(attr_name) = tt.into_token().and_then(|token| { - if token.kind() == T![ident] { Some(make::ext::ident_path(token.text())) } else { None } - }) else { - continue; - }; - let next_tt = iter.next()?; - let meta = match next_tt { - NodeOrToken::Node(tt) => make::meta_token_tree(attr_name, tt), - NodeOrToken::Token(token) if token.kind() == T![,] || token.kind() == T![')'] => { - make::meta_path(attr_name) - } - NodeOrToken::Token(token) => { - let equals = algo::skip_trivia_token(token, syntax::Direction::Next)?; - if equals.kind() != T![=] { - return None; - } - let expr_token = - algo::skip_trivia_token(equals.next_token()?, syntax::Direction::Next) - .and_then(|it| { - if it.kind().is_literal() { - Some(make::expr_literal(it.text())) - } else { - None - } - })?; - make::meta_expr(attr_name, ast::Expr::Literal(expr_token)) + +fn unwrap_cfg_attr(acc: &mut Assists, meta: ast::CfgAttrMeta) -> Option<()> { + let top_attr = ast::Meta::from(meta.clone()).parent_attr()?; + let range = top_attr.syntax().text_range(); + let inner_attrs = meta + .metas() + .map(|meta| { + if top_attr.excl_token().is_some() { + make::attr_inner(meta) + } else { + make::attr_outer(meta) } - }; - if attr.excl_token().is_some() { - inner_attrs.push(make::attr_inner(meta)); - } else { - inner_attrs.push(make::attr_outer(meta)); - } - } + }) + .collect::<Vec<_>>(); if inner_attrs.is_empty() { return None; } let handle_source_change = |f: &mut SourceChangeBuilder| { - let inner_attrs = - inner_attrs.iter().map(|it| it.to_string()).join(&format!("\n{}", attr.indent_level())); + let inner_attrs = inner_attrs + .iter() + .map(|it| it.to_string()) + .join(&format!("\n{}", top_attr.indent_level())); f.replace(range, inner_attrs); }; acc.add( @@ -388,7 +319,7 @@ mod tests { } "#, r#" - #[cfg_attr($0, derive(Debug))] + #[cfg_attr(${0:cfg}, derive(Debug))] pub struct Test { test: u32, } @@ -422,7 +353,7 @@ mod tests { "#, r#" pub struct Test { - #[cfg_attr($0, foo)] + #[cfg_attr(${0:cfg}, foo)] test: u32, } "#, @@ -456,7 +387,7 @@ mod tests { r#" pub struct Test { #[other_attr] - #[cfg_attr($0, foo, bar)] + #[cfg_attr(${0:cfg}, foo, bar)] #[other_attr] test: u32, } @@ -491,7 +422,7 @@ mod tests { "#, r#" pub struct Test { - #[cfg_attr($0, foo = "bar")] + #[cfg_attr(${0:cfg}, foo = "bar")] test: u32, } "#, @@ -520,7 +451,7 @@ mod tests { #![no_std$0] "#, r#" - #![cfg_attr($0, no_std)] + #![cfg_attr(${0:cfg}, no_std)] "#, ); check_assist( @@ -545,7 +476,7 @@ mod tests { "#, r#" #[derive( Clone, Copy)] - #[cfg_attr($0, derive(Debug))] + #[cfg_attr(${0:cfg}, derive(Debug))] pub struct Test { test: u32, } @@ -561,7 +492,7 @@ mod tests { "#, r#" #[derive(Clone, Copy)] - #[cfg_attr($0, derive(Debug))] + #[cfg_attr(${0:cfg}, derive(Debug))] pub struct Test { test: u32, } @@ -580,7 +511,7 @@ mod tests { "#, r#" #[derive( Clone, Copy)] - #[cfg_attr($0, derive(std::fmt::Debug))] + #[cfg_attr(${0:cfg}, derive(std::fmt::Debug))] pub struct Test { test: u32, } @@ -596,7 +527,7 @@ mod tests { "#, r#" #[derive(Clone, Copy)] - #[cfg_attr($0, derive(std::fmt::Debug))] + #[cfg_attr(${0:cfg}, derive(std::fmt::Debug))] pub struct Test { test: u32, } @@ -615,7 +546,7 @@ mod tests { "#, r#" #[derive(std::fmt::Debug, Clone)] - #[cfg_attr($0, derive(Copy))] + #[cfg_attr(${0:cfg}, derive(Copy))] pub struct Test { test: u32, } @@ -631,7 +562,7 @@ mod tests { "#, r#" #[derive(Clone, Copy)] - #[cfg_attr($0, derive(std::fmt::Debug))] + #[cfg_attr(${0:cfg}, derive(std::fmt::Debug))] pub struct Test { test: u32, } diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 66d5cf834f..a499607c1f 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -3852,7 +3852,7 @@ struct S { } "#####, r#####" -#[cfg_attr($0, derive(Debug))] +#[cfg_attr(${0:cfg}, derive(Debug))] struct S { field: i32 } diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index 3de8ec7f53..896743342c 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -598,9 +598,7 @@ fn generate_impl_text_inner( // Copy any cfg attrs from the original adt buf.push_str("\n\n"); - let cfg_attrs = adt - .attrs() - .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false)); + let cfg_attrs = adt.attrs().filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))); cfg_attrs.for_each(|attr| buf.push_str(&format!("{attr}\n"))); // `impl{generic_params} {trait_text} for {name}{generic_params.to_generic_args()}` @@ -740,8 +738,7 @@ fn generate_impl_inner( let ty = make::ty_path(make::ext::ident_path(&adt.name().unwrap().text())); - let cfg_attrs = - adt.attrs().filter(|attr| attr.as_simple_call().is_some_and(|(name, _arg)| name == "cfg")); + let cfg_attrs = adt.attrs().filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))); match trait_ { Some(trait_) => make::impl_trait( cfg_attrs, @@ -811,8 +808,7 @@ fn generate_impl_inner_with_factory( let ty: ast::Type = make.ty_path(make.ident_path(&adt.name().unwrap().text())).into(); - let cfg_attrs = - adt.attrs().filter(|attr| attr.as_simple_call().is_some_and(|(name, _arg)| name == "cfg")); + let cfg_attrs = adt.attrs().filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))); match trait_ { Some(trait_) => make.impl_trait( cfg_attrs, diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs index 20776f6c49..da1e664f96 100644 --- a/crates/ide-completion/src/completions/attribute.rs +++ b/crates/ide-completion/src/completions/attribute.rs @@ -30,6 +30,7 @@ mod lint; mod macro_use; mod repr; +pub(crate) use self::cfg::complete_cfg; pub(crate) use self::derive::complete_derive_path; /// Complete inputs to known builtin attributes as well as derive attributes @@ -37,7 +38,7 @@ pub(crate) fn complete_known_attribute_input( acc: &mut Completions, ctx: &CompletionContext<'_>, &colon_prefix: &bool, - fake_attribute_under_caret: &ast::Attr, + fake_attribute_under_caret: &ast::TokenTreeMeta, extern_crate: Option<&ast::ExternCrate>, ) -> Option<()> { let attribute = fake_attribute_under_caret; @@ -70,7 +71,6 @@ pub(crate) fn complete_known_attribute_input( lint::complete_lint(acc, ctx, colon_prefix, &existing_lints, &lints); } - ["cfg"] | ["cfg_attr"] => cfg::complete_cfg(acc, ctx), ["macro_use"] => macro_use::complete_macro_use( acc, ctx, diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index ae3f717607..485e5f0caf 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -408,9 +408,11 @@ pub(crate) enum CompletionAnalysis<'db> { /// Set if we are currently completing in an unexpanded attribute, this usually implies a builtin attribute like `allow($0)` UnexpandedAttrTT { colon_prefix: bool, - fake_attribute_under_caret: Option<ast::Attr>, + fake_attribute_under_caret: Option<ast::TokenTreeMeta>, extern_crate: Option<ast::ExternCrate>, }, + /// Set if we are inside the predicate of a #[cfg] or #[cfg_attr]. + CfgPredicate, MacroSegment, } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index d8f160c100..2a293313f2 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -284,9 +284,12 @@ fn expand( }; // Expand pseudo-derive expansion aka `derive(Debug$0)` - if let Some((orig_attr, spec_attr)) = attrs { + if let Some((orig_attr, spec_attr)) = attrs + && let Some(orig_meta) = orig_attr.meta() + { + // FIXME: Support speculative expansion with `cfg_attr`. if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_tokens))) = ( - sema.expand_derive_as_pseudo_attr_macro(&orig_attr), + sema.expand_derive_as_pseudo_attr_macro(&orig_meta), sema.speculative_expand_derive_as_pseudo_attr_macro( &orig_attr, &spec_attr, @@ -463,7 +466,9 @@ fn analyze<'db>( } // Overwrite the path kind for derives - if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx { + if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx + && let Some(origin_meta) = origin_attr.meta() + { if let Some(ast::NameLike::NameRef(name_ref)) = find_node_at_offset(&file_with_fake_ident, offset) { @@ -473,7 +478,7 @@ fn analyze<'db>( if let NameRefKind::Path(path_ctx) = &mut nameref_ctx.kind { path_ctx.kind = PathKind::Derive { existing_derives: sema - .resolve_derive_macro(&origin_attr) + .resolve_derive_macro(&origin_meta) .into_iter() .flatten() .flatten() @@ -498,7 +503,7 @@ fn analyze<'db>( let token = syntax::algo::skip_trivia_token(self_token.clone(), Direction::Prev)?; let p = token.parent()?; if p.kind() == SyntaxKind::TOKEN_TREE - && p.ancestors().any(|it| it.kind() == SyntaxKind::META) + && p.ancestors().any(|it| it.kind() == SyntaxKind::TOKEN_TREE_META) { let colon_prefix = previous_non_trivia_token(self_token.clone()) .is_some_and(|it| T![:] == it.kind()); @@ -506,7 +511,7 @@ fn analyze<'db>( CompletionAnalysis::UnexpandedAttrTT { fake_attribute_under_caret: fake_ident_token .parent_ancestors() - .find_map(ast::Attr::cast), + .find_map(ast::TokenTreeMeta::cast), colon_prefix, extern_crate: p.ancestors().find_map(ast::ExternCrate::cast), } @@ -525,6 +530,13 @@ fn analyze<'db>( } else { return None; } + } else if find_node_at_offset::<ast::CfgPredicate>( + &speculative_file, + speculative_offset, + ) + .is_some() + { + CompletionAnalysis::CfgPredicate } else { return None; } diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 69ca2af772..3867e65ae5 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -263,6 +263,7 @@ pub fn completions( extern_crate.as_ref(), ); } + CompletionAnalysis::CfgPredicate => completions::attribute::complete_cfg(acc, ctx), CompletionAnalysis::MacroSegment => { completions::macro_def::complete_macro_segment(acc, ctx); } diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs index 2f696d07e2..9018552afb 100644 --- a/crates/ide-db/src/imports/import_assets.rs +++ b/crates/ide-db/src/imports/import_assets.rs @@ -117,7 +117,9 @@ impl PathDefinitionKinds { // validate that the following segment resolve. SyntaxKind::PATH => Self { modules: true, type_namespace: true, ..Self::ALL_DISABLED }, SyntaxKind::MACRO_CALL => Self { bang_macros: true, ..Self::ALL_DISABLED }, - SyntaxKind::META => Self { attr_macros: true, ..Self::ALL_DISABLED }, + SyntaxKind::PATH_META | SyntaxKind::KEY_VALUE_META | SyntaxKind::TOKEN_TREE_META => { + Self { attr_macros: true, ..Self::ALL_DISABLED } + } SyntaxKind::USE_TREE => { if ast::UseTree::cast(parent).unwrap().use_tree_list().is_some() { Self { modules: true, ..Self::ALL_DISABLED } diff --git a/crates/ide-db/src/imports/insert_use.rs b/crates/ide-db/src/imports/insert_use.rs index 41ce1e5960..9318c3e132 100644 --- a/crates/ide-db/src/imports/insert_use.rs +++ b/crates/ide-db/src/imports/insert_use.rs @@ -101,14 +101,12 @@ impl ImportScope { { block = b.stmt_list(); } - if has_attrs - .attrs() - .any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")) + if has_attrs.attrs().any(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))) { if let Some(b) = block.clone() { - let current_cfgs = has_attrs.attrs().filter(|attr| { - attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg") - }); + let current_cfgs = has_attrs + .attrs() + .filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))); let total_cfgs: Vec<_> = required_cfgs.iter().cloned().chain(current_cfgs).collect(); @@ -118,7 +116,7 @@ impl ImportScope { if let Some(parent) = parent { can_merge = parent.children().filter_map(ast::Use::cast).any(|u| { let u_attrs = u.attrs().filter(|attr| { - attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg") + matches!(attr.meta(), Some(ast::Meta::CfgMeta(_))) }); crate::imports::merge_imports::eq_attrs( u_attrs, @@ -134,9 +132,11 @@ impl ImportScope { }); } } - required_cfgs.extend(has_attrs.attrs().filter(|attr| { - attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg") - })); + required_cfgs.extend( + has_attrs + .attrs() + .filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))), + ); } } } diff --git a/crates/ide-diagnostics/src/handlers/inactive_code.rs b/crates/ide-diagnostics/src/handlers/inactive_code.rs index 9bfbeeebf7..be4fe763a0 100644 --- a/crates/ide-diagnostics/src/handlers/inactive_code.rs +++ b/crates/ide-diagnostics/src/handlers/inactive_code.rs @@ -209,8 +209,8 @@ union FooBar { #[cfg(true)] fn active() {} - #[cfg(any(not(true)), false)] fn inactive2() {} -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: true is enabled + #[cfg(any(not(true), false))] fn inactive2() {} +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: true is enabled and false is disabled "#, ); diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index 6f4ea70e0a..fb885c2ad1 100644 --- a/crates/ide/src/expand_macro.rs +++ b/crates/ide/src/expand_macro.rs @@ -54,8 +54,9 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< let InFile { file_id, value: tokens } = hir::InMacroFile::new(macro_file, descended).upmap_once(db); let token = sema.parse_or_expand(file_id).covering_element(tokens[0]).into_token()?; - let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; + let attr = token.parent_ancestors().find_map(ast::Meta::cast)?; let expansions = sema.expand_derive_macro(&attr)?; + let ast::Meta::TokenTreeMeta(attr) = attr else { return None }; let idx = attr .token_tree()? .token_trees_and_tokens() diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html index ce9ec7431a..dcfe4dd41e 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html @@ -166,12 +166,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="comment documentation">///</span> <span class="comment documentation">/// ```</span> <span class="comment documentation">///</span><span class="none injected"> </span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span> -<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">not</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"false"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span> <span class="none attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span> +<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="keyword attribute">cfg_attr</span><span class="parenthesis attribute">(</span>not<span class="parenthesis attribute">(</span>feature <span class="operator attribute">=</span> <span class="string_literal attribute">"false"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span> <span class="builtin_attr attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span> <span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"</span><span class="keyword control injected">loop</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span><span class="string_literal attribute">"</span><span class="attribute_bracket attribute">]</span> <span class="comment documentation">/// ```</span> <span class="comment documentation">///</span> -<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"alloc"</span><span class="comma attribute">,</span> <span class="none attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"```rust"</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span> -<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute">cfg_attr</span><span class="parenthesis attribute">(</span><span class="none attribute">not</span><span class="parenthesis attribute">(</span><span class="none attribute">feature</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"alloc"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span> <span class="none attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"```ignore"</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span> +<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="keyword attribute">cfg_attr</span><span class="parenthesis attribute">(</span>feature <span class="operator attribute">=</span> <span class="string_literal attribute">"alloc"</span><span class="comma attribute">,</span> <span class="builtin_attr attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"```rust"</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span> +<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="keyword attribute">cfg_attr</span><span class="parenthesis attribute">(</span>not<span class="parenthesis attribute">(</span>feature <span class="operator attribute">=</span> <span class="string_literal attribute">"alloc"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span> <span class="builtin_attr attribute">doc</span> <span class="operator attribute">=</span> <span class="string_literal attribute">"```ignore"</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span> <span class="comment documentation">///</span><span class="none injected"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="module injected">alloc</span><span class="operator injected">::</span><span class="macro injected">vec</span><span class="macro_bang injected">!</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span> <span class="comment documentation">/// ```</span> <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration public">mix_and_match</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> diff --git a/crates/parser/src/grammar/attributes.rs b/crates/parser/src/grammar/attributes.rs index c0cf43a87b..2eeaa25257 100644 --- a/crates/parser/src/grammar/attributes.rs +++ b/crates/parser/src/grammar/attributes.rs @@ -40,6 +40,86 @@ fn attr(p: &mut Parser<'_>, inner: bool) { // #![unsafe] // #![unsafe =] +fn cfg_attr_meta(p: &mut Parser<'_>, m: Marker) { + // test cfg_attr + // #![cfg_attr(not(foo), unsafe(bar()), cfg_attr(all(true, foo = "bar"), baz = "baz"))] + p.eat_contextual_kw(T![cfg_attr]); + p.bump(T!['(']); + cfg_predicate(p); + p.expect(T![,]); + while !p.at(T![')']) && !p.at(EOF) { + meta(p); + if !p.eat(T![,]) { + break; + } + } + p.expect(T![')']); + m.complete(p, CFG_ATTR_META); +} + +const CFG_PREDICATE_FIRST_SET: TokenSet = TokenSet::new(&[T![true], T![false], T![ident]]); + +fn cfg_predicate(p: &mut Parser<'_>) { + let m = p.start(); + if p.eat(T![true]) || p.eat(T![false]) { + // test cfg_true_false_pred + // #![cfg(true)] + // #![cfg(false)] + m.complete(p, CFG_ATOM); + return; + } + p.expect(T![ident]); + if p.eat(T![=]) { + if p.at(T![ident]) { + // This is required for completion, that inserts an identifier, to work in cases like + // `#[cfg(key = $0)]`, and also makes sense on itself. + + // test_err key_ident_cfg_predicate + // #![cfg(key = value)] + p.err_and_bump("expected a string literal"); + } else { + // test cfg_key_value_pred + // #![cfg(key = "value")] + p.expect(T![string]); + } + m.complete(p, CFG_ATOM); + } else if p.at(T!['(']) { + // test cfg_composite_pred + // #![cfg(any(a, all(b = "c", d)))] + delimited( + p, + T!['('], + T![')'], + T![,], + || "expected a cfg predicate".to_owned(), + CFG_PREDICATE_FIRST_SET, + |p| { + if p.at_ts(CFG_PREDICATE_FIRST_SET) { + cfg_predicate(p); + true + } else { + false + } + }, + ); + m.complete(p, CFG_COMPOSITE); + } else { + m.complete(p, CFG_ATOM); + } +} + +fn cfg_meta(p: &mut Parser<'_>, m: Marker) { + // test cfg_meta + // #![cfg(foo)] + // #![cfg(foo = "bar",)] + p.eat_contextual_kw(T![cfg]); + p.bump(T!['(']); + cfg_predicate(p); + p.eat(T![,]); + p.expect(T![')']); + m.complete(p, CFG_META); +} + // test metas // #![simple_ident] // #![simple::path] @@ -62,11 +142,23 @@ fn attr(p: &mut Parser<'_>, inner: bool) { // #![unsafe(simple::path::tt[a b c])] // #![unsafe(simple::path::tt{a b c})] pub(super) fn meta(p: &mut Parser<'_>) { - let meta = p.start(); - let is_unsafe = p.eat(T![unsafe]); - if is_unsafe { + let m = p.start(); + if p.eat(T![unsafe]) { p.expect(T!['(']); + meta(p); + p.expect(T![')']); + m.complete(p, UNSAFE_META); + return; + } + + if p.nth_at(1, T!['(']) { + if p.at_contextual_kw(T![cfg_attr]) { + return cfg_attr_meta(p, m); + } else if p.at_contextual_kw(T![cfg]) { + return cfg_meta(p, m); + } } + paths::attr_path(p); match p.current() { @@ -75,13 +167,14 @@ pub(super) fn meta(p: &mut Parser<'_>) { if expressions::expr(p).is_none() { p.error("expected expression"); } + m.complete(p, KEY_VALUE_META); + } + T!['('] | T!['['] | T!['{'] => { + items::token_tree(p); + m.complete(p, TOKEN_TREE_META); + } + _ => { + m.complete(p, PATH_META); } - T!['('] | T!['['] | T!['{'] => items::token_tree(p), - _ => {} - } - if is_unsafe { - p.expect(T![')']); } - - meta.complete(p, META); } diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs index a2295e4495..9cd48f2aa4 100644 --- a/crates/parser/src/syntax_kind/generated.rs +++ b/crates/parser/src/syntax_kind/generated.rs @@ -116,6 +116,8 @@ pub enum SyntaxKind { AWAIT_KW, BIKESHED_KW, BUILTIN_KW, + CFG_ATTR_KW, + CFG_KW, CLOBBER_ABI_KW, DEFAULT_KW, DYN_KW, @@ -186,6 +188,10 @@ pub enum SyntaxKind { BREAK_EXPR, CALL_EXPR, CAST_EXPR, + CFG_ATOM, + CFG_ATTR_META, + CFG_COMPOSITE, + CFG_META, CLOSURE_EXPR, CONST, CONST_ARG, @@ -216,6 +222,7 @@ pub enum SyntaxKind { INDEX_EXPR, INFER_TYPE, ITEM_LIST, + KEY_VALUE_META, LABEL, LET_ELSE, LET_EXPR, @@ -238,7 +245,6 @@ pub enum SyntaxKind { MATCH_ARM_LIST, MATCH_EXPR, MATCH_GUARD, - META, METHOD_CALL_EXPR, MODULE, NAME, @@ -254,6 +260,7 @@ pub enum SyntaxKind { PAREN_TYPE, PATH, PATH_EXPR, + PATH_META, PATH_PAT, PATH_SEGMENT, PATH_TYPE, @@ -285,6 +292,7 @@ pub enum SyntaxKind { STMT_LIST, STRUCT, TOKEN_TREE, + TOKEN_TREE_META, TRAIT, TRY_BLOCK_MODIFIER, TRY_EXPR, @@ -302,6 +310,7 @@ pub enum SyntaxKind { TYPE_PARAM, UNDERSCORE_EXPR, UNION, + UNSAFE_META, USE, USE_BOUND_GENERIC_ARGS, USE_TREE, @@ -360,6 +369,10 @@ impl SyntaxKind { | BREAK_EXPR | CALL_EXPR | CAST_EXPR + | CFG_ATOM + | CFG_ATTR_META + | CFG_COMPOSITE + | CFG_META | CLOSURE_EXPR | CONST | CONST_ARG @@ -390,6 +403,7 @@ impl SyntaxKind { | INDEX_EXPR | INFER_TYPE | ITEM_LIST + | KEY_VALUE_META | LABEL | LET_ELSE | LET_EXPR @@ -412,7 +426,6 @@ impl SyntaxKind { | MATCH_ARM_LIST | MATCH_EXPR | MATCH_GUARD - | META | METHOD_CALL_EXPR | MODULE | NAME @@ -428,6 +441,7 @@ impl SyntaxKind { | PAREN_TYPE | PATH | PATH_EXPR + | PATH_META | PATH_PAT | PATH_SEGMENT | PATH_TYPE @@ -459,6 +473,7 @@ impl SyntaxKind { | STMT_LIST | STRUCT | TOKEN_TREE + | TOKEN_TREE_META | TRAIT | TRY_BLOCK_MODIFIER | TRY_EXPR @@ -476,6 +491,7 @@ impl SyntaxKind { | TYPE_PARAM | UNDERSCORE_EXPR | UNION + | UNSAFE_META | USE | USE_BOUND_GENERIC_ARGS | USE_TREE @@ -601,6 +617,8 @@ impl SyntaxKind { AUTO_KW => "auto", BIKESHED_KW => "bikeshed", BUILTIN_KW => "builtin", + CFG_KW => "cfg", + CFG_ATTR_KW => "cfg_attr", CLOBBER_ABI_KW => "clobber_abi", DEFAULT_KW => "default", DYN_KW => "dyn", @@ -704,6 +722,8 @@ impl SyntaxKind { AUTO_KW => true, BIKESHED_KW => true, BUILTIN_KW => true, + CFG_KW => true, + CFG_ATTR_KW => true, CLOBBER_ABI_KW => true, DEFAULT_KW => true, DYN_KW if edition < Edition::Edition2018 => true, @@ -795,6 +815,8 @@ impl SyntaxKind { AUTO_KW => true, BIKESHED_KW => true, BUILTIN_KW => true, + CFG_KW => true, + CFG_ATTR_KW => true, CLOBBER_ABI_KW => true, DEFAULT_KW => true, DYN_KW if edition < Edition::Edition2018 => true, @@ -949,6 +971,8 @@ impl SyntaxKind { "auto" => AUTO_KW, "bikeshed" => BIKESHED_KW, "builtin" => BUILTIN_KW, + "cfg" => CFG_KW, + "cfg_attr" => CFG_ATTR_KW, "clobber_abi" => CLOBBER_ABI_KW, "default" => DEFAULT_KW, "dyn" if edition < Edition::Edition2018 => DYN_KW, @@ -1121,6 +1145,8 @@ macro_rules ! T_ { [auto] => { $ crate :: SyntaxKind :: AUTO_KW }; [bikeshed] => { $ crate :: SyntaxKind :: BIKESHED_KW }; [builtin] => { $ crate :: SyntaxKind :: BUILTIN_KW }; + [cfg] => { $ crate :: SyntaxKind :: CFG_KW }; + [cfg_attr] => { $ crate :: SyntaxKind :: CFG_ATTR_KW }; [clobber_abi] => { $ crate :: SyntaxKind :: CLOBBER_ABI_KW }; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW }; [dyn] => { $ crate :: SyntaxKind :: DYN_KW }; diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index 01fc172ed9..71978390df 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -87,6 +87,22 @@ mod ok { #[test] fn cast_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/cast_expr.rs"); } #[test] + fn cfg_attr() { run_and_expect_no_errors("test_data/parser/inline/ok/cfg_attr.rs"); } + #[test] + fn cfg_composite_pred() { + run_and_expect_no_errors("test_data/parser/inline/ok/cfg_composite_pred.rs"); + } + #[test] + fn cfg_key_value_pred() { + run_and_expect_no_errors("test_data/parser/inline/ok/cfg_key_value_pred.rs"); + } + #[test] + fn cfg_meta() { run_and_expect_no_errors("test_data/parser/inline/ok/cfg_meta.rs"); } + #[test] + fn cfg_true_false_pred() { + run_and_expect_no_errors("test_data/parser/inline/ok/cfg_true_false_pred.rs"); + } + #[test] fn closure_binder() { run_and_expect_no_errors("test_data/parser/inline/ok/closure_binder.rs"); } @@ -826,6 +842,10 @@ mod err { ); } #[test] + fn key_ident_cfg_predicate() { + run_and_expect_errors("test_data/parser/inline/err/key_ident_cfg_predicate.rs"); + } + #[test] fn let_else_right_curly_brace() { run_and_expect_errors("test_data/parser/inline/err/let_else_right_curly_brace.rs"); } diff --git a/crates/parser/test_data/parser/err/0005_attribute_recover.rast b/crates/parser/test_data/parser/err/0005_attribute_recover.rast index 77b4d06321..cf45dcf522 100644 --- a/crates/parser/test_data/parser/err/0005_attribute_recover.rast +++ b/crates/parser/test_data/parser/err/0005_attribute_recover.rast @@ -3,7 +3,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -37,7 +37,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/err/0032_match_arms_inner_attrs.rast b/crates/parser/test_data/parser/err/0032_match_arms_inner_attrs.rast index b657e98341..2334b730e4 100644 --- a/crates/parser/test_data/parser/err/0032_match_arms_inner_attrs.rast +++ b/crates/parser/test_data/parser/err/0032_match_arms_inner_attrs.rast @@ -136,15 +136,12 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "test" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE "\n " ATTR diff --git a/crates/parser/test_data/parser/err/0033_match_arms_outer_attrs.rast b/crates/parser/test_data/parser/err/0033_match_arms_outer_attrs.rast index b5bc3d84df..acacee2348 100644 --- a/crates/parser/test_data/parser/err/0033_match_arms_outer_attrs.rast +++ b/crates/parser/test_data/parser/err/0033_match_arms_outer_attrs.rast @@ -48,15 +48,12 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "test" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE "\n " R_CURLY "}" diff --git a/crates/parser/test_data/parser/inline/err/key_ident_cfg_predicate.rast b/crates/parser/test_data/parser/inline/err/key_ident_cfg_predicate.rast new file mode 100644 index 0000000000..de5fc7d5bd --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/key_ident_cfg_predicate.rast @@ -0,0 +1,19 @@ +SOURCE_FILE + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM + IDENT "key" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ERROR + IDENT "value" + R_PAREN ")" + R_BRACK "]" + WHITESPACE "\n" +error 13: expected a string literal diff --git a/crates/parser/test_data/parser/inline/err/key_ident_cfg_predicate.rs b/crates/parser/test_data/parser/inline/err/key_ident_cfg_predicate.rs new file mode 100644 index 0000000000..9a981bf939 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/key_ident_cfg_predicate.rs @@ -0,0 +1 @@ +#![cfg(key = value)] diff --git a/crates/parser/test_data/parser/inline/err/meta_recovery.rast b/crates/parser/test_data/parser/inline/err/meta_recovery.rast index b5c16e0798..9e456c9855 100644 --- a/crates/parser/test_data/parser/inline/err/meta_recovery.rast +++ b/crates/parser/test_data/parser/inline/err/meta_recovery.rast @@ -3,14 +3,14 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + PATH_META R_BRACK "]" WHITESPACE "\n" ATTR POUND "#" BANG "!" L_BRACK "[" - META + KEY_VALUE_META PATH PATH_SEGMENT NAME_REF @@ -24,7 +24,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + PATH_META PATH PATH PATH_SEGMENT @@ -37,7 +37,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + KEY_VALUE_META PATH PATH PATH_SEGMENT @@ -52,18 +52,20 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + UNSAFE_META UNSAFE_KW "unsafe" + PATH_META R_BRACK "]" WHITESPACE "\n" ATTR POUND "#" BANG "!" L_BRACK "[" - META + UNSAFE_META UNSAFE_KW "unsafe" WHITESPACE " " - EQ "=" + KEY_VALUE_META + EQ "=" R_BRACK "]" WHITESPACE "\n" error 3: expected identifier, `self`, `super`, `crate`, or `Self` @@ -77,7 +79,7 @@ error 41: expected L_PAREN error 41: expected identifier, `self`, `super`, `crate`, or `Self` error 41: expected R_PAREN error 52: expected L_PAREN -error 52: expected identifier, `self`, `super`, `crate`, or `Self` +error 53: expected identifier, `self`, `super`, `crate`, or `Self` error 54: expected expression error 54: expected expression error 54: expected R_PAREN diff --git a/crates/parser/test_data/parser/inline/ok/arg_with_attr.rast b/crates/parser/test_data/parser/inline/ok/arg_with_attr.rast index ae1074c368..672f2c2f7f 100644 --- a/crates/parser/test_data/parser/inline/ok/arg_with_attr.rast +++ b/crates/parser/test_data/parser/inline/ok/arg_with_attr.rast @@ -24,7 +24,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/array_attrs.rast b/crates/parser/test_data/parser/inline/ok/array_attrs.rast index 6eb8af3311..2812bbf71b 100644 --- a/crates/parser/test_data/parser/inline/ok/array_attrs.rast +++ b/crates/parser/test_data/parser/inline/ok/array_attrs.rast @@ -31,15 +31,12 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "test" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE " " INT_NUMBER "2" diff --git a/crates/parser/test_data/parser/inline/ok/assoc_item_list_inner_attrs.rast b/crates/parser/test_data/parser/inline/ok/assoc_item_list_inner_attrs.rast index 9cb3c8a5c3..7c6dbf65cf 100644 --- a/crates/parser/test_data/parser/inline/ok/assoc_item_list_inner_attrs.rast +++ b/crates/parser/test_data/parser/inline/ok/assoc_item_list_inner_attrs.rast @@ -15,7 +15,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/attr_on_expr_stmt.rast b/crates/parser/test_data/parser/inline/ok/attr_on_expr_stmt.rast index 81b7f2b3cb..248e6d1360 100644 --- a/crates/parser/test_data/parser/inline/ok/attr_on_expr_stmt.rast +++ b/crates/parser/test_data/parser/inline/ok/attr_on_expr_stmt.rast @@ -17,7 +17,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -39,7 +39,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -61,7 +61,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -71,7 +71,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -87,7 +87,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/cfg_attr.rast b/crates/parser/test_data/parser/inline/ok/cfg_attr.rast new file mode 100644 index 0000000000..9af94f447d --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/cfg_attr.rast @@ -0,0 +1,63 @@ +SOURCE_FILE + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + CFG_ATTR_META + CFG_ATTR_KW "cfg_attr" + L_PAREN "(" + CFG_COMPOSITE + IDENT "not" + L_PAREN "(" + CFG_ATOM + IDENT "foo" + R_PAREN ")" + COMMA "," + WHITESPACE " " + UNSAFE_META + UNSAFE_KW "unsafe" + L_PAREN "(" + TOKEN_TREE_META + PATH + PATH_SEGMENT + NAME_REF + IDENT "bar" + TOKEN_TREE + L_PAREN "(" + R_PAREN ")" + R_PAREN ")" + COMMA "," + WHITESPACE " " + CFG_ATTR_META + CFG_ATTR_KW "cfg_attr" + L_PAREN "(" + CFG_COMPOSITE + IDENT "all" + L_PAREN "(" + CFG_ATOM + TRUE_KW "true" + COMMA "," + WHITESPACE " " + CFG_ATOM + IDENT "foo" + WHITESPACE " " + EQ "=" + WHITESPACE " " + STRING "\"bar\"" + R_PAREN ")" + COMMA "," + WHITESPACE " " + KEY_VALUE_META + PATH + PATH_SEGMENT + NAME_REF + IDENT "baz" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + STRING "\"baz\"" + R_PAREN ")" + R_PAREN ")" + R_BRACK "]" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/cfg_attr.rs b/crates/parser/test_data/parser/inline/ok/cfg_attr.rs new file mode 100644 index 0000000000..5fe2776144 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/cfg_attr.rs @@ -0,0 +1 @@ +#![cfg_attr(not(foo), unsafe(bar()), cfg_attr(all(true, foo = "bar"), baz = "baz"))] diff --git a/crates/parser/test_data/parser/inline/ok/cfg_composite_pred.rast b/crates/parser/test_data/parser/inline/ok/cfg_composite_pred.rast new file mode 100644 index 0000000000..89d06d134f --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/cfg_composite_pred.rast @@ -0,0 +1,33 @@ +SOURCE_FILE + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_COMPOSITE + IDENT "any" + L_PAREN "(" + CFG_ATOM + IDENT "a" + COMMA "," + WHITESPACE " " + CFG_COMPOSITE + IDENT "all" + L_PAREN "(" + CFG_ATOM + IDENT "b" + WHITESPACE " " + EQ "=" + WHITESPACE " " + STRING "\"c\"" + COMMA "," + WHITESPACE " " + CFG_ATOM + IDENT "d" + R_PAREN ")" + R_PAREN ")" + R_PAREN ")" + R_BRACK "]" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/cfg_composite_pred.rs b/crates/parser/test_data/parser/inline/ok/cfg_composite_pred.rs new file mode 100644 index 0000000000..7d830c1288 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/cfg_composite_pred.rs @@ -0,0 +1 @@ +#![cfg(any(a, all(b = "c", d)))] diff --git a/crates/parser/test_data/parser/inline/ok/cfg_key_value_pred.rast b/crates/parser/test_data/parser/inline/ok/cfg_key_value_pred.rast new file mode 100644 index 0000000000..e48d39bf55 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/cfg_key_value_pred.rast @@ -0,0 +1,17 @@ +SOURCE_FILE + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM + IDENT "key" + WHITESPACE " " + EQ "=" + WHITESPACE " " + STRING "\"value\"" + R_PAREN ")" + R_BRACK "]" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/cfg_key_value_pred.rs b/crates/parser/test_data/parser/inline/ok/cfg_key_value_pred.rs new file mode 100644 index 0000000000..dc194ed86b --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/cfg_key_value_pred.rs @@ -0,0 +1 @@ +#![cfg(key = "value")] diff --git a/crates/parser/test_data/parser/inline/ok/cfg_meta.rast b/crates/parser/test_data/parser/inline/ok/cfg_meta.rast new file mode 100644 index 0000000000..f024cfd1aa --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/cfg_meta.rast @@ -0,0 +1,30 @@ +SOURCE_FILE + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM + IDENT "foo" + R_PAREN ")" + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM + IDENT "foo" + WHITESPACE " " + EQ "=" + WHITESPACE " " + STRING "\"bar\"" + COMMA "," + R_PAREN ")" + R_BRACK "]" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/cfg_meta.rs b/crates/parser/test_data/parser/inline/ok/cfg_meta.rs new file mode 100644 index 0000000000..ef0030e75f --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/cfg_meta.rs @@ -0,0 +1,2 @@ +#![cfg(foo)] +#![cfg(foo = "bar",)] diff --git a/crates/parser/test_data/parser/inline/ok/cfg_true_false_pred.rast b/crates/parser/test_data/parser/inline/ok/cfg_true_false_pred.rast new file mode 100644 index 0000000000..e33595a93d --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/cfg_true_false_pred.rast @@ -0,0 +1,25 @@ +SOURCE_FILE + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM + TRUE_KW "true" + R_PAREN ")" + R_BRACK "]" + WHITESPACE "\n" + ATTR + POUND "#" + BANG "!" + L_BRACK "[" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM + FALSE_KW "false" + R_PAREN ")" + R_BRACK "]" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/cfg_true_false_pred.rs b/crates/parser/test_data/parser/inline/ok/cfg_true_false_pred.rs new file mode 100644 index 0000000000..473582164a --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/cfg_true_false_pred.rs @@ -0,0 +1,2 @@ +#![cfg(true)] +#![cfg(false)] diff --git a/crates/parser/test_data/parser/inline/ok/generic_param_attribute.rast b/crates/parser/test_data/parser/inline/ok/generic_param_attribute.rast index 28a216e873..5567a53c56 100644 --- a/crates/parser/test_data/parser/inline/ok/generic_param_attribute.rast +++ b/crates/parser/test_data/parser/inline/ok/generic_param_attribute.rast @@ -10,7 +10,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -25,7 +25,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/match_arms_inner_attribute.rast b/crates/parser/test_data/parser/inline/ok/match_arms_inner_attribute.rast index 6fd9f42467..edb0438733 100644 --- a/crates/parser/test_data/parser/inline/ok/match_arms_inner_attribute.rast +++ b/crates/parser/test_data/parser/inline/ok/match_arms_inner_attribute.rast @@ -26,7 +26,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -41,7 +41,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -56,7 +56,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/match_arms_outer_attributes.rast b/crates/parser/test_data/parser/inline/ok/match_arms_outer_attributes.rast index 0f7580c1a3..321db782d1 100644 --- a/crates/parser/test_data/parser/inline/ok/match_arms_outer_attributes.rast +++ b/crates/parser/test_data/parser/inline/ok/match_arms_outer_attributes.rast @@ -26,19 +26,16 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "feature" WHITESPACE " " EQ "=" WHITESPACE " " STRING "\"some\"" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE "\n " WILDCARD_PAT @@ -55,19 +52,16 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "feature" WHITESPACE " " EQ "=" WHITESPACE " " STRING "\"other\"" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE "\n " WILDCARD_PAT @@ -84,55 +78,46 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "feature" WHITESPACE " " EQ "=" WHITESPACE " " STRING "\"many\"" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE "\n " ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "feature" WHITESPACE " " EQ "=" WHITESPACE " " STRING "\"attributes\"" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE "\n " ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "feature" WHITESPACE " " EQ "=" WHITESPACE " " STRING "\"before\"" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE "\n " WILDCARD_PAT diff --git a/crates/parser/test_data/parser/inline/ok/metas.rast b/crates/parser/test_data/parser/inline/ok/metas.rast index b1ac60b530..6360552a6f 100644 --- a/crates/parser/test_data/parser/inline/ok/metas.rast +++ b/crates/parser/test_data/parser/inline/ok/metas.rast @@ -3,7 +3,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -14,7 +14,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + PATH_META PATH PATH PATH_SEGMENT @@ -30,7 +30,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + KEY_VALUE_META PATH PATH_SEGMENT NAME_REF @@ -46,7 +46,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + KEY_VALUE_META PATH PATH PATH @@ -72,7 +72,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -91,7 +91,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -110,7 +110,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -129,7 +129,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH PATH @@ -158,7 +158,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH PATH @@ -187,7 +187,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH PATH @@ -216,13 +216,14 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + UNSAFE_META UNSAFE_KW "unsafe" L_PAREN "(" - PATH - PATH_SEGMENT - NAME_REF - IDENT "simple_ident" + PATH_META + PATH + PATH_SEGMENT + NAME_REF + IDENT "simple_ident" R_PAREN ")" R_BRACK "]" WHITESPACE "\n" @@ -230,18 +231,19 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + UNSAFE_META UNSAFE_KW "unsafe" L_PAREN "(" - PATH + PATH_META PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "simple" + COLON2 "::" PATH_SEGMENT NAME_REF - IDENT "simple" - COLON2 "::" - PATH_SEGMENT - NAME_REF - IDENT "path" + IDENT "path" R_PAREN ")" R_BRACK "]" WHITESPACE "\n" @@ -249,18 +251,19 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + UNSAFE_META UNSAFE_KW "unsafe" L_PAREN "(" - PATH - PATH_SEGMENT - NAME_REF - IDENT "simple_ident_expr" - WHITESPACE " " - EQ "=" - WHITESPACE " " - LITERAL - STRING "\"\"" + KEY_VALUE_META + PATH + PATH_SEGMENT + NAME_REF + IDENT "simple_ident_expr" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + STRING "\"\"" R_PAREN ")" R_BRACK "]" WHITESPACE "\n" @@ -268,28 +271,29 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + UNSAFE_META UNSAFE_KW "unsafe" L_PAREN "(" - PATH + KEY_VALUE_META PATH PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "simple" + COLON2 "::" PATH_SEGMENT NAME_REF - IDENT "simple" + IDENT "path" COLON2 "::" PATH_SEGMENT NAME_REF - IDENT "path" - COLON2 "::" - PATH_SEGMENT - NAME_REF - IDENT "Expr" - WHITESPACE " " - EQ "=" - WHITESPACE " " - LITERAL - STRING "\"\"" + IDENT "Expr" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + STRING "\"\"" R_PAREN ")" R_BRACK "]" WHITESPACE "\n" @@ -297,21 +301,22 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + UNSAFE_META UNSAFE_KW "unsafe" L_PAREN "(" - PATH - PATH_SEGMENT - NAME_REF - IDENT "simple_ident_tt" - TOKEN_TREE - L_PAREN "(" - IDENT "a" - WHITESPACE " " - IDENT "b" - WHITESPACE " " - IDENT "c" - R_PAREN ")" + TOKEN_TREE_META + PATH + PATH_SEGMENT + NAME_REF + IDENT "simple_ident_tt" + TOKEN_TREE + L_PAREN "(" + IDENT "a" + WHITESPACE " " + IDENT "b" + WHITESPACE " " + IDENT "c" + R_PAREN ")" R_PAREN ")" R_BRACK "]" WHITESPACE "\n" @@ -319,21 +324,22 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + UNSAFE_META UNSAFE_KW "unsafe" L_PAREN "(" - PATH - PATH_SEGMENT - NAME_REF - IDENT "simple_ident_tt" - TOKEN_TREE - L_BRACK "[" - IDENT "a" - WHITESPACE " " - IDENT "b" - WHITESPACE " " - IDENT "c" - R_BRACK "]" + TOKEN_TREE_META + PATH + PATH_SEGMENT + NAME_REF + IDENT "simple_ident_tt" + TOKEN_TREE + L_BRACK "[" + IDENT "a" + WHITESPACE " " + IDENT "b" + WHITESPACE " " + IDENT "c" + R_BRACK "]" R_PAREN ")" R_BRACK "]" WHITESPACE "\n" @@ -341,21 +347,22 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + UNSAFE_META UNSAFE_KW "unsafe" L_PAREN "(" - PATH - PATH_SEGMENT - NAME_REF - IDENT "simple_ident_tt" - TOKEN_TREE - L_CURLY "{" - IDENT "a" - WHITESPACE " " - IDENT "b" - WHITESPACE " " - IDENT "c" - R_CURLY "}" + TOKEN_TREE_META + PATH + PATH_SEGMENT + NAME_REF + IDENT "simple_ident_tt" + TOKEN_TREE + L_CURLY "{" + IDENT "a" + WHITESPACE " " + IDENT "b" + WHITESPACE " " + IDENT "c" + R_CURLY "}" R_PAREN ")" R_BRACK "]" WHITESPACE "\n" @@ -363,31 +370,32 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + UNSAFE_META UNSAFE_KW "unsafe" L_PAREN "(" - PATH + TOKEN_TREE_META PATH PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "simple" + COLON2 "::" PATH_SEGMENT NAME_REF - IDENT "simple" + IDENT "path" COLON2 "::" PATH_SEGMENT NAME_REF - IDENT "path" - COLON2 "::" - PATH_SEGMENT - NAME_REF - IDENT "tt" - TOKEN_TREE - L_PAREN "(" - IDENT "a" - WHITESPACE " " - IDENT "b" - WHITESPACE " " - IDENT "c" - R_PAREN ")" + IDENT "tt" + TOKEN_TREE + L_PAREN "(" + IDENT "a" + WHITESPACE " " + IDENT "b" + WHITESPACE " " + IDENT "c" + R_PAREN ")" R_PAREN ")" R_BRACK "]" WHITESPACE "\n" @@ -395,31 +403,32 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + UNSAFE_META UNSAFE_KW "unsafe" L_PAREN "(" - PATH + TOKEN_TREE_META PATH PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "simple" + COLON2 "::" PATH_SEGMENT NAME_REF - IDENT "simple" + IDENT "path" COLON2 "::" PATH_SEGMENT NAME_REF - IDENT "path" - COLON2 "::" - PATH_SEGMENT - NAME_REF - IDENT "tt" - TOKEN_TREE - L_BRACK "[" - IDENT "a" - WHITESPACE " " - IDENT "b" - WHITESPACE " " - IDENT "c" - R_BRACK "]" + IDENT "tt" + TOKEN_TREE + L_BRACK "[" + IDENT "a" + WHITESPACE " " + IDENT "b" + WHITESPACE " " + IDENT "c" + R_BRACK "]" R_PAREN ")" R_BRACK "]" WHITESPACE "\n" @@ -427,31 +436,32 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + UNSAFE_META UNSAFE_KW "unsafe" L_PAREN "(" - PATH + TOKEN_TREE_META PATH PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "simple" + COLON2 "::" PATH_SEGMENT NAME_REF - IDENT "simple" + IDENT "path" COLON2 "::" PATH_SEGMENT NAME_REF - IDENT "path" - COLON2 "::" - PATH_SEGMENT - NAME_REF - IDENT "tt" - TOKEN_TREE - L_CURLY "{" - IDENT "a" - WHITESPACE " " - IDENT "b" - WHITESPACE " " - IDENT "c" - R_CURLY "}" + IDENT "tt" + TOKEN_TREE + L_CURLY "{" + IDENT "a" + WHITESPACE " " + IDENT "b" + WHITESPACE " " + IDENT "c" + R_CURLY "}" R_PAREN ")" R_BRACK "]" WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/param_outer_arg.rast b/crates/parser/test_data/parser/inline/ok/param_outer_arg.rast index c63ea020a3..7495ba7b31 100644 --- a/crates/parser/test_data/parser/inline/ok/param_outer_arg.rast +++ b/crates/parser/test_data/parser/inline/ok/param_outer_arg.rast @@ -10,7 +10,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/record_field_attrs.rast b/crates/parser/test_data/parser/inline/ok/record_field_attrs.rast index 639ee0eb77..cfa2694fd1 100644 --- a/crates/parser/test_data/parser/inline/ok/record_field_attrs.rast +++ b/crates/parser/test_data/parser/inline/ok/record_field_attrs.rast @@ -12,7 +12,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/record_literal_field_with_attr.rast b/crates/parser/test_data/parser/inline/ok/record_literal_field_with_attr.rast index a1df70841e..717dee8c9f 100644 --- a/crates/parser/test_data/parser/inline/ok/record_literal_field_with_attr.rast +++ b/crates/parser/test_data/parser/inline/ok/record_literal_field_with_attr.rast @@ -25,15 +25,12 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "test" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE " " NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/record_pat_field.rast b/crates/parser/test_data/parser/inline/ok/record_pat_field.rast index f3d2fde466..7fba529be9 100644 --- a/crates/parser/test_data/parser/inline/ok/record_pat_field.rast +++ b/crates/parser/test_data/parser/inline/ok/record_pat_field.rast @@ -88,18 +88,14 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_COMPOSITE IDENT "any" - TOKEN_TREE - L_PAREN "(" - R_PAREN ")" + L_PAREN "(" R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE " " NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/record_pat_field_list.rast b/crates/parser/test_data/parser/inline/ok/record_pat_field_list.rast index f69ae1d644..af5b82b889 100644 --- a/crates/parser/test_data/parser/inline/ok/record_pat_field_list.rast +++ b/crates/parser/test_data/parser/inline/ok/record_pat_field_list.rast @@ -146,18 +146,14 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_COMPOSITE IDENT "any" - TOKEN_TREE - L_PAREN "(" - R_PAREN ")" + L_PAREN "(" R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE " " DOT2 ".." diff --git a/crates/parser/test_data/parser/inline/ok/self_param_outer_attr.rast b/crates/parser/test_data/parser/inline/ok/self_param_outer_attr.rast index db583f7d52..3a163e5b8e 100644 --- a/crates/parser/test_data/parser/inline/ok/self_param_outer_attr.rast +++ b/crates/parser/test_data/parser/inline/ok/self_param_outer_attr.rast @@ -10,7 +10,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/inline/ok/tuple_attrs.rast b/crates/parser/test_data/parser/inline/ok/tuple_attrs.rast index 39857b23c6..76954927d5 100644 --- a/crates/parser/test_data/parser/inline/ok/tuple_attrs.rast +++ b/crates/parser/test_data/parser/inline/ok/tuple_attrs.rast @@ -34,15 +34,12 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "test" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE " " INT_NUMBER "2" diff --git a/crates/parser/test_data/parser/inline/ok/tuple_field_attrs.rast b/crates/parser/test_data/parser/inline/ok/tuple_field_attrs.rast index 1699602f4f..1f7100c46d 100644 --- a/crates/parser/test_data/parser/inline/ok/tuple_field_attrs.rast +++ b/crates/parser/test_data/parser/inline/ok/tuple_field_attrs.rast @@ -11,7 +11,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0006_inner_attributes.rast b/crates/parser/test_data/parser/ok/0006_inner_attributes.rast index cb63ba80e7..ddab028c06 100644 --- a/crates/parser/test_data/parser/ok/0006_inner_attributes.rast +++ b/crates/parser/test_data/parser/ok/0006_inner_attributes.rast @@ -3,7 +3,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -14,7 +14,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -29,7 +29,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -44,7 +44,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -89,7 +89,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -104,7 +104,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -123,7 +123,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -138,7 +138,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -153,7 +153,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -175,7 +175,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0008_mod_item.rast b/crates/parser/test_data/parser/ok/0008_mod_item.rast index adee67181b..cb6da8717d 100644 --- a/crates/parser/test_data/parser/ok/0008_mod_item.rast +++ b/crates/parser/test_data/parser/ok/0008_mod_item.rast @@ -48,7 +48,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0011_outer_attribute.rast b/crates/parser/test_data/parser/ok/0011_outer_attribute.rast index dbb9bc54da..47a5cfce08 100644 --- a/crates/parser/test_data/parser/ok/0011_outer_attribute.rast +++ b/crates/parser/test_data/parser/ok/0011_outer_attribute.rast @@ -3,21 +3,18 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "test" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE "\n" ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -41,7 +38,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + KEY_VALUE_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0017_attr_trailing_comma.rast b/crates/parser/test_data/parser/ok/0017_attr_trailing_comma.rast index 7c914e2542..c5d054702f 100644 --- a/crates/parser/test_data/parser/ok/0017_attr_trailing_comma.rast +++ b/crates/parser/test_data/parser/ok/0017_attr_trailing_comma.rast @@ -3,7 +3,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0035_weird_exprs.rast b/crates/parser/test_data/parser/ok/0035_weird_exprs.rast index 318d492ab4..15ce6c70be 100644 --- a/crates/parser/test_data/parser/ok/0035_weird_exprs.rast +++ b/crates/parser/test_data/parser/ok/0035_weird_exprs.rast @@ -11,7 +11,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -26,7 +26,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -41,7 +41,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -56,7 +56,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -71,7 +71,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + KEY_VALUE_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0044_let_attrs.rast b/crates/parser/test_data/parser/ok/0044_let_attrs.rast index f3c20337e4..fcdc1a9895 100644 --- a/crates/parser/test_data/parser/ok/0044_let_attrs.rast +++ b/crates/parser/test_data/parser/ok/0044_let_attrs.rast @@ -18,19 +18,16 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "feature" WHITESPACE " " EQ "=" WHITESPACE " " STRING "\"backtrace\"" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE "\n " LET_KW "let" diff --git a/crates/parser/test_data/parser/ok/0045_block_attrs.rast b/crates/parser/test_data/parser/ok/0045_block_attrs.rast index c22d99f1ae..f26bb85df2 100644 --- a/crates/parser/test_data/parser/ok/0045_block_attrs.rast +++ b/crates/parser/test_data/parser/ok/0045_block_attrs.rast @@ -16,7 +16,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -38,7 +38,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -53,7 +53,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -77,7 +77,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -119,7 +119,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF @@ -211,7 +211,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0046_extern_inner_attributes.rast b/crates/parser/test_data/parser/ok/0046_extern_inner_attributes.rast index 4eb51cfdf0..3d33eb4ff7 100644 --- a/crates/parser/test_data/parser/ok/0046_extern_inner_attributes.rast +++ b/crates/parser/test_data/parser/ok/0046_extern_inner_attributes.rast @@ -14,7 +14,7 @@ SOURCE_FILE POUND "#" BANG "!" L_BRACK "[" - META + TOKEN_TREE_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0051_parameter_attrs.rast b/crates/parser/test_data/parser/ok/0051_parameter_attrs.rast index eafee90db4..24d4392282 100644 --- a/crates/parser/test_data/parser/ok/0051_parameter_attrs.rast +++ b/crates/parser/test_data/parser/ok/0051_parameter_attrs.rast @@ -10,7 +10,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -20,7 +20,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -55,7 +55,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -116,7 +116,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -158,7 +158,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -181,7 +181,7 @@ SOURCE_FILE POUND "#" WHITESPACE " " L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -228,7 +228,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -255,7 +255,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -282,7 +282,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -316,7 +316,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -352,7 +352,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -389,7 +389,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -422,7 +422,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -456,7 +456,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rast b/crates/parser/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rast index b94d43beb3..c300b7af50 100644 --- a/crates/parser/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rast +++ b/crates/parser/test_data/parser/ok/0053_outer_attribute_on_macro_rules.rast @@ -5,7 +5,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0062_macro_2.0.rast b/crates/parser/test_data/parser/ok/0062_macro_2.0.rast index 1415a866b6..b92d78e5bd 100644 --- a/crates/parser/test_data/parser/ok/0062_macro_2.0.rast +++ b/crates/parser/test_data/parser/ok/0062_macro_2.0.rast @@ -54,7 +54,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0063_variadic_fun.rast b/crates/parser/test_data/parser/ok/0063_variadic_fun.rast index e36399123b..f1c6d2efeb 100644 --- a/crates/parser/test_data/parser/ok/0063_variadic_fun.rast +++ b/crates/parser/test_data/parser/ok/0063_variadic_fun.rast @@ -96,15 +96,12 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META - PATH - PATH_SEGMENT - NAME_REF - IDENT "cfg" - TOKEN_TREE - L_PAREN "(" + CFG_META + CFG_KW "cfg" + L_PAREN "(" + CFG_ATOM IDENT "never" - R_PAREN ")" + R_PAREN ")" R_BRACK "]" WHITESPACE " " SLICE_PAT diff --git a/crates/parser/test_data/parser/ok/0070_expr_attr_placement.rast b/crates/parser/test_data/parser/ok/0070_expr_attr_placement.rast index 3d00b27ab8..5229b97eb2 100644 --- a/crates/parser/test_data/parser/ok/0070_expr_attr_placement.rast +++ b/crates/parser/test_data/parser/ok/0070_expr_attr_placement.rast @@ -19,7 +19,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -39,7 +39,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/parser/test_data/parser/ok/0071_stmt_attr_placement.rast b/crates/parser/test_data/parser/ok/0071_stmt_attr_placement.rast index 1cafc775cd..c0685448f2 100644 --- a/crates/parser/test_data/parser/ok/0071_stmt_attr_placement.rast +++ b/crates/parser/test_data/parser/ok/0071_stmt_attr_placement.rast @@ -17,7 +17,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -31,7 +31,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF @@ -56,7 +56,7 @@ SOURCE_FILE ATTR POUND "#" L_BRACK "[" - META + PATH_META PATH PATH_SEGMENT NAME_REF diff --git a/crates/rust-analyzer/src/target_spec.rs b/crates/rust-analyzer/src/target_spec.rs index 01196b80cd..5bdc9d8ca3 100644 --- a/crates/rust-analyzer/src/target_spec.rs +++ b/crates/rust-analyzer/src/target_spec.rs @@ -381,23 +381,13 @@ mod tests { SmolStr, ast::{self, AstNode}, }; - use syntax_bridge::{ - DocCommentDesugarMode, - dummy_test_span_utils::{DUMMY, DummyTestSpanMap}, - syntax_node_to_token_tree, - }; fn check(cfg: &str, expected_features: &[&str]) { let cfg_expr = { let source_file = ast::SourceFile::parse(cfg, Edition::CURRENT).ok().unwrap(); - let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); - let tt = syntax_node_to_token_tree( - tt.syntax(), - &DummyTestSpanMap, - DUMMY, - DocCommentDesugarMode::Mbe, - ); - CfgExpr::parse(&tt) + let cfg_predicate = + source_file.syntax().descendants().find_map(ast::CfgPredicate::cast).unwrap(); + CfgExpr::parse_from_ast(cfg_predicate) }; let mut features = vec![]; diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml index 8909fb423c..e65836ed8d 100644 --- a/crates/syntax/Cargo.toml +++ b/crates/syntax/Cargo.toml @@ -21,6 +21,7 @@ rustc-literal-escaper.workspace = true smol_str.workspace = true triomphe.workspace = true tracing.workspace = true +smallvec.workspace = true parser.workspace = true stdx.workspace = true diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram index 3113fc7430..324b2bbd58 100644 --- a/crates/syntax/rust.ungram +++ b/crates/syntax/rust.ungram @@ -126,9 +126,41 @@ MacroStmts = Attr = '#' '!'? '[' Meta ']' +CfgAttrMeta = + 'cfg_attr' '(' CfgPredicate ',' (Meta (',' Meta)* ','?) ')' + +CfgMeta = + 'cfg' '(' CfgPredicate ','? ')' + +CfgPredicate = + CfgAtom +| CfgComposite + +CfgAtom = + ('#ident' | 'true' | 'false') ('=' '@string')? + +CfgComposite = + keyword:'#ident' '(' (CfgPredicate (',' CfgPredicate)* ','?) ')' + Meta = - 'unsafe' '(' Path ('=' Expr | TokenTree)? ')' -| Path ('=' Expr | TokenTree)? + CfgAttrMeta +| CfgMeta +| UnsafeMeta +| PathMeta +| KeyValueMeta +| TokenTreeMeta + +UnsafeMeta = + 'unsafe' '(' Meta ')' + +PathMeta = + Path + +KeyValueMeta = + Path '=' Expr + +TokenTreeMeta = + Path TokenTree //*************************// // Items // diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs index 5d67fd4491..dc592a4372 100644 --- a/crates/syntax/src/ast.rs +++ b/crates/syntax/src/ast.rs @@ -25,9 +25,9 @@ pub use self::{ expr_ext::{ArrayExprKind, BlockModifier, CallableExpr, ElseBranch, LiteralKind}, generated::{nodes::*, tokens::*}, node_ext::{ - AttrKind, FieldKind, Macro, NameLike, NameOrNameRef, PathSegmentKind, SelfParamKind, - SlicePatComponents, StructKind, TokenTreeChildren, TypeBoundKind, TypeOrConstParam, - VisibilityKind, + AttrKind, CfgAtomKey, FieldKind, Macro, NameLike, NameOrNameRef, PathSegmentKind, + SelfParamKind, SlicePatComponents, StructKind, TokenTreeChildren, TypeBoundKind, + TypeOrConstParam, VisibilityKind, }, operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp}, token_ext::{ diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index 7334de0fd9..cd7f6a018a 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -377,6 +377,68 @@ impl CastExpr { #[inline] pub fn as_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![as]) } } +pub struct CfgAtom { + pub(crate) syntax: SyntaxNode, +} +impl CfgAtom { + #[inline] + pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) } + #[inline] + pub fn false_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![false]) } + #[inline] + pub fn ident_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![ident]) } + #[inline] + pub fn string_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![string]) } + #[inline] + pub fn true_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![true]) } +} +pub struct CfgAttrMeta { + pub(crate) syntax: SyntaxNode, +} +impl CfgAttrMeta { + #[inline] + pub fn cfg_predicate(&self) -> Option<CfgPredicate> { support::child(&self.syntax) } + #[inline] + pub fn metas(&self) -> AstChildren<Meta> { support::children(&self.syntax) } + #[inline] + pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) } + #[inline] + pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) } + #[inline] + pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) } + #[inline] + pub fn cfg_attr_token(&self) -> Option<SyntaxToken> { + support::token(&self.syntax, T![cfg_attr]) + } +} +pub struct CfgComposite { + pub(crate) syntax: SyntaxNode, +} +impl CfgComposite { + #[inline] + pub fn cfg_predicates(&self) -> AstChildren<CfgPredicate> { support::children(&self.syntax) } + #[inline] + pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) } + #[inline] + pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) } + #[inline] + pub fn keyword(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![ident]) } +} +pub struct CfgMeta { + pub(crate) syntax: SyntaxNode, +} +impl CfgMeta { + #[inline] + pub fn cfg_predicate(&self) -> Option<CfgPredicate> { support::child(&self.syntax) } + #[inline] + pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) } + #[inline] + pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) } + #[inline] + pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) } + #[inline] + pub fn cfg_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![cfg]) } +} pub struct ClosureExpr { pub(crate) syntax: SyntaxNode, } @@ -783,6 +845,17 @@ impl ItemList { #[inline] pub fn r_curly_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['}']) } } +pub struct KeyValueMeta { + pub(crate) syntax: SyntaxNode, +} +impl KeyValueMeta { + #[inline] + pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) } + #[inline] + pub fn path(&self) -> Option<Path> { support::child(&self.syntax) } + #[inline] + pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) } +} pub struct Label { pub(crate) syntax: SyntaxNode, } @@ -1012,25 +1085,6 @@ impl MatchGuard { #[inline] pub fn if_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![if]) } } -pub struct Meta { - pub(crate) syntax: SyntaxNode, -} -impl Meta { - #[inline] - pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) } - #[inline] - pub fn path(&self) -> Option<Path> { support::child(&self.syntax) } - #[inline] - pub fn token_tree(&self) -> Option<TokenTree> { support::child(&self.syntax) } - #[inline] - pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) } - #[inline] - pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) } - #[inline] - pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) } - #[inline] - pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) } -} pub struct MethodCallExpr { pub(crate) syntax: SyntaxNode, } @@ -1225,6 +1279,13 @@ impl PathExpr { #[inline] pub fn path(&self) -> Option<Path> { support::child(&self.syntax) } } +pub struct PathMeta { + pub(crate) syntax: SyntaxNode, +} +impl PathMeta { + #[inline] + pub fn path(&self) -> Option<Path> { support::child(&self.syntax) } +} pub struct PathPat { pub(crate) syntax: SyntaxNode, } @@ -1607,6 +1668,15 @@ impl TokenTree { #[inline] pub fn r_curly_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['}']) } } +pub struct TokenTreeMeta { + pub(crate) syntax: SyntaxNode, +} +impl TokenTreeMeta { + #[inline] + pub fn path(&self) -> Option<Path> { support::child(&self.syntax) } + #[inline] + pub fn token_tree(&self) -> Option<TokenTree> { support::child(&self.syntax) } +} pub struct Trait { pub(crate) syntax: SyntaxNode, } @@ -1834,6 +1904,19 @@ impl Union { #[inline] pub fn union_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![union]) } } +pub struct UnsafeMeta { + pub(crate) syntax: SyntaxNode, +} +impl UnsafeMeta { + #[inline] + pub fn meta(&self) -> Option<Meta> { support::child(&self.syntax) } + #[inline] + pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) } + #[inline] + pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) } + #[inline] + pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) } +} pub struct Use { pub(crate) syntax: SyntaxNode, } @@ -2025,6 +2108,12 @@ impl ast::HasAttrs for AssocItem {} impl ast::HasDocComments for AssocItem {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum CfgPredicate { + CfgAtom(CfgAtom), + CfgComposite(CfgComposite), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Expr { ArrayExpr(ArrayExpr), AsmExpr(AsmExpr), @@ -2119,6 +2208,16 @@ pub enum Item { impl ast::HasAttrs for Item {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Meta { + CfgAttrMeta(CfgAttrMeta), + CfgMeta(CfgMeta), + KeyValueMeta(KeyValueMeta), + PathMeta(PathMeta), + TokenTreeMeta(TokenTreeMeta), + UnsafeMeta(UnsafeMeta), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Pat { BoxPat(BoxPat), ConstBlockPat(ConstBlockPat), @@ -3133,6 +3232,134 @@ impl fmt::Debug for CastExpr { f.debug_struct("CastExpr").field("syntax", &self.syntax).finish() } } +impl AstNode for CfgAtom { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + CFG_ATOM + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == CFG_ATOM } + #[inline] + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for CfgAtom { + fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for CfgAtom {} +impl PartialEq for CfgAtom { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for CfgAtom { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for CfgAtom { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CfgAtom").field("syntax", &self.syntax).finish() + } +} +impl AstNode for CfgAttrMeta { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + CFG_ATTR_META + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == CFG_ATTR_META } + #[inline] + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for CfgAttrMeta { + fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for CfgAttrMeta {} +impl PartialEq for CfgAttrMeta { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for CfgAttrMeta { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for CfgAttrMeta { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CfgAttrMeta").field("syntax", &self.syntax).finish() + } +} +impl AstNode for CfgComposite { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + CFG_COMPOSITE + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == CFG_COMPOSITE } + #[inline] + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for CfgComposite { + fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for CfgComposite {} +impl PartialEq for CfgComposite { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for CfgComposite { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for CfgComposite { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CfgComposite").field("syntax", &self.syntax).finish() + } +} +impl AstNode for CfgMeta { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + CFG_META + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == CFG_META } + #[inline] + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for CfgMeta { + fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for CfgMeta {} +impl PartialEq for CfgMeta { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for CfgMeta { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for CfgMeta { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CfgMeta").field("syntax", &self.syntax).finish() + } +} impl AstNode for ClosureExpr { #[inline] fn kind() -> SyntaxKind @@ -4093,6 +4320,38 @@ impl fmt::Debug for ItemList { f.debug_struct("ItemList").field("syntax", &self.syntax).finish() } } +impl AstNode for KeyValueMeta { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + KEY_VALUE_META + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == KEY_VALUE_META } + #[inline] + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for KeyValueMeta { + fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for KeyValueMeta {} +impl PartialEq for KeyValueMeta { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for KeyValueMeta { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for KeyValueMeta { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("KeyValueMeta").field("syntax", &self.syntax).finish() + } +} impl AstNode for Label { #[inline] fn kind() -> SyntaxKind @@ -4797,38 +5056,6 @@ impl fmt::Debug for MatchGuard { f.debug_struct("MatchGuard").field("syntax", &self.syntax).finish() } } -impl AstNode for Meta { - #[inline] - fn kind() -> SyntaxKind - where - Self: Sized, - { - META - } - #[inline] - fn can_cast(kind: SyntaxKind) -> bool { kind == META } - #[inline] - fn cast(syntax: SyntaxNode) -> Option<Self> { - if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } - } - #[inline] - fn syntax(&self) -> &SyntaxNode { &self.syntax } -} -impl hash::Hash for Meta { - fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); } -} -impl Eq for Meta {} -impl PartialEq for Meta { - fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } -} -impl Clone for Meta { - fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } -} -impl fmt::Debug for Meta { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Meta").field("syntax", &self.syntax).finish() - } -} impl AstNode for MethodCallExpr { #[inline] fn kind() -> SyntaxKind @@ -5309,6 +5536,38 @@ impl fmt::Debug for PathExpr { f.debug_struct("PathExpr").field("syntax", &self.syntax).finish() } } +impl AstNode for PathMeta { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + PATH_META + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == PATH_META } + #[inline] + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for PathMeta { + fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for PathMeta {} +impl PartialEq for PathMeta { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for PathMeta { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for PathMeta { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PathMeta").field("syntax", &self.syntax).finish() + } +} impl AstNode for PathPat { #[inline] fn kind() -> SyntaxKind @@ -6301,6 +6560,38 @@ impl fmt::Debug for TokenTree { f.debug_struct("TokenTree").field("syntax", &self.syntax).finish() } } +impl AstNode for TokenTreeMeta { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + TOKEN_TREE_META + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == TOKEN_TREE_META } + #[inline] + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for TokenTreeMeta { + fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for TokenTreeMeta {} +impl PartialEq for TokenTreeMeta { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for TokenTreeMeta { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for TokenTreeMeta { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TokenTreeMeta").field("syntax", &self.syntax).finish() + } +} impl AstNode for Trait { #[inline] fn kind() -> SyntaxKind @@ -6845,6 +7136,38 @@ impl fmt::Debug for Union { f.debug_struct("Union").field("syntax", &self.syntax).finish() } } +impl AstNode for UnsafeMeta { + #[inline] + fn kind() -> SyntaxKind + where + Self: Sized, + { + UNSAFE_META + } + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { kind == UNSAFE_META } + #[inline] + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { Some(Self { syntax }) } else { None } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} +impl hash::Hash for UnsafeMeta { + fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); } +} +impl Eq for UnsafeMeta {} +impl PartialEq for UnsafeMeta { + fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax } +} +impl Clone for UnsafeMeta { + fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } } +} +impl fmt::Debug for UnsafeMeta { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("UnsafeMeta").field("syntax", &self.syntax).finish() + } +} impl AstNode for Use { #[inline] fn kind() -> SyntaxKind @@ -7413,6 +7736,34 @@ impl AstNode for AssocItem { } } } +impl From<CfgAtom> for CfgPredicate { + #[inline] + fn from(node: CfgAtom) -> CfgPredicate { CfgPredicate::CfgAtom(node) } +} +impl From<CfgComposite> for CfgPredicate { + #[inline] + fn from(node: CfgComposite) -> CfgPredicate { CfgPredicate::CfgComposite(node) } +} +impl AstNode for CfgPredicate { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { matches!(kind, CFG_ATOM | CFG_COMPOSITE) } + #[inline] + fn cast(syntax: SyntaxNode) -> Option<Self> { + let res = match syntax.kind() { + CFG_ATOM => CfgPredicate::CfgAtom(CfgAtom { syntax }), + CFG_COMPOSITE => CfgPredicate::CfgComposite(CfgComposite { syntax }), + _ => return None, + }; + Some(res) + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + match self { + CfgPredicate::CfgAtom(it) => &it.syntax, + CfgPredicate::CfgComposite(it) => &it.syntax, + } + } +} impl From<ArrayExpr> for Expr { #[inline] fn from(node: ArrayExpr) -> Expr { Expr::ArrayExpr(node) } @@ -7970,6 +8321,63 @@ impl AstNode for Item { } } } +impl From<CfgAttrMeta> for Meta { + #[inline] + fn from(node: CfgAttrMeta) -> Meta { Meta::CfgAttrMeta(node) } +} +impl From<CfgMeta> for Meta { + #[inline] + fn from(node: CfgMeta) -> Meta { Meta::CfgMeta(node) } +} +impl From<KeyValueMeta> for Meta { + #[inline] + fn from(node: KeyValueMeta) -> Meta { Meta::KeyValueMeta(node) } +} +impl From<PathMeta> for Meta { + #[inline] + fn from(node: PathMeta) -> Meta { Meta::PathMeta(node) } +} +impl From<TokenTreeMeta> for Meta { + #[inline] + fn from(node: TokenTreeMeta) -> Meta { Meta::TokenTreeMeta(node) } +} +impl From<UnsafeMeta> for Meta { + #[inline] + fn from(node: UnsafeMeta) -> Meta { Meta::UnsafeMeta(node) } +} +impl AstNode for Meta { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + matches!( + kind, + CFG_ATTR_META | CFG_META | KEY_VALUE_META | PATH_META | TOKEN_TREE_META | UNSAFE_META + ) + } + #[inline] + fn cast(syntax: SyntaxNode) -> Option<Self> { + let res = match syntax.kind() { + CFG_ATTR_META => Meta::CfgAttrMeta(CfgAttrMeta { syntax }), + CFG_META => Meta::CfgMeta(CfgMeta { syntax }), + KEY_VALUE_META => Meta::KeyValueMeta(KeyValueMeta { syntax }), + PATH_META => Meta::PathMeta(PathMeta { syntax }), + TOKEN_TREE_META => Meta::TokenTreeMeta(TokenTreeMeta { syntax }), + UNSAFE_META => Meta::UnsafeMeta(UnsafeMeta { syntax }), + _ => return None, + }; + Some(res) + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + match self { + Meta::CfgAttrMeta(it) => &it.syntax, + Meta::CfgMeta(it) => &it.syntax, + Meta::KeyValueMeta(it) => &it.syntax, + Meta::PathMeta(it) => &it.syntax, + Meta::TokenTreeMeta(it) => &it.syntax, + Meta::UnsafeMeta(it) => &it.syntax, + } + } +} impl From<BoxPat> for Pat { #[inline] fn from(node: BoxPat) -> Pat { Pat::BoxPat(node) } @@ -9334,6 +9742,11 @@ impl std::fmt::Display for AssocItem { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for CfgPredicate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Expr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -9364,6 +9777,11 @@ impl std::fmt::Display for Item { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for Meta { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Pat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -9524,6 +9942,26 @@ impl std::fmt::Display for CastExpr { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for CfgAtom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for CfgAttrMeta { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for CfgComposite { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for CfgMeta { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for ClosureExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -9674,6 +10112,11 @@ impl std::fmt::Display for ItemList { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for KeyValueMeta { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Label { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -9784,11 +10227,6 @@ impl std::fmt::Display for MatchGuard { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for Meta { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(self.syntax(), f) - } -} impl std::fmt::Display for MethodCallExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -9864,6 +10302,11 @@ impl std::fmt::Display for PathExpr { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for PathMeta { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for PathPat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -10019,6 +10462,11 @@ impl std::fmt::Display for TokenTree { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for TokenTreeMeta { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Trait { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -10104,6 +10552,11 @@ impl std::fmt::Display for Union { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for UnsafeMeta { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for Use { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 00971569a2..ac02cc9e43 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -1322,6 +1322,18 @@ pub fn meta_path(path: ast::Path) -> ast::Meta { ast_from_text(&format!("#[{path}]")) } +pub fn cfg_attr_meta( + predicate: ast::CfgPredicate, + inner: impl IntoIterator<Item = ast::Meta>, +) -> ast::CfgAttrMeta { + let inner = inner.into_iter().join(", "); + ast_from_text(&format!("#![cfg_attr({predicate}, {inner})]")) +} + +pub fn cfg_flag(flag: &str) -> ast::CfgPredicate { + ast_from_text(&format!("#![cfg({flag})]")) +} + pub fn token_tree( delimiter: SyntaxKind, tt: impl IntoIterator<Item = NodeOrToken<ast::TokenTree, SyntaxToken>>, diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 3fc3b39fee..03118d01dc 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -8,6 +8,7 @@ use std::{borrow::Cow, fmt, iter::successors}; use itertools::Itertools; use parser::SyntaxKind; use rowan::{GreenNodeData, GreenTokenData}; +use smallvec::{SmallVec, smallvec}; use crate::{ NodeOrToken, SmolStr, SyntaxElement, SyntaxElementChildren, SyntaxToken, T, TokenText, @@ -201,53 +202,95 @@ impl AttrKind { } } -impl ast::Attr { +impl ast::Meta { pub fn as_simple_atom(&self) -> Option<SmolStr> { - let meta = self.meta()?; - if meta.eq_token().is_some() || meta.token_tree().is_some() { - return None; - } - self.simple_name() + Some(self.as_simple_path()?.as_single_name_ref()?.text().into()) } pub fn as_simple_call(&self) -> Option<(SmolStr, ast::TokenTree)> { - let tt = self.meta()?.token_tree()?; - Some((self.simple_name()?, tt)) + let ast::Meta::TokenTreeMeta(meta) = self else { return None }; + Some((meta.path()?.as_single_name_ref()?.text().into(), meta.token_tree()?)) } pub fn as_simple_path(&self) -> Option<ast::Path> { - let meta = self.meta()?; - if meta.eq_token().is_some() || meta.token_tree().is_some() { - return None; - } - self.path() + let ast::Meta::PathMeta(meta) = self else { return None }; + meta.path() } pub fn simple_name(&self) -> Option<SmolStr> { - let path = self.meta()?.path()?; - match (path.segment(), path.qualifier()) { - (Some(segment), None) => Some(segment.syntax().first_token()?.text().into()), - _ => None, + match self { + ast::Meta::CfgAttrMeta(_) => Some(SmolStr::new_static("cfg_attr")), + ast::Meta::CfgMeta(_) => Some(SmolStr::new_static("cfg")), + _ => { + let path = self.path()?; + match (path.segment(), path.qualifier()) { + (Some(segment), None) => Some(segment.syntax().first_token()?.text().into()), + _ => None, + } + } } } - pub fn kind(&self) -> AttrKind { - match self.excl_token() { - Some(_) => AttrKind::Inner, - None => AttrKind::Outer, + pub fn path(&self) -> Option<ast::Path> { + match self { + ast::Meta::CfgAttrMeta(_) | ast::Meta::CfgMeta(_) => None, + ast::Meta::KeyValueMeta(it) => it.path(), + ast::Meta::PathMeta(it) => it.path(), + ast::Meta::TokenTreeMeta(it) => it.path(), + ast::Meta::UnsafeMeta(it) => it.meta()?.path(), } } + /// Includes `cfg_attr()` inner metas (without considering the predicate). + pub fn skip_cfg_attrs(self) -> SmallVec<[ast::Meta; 1]> { + match self { + ast::Meta::CfgAttrMeta(meta) => { + meta.metas().flat_map(|meta| meta.skip_cfg_attrs()).collect() + } + _ => smallvec![self], + } + } + + /// FIXME: Calling this is almost always incorrect, as `cfg_attr` can contains multiple `Meta`s. + pub fn parent_attr(&self) -> Option<ast::Attr> { + self.syntax().ancestors().find_map(ast::Attr::cast) + } +} + +impl ast::Attr { + pub fn as_simple_atom(&self) -> Option<SmolStr> { + self.meta().and_then(|meta| meta.as_simple_atom()) + } + + pub fn as_simple_call(&self) -> Option<(SmolStr, ast::TokenTree)> { + self.meta().and_then(|meta| meta.as_simple_call()) + } + + pub fn as_simple_path(&self) -> Option<ast::Path> { + self.meta().and_then(|meta| meta.as_simple_path()) + } + + pub fn simple_name(&self) -> Option<SmolStr> { + self.meta().and_then(|meta| meta.simple_name()) + } + pub fn path(&self) -> Option<ast::Path> { - self.meta()?.path() + self.meta().and_then(|meta| meta.path()) } - pub fn expr(&self) -> Option<ast::Expr> { - self.meta()?.expr() + pub fn kind(&self) -> AttrKind { + match self.excl_token() { + Some(_) => AttrKind::Inner, + None => AttrKind::Outer, + } } - pub fn token_tree(&self) -> Option<ast::TokenTree> { - self.meta()?.token_tree() + /// Includes `cfg_attr()` inner metas (without considering the predicate). + pub fn skip_cfg_attrs(&self) -> SmallVec<[ast::Meta; 1]> { + match self.meta() { + Some(meta) => meta.skip_cfg_attrs(), + None => SmallVec::new(), + } } } @@ -1015,12 +1058,6 @@ impl ast::TokenTree { } } -impl ast::Meta { - pub fn parent_attr(&self) -> Option<ast::Attr> { - self.syntax().parent().and_then(ast::Attr::cast) - } -} - impl ast::GenericArgList { pub fn lifetime_args(&self) -> impl Iterator<Item = ast::LifetimeArg> { self.generic_args().filter_map(|arg| match arg { @@ -1164,6 +1201,25 @@ impl ast::OrPat { } } +#[derive(Debug, Clone)] +pub enum CfgAtomKey { + True, + False, + Ident(SyntaxToken), +} + +impl ast::CfgAtom { + pub fn key(&self) -> Option<CfgAtomKey> { + if self.true_token().is_some() { + Some(CfgAtomKey::True) + } else if self.false_token().is_some() { + Some(CfgAtomKey::False) + } else { + self.ident_token().map(CfgAtomKey::Ident) + } + } +} + /// An iterator over the elements in an [`ast::TokenTree`]. /// /// Does not yield trivia or the delimiters. diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs index e91e444a32..c66f096e83 100644 --- a/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -1848,7 +1848,36 @@ impl SyntaxFactory { if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); builder.map_node(path.syntax().clone(), ast.path().unwrap().syntax().clone()); - builder.map_node(tt.syntax().clone(), ast.token_tree().unwrap().syntax().clone()); + let ast::Meta::TokenTreeMeta(meta) = &ast else { unreachable!() }; + builder.map_node(tt.syntax().clone(), meta.token_tree().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + + pub fn cfg_flag(&self, flag: &str) -> ast::CfgPredicate { + make::cfg_flag(flag).clone_for_update() + } + + pub fn cfg_attr_meta( + &self, + predicate: ast::CfgPredicate, + inner: impl IntoIterator<Item = ast::Meta>, + ) -> ast::CfgAttrMeta { + let inner = Vec::from_iter(inner); + let ast = make::cfg_attr_meta(predicate.clone(), inner.iter().cloned()).clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node( + predicate.syntax().clone(), + ast.cfg_predicate().unwrap().syntax().clone(), + ); + builder.map_children( + inner.iter().map(|it| it.syntax().clone()), + ast.metas().map(|it| it.syntax().clone()), + ); builder.finish(&mut mapping); } diff --git a/crates/syntax/src/ast/traits.rs b/crates/syntax/src/ast/traits.rs index 2f4109a2c9..6fe5abb84e 100644 --- a/crates/syntax/src/ast/traits.rs +++ b/crates/syntax/src/ast/traits.rs @@ -73,9 +73,6 @@ pub trait HasAttrs: AstNode { fn attrs(&self) -> AstChildren<ast::Attr> { support::children(self.syntax()) } - fn has_atom_attr(&self, atom: &str) -> bool { - self.attrs().filter_map(|x| x.as_simple_atom()).any(|x| x == atom) - } /// This may return the same node as called with (with `SourceFile`). The caller has the responsibility /// to avoid duplicate attributes. diff --git a/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast b/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast index 50057a02d8..5fdde93c60 100644 --- a/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast +++ b/crates/syntax/test_data/parser/validation/0031_block_inner_attrs.rast @@ -29,7 +29,7 @@ [email protected] @@ -60,7 +60,7 @@ [email protected] @@ -75,7 +75,7 @@ [email protected] @@ -104,7 +104,7 @@ [email protected] diff --git a/xtask/src/codegen/grammar.rs b/xtask/src/codegen/grammar.rs index 4e980bb3d9..257429c426 100644 --- a/xtask/src/codegen/grammar.rs +++ b/xtask/src/codegen/grammar.rs @@ -780,7 +780,7 @@ impl Field { } fn token_kind(&self) -> Option<proc_macro2::TokenStream> { match self { - Field::Token(token) => { + Field::Token { token, .. } => { let token: proc_macro2::TokenStream = token.parse().unwrap(); Some(quote! { T![#token] }) } @@ -789,8 +789,11 @@ impl Field { } fn method_name(&self) -> String { match self { - Field::Token(name) => { - let name = match name.as_str() { + Field::Token { name, token, .. } => { + if let Some(name) = name { + return name.clone(); + } + let name = match token.as_str() { ";" => "semicolon", "->" => "thin_arrow", "'{'" => "l_curly", @@ -820,7 +823,7 @@ impl Field { "," => "comma", "|" => "pipe", "~" => "tilde", - _ => name, + _ => token, }; format!("{name}_token",) } @@ -835,7 +838,7 @@ impl Field { } fn ty(&self) -> proc_macro2::Ident { match self { - Field::Token(_) => format_ident!("SyntaxToken"), + Field::Token { .. } => format_ident!("SyntaxToken"), Field::Node { ty, .. } => format_ident!("{}", ty), } } @@ -885,7 +888,7 @@ fn lower(grammar: &Grammar) -> AstSrc { res.nodes.iter_mut().for_each(|it| { it.traits.sort(); it.fields.sort_by_key(|it| match it { - Field::Token(name) => (true, name.clone()), + Field::Token { token, .. } => (true, token.clone()), Field::Node { name, .. } => (false, name.clone()), }); }); @@ -925,12 +928,11 @@ fn lower_rule(acc: &mut Vec<Field>, grammar: &Grammar, label: Option<&String>, r acc.push(field); } Rule::Token(token) => { - assert!(label.is_none()); - let mut name = clean_token_name(&grammar[*token].name); - if "[]{}()".contains(&name) { - name = format!("'{name}'"); + let mut token = clean_token_name(&grammar[*token].name); + if "[]{}()".contains(&token) { + token = format!("'{token}'"); } - let field = Field::Token(name); + let field = Field::Token { name: label.cloned(), token }; acc.push(field); } Rule::Rep(inner) => { @@ -1018,8 +1020,8 @@ fn lower_separated_list( } match nt { Either::Right(token) => { - let name = clean_token_name(&grammar[*token].name); - let field = Field::Token(name); + let token = clean_token_name(&grammar[*token].name); + let field = Field::Token { token, name: None }; acc.push(field); } Either::Left(node) => { diff --git a/xtask/src/codegen/grammar/ast_src.rs b/xtask/src/codegen/grammar/ast_src.rs index 564d9cc24e..a0abdf09d3 100644 --- a/xtask/src/codegen/grammar/ast_src.rs +++ b/xtask/src/codegen/grammar/ast_src.rs @@ -111,8 +111,19 @@ const RESERVED: &[&str] = &[ ]; // keywords that are keywords only in specific parse contexts #[doc(alias = "WEAK_KEYWORDS")] -const CONTEXTUAL_KEYWORDS: &[&str] = - &["macro_rules", "union", "default", "raw", "dyn", "auto", "yeet", "safe", "bikeshed"]; +const CONTEXTUAL_KEYWORDS: &[&str] = &[ + "macro_rules", + "union", + "default", + "raw", + "dyn", + "auto", + "yeet", + "safe", + "bikeshed", + "cfg_attr", + "cfg", +]; // keywords we use for special macro expansions const CONTEXTUAL_BUILTIN_KEYWORDS: &[&str] = &[ "asm", @@ -261,7 +272,7 @@ pub(crate) struct AstNodeSrc { #[derive(Debug, Eq, PartialEq)] pub(crate) enum Field { - Token(String), + Token { name: Option<String>, token: String }, Node { name: String, ty: String, cardinality: Cardinality }, } |