1use 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 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
288pub 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 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 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 return None;
406 }
407 TypeNs::AdtId(id) | TypeNs::AdtSelfType(id) => Adt::from(id).ty(db),
408 TypeNs::EnumVariantId(id) => {
409 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 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 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 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 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 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}