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