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