hir/
attrs.rs

1//! Attributes & documentation for hir types.
2
3use cfg::CfgExpr;
4use either::Either;
5use hir_def::{
6    AssocItemId, AttrDefId, FieldId, GenericDefId, ItemContainerId, LifetimeParamId, ModuleDefId,
7    TraitId, TypeOrConstParamId,
8    attrs::{AttrFlags, Docs, IsInnerDoc},
9    expr_store::path::Path,
10    hir::generics::GenericParams,
11    item_scope::ItemInNs,
12    per_ns::Namespace,
13    resolver::{HasResolver, Resolver, TypeNs},
14};
15use hir_expand::{
16    mod_path::{ModPath, PathKind},
17    name::Name,
18};
19use hir_ty::{
20    db::HirDatabase,
21    method_resolution::{
22        self, CandidateId, MethodError, MethodResolutionContext, MethodResolutionUnstableFeatures,
23    },
24    next_solver::{DbInterner, TypingMode, infer::DbInternerInferExt},
25};
26use intern::Symbol;
27use stdx::never;
28
29use crate::{
30    Adt, AsAssocItem, AssocItem, BuiltinType, Const, ConstParam, DocLinkDef, Enum, EnumVariant,
31    ExternCrateDecl, Field, Function, GenericParam, HasCrate, Impl, LangItem, LifetimeParam, Macro,
32    Module, ModuleDef, Static, Struct, Trait, Type, TypeAlias, TypeParam, Union, Variant,
33};
34
35#[derive(Debug, Clone, Copy)]
36pub enum AttrsOwner {
37    AttrDef(AttrDefId),
38    Field(FieldId),
39    LifetimeParam(LifetimeParamId),
40    TypeOrConstParam(TypeOrConstParamId),
41    /// Things that do not have attributes. Used for builtin derives.
42    Dummy,
43}
44
45impl AttrsOwner {
46    #[inline]
47    fn attr_def(&self) -> Option<AttrDefId> {
48        match self {
49            AttrsOwner::AttrDef(it) => Some(*it),
50            _ => None,
51        }
52    }
53}
54
55#[derive(Debug, Clone)]
56pub struct AttrsWithOwner {
57    pub(crate) attrs: AttrFlags,
58    owner: AttrsOwner,
59}
60
61impl AttrsWithOwner {
62    fn new(db: &dyn HirDatabase, owner: AttrDefId) -> Self {
63        Self { attrs: AttrFlags::query(db, owner), owner: AttrsOwner::AttrDef(owner) }
64    }
65
66    fn new_field(db: &dyn HirDatabase, owner: FieldId) -> Self {
67        Self { attrs: AttrFlags::query_field(db, owner), owner: AttrsOwner::Field(owner) }
68    }
69
70    fn new_lifetime_param(db: &dyn HirDatabase, owner: LifetimeParamId) -> Self {
71        Self {
72            attrs: AttrFlags::query_lifetime_param(db, owner),
73            owner: AttrsOwner::LifetimeParam(owner),
74        }
75    }
76    fn new_type_or_const_param(db: &dyn HirDatabase, owner: TypeOrConstParamId) -> Self {
77        Self {
78            attrs: AttrFlags::query_type_or_const_param(db, owner),
79            owner: AttrsOwner::TypeOrConstParam(owner),
80        }
81    }
82
83    #[inline]
84    pub fn is_unstable(&self) -> bool {
85        self.attrs.contains(AttrFlags::IS_UNSTABLE)
86    }
87
88    #[inline]
89    pub fn is_macro_export(&self) -> bool {
90        self.attrs.contains(AttrFlags::IS_MACRO_EXPORT)
91    }
92
93    #[inline]
94    pub fn is_doc_notable_trait(&self) -> bool {
95        self.attrs.contains(AttrFlags::IS_DOC_NOTABLE_TRAIT)
96    }
97
98    #[inline]
99    pub fn is_doc_hidden(&self) -> bool {
100        self.attrs.contains(AttrFlags::IS_DOC_HIDDEN)
101    }
102
103    #[inline]
104    pub fn is_deprecated(&self) -> bool {
105        self.attrs.contains(AttrFlags::IS_DEPRECATED)
106    }
107
108    #[inline]
109    pub fn is_non_exhaustive(&self) -> bool {
110        self.attrs.contains(AttrFlags::NON_EXHAUSTIVE)
111    }
112
113    #[inline]
114    pub fn is_test(&self) -> bool {
115        self.attrs.contains(AttrFlags::IS_TEST)
116    }
117
118    #[inline]
119    pub fn lang(&self, db: &dyn HirDatabase) -> Option<LangItem> {
120        self.owner
121            .attr_def()
122            .and_then(|owner| self.attrs.lang_item_with_attrs(db, owner))
123            .and_then(|lang| LangItem::from_symbol(&lang))
124    }
125
126    #[inline]
127    pub fn doc_aliases<'db>(&self, db: &'db dyn HirDatabase) -> &'db [Symbol] {
128        let owner = match self.owner {
129            AttrsOwner::AttrDef(it) => Either::Left(it),
130            AttrsOwner::Field(it) => Either::Right(it),
131            AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) | AttrsOwner::Dummy => {
132                return &[];
133            }
134        };
135        self.attrs.doc_aliases(db, owner)
136    }
137
138    #[inline]
139    pub fn cfgs<'db>(&self, db: &'db dyn HirDatabase) -> Option<&'db CfgExpr> {
140        let owner = match self.owner {
141            AttrsOwner::AttrDef(it) => Either::Left(it),
142            AttrsOwner::Field(it) => Either::Right(it),
143            AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) | AttrsOwner::Dummy => {
144                return None;
145            }
146        };
147        self.attrs.cfgs(db, owner)
148    }
149
150    #[inline]
151    pub fn hir_docs<'db>(&self, db: &'db dyn HirDatabase) -> Option<&'db Docs> {
152        match self.owner {
153            AttrsOwner::AttrDef(it) => AttrFlags::docs(db, it).as_deref(),
154            AttrsOwner::Field(it) => AttrFlags::field_docs(db, it),
155            AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) | AttrsOwner::Dummy => {
156                None
157            }
158        }
159    }
160}
161
162pub trait HasAttrs: Sized {
163    #[inline]
164    fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner {
165        match self.attr_id(db) {
166            AttrsOwner::AttrDef(it) => AttrsWithOwner::new(db, it),
167            AttrsOwner::Field(it) => AttrsWithOwner::new_field(db, it),
168            AttrsOwner::LifetimeParam(it) => AttrsWithOwner::new_lifetime_param(db, it),
169            AttrsOwner::TypeOrConstParam(it) => AttrsWithOwner::new_type_or_const_param(db, it),
170            AttrsOwner::Dummy => {
171                AttrsWithOwner { attrs: AttrFlags::empty(), owner: AttrsOwner::Dummy }
172            }
173        }
174    }
175
176    #[doc(hidden)]
177    fn attr_id(self, db: &dyn HirDatabase) -> AttrsOwner;
178
179    #[inline]
180    fn hir_docs(self, db: &dyn HirDatabase) -> Option<&Docs> {
181        match self.attr_id(db) {
182            AttrsOwner::AttrDef(it) => AttrFlags::docs(db, it).as_deref(),
183            AttrsOwner::Field(it) => AttrFlags::field_docs(db, it),
184            AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) | AttrsOwner::Dummy => {
185                None
186            }
187        }
188    }
189}
190
191macro_rules! impl_has_attrs {
192    ($(($def:ident, $def_id:ident),)*) => {$(
193        impl HasAttrs for $def {
194            #[inline]
195            fn attr_id(self, _db: &dyn HirDatabase) -> AttrsOwner {
196                AttrsOwner::AttrDef(AttrDefId::$def_id(self.into()))
197            }
198        }
199    )*};
200}
201
202impl_has_attrs![
203    (EnumVariant, EnumVariantId),
204    (Static, StaticId),
205    (Const, ConstId),
206    (Trait, TraitId),
207    (TypeAlias, TypeAliasId),
208    (Macro, MacroId),
209    (Adt, AdtId),
210    (ExternCrateDecl, ExternCrateId),
211];
212
213impl HasAttrs for Function {
214    fn attr_id(self, _db: &dyn HirDatabase) -> AttrsOwner {
215        match self.id {
216            crate::AnyFunctionId::FunctionId(id) => AttrsOwner::AttrDef(id.into()),
217            crate::AnyFunctionId::BuiltinDeriveImplMethod { .. } => AttrsOwner::Dummy,
218        }
219    }
220}
221
222impl HasAttrs for Impl {
223    fn attr_id(self, _db: &dyn HirDatabase) -> AttrsOwner {
224        match self.id {
225            hir_ty::next_solver::AnyImplId::ImplId(id) => AttrsOwner::AttrDef(id.into()),
226            hir_ty::next_solver::AnyImplId::BuiltinDeriveImplId(..) => AttrsOwner::Dummy,
227        }
228    }
229}
230
231macro_rules! impl_has_attrs_enum {
232    ($($variant:ident),* for $enum:ident) => {$(
233        impl HasAttrs for $variant {
234            #[inline]
235            fn attr_id(self, db: &dyn HirDatabase) -> AttrsOwner {
236                $enum::$variant(self).attr_id(db)
237            }
238        }
239    )*};
240}
241
242impl_has_attrs_enum![Struct, Union, Enum for Adt];
243impl_has_attrs_enum![TypeParam, ConstParam, LifetimeParam for GenericParam];
244
245impl HasAttrs for Module {
246    #[inline]
247    fn attr_id(self, _: &dyn HirDatabase) -> AttrsOwner {
248        AttrsOwner::AttrDef(AttrDefId::ModuleId(self.id))
249    }
250}
251
252impl HasAttrs for GenericParam {
253    #[inline]
254    fn attr_id(self, _db: &dyn HirDatabase) -> AttrsOwner {
255        match self {
256            GenericParam::TypeParam(it) => AttrsOwner::TypeOrConstParam(it.merge().into()),
257            GenericParam::ConstParam(it) => AttrsOwner::TypeOrConstParam(it.merge().into()),
258            GenericParam::LifetimeParam(it) => AttrsOwner::LifetimeParam(it.into()),
259        }
260    }
261}
262
263impl HasAttrs for AssocItem {
264    #[inline]
265    fn attr_id(self, db: &dyn HirDatabase) -> AttrsOwner {
266        match self {
267            AssocItem::Function(it) => it.attr_id(db),
268            AssocItem::Const(it) => it.attr_id(db),
269            AssocItem::TypeAlias(it) => it.attr_id(db),
270        }
271    }
272}
273
274impl HasAttrs for crate::Crate {
275    #[inline]
276    fn attr_id(self, db: &dyn HirDatabase) -> AttrsOwner {
277        self.root_module(db).attr_id(db)
278    }
279}
280
281impl HasAttrs for Field {
282    #[inline]
283    fn attr_id(self, _db: &dyn HirDatabase) -> AttrsOwner {
284        AttrsOwner::Field(self.into())
285    }
286}
287
288/// Resolves the item `link` points to in the scope of `def`.
289pub fn resolve_doc_path_on(
290    db: &dyn HirDatabase,
291    def: impl HasAttrs + Copy,
292    link: &str,
293    ns: Option<Namespace>,
294    is_inner_doc: IsInnerDoc,
295) -> Option<DocLinkDef> {
296    resolve_doc_path_on_(db, link, def.attr_id(db), ns, is_inner_doc)
297}
298
299fn resolve_doc_path_on_(
300    db: &dyn HirDatabase,
301    link: &str,
302    attr_id: AttrsOwner,
303    ns: Option<Namespace>,
304    is_inner_doc: IsInnerDoc,
305) -> Option<DocLinkDef> {
306    let resolver = match attr_id {
307        AttrsOwner::AttrDef(AttrDefId::ModuleId(it)) => {
308            if is_inner_doc.yes() {
309                it.resolver(db)
310            } else if let Some(parent) = Module::from(it).parent(db) {
311                parent.id.resolver(db)
312            } else {
313                it.resolver(db)
314            }
315        }
316        AttrsOwner::AttrDef(AttrDefId::AdtId(it)) => it.resolver(db),
317        AttrsOwner::AttrDef(AttrDefId::FunctionId(it)) => it.resolver(db),
318        AttrsOwner::AttrDef(AttrDefId::EnumVariantId(it)) => it.resolver(db),
319        AttrsOwner::AttrDef(AttrDefId::StaticId(it)) => it.resolver(db),
320        AttrsOwner::AttrDef(AttrDefId::ConstId(it)) => it.resolver(db),
321        AttrsOwner::AttrDef(AttrDefId::TraitId(it)) => it.resolver(db),
322        AttrsOwner::AttrDef(AttrDefId::TypeAliasId(it)) => it.resolver(db),
323        AttrsOwner::AttrDef(AttrDefId::ImplId(it)) => it.resolver(db),
324        AttrsOwner::AttrDef(AttrDefId::ExternBlockId(it)) => it.resolver(db),
325        AttrsOwner::AttrDef(AttrDefId::UseId(it)) => it.resolver(db),
326        AttrsOwner::AttrDef(AttrDefId::MacroId(it)) => it.resolver(db),
327        AttrsOwner::AttrDef(AttrDefId::ExternCrateId(it)) => it.resolver(db),
328        AttrsOwner::Field(it) => it.parent.resolver(db),
329        AttrsOwner::LifetimeParam(_) | AttrsOwner::TypeOrConstParam(_) | AttrsOwner::Dummy => {
330            return None;
331        }
332    };
333
334    let mut modpath = doc_modpath_from_str(link)?;
335
336    let resolved = resolver.resolve_module_path_in_items(db, &modpath);
337    if resolved.is_none() {
338        let last_name = modpath.pop_segment()?;
339        resolve_assoc_or_field(db, resolver, modpath, last_name, ns)
340    } else {
341        let def = match ns {
342            Some(Namespace::Types) => resolved.take_types(),
343            Some(Namespace::Values) => resolved.take_values(),
344            Some(Namespace::Macros) => resolved.take_macros().map(ModuleDefId::MacroId),
345            None => resolved.iter_items().next().map(|(it, _)| match it {
346                ItemInNs::Types(it) => it,
347                ItemInNs::Values(it) => it,
348                ItemInNs::Macros(it) => ModuleDefId::MacroId(it),
349            }),
350        };
351        Some(DocLinkDef::ModuleDef(def?.into()))
352    }
353}
354
355fn resolve_assoc_or_field(
356    db: &dyn HirDatabase,
357    resolver: Resolver<'_>,
358    path: ModPath,
359    name: Name,
360    ns: Option<Namespace>,
361) -> Option<DocLinkDef> {
362    let path = Path::from_known_path_with_no_generic(path);
363    let base_def = resolver.resolve_path_in_type_ns_fully(db, &path)?;
364
365    let handle_trait = |id: TraitId| {
366        // Doc paths in this context may only resolve to an item of this trait
367        // (i.e. no items of its supertraits), so we need to handle them here
368        // independently of others.
369        id.trait_items(db).items.iter().find(|it| it.0 == name).map(|(_, assoc_id)| {
370            let def = match *assoc_id {
371                AssocItemId::FunctionId(it) => ModuleDef::Function(it.into()),
372                AssocItemId::ConstId(it) => ModuleDef::Const(it.into()),
373                AssocItemId::TypeAliasId(it) => ModuleDef::TypeAlias(it.into()),
374            };
375            DocLinkDef::ModuleDef(def)
376        })
377    };
378    let ty = match base_def {
379        TypeNs::SelfType(id) => Impl::from(id).self_ty(db),
380        TypeNs::GenericParam(param) => {
381            let generic_params = GenericParams::of(db, param.parent());
382            if generic_params[param.local_id()].is_trait_self() {
383                // `Self::assoc` in traits should refer to the trait itself.
384                let parent_trait = |container| match container {
385                    ItemContainerId::TraitId(trait_) => handle_trait(trait_),
386                    _ => {
387                        never!("container {container:?} should be a trait");
388                        None
389                    }
390                };
391                return match param.parent() {
392                    GenericDefId::TraitId(trait_) => handle_trait(trait_),
393                    GenericDefId::ConstId(it) => parent_trait(it.loc(db).container),
394                    GenericDefId::FunctionId(it) => parent_trait(it.loc(db).container),
395                    GenericDefId::TypeAliasId(it) => parent_trait(it.loc(db).container),
396                    _ => {
397                        never!("type param {param:?} should belong to a trait");
398                        None
399                    }
400                };
401            }
402
403            // Even if this generic parameter has some trait bounds, rustdoc doesn't
404            // resolve `name` to trait items.
405            return None;
406        }
407        TypeNs::AdtId(id) | TypeNs::AdtSelfType(id) => Adt::from(id).ty(db),
408        TypeNs::EnumVariantId(id) => {
409            // Enum variants don't have path candidates.
410            let variant = EnumVariant::from(id);
411            return resolve_field(db, variant.into(), name, ns);
412        }
413        TypeNs::TypeAliasId(id) => {
414            let alias = TypeAlias::from(id);
415            if alias.as_assoc_item(db).is_some() {
416                // We don't normalize associated type aliases, so we have nothing to
417                // resolve `name` to.
418                return None;
419            }
420            alias.ty(db)
421        }
422        TypeNs::BuiltinType(id) => BuiltinType::from(id).ty(db),
423        TypeNs::TraitId(id) => return handle_trait(id),
424        TypeNs::ModuleId(_) => {
425            return None;
426        }
427    };
428
429    // Resolve inherent items first, then trait items, then fields.
430    if let Some(assoc_item_def) = resolve_assoc_item(db, &ty, &name, ns) {
431        return Some(assoc_item_def);
432    }
433
434    if let Some(impl_trait_item_def) = resolve_impl_trait_item(db, resolver, &ty, &name, ns) {
435        return Some(impl_trait_item_def);
436    }
437
438    let variant_def = match ty.as_adt()? {
439        Adt::Struct(it) => it.into(),
440        Adt::Union(it) => it.into(),
441        Adt::Enum(enum_) => {
442            // Can happen on `Self::Variant` (otherwise would be fully resolved by the resolver).
443            return enum_
444                .id
445                .enum_variants(db)
446                .variant(&name)
447                .map(|variant| DocLinkDef::ModuleDef(ModuleDef::EnumVariant(variant.into())));
448        }
449    };
450    resolve_field(db, variant_def, name, ns)
451}
452
453fn resolve_assoc_item<'db>(
454    db: &'db dyn HirDatabase,
455    ty: &Type<'db>,
456    name: &Name,
457    ns: Option<Namespace>,
458) -> Option<DocLinkDef> {
459    ty.iterate_assoc_items(db, move |assoc_item| {
460        if assoc_item.name(db)? != *name {
461            return None;
462        }
463        as_module_def_if_namespace_matches(assoc_item, ns)
464    })
465}
466
467fn resolve_impl_trait_item<'db>(
468    db: &'db dyn HirDatabase,
469    resolver: Resolver<'_>,
470    ty: &Type<'db>,
471    name: &Name,
472    ns: Option<Namespace>,
473) -> Option<DocLinkDef> {
474    let krate = ty.krate(db);
475    let environment = crate::param_env_from_resolver(db, &resolver);
476    let traits_in_scope = resolver.traits_in_scope(db);
477
478    // `ty.iterate_path_candidates()` require a scope, which is not available when resolving
479    // attributes here. Use path resolution directly instead.
480    //
481    // FIXME: resolve type aliases (which are not yielded by iterate_path_candidates)
482    let interner = DbInterner::new_with(db, environment.krate);
483    let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis);
484    let unstable_features =
485        MethodResolutionUnstableFeatures::from_def_map(resolver.top_level_def_map());
486    let ctx = MethodResolutionContext {
487        infcx: &infcx,
488        resolver: &resolver,
489        param_env: environment.param_env,
490        traits_in_scope: &traits_in_scope,
491        edition: krate.edition(db),
492        unstable_features: &unstable_features,
493    };
494    let resolution = ctx.probe_for_name(method_resolution::Mode::Path, name.clone(), ty.ty);
495    let resolution = match resolution {
496        Ok(resolution) => resolution.item,
497        Err(MethodError::PrivateMatch(resolution)) => resolution.item,
498        _ => return None,
499    };
500    let resolution = match resolution {
501        CandidateId::FunctionId(id) => AssocItem::Function(id.into()),
502        CandidateId::ConstId(id) => AssocItem::Const(id.into()),
503    };
504    as_module_def_if_namespace_matches(resolution, ns)
505}
506
507fn resolve_field(
508    db: &dyn HirDatabase,
509    def: Variant,
510    name: Name,
511    ns: Option<Namespace>,
512) -> Option<DocLinkDef> {
513    if let Some(Namespace::Types | Namespace::Macros) = ns {
514        return None;
515    }
516    def.fields(db).into_iter().find(|f| f.name(db) == name).map(DocLinkDef::Field)
517}
518
519fn as_module_def_if_namespace_matches(
520    assoc_item: AssocItem,
521    ns: Option<Namespace>,
522) -> Option<DocLinkDef> {
523    let (def, expected_ns) = match assoc_item {
524        AssocItem::Function(it) => (ModuleDef::Function(it), Namespace::Values),
525        AssocItem::Const(it) => (ModuleDef::Const(it), Namespace::Values),
526        AssocItem::TypeAlias(it) => (ModuleDef::TypeAlias(it), Namespace::Types),
527    };
528
529    (ns.unwrap_or(expected_ns) == expected_ns).then_some(DocLinkDef::ModuleDef(def))
530}
531
532fn doc_modpath_from_str(link: &str) -> Option<ModPath> {
533    // FIXME: this is not how we should get a mod path here.
534    let try_get_modpath = |link: &str| {
535        let mut parts = link.split("::");
536        let mut first_segment = None;
537        let kind = match parts.next()? {
538            "" => PathKind::Abs,
539            "crate" => PathKind::Crate,
540            "self" => PathKind::SELF,
541            "super" => {
542                let mut deg = 1;
543                for segment in parts.by_ref() {
544                    if segment == "super" {
545                        deg += 1;
546                    } else {
547                        first_segment = Some(segment);
548                        break;
549                    }
550                }
551                PathKind::Super(deg)
552            }
553            segment => {
554                first_segment = Some(segment);
555                PathKind::Plain
556            }
557        };
558        let parts = first_segment.into_iter().chain(parts).map(|segment| match segment.parse() {
559            Ok(idx) => Name::new_tuple_field(idx),
560            Err(_) => Name::new_root(segment.split_once('<').map_or(segment, |it| it.0)),
561        });
562        Some(ModPath::from_segments(kind, parts))
563    };
564    try_get_modpath(link)
565}