1use 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 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 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#[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 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 return None;
769 }
770
771 Self::repr_assume_has(db, owner)
772 }
773
774 #[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 #[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 #[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 #[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 #[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 #[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 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 #[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 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 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 Flag(SmolStr),
1372 KeyValue { key: SmolStr, value: SmolStr },
1377 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}