about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJoshua Nelson <jyn514@gmail.com>2020-06-20 17:01:03 -0400
committerJoshua Nelson <jyn514@gmail.com>2020-07-15 10:54:05 -0400
commit1b8accb7497e6fe66be331e40f8663d198a6b648 (patch)
tree90f57dafaad4deccde2a8e14b7eecd9437c97ed3
parentb3187aabd20637e0bb9a930b4b930a079b785ca9 (diff)
downloadrust-1b8accb7497e6fe66be331e40f8663d198a6b648.tar.gz
rust-1b8accb7497e6fe66be331e40f8663d198a6b648.zip
Add an option not to report resolution errors for rustdoc
- Remove unnecessary `should_loop` variable
- Report errors for trait implementations

These should give resolution errors because they are visible outside the
current scope. Without these errors, rustdoc will give ICEs:

```
thread 'rustc' panicked at 'attempted .def_id() on invalid res: Err', /home/joshua/src/rust/src/libstd/macros.rs:16:9
  15: rustc_hir::def::Res<Id>::def_id
             at /home/joshua/src/rust/src/librustc_hir/def.rs:382
  16: rustdoc::clean::utils::register_res
             at src/librustdoc/clean/utils.rs:627
  17: rustdoc::clean::utils::resolve_type
             at src/librustdoc/clean/utils.rs:587
```

- Add much more extensive tests
  + fn -> impl -> fn
  + fn -> impl -> fn -> macro
  + errors in function parameters
  + errors in trait bounds
  + errors in the type implementing the trait
  + unknown bounds for the type
  + unknown types in function bodies
  + errors generated by macros

- Use explicit state instead of trying to reconstruct it from random info
- Use an enum instead of a boolean
- Add example of ignored error
-rw-r--r--src/librustc_interface/passes.rs25
-rw-r--r--src/librustc_resolve/late.rs99
-rw-r--r--src/librustc_resolve/lib.rs3
-rw-r--r--src/test/rustdoc-ui/impl-fn-nesting.rs49
-rw-r--r--src/test/rustdoc-ui/impl-fn-nesting.stderr60
-rw-r--r--src/test/rustdoc/doc-cfg.rs4
6 files changed, 201 insertions, 39 deletions
diff --git a/src/librustc_interface/passes.rs b/src/librustc_interface/passes.rs
index b814283555b..690ed9decb9 100644
--- a/src/librustc_interface/passes.rs
+++ b/src/librustc_interface/passes.rs
@@ -233,6 +233,8 @@ fn configure_and_expand_inner<'a>(
     resolver_arenas: &'a ResolverArenas<'a>,
     metadata_loader: &'a MetadataLoaderDyn,
 ) -> Result<(ast::Crate, Resolver<'a>)> {
+    use rustc_resolve::IgnoreState;
+
     log::trace!("configure_and_expand_inner");
     pre_expansion_lint(sess, lint_store, &krate);
 
@@ -354,13 +356,18 @@ fn configure_and_expand_inner<'a>(
         )
     });
 
-    let crate_types = sess.crate_types();
-    let is_proc_macro_crate = crate_types.contains(&CrateType::ProcMacro);
+    if let Some(PpMode::PpmSource(PpSourceMode::PpmEveryBodyLoops)) = sess.opts.pretty {
+        log::debug!("replacing bodies with loop {{}}");
+        util::ReplaceBodyWithLoop::new(&mut resolver).visit_crate(&mut krate);
+    }
 
     let has_proc_macro_decls = sess.time("AST_validation", || {
         rustc_ast_passes::ast_validation::check_crate(sess, &krate, &mut resolver.lint_buffer())
     });
 
+    let crate_types = sess.crate_types();
+    let is_proc_macro_crate = crate_types.contains(&CrateType::ProcMacro);
+
     // For backwards compatibility, we don't try to run proc macro injection
     // if rustdoc is run on a proc macro crate without '--crate-type proc-macro' being
     // specified. This should only affect users who manually invoke 'rustdoc', as
