hir/
attrs.rs

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