1use 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 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 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 string_offset: TextSize,
460 ast_offset: TextSize,
462}
463
464#[derive(Debug, Clone, PartialEq, Eq, Hash)]
465pub struct Docs {
466 docs: String,
468 docs_source_map: Vec<DocsSourceMapLine>,
470 outline_mod: Option<(HirFileId, usize)>,
475 inline_file: HirFileId,
476 prefix_len: TextSize,
478 inline_inner_docs_start: Option<TextSize>,
480 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 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 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 &self.docs_source_map[outline_mod_end..]
531 } else {
532 return None;
534 }
535 } else {
536 file = outline_mod_file;
538 inner_docs_start = self.outline_inner_docs_start;
539 &self.docs_source_map
540 }
541 } else {
542 &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 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 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 *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 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 unsafe { docs.as_mut_vec() }.clear();
650 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 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 line_source.string_offset -= accumulated_offset;
702 line_source.ast_offset += indent_size;
703
704 accumulated_offset += indent_size;
705 }
706 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 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 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 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 #[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 #[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 #[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 #[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 #[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 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 #[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 Flag(SmolStr),
1457 KeyValue { key: SmolStr, value: SmolStr },
1462 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}