about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJoshua Nelson <jyn514@gmail.com>2020-08-31 23:28:38 -0400
committerJoshua Nelson <jyn514@gmail.com>2020-09-05 13:56:57 -0400
commitcd72d9029ff5b2368e5c539f9b326a2eea855127 (patch)
tree43695d1e5e3a3ae5f67c9608f64cc232810d893b
parent8318a185f301de617c64376fbd3a2d556296c8bb (diff)
downloadrust-cd72d9029ff5b2368e5c539f9b326a2eea855127.tar.gz
rust-cd72d9029ff5b2368e5c539f9b326a2eea855127.zip
Find the first segment that failed to resolve for _any_ namespace
Moves this detection into `resolution_failure` to avoid doing
unnecessary work and make the control flow a little easier to work with.
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs109
-rw-r--r--src/test/rustdoc-ui/intra-link-errors.rs8
-rw-r--r--src/test/rustdoc-ui/intra-link-errors.stderr44
3 files changed, 102 insertions, 59 deletions
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index df8ecf26a41..d8abf411de7 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -60,8 +60,8 @@ enum ResolutionFailure<'a> {
     /// This has a partial resolution, but is not in the TypeNS and so cannot
     /// have associated items or fields.
     CannotHaveAssociatedItems(Res, Namespace),
-    /// `String` is the base name of the path (not necessarily the whole link)
-    NotInScope(Cow<'a, str>),
+    /// `name` is the base name of the path (not necessarily the whole link)
+    NotInScope { module_id: DefId, name: Cow<'a, str> },
     /// this is a primitive type without an impls (no associated methods)
     /// when will this actually happen?
     /// the `Res` is the primitive it resolved to
@@ -92,7 +92,7 @@ impl ResolutionFailure<'a> {
             | NotAVariant(res, _)
             | WrongNamespace(res, _)
             | CannotHaveAssociatedItems(res, _) => Some(*res),
-            NotInScope(_) | NoParentItem | Dummy => None,
+            NotInScope { .. } | NoParentItem | Dummy => None,
         }
     }
 
@@ -142,7 +142,10 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
             .expect("fold_item should ensure link is non-empty");
         let variant_name =
             // we're not sure this is a variant at all, so use the full string
-            split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::Resolve(ResolutionFailure::NotInScope(path_str.into())))?;
+            split.next().map(|f| Symbol::intern(f)).ok_or(ErrorKind::Resolve(ResolutionFailure::NotInScope{
+                module_id,
+                name: path_str.into(),
+            }))?;
         let path = split
             .next()
             .map(|f| {
@@ -153,38 +156,21 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 }
                 f.to_owned()
             })
-            .ok_or(ErrorKind::Resolve(ResolutionFailure::NotInScope(
-                variant_name.to_string().into(),
-            )))?;
+            .ok_or(ErrorKind::Resolve(ResolutionFailure::NotInScope {
+                module_id,
+                name: variant_name.to_string().into(),
+            }))?;
         let ty_res = cx
             .enter_resolver(|resolver| {
                 resolver.resolve_str_path_error(DUMMY_SP, &path, TypeNS, module_id)
             })
             .map(|(_, res)| res)
             .unwrap_or(Res::Err);
-        // This code only gets hit if three path segments in a row don't get resolved.
-        // It's a good time to check if _any_ parent of the path gets resolved.
-        // If so, report it and say the first which failed; if not, say the first path segment didn't resolve.
         if let Res::Err = ty_res {
-            let mut current = path.as_str();
-            while let Some(parent) = current.rsplitn(2, "::").nth(1) {
-                current = parent;
-                if let Some(res) = self.check_full_res(
-                    TypeNS,
-                    &current,
-                    Some(module_id),
-                    current_item,
-                    extra_fragment,
-                ) {
-                    return Err(ErrorKind::Resolve(ResolutionFailure::NoAssocItem(
-                        res,
-                        Symbol::intern(&path),
-                    )));
-                }
-            }
-            return Err(ErrorKind::Resolve(ResolutionFailure::NotInScope(
-                current.to_string().into(),
-            )));
+            return Err(ErrorKind::Resolve(ResolutionFailure::NotInScope {
+                module_id,
+                name: path.into(),
+            }));
         }
         let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
         match ty_res {
@@ -301,7 +287,10 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                     });
                 }
             }