@@ -407,17 +414,9 @@ fn configure_and_expand_inner<'a>(
     }
 
     // If we're actually rustdoc then avoid giving a name resolution error for `cfg()` items.
-    // anything, so switch everything to just looping
-    resolver.resolve_crate(&krate, sess.opts.actually_rustdoc);
-
-    let mut should_loop = false;
-    if let Some(PpMode::PpmSource(PpSourceMode::PpmEveryBodyLoops)) = sess.opts.pretty {
-        should_loop |= true;
-    }
-    if should_loop {
-        log::debug!("replacing bodies with loop {{}}");
-        util::ReplaceBodyWithLoop::new(&mut resolver).visit_crate(&mut krate);
-    }
+    let ignore_bodies =
+        if sess.opts.actually_rustdoc { IgnoreState::Ignore } else { IgnoreState::Report };
+    resolver.resolve_crate(&krate, ignore_bodies);
 
     // Needs to go *after* expansion to be able to check the results of macro expansion.
     sess.time("complete_gated_feature_checking", || {
diff --git a/src/librustc_resolve/late.rs b/src/librustc_resolve/late.rs
index ddce82494e1..637326bb88d 100644
--- a/src/librustc_resolve/late.rs
+++ b/src/librustc_resolve/late.rs
@@ -376,6 +376,19 @@ struct DiagnosticMetadata<'ast> {
     current_let_binding: Option<(Span, Option<Span>, Option<Span>)>,
 }
 
+/// Keeps track of whether errors should be reported.
+///
+/// Used by rustdoc to ignore errors in function bodies.
+/// This is just a fancy boolean so it can have doc-comments.
+#[derive(Copy, Clone, Debug)]
+pub enum IgnoreState {
+    /// We are at global scope or in a trait implementation, so all errors should be reported.
+    Report,
+    /// We are in a function body, so errors shouldn't be reported.
+    Ignore,
+    // Note that we don't need to worry about macros, which must always be resolved (or we wouldn't have gotten to the late pass).
+}
+
 struct LateResolutionVisitor<'a, 'b, 'ast> {
     r: &'b mut Resolver<'a>,
 
@@ -395,10 +408,12 @@ struct LateResolutionVisitor<'a, 'b, 'ast> {
     /// Fields used to add information to diagnostic errors.
     diagnostic_metadata: DiagnosticMetadata<'ast>,
 
-    /// Whether to report resolution errors for item bodies.
+    /// State used to know whether to ignore resolution errors for item bodies.
     ///
     /// In particular, rustdoc uses this to avoid giving errors for `cfg()` items.
-    ignore_bodies: bool,
+    /// In most cases this will be `None`, in which case errors will always be reported.
+    /// If it is `Some(_)`, then it will be updated when entering a nested function or trait body.
+    ignore_bodies: Option<IgnoreState>,
 }
 
 /// Walks the whole crate in DFS order, visiting each item, resolving names as it goes.
@@ -502,6 +517,10 @@ impl<'a, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
 
                 visit::walk_fn_ret_ty(this, &declaration.output);
 
+                let previous_ignore = this.ignore_bodies.take();
+                // Ignore errors in function bodies if originally passed `ignore_state: true`
+                // Be sure not to set this until the function signature has been resolved.
+                this.ignore_bodies = previous_ignore.and(Some(IgnoreState::Ignore));
                 // Resolve the function body, potentially inside the body of an async closure
                 match fn_kind {
                     FnKind::Fn(.., body) => walk_list!(this, visit_block, body),
@@ -509,6 +528,7 @@ impl<'a, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
                 };
 
                 debug!("(resolving function) leaving function");
+                this.ignore_bodies = previous_ignore;
             })
         });
         self.diagnostic_metadata.current_function = previous_value;
@@ -634,7 +654,7 @@ impl<'a, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
 impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
     fn new(
         resolver: &'b mut Resolver<'a>,
-        ignore_bodies: bool,
+        ignore_bodies: IgnoreState,
     ) -> LateResolutionVisitor<'a, 'b, 'ast> {
         // During late resolution we only track the module component of the parent scope,
         // although it may be useful to track other components as well for diagnostics.
