33use hir_def:: {
44 attr:: { AttrsWithOwner , Documentation } ,
55 item_scope:: ItemInNs ,
6- path:: ModPath ,
7- resolver:: HasResolver ,
8- AttrDefId , GenericParamId , ModuleDefId ,
6+ path:: { ModPath , Path } ,
7+ resolver:: { HasResolver , Resolver , TypeNs } ,
8+ AssocItemId , AttrDefId , GenericParamId , ModuleDefId ,
99} ;
10- use hir_expand:: hygiene:: Hygiene ;
10+ use hir_expand:: { hygiene:: Hygiene , name :: Name } ;
1111use hir_ty:: db:: HirDatabase ;
1212use syntax:: { ast, AstNode } ;
1313
1414use crate :: {
15- Adt , AssocItem , Const , ConstParam , Enum , ExternCrateDecl , Field , Function , GenericParam , Impl ,
16- LifetimeParam , Macro , Module , ModuleDef , Static , Struct , Trait , TraitAlias , TypeAlias ,
17- TypeParam , Union , Variant ,
15+ Adt , AsAssocItem , AssocItem , BuiltinType , Const , ConstParam , Enum , ExternCrateDecl , Field ,
16+ Function , GenericParam , Impl , LifetimeParam , Macro , Module , ModuleDef , Static , Struct , Trait ,
17+ TraitAlias , TypeAlias , TypeParam , Union , Variant , VariantDef ,
1818} ;
1919
2020pub trait HasAttrs {
@@ -25,7 +25,7 @@ pub trait HasAttrs {
2525 db : & dyn HirDatabase ,
2626 link : & str ,
2727 ns : Option < Namespace > ,
28- ) -> Option < ModuleDef > ;
28+ ) -> Option < DocLinkDef > ;
2929}
3030
3131#[ derive( PartialEq , Eq , Hash , Copy , Clone , Debug ) ]
@@ -35,6 +35,13 @@ pub enum Namespace {
3535 Macros ,
3636}
3737
38+ /// Subset of `ide_db::Definition` that doc links can resolve to.
39+ pub enum DocLinkDef {
40+ ModuleDef ( ModuleDef ) ,
41+ Field ( Field ) ,
42+ SelfType ( Trait ) ,
43+ }
44+
3845macro_rules! impl_has_attrs {
3946 ( $( ( $def: ident, $def_id: ident) , ) * ) => { $(
4047 impl HasAttrs for $def {
@@ -46,9 +53,14 @@ macro_rules! impl_has_attrs {
4653 let def = AttrDefId :: $def_id( self . into( ) ) ;
4754 db. attrs( def) . docs( )
4855 }
49- fn resolve_doc_path( self , db: & dyn HirDatabase , link: & str , ns: Option <Namespace >) -> Option <ModuleDef > {
56+ fn resolve_doc_path(
57+ self ,
58+ db: & dyn HirDatabase ,
59+ link: & str ,
60+ ns: Option <Namespace >
61+ ) -> Option <DocLinkDef > {
5062 let def = AttrDefId :: $def_id( self . into( ) ) ;
51- resolve_doc_path( db, def, link, ns) . map ( ModuleDef :: from )
63+ resolve_doc_path( db, def, link, ns)
5264 }
5365 }
5466 ) * } ;
@@ -79,7 +91,12 @@ macro_rules! impl_has_attrs_enum {
7991 fn docs( self , db: & dyn HirDatabase ) -> Option <Documentation > {
8092 $enum:: $variant( self ) . docs( db)
8193 }
82- fn resolve_doc_path( self , db: & dyn HirDatabase , link: & str , ns: Option <Namespace >) -> Option <ModuleDef > {
94+ fn resolve_doc_path(
95+ self ,
96+ db: & dyn HirDatabase ,
97+ link: & str ,
98+ ns: Option <Namespace >
99+ ) -> Option <DocLinkDef > {
83100 $enum:: $variant( self ) . resolve_doc_path( db, link, ns)
84101 }
85102 }
@@ -111,7 +128,7 @@ impl HasAttrs for AssocItem {
111128 db : & dyn HirDatabase ,
112129 link : & str ,
113130 ns : Option < Namespace > ,
114- ) -> Option < ModuleDef > {
131+ ) -> Option < DocLinkDef > {
115132 match self {
116133 AssocItem :: Function ( it) => it. resolve_doc_path ( db, link, ns) ,
117134 AssocItem :: Const ( it) => it. resolve_doc_path ( db, link, ns) ,
@@ -147,9 +164,9 @@ impl HasAttrs for ExternCrateDecl {
147164 db : & dyn HirDatabase ,
148165 link : & str ,
149166 ns : Option < Namespace > ,
150- ) -> Option < ModuleDef > {
167+ ) -> Option < DocLinkDef > {
151168 let def = AttrDefId :: ExternCrateId ( self . into ( ) ) ;
152- resolve_doc_path ( db, def, link, ns) . map ( ModuleDef :: from )
169+ resolve_doc_path ( db, def, link, ns)
153170 }
154171}
155172
@@ -159,7 +176,7 @@ fn resolve_doc_path(
159176 def : AttrDefId ,
160177 link : & str ,
161178 ns : Option < Namespace > ,
162- ) -> Option < ModuleDefId > {
179+ ) -> Option < DocLinkDef > {
163180 let resolver = match def {
164181 AttrDefId :: ModuleId ( it) => it. resolver ( db. upcast ( ) ) ,
165182 AttrDefId :: FieldId ( it) => it. parent . resolver ( db. upcast ( ) ) ,
@@ -184,32 +201,128 @@ fn resolve_doc_path(
184201 . resolver ( db. upcast ( ) ) ,
185202 } ;
186203
187- let modpath = {
188- // FIXME: this is not how we should get a mod path here
204+ let mut modpath = modpath_from_str ( db, link) ?;
205+
206+ let resolved = resolver. resolve_module_path_in_items ( db. upcast ( ) , & modpath) ;
207+ if resolved. is_none ( ) {
208+ let last_name = modpath. pop_segment ( ) ?;
209+ resolve_assoc_or_field ( db, resolver, modpath, last_name, ns)
210+ } else {
211+ let def = match ns {
212+ Some ( Namespace :: Types ) => resolved. take_types ( ) ,
213+ Some ( Namespace :: Values ) => resolved. take_values ( ) ,
214+ Some ( Namespace :: Macros ) => resolved. take_macros ( ) . map ( ModuleDefId :: MacroId ) ,
215+ None => resolved. iter_items ( ) . next ( ) . map ( |it| match it {
216+ ItemInNs :: Types ( it) => it,
217+ ItemInNs :: Values ( it) => it,
218+ ItemInNs :: Macros ( it) => ModuleDefId :: MacroId ( it) ,
219+ } ) ,
220+ } ;
221+ Some ( DocLinkDef :: ModuleDef ( def?. into ( ) ) )
222+ }
223+ }
224+
225+ fn resolve_assoc_or_field (
226+ db : & dyn HirDatabase ,
227+ resolver : Resolver ,
228+ path : ModPath ,
229+ name : Name ,
230+ ns : Option < Namespace > ,
231+ ) -> Option < DocLinkDef > {
232+ let path = Path :: from_known_path_with_no_generic ( path) ;
233+ // FIXME: This does not handle `Self` on trait definitions, which we should resolve to the
234+ // trait itself.
235+ let base_def = resolver. resolve_path_in_type_ns_fully ( db. upcast ( ) , & path) ?;
236+
237+ let ty = match base_def {
238+ TypeNs :: SelfType ( id) => Impl :: from ( id) . self_ty ( db) ,
239+ TypeNs :: GenericParam ( _) => {
240+ // Even if this generic parameter has some trait bounds, rustdoc doesn't
241+ // resolve `name` to trait items.
242+ return None ;
243+ }
244+ TypeNs :: AdtId ( id) | TypeNs :: AdtSelfType ( id) => Adt :: from ( id) . ty ( db) ,
245+ TypeNs :: EnumVariantId ( id) => {
246+ // Enum variants don't have path candidates.
247+ let variant = Variant :: from ( id) ;
248+ return resolve_field ( db, variant. into ( ) , name, ns) ;
249+ }
250+ TypeNs :: TypeAliasId ( id) => {
251+ let alias = TypeAlias :: from ( id) ;
252+ if alias. as_assoc_item ( db) . is_some ( ) {
253+ // We don't normalize associated type aliases, so we have nothing to
254+ // resolve `name` to.
255+ return None ;
256+ }
257+ alias. ty ( db)
258+ }
259+ TypeNs :: BuiltinType ( id) => BuiltinType :: from ( id) . ty ( db) ,
260+ TypeNs :: TraitId ( id) => {
261+ // Doc paths in this context may only resolve to an item of this trait
262+ // (i.e. no items of its supertraits), so we need to handle them here
263+ // independently of others.
264+ return db. trait_data ( id) . items . iter ( ) . find ( |it| it. 0 == name) . map ( |( _, assoc_id) | {
265+ let def = match * assoc_id {
266+ AssocItemId :: FunctionId ( it) => ModuleDef :: Function ( it. into ( ) ) ,
267+ AssocItemId :: ConstId ( it) => ModuleDef :: Const ( it. into ( ) ) ,
268+ AssocItemId :: TypeAliasId ( it) => ModuleDef :: TypeAlias ( it. into ( ) ) ,
269+ } ;
270+ DocLinkDef :: ModuleDef ( def)
271+ } ) ;
272+ }
273+ TypeNs :: TraitAliasId ( _) => {
274+ // XXX: Do these get resolved?
275+ return None ;
276+ }
277+ } ;
278+
279+ // FIXME: Resolve associated items here, e.g. `Option::map`. Note that associated items take
280+ // precedence over fields.
281+
282+ let variant_def = match ty. as_adt ( ) ? {
283+ Adt :: Struct ( it) => it. into ( ) ,
284+ Adt :: Union ( it) => it. into ( ) ,
285+ Adt :: Enum ( _) => return None ,
286+ } ;
287+ resolve_field ( db, variant_def, name, ns)
288+ }
289+
290+ fn resolve_field (
291+ db : & dyn HirDatabase ,
292+ def : VariantDef ,
293+ name : Name ,
294+ ns : Option < Namespace > ,
295+ ) -> Option < DocLinkDef > {
296+ if let Some ( Namespace :: Types | Namespace :: Macros ) = ns {
297+ return None ;
298+ }
299+ def. fields ( db) . into_iter ( ) . find ( |f| f. name ( db) == name) . map ( DocLinkDef :: Field )
300+ }
301+
302+ fn modpath_from_str ( db : & dyn HirDatabase , link : & str ) -> Option < ModPath > {
303+ // FIXME: this is not how we should get a mod path here.
304+ let try_get_modpath = |link : & str | {
189305 let ast_path = ast:: SourceFile :: parse ( & format ! ( "type T = {link};" ) )
190306 . syntax_node ( )
191307 . descendants ( )
192308 . find_map ( ast:: Path :: cast) ?;
193309 if ast_path. syntax ( ) . text ( ) != link {
194310 return None ;
195311 }
196- ModPath :: from_src ( db. upcast ( ) , ast_path, & Hygiene :: new_unhygienic ( ) ) ?
312+ ModPath :: from_src ( db. upcast ( ) , ast_path, & Hygiene :: new_unhygienic ( ) )
197313 } ;
198314
199- let resolved = resolver. resolve_module_path_in_items ( db. upcast ( ) , & modpath) ;
200- let resolved = if resolved. is_none ( ) {
201- resolver. resolve_module_path_in_trait_assoc_items ( db. upcast ( ) , & modpath) ?
202- } else {
203- resolved
204- } ;
205- match ns {
206- Some ( Namespace :: Types ) => resolved. take_types ( ) ,
207- Some ( Namespace :: Values ) => resolved. take_values ( ) ,
208- Some ( Namespace :: Macros ) => resolved. take_macros ( ) . map ( ModuleDefId :: MacroId ) ,
209- None => resolved. iter_items ( ) . next ( ) . map ( |it| match it {
210- ItemInNs :: Types ( it) => it,
211- ItemInNs :: Values ( it) => it,
212- ItemInNs :: Macros ( it) => ModuleDefId :: MacroId ( it) ,
213- } ) ,
315+ let full = try_get_modpath ( link) ;
316+ if full. is_some ( ) {
317+ return full;
214318 }
319+
320+ // Tuple field names cannot be a part of `ModPath` usually, but rustdoc can
321+ // resolve doc paths like `TupleStruct::0`.
322+ // FIXME: Find a better way to handle these.
323+ let ( base, maybe_tuple_field) = link. rsplit_once ( "::" ) ?;
324+ let tuple_field = Name :: new_tuple_field ( maybe_tuple_field. parse ( ) . ok ( ) ?) ;
325+ let mut modpath = try_get_modpath ( base) ?;
326+ modpath. push_segment ( tuple_field) ;
327+ Some ( modpath)
215328}
0 commit comments