-            Err(ResolutionFailure::NotInScope(path_str.into()))
+            Err(ResolutionFailure::NotInScope {
+                module_id: parent_id.expect("already saw `Some` when resolving as a macro"),
+                name: path_str.into(),
+            })
         })
     }
     /// Resolves a string as a path within a particular namespace. Also returns an optional
@@ -384,7 +373,10 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                 // So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved.
                 .ok_or_else(|| {
                     debug!("found no `::`, assumming {} was correctly not in scope", item_name);
-                    ErrorKind::Resolve(ResolutionFailure::NotInScope(item_name.to_string().into()))
+                    ErrorKind::Resolve(ResolutionFailure::NotInScope {
+                        module_id,
+                        name: item_name.to_string().into(),
+                    })
                 })?;
 
             if let Some((path, prim)) = is_primitive(&path_root, TypeNS) {
@@ -451,7 +443,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
                                     }
                                 }
                             }
-                            ResolutionFailure::NotInScope(path_root.into())
+                            ResolutionFailure::NotInScope { module_id, name: path_root.into() }
                         });
                         Err(ErrorKind::Resolve(kind))
                     };
@@ -996,7 +988,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
                                     }
                                 }
                                 resolution_failure(
-                                    cx,
+                                    self,
                                     &item,
                                     path_str,
                                     disambiguator,
@@ -1076,7 +1068,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
                         if len == 0 {
                             drop(candidates_iter);
                             resolution_failure(
-                                cx,
+                                self,
                                 &item,
                                 path_str,
                                 disambiguator,
@@ -1096,8 +1088,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
                         } else {
                             drop(candidates_iter);
                             if is_derive_trait_collision(&candidates) {
-                                candidates.macro_ns =
-                                    Err(ResolutionFailure::NotInScope(path_str.into()));
+                                candidates.macro_ns = Err(ResolutionFailure::Dummy);
                             }
                             // If we're reporting an ambiguity, don't mention the namespaces that failed
                             let candidates =
@@ -1131,7 +1122,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
                                     }
                                 }
                                 resolution_failure(
-                                    cx,
+                                    self,
                                     &item,
                                     path_str,
                                     disambiguator,
@@ -1507,7 +1498,7 @@ fn report_diagnostic(
 }
 
 fn resolution_failure(
-    cx: &DocContext<'_>,
+    collector: &LinkCollector<'_, '_>,
     item: &Item,
     path_str: &str,
     disambiguator: Option<Disambiguator>,
@@ -1516,7 +1507,7 @@ fn resolution_failure(
     kinds: SmallVec<[ResolutionFailure<'_>; 3]>,
 ) {
     report_diagnostic(
-        cx,
+        collector.cx,
         &format!("unresolved link to `{}`", path_str),
         item,
         dox,
@@ -1524,11 +1515,15 @@ fn resolution_failure(
         |diag, sp| {
             let in_scope = kinds.iter().any(|kind| kind.res().is_some());
             let item = |res: Res| {
-                format!("the {} `{}`", res.descr(), cx.tcx.item_name(res.def_id()).to_string())
+                format!(
+                    "the {} `{}`",
+                    res.descr(),
+                    collector.cx.tcx.item_name(res.def_id()).to_string()
+                )
             };
             let assoc_item_not_allowed = |res: Res, diag: &mut DiagnosticBuilder<'_>| {
                 let def_id = res.def_id();
-                let name = cx.tcx.item_name(def_id);
+                let name = collector.cx.tcx.item_name(def_id);
                 let note = format!(
                     "`{}` is {} {}, not a module or type, and cannot have associated items",
                     name,
@@ -1539,18 +1534,42 @@ fn resolution_failure(
             };
             // ignore duplicates
             let mut variants_seen = SmallVec::<[_; 3]>::new();
-            for failure in kinds {
+            for mut failure in kinds {
+                // Check if _any_ parent of the path gets resolved.
+                // If so, report it and say the first which failed; if not, say the first path segment didn't resolve.
+                if let ResolutionFailure::NotInScope { module_id, name } = &mut failure {
+                    let mut current = name.as_ref();
+                    loop {
+                        current = match current.rsplitn(2, "::").nth(1) {
+                            Some(p) => p,
+                            None => {
+                                *name = current.to_owned().into();
+                                break;
+                            }
+                        };
+                        if let Some(res) = collector.check_full_res(
+                            TypeNS,
+                            &current,
+                            Some(*module_id),
+                            &None,
+                            &None,
+                        ) {
+                            failure = ResolutionFailure::NoAssocItem(res, Symbol::intern(current));
+                            break;
+                        }
+                    }
+                }
                 let variant = std::mem::discriminant(&failure);
                 if variants_seen.contains(&variant) {
                     continue;
                 }
                 variants_seen.push(variant);
                 match failure {
-                    ResolutionFailure::NotInScope(base) => {
+                    ResolutionFailure::NotInScope { name, .. } => {
                         if in_scope {
                             continue;
                         }
-                        diag.note(&format!("no item named `{}` is in scope", base));
+                        diag.note(&format!("no item named `{}` is in scope", name));
                         // If the link has `::` in the path, assume it's meant to be an intra-doc link
                         if !path_str.contains("::") {
                             // Otherwise, the `[]` might be unrelated.
@@ -1608,7 +1627,7 @@ fn resolution_failure(
                                 x,
                             ),
                         };
-                        let name = cx.tcx.item_name(def_id);
+                        let name = collector.cx.tcx.item_name(def_id);
                         let path_description = if let Some(disambiguator) = disambiguator {
                             disambiguator.descr()
                         } else {
diff --git a/src/test/rustdoc-ui/intra-link-errors.rs b/src/test/rustdoc-ui/intra-link-errors.rs
index ae89f418984..477ad6464f8 100644
--- a/src/test/rustdoc-ui/intra-link-errors.rs
+++ b/src/test/rustdoc-ui/intra-link-errors.rs
@@ -8,6 +8,14 @@
 //~^ ERROR unresolved link
 //~| NOTE no item named `path` is in scope
 
+/// [path::to::nonexistent::macro!]
+//~^ ERROR unresolved link
+//~| NOTE no item named `path` is in scope
+
+/// [type@path::to::nonexistent::type]
+//~^ ERROR unresolved link
+//~| NOTE no item named `path` is in scope
+
 /// [std::io::not::here]
 //~^ ERROR unresolved link
 //~| NOTE the module `io` has no inner item
diff --git a/src/test/rustdoc-ui/intra-link-errors.stderr b/src/test/rustdoc-ui/intra-link-errors.stderr
index 73e16bef0aa..3b1a09f913e 100644
--- a/src/test/rustdoc-ui/intra-link-errors.stderr
+++ b/src/test/rustdoc-ui/intra-link-errors.stderr
@@ -11,16 +11,32 @@ LL | #![deny(broken_intra_doc_links)]
    |         ^^^^^^^^^^^^^^^^^^^^^^
    = note: no item named `path` is in scope
 
-error: unresolved link to `std::io::not::here`
+error: unresolved link to `path::to::nonexistent::macro`
   --> $DIR/intra-link-errors.rs:11:6
    |
+LL | /// [path::to::nonexistent::macro!]
+   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: no item named `path` is in scope
+
+error: unresolved link to `path::to::nonexistent::type`
+  --> $DIR/intra-link-errors.rs:15:6
+   |
+LL | /// [type@path::to::nonexistent::type]
+   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: no item named `path` is in scope
+
+error: unresolved link to `std::io::not::here`
+  --> $DIR/intra-link-errors.rs:19:6
+   |
 LL | /// [std::io::not::here]
    |      ^^^^^^^^^^^^^^^^^^
    |
    = note: the module `io` has no inner item named `not`
 
 error: unresolved link to `std::io::Error::x`
-  --> $DIR/intra-link-errors.rs:15:6
+  --> $DIR/intra-link-errors.rs:23:6
    |
 LL | /// [std::io::Error::x]
    |      ^^^^^^^^^^^^^^^^^
@@ -28,7 +44,7 @@ LL | /// [std::io::Error::x]
    = note: the struct `Error` has no field or associated item named `x`
 
 error: unresolved link to `std::io::ErrorKind::x`
-  --> $DIR/intra-link-errors.rs:19:6
+  --> $DIR/intra-link-errors.rs:27:6
    |
 LL | /// [std::io::ErrorKind::x]
    |      ^^^^^^^^^^^^^^^^^^^^^
@@ -36,7 +52,7 @@ LL | /// [std::io::ErrorKind::x]
    = note: the enum `ErrorKind` has no variant or associated item named `x`
 
 error: unresolved link to `f::A`
-  --> $DIR/intra-link-errors.rs:23:6
+  --> $DIR/intra-link-errors.rs:31:6
    |
 LL | /// [f::A]
    |      ^^^^
@@ -44,7 +60,7 @@ LL | /// [f::A]
    = note: `f` is a function, not a module or type, and cannot have associated items
 
 error: unresolved link to `S::A`
-  --> $DIR/intra-link-errors.rs:27:6
+  --> $DIR/intra-link-errors.rs:35:6
    |
 LL | /// [S::A]
    |      ^^^^
@@ -52,7 +68,7 @@ LL | /// [S::A]
    = note: the struct `S` has no field or associated item named `A`
 
 error: unresolved link to `S::fmt`
-  --> $DIR/intra-link-errors.rs:31:6
+  --> $DIR/intra-link-errors.rs:39:6
    |
 LL | /// [S::fmt]
    |      ^^^^^^
@@ -60,7 +76,7 @@ LL | /// [S::fmt]
    = note: the struct `S` has no field or associated item named `fmt`
 
 error: unresolved link to `E::D`
-  --> $DIR/intra-link-errors.rs:35:6
+  --> $DIR/intra-link-errors.rs:43:6
    |
 LL | /// [E::D]
    |      ^^^^
@@ -68,7 +84,7 @@ LL | /// [E::D]
    = note: the enum `E` has no variant or associated item named `D`
 
 error: unresolved link to `u8::not_found`
-  --> $DIR/intra-link-errors.rs:39:6
+  --> $DIR/intra-link-errors.rs:47:6
    |
 LL | /// [u8::not_found]
    |      ^^^^^^^^^^^^^
@@ -76,7 +92,7 @@ LL | /// [u8::not_found]
    = note: the builtin type `u8` does not have an associated item named `not_found`
 
 error: unresolved link to `S`
-  --> $DIR/intra-link-errors.rs:43:6
+  --> $DIR/intra-link-errors.rs:51:6
    |
 LL | /// [S!]
    |      ^^ help: to link to the struct, prefix with `struct@`: `struct@S`
@@ -84,7 +100,7 @@ LL | /// [S!]
    = note: this link resolves to the struct `S`, which is not in the macro namespace
 
 error: unresolved link to `T::g`
-  --> $DIR/intra-link-errors.rs:61:6
+  --> $DIR/intra-link-errors.rs:69:6
    |
 LL | /// [type@T::g]
    |      ^^^^^^^^^ help: to link to the associated function, add parentheses: `T::g()`
@@ -92,7 +108,7 @@ LL | /// [type@T::g]
    = note: this link resolves to the associated function `g`, which is not in the type namespace
 
 error: unresolved link to `T::h`
-  --> $DIR/intra-link-errors.rs:66:6
+  --> $DIR/intra-link-errors.rs:74:6
    |
 LL | /// [T::h!]
    |      ^^^^^
@@ -100,7 +116,7 @@ LL | /// [T::h!]
    = note: the trait `T` has no macro named `h`
 
 error: unresolved link to `S::h`
-  --> $DIR/intra-link-errors.rs:53:6
+  --> $DIR/intra-link-errors.rs:61:6
    |
 LL | /// [type@S::h]
    |      ^^^^^^^^^ help: to link to the associated function, add parentheses: `S::h()`
@@ -108,12 +124,12 @@ LL | /// [type@S::h]
    = note: this link resolves to the associated function `h`, which is not in the type namespace
 
 error: unresolved link to `m`
-  --> $DIR/intra-link-errors.rs:73:6
+  --> $DIR/intra-link-errors.rs:81:6
    |
 LL | /// [m()]
    |      ^^^ help: to link to the macro, add an exclamation mark: `m!`
    |
    = note: this link resolves to the macro `m`, which is not in the value namespace
 
-error: aborting due to 14 previous errors
+error: aborting due to 16 previous errors