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,
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 #[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
305pub 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 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 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 return None;
423 }
424 TypeNs::AdtId(id) | TypeNs::AdtSelfType(id) => Adt::from(id).ty(db),
425 TypeNs::EnumVariantId(id) => {
426 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 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 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 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 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 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}