1use 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
256pub 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 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 return None;
339 }
340 TypeNs::AdtId(id) | TypeNs::AdtSelfType(id) => Adt::from(id).ty(db),
341 TypeNs::EnumVariantId(id) => {
342 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 return None;
352 }
353 alias.ty(db)
354 }
355 TypeNs::BuiltinType(id) => BuiltinType::from(id).ty(db),
356 TypeNs::TraitId(id) => {
357 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 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 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 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}