about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--compiler/rustc_codegen_ssa/src/back/archive.rs2
-rw-r--r--compiler/rustc_log/src/lib.rs2
-rw-r--r--compiler/rustc_parse_format/src/lib.rs4
-rw-r--r--src/bootstrap/bootstrap.py4
-rw-r--r--src/bootstrap/builder/tests.rs2
-rw-r--r--src/bootstrap/flags.rs10
-rw-r--r--src/bootstrap/format.rs18
-rw-r--r--src/bootstrap/test.rs4
-rw-r--r--src/librustdoc/clean/mod.rs8
-rw-r--r--src/librustdoc/html/markdown.rs6
-rw-r--r--src/librustdoc/html/render/print_item.rs27
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs29
-rw-r--r--src/librustdoc/visit_ast.rs2
-rw-r--r--src/tools/build_helper/src/git.rs72
-rw-r--r--src/tools/compiletest/Cargo.toml1
-rw-r--r--src/tools/compiletest/src/common.rs3
-rw-r--r--src/tools/compiletest/src/main.rs56
-rw-r--r--tests/rustdoc-ui/intra-doc/errors.rs16
-rw-r--r--tests/rustdoc-ui/intra-doc/errors.stderr14
-rw-r--r--tests/rustdoc/reexport-macro.rs23
21 files changed, 242 insertions, 62 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6beefb3870f..a130f49b0be 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -895,6 +895,7 @@ dependencies = [
 name = "compiletest"
 version = "0.0.0"
 dependencies = [
+ "build_helper",
  "colored",
  "diff",
  "getopts",
diff --git a/compiler/rustc_codegen_ssa/src/back/archive.rs b/compiler/rustc_codegen_ssa/src/back/archive.rs
index d3cd085cfb6..66ec8f5f57d 100644
--- a/compiler/rustc_codegen_ssa/src/back/archive.rs
+++ b/compiler/rustc_codegen_ssa/src/back/archive.rs
@@ -203,7 +203,7 @@ impl<'a> ArchiveBuilder<'a> for ArArchiveBuilder<'a> {
             }
         }
 
-        self.src_archives.push((archive_path.to_owned(), archive_map));
+        self.src_archives.push((archive_path, archive_map));
         Ok(())
     }
 
diff --git a/compiler/rustc_log/src/lib.rs b/compiler/rustc_log/src/lib.rs
index de26fd61e4d..22924efa948 100644
--- a/compiler/rustc_log/src/lib.rs
+++ b/compiler/rustc_log/src/lib.rs
@@ -92,7 +92,7 @@ pub fn init_env_logger(env: &str) -> Result<(), Error> {
             let fmt_layer = tracing_subscriber::fmt::layer()
                 .with_writer(io::stderr)
                 .without_time()
-                .event_format(BacktraceFormatter { backtrace_target: str.to_string() });
+                .event_format(BacktraceFormatter { backtrace_target: str });
             let subscriber = subscriber.with(fmt_layer);
             tracing::subscriber::set_global_default(subscriber).unwrap();
         }