@@ -652,7 +672,11 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
             label_ribs: Vec::new(),
             current_trait_ref: None,
             diagnostic_metadata: DiagnosticMetadata::default(),
-            ignore_bodies,
+            ignore_bodies: match ignore_bodies {
+                // errors at module scope should always be reported
+                IgnoreState::Ignore => Some(IgnoreState::Report),
+                IgnoreState::Report => None,
+            },
         }
     }
 
@@ -842,7 +866,11 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
             };
             let report_error = |this: &Self, ns| {
                 let what = if ns == TypeNS { "type parameters" } else { "local variables" };
-                this.r.session.span_err(ident.span, &format!("imports cannot refer to {}", what));
+                if this.should_report_errs() {
+                    this.r
+                        .session
+                        .span_err(ident.span, &format!("imports cannot refer to {}", what));
+                }
             };
 
             for &ns in nss {
@@ -1166,6 +1194,9 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
         impl_items: &'ast [P<AssocItem>],
     ) {
         debug!("resolve_implementation");
+        let old_ignore = self.ignore_bodies.take();
+        // Never ignore errors in trait implementations.
+        self.ignore_bodies = old_ignore.and(Some(IgnoreState::Report));
         // If applicable, create a rib for the type parameters.
         self.with_generic_param_rib(generics, ItemRibKind(HasGenericParams::Yes), |this| {
             // Dummy self type for better errors if `Self` is used in the trait path.
@@ -1261,6 +1292,7 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                 });
             });
         });
+        self.ignore_bodies = old_ignore;
     }
 
     fn check_trait_item<F>(&mut self, ident: Ident, ns: Namespace, span: Span, err: F)
