Skip to main content

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