1
- use rustc:: lint as lint;
2
- use rustc:: hir;
3
- use rustc:: hir:: def:: Def ;
1
+ use errors:: Applicability ;
2
+ use rustc:: hir:: def:: { Def , Namespace :: { self , * } , PerNS } ;
4
3
use rustc:: hir:: def_id:: DefId ;
4
+ use rustc:: hir;
5
+ use rustc:: lint as lint;
5
6
use rustc:: ty;
6
7
use syntax;
7
8
use syntax:: ast:: { self , Ident } ;
@@ -35,39 +36,24 @@ pub fn collect_intra_doc_links(krate: Crate, cx: &DocContext<'_>) -> Crate {
35
36
}
36
37
}
37
38
38
- #[ derive( Debug ) ]
39
- enum PathKind {
40
- /// Either a value or type, but not a macro
41
- Unknown ,
42
- /// Macro
43
- Macro ,
44
- /// Values, functions, consts, statics (everything in the value namespace)
45
- Value ,
46
- /// Types, traits (everything in the type namespace)
47
- Type ,
48
- }
49
-
50
39
struct LinkCollector < ' a , ' tcx > {
51
40
cx : & ' a DocContext < ' tcx > ,
52
41
mod_ids : Vec < ast:: NodeId > ,
53
- is_nightly_build : bool ,
54
42
}
55
43
56
44
impl < ' a , ' tcx > LinkCollector < ' a , ' tcx > {
57
45
fn new ( cx : & ' a DocContext < ' tcx > ) -> Self {
58
46
LinkCollector {
59
47
cx,
60
48
mod_ids : Vec :: new ( ) ,
61
- is_nightly_build : UnstableFeatures :: from_environment ( ) . is_nightly_build ( ) ,
62
49
}
63
50
}
64
51
65
- /// Resolves a given string as a path, along with whether or not it is
66
- /// in the value namespace. Also returns an optional URL fragment in the case
67
- /// of variants and methods.
52
+ /// Resolves a string as a path within a particular namespace. Also returns an optional
53
+ /// URL fragment in the case of variants and methods.
68
54
fn resolve ( & self ,
69
55
path_str : & str ,
70
- is_val : bool ,
56
+ ns : Namespace ,
71
57
current_item : & Option < String > ,
72
58
parent_id : Option < ast:: NodeId > )
73
59
-> Result < ( Def , Option < String > ) , ( ) >
@@ -78,11 +64,11 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
78
64
// path.
79
65
if let Some ( id) = parent_id. or ( self . mod_ids . last ( ) . cloned ( ) ) {
80
66
// FIXME: `with_scope` requires the `NodeId` of a module.
81
- let result = cx. enter_resolver ( |resolver| resolver . with_scope ( id ,
82
- |resolver| {
83
- resolver. resolve_str_path_error ( DUMMY_SP ,
84
- & path_str , is_val )
85
- } ) ) ;
67
+ let result = cx. enter_resolver ( |resolver| {
68
+ resolver . with_scope ( id , |resolver| {
69
+ resolver. resolve_str_path_error ( DUMMY_SP , & path_str , ns == ValueNS )
70
+ } )
71
+ } ) ;
86
72
87
73
if let Ok ( result) = result {
88
74
// In case this is a trait item, skip the
@@ -95,16 +81,16 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
95
81
_ => return Ok ( ( result. def , None ) )
96
82
} ;
97
83
98
- if value != is_val {
84
+ if value != ( ns == ValueNS ) {
99
85
return Err ( ( ) )
100
86
}
101
- } else if let Some ( prim) = is_primitive ( path_str, is_val ) {
87
+ } else if let Some ( prim) = is_primitive ( path_str, ns ) {
102
88
return Ok ( ( prim, Some ( path_str. to_owned ( ) ) ) )
103
89
} else {
104
90
// If resolution failed, it may still be a method
105
91
// because methods are not handled by the resolver
106
92
// If so, bail when we're not looking for a value.
107
- if !is_val {
93
+ if ns != ValueNS {
108
94
return Err ( ( ) )
109
95
}
110
96
}
@@ -128,7 +114,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
128
114
path = name. clone ( ) ;
129
115
}
130
116
}
131
- if let Some ( prim) = is_primitive ( & path, false ) {
117
+ if let Some ( prim) = is_primitive ( & path, TypeNS ) {
132
118
let did = primitive_impl ( cx, & path) . ok_or ( ( ) ) ?;
133
119
return cx. tcx . associated_items ( did)
134
120
. find ( |item| item. ident . name == item_name)
@@ -152,8 +138,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
152
138
. find ( |item| item. ident . name == item_name) ;
153
139
if let Some ( item) = item {
154
140
let out = match item. kind {
155
- ty:: AssociatedKind :: Method if is_val => "method" ,
156
- ty:: AssociatedKind :: Const if is_val => "associatedconstant" ,
141
+ ty:: AssociatedKind :: Method if ns == ValueNS => "method" ,
142
+ ty:: AssociatedKind :: Const if ns == ValueNS => "associatedconstant" ,
157
143
_ => return Err ( ( ) )
158
144
} ;
159
145
Ok ( ( ty. def , Some ( format ! ( "{}.{}" , out, item_name) ) ) )
@@ -190,9 +176,9 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
190
176
. find ( |item| item. ident . name == item_name) ;
191
177
if let Some ( item) = item {
192
178
let kind = match item. kind {
193
- ty:: AssociatedKind :: Const if is_val => "associatedconstant" ,
194
- ty:: AssociatedKind :: Type if !is_val => "associatedtype" ,
195
- ty:: AssociatedKind :: Method if is_val => {
179
+ ty:: AssociatedKind :: Const if ns == ValueNS => "associatedconstant" ,
180
+ ty:: AssociatedKind :: Type if ns == TypeNS => "associatedtype" ,
181
+ ty:: AssociatedKind :: Method if ns == ValueNS => {
196
182
if item. defaultness . has_value ( ) {
197
183
"method"
198
184
} else {
@@ -279,39 +265,35 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
279
265
280
266
look_for_tests ( & cx, & dox, & item, true ) ;
281
267
282
- if !self . is_nightly_build {
283
- return None ;
284
- }
285
-
286
268
for ( ori_link, link_range) in markdown_links ( & dox) {
287
269
// Bail early for real links.
288
270
if ori_link. contains ( '/' ) {
289
271
continue ;
290
272
}
291
273
let link = ori_link. replace ( "`" , "" ) ;
292
274
let ( def, fragment) = {
293
- let mut kind = PathKind :: Unknown ;
275
+ let mut kind = None ;
294
276
let path_str = if let Some ( prefix) =
295
277
[ "struct@" , "enum@" , "type@" ,
296
278
"trait@" , "union@" ] . iter ( )
297
279
. find ( |p| link. starts_with ( * * p) ) {
298
- kind = PathKind :: Type ;
280
+ kind = Some ( TypeNS ) ;
299
281
link. trim_start_matches ( prefix)
300
282
} else if let Some ( prefix) =
301
283
[ "const@" , "static@" ,
302
284
"value@" , "function@" , "mod@" ,
303
285
"fn@" , "module@" , "method@" ]
304
286
. iter ( ) . find ( |p| link. starts_with ( * * p) ) {
305
- kind = PathKind :: Value ;
287
+ kind = Some ( ValueNS ) ;
306
288
link. trim_start_matches ( prefix)
307
289
} else if link. ends_with ( "()" ) {
308
- kind = PathKind :: Value ;
290
+ kind = Some ( ValueNS ) ;
309
291
link. trim_end_matches ( "()" )
310
292
} else if link. starts_with ( "macro@" ) {
311
- kind = PathKind :: Macro ;
293
+ kind = Some ( MacroNS ) ;
312
294
link. trim_start_matches ( "macro@" )
313
295
} else if link. ends_with ( '!' ) {
314
- kind = PathKind :: Macro ;
296
+ kind = Some ( MacroNS ) ;
315
297
link. trim_end_matches ( '!' )
316
298
} else {
317
299
& link[ ..]
@@ -323,8 +305,8 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
323
305
}
324
306
325
307
match kind {
326
- PathKind :: Value => {
327
- if let Ok ( def) = self . resolve ( path_str, true , & current_item, parent_node) {
308
+ Some ( ns @ ValueNS ) => {
309
+ if let Ok ( def) = self . resolve ( path_str, ns , & current_item, parent_node) {
328
310
def
329
311
} else {
330
312
resolution_failure ( cx, & item. attrs , path_str, & dox, link_range) ;
@@ -334,71 +316,58 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
334
316
continue ;
335
317
}
336
318
}
337
- PathKind :: Type => {
338
- if let Ok ( def) = self . resolve ( path_str, false , & current_item, parent_node) {
319
+ Some ( ns @ TypeNS ) => {
320
+ if let Ok ( def) = self . resolve ( path_str, ns , & current_item, parent_node) {
339
321
def
340
322
} else {
341
323
resolution_failure ( cx, & item. attrs , path_str, & dox, link_range) ;
342
324
// This could just be a normal link.
343
325
continue ;
344
326
}
345
327
}
346
- PathKind :: Unknown => {
328
+ None => {
347
329
// Try everything!
348
- if let Some ( macro_def) = macro_resolve ( cx, path_str) {
349
- if let Ok ( type_def) =
350
- self . resolve ( path_str, false , & current_item, parent_node)
351
- {
352
- let ( type_kind, article, type_disambig)
353
- = type_ns_kind ( type_def. 0 , path_str) ;
354
- ambiguity_error ( cx, & item. attrs , path_str,
355
- article, type_kind, & type_disambig,
356
- "a" , "macro" , & format ! ( "macro@{}" , path_str) ) ;
357
- continue ;
358
- } else if let Ok ( value_def) =
359
- self . resolve ( path_str, true , & current_item, parent_node)
360
- {
361
- let ( value_kind, value_disambig)
362
- = value_ns_kind ( value_def. 0 , path_str)
363
- . expect ( "struct and mod cases should have been \
364
- caught in previous branch") ;
365
- ambiguity_error ( cx, & item. attrs , path_str,
366
- "a" , value_kind, & value_disambig,
367
- "a" , "macro" , & format ! ( "macro@{}" , path_str) ) ;
368
- }
369
- ( macro_def, None )
370
- } else if let Ok ( type_def) =
371
- self . resolve ( path_str, false , & current_item, parent_node)
372
- {
373
- // It is imperative we search for not-a-value first
374
- // Otherwise we will find struct ctors for when we are looking
375
- // for structs, and the link won't work if there is something in
376
- // both namespaces.
377
- if let Ok ( value_def) =
378
- self . resolve ( path_str, true , & current_item, parent_node)
379
- {
380
- let kind = value_ns_kind ( value_def. 0 , path_str) ;
381
- if let Some ( ( value_kind, value_disambig) ) = kind {
382
- let ( type_kind, article, type_disambig)
383
- = type_ns_kind ( type_def. 0 , path_str) ;
384
- ambiguity_error ( cx, & item. attrs , path_str,
385
- article, type_kind, & type_disambig,
386
- "a" , value_kind, & value_disambig) ;
387
- continue ;
388
- }
389
- }
390
- type_def
391
- } else if let Ok ( value_def) =
392
- self . resolve ( path_str, true , & current_item, parent_node)
393
- {
394
- value_def
395
- } else {
330
+ let candidates = PerNS {
331
+ macro_ns : macro_resolve ( cx, path_str) . map ( |def| ( def, None ) ) ,
332
+ type_ns : self
333
+ . resolve ( path_str, TypeNS , & current_item, parent_node)
334
+ . ok ( ) ,
335
+ value_ns : self
336
+ . resolve ( path_str, ValueNS , & current_item, parent_node)
337
+ . ok ( )
338
+ . and_then ( |( def, fragment) | {
339
+ // Constructors are picked up in the type namespace.
340
+ match def {
341
+ Def :: StructCtor ( ..)
342
+ | Def :: VariantCtor ( ..)
343
+ | Def :: SelfCtor ( ..) => None ,
344
+ _ => Some ( ( def, fragment) )
345
+ }
346
+ } ) ,
347
+ } ;
348
+
349
+ if candidates. is_empty ( ) {
396
350
resolution_failure ( cx, & item. attrs , path_str, & dox, link_range) ;
397
351
// this could just be a normal link
398
352
continue ;
399
353
}
354
+
355
+ let is_unambiguous = candidates. clone ( ) . present_items ( ) . count ( ) == 1 ;
356
+ if is_unambiguous {
357
+ candidates. present_items ( ) . next ( ) . unwrap ( )
358
+ } else {
359
+ ambiguity_error (
360
+ cx,
361
+ & item. attrs ,
362
+ path_str,
363
+ & dox,
364
+ link_range,
365
+ candidates. map ( |candidate| candidate. map ( |( def, _) | def) ) ,
366
+ ) ;
367
+ continue ;
368
+ }
400
369
}
401
- PathKind :: Macro => {
370
+ Some ( MacroNS ) => {
402
371
if let Some ( def) = macro_resolve ( cx, path_str) {
403
372
( def, None )
404
373
} else {
@@ -505,59 +474,114 @@ fn resolution_failure(
505
474
diag. emit ( ) ;
506
475
}
507
476
508
- fn ambiguity_error ( cx : & DocContext < ' _ > , attrs : & Attributes ,
509
- path_str : & str ,
510
- article1 : & str , kind1 : & str , disambig1 : & str ,
511
- article2 : & str , kind2 : & str , disambig2 : & str ) {
477
+ fn ambiguity_error (
478
+ cx : & DocContext < ' _ > ,
479
+ attrs : & Attributes ,
480
+ path_str : & str ,
481
+ dox : & str ,
482
+ link_range : Option < Range < usize > > ,
483
+ candidates : PerNS < Option < Def > > ,
484
+ ) {
512
485
let sp = span_of_attrs ( attrs) ;
513
- cx. sess ( )
514
- . struct_span_warn ( sp,
515
- & format ! ( "`{}` is both {} {} and {} {}" ,
516
- path_str, article1, kind1,
517
- article2, kind2) )
518
- . help ( & format ! ( "try `{}` if you want to select the {}, \
519
- or `{}` if you want to \
520
- select the {}",
521
- disambig1, kind1, disambig2,
522
- kind2) )
523
- . emit ( ) ;
524
- }
525
486
526
- /// Given a def, returns its name and disambiguator
527
- /// for a value namespace.
528
- ///
529
- /// Returns `None` for things which cannot be ambiguous since
530
- /// they exist in both namespaces (structs and modules).
531
- fn value_ns_kind ( def : Def , path_str : & str ) -> Option < ( & ' static str , String ) > {
532
- match def {
533
- // Structs, variants, and mods exist in both namespaces; skip them.
534
- Def :: StructCtor ( ..) | Def :: Mod ( ..) | Def :: Variant ( ..) |
535
- Def :: VariantCtor ( ..) | Def :: SelfCtor ( ..)
536
- => None ,
537
- Def :: Fn ( ..)
538
- => Some ( ( "function" , format ! ( "{}()" , path_str) ) ) ,
539
- Def :: Method ( ..)
540
- => Some ( ( "method" , format ! ( "{}()" , path_str) ) ) ,
541
- Def :: Const ( ..)
542
- => Some ( ( "const" , format ! ( "const@{}" , path_str) ) ) ,
543
- Def :: Static ( ..)
544
- => Some ( ( "static" , format ! ( "static@{}" , path_str) ) ) ,
545
- _ => Some ( ( "value" , format ! ( "value@{}" , path_str) ) ) ,
487
+ let mut msg = format ! ( "`{}` is " , path_str) ;
488
+
489
+ let candidates = [ TypeNS , ValueNS , MacroNS ] . iter ( ) . filter_map ( |& ns| {
490
+ candidates[ ns] . map ( |def| ( def, ns) )
491
+ } ) . collect :: < Vec < _ > > ( ) ;
492
+ match candidates. as_slice ( ) {
493
+ [ ( first_def, _) , ( second_def, _) ] => {
494
+ msg += & format ! (
495
+ "both {} {} and {} {}" ,
496
+ first_def. article( ) ,
497
+ first_def. kind_name( ) ,
498
+ second_def. article( ) ,
499
+ second_def. kind_name( ) ,
500
+ ) ;
501
+ }
502
+ _ => {
503
+ let mut candidates = candidates. iter ( ) . peekable ( ) ;
504
+ while let Some ( ( def, _) ) = candidates. next ( ) {
505
+ if candidates. peek ( ) . is_some ( ) {
506
+ msg += & format ! ( "{} {}, " , def. article( ) , def. kind_name( ) ) ;
507
+ } else {
508
+ msg += & format ! ( "and {} {}" , def. article( ) , def. kind_name( ) ) ;
509
+ }
510
+ }
511
+ }
546
512
}
547
- }
548
513
549
- /// Given a def, returns its name, the article to be used, and a disambiguator
550
- /// for the type namespace.
551
- fn type_ns_kind ( def : Def , path_str : & str ) -> ( & ' static str , & ' static str , String ) {
552
- let ( kind, article) = match def {
553
- // We can still have non-tuple structs.
554
- Def :: Struct ( ..) => ( "struct" , "a" ) ,
555
- Def :: Enum ( ..) => ( "enum" , "an" ) ,
556
- Def :: Trait ( ..) => ( "trait" , "a" ) ,
557
- Def :: Union ( ..) => ( "union" , "a" ) ,
558
- _ => ( "type" , "a" ) ,
559
- } ;
560
- ( kind, article, format ! ( "{}@{}" , kind, path_str) )
514
+ let mut diag = cx. tcx . struct_span_lint_hir (
515
+ lint:: builtin:: INTRA_DOC_LINK_RESOLUTION_FAILURE ,
516
+ hir:: CRATE_HIR_ID ,
517
+ sp,
518
+ & msg,
519
+ ) ;
520
+
521
+ if let Some ( link_range) = link_range {
522
+ if let Some ( sp) = super :: source_span_for_markdown_range ( cx, dox, & link_range, attrs) {
523
+ diag. set_span ( sp) ;
524
+ diag. span_label ( sp, "ambiguous link" ) ;
525
+
526
+ for ( def, ns) in candidates {
527
+ let ( action, mut suggestion) = match def {
528
+ Def :: Method ( ..) | Def :: Fn ( ..) => {
529
+ ( "add parentheses" , format ! ( "{}()" , path_str) )
530
+ }
531
+ Def :: Macro ( ..) => {
532
+ ( "add an exclamation mark" , format ! ( "{}!" , path_str) )
533
+ }
534
+ _ => {
535
+ let type_ = match ( def, ns) {
536
+ ( Def :: Const ( ..) , _) => "const" ,
537
+ ( Def :: Static ( ..) , _) => "static" ,
538
+ ( Def :: Struct ( ..) , _) => "struct" ,
539
+ ( Def :: Enum ( ..) , _) => "enum" ,
540
+ ( Def :: Union ( ..) , _) => "union" ,
541
+ ( Def :: Trait ( ..) , _) => "trait" ,
542
+ ( Def :: Mod ( ..) , _) => "module" ,
543
+ ( _, TypeNS ) => "type" ,
544
+ ( _, ValueNS ) => "value" ,
545
+ ( _, MacroNS ) => "macro" ,
546
+ } ;
547
+
548
+ // FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
549
+ ( "prefix with the item type" , format ! ( "{}@{}" , type_, path_str) )
550
+ }
551
+ } ;
552
+
553
+ if dox. bytes ( ) . nth ( link_range. start ) == Some ( b'`' ) {
554
+ suggestion = format ! ( "`{}`" , suggestion) ;
555
+ }
556
+
557
+ diag. span_suggestion (
558
+ sp,
559
+ & format ! ( "to link to the {}, {}" , def. kind_name( ) , action) ,
560
+ suggestion,
561
+ Applicability :: MaybeIncorrect ,
562
+ ) ;
563
+ }
564
+ } else {
565
+ // blah blah blah\nblah\nblah [blah] blah blah\nblah blah
566
+ // ^ ~~~~
567
+ // | link_range
568
+ // last_new_line_offset
569
+ let last_new_line_offset = dox[ ..link_range. start ] . rfind ( '\n' ) . map_or ( 0 , |n| n + 1 ) ;
570
+ let line = dox[ last_new_line_offset..] . lines ( ) . next ( ) . unwrap_or ( "" ) ;
571
+
572
+ // Print the line containing the `link_range` and manually mark it with '^'s.
573
+ diag. note ( & format ! (
574
+ "the link appears in this line:\n \n {line}\n \
575
+ {indicator: <before$}{indicator:^<found$}",
576
+ line=line,
577
+ indicator="" ,
578
+ before=link_range. start - last_new_line_offset,
579
+ found=link_range. len( ) ,
580
+ ) ) ;
581
+ }
582
+ }
583
+
584
+ diag. emit ( ) ;
561
585
}
562
586
563
587
/// Given an enum variant's def, return the def of its enum and the associated fragment.
@@ -594,11 +618,11 @@ const PRIMITIVES: &[(&str, Def)] = &[
594
618
( "char" , Def :: PrimTy ( hir:: PrimTy :: Char ) ) ,
595
619
] ;
596
620
597
- fn is_primitive ( path_str : & str , is_val : bool ) -> Option < Def > {
598
- if is_val {
599
- None
600
- } else {
621
+ fn is_primitive ( path_str : & str , ns : Namespace ) -> Option < Def > {
622
+ if ns == TypeNS {
601
623
PRIMITIVES . iter ( ) . find ( |x| x. 0 == path_str) . map ( |x| x. 1 )
624
+ } else {
625
+ None
602
626
}
603
627
}
604
628
0 commit comments