@@ -1298,6 +1330,7 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
     }
 
     fn resolve_local(&mut self, local: &'ast Local) {
+        debug!("resolving local ({:?})", local);
         // Resolve the type.
         walk_list!(self, visit_ty, &local.ty);
 
@@ -1686,18 +1719,27 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
         source: PathSource<'ast>,
         crate_lint: CrateLint,
     ) -> PartialRes {
+        log::debug!("smart_resolve_path_fragment(id={:?},qself={:?},path={:?}", id, qself, path);
         let ns = source.namespace();
         let is_expected = &|res| source.is_expected(res);
 
         let report_errors = |this: &mut Self, res: Option<Res>| {
-            let (err, candidates) = this.smart_resolve_report_errors(path, span, source, res);
-
-            let def_id = this.parent_scope.module.normal_ancestor_id;
-            let instead = res.is_some();
-            let suggestion =
-                if res.is_none() { this.report_missing_type_error(path) } else { None };
-
-            this.r.use_injections.push(UseError { err, candidates, def_id, instead, suggestion });
+            if this.should_report_errs() {
+                let (err, candidates) = this.smart_resolve_report_errors(path, span, source, res);
+
+                let def_id = this.parent_scope.module.normal_ancestor_id;
+                let instead = res.is_some();
+                let suggestion =
+                    if res.is_none() { this.report_missing_type_error(path) } else { None };
+
+                this.r.use_injections.push(UseError {
+                    err,
+                    candidates,
+                    def_id,
+                    instead,
+                    suggestion,
+                });
+            }
 
             PartialRes::new(Res::Err)
         };
@@ -1755,13 +1797,17 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
 
             let def_id = this.parent_scope.module.normal_ancestor_id;
 
-            this.r.use_injections.push(UseError {
-                err,
-                candidates,
-                def_id,
-                instead: false,
-                suggestion: None,
-            });
+            if this.should_report_errs() {
+                this.r.use_injections.push(UseError {
+                    err,
+                    candidates,
+                    def_id,
+                    instead: false,
+                    suggestion: None,
+                });
+            } else {
+                err.cancel();
+            }
 
             // We don't return `Some(parent_err)` here, because the error will
             // be already printed as part of the `use` injections
@@ -1856,11 +1902,20 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
     ///
     /// This doesn't emit errors for function bodies if `ignore_bodies` is set.
     fn report_error(&self, span: Span, resolution_error: ResolutionError<'_>) {
-        if !self.ignore_bodies || self.diagnostic_metadata.current_function.is_none() {
+        if self.should_report_errs() {
             self.r.report_error(span, resolution_error);
         }
     }
 
+    #[inline]
+    fn should_report_errs(&self) -> bool {
+        debug!("should_report_errs(state={:?})", self.ignore_bodies);
+        match self.ignore_bodies {
+            None | Some(IgnoreState::Report) => true,
+            Some(IgnoreState::Ignore) => false,
+        }
+    }
+
     // Resolve in alternative namespaces if resolution in the primary namespace fails.
     fn resolve_qpath_anywhere(
         &mut self,
@@ -2357,7 +2412,7 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
 }
 
 impl<'a> Resolver<'a> {
-    pub(crate) fn late_resolve_crate(&mut self, krate: &Crate, ignore_bodies: bool) {
+    pub(crate) fn late_resolve_crate(&mut self, krate: &Crate, ignore_bodies: IgnoreState) {
         let mut late_resolution_visitor = LateResolutionVisitor::new(self, ignore_bodies);
         visit::walk_crate(&mut late_resolution_visitor, krate);
         for (id, span) in late_resolution_visitor.diagnostic_metadata.unused_labels.iter() {
diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs
index 786dc28ba0e..23bd0028bd1 100644
--- a/src/librustc_resolve/lib.rs
+++ b/src/librustc_resolve/lib.rs
@@ -15,6 +15,7 @@
 #![feature(or_patterns)]
 #![recursion_limit = "256"]
 
+pub use late::IgnoreState;
 pub use rustc_hir::def::{Namespace, PerNS};
 
 use Determinacy::*;
@@ -1441,7 +1442,7 @@ impl<'a> Resolver<'a> {
     }
 
     /// Entry point to crate resolution.
-    pub fn resolve_crate(&mut self, krate: &Crate, ignore_bodies: bool) {
+    pub fn resolve_crate(&mut self, krate: &Crate, ignore_bodies: IgnoreState) {
         let _prof_timer = self.session.prof.generic_activity("resolve_crate");
 
         ImportResolver { r: self }.finalize_imports();
diff --git a/src/test/rustdoc-ui/impl-fn-nesting.rs b/src/test/rustdoc-ui/impl-fn-nesting.rs
new file mode 100644
index 00000000000..d2dd8d835fd
--- /dev/null
+++ b/src/test/rustdoc-ui/impl-fn-nesting.rs
@@ -0,0 +1,49 @@
+// Ensure that rustdoc gives errors for trait impls inside function bodies that don't resolve.
+// See https://github.com/rust-lang/rust/pull/73566
+pub struct ValidType;
+pub trait ValidTrait {}
+pub trait NeedsBody {
+    type Item;
+    fn f();
+}
+
+/// This function has docs
+pub fn f<B: UnknownBound>(a: UnknownType, b: B) {
+//~^ ERROR cannot find trait `UnknownBound` in this scope
+//~| ERROR cannot find type `UnknownType` in this scope
+    impl UnknownTrait for ValidType {} //~ ERROR cannot find trait `UnknownTrait`
+    impl<T: UnknownBound> UnknownTrait for T {}
+    //~^ ERROR cannot find trait `UnknownBound` in this scope
+    //~| ERROR cannot find trait `UnknownTrait` in this scope
+    impl ValidTrait for UnknownType {}
+    //~^ ERROR cannot find type `UnknownType` in this scope
+    impl ValidTrait for ValidType where ValidTrait: UnknownBound {}
+    //~^ ERROR cannot find trait `UnknownBound` in this scope
+
+    /// This impl has documentation
+    impl NeedsBody for ValidType {
+        type Item = UnknownType;
+        //~^ ERROR cannot find type `UnknownType` in this scope
+
+        /// This function has documentation
+        fn f() {
+            <UnknownTypeShouldBeIgnored>::a();
+            content::shouldnt::matter();
+            unknown_macro!();
+            //~^ ERROR cannot find macro `unknown_macro` in this scope
+
+            /// This is documentation for a macro
+            macro_rules! can_define_macros_here_too {
+                () => {
+                    this::content::should::also::be::ignored()
+                }
+            }
+            can_define_macros_here_too!();
+
+            /// This also is documented.
+            pub fn doubly_nested(c: UnknownTypeShouldBeIgnored) {
+
+            }
+        }
+    }
+}
diff --git a/src/test/rustdoc-ui/impl-fn-nesting.stderr b/src/test/rustdoc-ui/impl-fn-nesting.stderr
new file mode 100644
index 00000000000..f8629964c07
--- /dev/null
+++ b/src/test/rustdoc-ui/impl-fn-nesting.stderr
@@ -0,0 +1,60 @@
+error: cannot find macro `unknown_macro` in this scope
+  --> $DIR/impl-fn-nesting.rs:32:13
+   |
+LL |             unknown_macro!();
+   |             ^^^^^^^^^^^^^
+
+error[E0405]: cannot find trait `UnknownBound` in this scope
+  --> $DIR/impl-fn-nesting.rs:11:13
+   |
+LL | pub fn f<B: UnknownBound>(a: UnknownType, b: B) {
+   |             ^^^^^^^^^^^^ not found in this scope
+
+error[E0412]: cannot find type `UnknownType` in this scope
+  --> $DIR/impl-fn-nesting.rs:11:30
+   |
+LL | pub fn f<B: UnknownBound>(a: UnknownType, b: B) {
+   |                              ^^^^^^^^^^^ not found in this scope
+
+error[E0405]: cannot find trait `UnknownTrait` in this scope
+  --> $DIR/impl-fn-nesting.rs:14:10
+   |
+LL |     impl UnknownTrait for ValidType {}
+   |          ^^^^^^^^^^^^ not found in this scope
+
+error[E0405]: cannot find trait `UnknownTrait` in this scope
+  --> $DIR/impl-fn-nesting.rs:15:27
+   |
+LL |     impl<T: UnknownBound> UnknownTrait for T {}
+   |                           ^^^^^^^^^^^^ not found in this scope
+
+error[E0405]: cannot find trait `UnknownBound` in this scope
+  --> $DIR/impl-fn-nesting.rs:15:13
+   |
+LL |     impl<T: UnknownBound> UnknownTrait for T {}
+   |             ^^^^^^^^^^^^ not found in this scope
+
+error[E0412]: cannot find type `UnknownType` in this scope
+  --> $DIR/impl-fn-nesting.rs:18:25
+   |
+LL |     impl ValidTrait for UnknownType {}
+   |                         ^^^^^^^^^^^ not found in this scope
+
+error[E0405]: cannot find trait `UnknownBound` in this scope
+  --> $DIR/impl-fn-nesting.rs:20:53
+   |
+LL |     impl ValidTrait for ValidType where ValidTrait: UnknownBound {}
+   |                                                     ^^^^^^^^^^^^ not found in this scope
+
+error[E0412]: cannot find type `UnknownType` in this scope
+  --> $DIR/impl-fn-nesting.rs:25:21
+   |
+LL |         type Item = UnknownType;
+   |                     ^^^^^^^^^^^ not found in this scope
+
+error: Compilation failed, aborting rustdoc
+
+error: aborting due to 10 previous errors
+
+Some errors have detailed explanations: E0405, E0412.
+For more information about an error, try `rustc --explain E0405`.
diff --git a/src/test/rustdoc/doc-cfg.rs b/src/test/rustdoc/doc-cfg.rs
index 8664930bc94..aa407b7e926 100644
--- a/src/test/rustdoc/doc-cfg.rs
+++ b/src/test/rustdoc/doc-cfg.rs
@@ -57,7 +57,5 @@ pub unsafe fn uses_target_feature() {
 //        'This is supported with target feature avx only.'
 #[doc(cfg(target_feature = "avx"))]
 pub fn uses_cfg_target_feature() {
-    unsafe {
-        uses_target_feature();
-    }
+    uses_target_feature();
 }