diff --git a/compiler/rustc_parse_format/src/lib.rs b/compiler/rustc_parse_format/src/lib.rs
index 088a87ca571..34a4fd02ea6 100644
--- a/compiler/rustc_parse_format/src/lib.rs
+++ b/compiler/rustc_parse_format/src/lib.rs
@@ -847,9 +847,7 @@ impl<'a> Parser<'a> {
                 0,
                 ParseError {
                     description: "expected format parameter to occur after `:`".to_owned(),
-                    note: Some(
-                        format!("`?` comes after `:`, try `{}:{}` instead", word, "?").to_owned(),
-                    ),
+                    note: Some(format!("`?` comes after `:`, try `{}:{}` instead", word, "?")),
                     label: "expected `?` to occur after `:`".to_owned(),
                     span: pos.to(pos),
                     secondary_label: None,
diff --git a/src/bootstrap/bootstrap.py b/src/bootstrap/bootstrap.py
index 45f238ef4bf..013d1ab525b 100644
--- a/src/bootstrap/bootstrap.py
+++ b/src/bootstrap/bootstrap.py
@@ -784,6 +784,8 @@ class RustBuild(object):
         if self.get_toml("metrics", "build"):
             args.append("--features")
             args.append("build-metrics")
+        if self.json_output:
+            args.append("--message-format=json")
         if color == "always":
             args.append("--color=always")
         elif color == "never":
@@ -841,6 +843,7 @@ def parse_args():
     parser.add_argument('--build')
     parser.add_argument('--color', choices=['always', 'never', 'auto'])
     parser.add_argument('--clean', action='store_true')
+    parser.add_argument('--json-output', action='store_true')
     parser.add_argument('-v', '--verbose', action='count', default=0)
 
     return parser.parse_known_args(sys.argv)[0]
@@ -852,6 +855,7 @@ def bootstrap(args):
     build.rust_root = os.path.abspath(os.path.join(__file__, '../../..'))
     build.verbose = args.verbose != 0
     build.clean = args.clean
+    build.json_output = args.json_output
 
     # Read from `--config`, then `RUST_BOOTSTRAP_CONFIG`, then `./config.toml`,
     # then `config.toml` in the root directory.
diff --git a/src/bootstrap/builder/tests.rs b/src/bootstrap/builder/tests.rs
index d5fcd107502..3574f11189e 100644
--- a/src/bootstrap/builder/tests.rs
+++ b/src/bootstrap/builder/tests.rs
@@ -557,6 +557,7 @@ mod dist {
             rustfix_coverage: false,
             pass: None,
             run: None,
+            only_modified: false,
         };
 
         let build = Build::new(config);
@@ -627,6 +628,7 @@ mod dist {
             rustfix_coverage: false,
             pass: None,
             run: None,
+            only_modified: false,
         };
         // Make sure rustfmt binary not being found isn't an error.
         config.channel = "beta".to_string();
diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs
index 52c3dc0bf75..ff927ed561b 100644
--- a/src/bootstrap/flags.rs
+++ b/src/bootstrap/flags.rs
@@ -124,6 +124,7 @@ pub enum Subcommand {
         fail_fast: bool,
         doc_tests: DocTests,
         rustfix_coverage: bool,
+        only_modified: bool,
     },
     Bench {
         paths: Vec<PathBuf>,
@@ -301,6 +302,7 @@ To learn more about a subcommand, run `./x.py <subcommand> -h`",
                 opts.optflag("", "doc", "only run doc tests");
                 opts.optflag("", "bless", "update all stderr/stdout files of failing ui tests");
                 opts.optflag("", "force-rerun", "rerun tests even if the inputs are unchanged");
+                opts.optflag("", "only-modified", "only run tests that result has been changed");
                 opts.optopt(
                     "",
                     "compare-mode",
@@ -598,6 +600,7 @@ Arguments:
                 rustc_args: matches.opt_strs("rustc-args"),
                 fail_fast: !matches.opt_present("no-fail-fast"),
                 rustfix_coverage: matches.opt_present("rustfix-coverage"),
+                only_modified: matches.opt_present("only-modified"),
                 doc_tests: if matches.opt_present("doc") {
                     DocTests::Only
                 } else if matches.opt_present("no-doc") {
@@ -777,6 +780,13 @@ impl Subcommand {
         }
     }
 
+    pub fn only_modified(&self) -> bool {
+        match *self {
+            Subcommand::Test { only_modified, .. } => only_modified,
+            _ => false,
+        }
+    }
+
     pub fn force_rerun(&self) -> bool {
         match *self {
             Subcommand::Test { force_rerun, .. } => force_rerun,
diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs
index bfc57a85cdb..6c9c26faef6 100644
--- a/src/bootstrap/format.rs
+++ b/src/bootstrap/format.rs
@@ -1,8 +1,8 @@
 //! Runs rustfmt on the repository.
 
 use crate::builder::Builder;
-use crate::util::{output, output_result, program_out_of_date, t};
-use build_helper::git::updated_master_branch;
+use crate::util::{output, program_out_of_date, t};
+use build_helper::git::get_git_modified_files;
 use ignore::WalkBuilder;
 use std::collections::VecDeque;
 use std::path::{Path, PathBuf};
@@ -80,23 +80,11 @@ fn update_rustfmt_version(build: &Builder<'_>) {
 ///
 /// Returns `None` if all files should be formatted.
 fn get_modified_rs_files(build: &Builder<'_>) -> Result<Option<Vec<String>>, String> {
-    let Ok(updated_master) = updated_master_branch(Some(&build.config.src)) else { return Ok(None); };
-
     if !verify_rustfmt_version(build) {
         return Ok(None);
     }
 
-    let merge_base =
-        output_result(build.config.git().arg("merge-base").arg(&updated_master).arg("HEAD"))?;
-    Ok(Some(
-        output_result(
-            build.config.git().arg("diff-index").arg("--name-only").arg(merge_base.trim()),
-        )?
-        .lines()
-        .map(|s| s.trim().to_owned())
-        .filter(|f| Path::new(f).extension().map_or(false, |ext| ext == "rs"))
-        .collect(),
-    ))
+    get_git_modified_files(Some(&build.config.src), &vec!["rs"])
 }
 
 #[derive(serde::Deserialize)]
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index 8a0c532cfb0..f8835fe11a8 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -1510,6 +1510,10 @@ note: if you're sure you want to do this, please open an issue as to why. In the
         if builder.config.rust_optimize_tests {
             cmd.arg("--optimize-tests");
         }
+        if builder.config.cmd.only_modified() {
+            cmd.arg("--only-modified");
+        }
+
         let mut flags = if is_rustdoc { Vec::new() } else { vec!["-Crpath".to_string()] };
         flags.push(format!("-Cdebuginfo={}", builder.config.rust_debuginfo_level_tests));
         flags.extend(builder.config.cmd.rustc_args().iter().map(|s| s.to_string()));
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index d497f7ea679..42bdbddbce6 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -2209,10 +2209,12 @@ fn clean_maybe_renamed_item<'tcx>(
         };
 
         let mut extra_attrs = Vec::new();
-        if let Some(hir::Node::Item(use_node)) =
-            import_id.and_then(|def_id| cx.tcx.hir().find_by_def_id(def_id))
+        if let Some(import_id) = import_id &&
+            let Some(hir::Node::Item(use_node)) = cx.tcx.hir().find_by_def_id(import_id)
         {
-            // We get all the various imports' attributes.
+            // First, we add the attributes from the current import.
+            extra_attrs.extend_from_slice(inline::load_attrs(cx, import_id.to_def_id()));
+            // Then we get all the various imports' attributes.
             get_all_import_attributes(use_node, cx.tcx, item.owner_id.def_id, &mut extra_attrs);
         }
 
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index dee0a01a654..8f401a2fc1d 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -740,11 +740,7 @@ pub(crate) fn find_testable_code<T: doctest::Tester>(
             }
             Event::Text(ref s) if register_header.is_some() => {
                 let level = register_header.unwrap();
-                if s.is_empty() {
-                    tests.register_header("", level);
-                } else {
-                    tests.register_header(s, level);
-                }
+                tests.register_header(s, level);
                 register_header = None;
             }
             _ => {}
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 82a51a8467b..d0f497321ab 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -1081,10 +1081,10 @@ fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clea
     fn write_content(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Typedef) {
         wrap_item(w, |w| {
             render_attributes_in_pre(w, it, "");
-            write!(w, "{}", visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx));
             write!(
                 w,
-                "type {}{}{where_clause} = {type_};",
+                "{}type {}{}{where_clause} = {type_};",
+                visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx),
                 it.name.unwrap(),
                 t.generics.print(cx),
                 where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline),
@@ -1138,13 +1138,11 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean:
                      <a href=\"#{id}\" class=\"anchor field\">§</a>\
                      <code>{name}: {ty}</code>\
                  </span>",
-                id = id,
-                name = name,
                 shortty = ItemType::StructField,
                 ty = ty.print(cx),
             );
             if let Some(stability_class) = field.stability_class(cx.tcx()) {
-                write!(w, "<span class=\"stab {stab}\"></span>", stab = stability_class);
+                write!(w, "<span class=\"stab {stability_class}\"></span>");
             }
             document(w, cx, field, Some(it), HeadingOffset::H3);
         }
@@ -1242,7 +1240,6 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
                 w,
                 "<section id=\"{id}\" class=\"variant\">\
                     <a href=\"#{id}\" class=\"anchor\">§</a>",
-                id = id,
             );
             render_stability_since_raw_with_extra(
                 w,
@@ -1280,8 +1277,11 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
             if let Some((heading, fields)) = heading_and_fields {
                 let variant_id =
                     cx.derive_id(format!("{}.{}.fields", ItemType::Variant, variant.name.unwrap()));
-                write!(w, "<div class=\"sub-variant\" id=\"{id}\">", id = variant_id);
-                write!(w, "<h4>{heading}</h4>", heading = heading);
+                write!(
+                    w,
+                    "<div class=\"sub-variant\" id=\"{variant_id}\">\
+                        <h4>{heading}</h4>",
+                );
                 document_non_exhaustive(w, variant);
                 for field in fields {
                     match *field.kind {
@@ -1299,7 +1299,6 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
                                      <a href=\"#{id}\" class=\"anchor field\">§</a>\
                                      <code>{f}: {t}</code>\
                                  </span>",
-                                id = id,
                                 f = field.name.unwrap(),
                                 t = ty.print(cx)
                             );
@@ -1450,11 +1449,9 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
                     w,
                     "<span id=\"{id}\" class=\"{item_type} small-section-header\">\
                          <a href=\"#{id}\" class=\"anchor field\">§</a>\
-                         <code>{name}: {ty}</code>\
+                         <code>{field_name}: {ty}</code>\
                      </span>",
                     item_type = ItemType::StructField,
-                    id = id,
-                    name = field_name,
                     ty = ty.print(cx)
                 );
                 document(w, cx, field, Some(it), HeadingOffset::H3);
@@ -1840,8 +1837,8 @@ fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) {
         if layout.abi.is_unsized() {
             write!(w, "(unsized)");
         } else {
-            let bytes = layout.size.bytes() - tag_size;
-            write!(w, "{size} byte{pl}", size = bytes, pl = if bytes == 1 { "" } else { "s" },);
+            let size = layout.size.bytes() - tag_size;
+            write!(w, "{size} byte{pl}", pl = if size == 1 { "" } else { "s" },);
         }
     }
 
@@ -1896,7 +1893,7 @@ fn document_type_layout(w: &mut Buffer, cx: &Context<'_>, ty_def_id: DefId) {
 
                     for (index, layout) in variants.iter_enumerated() {
                         let name = adt.variant(index).name;
-                        write!(w, "<li><code>{name}</code>: ", name = name);
+                        write!(w, "<li><code>{name}</code>: ");
                         write_size_of_layout(w, layout, tag_size);
                         writeln!(w, "</li>");
                     }
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 692adcf0a80..62e190b4bd8 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -297,7 +297,8 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
         match ty_res {
             Res::Def(DefKind::Enum, did) => match tcx.type_of(did).kind() {
                 ty::Adt(def, _) if def.is_enum() => {
-                    if let Some(field) = def.all_fields().find(|f| f.name == variant_field_name) {
+                    if let Some(variant) = def.variants().iter().find(|v| v.name == variant_name)
+                        && let Some(field) = variant.fields.iter().find(|f| f.name == variant_field_name) {
                         Ok((ty_res, field.did))
                     } else {
                         Err(UnresolvedPath {
@@ -1716,15 +1717,35 @@ fn resolution_failure(
 
                     // Otherwise, it must be an associated item or variant
                     let res = partial_res.expect("None case was handled by `last_found_module`");
-                    let kind = match res {
-                        Res::Def(kind, _) => Some(kind),
+                    let kind_did = match res {
+                        Res::Def(kind, did) => Some((kind, did)),
                         Res::Primitive(_) => None,
                     };
-                    let path_description = if let Some(kind) = kind {
+                    let is_struct_variant = |did| {
+                        if let ty::Adt(def, _) = tcx.type_of(did).kind()
+                        && def.is_enum()
+                        && let Some(variant) = def.variants().iter().find(|v| v.name == res.name(tcx)) {
+                            // ctor is `None` if variant is a struct
+                            variant.ctor.is_none()
+                        } else {
+                            false
+                        }
+                    };
+                    let path_description = if let Some((kind, did)) = kind_did {
                         match kind {
                             Mod | ForeignMod => "inner item",
                             Struct => "field or associated item",
                             Enum | Union => "variant or associated item",
+                            Variant if is_struct_variant(did) => {
+                                let variant = res.name(tcx);
+                                let note = format!("variant `{variant}` has no such field");
+                                if let Some(span) = sp {
+                                    diag.span_label(span, &note);
+                                } else {
+                                    diag.note(&note);
+                                }
+                                return;
+                            }
                             Variant
                             | Field
                             | Closure
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index 088cb3f3394..9c1e5f4a3cd 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -378,7 +378,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
                 let nonexported = !tcx.has_attr(def_id, sym::macro_export);
 
                 if is_macro_2_0 || nonexported || self.inlining {
-                    self.add_to_current_mod(item, renamed, None);
+                    self.add_to_current_mod(item, renamed, import_id);
                 }
             }
             hir::ItemKind::Mod(ref m) => {
diff --git a/src/tools/build_helper/src/git.rs b/src/tools/build_helper/src/git.rs
index dc62051cb85..168633c8f63 100644
--- a/src/tools/build_helper/src/git.rs
+++ b/src/tools/build_helper/src/git.rs
@@ -1,5 +1,24 @@
+use std::process::Stdio;
 use std::{path::Path, process::Command};
 
+/// Runs a command and returns the output
+fn output_result(cmd: &mut Command) -> Result<String, String> {
+    let output = match cmd.stderr(Stdio::inherit()).output() {
+        Ok(status) => status,
+        Err(e) => return Err(format!("failed to run command: {:?}: {}", cmd, e)),
+    };
+    if !output.status.success() {
+        return Err(format!(
+            "command did not execute successfully: {:?}\n\
+             expected success, got: {}\n{}",
+            cmd,
+            output.status,
+            String::from_utf8(output.stderr).map_err(|err| format!("{err:?}"))?
+        ));
+    }
+    Ok(String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?)
+}
+
 /// Finds the remote for rust-lang/rust.
 /// For example for these remotes it will return `upstream`.
 /// ```text
@@ -14,13 +33,7 @@ pub fn get_rust_lang_rust_remote(git_dir: Option<&Path>) -> Result<String, Strin
         git.current_dir(git_dir);
     }
     git.args(["config", "--local", "--get-regex", "remote\\..*\\.url"]);
-
-    let output = git.output().map_err(|err| format!("{err:?}"))?;
-    if !output.status.success() {
-        return Err("failed to execute git config command".to_owned());
-    }
-
-    let stdout = String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?;
+    let stdout = output_result(&mut git)?;
 
     let rust_lang_remote = stdout
         .lines()
@@ -73,3 +86,48 @@ pub fn updated_master_branch(git_dir: Option<&Path>) -> Result<String, String> {
     // We could implement smarter logic here in the future.
     Ok("origin/master".into())
 }
+
+/// Returns the files that have been modified in the current branch compared to the master branch.
+/// The `extensions` parameter can be used to filter the files by their extension.
+/// If `extensions` is empty, all files will be returned.
+pub fn get_git_modified_files(
+    git_dir: Option<&Path>,
+    extensions: &Vec<&str>,
+) -> Result<Option<Vec<String>>, String> {
+    let Ok(updated_master) = updated_master_branch(git_dir) else { return Ok(None); };
+
+    let git = || {
+        let mut git = Command::new("git");
+        if let Some(git_dir) = git_dir {
+            git.current_dir(git_dir);
+        }
+        git
+    };
+
+    let merge_base = output_result(git().arg("merge-base").arg(&updated_master).arg("HEAD"))?;
+    let files = output_result(git().arg("diff-index").arg("--name-only").arg(merge_base.trim()))?
+        .lines()
+        .map(|s| s.trim().to_owned())
+        .filter(|f| {
+            Path::new(f).extension().map_or(false, |ext| {
+                extensions.is_empty() || extensions.contains(&ext.to_str().unwrap())
+            })
+        })
+        .collect();
+    Ok(Some(files))
+}
+
+/// Returns the files that haven't been added to git yet.
+pub fn get_git_untracked_files(git_dir: Option<&Path>) -> Result<Option<Vec<String>>, String> {
+    let Ok(_updated_master) = updated_master_branch(git_dir) else { return Ok(None); };
+    let mut git = Command::new("git");
+    if let Some(git_dir) = git_dir {
+        git.current_dir(git_dir);
+    }
+
+    let files = output_result(git.arg("ls-files").arg("--others").arg("--exclude-standard"))?
+        .lines()
+        .map(|s| s.trim().to_owned())
+        .collect();
+    Ok(Some(files))
+}
diff --git a/src/tools/compiletest/Cargo.toml b/src/tools/compiletest/Cargo.toml
index 1911f0f9c94..deed6fbd439 100644
--- a/src/tools/compiletest/Cargo.toml
+++ b/src/tools/compiletest/Cargo.toml
@@ -9,6 +9,7 @@ diff = "0.1.10"
 unified-diff = "0.2.1"
 getopts = "0.2"
 miropt-test-tools = { path = "../miropt-test-tools" }
+build_helper = { path = "../build_helper" }
 tracing = "0.1"
 tracing-subscriber = { version = "0.3.3", default-features = false, features = ["fmt", "env-filter", "smallvec", "parking_lot", "ansi"] }
 regex = "1.0"
diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs
index 3676f69b100..7fe2e6257d9 100644
--- a/src/tools/compiletest/src/common.rs
+++ b/src/tools/compiletest/src/common.rs
@@ -380,6 +380,9 @@ pub struct Config {
     /// Whether to rerun tests even if the inputs are unchanged.
     pub force_rerun: bool,
 
+    /// Only rerun the tests that result has been modified accoring to Git status
+    pub only_modified: bool,
+
     pub target_cfg: LazyCell<TargetCfg>,
 }
 
diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs
index 3092c656cd7..c648b2f12f1 100644
--- a/src/tools/compiletest/src/main.rs
+++ b/src/tools/compiletest/src/main.rs
@@ -8,15 +8,17 @@ extern crate test;
 use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS};
 use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, TestPaths};
 use crate::util::logv;
+use build_helper::git::{get_git_modified_files, get_git_untracked_files};
+use core::panic;
 use getopts::Options;
 use lazycell::LazyCell;
-use std::env;
 use std::ffi::OsString;
 use std::fs;
 use std::io::{self, ErrorKind};
 use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
 use std::time::SystemTime;
+use std::{env, vec};
 use test::ColorConfig;
 use tracing::*;
 use walkdir::WalkDir;
@@ -145,9 +147,10 @@ pub fn parse_config(args: Vec<String>) -> Config {
             "",
             "rustfix-coverage",
             "enable this to generate a Rustfix coverage file, which is saved in \
-                `./<build_base>/rustfix_missing_coverage.txt`",
+            `./<build_base>/rustfix_missing_coverage.txt`",
         )
         .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
+        .optflag("", "only-modified", "only run tests that result been modified")
         .optflag("h", "help", "show this message")
         .reqopt("", "channel", "current Rust channel", "CHANNEL")
         .optopt("", "edition", "default Rust edition", "EDITION");
@@ -279,6 +282,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
         lldb_python_dir: matches.opt_str("lldb-python-dir"),
         verbose: matches.opt_present("verbose"),
         quiet: matches.opt_present("quiet"),
+        only_modified: matches.opt_present("only-modified"),
         color,
         remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
         compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse),
@@ -521,8 +525,18 @@ pub fn test_opts(config: &Config) -> test::TestOpts {
 pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) {
     debug!("making tests from {:?}", config.src_base.display());
     let inputs = common_inputs_stamp(config);
-    collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests)
-        .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
+    let modified_tests = modified_tests(config, &config.src_base).unwrap_or_else(|err| {
+        panic!("modified_tests got error from dir: {}, error: {}", config.src_base.display(), err)
+    });
+    collect_tests_from_dir(
+        config,
+        &config.src_base,
+        &PathBuf::new(),
+        &inputs,
+        tests,
+        &modified_tests,
+    )
+    .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display()));
 }
 
 /// Returns a stamp constructed from input files common to all test cases.
@@ -561,12 +575,35 @@ fn common_inputs_stamp(config: &Config) -> Stamp {
     stamp
 }
 
+fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> {
+    if !config.only_modified {
+        return Ok(vec![]);
+    }
+    let files =
+        get_git_modified_files(Some(dir), &vec!["rs", "stderr", "fixed"])?.unwrap_or(vec![]);
+    // Add new test cases to the list, it will be convenient in daily development.
+    let untracked_files = get_git_untracked_files(None)?.unwrap_or(vec![]);
+
+    let all_paths = [&files[..], &untracked_files[..]].concat();
+    let full_paths = {
+        let mut full_paths: Vec<PathBuf> = all_paths
+            .into_iter()
+            .map(|f| fs::canonicalize(&f).unwrap().with_extension("").with_extension("rs"))
+            .collect();
+        full_paths.dedup();
+        full_paths.sort_unstable();
+        full_paths
+    };
+    Ok(full_paths)
+}
+
 fn collect_tests_from_dir(
     config: &Config,
     dir: &Path,
     relative_dir_path: &Path,
     inputs: &Stamp,
     tests: &mut Vec<test::TestDescAndFn>,
+    modified_tests: &Vec<PathBuf>,
 ) -> io::Result<()> {
     // Ignore directories that contain a file named `compiletest-ignore-dir`.
     if dir.join("compiletest-ignore-dir").exists() {
@@ -597,7 +634,7 @@ fn collect_tests_from_dir(
         let file = file?;
         let file_path = file.path();
         let file_name = file.file_name();
-        if is_test(&file_name) {
+        if is_test(&file_name) && (!config.only_modified || modified_tests.contains(&file_path)) {
             debug!("found test file: {:?}", file_path.display());
             let paths =
                 TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() };
@@ -607,7 +644,14 @@ fn collect_tests_from_dir(
             let relative_file_path = relative_dir_path.join(file.file_name());
             if &file_name != "auxiliary" {
                 debug!("found directory: {:?}", file_path.display());
-                collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?;
+                collect_tests_from_dir(
+                    config,
+                    &file_path,
+                    &relative_file_path,
+                    inputs,
+                    tests,
+                    modified_tests,
+                )?;
             }
         } else {
             debug!("found other file/directory: {:?}", file_path.display());
diff --git a/tests/rustdoc-ui/intra-doc/errors.rs b/tests/rustdoc-ui/intra-doc/errors.rs
index b29f7c29b5d..95dd2b98e03 100644
--- a/tests/rustdoc-ui/intra-doc/errors.rs
+++ b/tests/rustdoc-ui/intra-doc/errors.rs
@@ -103,3 +103,19 @@ pub trait T {
 macro_rules! m {
     () => {};
 }
+
+///[`TestEnum::Variant1::field_name`]
+//~^ ERROR unresolved link
+//~| NOTE variant `Variant1` has no such field
+pub enum TestEnum {
+    Variant1 {},
+    Variant2 { field_name: u64 },
+}
+
+///[`TestEnumNoFields::Variant1::field_name`]
+//~^ ERROR unresolved link
+//~| NOTE `Variant1` is a variant, not a module or type, and cannot have associated items
+pub enum TestEnumNoFields {
+    Variant1 (),
+    Variant2 {},
+}
diff --git a/tests/rustdoc-ui/intra-doc/errors.stderr b/tests/rustdoc-ui/intra-doc/errors.stderr
index 9a1896fb0cd..1b2416d7da7 100644
--- a/tests/rustdoc-ui/intra-doc/errors.stderr
+++ b/tests/rustdoc-ui/intra-doc/errors.stderr
@@ -142,6 +142,18 @@ error: unresolved link to `T::h`
 LL | /// [T::h!]
    |      ^^^^^ the trait `T` has no macro named `h`
 
+error: unresolved link to `TestEnum::Variant1::field_name`
+  --> $DIR/errors.rs:107:6
+   |
+LL | ///[`TestEnum::Variant1::field_name`]
+   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ variant `Variant1` has no such field
+
+error: unresolved link to `TestEnumNoFields::Variant1::field_name`
+  --> $DIR/errors.rs:115:6
+   |
+LL | ///[`TestEnumNoFields::Variant1::field_name`]
+   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Variant1` is a variant, not a module or type, and cannot have associated items
+
 error: unresolved link to `m`
   --> $DIR/errors.rs:98:6
    |
@@ -153,5 +165,5 @@ help: to link to the macro, add an exclamation mark
 LL | /// [m!()]
    |       +
 
-error: aborting due to 20 previous errors
+error: aborting due to 22 previous errors
 
diff --git a/tests/rustdoc/reexport-macro.rs b/tests/rustdoc/reexport-macro.rs
new file mode 100644
index 00000000000..c4dec703aed
--- /dev/null
+++ b/tests/rustdoc/reexport-macro.rs
@@ -0,0 +1,23 @@
+// Ensure that macros are correctly reexported and that they get both the comment from the
+// `pub use` and from the macro.
+
+#![crate_name = "foo"]
+
+// @has 'foo/macro.foo.html'
+// @!has - '//*[@class="toggle top-doc"]/*[@class="docblock"]' 'x y'
+// @has - '//*[@class="toggle top-doc"]/*[@class="docblock"]' 'y'
+#[macro_use]
+mod my_module {
+    /// y
+    #[macro_export]
+    macro_rules! foo {
+        () => ();
+    }
+}
+
+// @has 'foo/another_mod/macro.bar.html'
+// @has - '//*[@class="toggle top-doc"]/*[@class="docblock"]' 'x y'
+pub mod another_mod {
+    /// x
+    pub use crate::foo as bar;
+}