Skip to main content

hir_def/
attrs.rs

1//! Attributes for anything that is not name resolution.
2//!
3//! The fundamental idea of this module stems from the observation that most "interesting"
4//! attributes have a more memory-compact form than storing their full syntax, and
5//! that most of the attributes are flags, and those that are not are rare. Therefore,
6//! this module defines [`AttrFlags`], which is a bitflag enum that contains only a yes/no
7//! answer to whether an attribute is present on an item. For most attributes, that's all
8//! that is interesting us; for the rest of them, we define another query that extracts
9//! their data. A key part is that every one of those queries will have a wrapper method
10//! that queries (or is given) the `AttrFlags` and checks for the presence of the attribute;
11//! if it is not present, we do not call the query, to prevent Salsa from needing to record
12//! its value. This way, queries are only called on items that have the attribute, which is
13//! usually only a few.
14//!
15//! Documentation (doc comments and `#[doc = "..."]` attributes) is handled by the [`docs`]
16//! submodule.
17
18use std::{convert::Infallible, iter::Peekable, ops::ControlFlow};
19
20use base_db::{Crate, SourceDatabase};
21use cfg::{CfgExpr, CfgOptions};
22use either::Either;
23use hir_expand::{
24    InFile, Lookup,
25    attrs::{AstKeyValueMetaExt, AstPathExt, expand_cfg_attr},
26    name::Name,
27};
28use intern::Symbol;
29use itertools::Itertools;
30use la_arena::ArenaMap;
31use rustc_abi::ReprOptions;
32use rustc_hash::FxHashSet;
33use smallvec::SmallVec;
34use syntax::{
35    AstNode, AstToken, NodeOrToken, SmolStr, SourceFile, T,
36    ast::{self, HasAttrs, TokenTreeChildren},
37};
38use tt::TextSize;
39
40use crate::{
41    AdtId, AstIdLoc, AttrDefId, FieldId, FunctionId, GenericDefId, HasModule, LifetimeParamId,
42    LocalFieldId, MacroId, ModuleId, TraitId, TypeOrConstParamId, VariantId,
43    hir::generics::{GenericParams, LocalLifetimeParamId, LocalTypeOrConstParamId},
44    nameres::ModuleOrigin,
45    resolver::{HasResolver, Resolver},
46    src::{HasChildSource, HasSource},
47};
48
49pub mod docs;
50
51pub use self::docs::{Docs, IsInnerDoc};
52
53#[inline]
54fn attrs_from_ast_id_loc<N: AstNode + Into<ast::AnyHasAttrs>>(
55    db: &dyn SourceDatabase,
56    lookup: impl Lookup<Data = impl AstIdLoc<Ast = N> + HasModule>,
57) -> (InFile<ast::AnyHasAttrs>, Crate) {
58    let loc = lookup.lookup(db);
59    let source = loc.source(db);
60    let krate = loc.krate(db);
61    (source.map(|it| it.into()), krate)
62}
63
64#[inline]
65fn extract_doc_tt_attr(attr_flags: &mut AttrFlags, tt: ast::TokenTree) {
66    for atom in DocAtom::parse(tt) {
67        match atom {
68            DocAtom::Flag(flag) => match &*flag {
69                "notable_trait" => attr_flags.insert(AttrFlags::IS_DOC_NOTABLE_TRAIT),
70                "hidden" => attr_flags.insert(AttrFlags::IS_DOC_HIDDEN),
71                _ => {}
72            },
73            DocAtom::KeyValue { key, value: _ } => match &*key {
74                "alias" => attr_flags.insert(AttrFlags::HAS_DOC_ALIASES),
75                "keyword" => attr_flags.insert(AttrFlags::HAS_DOC_KEYWORD),
76                _ => {}
77            },
78            DocAtom::Alias(_) => attr_flags.insert(AttrFlags::HAS_DOC_ALIASES),
79        }
80    }
81}
82
83fn extract_ra_completions(attr_flags: &mut AttrFlags, tt: ast::TokenTree) {
84    let tt = TokenTreeChildren::new(&tt);
85    if let Ok(NodeOrToken::Token(option)) = Itertools::exactly_one(tt)
86        && option.kind().is_any_identifier()
87    {
88        match option.text() {
89            "ignore_flyimport" => attr_flags.insert(AttrFlags::COMPLETE_IGNORE_FLYIMPORT),
90            "ignore_methods" => attr_flags.insert(AttrFlags::COMPLETE_IGNORE_METHODS),
91            "ignore_flyimport_methods" => {
92                attr_flags.insert(AttrFlags::COMPLETE_IGNORE_FLYIMPORT_METHODS)
93            }
94            _ => {}
95        }
96    }
97}
98
99fn extract_ra_macro_style(attr_flags: &mut AttrFlags, tt: ast::TokenTree) {
100    let tt = TokenTreeChildren::new(&tt);
101    if let Ok(NodeOrToken::Token(option)) = Itertools::exactly_one(tt)
102        && option.kind().is_any_identifier()
103    {
104        match option.text() {
105            "braces" => attr_flags.insert(AttrFlags::MACRO_STYLE_BRACES),
106            "brackets" => attr_flags.insert(AttrFlags::MACRO_STYLE_BRACKETS),
107            "parentheses" => attr_flags.insert(AttrFlags::MACRO_STYLE_PARENTHESES),
108            _ => {}
109        }
110    }
111}
112
113fn extract_rustc_skip_during_method_dispatch(attr_flags: &mut AttrFlags, tt: ast::TokenTree) {
114    let iter = TokenTreeChildren::new(&tt);
115    for kind in iter {
116        if let NodeOrToken::Token(kind) = kind
117            && kind.kind().is_any_identifier()
118        {
119            match kind.text() {
120                "array" => attr_flags.insert(AttrFlags::RUSTC_SKIP_ARRAY_DURING_METHOD_DISPATCH),
121                "boxed_slice" => {
122                    attr_flags.insert(AttrFlags::RUSTC_SKIP_BOXED_SLICE_DURING_METHOD_DISPATCH)
123                }
124                _ => {}
125            }
126        }
127    }
128}
129
130#[inline]
131fn match_attr_flags(attr_flags: &mut AttrFlags, attr: ast::Meta) -> ControlFlow<Infallible> {
132    match attr {
133        ast::Meta::CfgMeta(_) => attr_flags.insert(AttrFlags::HAS_CFG),
134        ast::Meta::KeyValueMeta(attr) => {
135            let Some(key) = attr.path().as_one_segment() else { return ControlFlow::Continue(()) };
136            match &*key {
137                "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED),
138                "ignore" => attr_flags.insert(AttrFlags::IS_IGNORE),
139                "lang" => attr_flags.insert(AttrFlags::LANG_ITEM),
140                "must_use" => attr_flags.insert(AttrFlags::IS_MUST_USE),
141                "path" => attr_flags.insert(AttrFlags::HAS_PATH),
142                "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE),
143                "rustc_reservation_impl" => attr_flags.insert(AttrFlags::RUSTC_RESERVATION_IMPL),
144                "export_name" => {
145                    if let Some(value) = attr.value_string()
146                        && *value == *"main"
147                    {
148                        attr_flags.insert(AttrFlags::IS_EXPORT_NAME_MAIN);
149                    }
150                }
151                _ => {}
152            }
153        }
154        ast::Meta::TokenTreeMeta(attr) => {
155            let (Some((first_segment, second_segment)), Some(tt)) =
156                (attr.path().as_up_to_two_segment(), attr.token_tree())
157            else {
158                return ControlFlow::Continue(());
159            };
160            match second_segment {
161                None => match &*first_segment {
162                    "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED),
163                    "doc" => extract_doc_tt_attr(attr_flags, tt),
164                    "repr" | "rustc_scalable_vector" => attr_flags.insert(AttrFlags::HAS_REPR),
165                    "target_feature" => attr_flags.insert(AttrFlags::HAS_TARGET_FEATURE),
166                    "proc_macro_derive" | "rustc_builtin_macro" => {
167                        attr_flags.insert(AttrFlags::IS_DERIVE_OR_BUILTIN_MACRO)
168                    }
169                    "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE),
170                    "rustc_layout_scalar_valid_range_start"
171                    | "rustc_layout_scalar_valid_range_end" => {
172                        attr_flags.insert(AttrFlags::RUSTC_LAYOUT_SCALAR_VALID_RANGE)
173                    }
174                    "rustc_legacy_const_generics" => {
175                        attr_flags.insert(AttrFlags::HAS_LEGACY_CONST_GENERICS)
176                    }
177                    "rustc_skip_during_method_dispatch" => {
178                        extract_rustc_skip_during_method_dispatch(attr_flags, tt)
179                    }
180                    "rustc_deprecated_safe_2024" => {
181                        attr_flags.insert(AttrFlags::RUSTC_DEPRECATED_SAFE_2024)
182                    }
183                    "rustc_must_implement_one_of" => {
184                        attr_flags.insert(AttrFlags::HAS_RUSTC_MUST_IMPLEMENT_ONE_OF)
185                    }
186                    _ => {}
187                },
188                Some(second_segment) => match &*first_segment {
189                    "rust_analyzer" => match &*second_segment {
190                        "completions" => extract_ra_completions(attr_flags, tt),
191                        "macro_style" => extract_ra_macro_style(attr_flags, tt),
192                        _ => {}
193                    },
194                    _ => {}
195                },
196            }
197        }
198        ast::Meta::PathMeta(attr) => {
199            let is_test = attr.path().is_some_and(|path| {
200                let Some(segment1) = (|| path.segment()?.name_ref())() else { return false };
201                let segment2 = path.qualifier();
202                let segment3 = segment2.as_ref().and_then(|it| it.qualifier());
203                let segment4 = segment3.as_ref().and_then(|it| it.qualifier());
204                let segment3 = segment3.and_then(|it| it.segment()?.name_ref());
205                let segment4 = segment4.and_then(|it| it.segment()?.name_ref());
206                segment1.text() == "test"
207                    && segment3.is_none_or(|it| it.text() == "prelude")
208                    && segment4.is_none_or(|it| matches!(it.text(), "core" | "std"))
209            });
210            if is_test {
211                attr_flags.insert(AttrFlags::IS_TEST);
212            }
213
214            let Some((first_segment, second_segment)) = attr.path().as_up_to_two_segment() else {
215                return ControlFlow::Continue(());
216            };
217            match second_segment {
218                None => match &*first_segment {
219                    "rustc_has_incoherent_inherent_impls" => {
220                        attr_flags.insert(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS)
221                    }
222                    "rustc_allow_incoherent_impl" => {
223                        attr_flags.insert(AttrFlags::RUSTC_ALLOW_INCOHERENT_IMPL)
224                    }
225                    "rustc_scalable_vector" => attr_flags.insert(AttrFlags::HAS_REPR),
226                    "fundamental" => attr_flags.insert(AttrFlags::FUNDAMENTAL),
227                    "no_std" => attr_flags.insert(AttrFlags::IS_NO_STD),
228                    "may_dangle" => attr_flags.insert(AttrFlags::MAY_DANGLE),
229                    "rustc_paren_sugar" => attr_flags.insert(AttrFlags::RUSTC_PAREN_SUGAR),
230                    "rustc_coinductive" => attr_flags.insert(AttrFlags::RUSTC_COINDUCTIVE),
231                    "rustc_force_inline" => attr_flags.insert(AttrFlags::RUSTC_FORCE_INLINE),
232                    "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE),
233                    "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED),
234                    "macro_export" => attr_flags.insert(AttrFlags::IS_MACRO_EXPORT),
235                    "must_use" => attr_flags.insert(AttrFlags::IS_MUST_USE),
236                    "no_mangle" => attr_flags.insert(AttrFlags::NO_MANGLE),
237                    "pointee" => attr_flags.insert(AttrFlags::IS_POINTEE),
238                    "non_exhaustive" => attr_flags.insert(AttrFlags::NON_EXHAUSTIVE),
239                    "ignore" => attr_flags.insert(AttrFlags::IS_IGNORE),
240                    "bench" => attr_flags.insert(AttrFlags::IS_BENCH),
241                    "rustc_const_panic_str" => attr_flags.insert(AttrFlags::RUSTC_CONST_PANIC_STR),
242                    "rustc_intrinsic" => attr_flags.insert(AttrFlags::RUSTC_INTRINSIC),
243                    "rustc_safe_intrinsic" => attr_flags.insert(AttrFlags::RUSTC_SAFE_INTRINSIC),
244                    "rustc_intrinsic_must_be_overridden" => {
245                        attr_flags.insert(AttrFlags::RUSTC_INTRINSIC_MUST_BE_OVERRIDDEN)
246                    }
247                    "rustc_allocator" => attr_flags.insert(AttrFlags::RUSTC_ALLOCATOR),
248                    "rustc_deallocator" => attr_flags.insert(AttrFlags::RUSTC_DEALLOCATOR),
249                    "rustc_reallocator" => attr_flags.insert(AttrFlags::RUSTC_REALLOCATOR),
250                    "rustc_allocator_zeroed" => {
251                        attr_flags.insert(AttrFlags::RUSTC_ALLOCATOR_ZEROED)
252                    }
253                    "rustc_reservation_impl" => {
254                        attr_flags.insert(AttrFlags::RUSTC_RESERVATION_IMPL)
255                    }
256                    "rustc_deprecated_safe_2024" => {
257                        attr_flags.insert(AttrFlags::RUSTC_DEPRECATED_SAFE_2024)
258                    }
259                    "rustc_skip_array_during_method_dispatch" => {
260                        attr_flags.insert(AttrFlags::RUSTC_SKIP_ARRAY_DURING_METHOD_DISPATCH)
261                    }
262                    _ => {}
263                },
264                Some(second_segment) => match &*first_segment {
265                    "rust_analyzer" => match &*second_segment {
266                        "skip" => attr_flags.insert(AttrFlags::RUST_ANALYZER_SKIP),
267                        "prefer_underscore_import" => {
268                            attr_flags.insert(AttrFlags::PREFER_UNDERSCORE_IMPORT)
269                        }
270                        _ => {}
271                    },
272                    "diagnostic" => match &*second_segment {
273                        "do_not_recommend" => {
274                            attr_flags.insert(AttrFlags::DIAGNOSTIC_DO_NOT_RECOMMEND)
275                        }
276                        _ => {}
277                    },
278                    _ => {}
279                },
280            }
281        }
282        _ => {}
283    };
284    ControlFlow::Continue(())
285}
286
287bitflags::bitflags! {
288    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
289    pub struct AttrFlags: u64 {
290        const RUST_ANALYZER_SKIP = 1 << 0;
291
292        const LANG_ITEM = 1 << 1;
293
294        const HAS_DOC_ALIASES = 1 << 2;
295        const HAS_DOC_KEYWORD = 1 << 3;
296        const IS_DOC_NOTABLE_TRAIT = 1 << 4;
297        const IS_DOC_HIDDEN = 1 << 5;
298
299        const RUSTC_HAS_INCOHERENT_INHERENT_IMPLS = 1 << 6;
300        const RUSTC_ALLOW_INCOHERENT_IMPL = 1 << 7;
301        const FUNDAMENTAL = 1 << 8;
302        const RUSTC_SKIP_ARRAY_DURING_METHOD_DISPATCH = 1 << 9;
303        const RUSTC_SKIP_BOXED_SLICE_DURING_METHOD_DISPATCH = 1 << 10;
304        const HAS_REPR = 1 << 11;
305        const HAS_TARGET_FEATURE = 1 << 12;
306        const RUSTC_DEPRECATED_SAFE_2024 = 1 << 13;
307        const HAS_LEGACY_CONST_GENERICS = 1 << 14;
308        const NO_MANGLE = 1 << 15;
309        const NON_EXHAUSTIVE = 1 << 16;
310        const RUSTC_RESERVATION_IMPL = 1 << 17;
311        const RUSTC_CONST_PANIC_STR = 1 << 18;
312        const MAY_DANGLE = 1 << 19;
313
314        const RUSTC_INTRINSIC = 1 << 20;
315        const RUSTC_SAFE_INTRINSIC = 1 << 21;
316        const RUSTC_INTRINSIC_MUST_BE_OVERRIDDEN = 1 << 22;
317        const RUSTC_ALLOCATOR = 1 << 23;
318        const RUSTC_DEALLOCATOR = 1 << 24;
319        const RUSTC_REALLOCATOR = 1 << 25;
320        const RUSTC_ALLOCATOR_ZEROED = 1 << 26;
321
322        const IS_UNSTABLE = 1 << 27;
323        const IS_IGNORE = 1 << 28;
324        // FIXME: `IS_TEST` and `IS_BENCH` should be based on semantic information, not textual match.
325        const IS_BENCH = 1 << 29;
326        const IS_TEST = 1 << 30;
327        const IS_EXPORT_NAME_MAIN = 1 << 31;
328        const IS_MACRO_EXPORT = 1 << 32;
329        const IS_NO_STD = 1 << 33;
330        const IS_DERIVE_OR_BUILTIN_MACRO = 1 << 34;
331        const IS_DEPRECATED = 1 << 35;
332        const HAS_PATH = 1 << 36;
333        const HAS_CFG = 1 << 37;
334
335        const COMPLETE_IGNORE_FLYIMPORT = 1 << 38;
336        const COMPLETE_IGNORE_FLYIMPORT_METHODS = 1 << 39;
337        const COMPLETE_IGNORE_METHODS = 1 << 40;
338
339        const RUSTC_LAYOUT_SCALAR_VALID_RANGE = 1 << 41;
340        const RUSTC_PAREN_SUGAR = 1 << 42;
341        const RUSTC_COINDUCTIVE = 1 << 43;
342        const RUSTC_FORCE_INLINE = 1 << 44;
343        const IS_POINTEE = 1 << 45;
344
345        const MACRO_STYLE_BRACES = 1 << 46;
346        const MACRO_STYLE_BRACKETS = 1 << 47;
347        const MACRO_STYLE_PARENTHESES = 1 << 48;
348
349        const PREFER_UNDERSCORE_IMPORT = 1 << 49;
350
351        const IS_MUST_USE = 1 << 50;
352
353        const DIAGNOSTIC_DO_NOT_RECOMMEND = 1 << 51;
354
355        const HAS_RUSTC_MUST_IMPLEMENT_ONE_OF = 1 << 52;
356    }
357}
358
359pub fn parse_extra_crate_attrs(db: &dyn SourceDatabase, krate: Crate) -> Option<SourceFile> {
360    let crate_data = krate.data(db);
361    let crate_attrs = &crate_data.crate_attrs;
362    if crate_attrs.is_empty() {
363        return None;
364    }
365    // All attributes are already enclosed in `#![]`.
366    let combined = crate_attrs.concat();
367    let p = SourceFile::parse(&combined, crate_data.edition);
368
369    let errs = p.errors();
370    if !errs.is_empty() {
371        let base_msg = "Failed to parse extra crate-level attribute";
372        let crate_name =
373            krate.extra_data(db).display_name.as_ref().map_or("{unknown}", |name| name.as_str());
374        let mut errs = errs.iter().peekable();
375        let mut offset = TextSize::from(0);
376        for raw_attr in crate_attrs {
377            let attr_end = offset + TextSize::of(&**raw_attr);
378            if errs.peeking_take_while(|e| e.range().start() < attr_end).count() > 0 {
379                tracing::error!("{base_msg} {raw_attr} for crate {crate_name}");
380            }
381            offset = attr_end
382        }
383        return None;
384    }
385
386    Some(p.tree())
387}
388
389/// Whether the crate root declares `#![no_std]`, looking through `cfg_attr` gating.
390#[salsa::tracked(returns(copy))]
391pub(crate) fn crate_supports_no_std(db: &dyn SourceDatabase, krate: Crate) -> bool {
392    fn contains_no_std(meta: ast::Meta) -> bool {
393        match meta {
394            ast::Meta::PathMeta(meta) => meta.path().is1("no_std"),
395            ast::Meta::CfgAttrMeta(meta) => meta.metas().any(contains_no_std),
396            ast::Meta::UnsafeMeta(meta) => meta.meta().is_some_and(contains_no_std),
397            ast::Meta::CfgMeta(_) | ast::Meta::KeyValueMeta(_) | ast::Meta::TokenTreeMeta(_) => {
398                false
399            }
400        }
401    }
402
403    let root_file = krate.root_file_id(db).parse(db).tree();
404    parse_extra_crate_attrs(db, krate)
405        .into_iter()
406        .flat_map(|extra| extra.attrs())
407        .chain(root_file.attrs())
408        .filter_map(|attr| attr.meta())
409        .any(contains_no_std)
410}
411
412fn attrs_source(
413    db: &dyn SourceDatabase,
414    owner: AttrDefId,
415) -> (InFile<ast::AnyHasAttrs>, Option<InFile<ast::Module>>, Option<SourceFile>, Crate) {
416    let (owner, krate) = match owner {
417        AttrDefId::ModuleId(id) => {
418            let def_map = id.def_map(db);
419            let krate = def_map.krate();
420            let (definition, declaration, extra_crate_attrs) = match def_map[id].origin {
421                ModuleOrigin::CrateRoot { definition } => {
422                    let definition_source = definition.parse(db).tree();
423                    let definition = InFile::new(definition.into(), definition_source.into());
424                    let extra_crate_attrs = parse_extra_crate_attrs(db, krate);
425                    (definition, None, extra_crate_attrs)
426                }
427                ModuleOrigin::File { declaration, declaration_tree_id, definition, .. } => {
428                    let definition_source = definition.parse(db).tree();
429                    let definition = InFile::new(definition.into(), definition_source.into());
430                    let declaration = InFile::new(declaration_tree_id.file_id(), declaration);
431                    let declaration = declaration.with_value(declaration.to_node(db));
432                    (definition, Some(declaration), None)
433                }
434                ModuleOrigin::Inline { definition_tree_id, definition } => {
435                    let definition = InFile::new(definition_tree_id.file_id(), definition);
436                    let definition = definition.with_value(definition.to_node(db).into());
437                    (definition, None, None)
438                }
439                ModuleOrigin::BlockExpr { block, .. } => {
440                    let definition = block.to_node(db);
441                    (block.with_value(definition.into()), None, None)
442                }
443            };
444            return (definition, declaration, extra_crate_attrs, krate);
445        }
446        AttrDefId::AdtId(AdtId::StructId(it)) => attrs_from_ast_id_loc(db, it),
447        AttrDefId::AdtId(AdtId::UnionId(it)) => attrs_from_ast_id_loc(db, it),
448        AttrDefId::AdtId(AdtId::EnumId(it)) => attrs_from_ast_id_loc(db, it),
449        AttrDefId::FunctionId(it) => attrs_from_ast_id_loc(db, it),
450        AttrDefId::EnumVariantId(it) => attrs_from_ast_id_loc(db, it),
451        AttrDefId::StaticId(it) => attrs_from_ast_id_loc(db, it),
452        AttrDefId::ConstId(it) => attrs_from_ast_id_loc(db, it),
453        AttrDefId::TraitId(it) => attrs_from_ast_id_loc(db, it),
454        AttrDefId::TypeAliasId(it) => attrs_from_ast_id_loc(db, it),
455        AttrDefId::MacroId(MacroId::MacroRulesId(it)) => attrs_from_ast_id_loc(db, it),
456        AttrDefId::MacroId(MacroId::Macro2Id(it)) => attrs_from_ast_id_loc(db, it),
457        AttrDefId::MacroId(MacroId::ProcMacroId(it)) => attrs_from_ast_id_loc(db, it),
458        AttrDefId::ImplId(it) => attrs_from_ast_id_loc(db, it),
459        AttrDefId::ExternBlockId(it) => attrs_from_ast_id_loc(db, it),
460        AttrDefId::ExternCrateId(it) => attrs_from_ast_id_loc(db, it),
461        AttrDefId::UseId(it) => attrs_from_ast_id_loc(db, it),
462    };
463    (owner, None, None, krate)
464}
465
466fn resolver_for_attr_def_id(db: &dyn SourceDatabase, owner: AttrDefId) -> Resolver<'_> {
467    match owner {
468        AttrDefId::ModuleId(id) => id.resolver(db),
469        AttrDefId::AdtId(AdtId::StructId(id)) => id.resolver(db),
470        AttrDefId::AdtId(AdtId::UnionId(id)) => id.resolver(db),
471        AttrDefId::AdtId(AdtId::EnumId(id)) => id.resolver(db),
472        AttrDefId::FunctionId(id) => id.resolver(db),
473        AttrDefId::EnumVariantId(id) => id.resolver(db),
474        AttrDefId::StaticId(id) => id.resolver(db),
475        AttrDefId::ConstId(id) => id.resolver(db),
476        AttrDefId::TraitId(id) => id.resolver(db),
477        AttrDefId::TypeAliasId(id) => id.resolver(db),
478        AttrDefId::MacroId(MacroId::Macro2Id(id)) => id.resolver(db),
479        AttrDefId::MacroId(MacroId::MacroRulesId(id)) => id.resolver(db),
480        AttrDefId::MacroId(MacroId::ProcMacroId(id)) => id.resolver(db),
481        AttrDefId::ImplId(id) => id.resolver(db),
482        AttrDefId::ExternBlockId(id) => id.resolver(db),
483        AttrDefId::ExternCrateId(id) => id.resolver(db),
484        AttrDefId::UseId(id) => id.resolver(db),
485    }
486}
487
488fn collect_attrs<BreakValue>(
489    db: &dyn SourceDatabase,
490    owner: AttrDefId,
491    mut callback: impl FnMut(ast::Meta) -> ControlFlow<BreakValue>,
492) -> Option<BreakValue> {
493    let (source, outer_mod_decl, extra_crate_attrs, krate) = attrs_source(db, owner);
494    let extra_attrs = extra_crate_attrs
495        .into_iter()
496        .flat_map(|src| src.attrs())
497        .chain(outer_mod_decl.into_iter().flat_map(|it| it.value.attrs()));
498
499    let mut cfg_options = None;
500    expand_cfg_attr(
501        extra_attrs.chain(ast::attrs_including_inner(&source.value)),
502        || cfg_options.get_or_insert_with(|| krate.cfg_options(db)),
503        move |meta, _| callback(meta),
504    )
505}
506
507fn collect_field_attrs<T>(
508    db: &dyn SourceDatabase,
509    variant: VariantId,
510    mut field_attrs: impl FnMut(&CfgOptions, InFile<ast::AnyHasAttrs>) -> T,
511) -> ArenaMap<LocalFieldId, T> {
512    let (variant_syntax, krate) = match variant {
513        VariantId::EnumVariantId(it) => attrs_from_ast_id_loc(db, it),
514        VariantId::StructId(it) => attrs_from_ast_id_loc(db, it),
515        VariantId::UnionId(it) => attrs_from_ast_id_loc(db, it),
516    };
517    let cfg_options = krate.cfg_options(db);
518    let variant_syntax = variant_syntax
519        .with_value(ast::VariantDef::cast(variant_syntax.value.syntax().clone()).unwrap());
520    let fields = match &variant_syntax.value {
521        ast::VariantDef::Struct(it) => it.field_list(),
522        ast::VariantDef::Union(it) => it.record_field_list().map(ast::FieldList::RecordFieldList),
523        ast::VariantDef::Variant(it) => it.field_list(),
524    };
525    let Some(fields) = fields else {
526        return ArenaMap::new();
527    };
528
529    let mut result = ArenaMap::new();
530    let mut idx = 0;
531    match fields {
532        ast::FieldList::RecordFieldList(fields) => {
533            for field in fields.fields() {
534                if AttrFlags::is_cfg_enabled_for(&field, cfg_options).is_ok() {
535                    result.insert(
536                        la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(idx)),
537                        field_attrs(cfg_options, variant_syntax.with_value(field.into())),
538                    );
539                    idx += 1;
540                }
541            }
542        }
543        ast::FieldList::TupleFieldList(fields) => {
544            for field in fields.fields() {
545                if AttrFlags::is_cfg_enabled_for(&field, cfg_options).is_ok() {
546                    result.insert(
547                        la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(idx)),
548                        field_attrs(cfg_options, variant_syntax.with_value(field.into())),
549                    );
550                    idx += 1;
551                }
552            }
553        }
554    }
555    result.shrink_to_fit();
556    result
557}
558
559#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
560pub struct RustcLayoutScalarValidRange {
561    pub start: Option<u128>,
562    pub end: Option<u128>,
563}
564
565#[derive(Debug, PartialEq, Eq, Hash)]
566pub struct DeriveInfo {
567    pub trait_name: Symbol,
568    pub helpers: Box<[Symbol]>,
569}
570
571fn extract_doc_aliases(result: &mut Vec<Symbol>, attr: ast::Meta) -> ControlFlow<Infallible> {
572    if let ast::Meta::TokenTreeMeta(attr) = attr
573        && attr.path().is1("doc")
574        && let Some(tt) = attr.token_tree()
575    {
576        for atom in DocAtom::parse(tt) {
577            match atom {
578                DocAtom::Alias(aliases) => {
579                    result.extend(aliases.into_iter().map(|alias| Symbol::intern(&alias)))
580                }
581                DocAtom::KeyValue { key, value } if key == "alias" => {
582                    result.push(Symbol::intern(&value))
583                }
584                _ => {}
585            }
586        }
587    }
588    ControlFlow::Continue(())
589}
590
591fn extract_cfgs(result: &mut Vec<CfgExpr>, attr: ast::Meta) -> ControlFlow<Infallible> {
592    if let ast::Meta::CfgMeta(attr) = attr
593        && let Some(cfg_predicate) = attr.cfg_predicate()
594    {
595        result.push(CfgExpr::parse_from_ast(cfg_predicate));
596    }
597    ControlFlow::Continue(())
598}
599
600#[salsa::tracked]
601impl AttrFlags {
602    #[salsa::tracked(returns(copy))]
603    pub fn query(db: &dyn SourceDatabase, owner: AttrDefId) -> AttrFlags {
604        let mut attr_flags = AttrFlags::empty();
605        collect_attrs(db, owner, |attr| match_attr_flags(&mut attr_flags, attr));
606        attr_flags
607    }
608
609    #[inline]
610    pub fn query_field(db: &dyn SourceDatabase, field: FieldId) -> AttrFlags {
611        return field_attr_flags(db, field.parent)
612            .get(field.local_id)
613            .copied()
614            .unwrap_or_else(AttrFlags::empty);
615
616        #[salsa::tracked(returns(ref))]
617        fn field_attr_flags(
618            db: &dyn SourceDatabase,
619            variant: VariantId,
620        ) -> ArenaMap<LocalFieldId, AttrFlags> {
621            collect_field_attrs(db, variant, |cfg_options, field| {
622                let mut attr_flags = AttrFlags::empty();
623                expand_cfg_attr(
624                    field.value.attrs(),
625                    || cfg_options,
626                    |attr, _| match_attr_flags(&mut attr_flags, attr),
627                );
628                attr_flags
629            })
630        }
631    }
632
633    #[inline]
634    pub fn query_generic_params(
635        db: &dyn SourceDatabase,
636        def: GenericDefId,
637    ) -> &(ArenaMap<LocalLifetimeParamId, AttrFlags>, ArenaMap<LocalTypeOrConstParamId, AttrFlags>)
638    {
639        let generic_params = GenericParams::of(db, def);
640        let params_count_excluding_self =
641            generic_params.len() - usize::from(generic_params.trait_self_param().is_some());
642        if params_count_excluding_self == 0 {
643            return const { &(ArenaMap::new(), ArenaMap::new()) };
644        }
645        return generic_params_attr_flags(db, def);
646
647        #[salsa::tracked(returns(ref))]
648        fn generic_params_attr_flags(
649            db: &dyn SourceDatabase,
650            def: GenericDefId,
651        ) -> (ArenaMap<LocalLifetimeParamId, AttrFlags>, ArenaMap<LocalTypeOrConstParamId, AttrFlags>)
652        {
653            let mut lifetimes = ArenaMap::new();
654            let mut type_and_consts = ArenaMap::new();
655
656            let mut cfg_options = None;
657            let mut cfg_options =
658                || *cfg_options.get_or_insert_with(|| def.krate(db).cfg_options(db));
659
660            let lifetimes_source = HasChildSource::<LocalLifetimeParamId>::child_source(&def, db);
661            for (lifetime_id, lifetime) in lifetimes_source.value.iter() {
662                let mut attr_flags = AttrFlags::empty();
663                expand_cfg_attr(lifetime.attrs(), &mut cfg_options, |attr, _| {
664                    match_attr_flags(&mut attr_flags, attr)
665                });
666                if !attr_flags.is_empty() {
667                    lifetimes.insert(lifetime_id, attr_flags);
668                }
669            }
670
671            let type_and_consts_source =
672                HasChildSource::<LocalTypeOrConstParamId>::child_source(&def, db);
673            for (type_or_const_id, type_or_const) in type_and_consts_source.value.iter() {
674                let mut attr_flags = AttrFlags::empty();
675                expand_cfg_attr(type_or_const.attrs(), &mut cfg_options, |attr, _| {
676                    match_attr_flags(&mut attr_flags, attr)
677                });
678                if !attr_flags.is_empty() {
679                    type_and_consts.insert(type_or_const_id, attr_flags);
680                }
681            }
682
683            lifetimes.shrink_to_fit();
684            type_and_consts.shrink_to_fit();
685            (lifetimes, type_and_consts)
686        }
687    }
688
689    #[inline]
690    pub fn query_lifetime_param(db: &dyn SourceDatabase, owner: LifetimeParamId) -> AttrFlags {
691        AttrFlags::query_generic_params(db, owner.parent)
692            .0
693            .get(owner.local_id)
694            .copied()
695            .unwrap_or_else(AttrFlags::empty)
696    }
697    #[inline]
698    pub fn query_type_or_const_param(
699        db: &dyn SourceDatabase,
700        owner: TypeOrConstParamId,
701    ) -> AttrFlags {
702        AttrFlags::query_generic_params(db, owner.parent)
703            .1
704            .get(owner.local_id)
705            .copied()
706            .unwrap_or_else(AttrFlags::empty)
707    }
708
709    pub(crate) fn is_cfg_enabled_for(
710        owner: &dyn HasAttrs,
711        cfg_options: &CfgOptions,
712    ) -> Result<(), CfgExpr> {
713        let attrs = ast::attrs_including_inner(owner);
714        let result = expand_cfg_attr(
715            attrs,
716            || cfg_options,
717            |attr, _| {
718                if let ast::Meta::CfgMeta(attr) = attr
719                    && let Some(cfg_predicate) = attr.cfg_predicate()
720                    && let cfg = CfgExpr::parse_from_ast(cfg_predicate)
721                    && cfg_options.check(&cfg) == Some(false)
722                {
723                    ControlFlow::Break(cfg)
724                } else {
725                    ControlFlow::Continue(())
726                }
727            },
728        );
729        match result {
730            Some(cfg) => Err(cfg),
731            None => Ok(()),
732        }
733    }
734
735    #[inline]
736    pub fn lang_item(db: &dyn SourceDatabase, owner: AttrDefId) -> Option<Symbol> {
737        AttrFlags::query(db, owner).lang_item_with_attrs(db, owner)
738    }
739
740    #[inline]
741    pub fn lang_item_with_attrs(self, db: &dyn SourceDatabase, owner: AttrDefId) -> Option<Symbol> {
742        if !self.contains(AttrFlags::LANG_ITEM) {
743            // Don't create the query in case this is not a lang item, this wastes memory.
744            return None;
745        }
746
747        return lang_item(db, owner);
748
749        #[salsa::tracked(returns(clone))]
750        fn lang_item(db: &dyn SourceDatabase, owner: AttrDefId) -> Option<Symbol> {
751            collect_attrs(db, owner, |attr| {
752                if let ast::Meta::KeyValueMeta(attr) = attr
753                    && attr.path().is1("lang")
754                    && let Some(value) = attr.value_string()
755                {
756                    ControlFlow::Break(Symbol::intern(&value))
757                } else {
758                    ControlFlow::Continue(())
759                }
760            })
761        }
762    }
763
764    #[inline]
765    pub fn repr(db: &dyn SourceDatabase, owner: AdtId) -> Option<ReprOptions> {
766        if !AttrFlags::query(db, owner.into()).contains(AttrFlags::HAS_REPR) {
767            // Don't create the query in case this has no repr, this wastes memory.
768            return None;
769        }
770
771        Self::repr_assume_has(db, owner)
772    }
773
774    /// Only call this when you've verified the type indeed has a `#[repr]` attribute!
775    ///
776    /// Prefer [`AttrFlags::repr()`] in non-perf-sensitive places as it also has a check that
777    /// that the ADT has repr.
778    #[salsa::tracked(returns(copy))]
779    pub fn repr_assume_has(db: &dyn SourceDatabase, owner: AdtId) -> Option<ReprOptions> {
780        let mut result = None;
781        collect_attrs::<Infallible>(db, owner.into(), |attr| {
782            let mut current = None;
783            if let ast::Meta::TokenTreeMeta(attr) = &attr
784                && let Some(path) = attr.path()
785                && let Some(tt) = attr.token_tree()
786            {
787                if path.is1("repr")
788                    && let Some(repr) = parse_repr_tt(&tt)
789                {
790                    current = Some(repr);
791                } else if path.is1("rustc_scalable_vector")
792                    && let mut tt = TokenTreeChildren::new(&tt)
793                    && let Some(NodeOrToken::Token(scalable)) = tt.next()
794                    && let Some(scalable) = ast::IntNumber::cast(scalable)
795                    && let Ok(scalable) = scalable.value()
796                    && let Ok(scalable) = scalable.try_into()
797                {
798                    current = Some(ReprOptions {
799                        scalable: Some(rustc_abi::ScalableElt::ElementCount(scalable)),
800                        ..ReprOptions::default()
801                    });
802                }
803            } else if let ast::Meta::PathMeta(attr) = &attr
804                && attr.path().is1("rustc_scalable_vector")
805            {
806                current = Some(ReprOptions {
807                    scalable: Some(rustc_abi::ScalableElt::Container),
808                    ..ReprOptions::default()
809                });
810            }
811
812            if let Some(current) = current {
813                match &mut result {
814                    Some(existing) => merge_repr(existing, current),
815                    None => result = Some(current),
816                }
817            }
818            ControlFlow::Continue(())
819        });
820        result
821    }
822
823    /// Call this only if there are legacy const generics, to save memory.
824    #[salsa::tracked(returns(ref))]
825    pub(crate) fn legacy_const_generic_indices(
826        db: &dyn SourceDatabase,
827        owner: FunctionId,
828    ) -> Option<Box<[u32]>> {
829        let result = collect_attrs(db, owner.into(), |attr| {
830            if let ast::Meta::TokenTreeMeta(attr) = attr
831                && attr.path().is1("rustc_legacy_const_generics")
832                && let Some(tt) = attr.token_tree()
833            {
834                let result = parse_rustc_legacy_const_generics(tt);
835                ControlFlow::Break(result)
836            } else {
837                ControlFlow::Continue(())
838            }
839        });
840        result.filter(|it| !it.is_empty())
841    }
842
843    // There aren't typically many crates, so it's okay to always make this a query without a flag.
844    #[salsa::tracked(returns(ref))]
845    pub fn doc_html_root_url(db: &dyn SourceDatabase, krate: Crate) -> Option<SmolStr> {
846        let root_file_id = krate.root_file_id(db);
847        let syntax = root_file_id.parse(db).tree();
848        let extra_crate_attrs =
849            parse_extra_crate_attrs(db, krate).into_iter().flat_map(|src| src.attrs());
850
851        let mut cfg_options = None;
852        expand_cfg_attr(
853            extra_crate_attrs.chain(syntax.attrs()),
854            || cfg_options.get_or_insert(krate.cfg_options(db)),
855            |attr, _| {
856                if let ast::Meta::TokenTreeMeta(attr) = attr
857                    && attr.path().is1("doc")
858                    && let Some(tt) = attr.token_tree()
859                    && let Some(result) = DocAtom::parse(tt).into_iter().find_map(|atom| {
860                        if let DocAtom::KeyValue { key, value } = atom
861                            && key == "html_root_url"
862                        {
863                            Some(value)
864                        } else {
865                            None
866                        }
867                    })
868                {
869                    ControlFlow::Break(result)
870                } else {
871                    ControlFlow::Continue(())
872                }
873            },
874        )
875    }
876
877    #[inline]
878    pub fn target_features(db: &dyn SourceDatabase, owner: FunctionId) -> &FxHashSet<Symbol> {
879        if !AttrFlags::query(db, owner.into()).contains(AttrFlags::HAS_TARGET_FEATURE) {
880            return const { &FxHashSet::with_hasher(rustc_hash::FxBuildHasher) };
881        }
882
883        return target_features(db, owner);
884
885        #[salsa::tracked(returns(ref))]
886        fn target_features(db: &dyn SourceDatabase, owner: FunctionId) -> FxHashSet<Symbol> {
887            let mut result = FxHashSet::default();
888            collect_attrs::<Infallible>(db, owner.into(), |attr| {
889                if let ast::Meta::TokenTreeMeta(attr) = attr
890                    && attr.path().is1("target_feature")
891                    && let Some(tt) = attr.token_tree()
892                {
893                    let mut tt = TokenTreeChildren::new(&tt);
894                    while let Some(NodeOrToken::Token(enable_ident)) = tt.next()
895                        && enable_ident.text() == "enable"
896                        && let Some(NodeOrToken::Token(eq_token)) = tt.next()
897                        && eq_token.kind() == T![=]
898                        && let Some(NodeOrToken::Token(features)) = tt.next()
899                        && let Some(features) = ast::String::cast(features)
900                        && let Ok(features) = features.value()
901                    {
902                        result.extend(features.split(',').map(Symbol::intern));
903                        if tt
904                            .next()
905                            .and_then(NodeOrToken::into_token)
906                            .is_none_or(|it| it.kind() != T![,])
907                        {
908                            break;
909                        }
910                    }
911                }
912                ControlFlow::Continue(())
913            });
914            result.shrink_to_fit();
915            result
916        }
917    }
918
919    #[inline]
920    pub fn rustc_layout_scalar_valid_range(
921        db: &dyn SourceDatabase,
922        owner: AdtId,
923    ) -> RustcLayoutScalarValidRange {
924        if !AttrFlags::query(db, owner.into()).contains(AttrFlags::RUSTC_LAYOUT_SCALAR_VALID_RANGE)
925        {
926            return RustcLayoutScalarValidRange::default();
927        }
928
929        return rustc_layout_scalar_valid_range(db, owner);
930
931        #[salsa::tracked(returns(copy))]
932        fn rustc_layout_scalar_valid_range(
933            db: &dyn SourceDatabase,
934            owner: AdtId,
935        ) -> RustcLayoutScalarValidRange {
936            let mut result = RustcLayoutScalarValidRange::default();
937            collect_attrs::<Infallible>(db, owner.into(), |attr| {
938                if let ast::Meta::TokenTreeMeta(attr) = attr
939                    && let path = attr.path()
940                    && (path.is1("rustc_layout_scalar_valid_range_start")
941                        || path.is1("rustc_layout_scalar_valid_range_end"))
942                    && let Some(tt) = attr.token_tree()
943                    && let tt = TokenTreeChildren::new(&tt)
944                    && let Ok(NodeOrToken::Token(value)) = Itertools::exactly_one(tt)
945                    && let Some(value) = ast::IntNumber::cast(value)
946                    && let Ok(value) = value.value()
947                {
948                    if path.is1("rustc_layout_scalar_valid_range_start") {
949                        result.start = Some(value)
950                    } else {
951                        result.end = Some(value);
952                    }
953                }
954                ControlFlow::Continue(())
955            });
956            result
957        }
958    }
959
960    #[inline]
961    pub fn doc_aliases(
962        self,
963        db: &dyn SourceDatabase,
964        owner: Either<AttrDefId, FieldId>,
965    ) -> &[Symbol] {
966        if !self.contains(AttrFlags::HAS_DOC_ALIASES) {
967            return &[];
968        }
969        return match owner {
970            Either::Left(it) => doc_aliases(db, it),
971            Either::Right(field) => fields_doc_aliases(db, field.parent)
972                .get(field.local_id)
973                .map(|it| &**it)
974                .unwrap_or_default(),
975        };
976
977        #[salsa::tracked(returns(ref))]
978        fn doc_aliases(db: &dyn SourceDatabase, owner: AttrDefId) -> Box<[Symbol]> {
979            let mut result = Vec::new();
980            collect_attrs::<Infallible>(db, owner, |attr| extract_doc_aliases(&mut result, attr));
981            result.into_boxed_slice()
982        }
983
984        #[salsa::tracked(returns(ref))]
985        fn fields_doc_aliases(
986            db: &dyn SourceDatabase,
987            variant: VariantId,
988        ) -> ArenaMap<LocalFieldId, Box<[Symbol]>> {
989            collect_field_attrs(db, variant, |cfg_options, field| {
990                let mut result = Vec::new();
991                expand_cfg_attr(
992                    field.value.attrs(),
993                    || cfg_options,
994                    |attr, _| extract_doc_aliases(&mut result, attr),
995                );
996                result.into_boxed_slice()
997            })
998        }
999    }
1000
1001    #[inline]
1002    pub fn cfgs(
1003        self,
1004        db: &dyn SourceDatabase,
1005        owner: Either<AttrDefId, FieldId>,
1006    ) -> Option<&CfgExpr> {
1007        if !self.contains(AttrFlags::HAS_CFG) {
1008            return None;
1009        }
1010        return match owner {
1011            Either::Left(it) => cfgs(db, it).as_ref(),
1012            Either::Right(field) => {
1013                fields_cfgs(db, field.parent).get(field.local_id).and_then(|it| it.as_ref())
1014            }
1015        };
1016
1017        // We LRU this query because it is only used by IDE.
1018        #[salsa::tracked(returns(ref), lru = 250)]
1019        fn cfgs(db: &dyn SourceDatabase, owner: AttrDefId) -> Option<CfgExpr> {
1020            let mut result = Vec::new();
1021            collect_attrs::<Infallible>(db, owner, |attr| extract_cfgs(&mut result, attr));
1022            match result.len() {
1023                0 => None,
1024                1 => result.into_iter().next(),
1025                _ => Some(CfgExpr::All(result.into_boxed_slice())),
1026            }
1027        }
1028
1029        // We LRU this query because it is only used by IDE.
1030        #[salsa::tracked(returns(ref), lru = 50)]
1031        fn fields_cfgs(
1032            db: &dyn SourceDatabase,
1033            variant: VariantId,
1034        ) -> ArenaMap<LocalFieldId, Option<CfgExpr>> {
1035            collect_field_attrs(db, variant, |cfg_options, field| {
1036                let mut result = Vec::new();
1037                expand_cfg_attr(
1038                    field.value.attrs(),
1039                    || cfg_options,
1040                    |attr, _| extract_cfgs(&mut result, attr),
1041                );
1042                match result.len() {
1043                    0 => None,
1044                    1 => result.into_iter().next(),
1045                    _ => Some(CfgExpr::All(result.into_boxed_slice())),
1046                }
1047            })
1048        }
1049    }
1050
1051    #[inline]
1052    pub fn doc_keyword(db: &dyn SourceDatabase, owner: ModuleId) -> Option<Symbol> {
1053        if !AttrFlags::query(db, AttrDefId::ModuleId(owner)).contains(AttrFlags::HAS_DOC_KEYWORD) {
1054            return None;
1055        }
1056        return doc_keyword(db, owner);
1057
1058        #[salsa::tracked(returns(clone))]
1059        fn doc_keyword(db: &dyn SourceDatabase, owner: ModuleId) -> Option<Symbol> {
1060            collect_attrs(db, AttrDefId::ModuleId(owner), |attr| {
1061                if let ast::Meta::TokenTreeMeta(attr) = attr
1062                    && attr.path().is1("doc")
1063                    && let Some(tt) = attr.token_tree()
1064                {
1065                    for atom in DocAtom::parse(tt) {
1066                        if let DocAtom::KeyValue { key, value } = atom
1067                            && key == "keyword"
1068                        {
1069                            return ControlFlow::Break(Symbol::intern(&value));
1070                        }
1071                    }
1072                }
1073                ControlFlow::Continue(())
1074            })
1075        }
1076    }
1077
1078    // We LRU this query because it is only used by IDE.
1079    #[salsa::tracked(returns(as_deref), lru = 250)]
1080    pub fn docs(db: &dyn SourceDatabase, owner: AttrDefId) -> Option<Box<Docs>> {
1081        let (source, outer_mod_decl, _extra_crate_attrs, krate) = attrs_source(db, owner);
1082        let inner_attrs_node = source.value.inner_attributes_node();
1083        // Note: we don't have to pass down `_extra_crate_attrs` here, since `extract_docs`
1084        // does not handle crate-level attributes related to docs.
1085        // See: https://doc.rust-lang.org/rustdoc/write-documentation/the-doc-attribute.html#at-the-crate-level
1086        self::docs::extract_docs(
1087            db,
1088            krate,
1089            &|| resolver_for_attr_def_id(db, owner),
1090            &|| krate.cfg_options(db),
1091            source,
1092            outer_mod_decl,
1093            inner_attrs_node,
1094        )
1095    }
1096
1097    #[inline]
1098    pub fn field_docs(db: &dyn SourceDatabase, field: FieldId) -> Option<&Docs> {
1099        Self::fields_docs(db, field.parent).get(field.local_id).and_then(|it| it.as_deref())
1100    }
1101
1102    // We LRU this query because it is only used by IDE.
1103    #[salsa::tracked(returns(ref), lru = 50)]
1104    pub fn fields_docs(
1105        db: &dyn SourceDatabase,
1106        variant: VariantId,
1107    ) -> ArenaMap<LocalFieldId, Option<Box<Docs>>> {
1108        let krate = variant.module(db).krate(db);
1109        collect_field_attrs(db, variant, |cfg_options, field| {
1110            self::docs::extract_docs(
1111                db,
1112                krate,
1113                &|| variant.resolver(db),
1114                &|| cfg_options,
1115                field,
1116                None,
1117                None,
1118            )
1119        })
1120    }
1121
1122    #[inline]
1123    pub fn derive_info(db: &dyn SourceDatabase, owner: MacroId) -> Option<&DeriveInfo> {
1124        if !AttrFlags::query(db, owner.into()).contains(AttrFlags::IS_DERIVE_OR_BUILTIN_MACRO) {
1125            return None;
1126        }
1127
1128        return derive_info(db, owner).as_ref();
1129
1130        #[salsa::tracked(returns(ref))]
1131        fn derive_info(db: &dyn SourceDatabase, owner: MacroId) -> Option<DeriveInfo> {
1132            collect_attrs(db, owner.into(), |attr| {
1133                if let ast::Meta::TokenTreeMeta(attr) = attr
1134                    && (attr.path().is1("proc_macro_derive")
1135                        || attr.path().is1("rustc_builtin_macro"))
1136                    && let Some(tt) = attr.token_tree()
1137                    && let mut tt = TokenTreeChildren::new(&tt)
1138                    && let Some(NodeOrToken::Token(trait_name)) = tt.next()
1139                    && trait_name.kind().is_any_identifier()
1140                {
1141                    let trait_name = Symbol::intern(trait_name.text());
1142
1143                    let helpers = if let Some(NodeOrToken::Token(comma)) = tt.next()
1144                        && comma.kind() == T![,]
1145                        && let Some(NodeOrToken::Token(attributes)) = tt.next()
1146                        && attributes.text() == "attributes"
1147                        && let Some(NodeOrToken::Node(attributes)) = tt.next()
1148                    {
1149                        attributes
1150                            .syntax()
1151                            .children_with_tokens()
1152                            .filter_map(NodeOrToken::into_token)
1153                            .filter(|it| it.kind().is_any_identifier())
1154                            .map(|it| Symbol::intern(it.text()))
1155                            .collect::<Box<[_]>>()
1156                    } else {
1157                        Box::new([])
1158                    };
1159
1160                    ControlFlow::Break(DeriveInfo { trait_name, helpers })
1161                } else {
1162                    ControlFlow::Continue(())
1163                }
1164            })
1165        }
1166    }
1167
1168    pub fn unstable_feature(self, db: &dyn SourceDatabase, owner: AttrDefId) -> Option<Symbol> {
1169        if !self.contains(AttrFlags::IS_UNSTABLE) {
1170            return None;
1171        }
1172
1173        return unstable_feature(db, owner);
1174
1175        #[salsa::tracked(returns(clone))]
1176        fn unstable_feature(db: &dyn SourceDatabase, owner: AttrDefId) -> Option<Symbol> {
1177            collect_attrs(db, owner, |attr| {
1178                if let ast::Meta::TokenTreeMeta(attr) = attr
1179                    && let path = attr.path()
1180                    && path.is1("unstable")
1181                    && let Some(tt) = attr.token_tree()
1182                {
1183                    let mut tt = TokenTreeChildren::new(&tt);
1184                    // Technically the `feature = "..."` always comes first, but it's not a requirement.
1185                    while let Some(token) = tt.next() {
1186                        if let NodeOrToken::Token(token) = token
1187                            && token.text() == "feature"
1188                            && let Some(NodeOrToken::Token(eq)) = tt.next()
1189                            && eq.kind() == T![=]
1190                            && let Some(NodeOrToken::Token(feature)) = tt.next()
1191                            && let Some(feature) = ast::String::cast(feature)
1192                            && let Ok(feature) = feature.value()
1193                        {
1194                            return ControlFlow::Break(Symbol::intern(&feature));
1195                        }
1196                    }
1197                }
1198                ControlFlow::Continue(())
1199            })
1200        }
1201    }
1202
1203    /// Returns `None` if there is no `#[must_use]`, `Some(None)` if there is a `#[must_use]` without a message,
1204    /// and `Some(Some(message))` if there is a `#[must_use]` with a message.
1205    pub fn must_use_message(db: &dyn SourceDatabase, owner: AttrDefId) -> Option<Option<&str>> {
1206        if !AttrFlags::query(db, owner).contains(AttrFlags::IS_MUST_USE) {
1207            return None;
1208        }
1209        return Some(must_use_message(db, owner));
1210
1211        #[salsa::tracked(returns(as_deref))]
1212        fn must_use_message(db: &dyn SourceDatabase, owner: AttrDefId) -> Option<Box<str>> {
1213            collect_attrs(db, owner, |attr| {
1214                if let ast::Meta::KeyValueMeta(attr) = attr
1215                    && attr.path().is1("must_use")
1216                    && let Some(message) = attr.value_string()
1217                {
1218                    return ControlFlow::Break(Box::from(&*message));
1219                }
1220                ControlFlow::Continue(())
1221            })
1222        }
1223    }
1224
1225    pub fn must_implement_one_of(db: &dyn SourceDatabase, owner: TraitId) -> Option<&[Name]> {
1226        if !AttrFlags::query(db, owner.into()).contains(AttrFlags::HAS_RUSTC_MUST_IMPLEMENT_ONE_OF)
1227        {
1228            return None;
1229        }
1230        return must_implement_one_of(db, owner);
1231
1232        #[salsa::tracked(returns(as_deref))]
1233        pub fn must_implement_one_of(
1234            db: &dyn SourceDatabase,
1235            owner: TraitId,
1236        ) -> Option<Box<[Name]>> {
1237            collect_attrs(db, owner.into(), |attr| {
1238                if let ast::Meta::TokenTreeMeta(attr) = attr
1239                    && attr.path().is1("rustc_must_implement_one_of")
1240                    && let Some(tt) = attr.token_tree()
1241                    && let names = tt
1242                        .token_trees_and_tokens()
1243                        .filter_map(|it| ast::Ident::cast(it.into_token()?))
1244                        .map(|name| Name::new_symbol_root(Symbol::intern(name.text())))
1245                        .collect::<Box<[_]>>()
1246                    && !names.is_empty()
1247                {
1248                    return ControlFlow::Break(names);
1249                }
1250                ControlFlow::Continue(())
1251            })
1252        }
1253    }
1254}
1255
1256fn merge_repr(this: &mut ReprOptions, other: ReprOptions) {
1257    let ReprOptions { int, align, pack, flags, scalable, field_shuffle_seed: _ } = this;
1258    flags.insert(other.flags);
1259    *align = (*align).max(other.align);
1260    *pack = match (*pack, other.pack) {
1261        (Some(pack), None) | (None, Some(pack)) => Some(pack),
1262        _ => (*pack).min(other.pack),
1263    };
1264    if other.int.is_some() {
1265        *int = other.int;
1266    }
1267    if other.scalable.is_some() {
1268        *scalable = other.scalable;
1269    }
1270}
1271
1272fn parse_repr_tt(tt: &ast::TokenTree) -> Option<ReprOptions> {
1273    use crate::builtin_type::{BuiltinInt, BuiltinUint};
1274    use rustc_abi::{Align, Integer, IntegerType, ReprFlags, ReprOptions};
1275
1276    let mut tts = TokenTreeChildren::new(tt).peekable();
1277
1278    let mut acc = ReprOptions::default();
1279    while let Some(tt) = tts.next() {
1280        let NodeOrToken::Token(ident) = tt else {
1281            continue;
1282        };
1283        if !ident.kind().is_any_identifier() {
1284            continue;
1285        }
1286        let repr = match ident.text() {
1287            "packed" => {
1288                let pack = if let Some(NodeOrToken::Node(tt)) = tts.peek() {
1289                    let tt = tt.clone();
1290                    tts.next();
1291                    let mut tt_iter = TokenTreeChildren::new(&tt);
1292                    if let Some(NodeOrToken::Token(lit)) = tt_iter.next()
1293                        && let Some(lit) = ast::IntNumber::cast(lit)
1294                        && let Ok(lit) = lit.value()
1295                        && let Ok(lit) = lit.try_into()
1296                    {
1297                        lit
1298                    } else {
1299                        0
1300                    }
1301                } else {
1302                    0
1303                };
1304                let pack = Some(Align::from_bytes(pack).unwrap_or(Align::ONE));
1305                ReprOptions { pack, ..Default::default() }
1306            }
1307            "align" => {
1308                let mut align = None;
1309                if let Some(NodeOrToken::Node(tt)) = tts.peek() {
1310                    let tt = tt.clone();
1311                    tts.next();
1312                    let mut tt_iter = TokenTreeChildren::new(&tt);
1313                    if let Some(NodeOrToken::Token(lit)) = tt_iter.next()
1314                        && let Some(lit) = ast::IntNumber::cast(lit)
1315                        && let Ok(lit) = lit.value()
1316                        && let Ok(lit) = lit.try_into()
1317                    {
1318                        align = Align::from_bytes(lit).ok();
1319                    }
1320                }
1321                ReprOptions { align, ..Default::default() }
1322            }
1323            "C" => ReprOptions { flags: ReprFlags::IS_C, ..Default::default() },
1324            "transparent" => ReprOptions { flags: ReprFlags::IS_TRANSPARENT, ..Default::default() },
1325            "simd" => ReprOptions { flags: ReprFlags::IS_SIMD, ..Default::default() },
1326            repr => {
1327                let mut int = None;
1328                if let Some(builtin) = BuiltinInt::from_suffix(repr)
1329                    .map(Either::Left)
1330                    .or_else(|| BuiltinUint::from_suffix(repr).map(Either::Right))
1331                {
1332                    int = Some(match builtin {
1333                        Either::Left(bi) => match bi {
1334                            BuiltinInt::Isize => IntegerType::Pointer(true),
1335                            BuiltinInt::I8 => IntegerType::Fixed(Integer::I8, true),
1336                            BuiltinInt::I16 => IntegerType::Fixed(Integer::I16, true),
1337                            BuiltinInt::I32 => IntegerType::Fixed(Integer::I32, true),
1338                            BuiltinInt::I64 => IntegerType::Fixed(Integer::I64, true),
1339                            BuiltinInt::I128 => IntegerType::Fixed(Integer::I128, true),
1340                        },
1341                        Either::Right(bu) => match bu {
1342                            BuiltinUint::Usize => IntegerType::Pointer(false),
1343                            BuiltinUint::U8 => IntegerType::Fixed(Integer::I8, false),
1344                            BuiltinUint::U16 => IntegerType::Fixed(Integer::I16, false),
1345                            BuiltinUint::U32 => IntegerType::Fixed(Integer::I32, false),
1346                            BuiltinUint::U64 => IntegerType::Fixed(Integer::I64, false),
1347                            BuiltinUint::U128 => IntegerType::Fixed(Integer::I128, false),
1348                        },
1349                    });
1350                }
1351                ReprOptions { int, ..Default::default() }
1352            }
1353        };
1354        merge_repr(&mut acc, repr);
1355    }
1356
1357    Some(acc)
1358}
1359
1360fn parse_rustc_legacy_const_generics(tt: ast::TokenTree) -> Box<[u32]> {
1361    TokenTreeChildren::new(&tt)
1362        .filter_map(|param| {
1363            ast::IntNumber::cast(param.into_token()?)?.value().ok()?.try_into().ok()
1364        })
1365        .collect()
1366}
1367
1368#[derive(Debug)]
1369enum DocAtom {
1370    /// eg. `#[doc(hidden)]`
1371    Flag(SmolStr),
1372    /// eg. `#[doc(alias = "it")]`
1373    ///
1374    /// Note that a key can have multiple values that are all considered "active" at the same time.
1375    /// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`.
1376    KeyValue { key: SmolStr, value: SmolStr },
1377    /// eg. `#[doc(alias("x", "y"))]`
1378    Alias(Vec<SmolStr>),
1379}
1380
1381impl DocAtom {
1382    fn parse(tt: ast::TokenTree) -> SmallVec<[DocAtom; 1]> {
1383        let mut iter = TokenTreeChildren::new(&tt).peekable();
1384        let mut result = SmallVec::new();
1385        while iter.peek().is_some() {
1386            if let Some(expr) = next_doc_expr(&mut iter) {
1387                result.push(expr);
1388            }
1389        }
1390        result
1391    }
1392}
1393
1394fn next_doc_expr(it: &mut Peekable<TokenTreeChildren>) -> Option<DocAtom> {
1395    let name = match it.next() {
1396        Some(NodeOrToken::Token(token)) if token.kind().is_any_identifier() => {
1397            SmolStr::new(token.text())
1398        }
1399        _ => return None,
1400    };
1401
1402    let ret = match it.peek() {
1403        Some(NodeOrToken::Token(eq)) if eq.kind() == T![=] => {
1404            it.next();
1405            if let Some(NodeOrToken::Token(value)) = it.next()
1406                && let Some(value) = ast::String::cast(value)
1407                && let Ok(value) = value.value()
1408            {
1409                DocAtom::KeyValue { key: name, value: SmolStr::new(&*value) }
1410            } else {
1411                return None;
1412            }
1413        }
1414        Some(NodeOrToken::Node(subtree)) => {
1415            if name != "alias" {
1416                return None;
1417            }
1418            let aliases = TokenTreeChildren::new(subtree)
1419                .filter_map(|alias| {
1420                    Some(SmolStr::new(&*ast::String::cast(alias.into_token()?)?.value().ok()?))
1421                })
1422                .collect();
1423            it.next();
1424            DocAtom::Alias(aliases)
1425        }
1426        _ => DocAtom::Flag(name),
1427    };
1428    Some(ret)
1429}
1430
1431#[cfg(test)]
1432mod tests {
1433    use test_fixture::WithFixture;
1434
1435    use crate::AttrDefId;
1436    use crate::attrs::AttrFlags;
1437    use crate::test_db::TestDB;
1438
1439    #[test]
1440    fn crate_attrs() {
1441        let fixture = r#"
1442//- /lib.rs crate:foo crate-attr:no_std crate-attr:cfg(target_arch="x86")
1443        "#;
1444        let (db, file_id) = TestDB::with_single_file(fixture);
1445        let module = db.module_for_file(file_id.file_id(&db));
1446        let attrs = AttrFlags::query(&db, AttrDefId::ModuleId(module));
1447        assert!(attrs.contains(AttrFlags::IS_NO_STD | AttrFlags::HAS_CFG));
1448    }
1449}