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//! An exception to this model that is also defined in this module is documentation (doc
16//! comments and `#[doc = "..."]` attributes). But it also has a more compact form than
17//! the attribute: a concatenated string of the full docs as well as a source map
18//! to map it back to AST (which is needed for things like resolving links in doc comments
19//! and highlight injection). The lowering and upmapping of doc comments is a bit complicated,
20//! but it is encapsulated in the [`Docs`] struct.
21
22use std::{
23    convert::Infallible,
24    iter::Peekable,
25    ops::{ControlFlow, Range},
26};
27
28use base_db::Crate;
29use cfg::{CfgExpr, CfgOptions};
30use either::Either;
31use hir_expand::{
32    HirFileId, InFile, Lookup,
33    attrs::{Meta, expand_cfg_attr, expand_cfg_attr_with_doc_comments},
34};
35use intern::Symbol;
36use itertools::Itertools;
37use la_arena::ArenaMap;
38use rustc_abi::ReprOptions;
39use rustc_hash::FxHashSet;
40use smallvec::SmallVec;
41use syntax::{
42    AstNode, AstToken, NodeOrToken, SmolStr, SourceFile, SyntaxNode, SyntaxToken, T,
43    ast::{self, AttrDocCommentIter, HasAttrs, IsString, TokenTreeChildren},
44};
45use tt::{TextRange, TextSize};
46
47use crate::{
48    AdtId, AstIdLoc, AttrDefId, FieldId, FunctionId, GenericDefId, HasModule, LifetimeParamId,
49    LocalFieldId, MacroId, ModuleId, TypeOrConstParamId, VariantId,
50    db::DefDatabase,
51    hir::generics::{GenericParams, LocalLifetimeParamId, LocalTypeOrConstParamId},
52    nameres::ModuleOrigin,
53    src::{HasChildSource, HasSource},
54};
55
56#[inline]
57fn attrs_from_ast_id_loc<N: AstNode + Into<ast::AnyHasAttrs>>(
58    db: &dyn DefDatabase,
59    lookup: impl Lookup<Database = dyn DefDatabase, Data = impl AstIdLoc<Ast = N> + HasModule>,
60) -> (InFile<ast::AnyHasAttrs>, Crate) {
61    let loc = lookup.lookup(db);
62    let source = loc.source(db);
63    let krate = loc.krate(db);
64    (source.map(|it| it.into()), krate)
65}
66
67#[inline]
68fn extract_doc_tt_attr(attr_flags: &mut AttrFlags, tt: ast::TokenTree) {
69    for atom in DocAtom::parse(tt) {
70        match atom {
71            DocAtom::Flag(flag) => match &*flag {
72                "notable_trait" => attr_flags.insert(AttrFlags::IS_DOC_NOTABLE_TRAIT),
73                "hidden" => attr_flags.insert(AttrFlags::IS_DOC_HIDDEN),
74                _ => {}
75            },
76            DocAtom::KeyValue { key, value: _ } => match &*key {
77                "alias" => attr_flags.insert(AttrFlags::HAS_DOC_ALIASES),
78                "keyword" => attr_flags.insert(AttrFlags::HAS_DOC_KEYWORD),
79                _ => {}
80            },
81            DocAtom::Alias(_) => attr_flags.insert(AttrFlags::HAS_DOC_ALIASES),
82        }
83    }
84}
85
86fn extract_ra_completions(attr_flags: &mut AttrFlags, tt: ast::TokenTree) {
87    let tt = TokenTreeChildren::new(&tt);
88    if let Ok(NodeOrToken::Token(option)) = Itertools::exactly_one(tt)
89        && option.kind().is_any_identifier()
90    {
91        match option.text() {
92            "ignore_flyimport" => attr_flags.insert(AttrFlags::COMPLETE_IGNORE_FLYIMPORT),
93            "ignore_methods" => attr_flags.insert(AttrFlags::COMPLETE_IGNORE_METHODS),
94            "ignore_flyimport_methods" => {
95                attr_flags.insert(AttrFlags::COMPLETE_IGNORE_FLYIMPORT_METHODS)
96            }
97            _ => {}
98        }
99    }
100}
101
102fn extract_rustc_skip_during_method_dispatch(attr_flags: &mut AttrFlags, tt: ast::TokenTree) {
103    let iter = TokenTreeChildren::new(&tt);
104    for kind in iter {
105        if let NodeOrToken::Token(kind) = kind
106            && kind.kind().is_any_identifier()
107        {
108            match kind.text() {
109                "array" => attr_flags.insert(AttrFlags::RUSTC_SKIP_ARRAY_DURING_METHOD_DISPATCH),
110                "boxed_slice" => {
111                    attr_flags.insert(AttrFlags::RUSTC_SKIP_BOXED_SLICE_DURING_METHOD_DISPATCH)
112                }
113                _ => {}
114            }
115        }
116    }
117}
118
119#[inline]
120fn match_attr_flags(attr_flags: &mut AttrFlags, attr: Meta) -> ControlFlow<Infallible> {
121    match attr {
122        Meta::NamedKeyValue { name: Some(name), value, .. } => match name.text() {
123            "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED),
124            "lang" => attr_flags.insert(AttrFlags::LANG_ITEM),
125            "path" => attr_flags.insert(AttrFlags::HAS_PATH),
126            "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE),
127            "export_name" => {
128                if let Some(value) = value
129                    && let Some(value) = ast::String::cast(value)
130                    && let Ok(value) = value.value()
131                    && *value == *"main"
132                {
133                    attr_flags.insert(AttrFlags::IS_EXPORT_NAME_MAIN);
134                }
135            }
136            _ => {}
137        },
138        Meta::TokenTree { path, tt } => match path.segments.len() {
139            1 => match path.segments[0].text() {
140                "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED),
141                "cfg" => attr_flags.insert(AttrFlags::HAS_CFG),
142                "doc" => extract_doc_tt_attr(attr_flags, tt),
143                "repr" => attr_flags.insert(AttrFlags::HAS_REPR),
144                "target_feature" => attr_flags.insert(AttrFlags::HAS_TARGET_FEATURE),
145                "proc_macro_derive" | "rustc_builtin_macro" => {
146                    attr_flags.insert(AttrFlags::IS_DERIVE_OR_BUILTIN_MACRO)
147                }
148                "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE),
149                "rustc_layout_scalar_valid_range_start" | "rustc_layout_scalar_valid_range_end" => {
150                    attr_flags.insert(AttrFlags::RUSTC_LAYOUT_SCALAR_VALID_RANGE)
151                }
152                "rustc_legacy_const_generics" => {
153                    attr_flags.insert(AttrFlags::HAS_LEGACY_CONST_GENERICS)
154                }
155                "rustc_skip_during_method_dispatch" => {
156                    extract_rustc_skip_during_method_dispatch(attr_flags, tt)
157                }
158                "rustc_deprecated_safe_2024" => {
159                    attr_flags.insert(AttrFlags::RUSTC_DEPRECATED_SAFE_2024)
160                }
161                _ => {}
162            },
163            2 => match path.segments[0].text() {
164                "rust_analyzer" => match path.segments[1].text() {
165                    "completions" => extract_ra_completions(attr_flags, tt),
166                    _ => {}
167                },
168                _ => {}
169            },
170            _ => {}
171        },
172        Meta::Path { path } => {
173            match path.segments.len() {
174                1 => match path.segments[0].text() {
175                    "rustc_has_incoherent_inherent_impls" => {
176                        attr_flags.insert(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS)
177                    }
178                    "rustc_allow_incoherent_impl" => {
179                        attr_flags.insert(AttrFlags::RUSTC_ALLOW_INCOHERENT_IMPL)
180                    }
181                    "fundamental" => attr_flags.insert(AttrFlags::FUNDAMENTAL),
182                    "no_std" => attr_flags.insert(AttrFlags::IS_NO_STD),
183                    "may_dangle" => attr_flags.insert(AttrFlags::MAY_DANGLE),
184                    "rustc_paren_sugar" => attr_flags.insert(AttrFlags::RUSTC_PAREN_SUGAR),
185                    "rustc_coinductive" => attr_flags.insert(AttrFlags::RUSTC_COINDUCTIVE),
186                    "rustc_force_inline" => attr_flags.insert(AttrFlags::RUSTC_FORCE_INLINE),
187                    "unstable" => attr_flags.insert(AttrFlags::IS_UNSTABLE),
188                    "deprecated" => attr_flags.insert(AttrFlags::IS_DEPRECATED),
189                    "macro_export" => attr_flags.insert(AttrFlags::IS_MACRO_EXPORT),
190                    "no_mangle" => attr_flags.insert(AttrFlags::NO_MANGLE),
191                    "non_exhaustive" => attr_flags.insert(AttrFlags::NON_EXHAUSTIVE),
192                    "ignore" => attr_flags.insert(AttrFlags::IS_IGNORE),
193                    "bench" => attr_flags.insert(AttrFlags::IS_BENCH),
194                    "rustc_const_panic_str" => attr_flags.insert(AttrFlags::RUSTC_CONST_PANIC_STR),
195                    "rustc_intrinsic" => attr_flags.insert(AttrFlags::RUSTC_INTRINSIC),
196                    "rustc_safe_intrinsic" => attr_flags.insert(AttrFlags::RUSTC_SAFE_INTRINSIC),
197                    "rustc_intrinsic_must_be_overridden" => {
198                        attr_flags.insert(AttrFlags::RUSTC_INTRINSIC_MUST_BE_OVERRIDDEN)
199                    }
200                    "rustc_allocator" => attr_flags.insert(AttrFlags::RUSTC_ALLOCATOR),
201                    "rustc_deallocator" => attr_flags.insert(AttrFlags::RUSTC_DEALLOCATOR),
202                    "rustc_reallocator" => attr_flags.insert(AttrFlags::RUSTC_REALLOCATOR),
203                    "rustc_allocator_zeroed" => {
204                        attr_flags.insert(AttrFlags::RUSTC_ALLOCATOR_ZEROED)
205                    }
206                    "rustc_reservation_impl" => {
207                        attr_flags.insert(AttrFlags::RUSTC_RESERVATION_IMPL)
208                    }
209                    "rustc_deprecated_safe_2024" => {
210                        attr_flags.insert(AttrFlags::RUSTC_DEPRECATED_SAFE_2024)
211                    }
212                    "rustc_skip_array_during_method_dispatch" => {
213                        attr_flags.insert(AttrFlags::RUSTC_SKIP_ARRAY_DURING_METHOD_DISPATCH)
214                    }
215                    _ => {}
216                },
217                2 => match path.segments[0].text() {
218                    "rust_analyzer" => match path.segments[1].text() {
219                        "skip" => attr_flags.insert(AttrFlags::RUST_ANALYZER_SKIP),
220                        _ => {}
221                    },
222                    _ => {}
223                },
224                _ => {}
225            }
226
227            if path.is_test {
228                attr_flags.insert(AttrFlags::IS_TEST);
229            }
230        }
231        _ => {}
232    };
233    ControlFlow::Continue(())
234}
235
236bitflags::bitflags! {
237    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
238    pub struct AttrFlags: u64 {
239        const RUST_ANALYZER_SKIP = 1 << 0;
240
241        const LANG_ITEM = 1 << 1;
242
243        const HAS_DOC_ALIASES = 1 << 2;
244        const HAS_DOC_KEYWORD = 1 << 3;
245        const IS_DOC_NOTABLE_TRAIT = 1 << 4;
246        const IS_DOC_HIDDEN = 1 << 5;
247
248        const RUSTC_HAS_INCOHERENT_INHERENT_IMPLS = 1 << 6;
249        const RUSTC_ALLOW_INCOHERENT_IMPL = 1 << 7;
250        const FUNDAMENTAL = 1 << 8;
251        const RUSTC_SKIP_ARRAY_DURING_METHOD_DISPATCH = 1 << 9;
252        const RUSTC_SKIP_BOXED_SLICE_DURING_METHOD_DISPATCH = 1 << 10;
253        const HAS_REPR = 1 << 11;
254        const HAS_TARGET_FEATURE = 1 << 12;
255        const RUSTC_DEPRECATED_SAFE_2024 = 1 << 13;
256        const HAS_LEGACY_CONST_GENERICS = 1 << 14;
257        const NO_MANGLE = 1 << 15;
258        const NON_EXHAUSTIVE = 1 << 16;
259        const RUSTC_RESERVATION_IMPL = 1 << 17;
260        const RUSTC_CONST_PANIC_STR = 1 << 18;
261        const MAY_DANGLE = 1 << 19;
262
263        const RUSTC_INTRINSIC = 1 << 20;
264        const RUSTC_SAFE_INTRINSIC = 1 << 21;
265        const RUSTC_INTRINSIC_MUST_BE_OVERRIDDEN = 1 << 22;
266        const RUSTC_ALLOCATOR = 1 << 23;
267        const RUSTC_DEALLOCATOR = 1 << 24;
268        const RUSTC_REALLOCATOR = 1 << 25;
269        const RUSTC_ALLOCATOR_ZEROED = 1 << 26;
270
271        const IS_UNSTABLE = 1 << 27;
272        const IS_IGNORE = 1 << 28;
273        // FIXME: `IS_TEST` and `IS_BENCH` should be based on semantic information, not textual match.
274        const IS_BENCH = 1 << 29;
275        const IS_TEST = 1 << 30;
276        const IS_EXPORT_NAME_MAIN = 1 << 31;
277        const IS_MACRO_EXPORT = 1 << 32;
278        const IS_NO_STD = 1 << 33;
279        const IS_DERIVE_OR_BUILTIN_MACRO = 1 << 34;
280        const IS_DEPRECATED = 1 << 35;
281        const HAS_PATH = 1 << 36;
282        const HAS_CFG = 1 << 37;
283
284        const COMPLETE_IGNORE_FLYIMPORT = 1 << 38;
285        const COMPLETE_IGNORE_FLYIMPORT_METHODS = 1 << 39;
286        const COMPLETE_IGNORE_METHODS = 1 << 40;
287
288        const RUSTC_LAYOUT_SCALAR_VALID_RANGE = 1 << 41;
289        const RUSTC_PAREN_SUGAR = 1 << 42;
290        const RUSTC_COINDUCTIVE = 1 << 43;
291        const RUSTC_FORCE_INLINE = 1 << 44;
292    }
293}
294
295pub fn parse_extra_crate_attrs(db: &dyn DefDatabase, krate: Crate) -> Option<SourceFile> {
296    let crate_data = krate.data(db);
297    let crate_attrs = &crate_data.crate_attrs;
298    if crate_attrs.is_empty() {
299        return None;
300    }
301    // All attributes are already enclosed in `#![]`.
302    let combined = crate_attrs.concat();
303    let p = SourceFile::parse(&combined, crate_data.edition);
304
305    let errs = p.errors();
306    if !errs.is_empty() {
307        let base_msg = "Failed to parse extra crate-level attribute";
308        let crate_name =
309            krate.extra_data(db).display_name.as_ref().map_or("{unknown}", |name| name.as_str());
310        let mut errs = errs.iter().peekable();
311        let mut offset = TextSize::from(0);
312        for raw_attr in crate_attrs {
313            let attr_end = offset + TextSize::of(&**raw_attr);
314            if errs.peeking_take_while(|e| e.range().start() < attr_end).count() > 0 {
315                tracing::error!("{base_msg} {raw_attr} for crate {crate_name}");
316            }
317            offset = attr_end
318        }
319        return None;
320    }
321
322    Some(p.tree())
323}
324
325fn attrs_source(
326    db: &dyn DefDatabase,
327    owner: AttrDefId,
328) -> (InFile<ast::AnyHasAttrs>, Option<InFile<ast::Module>>, Option<SourceFile>, Crate) {
329    let (owner, krate) = match owner {
330        AttrDefId::ModuleId(id) => {
331            let def_map = id.def_map(db);
332            let krate = def_map.krate();
333            let (definition, declaration, extra_crate_attrs) = match def_map[id].origin {
334                ModuleOrigin::CrateRoot { definition } => {
335                    let definition_source = db.parse(definition).tree();
336                    let definition = InFile::new(definition.into(), definition_source.into());
337                    let extra_crate_attrs = parse_extra_crate_attrs(db, krate);
338                    (definition, None, extra_crate_attrs)
339                }
340                ModuleOrigin::File { declaration, declaration_tree_id, definition, .. } => {
341                    let definition_source = db.parse(definition).tree();
342                    let definition = InFile::new(definition.into(), definition_source.into());
343                    let declaration = InFile::new(declaration_tree_id.file_id(), declaration);
344                    let declaration = declaration.with_value(declaration.to_node(db));
345                    (definition, Some(declaration), None)
346                }
347                ModuleOrigin::Inline { definition_tree_id, definition } => {
348                    let definition = InFile::new(definition_tree_id.file_id(), definition);
349                    let definition = definition.with_value(definition.to_node(db).into());
350                    (definition, None, None)
351                }
352                ModuleOrigin::BlockExpr { block, .. } => {
353                    let definition = block.to_node(db);
354                    (block.with_value(definition.into()), None, None)
355                }
356            };
357            return (definition, declaration, extra_crate_attrs, krate);
358        }
359        AttrDefId::AdtId(AdtId::StructId(it)) => attrs_from_ast_id_loc(db, it),
360        AttrDefId::AdtId(AdtId::UnionId(it)) => attrs_from_ast_id_loc(db, it),
361        AttrDefId::AdtId(AdtId::EnumId(it)) => attrs_from_ast_id_loc(db, it),
362        AttrDefId::FunctionId(it) => attrs_from_ast_id_loc(db, it),
363        AttrDefId::EnumVariantId(it) => attrs_from_ast_id_loc(db, it),
364        AttrDefId::StaticId(it) => attrs_from_ast_id_loc(db, it),
365        AttrDefId::ConstId(it) => attrs_from_ast_id_loc(db, it),
366        AttrDefId::TraitId(it) => attrs_from_ast_id_loc(db, it),
367        AttrDefId::TypeAliasId(it) => attrs_from_ast_id_loc(db, it),
368        AttrDefId::MacroId(MacroId::MacroRulesId(it)) => attrs_from_ast_id_loc(db, it),
369        AttrDefId::MacroId(MacroId::Macro2Id(it)) => attrs_from_ast_id_loc(db, it),
370        AttrDefId::MacroId(MacroId::ProcMacroId(it)) => attrs_from_ast_id_loc(db, it),
371        AttrDefId::ImplId(it) => attrs_from_ast_id_loc(db, it),
372        AttrDefId::ExternBlockId(it) => attrs_from_ast_id_loc(db, it),
373        AttrDefId::ExternCrateId(it) => attrs_from_ast_id_loc(db, it),
374        AttrDefId::UseId(it) => attrs_from_ast_id_loc(db, it),
375    };
376    (owner, None, None, krate)
377}
378
379fn collect_attrs<BreakValue>(
380    db: &dyn DefDatabase,
381    owner: AttrDefId,
382    mut callback: impl FnMut(Meta) -> ControlFlow<BreakValue>,
383) -> Option<BreakValue> {
384    let (source, outer_mod_decl, extra_crate_attrs, krate) = attrs_source(db, owner);
385    let extra_attrs = extra_crate_attrs
386        .into_iter()
387        .flat_map(|src| src.attrs())
388        .chain(outer_mod_decl.into_iter().flat_map(|it| it.value.attrs()));
389
390    let mut cfg_options = None;
391    expand_cfg_attr(
392        extra_attrs.chain(ast::attrs_including_inner(&source.value)),
393        || cfg_options.get_or_insert_with(|| krate.cfg_options(db)),
394        move |meta, _, _, _| callback(meta),
395    )
396}
397
398fn collect_field_attrs<T>(
399    db: &dyn DefDatabase,
400    variant: VariantId,
401    mut field_attrs: impl FnMut(&CfgOptions, InFile<ast::AnyHasAttrs>) -> T,
402) -> ArenaMap<LocalFieldId, T> {
403    let (variant_syntax, krate) = match variant {
404        VariantId::EnumVariantId(it) => attrs_from_ast_id_loc(db, it),
405        VariantId::StructId(it) => attrs_from_ast_id_loc(db, it),
406        VariantId::UnionId(it) => attrs_from_ast_id_loc(db, it),
407    };
408    let cfg_options = krate.cfg_options(db);
409    let variant_syntax = variant_syntax
410        .with_value(ast::VariantDef::cast(variant_syntax.value.syntax().clone()).unwrap());
411    let fields = match &variant_syntax.value {
412        ast::VariantDef::Struct(it) => it.field_list(),
413        ast::VariantDef::Union(it) => it.record_field_list().map(ast::FieldList::RecordFieldList),
414        ast::VariantDef::Variant(it) => it.field_list(),
415    };
416    let Some(fields) = fields else {
417        return ArenaMap::new();
418    };
419
420    let mut result = ArenaMap::new();
421    let mut idx = 0;
422    match fields {
423        ast::FieldList::RecordFieldList(fields) => {
424            for field in fields.fields() {
425                if AttrFlags::is_cfg_enabled_for(&field, cfg_options).is_ok() {
426                    result.insert(
427                        la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(idx)),
428                        field_attrs(cfg_options, variant_syntax.with_value(field.into())),
429                    );
430                    idx += 1;
431                }
432            }
433        }
434        ast::FieldList::TupleFieldList(fields) => {
435            for field in fields.fields() {
436                if AttrFlags::is_cfg_enabled_for(&field, cfg_options).is_ok() {
437                    result.insert(
438                        la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(idx)),
439                        field_attrs(cfg_options, variant_syntax.with_value(field.into())),
440                    );
441                    idx += 1;
442                }
443            }
444        }
445    }
446    result.shrink_to_fit();
447    result
448}
449
450#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
451pub struct RustcLayoutScalarValidRange {
452    pub start: Option<u128>,
453    pub end: Option<u128>,
454}
455
456#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
457struct DocsSourceMapLine {
458    /// The offset in [`Docs::docs`].
459    string_offset: TextSize,
460    /// The offset in the AST of the text.
461    ast_offset: TextSize,
462}
463
464#[derive(Debug, Clone, PartialEq, Eq, Hash)]
465pub struct Docs {
466    /// The concatenated string of all `#[doc = "..."]` attributes and documentation comments.
467    docs: String,
468    /// A sorted map from an offset in `docs` to an offset in the source code.
469    docs_source_map: Vec<DocsSourceMapLine>,
470    /// If the item is an outlined module (`mod foo;`), `docs_source_map` store the concatenated
471    /// list of the outline and inline docs (outline first). Then, this field contains the [`HirFileId`]
472    /// of the outline declaration, and the index in `docs` from which the inline docs
473    /// begin.
474    outline_mod: Option<(HirFileId, usize)>,
475    inline_file: HirFileId,
476    /// The size the prepended prefix, which does not map to real doc comments.
477    prefix_len: TextSize,
478    /// The offset in `docs` from which the docs are inner attributes/comments.
479    inline_inner_docs_start: Option<TextSize>,
480    /// Like `inline_inner_docs_start`, but for `outline_mod`. This can happen only when merging `Docs`
481    /// (as outline modules don't have inner attributes).
482    outline_inner_docs_start: Option<TextSize>,
483}
484
485#[derive(Debug, Clone, Copy, PartialEq, Eq)]
486pub enum IsInnerDoc {
487    No,
488    Yes,
489}
490
491impl IsInnerDoc {
492    #[inline]
493    pub fn yes(self) -> bool {
494        self == IsInnerDoc::Yes
495    }
496}
497
498impl Docs {
499    #[inline]
500    pub fn docs(&self) -> &str {
501        &self.docs
502    }
503
504    #[inline]
505    pub fn into_docs(self) -> String {
506        self.docs
507    }
508
509    pub fn find_ast_range(
510        &self,
511        mut string_range: TextRange,
512    ) -> Option<(InFile<TextRange>, IsInnerDoc)> {
513        if string_range.start() < self.prefix_len {
514            return None;
515        }
516        string_range -= self.prefix_len;
517
518        let mut file = self.inline_file;
519        let mut inner_docs_start = self.inline_inner_docs_start;
520        // Check whether the range is from the outline, the inline, or both.
521        let source_map = if let Some((outline_mod_file, outline_mod_end)) = self.outline_mod {
522            if let Some(first_inline) = self.docs_source_map.get(outline_mod_end) {
523                if string_range.end() <= first_inline.string_offset {
524                    // The range is completely in the outline.
525                    file = outline_mod_file;
526                    inner_docs_start = self.outline_inner_docs_start;
527                    &self.docs_source_map[..outline_mod_end]
528                } else if string_range.start() >= first_inline.string_offset {
529                    // The range is completely in the inline.
530                    &self.docs_source_map[outline_mod_end..]
531                } else {
532                    // The range is combined from the outline and the inline - cannot map it back.
533                    return None;
534                }
535            } else {
536                // There is no inline.
537                file = outline_mod_file;
538                inner_docs_start = self.outline_inner_docs_start;
539                &self.docs_source_map
540            }
541        } else {
542            // There is no outline.
543            &self.docs_source_map
544        };
545
546        let after_range =
547            source_map.partition_point(|line| line.string_offset <= string_range.start()) - 1;
548        let after_range = &source_map[after_range..];
549        let line = after_range.first()?;
550        if after_range.get(1).is_some_and(|next_line| next_line.string_offset < string_range.end())
551        {
552            // The range is combined from two lines - cannot map it back.
553            return None;
554        }
555        let ast_range = string_range - line.string_offset + line.ast_offset;
556        let is_inner = if inner_docs_start
557            .is_some_and(|inner_docs_start| string_range.start() >= inner_docs_start)
558        {
559            IsInnerDoc::Yes
560        } else {
561            IsInnerDoc::No
562        };
563        Some((InFile::new(file, ast_range), is_inner))
564    }
565
566    #[inline]
567    pub fn shift_by(&mut self, offset: TextSize) {
568        self.prefix_len += offset;
569    }
570
571    pub fn prepend_str(&mut self, s: &str) {
572        self.prefix_len += TextSize::of(s);
573        self.docs.insert_str(0, s);
574    }
575
576    pub fn append_str(&mut self, s: &str) {
577        self.docs.push_str(s);
578    }
579
580    pub fn append(&mut self, other: &Docs) {
581        let other_offset = TextSize::of(&self.docs);
582
583        assert!(
584            self.outline_mod.is_none() && other.outline_mod.is_none(),
585            "cannot merge `Docs` that have `outline_mod` set"
586        );
587        self.outline_mod = Some((self.inline_file, self.docs_source_map.len()));
588        self.inline_file = other.inline_file;
589        self.outline_inner_docs_start = self.inline_inner_docs_start;
590        self.inline_inner_docs_start = other.inline_inner_docs_start.map(|it| it + other_offset);
591
592        self.docs.push_str(&other.docs);
593        self.docs_source_map.extend(other.docs_source_map.iter().map(
594            |&DocsSourceMapLine { string_offset, ast_offset }| DocsSourceMapLine {
595                ast_offset,
596                string_offset: string_offset + other_offset,
597            },
598        ));
599    }
600
601    fn extend_with_doc_comment(&mut self, comment: ast::Comment, indent: &mut usize) {
602        let Some((doc, offset)) = comment.doc_comment() else { return };
603        self.extend_with_doc_str(doc, comment.syntax().text_range().start() + offset, indent);
604    }
605
606    fn extend_with_doc_attr(&mut self, value: SyntaxToken, indent: &mut usize) {
607        let Some(value) = ast::String::cast(value) else { return };
608        let Some(value_offset) = value.text_range_between_quotes() else { return };
609        let value_offset = value_offset.start();
610        let Ok(value) = value.value() else { return };
611        // FIXME: Handle source maps for escaped text.
612        self.extend_with_doc_str(&value, value_offset, indent);
613    }
614
615    fn extend_with_doc_str(&mut self, doc: &str, mut offset_in_ast: TextSize, indent: &mut usize) {
616        for line in doc.split('\n') {
617            self.docs_source_map.push(DocsSourceMapLine {
618                string_offset: TextSize::of(&self.docs),
619                ast_offset: offset_in_ast,
620            });
621            offset_in_ast += TextSize::of(line) + TextSize::of("\n");
622
623            let line = line.trim_end();
624            if let Some(line_indent) = line.chars().position(|ch| !ch.is_whitespace()) {
625                // Empty lines are handled because `position()` returns `None` for them.
626                *indent = std::cmp::min(*indent, line_indent);
627            }
628            self.docs.push_str(line);
629            self.docs.push('\n');
630        }
631    }
632
633    fn remove_indent(&mut self, indent: usize, start_source_map_index: usize) {
634        /// In case of panics, we want to avoid corrupted UTF-8 in `self.docs`, so we clear it.
635        struct Guard<'a>(&'a mut Docs);
636        impl Drop for Guard<'_> {
637            fn drop(&mut self) {
638                let Docs {
639                    docs,
640                    docs_source_map,
641                    outline_mod,
642                    inline_file: _,
643                    prefix_len: _,
644                    inline_inner_docs_start: _,
645                    outline_inner_docs_start: _,
646                } = self.0;
647                // Don't use `String::clear()` here because it's not guaranteed to not do UTF-8-dependent things,
648                // and we may have temporarily broken the string's encoding.
649                unsafe { docs.as_mut_vec() }.clear();
650                // This is just to avoid panics down the road.
651                docs_source_map.clear();
652                *outline_mod = None;
653            }
654        }
655
656        if self.docs.is_empty() {
657            return;
658        }
659
660        let guard = Guard(self);
661        let source_map = &mut guard.0.docs_source_map[start_source_map_index..];
662        let Some(&DocsSourceMapLine { string_offset: mut copy_into, .. }) = source_map.first()
663        else {
664            return;
665        };
666        // We basically want to remove multiple ranges from a string. Doing this efficiently (without O(N^2)
667        // or allocations) requires unsafe. Basically, for each line, we copy the line minus the indent into
668        // consecutive to the previous line (which may have moved). Then at the end we truncate.
669        let mut accumulated_offset = TextSize::new(0);
670        for idx in 0..source_map.len() {
671            let string_end_offset = source_map
672                .get(idx + 1)
673                .map_or_else(|| TextSize::of(&guard.0.docs), |next_attr| next_attr.string_offset);
674            let line_source = &mut source_map[idx];
675            let line_docs =
676                &guard.0.docs[TextRange::new(line_source.string_offset, string_end_offset)];
677            let line_docs_len = TextSize::of(line_docs);
678            let indent_size = line_docs.char_indices().nth(indent).map_or_else(
679                || TextSize::of(line_docs) - TextSize::of("\n"),
680                |(offset, _)| TextSize::new(offset as u32),
681            );
682            unsafe { guard.0.docs.as_bytes_mut() }.copy_within(
683                Range::<usize>::from(TextRange::new(
684                    line_source.string_offset + indent_size,
685                    string_end_offset,
686                )),
687                copy_into.into(),
688            );
689            copy_into += line_docs_len - indent_size;
690
691            if let Some(inner_attrs_start) = &mut guard.0.inline_inner_docs_start
692                && *inner_attrs_start == line_source.string_offset
693            {
694                *inner_attrs_start -= accumulated_offset;
695            }
696            // The removals in the string accumulate, but in the AST not, because it already points
697            // to the beginning of each attribute.
698            // Also, we need to shift the AST offset of every line, but the string offset of the first
699            // line should not get shifted (in general, the shift for the string offset is by the
700            // number of lines until the current one, excluding the current one).
701            line_source.string_offset -= accumulated_offset;
702            line_source.ast_offset += indent_size;
703
704            accumulated_offset += indent_size;
705        }
706        // Don't use `String::truncate()` here because it's not guaranteed to not do UTF-8-dependent things,
707        // and we may have temporarily broken the string's encoding.
708        unsafe { guard.0.docs.as_mut_vec() }.truncate(copy_into.into());
709
710        std::mem::forget(guard);
711    }
712
713    fn remove_last_newline(&mut self) {
714        self.docs.truncate(self.docs.len().saturating_sub(1));
715    }
716
717    fn shrink_to_fit(&mut self) {
718        let Docs {
719            docs,
720            docs_source_map,
721            outline_mod: _,
722            inline_file: _,
723            prefix_len: _,
724            inline_inner_docs_start: _,
725            outline_inner_docs_start: _,
726        } = self;
727        docs.shrink_to_fit();
728        docs_source_map.shrink_to_fit();
729    }
730}
731
732#[derive(Debug, PartialEq, Eq, Hash)]
733pub struct DeriveInfo {
734    pub trait_name: Symbol,
735    pub helpers: Box<[Symbol]>,
736}
737
738fn extract_doc_aliases(result: &mut Vec<Symbol>, attr: Meta) -> ControlFlow<Infallible> {
739    if let Meta::TokenTree { path, tt } = attr
740        && path.is1("doc")
741    {
742        for atom in DocAtom::parse(tt) {
743            match atom {
744                DocAtom::Alias(aliases) => {
745                    result.extend(aliases.into_iter().map(|alias| Symbol::intern(&alias)))
746                }
747                DocAtom::KeyValue { key, value } if key == "alias" => {
748                    result.push(Symbol::intern(&value))
749                }
750                _ => {}
751            }
752        }
753    }
754    ControlFlow::Continue(())
755}
756
757fn extract_cfgs(result: &mut Vec<CfgExpr>, attr: Meta) -> ControlFlow<Infallible> {
758    if let Meta::TokenTree { path, tt } = attr
759        && path.is1("cfg")
760    {
761        result.push(CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(&tt).peekable()));
762    }
763    ControlFlow::Continue(())
764}
765
766fn extract_docs<'a>(
767    get_cfg_options: &dyn Fn() -> &'a CfgOptions,
768    source: InFile<ast::AnyHasAttrs>,
769    outer_mod_decl: Option<InFile<ast::Module>>,
770    inner_attrs_node: Option<SyntaxNode>,
771) -> Option<Box<Docs>> {
772    let mut result = Docs {
773        docs: String::new(),
774        docs_source_map: Vec::new(),
775        outline_mod: None,
776        inline_file: source.file_id,
777        prefix_len: TextSize::new(0),
778        inline_inner_docs_start: None,
779        outline_inner_docs_start: None,
780    };
781
782    let mut cfg_options = None;
783    let mut extend_with_attrs =
784        |result: &mut Docs, node: &SyntaxNode, expect_inner_attrs, indent: &mut usize| {
785            expand_cfg_attr_with_doc_comments::<_, Infallible>(
786                AttrDocCommentIter::from_syntax_node(node).filter(|attr| match attr {
787                    Either::Left(attr) => attr.kind().is_inner() == expect_inner_attrs,
788                    Either::Right(comment) => comment.kind().doc.is_some_and(|kind| {
789                        (kind == ast::CommentPlacement::Inner) == expect_inner_attrs
790                    }),
791                }),
792                || cfg_options.get_or_insert_with(get_cfg_options),
793                |attr| {
794                    match attr {
795                        Either::Right(doc_comment) => {
796                            result.extend_with_doc_comment(doc_comment, indent)
797                        }
798                        Either::Left((attr, _, _, _)) => match attr {
799                            // FIXME: Handle macros: `#[doc = concat!("foo", "bar")]`.
800                            Meta::NamedKeyValue {
801                                name: Some(name), value: Some(value), ..
802                            } if name.text() == "doc" => {
803                                result.extend_with_doc_attr(value, indent);
804                            }
805                            _ => {}
806                        },
807                    }
808                    ControlFlow::Continue(())
809                },
810            );
811        };
812
813    if let Some(outer_mod_decl) = outer_mod_decl {
814        let mut indent = usize::MAX;
815        extend_with_attrs(&mut result, outer_mod_decl.value.syntax(), false, &mut indent);
816        result.remove_indent(indent, 0);
817        result.outline_mod = Some((outer_mod_decl.file_id, result.docs_source_map.len()));
818    }
819
820    let inline_source_map_start = result.docs_source_map.len();
821    let mut indent = usize::MAX;
822    extend_with_attrs(&mut result, source.value.syntax(), false, &mut indent);
823    if let Some(inner_attrs_node) = &inner_attrs_node {
824        result.inline_inner_docs_start = Some(TextSize::of(&result.docs));
825        extend_with_attrs(&mut result, inner_attrs_node, true, &mut indent);
826    }
827    result.remove_indent(indent, inline_source_map_start);
828
829    result.remove_last_newline();
830
831    result.shrink_to_fit();
832
833    if result.docs.is_empty() { None } else { Some(Box::new(result)) }
834}
835
836#[salsa::tracked]
837impl AttrFlags {
838    #[salsa::tracked]
839    pub fn query(db: &dyn DefDatabase, owner: AttrDefId) -> AttrFlags {
840        let mut attr_flags = AttrFlags::empty();
841        collect_attrs(db, owner, |attr| match_attr_flags(&mut attr_flags, attr));
842        attr_flags
843    }
844
845    #[inline]
846    pub fn query_field(db: &dyn DefDatabase, field: FieldId) -> AttrFlags {
847        return field_attr_flags(db, field.parent)
848            .get(field.local_id)
849            .copied()
850            .unwrap_or_else(AttrFlags::empty);
851
852        #[salsa::tracked(returns(ref))]
853        fn field_attr_flags(
854            db: &dyn DefDatabase,
855            variant: VariantId,
856        ) -> ArenaMap<LocalFieldId, AttrFlags> {
857            collect_field_attrs(db, variant, |cfg_options, field| {
858                let mut attr_flags = AttrFlags::empty();
859                expand_cfg_attr(
860                    field.value.attrs(),
861                    || cfg_options,
862                    |attr, _, _, _| match_attr_flags(&mut attr_flags, attr),
863                );
864                attr_flags
865            })
866        }
867    }
868
869    #[inline]
870    pub fn query_generic_params(
871        db: &dyn DefDatabase,
872        def: GenericDefId,
873    ) -> &(ArenaMap<LocalLifetimeParamId, AttrFlags>, ArenaMap<LocalTypeOrConstParamId, AttrFlags>)
874    {
875        let generic_params = GenericParams::new(db, def);
876        let params_count_excluding_self =
877            generic_params.len() - usize::from(generic_params.trait_self_param().is_some());
878        if params_count_excluding_self == 0 {
879            return const { &(ArenaMap::new(), ArenaMap::new()) };
880        }
881        return generic_params_attr_flags(db, def);
882
883        #[salsa::tracked(returns(ref))]
884        fn generic_params_attr_flags(
885            db: &dyn DefDatabase,
886            def: GenericDefId,
887        ) -> (ArenaMap<LocalLifetimeParamId, AttrFlags>, ArenaMap<LocalTypeOrConstParamId, AttrFlags>)
888        {
889            let mut lifetimes = ArenaMap::new();
890            let mut type_and_consts = ArenaMap::new();
891
892            let mut cfg_options = None;
893            let mut cfg_options =
894                || *cfg_options.get_or_insert_with(|| def.krate(db).cfg_options(db));
895
896            let lifetimes_source = HasChildSource::<LocalLifetimeParamId>::child_source(&def, db);
897            for (lifetime_id, lifetime) in lifetimes_source.value.iter() {
898                let mut attr_flags = AttrFlags::empty();
899                expand_cfg_attr(lifetime.attrs(), &mut cfg_options, |attr, _, _, _| {
900                    match_attr_flags(&mut attr_flags, attr)
901                });
902                if !attr_flags.is_empty() {
903                    lifetimes.insert(lifetime_id, attr_flags);
904                }
905            }
906
907            let type_and_consts_source =
908                HasChildSource::<LocalTypeOrConstParamId>::child_source(&def, db);
909            for (type_or_const_id, type_or_const) in type_and_consts_source.value.iter() {
910                let mut attr_flags = AttrFlags::empty();
911                expand_cfg_attr(type_or_const.attrs(), &mut cfg_options, |attr, _, _, _| {
912                    match_attr_flags(&mut attr_flags, attr)
913                });
914                if !attr_flags.is_empty() {
915                    type_and_consts.insert(type_or_const_id, attr_flags);
916                }
917            }
918
919            lifetimes.shrink_to_fit();
920            type_and_consts.shrink_to_fit();
921            (lifetimes, type_and_consts)
922        }
923    }
924
925    #[inline]
926    pub fn query_lifetime_param(db: &dyn DefDatabase, owner: LifetimeParamId) -> AttrFlags {
927        AttrFlags::query_generic_params(db, owner.parent)
928            .0
929            .get(owner.local_id)
930            .copied()
931            .unwrap_or_else(AttrFlags::empty)
932    }
933    #[inline]
934    pub fn query_type_or_const_param(db: &dyn DefDatabase, owner: TypeOrConstParamId) -> AttrFlags {
935        AttrFlags::query_generic_params(db, owner.parent)
936            .1
937            .get(owner.local_id)
938            .copied()
939            .unwrap_or_else(AttrFlags::empty)
940    }
941
942    pub(crate) fn is_cfg_enabled_for(
943        owner: &dyn HasAttrs,
944        cfg_options: &CfgOptions,
945    ) -> Result<(), CfgExpr> {
946        let attrs = ast::attrs_including_inner(owner);
947        let result = expand_cfg_attr(
948            attrs,
949            || cfg_options,
950            |attr, _, _, _| {
951                if let Meta::TokenTree { path, tt } = attr
952                    && path.is1("cfg")
953                    && let cfg =
954                        CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(&tt).peekable())
955                    && cfg_options.check(&cfg) == Some(false)
956                {
957                    ControlFlow::Break(cfg)
958                } else {
959                    ControlFlow::Continue(())
960                }
961            },
962        );
963        match result {
964            Some(cfg) => Err(cfg),
965            None => Ok(()),
966        }
967    }
968
969    #[inline]
970    pub fn lang_item(db: &dyn DefDatabase, owner: AttrDefId) -> Option<Symbol> {
971        AttrFlags::query(db, owner).lang_item_with_attrs(db, owner)
972    }
973
974    #[inline]
975    pub fn lang_item_with_attrs(self, db: &dyn DefDatabase, owner: AttrDefId) -> Option<Symbol> {
976        if !self.contains(AttrFlags::LANG_ITEM) {
977            // Don't create the query in case this is not a lang item, this wastes memory.
978            return None;
979        }
980
981        return lang_item(db, owner);
982
983        #[salsa::tracked]
984        fn lang_item(db: &dyn DefDatabase, owner: AttrDefId) -> Option<Symbol> {
985            collect_attrs(db, owner, |attr| {
986                if let Meta::NamedKeyValue { name: Some(name), value: Some(value), .. } = attr
987                    && name.text() == "lang"
988                    && let Some(value) = ast::String::cast(value)
989                    && let Ok(value) = value.value()
990                {
991                    ControlFlow::Break(Symbol::intern(&value))
992                } else {
993                    ControlFlow::Continue(())
994                }
995            })
996        }
997    }
998
999    #[inline]
1000    pub fn repr(db: &dyn DefDatabase, owner: AdtId) -> Option<ReprOptions> {
1001        if !AttrFlags::query(db, owner.into()).contains(AttrFlags::HAS_REPR) {
1002            // Don't create the query in case this has no repr, this wastes memory.
1003            return None;
1004        }
1005
1006        return repr(db, owner);
1007
1008        #[salsa::tracked]
1009        fn repr(db: &dyn DefDatabase, owner: AdtId) -> Option<ReprOptions> {
1010            let mut result = None;
1011            collect_attrs::<Infallible>(db, owner.into(), |attr| {
1012                if let Meta::TokenTree { path, tt } = attr
1013                    && path.is1("repr")
1014                    && let Some(repr) = parse_repr_tt(&tt)
1015                {
1016                    match &mut result {
1017                        Some(existing) => merge_repr(existing, repr),
1018                        None => result = Some(repr),
1019                    }
1020                }
1021                ControlFlow::Continue(())
1022            });
1023            result
1024        }
1025    }
1026
1027    /// Call this only if there are legacy const generics, to save memory.
1028    #[salsa::tracked(returns(ref))]
1029    pub(crate) fn legacy_const_generic_indices(
1030        db: &dyn DefDatabase,
1031        owner: FunctionId,
1032    ) -> Option<Box<[u32]>> {
1033        let result = collect_attrs(db, owner.into(), |attr| {
1034            if let Meta::TokenTree { path, tt } = attr
1035                && path.is1("rustc_legacy_const_generics")
1036            {
1037                let result = parse_rustc_legacy_const_generics(tt);
1038                ControlFlow::Break(result)
1039            } else {
1040                ControlFlow::Continue(())
1041            }
1042        });
1043        result.filter(|it| !it.is_empty())
1044    }
1045
1046    // There aren't typically many crates, so it's okay to always make this a query without a flag.
1047    #[salsa::tracked(returns(ref))]
1048    pub fn doc_html_root_url(db: &dyn DefDatabase, krate: Crate) -> Option<SmolStr> {
1049        let root_file_id = krate.root_file_id(db);
1050        let syntax = db.parse(root_file_id).tree();
1051        let extra_crate_attrs =
1052            parse_extra_crate_attrs(db, krate).into_iter().flat_map(|src| src.attrs());
1053
1054        let mut cfg_options = None;
1055        expand_cfg_attr(
1056            extra_crate_attrs.chain(syntax.attrs()),
1057            || cfg_options.get_or_insert(krate.cfg_options(db)),
1058            |attr, _, _, _| {
1059                if let Meta::TokenTree { path, tt } = attr
1060                    && path.is1("doc")
1061                    && let Some(result) = DocAtom::parse(tt).into_iter().find_map(|atom| {
1062                        if let DocAtom::KeyValue { key, value } = atom
1063                            && key == "html_root_url"
1064                        {
1065                            Some(value)
1066                        } else {
1067                            None
1068                        }
1069                    })
1070                {
1071                    ControlFlow::Break(result)
1072                } else {
1073                    ControlFlow::Continue(())
1074                }
1075            },
1076        )
1077    }
1078
1079    #[inline]
1080    pub fn target_features(db: &dyn DefDatabase, owner: FunctionId) -> &FxHashSet<Symbol> {
1081        if !AttrFlags::query(db, owner.into()).contains(AttrFlags::HAS_TARGET_FEATURE) {
1082            return const { &FxHashSet::with_hasher(rustc_hash::FxBuildHasher) };
1083        }
1084
1085        return target_features(db, owner);
1086
1087        #[salsa::tracked(returns(ref))]
1088        fn target_features(db: &dyn DefDatabase, owner: FunctionId) -> FxHashSet<Symbol> {
1089            let mut result = FxHashSet::default();
1090            collect_attrs::<Infallible>(db, owner.into(), |attr| {
1091                if let Meta::TokenTree { path, tt } = attr
1092                    && path.is1("target_feature")
1093                {
1094                    let mut tt = TokenTreeChildren::new(&tt);
1095                    while let Some(NodeOrToken::Token(enable_ident)) = tt.next()
1096                        && enable_ident.text() == "enable"
1097                        && let Some(NodeOrToken::Token(eq_token)) = tt.next()
1098                        && eq_token.kind() == T![=]
1099                        && let Some(NodeOrToken::Token(features)) = tt.next()
1100                        && let Some(features) = ast::String::cast(features)
1101                        && let Ok(features) = features.value()
1102                    {
1103                        result.extend(features.split(',').map(Symbol::intern));
1104                        if tt
1105                            .next()
1106                            .and_then(NodeOrToken::into_token)
1107                            .is_none_or(|it| it.kind() != T![,])
1108                        {
1109                            break;
1110                        }
1111                    }
1112                }
1113                ControlFlow::Continue(())
1114            });
1115            result.shrink_to_fit();
1116            result
1117        }
1118    }
1119
1120    #[inline]
1121    pub fn rustc_layout_scalar_valid_range(
1122        db: &dyn DefDatabase,
1123        owner: AdtId,
1124    ) -> RustcLayoutScalarValidRange {
1125        if !AttrFlags::query(db, owner.into()).contains(AttrFlags::RUSTC_LAYOUT_SCALAR_VALID_RANGE)
1126        {
1127            return RustcLayoutScalarValidRange::default();
1128        }
1129
1130        return rustc_layout_scalar_valid_range(db, owner);
1131
1132        #[salsa::tracked]
1133        fn rustc_layout_scalar_valid_range(
1134            db: &dyn DefDatabase,
1135            owner: AdtId,
1136        ) -> RustcLayoutScalarValidRange {
1137            let mut result = RustcLayoutScalarValidRange::default();
1138            collect_attrs::<Infallible>(db, owner.into(), |attr| {
1139                if let Meta::TokenTree { path, tt } = attr
1140                    && (path.is1("rustc_layout_scalar_valid_range_start")
1141                        || path.is1("rustc_layout_scalar_valid_range_end"))
1142                    && let tt = TokenTreeChildren::new(&tt)
1143                    && let Ok(NodeOrToken::Token(value)) = Itertools::exactly_one(tt)
1144                    && let Some(value) = ast::IntNumber::cast(value)
1145                    && let Ok(value) = value.value()
1146                {
1147                    if path.is1("rustc_layout_scalar_valid_range_start") {
1148                        result.start = Some(value)
1149                    } else {
1150                        result.end = Some(value);
1151                    }
1152                }
1153                ControlFlow::Continue(())
1154            });
1155            result
1156        }
1157    }
1158
1159    #[inline]
1160    pub fn doc_aliases(self, db: &dyn DefDatabase, owner: Either<AttrDefId, FieldId>) -> &[Symbol] {
1161        if !self.contains(AttrFlags::HAS_DOC_ALIASES) {
1162            return &[];
1163        }
1164        return match owner {
1165            Either::Left(it) => doc_aliases(db, it),
1166            Either::Right(field) => fields_doc_aliases(db, field.parent)
1167                .get(field.local_id)
1168                .map(|it| &**it)
1169                .unwrap_or_default(),
1170        };
1171
1172        #[salsa::tracked(returns(ref))]
1173        fn doc_aliases(db: &dyn DefDatabase, owner: AttrDefId) -> Box<[Symbol]> {
1174            let mut result = Vec::new();
1175            collect_attrs::<Infallible>(db, owner, |attr| extract_doc_aliases(&mut result, attr));
1176            result.into_boxed_slice()
1177        }
1178
1179        #[salsa::tracked(returns(ref))]
1180        fn fields_doc_aliases(
1181            db: &dyn DefDatabase,
1182            variant: VariantId,
1183        ) -> ArenaMap<LocalFieldId, Box<[Symbol]>> {
1184            collect_field_attrs(db, variant, |cfg_options, field| {
1185                let mut result = Vec::new();
1186                expand_cfg_attr(
1187                    field.value.attrs(),
1188                    || cfg_options,
1189                    |attr, _, _, _| extract_doc_aliases(&mut result, attr),
1190                );
1191                result.into_boxed_slice()
1192            })
1193        }
1194    }
1195
1196    #[inline]
1197    pub fn cfgs(self, db: &dyn DefDatabase, owner: Either<AttrDefId, FieldId>) -> Option<&CfgExpr> {
1198        if !self.contains(AttrFlags::HAS_CFG) {
1199            return None;
1200        }
1201        return match owner {
1202            Either::Left(it) => cfgs(db, it).as_ref(),
1203            Either::Right(field) => {
1204                fields_cfgs(db, field.parent).get(field.local_id).and_then(|it| it.as_ref())
1205            }
1206        };
1207
1208        // We LRU this query because it is only used by IDE.
1209        #[salsa::tracked(returns(ref), lru = 250)]
1210        fn cfgs(db: &dyn DefDatabase, owner: AttrDefId) -> Option<CfgExpr> {
1211            let mut result = Vec::new();
1212            collect_attrs::<Infallible>(db, owner, |attr| extract_cfgs(&mut result, attr));
1213            match result.len() {
1214                0 => None,
1215                1 => result.into_iter().next(),
1216                _ => Some(CfgExpr::All(result.into_boxed_slice())),
1217            }
1218        }
1219
1220        // We LRU this query because it is only used by IDE.
1221        #[salsa::tracked(returns(ref), lru = 50)]
1222        fn fields_cfgs(
1223            db: &dyn DefDatabase,
1224            variant: VariantId,
1225        ) -> ArenaMap<LocalFieldId, Option<CfgExpr>> {
1226            collect_field_attrs(db, variant, |cfg_options, field| {
1227                let mut result = Vec::new();
1228                expand_cfg_attr(
1229                    field.value.attrs(),
1230                    || cfg_options,
1231                    |attr, _, _, _| extract_cfgs(&mut result, attr),
1232                );
1233                match result.len() {
1234                    0 => None,
1235                    1 => result.into_iter().next(),
1236                    _ => Some(CfgExpr::All(result.into_boxed_slice())),
1237                }
1238            })
1239        }
1240    }
1241
1242    #[inline]
1243    pub fn doc_keyword(db: &dyn DefDatabase, owner: ModuleId) -> Option<Symbol> {
1244        if !AttrFlags::query(db, AttrDefId::ModuleId(owner)).contains(AttrFlags::HAS_DOC_KEYWORD) {
1245            return None;
1246        }
1247        return doc_keyword(db, owner);
1248
1249        #[salsa::tracked]
1250        fn doc_keyword(db: &dyn DefDatabase, owner: ModuleId) -> Option<Symbol> {
1251            collect_attrs(db, AttrDefId::ModuleId(owner), |attr| {
1252                if let Meta::TokenTree { path, tt } = attr
1253                    && path.is1("doc")
1254                {
1255                    for atom in DocAtom::parse(tt) {
1256                        if let DocAtom::KeyValue { key, value } = atom
1257                            && key == "keyword"
1258                        {
1259                            return ControlFlow::Break(Symbol::intern(&value));
1260                        }
1261                    }
1262                }
1263                ControlFlow::Continue(())
1264            })
1265        }
1266    }
1267
1268    // We LRU this query because it is only used by IDE.
1269    #[salsa::tracked(returns(ref), lru = 250)]
1270    pub fn docs(db: &dyn DefDatabase, owner: AttrDefId) -> Option<Box<Docs>> {
1271        let (source, outer_mod_decl, _extra_crate_attrs, krate) = attrs_source(db, owner);
1272        let inner_attrs_node = source.value.inner_attributes_node();
1273        // Note: we don't have to pass down `_extra_crate_attrs` here, since `extract_docs`
1274        // does not handle crate-level attributes related to docs.
1275        // See: https://doc.rust-lang.org/rustdoc/write-documentation/the-doc-attribute.html#at-the-crate-level
1276        extract_docs(&|| krate.cfg_options(db), source, outer_mod_decl, inner_attrs_node)
1277    }
1278
1279    #[inline]
1280    pub fn field_docs(db: &dyn DefDatabase, field: FieldId) -> Option<&Docs> {
1281        return fields_docs(db, field.parent).get(field.local_id).and_then(|it| it.as_deref());
1282
1283        // We LRU this query because it is only used by IDE.
1284        #[salsa::tracked(returns(ref), lru = 50)]
1285        pub fn fields_docs(
1286            db: &dyn DefDatabase,
1287            variant: VariantId,
1288        ) -> ArenaMap<LocalFieldId, Option<Box<Docs>>> {
1289            collect_field_attrs(db, variant, |cfg_options, field| {
1290                extract_docs(&|| cfg_options, field, None, None)
1291            })
1292        }
1293    }
1294
1295    #[inline]
1296    pub fn derive_info(db: &dyn DefDatabase, owner: MacroId) -> Option<&DeriveInfo> {
1297        if !AttrFlags::query(db, owner.into()).contains(AttrFlags::IS_DERIVE_OR_BUILTIN_MACRO) {
1298            return None;
1299        }
1300
1301        return derive_info(db, owner).as_ref();
1302
1303        #[salsa::tracked(returns(ref))]
1304        fn derive_info(db: &dyn DefDatabase, owner: MacroId) -> Option<DeriveInfo> {
1305            collect_attrs(db, owner.into(), |attr| {
1306                if let Meta::TokenTree { path, tt } = attr
1307                    && path.segments.len() == 1
1308                    && matches!(
1309                        path.segments[0].text(),
1310                        "proc_macro_derive" | "rustc_builtin_macro"
1311                    )
1312                    && let mut tt = TokenTreeChildren::new(&tt)
1313                    && let Some(NodeOrToken::Token(trait_name)) = tt.next()
1314                    && trait_name.kind().is_any_identifier()
1315                {
1316                    let trait_name = Symbol::intern(trait_name.text());
1317
1318                    let helpers = if let Some(NodeOrToken::Token(comma)) = tt.next()
1319                        && comma.kind() == T![,]
1320                        && let Some(NodeOrToken::Token(attributes)) = tt.next()
1321                        && attributes.text() == "attributes"
1322                        && let Some(NodeOrToken::Node(attributes)) = tt.next()
1323                    {
1324                        attributes
1325                            .syntax()
1326                            .children_with_tokens()
1327                            .filter_map(NodeOrToken::into_token)
1328                            .filter(|it| it.kind().is_any_identifier())
1329                            .map(|it| Symbol::intern(it.text()))
1330                            .collect::<Box<[_]>>()
1331                    } else {
1332                        Box::new([])
1333                    };
1334
1335                    ControlFlow::Break(DeriveInfo { trait_name, helpers })
1336                } else {
1337                    ControlFlow::Continue(())
1338                }
1339            })
1340        }
1341    }
1342}
1343
1344fn merge_repr(this: &mut ReprOptions, other: ReprOptions) {
1345    let ReprOptions { int, align, pack, flags, field_shuffle_seed: _ } = this;
1346    flags.insert(other.flags);
1347    *align = (*align).max(other.align);
1348    *pack = match (*pack, other.pack) {
1349        (Some(pack), None) | (None, Some(pack)) => Some(pack),
1350        _ => (*pack).min(other.pack),
1351    };
1352    if other.int.is_some() {
1353        *int = other.int;
1354    }
1355}
1356
1357fn parse_repr_tt(tt: &ast::TokenTree) -> Option<ReprOptions> {
1358    use crate::builtin_type::{BuiltinInt, BuiltinUint};
1359    use rustc_abi::{Align, Integer, IntegerType, ReprFlags, ReprOptions};
1360
1361    let mut tts = TokenTreeChildren::new(tt).peekable();
1362
1363    let mut acc = ReprOptions::default();
1364    while let Some(tt) = tts.next() {
1365        let NodeOrToken::Token(ident) = tt else {
1366            continue;
1367        };
1368        if !ident.kind().is_any_identifier() {
1369            continue;
1370        }
1371        let repr = match ident.text() {
1372            "packed" => {
1373                let pack = if let Some(NodeOrToken::Node(tt)) = tts.peek() {
1374                    let tt = tt.clone();
1375                    tts.next();
1376                    let mut tt_iter = TokenTreeChildren::new(&tt);
1377                    if let Some(NodeOrToken::Token(lit)) = tt_iter.next()
1378                        && let Some(lit) = ast::IntNumber::cast(lit)
1379                        && let Ok(lit) = lit.value()
1380                        && let Ok(lit) = lit.try_into()
1381                    {
1382                        lit
1383                    } else {
1384                        0
1385                    }
1386                } else {
1387                    0
1388                };
1389                let pack = Some(Align::from_bytes(pack).unwrap_or(Align::ONE));
1390                ReprOptions { pack, ..Default::default() }
1391            }
1392            "align" => {
1393                let mut align = None;
1394                if let Some(NodeOrToken::Node(tt)) = tts.peek() {
1395                    let tt = tt.clone();
1396                    tts.next();
1397                    let mut tt_iter = TokenTreeChildren::new(&tt);
1398                    if let Some(NodeOrToken::Token(lit)) = tt_iter.next()
1399                        && let Some(lit) = ast::IntNumber::cast(lit)
1400                        && let Ok(lit) = lit.value()
1401                        && let Ok(lit) = lit.try_into()
1402                    {
1403                        align = Align::from_bytes(lit).ok();
1404                    }
1405                }
1406                ReprOptions { align, ..Default::default() }
1407            }
1408            "C" => ReprOptions { flags: ReprFlags::IS_C, ..Default::default() },
1409            "transparent" => ReprOptions { flags: ReprFlags::IS_TRANSPARENT, ..Default::default() },
1410            "simd" => ReprOptions { flags: ReprFlags::IS_SIMD, ..Default::default() },
1411            repr => {
1412                let mut int = None;
1413                if let Some(builtin) = BuiltinInt::from_suffix(repr)
1414                    .map(Either::Left)
1415                    .or_else(|| BuiltinUint::from_suffix(repr).map(Either::Right))
1416                {
1417                    int = Some(match builtin {
1418                        Either::Left(bi) => match bi {
1419                            BuiltinInt::Isize => IntegerType::Pointer(true),
1420                            BuiltinInt::I8 => IntegerType::Fixed(Integer::I8, true),
1421                            BuiltinInt::I16 => IntegerType::Fixed(Integer::I16, true),
1422                            BuiltinInt::I32 => IntegerType::Fixed(Integer::I32, true),
1423                            BuiltinInt::I64 => IntegerType::Fixed(Integer::I64, true),
1424                            BuiltinInt::I128 => IntegerType::Fixed(Integer::I128, true),
1425                        },
1426                        Either::Right(bu) => match bu {
1427                            BuiltinUint::Usize => IntegerType::Pointer(false),
1428                            BuiltinUint::U8 => IntegerType::Fixed(Integer::I8, false),
1429                            BuiltinUint::U16 => IntegerType::Fixed(Integer::I16, false),
1430                            BuiltinUint::U32 => IntegerType::Fixed(Integer::I32, false),
1431                            BuiltinUint::U64 => IntegerType::Fixed(Integer::I64, false),
1432                            BuiltinUint::U128 => IntegerType::Fixed(Integer::I128, false),
1433                        },
1434                    });
1435                }
1436                ReprOptions { int, ..Default::default() }
1437            }
1438        };
1439        merge_repr(&mut acc, repr);
1440    }
1441
1442    Some(acc)
1443}
1444
1445fn parse_rustc_legacy_const_generics(tt: ast::TokenTree) -> Box<[u32]> {
1446    TokenTreeChildren::new(&tt)
1447        .filter_map(|param| {
1448            ast::IntNumber::cast(param.into_token()?)?.value().ok()?.try_into().ok()
1449        })
1450        .collect()
1451}
1452
1453#[derive(Debug)]
1454enum DocAtom {
1455    /// eg. `#[doc(hidden)]`
1456    Flag(SmolStr),
1457    /// eg. `#[doc(alias = "it")]`
1458    ///
1459    /// Note that a key can have multiple values that are all considered "active" at the same time.
1460    /// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`.
1461    KeyValue { key: SmolStr, value: SmolStr },
1462    /// eg. `#[doc(alias("x", "y"))]`
1463    Alias(Vec<SmolStr>),
1464}
1465
1466impl DocAtom {
1467    fn parse(tt: ast::TokenTree) -> SmallVec<[DocAtom; 1]> {
1468        let mut iter = TokenTreeChildren::new(&tt).peekable();
1469        let mut result = SmallVec::new();
1470        while iter.peek().is_some() {
1471            if let Some(expr) = next_doc_expr(&mut iter) {
1472                result.push(expr);
1473            }
1474        }
1475        result
1476    }
1477}
1478
1479fn next_doc_expr(it: &mut Peekable<TokenTreeChildren>) -> Option<DocAtom> {
1480    let name = match it.next() {
1481        Some(NodeOrToken::Token(token)) if token.kind().is_any_identifier() => {
1482            SmolStr::new(token.text())
1483        }
1484        _ => return None,
1485    };
1486
1487    let ret = match it.peek() {
1488        Some(NodeOrToken::Token(eq)) if eq.kind() == T![=] => {
1489            it.next();
1490            if let Some(NodeOrToken::Token(value)) = it.next()
1491                && let Some(value) = ast::String::cast(value)
1492                && let Ok(value) = value.value()
1493            {
1494                DocAtom::KeyValue { key: name, value: SmolStr::new(&*value) }
1495            } else {
1496                return None;
1497            }
1498        }
1499        Some(NodeOrToken::Node(subtree)) => {
1500            if name != "alias" {
1501                return None;
1502            }
1503            let aliases = TokenTreeChildren::new(subtree)
1504                .filter_map(|alias| {
1505                    Some(SmolStr::new(&*ast::String::cast(alias.into_token()?)?.value().ok()?))
1506                })
1507                .collect();
1508            it.next();
1509            DocAtom::Alias(aliases)
1510        }
1511        _ => DocAtom::Flag(name),
1512    };
1513    Some(ret)
1514}
1515
1516#[cfg(test)]
1517mod tests {
1518    use expect_test::expect;
1519    use hir_expand::InFile;
1520    use test_fixture::WithFixture;
1521    use tt::{TextRange, TextSize};
1522
1523    use crate::AttrDefId;
1524    use crate::attrs::{AttrFlags, Docs, IsInnerDoc};
1525    use crate::test_db::TestDB;
1526
1527    #[test]
1528    fn docs() {
1529        let (_db, file_id) = TestDB::with_single_file("");
1530        let mut docs = Docs {
1531            docs: String::new(),
1532            docs_source_map: Vec::new(),
1533            outline_mod: None,
1534            inline_file: file_id.into(),
1535            prefix_len: TextSize::new(0),
1536            inline_inner_docs_start: None,
1537            outline_inner_docs_start: None,
1538        };
1539        let mut indent = usize::MAX;
1540
1541        let outer = " foo\n\tbar  baz";
1542        let mut ast_offset = TextSize::new(123);
1543        for line in outer.split('\n') {
1544            docs.extend_with_doc_str(line, ast_offset, &mut indent);
1545            ast_offset += TextSize::of(line) + TextSize::of("\n");
1546        }
1547
1548        docs.inline_inner_docs_start = Some(TextSize::of(&docs.docs));
1549        ast_offset += TextSize::new(123);
1550        let inner = " bar \n baz";
1551        for line in inner.split('\n') {
1552            docs.extend_with_doc_str(line, ast_offset, &mut indent);
1553            ast_offset += TextSize::of(line) + TextSize::of("\n");
1554        }
1555
1556        assert_eq!(indent, 1);
1557        expect![[r#"
1558            [
1559                DocsSourceMapLine {
1560                    string_offset: 0,
1561                    ast_offset: 123,
1562                },
1563                DocsSourceMapLine {
1564                    string_offset: 5,
1565                    ast_offset: 128,
1566                },
1567                DocsSourceMapLine {
1568                    string_offset: 15,
1569                    ast_offset: 261,
1570                },
1571                DocsSourceMapLine {
1572                    string_offset: 20,
1573                    ast_offset: 267,
1574                },
1575            ]
1576        "#]]
1577        .assert_debug_eq(&docs.docs_source_map);
1578
1579        docs.remove_indent(indent, 0);
1580
1581        assert_eq!(docs.inline_inner_docs_start, Some(TextSize::new(13)));
1582
1583        assert_eq!(docs.docs, "foo\nbar  baz\nbar\nbaz\n");
1584        expect![[r#"
1585            [
1586                DocsSourceMapLine {
1587                    string_offset: 0,
1588                    ast_offset: 124,
1589                },
1590                DocsSourceMapLine {
1591                    string_offset: 4,
1592                    ast_offset: 129,
1593                },
1594                DocsSourceMapLine {
1595                    string_offset: 13,
1596                    ast_offset: 262,
1597                },
1598                DocsSourceMapLine {
1599                    string_offset: 17,
1600                    ast_offset: 268,
1601                },
1602            ]
1603        "#]]
1604        .assert_debug_eq(&docs.docs_source_map);
1605
1606        docs.append(&docs.clone());
1607        docs.prepend_str("prefix---");
1608        assert_eq!(docs.docs, "prefix---foo\nbar  baz\nbar\nbaz\nfoo\nbar  baz\nbar\nbaz\n");
1609        expect![[r#"
1610            [
1611                DocsSourceMapLine {
1612                    string_offset: 0,
1613                    ast_offset: 124,
1614                },
1615                DocsSourceMapLine {
1616                    string_offset: 4,
1617                    ast_offset: 129,
1618                },
1619                DocsSourceMapLine {
1620                    string_offset: 13,
1621                    ast_offset: 262,
1622                },
1623                DocsSourceMapLine {
1624                    string_offset: 17,
1625                    ast_offset: 268,
1626                },
1627                DocsSourceMapLine {
1628                    string_offset: 21,
1629                    ast_offset: 124,
1630                },
1631                DocsSourceMapLine {
1632                    string_offset: 25,
1633                    ast_offset: 129,
1634                },
1635                DocsSourceMapLine {
1636                    string_offset: 34,
1637                    ast_offset: 262,
1638                },
1639                DocsSourceMapLine {
1640                    string_offset: 38,
1641                    ast_offset: 268,
1642                },
1643            ]
1644        "#]]
1645        .assert_debug_eq(&docs.docs_source_map);
1646
1647        let range = |start, end| TextRange::new(TextSize::new(start), TextSize::new(end));
1648        let in_file = |range| InFile::new(file_id.into(), range);
1649        assert_eq!(docs.find_ast_range(range(0, 2)), None);
1650        assert_eq!(docs.find_ast_range(range(8, 10)), None);
1651        assert_eq!(
1652            docs.find_ast_range(range(9, 10)),
1653            Some((in_file(range(124, 125)), IsInnerDoc::No))
1654        );
1655        assert_eq!(docs.find_ast_range(range(20, 23)), None);
1656        assert_eq!(
1657            docs.find_ast_range(range(23, 25)),
1658            Some((in_file(range(263, 265)), IsInnerDoc::Yes))
1659        );
1660    }
1661
1662    #[test]
1663    fn crate_attrs() {
1664        let fixture = r#"
1665//- /lib.rs crate:foo crate-attr:no_std crate-attr:cfg(target_arch="x86")
1666        "#;
1667        let (db, file_id) = TestDB::with_single_file(fixture);
1668        let module = db.module_for_file(file_id.file_id(&db));
1669        let attrs = AttrFlags::query(&db, AttrDefId::ModuleId(module));
1670        assert!(attrs.contains(AttrFlags::IS_NO_STD | AttrFlags::HAS_CFG));
1671    }
1672}