Rollup merge of #149252 - madsmtm:miri-jemalloc, r=RalfJung

miri: use `tikv-jemalloc-sys` from sysroot

This allows LTO to work when compiling jemalloc (which is currently broken due to https://github.com/rust-lang/cc-rs/issues/1613), which should yield a small performance boost, and makes Miri's behaviour here match Clippy
and Rustdoc.

Follow-up to https://github.com/rust-lang/rust/pull/148925 / https://github.com/rust-lang/rust/pull/146627 after discussion in https://github.com/rust-lang/rust/pull/148925#pullrequestreview-3465393783.

r? RalfJung
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index 876297d..3092c6e 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -589,13 +589,13 @@ fn for_each_relevant_impl(
             | ty::Never
             | ty::Tuple(_)
             | ty::UnsafeBinder(_) => {
-                let simp = ty::fast_reject::simplify_type(
+                if let Some(simp) = ty::fast_reject::simplify_type(
                     tcx,
                     self_ty,
                     ty::fast_reject::TreatParams::AsRigid,
-                )
-                .unwrap();
-                consider_impls_for_simplified_type(simp);
+                ) {
+                    consider_impls_for_simplified_type(simp);
+                }
             }
 
             // HACK: For integer and float variables we have to manually look at all impls
diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl
index c74333f..7055e60 100644
--- a/compiler/rustc_parse/messages.ftl
+++ b/compiler/rustc_parse/messages.ftl
@@ -732,8 +732,6 @@
 
 parse_or_pattern_not_allowed_in_fn_parameters = function parameters require top-level or-patterns in parentheses
 parse_or_pattern_not_allowed_in_let_binding = `let` bindings require top-level or-patterns in parentheses
-parse_out_of_range_hex_escape = out of range hex escape
-    .label = must be a character in the range [\x00-\x7f]
 
 parse_outer_attr_explanation = outer attributes, like `#[test]`, annotate the item following them
 
diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs
index bde179c..62a333f 100644
--- a/compiler/rustc_parse/src/errors.rs
+++ b/compiler/rustc_parse/src/errors.rs
@@ -2455,12 +2455,6 @@ pub(crate) enum UnescapeError {
         is_hex: bool,
         ch: String,
     },
-    #[diag(parse_out_of_range_hex_escape)]
-    OutOfRangeHexEscape(
-        #[primary_span]
-        #[label]
-        Span,
-    ),
     #[diag(parse_leading_underscore_unicode_escape)]
     LeadingUnderscoreUnicodeEscape {
         #[primary_span]
diff --git a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
index ec59a1a..895374a 100644
--- a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
+++ b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs
@@ -226,7 +226,24 @@ pub(crate) fn emit_unescape_error(
             err.emit()
         }
         EscapeError::OutOfRangeHexEscape => {
-            dcx.emit_err(UnescapeError::OutOfRangeHexEscape(err_span))
+            let mut err = dcx.struct_span_err(err_span, "out of range hex escape");
+            err.span_label(err_span, "must be a character in the range [\\x00-\\x7f]");
+
+            let escape_str = &lit[range];
+            if lit.len() <= 4
+                && escape_str.len() == 4
+                && escape_str.starts_with("\\x")
+                && let Ok(value) = u8::from_str_radix(&escape_str[2..4], 16)
+                && matches!(mode, Mode::Char | Mode::Str)
+            {
+                err.help(format!("if you want to write a byte literal, use `b'{}'`", escape_str));
+                err.help(format!(
+                    "if you want to write a Unicode character, use `'\\u{{{:X}}}'`",
+                    value
+                ));
+            }
+
+            err.emit()
         }
         EscapeError::LeadingUnderscoreUnicodeEscape => {
             let (c, span) = last_char();
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index b33ccba..44177d4 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -126,6 +126,14 @@
 passes_doc_alias_start_end =
     {$attr_str} cannot start or end with ' '
 
+passes_doc_attr_expects_no_value =
+    `doc({$attr_name})` does not accept a value
+    .suggestion = use `doc({$attr_name})`
+
+passes_doc_attr_expects_string =
+    `doc({$attr_name})` expects a string value
+    .suggestion = use `doc({$attr_name} = "...")`
+
 passes_doc_attr_not_crate_level =
     `#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute
 
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 30bb7d6..58e5521 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -1123,6 +1123,28 @@ fn check_attr_crate_level(
         true
     }
 
+    fn check_doc_attr_string_value(&self, meta: &MetaItemInner, hir_id: HirId) {
+        if meta.value_str().is_none() {
+            self.tcx.emit_node_span_lint(
+                INVALID_DOC_ATTRIBUTES,
+                hir_id,
+                meta.span(),
+                errors::DocAttrExpectsString { attr_name: meta.name().unwrap() },
+            );
+        }
+    }
+
+    fn check_doc_attr_no_value(&self, meta: &MetaItemInner, hir_id: HirId) {
+        if !meta.is_word() {
+            self.tcx.emit_node_span_lint(
+                INVALID_DOC_ATTRIBUTES,
+                hir_id,
+                meta.span(),
+                errors::DocAttrExpectsNoValue { attr_name: meta.name().unwrap() },
+            );
+        }
+    }
+
     /// Checks that `doc(test(...))` attribute contains only valid attributes and are at the right place.
     fn check_test_attr(
         &self,
@@ -1293,10 +1315,15 @@ fn check_doc_attrs(
                             | sym::html_logo_url
                             | sym::html_playground_url
                             | sym::issue_tracker_base_url
-                            | sym::html_root_url
-                            | sym::html_no_source,
+                            | sym::html_root_url,
                         ) => {
                             self.check_attr_crate_level(attr_span, style, meta, hir_id);
+                            self.check_doc_attr_string_value(meta, hir_id);
+                        }
+
+                        Some(sym::html_no_source) => {
+                            self.check_attr_crate_level(attr_span, style, meta, hir_id);
+                            self.check_doc_attr_no_value(meta, hir_id);
                         }
 
                         Some(sym::auto_cfg) => {
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index ed4cf77..e4826cc 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -24,6 +24,18 @@
 #[diag(passes_incorrect_do_not_recommend_args)]
 pub(crate) struct DoNotRecommendDoesNotExpectArgs;
 
+#[derive(LintDiagnostic)]
+#[diag(passes_doc_attr_expects_string)]
+pub(crate) struct DocAttrExpectsString {
+    pub(crate) attr_name: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(passes_doc_attr_expects_no_value)]
+pub(crate) struct DocAttrExpectsNoValue {
+    pub(crate) attr_name: Symbol,
+}
+
 #[derive(Diagnostic)]
 #[diag(passes_autodiff_attr)]
 pub(crate) struct AutoDiffAttr {
diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs
index 13d38d3..6f587df 100644
--- a/library/alloc/src/vec/mod.rs
+++ b/library/alloc/src/vec/mod.rs
@@ -433,6 +433,8 @@
 #[stable(feature = "rust1", since = "1.0.0")]
 #[rustc_diagnostic_item = "Vec"]
 #[rustc_insignificant_dtor]
+#[doc(alias = "list")]
+#[doc(alias = "vector")]
 pub struct Vec<T, #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global> {
     buf: RawVec<T, A>,
     len: usize,
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index a26ae7d..08465ea 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -197,6 +197,37 @@
 the flag in question to Rustdoc on the command-line. To do this from Cargo, you can either use the
 `RUSTDOCFLAGS` environment variable or the `cargo rustdoc` command.
 
+### `--merge`, `--parts-out-dir`, and `--include-parts-dir`
+
+These options control how rustdoc handles files that combine data from multiple crates.
+
+By default, they act like `--merge=shared` is set, and `--parts-out-dir` and `--include-parts-dir`
+are turned off. The `--merge=shared` mode causes rustdoc to load the existing data in the out-dir,
+combine the new crate data into it, and write the result. This is very easy to use in scripts that
+manually invoke rustdoc, but it's also slow, because it performs O(crates) work on
+every crate, meaning it performs O(crates<sup>2</sup>) work.
+
+```console
+$ rustdoc crate1.rs --out-dir=doc
+$ cat doc/search.index/crateNames/*
+rd_("fcrate1")
+$ rustdoc crate2.rs --out-dir=doc
+$ cat doc/search.index/crateNames/*
+rd_("fcrate1fcrate2")
+```
+
+To delay shared-data merging until the end of a build, so that you only have to perform O(crates)
+work, use `--merge=none` on every crate except the last one, which will use `--merge=finalize`.
+
+```console
+$ rustdoc +nightly crate1.rs --merge=none --parts-out-dir=crate1.d -Zunstable-options
+$ cat doc/search.index/crateNames/*
+cat: 'doc/search.index/crateNames/*': No such file or directory
+$ rustdoc +nightly crate2.rs --merge=finalize --include-parts-dir=crate1.d -Zunstable-options
+$ cat doc/search.index/crateNames/*
+rd_("fcrate1fcrate2")
+```
+
 ### `--document-hidden-items`: Show items that are `#[doc(hidden)]`
 <span id="document-hidden-items"></span>
 
diff --git a/src/etc/lldb_commands b/src/etc/lldb_commands
index 508296c..eff065d 100644
--- a/src/etc/lldb_commands
+++ b/src/etc/lldb_commands
@@ -1,6 +1,5 @@
 # Forces test-compliant formatting to all other types
 type synthetic add -l lldb_lookup.synthetic_lookup -x ".*" --category Rust
-type summary add -F _ -e -x -h "^.*$" --category Rust
 # Std String
 type synthetic add -l lldb_lookup.StdStringSyntheticProvider -x "^(alloc::([a-z_]+::)+)String$" --category Rust
 type summary add -F lldb_lookup.StdStringSummaryProvider  -e -x -h "^(alloc::([a-z_]+::)+)String$" --category Rust
@@ -66,6 +65,7 @@
 type synthetic add -l lldb_lookup.synthetic_lookup -x "^&(mut )?(std::([a-z_]+::)+)Path$" --category Rust
 type summary add -F lldb_lookup.summary_lookup  -e -x -h "^&(mut )?(std::([a-z_]+::)+)Path$" --category Rust
 # Enum
+# type summary add -F lldb_lookup.ClangEncodedEnumSummaryProvider -e -h "lldb_lookup.is_sum_type_enum" --recognizer-function --category Rust
 ## MSVC
 type synthetic add -l lldb_lookup.MSVCEnumSyntheticProvider -x "^enum2\$<.+>$" --category Rust
 type summary add -F lldb_lookup.MSVCEnumSummaryProvider -e -x -h "^enum2\$<.+>$" --category Rust
@@ -74,6 +74,7 @@
 type summary add -F lldb_lookup.summary_lookup  -e -x -h "^enum2\$<.+>::.*$" --category Rust
 # Tuple
 type synthetic add -l lldb_lookup.synthetic_lookup -x "^\(.*\)$" --category Rust
+type summary add -F lldb_lookup.TupleSummaryProvider -e -x -h "^\(.*\)$" --category Rust
 ## MSVC
 type synthetic add -l lldb_lookup.MSVCTupleSyntheticProvider -x "^tuple\$<.+>$" --category Rust
 type summary add -F lldb_lookup.TupleSummaryProvider -e -x -h "^tuple\$<.+>$" --category Rust
diff --git a/src/etc/lldb_lookup.py b/src/etc/lldb_lookup.py
index f43d2c6..2b90d40 100644
--- a/src/etc/lldb_lookup.py
+++ b/src/etc/lldb_lookup.py
@@ -10,9 +10,6 @@
 
 
 def classify_rust_type(type: lldb.SBType) -> str:
-    if type.IsPointerType():
-        type = type.GetPointeeType()
-
     type_class = type.GetTypeClass()
     if type_class == lldb.eTypeClassStruct:
         return classify_struct(type.name, type.fields)
@@ -88,6 +85,26 @@
     if rust_type == RustType.SINGLETON_ENUM:
         return synthetic_lookup(valobj.GetChildAtIndex(0), _dict)
     if rust_type == RustType.ENUM:
+        # this little trick lets us treat `synthetic_lookup` as a "recognizer function" for the enum
+        # summary providers, reducing the number of lookups we have to do. This is a huge time save
+        # because there's no way (via type name) to recognize sum-type enums on `*-gnu` targets. The
+        # alternative would be to shove every single type through `summary_lookup`, which is
+        # incredibly wasteful. Once these scripts are updated for LLDB 19.0 and we can use
+        # `--recognizer-function`, this hack will only be needed for backwards compatibility.
+        summary: lldb.SBTypeSummary = valobj.GetTypeSummary()
+        if (
+            summary.summary_data is None
+            or summary.summary_data.strip()
+            != "lldb_lookup.ClangEncodedEnumSummaryProvider(valobj,internal_dict)"
+        ):
+            rust_category: lldb.SBTypeCategory = lldb.debugger.GetCategory("Rust")
+            rust_category.AddTypeSummary(
+                lldb.SBTypeNameSpecifier(valobj.GetTypeName()),
+                lldb.SBTypeSummary().CreateWithFunctionName(
+                    "lldb_lookup.ClangEncodedEnumSummaryProvider"
+                ),
+            )
+
         return ClangEncodedEnumProvider(valobj, _dict)
     if rust_type == RustType.STD_VEC:
         return StdVecSyntheticProvider(valobj, _dict)
diff --git a/src/etc/lldb_providers.py b/src/etc/lldb_providers.py
index 13c66ee..5824716 100644
--- a/src/etc/lldb_providers.py
+++ b/src/etc/lldb_providers.py
@@ -1,7 +1,6 @@
 from __future__ import annotations
-import re
 import sys
-from typing import List, TYPE_CHECKING, Generator
+from typing import Generator, List, TYPE_CHECKING
 
 from lldb import (
     SBData,
@@ -12,6 +11,8 @@
     eFormatChar,
 )
 
+from rust_types import is_tuple_fields
+
 if TYPE_CHECKING:
     from lldb import SBValue, SBType, SBTypeStaticField, SBTarget
 
@@ -206,6 +207,34 @@
     return result
 
 
+def StructSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str:
+    # structs need the field name before the field value
+    output = (
+        f"{valobj.GetChildAtIndex(i).GetName()}:{child}"
+        for i, child in enumerate(aggregate_field_summary(valobj, _dict))
+    )
+
+    return "{" + ", ".join(output) + "}"
+
+
+def TupleSummaryProvider(valobj: SBValue, _dict: LLDBOpaque):
+    return "(" + ", ".join(aggregate_field_summary(valobj, _dict)) + ")"
+
+
+def aggregate_field_summary(valobj: SBValue, _dict) -> Generator[str, None, None]:
+    for i in range(0, valobj.GetNumChildren()):
+        child: SBValue = valobj.GetChildAtIndex(i)
+        summary = child.summary
+        if summary is None:
+            summary = child.value
+            if summary is None:
+                if is_tuple_fields(child):
+                    summary = TupleSummaryProvider(child, _dict)
+                else:
+                    summary = StructSummaryProvider(child, _dict)
+        yield summary
+
+
 def SizeSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str:
     return "size=" + str(valobj.GetNumChildren())
 
@@ -454,49 +483,55 @@
             return "&str"
 
 
-def _getVariantName(variant) -> str:
+def _getVariantName(variant: SBValue) -> str:
     """
     Since the enum variant's type name is in the form `TheEnumName::TheVariantName$Variant`,
     we can extract `TheVariantName` from it for display purpose.
     """
     s = variant.GetType().GetName()
-    match = re.search(r"::([^:]+)\$Variant$", s)
-    return match.group(1) if match else ""
+    if not s.endswith("$Variant"):
+        return ""
+
+    # trim off path and "$Variant"
+    # len("$Variant") == 8
+    return s.rsplit("::", 1)[1][:-8]
 
 
 class ClangEncodedEnumProvider:
     """Pretty-printer for 'clang-encoded' enums support implemented in LLDB"""
 
+    valobj: SBValue
+    variant: SBValue
+    value: SBValue
+
     DISCRIMINANT_MEMBER_NAME = "$discr$"
     VALUE_MEMBER_NAME = "value"
 
+    __slots__ = ("valobj", "variant", "value")
+
     def __init__(self, valobj: SBValue, _dict: LLDBOpaque):
         self.valobj = valobj
         self.update()
 
     def has_children(self) -> bool:
-        return True
+        return self.value.MightHaveChildren()
 
     def num_children(self) -> int:
-        return 1
+        return self.value.GetNumChildren()
 
-    def get_child_index(self, _name: str) -> int:
-        return -1
+    def get_child_index(self, name: str) -> int:
+        return self.value.GetIndexOfChildWithName(name)
 
     def get_child_at_index(self, index: int) -> SBValue:
-        if index == 0:
-            value = self.variant.GetChildMemberWithName(
-                ClangEncodedEnumProvider.VALUE_MEMBER_NAME
-            )
-            return value.CreateChildAtOffset(
-                _getVariantName(self.variant), 0, value.GetType()
-            )
-        return None
+        return self.value.GetChildAtIndex(index)
 
     def update(self):
         all_variants = self.valobj.GetChildAtIndex(0)
         index = self._getCurrentVariantIndex(all_variants)
         self.variant = all_variants.GetChildAtIndex(index)
+        self.value = self.variant.GetChildMemberWithName(
+            ClangEncodedEnumProvider.VALUE_MEMBER_NAME
+        ).GetSyntheticValue()
 
     def _getCurrentVariantIndex(self, all_variants: SBValue) -> int:
         default_index = 0
@@ -514,6 +549,23 @@
         return default_index
 
 
+def ClangEncodedEnumSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str:
+    enum_synth = ClangEncodedEnumProvider(valobj.GetNonSyntheticValue(), _dict)
+    variant = enum_synth.variant
+    name = _getVariantName(variant)
+
+    if valobj.GetNumChildren() == 0:
+        return name
+
+    child_name: str = valobj.GetChildAtIndex(0).name
+    if child_name == "0" or child_name == "__0":
+        # enum variant is a tuple struct
+        return name + TupleSummaryProvider(valobj, _dict)
+    else:
+        # enum variant is a regular struct
+        return name + StructSummaryProvider(valobj, _dict)
+
+
 class MSVCEnumSyntheticProvider:
     """
     Synthetic provider for sum-type enums on MSVC. For a detailed explanation of the internals,
@@ -522,12 +574,14 @@
     https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/cpp_like.rs
     """
 
+    valobj: SBValue
+    variant: SBValue
+    value: SBValue
+
     __slots__ = ["valobj", "variant", "value"]
 
     def __init__(self, valobj: SBValue, _dict: LLDBOpaque):
         self.valobj = valobj
-        self.variant: SBValue
-        self.value: SBValue
         self.update()
 
     def update(self):
@@ -695,21 +749,6 @@
         return name
 
 
-def StructSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str:
-    output = []
-    for i in range(valobj.GetNumChildren()):
-        child: SBValue = valobj.GetChildAtIndex(i)
-        summary = child.summary
-        if summary is None:
-            summary = child.value
-            if summary is None:
-                summary = StructSummaryProvider(child, _dict)
-        summary = child.GetName() + ":" + summary
-        output.append(summary)
-
-    return "{" + ", ".join(output) + "}"
-
-
 def MSVCEnumSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str:
     enum_synth = MSVCEnumSyntheticProvider(valobj.GetNonSyntheticValue(), _dict)
     variant_names: SBType = valobj.target.FindFirstType(
@@ -826,21 +865,6 @@
         return "(" + name + ")"
 
 
-def TupleSummaryProvider(valobj: SBValue, _dict: LLDBOpaque):
-    output: List[str] = []
-
-    for i in range(0, valobj.GetNumChildren()):
-        child: SBValue = valobj.GetChildAtIndex(i)
-        summary = child.summary
-        if summary is None:
-            summary = child.value
-            if summary is None:
-                summary = "{...}"
-        output.append(summary)
-
-    return "(" + ", ".join(output) + ")"
-
-
 class StdVecSyntheticProvider:
     """Pretty-printer for alloc::vec::Vec<T>
 
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index da19f50..5d16dff 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -978,15 +978,16 @@ fn parse_extern_html_roots(
     Ok(externs)
 }
 
-/// Path directly to crate-info file.
+/// Path directly to crate-info directory.
 ///
-/// For example, `/home/user/project/target/doc.parts/<crate>/crate-info`.
+/// For example, `/home/user/project/target/doc.parts`.
+/// Each crate has its info stored in a file called `CRATENAME.json`.
 #[derive(Clone, Debug)]
 pub(crate) struct PathToParts(pub(crate) PathBuf);
 
 impl PathToParts {
     fn from_flag(path: String) -> Result<PathToParts, String> {
-        let mut path = PathBuf::from(path);
+        let path = PathBuf::from(path);
         // check here is for diagnostics
         if path.exists() && !path.is_dir() {
             Err(format!(
@@ -995,20 +996,22 @@ fn from_flag(path: String) -> Result<PathToParts, String> {
             ))
         } else {
             // if it doesn't exist, we'll create it. worry about that in write_shared
-            path.push("crate-info");
             Ok(PathToParts(path))
         }
     }
 }
 
-/// Reports error if --include-parts-dir / crate-info is not a file
+/// Reports error if --include-parts-dir is not a directory
 fn parse_include_parts_dir(m: &getopts::Matches) -> Result<Vec<PathToParts>, String> {
     let mut ret = Vec::new();
     for p in m.opt_strs("include-parts-dir") {
         let p = PathToParts::from_flag(p)?;
         // this is just for diagnostic
-        if !p.0.is_file() {
-            return Err(format!("--include-parts-dir expected {} to be a file", p.0.display()));
+        if !p.0.is_dir() {
+            return Err(format!(
+                "--include-parts-dir expected {} to be a directory",
+                p.0.display()
+            ));
         }
         ret.push(p);
     }
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index dd91cec..eee13ff 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -1252,9 +1252,9 @@ fn write_str(&mut self, s: &str) -> fmt::Result {
 
 impl Display for Indent {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        (0..self.0).for_each(|_| {
-            f.write_char(' ').unwrap();
-        });
+        for _ in 0..self.0 {
+            f.write_char(' ')?;
+        }
         Ok(())
     }
 }
diff --git a/src/librustdoc/html/length_limit.rs b/src/librustdoc/html/length_limit.rs
index fdb1ddc..f786215 100644
--- a/src/librustdoc/html/length_limit.rs
+++ b/src/librustdoc/html/length_limit.rs
@@ -87,7 +87,7 @@ pub(super) fn open_tag(&mut self, tag_name: &'static str) {
     pub(super) fn close_tag(&mut self) {
         if let Some(tag_name) = self.unclosed_tags.pop() {
             // Close the most recently opened tag.
-            write!(self.buf, "</{tag_name}>").unwrap()
+            write!(self.buf, "</{tag_name}>").expect("infallible string operation");
         }
         // There are valid cases where `close_tag()` is called without
         // there being any tags to close. For example, this occurs when
@@ -99,7 +99,7 @@ pub(super) fn close_tag(&mut self) {
     /// Write all queued tags and add them to the `unclosed_tags` list.
     fn flush_queue(&mut self) {
         for tag_name in self.queued_tags.drain(..) {
-            write!(self.buf, "<{tag_name}>").unwrap();
+            write!(self.buf, "<{tag_name}>").expect("infallible string operation");
 
             self.unclosed_tags.push(tag_name);
         }
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 871ed53..9c8e599 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -916,7 +916,7 @@ fn render_impls(
     impls: &[&Impl],
     containing_item: &clean::Item,
     toggle_open_by_default: bool,
-) {
+) -> fmt::Result {
     let mut rendered_impls = impls
         .iter()
         .map(|i| {
@@ -942,7 +942,7 @@ fn render_impls(
         })
         .collect::<Vec<_>>();
     rendered_impls.sort();
-    w.write_str(&rendered_impls.join("")).unwrap();
+    w.write_str(&rendered_impls.join(""))
 }
 
 /// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item.
@@ -1037,7 +1037,7 @@ fn assoc_const(
 ) -> impl fmt::Display {
     let tcx = cx.tcx();
     fmt::from_fn(move |w| {
-        render_attributes_in_code(w, it, &" ".repeat(indent), cx);
+        render_attributes_in_code(w, it, &" ".repeat(indent), cx)?;
         write!(
             w,
             "{indent}{vis}const <a{href} class=\"constant\">{name}</a>{generics}: {ty}",
@@ -1145,10 +1145,10 @@ fn assoc_method(
         let (indent, indent_str, end_newline) = if parent == ItemType::Trait {
             header_len += 4;
             let indent_str = "    ";
-            render_attributes_in_code(w, meth, indent_str, cx);
+            render_attributes_in_code(w, meth, indent_str, cx)?;
             (4, indent_str, Ending::NoNewline)
         } else {
-            render_attributes_in_code(w, meth, "", cx);
+            render_attributes_in_code(w, meth, "", cx)?;
             (0, "", Ending::Newline)
         };
         write!(
@@ -1365,10 +1365,10 @@ fn render_all_impls(
     concrete: &[&Impl],
     synthetic: &[&Impl],
     blanket_impl: &[&Impl],
-) {
+) -> fmt::Result {
     let impls = {
         let mut buf = String::new();
-        render_impls(cx, &mut buf, concrete, containing_item, true);
+        render_impls(cx, &mut buf, concrete, containing_item, true)?;
         buf
     };
     if !impls.is_empty() {
@@ -1376,8 +1376,7 @@ fn render_all_impls(
             w,
             "{}<div id=\"trait-implementations-list\">{impls}</div>",
             write_impl_section_heading("Trait Implementations", "trait-implementations")
-        )
-        .unwrap();
+        )?;
     }
 
     if !synthetic.is_empty() {
@@ -1385,10 +1384,9 @@ fn render_all_impls(
             w,
             "{}<div id=\"synthetic-implementations-list\">",
             write_impl_section_heading("Auto Trait Implementations", "synthetic-implementations",)
-        )
-        .unwrap();
-        render_impls(cx, &mut w, synthetic, containing_item, false);
-        w.write_str("</div>").unwrap();
+        )?;
+        render_impls(cx, &mut w, synthetic, containing_item, false)?;
+        w.write_str("</div>")?;
     }
 
     if !blanket_impl.is_empty() {
@@ -1396,11 +1394,11 @@ fn render_all_impls(
             w,
             "{}<div id=\"blanket-implementations-list\">",
             write_impl_section_heading("Blanket Implementations", "blanket-implementations")
-        )
-        .unwrap();
-        render_impls(cx, &mut w, blanket_impl, containing_item, false);
-        w.write_str("</div>").unwrap();
+        )?;
+        render_impls(cx, &mut w, blanket_impl, containing_item, false)?;
+        w.write_str("</div>")?;
     }
+    Ok(())
 }
 
 fn render_assoc_items(
@@ -1412,8 +1410,7 @@ fn render_assoc_items(
     fmt::from_fn(move |f| {
         let mut derefs = DefIdSet::default();
         derefs.insert(it);
-        render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs);
-        Ok(())
+        render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs)
     })
 }
 
@@ -1424,10 +1421,10 @@ fn render_assoc_items_inner(
     it: DefId,
     what: AssocItemRender<'_>,
     derefs: &mut DefIdSet,
-) {
+) -> fmt::Result {
     info!("Documenting associated items of {:?}", containing_item.name);
     let cache = &cx.shared.cache;
-    let Some(v) = cache.impls.get(&it) else { return };
+    let Some(v) = cache.impls.get(&it) else { return Ok(()) };
     let (mut non_trait, traits): (Vec<_>, _) =
         v.iter().partition(|i| i.inner_impl().trait_.is_none());
     if !non_trait.is_empty() {
@@ -1511,8 +1508,7 @@ fn render_assoc_items_inner(
                 matches!(what, AssocItemRender::DerefFor { .. })
                     .then_some("</details>")
                     .maybe_display(),
-            )
-            .unwrap();
+            )?;
         }
     }
 
@@ -1522,13 +1518,13 @@ fn render_assoc_items_inner(
         if let Some(impl_) = deref_impl {
             let has_deref_mut =
                 traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait());
-            render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs);
+            render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs)?;
         }
 
         // If we were already one level into rendering deref methods, we don't want to render
         // anything after recursing into any further deref methods above.
         if let AssocItemRender::DerefFor { .. } = what {
-            return;
+            return Ok(());
         }
 
         let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) =
@@ -1536,8 +1532,9 @@ fn render_assoc_items_inner(
         let (blanket_impl, concrete): (Vec<&Impl>, _) =
             concrete.into_iter().partition(|t| t.inner_impl().kind.is_blanket());
 
-        render_all_impls(w, cx, containing_item, &concrete, &synthetic, &blanket_impl);
+        render_all_impls(w, cx, containing_item, &concrete, &synthetic, &blanket_impl)?;
     }
+    Ok(())
 }
 
 /// `derefs` is the set of all deref targets that have already been handled.
@@ -1548,7 +1545,7 @@ fn render_deref_methods(
     container_item: &clean::Item,
     deref_mut: bool,
     derefs: &mut DefIdSet,
-) {
+) -> fmt::Result {
     let cache = cx.cache();
     let deref_type = impl_.inner_impl().trait_.as_ref().unwrap();
     let (target, real_target) = impl_
@@ -1574,15 +1571,16 @@ fn render_deref_methods(
             // `impl Deref<Target = S> for S`
             if did == type_did || !derefs.insert(did) {
                 // Avoid infinite cycles
-                return;
+                return Ok(());
             }
         }
-        render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs);
+        render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs)?;
     } else if let Some(prim) = target.primitive_type()
         && let Some(&did) = cache.primitive_locations.get(&prim)
     {
-        render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs);
+        render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs)?;
     }
+    Ok(())
 }
 
 fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> bool {
@@ -1805,8 +1803,7 @@ fn doc_impl_item(
                             // because impls can't have a stability.
                             if !item.doc_value().is_empty() {
                                 document_item_info(cx, it, Some(parent))
-                                    .render_into(&mut info_buffer)
-                                    .unwrap();
+                                    .render_into(&mut info_buffer)?;
                                 doc_buffer = document_full(item, cx, HeadingOffset::H5).to_string();
                                 short_documented = false;
                             } else {
@@ -1823,9 +1820,7 @@ fn doc_impl_item(
                             }
                         }
                     } else {
-                        document_item_info(cx, item, Some(parent))
-                            .render_into(&mut info_buffer)
-                            .unwrap();
+                        document_item_info(cx, item, Some(parent)).render_into(&mut info_buffer)?;
                         if rendering_params.show_def_docs {
                             doc_buffer = document_full(item, cx, HeadingOffset::H5).to_string();
                             short_documented = false;
@@ -2920,7 +2915,7 @@ fn render_attributes_in_code(
     item: &clean::Item,
     prefix: &str,
     cx: &Context<'_>,
-) {
+) -> fmt::Result {
     for attr in &item.attrs.other_attrs {
         let hir::Attribute::Parsed(kind) = attr else { continue };
         let attr = match kind {
@@ -2934,24 +2929,30 @@ fn render_attributes_in_code(
             AttributeKind::NonExhaustive(..) => Cow::Borrowed("#[non_exhaustive]"),
             _ => continue,
         };
-        render_code_attribute(prefix, attr.as_ref(), w);
+        render_code_attribute(prefix, attr.as_ref(), w)?;
     }
 
     if let Some(def_id) = item.def_id()
         && let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id)
     {
-        render_code_attribute(prefix, &repr, w);
+        render_code_attribute(prefix, &repr, w)?;
     }
+    Ok(())
 }
 
-fn render_repr_attribute_in_code(w: &mut impl fmt::Write, cx: &Context<'_>, def_id: DefId) {
+fn render_repr_attribute_in_code(
+    w: &mut impl fmt::Write,
+    cx: &Context<'_>,
+    def_id: DefId,
+) -> fmt::Result {
     if let Some(repr) = repr_attribute(cx.tcx(), cx.cache(), def_id) {
-        render_code_attribute("", &repr, w);
+        render_code_attribute("", &repr, w)?;
     }
+    Ok(())
 }
 
-fn render_code_attribute(prefix: &str, attr: &str, w: &mut impl fmt::Write) {
-    write!(w, "<div class=\"code-attribute\">{prefix}{attr}</div>").unwrap();
+fn render_code_attribute(prefix: &str, attr: &str, w: &mut impl fmt::Write) -> fmt::Result {
+    write!(w, "<div class=\"code-attribute\">{prefix}{attr}</div>")
 }
 
 /// Compute the *public* `#[repr]` of the item given by `DefId`.
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index fa7c9e7..0c51173 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -457,7 +457,7 @@ fn cmp(i1: &clean::Item, i2: &clean::Item, tcx: TyCtxt<'_>) -> Ordering {
                             "<dt{id}>\
                                 <code>"
                         )?;
-                        render_attributes_in_code(w, myitem, "", cx);
+                        render_attributes_in_code(w, myitem, "", cx)?;
                         write!(
                             w,
                             "{vis}{imp}</code>{stab_tags}\
@@ -625,7 +625,7 @@ fn item_function(cx: &Context<'_>, it: &clean::Item, f: &clean::Function) -> imp
         let notable_traits = notable_traits_button(&f.decl.output, cx).maybe_display();
 
         wrap_item(w, |w| {
-            render_attributes_in_code(w, it, "", cx);
+            render_attributes_in_code(w, it, "", cx)?;
             write!(
                 w,
                 "{vis}{constness}{asyncness}{safety}{abi}fn \
@@ -666,7 +666,7 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt:
 
         // Output the trait definition
         wrap_item(w, |mut w| {
-            render_attributes_in_code(&mut w, it, "", cx);
+            render_attributes_in_code(&mut w, it, "", cx)?;
             write!(
                 w,
                 "{vis}{safety}{is_auto}trait {name}{generics}{bounds}",
@@ -1240,7 +1240,7 @@ fn item_trait_alias(
 ) -> impl fmt::Display {
     fmt::from_fn(|w| {
         wrap_item(w, |w| {
-            render_attributes_in_code(w, it, "", cx);
+            render_attributes_in_code(w, it, "", cx)?;
             write!(
                 w,
                 "trait {name}{generics} = {bounds}{where_clause};",
@@ -1268,7 +1268,7 @@ fn item_trait_alias(
 fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) -> impl fmt::Display {
     fmt::from_fn(|w| {
         wrap_item(w, |w| {
-            render_attributes_in_code(w, it, "", cx);
+            render_attributes_in_code(w, it, "", cx)?;
             write!(
                 w,
                 "{vis}type {name}{generics}{where_clause} = {type_};",
@@ -1464,7 +1464,7 @@ fn render_union(&self) -> impl Display {
 
     fn print_field_attrs(&self, field: &'a clean::Item) -> impl Display {
         fmt::from_fn(move |w| {
-            render_attributes_in_code(w, field, "", self.cx);
+            render_attributes_in_code(w, field, "", self.cx)?;
             Ok(())
         })
     }
@@ -1554,9 +1554,9 @@ fn render_into<W: fmt::Write>(
         wrap_item(w, |w| {
             if is_type_alias {
                 // For now the only attributes we render for type aliases are `repr` attributes.
-                render_repr_attribute_in_code(w, cx, self.def_id);
+                render_repr_attribute_in_code(w, cx, self.def_id)?;
             } else {
-                render_attributes_in_code(w, it, "", cx);
+                render_attributes_in_code(w, it, "", cx)?;
             }
             write!(
                 w,
@@ -1695,7 +1695,7 @@ fn render_enum_fields(
                 if v.is_stripped() {
                     continue;
                 }
-                render_attributes_in_code(w, v, TAB, cx);
+                render_attributes_in_code(w, v, TAB, cx)?;
                 w.write_str(TAB)?;
                 match v.kind {
                     clean::VariantItem(ref var) => match var.kind {
@@ -1779,7 +1779,7 @@ fn item_variants(
                 )
                 .maybe_display()
             )?;
-            render_attributes_in_code(w, variant, "", cx);
+            render_attributes_in_code(w, variant, "", cx)?;
             if let clean::VariantItem(ref var) = variant.kind
                 && let clean::VariantKind::CLike = var.kind
             {
@@ -1855,7 +1855,7 @@ fn item_variants(
                                         <a href=\"#{id}\" class=\"anchor field\">§</a>\
                                         <code>"
                             )?;
-                            render_attributes_in_code(w, field, "", cx);
+                            render_attributes_in_code(w, field, "", cx)?;
                             write!(
                                 w,
                                 "{f}: {t}</code>\
@@ -1881,7 +1881,7 @@ fn item_macro(cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) -> impl fmt:
     fmt::from_fn(|w| {
         wrap_item(w, |w| {
             // FIXME: Also print `#[doc(hidden)]` for `macro_rules!` if it `is_doc_hidden`.
-            render_attributes_in_code(w, it, "", cx);
+            render_attributes_in_code(w, it, "", cx)?;
             if !t.macro_rules {
                 write!(w, "{}", visibility_print_with_space(it, cx))?;
             }
@@ -1927,16 +1927,15 @@ fn item_primitive(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Display {
         let def_id = it.item_id.expect_def_id();
         write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?;
         if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) {
-            write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All))?;
+            write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All))
         } else {
             // We handle the "reference" primitive type on its own because we only want to list
             // implementations on generic types.
             let (concrete, synthetic, blanket_impl) =
                 get_filtered_impls_for_reference(&cx.shared, it);
 
-            render_all_impls(w, cx, it, &concrete, &synthetic, &blanket_impl);
+            render_all_impls(w, cx, it, &concrete, &synthetic, &blanket_impl)
         }
-        Ok(())
     })
 }
 
@@ -1950,7 +1949,7 @@ fn item_constant(
     fmt::from_fn(|w| {
         wrap_item(w, |w| {
             let tcx = cx.tcx();
-            render_attributes_in_code(w, it, "", cx);
+            render_attributes_in_code(w, it, "", cx)?;
 
             write!(
                 w,
@@ -2016,9 +2015,9 @@ fn render_into<W: fmt::Write>(
         wrap_item(w, |w| {
             if is_type_alias {
                 // For now the only attributes we render for type aliases are `repr` attributes.
-                render_repr_attribute_in_code(w, cx, self.def_id);
+                render_repr_attribute_in_code(w, cx, self.def_id)?;
             } else {
-                render_attributes_in_code(w, it, "", cx);
+                render_attributes_in_code(w, it, "", cx)?;
             }
             write!(
                 w,
@@ -2097,7 +2096,7 @@ fn item_fields(
                         <code>",
                     item_type = ItemType::StructField,
                 )?;
-                render_attributes_in_code(w, field, "", cx);
+                render_attributes_in_code(w, field, "", cx)?;
                 write!(
                     w,
                     "{field_name}: {ty}</code>\
@@ -2120,7 +2119,7 @@ fn item_static(
 ) -> impl fmt::Display {
     fmt::from_fn(move |w| {
         wrap_item(w, |w| {
-            render_attributes_in_code(w, it, "", cx);
+            render_attributes_in_code(w, it, "", cx)?;
             write!(
                 w,
                 "{vis}{safe}static {mutability}{name}: {typ}",
@@ -2140,8 +2139,8 @@ fn item_foreign_type(cx: &Context<'_>, it: &clean::Item) -> impl fmt::Display {
     fmt::from_fn(|w| {
         wrap_item(w, |w| {
             w.write_str("extern {\n")?;
-            render_attributes_in_code(w, it, "", cx);
-            write!(w, "    {}type {};\n}}", visibility_print_with_space(it, cx), it.name.unwrap(),)
+            render_attributes_in_code(w, it, "", cx)?;
+            write!(w, "    {}type {};\n}}", visibility_print_with_space(it, cx), it.name.unwrap())
         })?;
 
         write!(
@@ -2292,15 +2291,14 @@ fn print_bounds(
         .maybe_display()
 }
 
-fn wrap_item<W, F, T>(w: &mut W, f: F) -> T
+fn wrap_item<W, F>(w: &mut W, f: F) -> fmt::Result
 where
     W: fmt::Write,
-    F: FnOnce(&mut W) -> T,
+    F: FnOnce(&mut W) -> fmt::Result,
 {
-    write!(w, r#"<pre class="rust item-decl"><code>"#).unwrap();
-    let res = f(w);
-    write!(w, "</code></pre>").unwrap();
-    res
+    w.write_str(r#"<pre class="rust item-decl"><code>"#)?;
+    f(w)?;
+    w.write_str("</code></pre>")
 }
 
 #[derive(PartialEq, Eq)]
@@ -2370,9 +2368,9 @@ fn render_union(
     fmt::from_fn(move |mut f| {
         if is_type_alias {
             // For now the only attributes we render for type aliases are `repr` attributes.
-            render_repr_attribute_in_code(f, cx, def_id);
+            render_repr_attribute_in_code(f, cx, def_id)?;
         } else {
-            render_attributes_in_code(f, it, "", cx);
+            render_attributes_in_code(f, it, "", cx)?;
         }
         write!(f, "{}union {}", visibility_print_with_space(it, cx), it.name.unwrap(),)?;
 
@@ -2403,7 +2401,7 @@ fn render_union(
 
         for field in fields {
             if let clean::StructFieldItem(ref ty) = field.kind {
-                render_attributes_in_code(&mut f, field, "    ", cx);
+                render_attributes_in_code(&mut f, field, "    ", cx)?;
                 writeln!(
                     f,
                     "    {}{}: {},",
@@ -2500,7 +2498,7 @@ fn render_struct_fields(
                 }
                 for field in fields {
                     if let clean::StructFieldItem(ref ty) = field.kind {
-                        render_attributes_in_code(w, field, &format!("{tab}    "), cx);
+                        render_attributes_in_code(w, field, &format!("{tab}    "), cx)?;
                         writeln!(
                             w,
                             "{tab}    {vis}{name}: {ty},",
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index 6045b9a..8b6776f 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -14,7 +14,7 @@
 //!    or contains "invocation-specific".
 
 use std::cell::RefCell;
-use std::ffi::OsString;
+use std::ffi::{OsStr, OsString};
 use std::fs::File;
 use std::io::{self, Write as _};
 use std::iter::once;
@@ -84,9 +84,11 @@ pub(crate) fn write_shared(
     };
 
     if let Some(parts_out_dir) = &opt.parts_out_dir {
-        create_parents(&parts_out_dir.0)?;
+        let mut parts_out_file = parts_out_dir.0.clone();
+        parts_out_file.push(&format!("{crate_name}.json"));
+        create_parents(&parts_out_file)?;
         try_err!(
-            fs::write(&parts_out_dir.0, serde_json::to_string(&info).unwrap()),
+            fs::write(&parts_out_file, serde_json::to_string(&info).unwrap()),
             &parts_out_dir.0
         );
     }
@@ -238,13 +240,25 @@ impl CrateInfo {
     pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
         parts_paths
             .iter()
-            .map(|parts_path| {
-                let path = &parts_path.0;
-                let parts = try_err!(fs::read(path), &path);
-                let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), &path);
-                Ok::<_, Error>(parts)
+            .fold(Ok(Vec::new()), |acc, parts_path| {
+                let mut acc = acc?;
+                let dir = &parts_path.0;
+                acc.append(&mut try_err!(std::fs::read_dir(dir), dir.as_path())
+                    .filter_map(|file| {
+                        let to_crate_info = |file: Result<std::fs::DirEntry, std::io::Error>| -> Result<Option<CrateInfo>, Error> {
+                            let file = try_err!(file, dir.as_path());
+                            if file.path().extension() != Some(OsStr::new("json")) {
+                                return Ok(None);
+                            }
+                            let parts = try_err!(fs::read(file.path()), file.path());
+                            let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), file.path());
+                            Ok(Some(parts))
+                        };
+                        to_crate_info(file).transpose()
+                    })
+                    .collect::<Result<Vec<CrateInfo>, Error>>()?);
+                Ok(acc)
             })
-            .collect::<Result<Vec<CrateInfo>, Error>>()
     }
 }
 
diff --git a/tests/debuginfo/basic-types-globals.rs b/tests/debuginfo/basic-types-globals.rs
index 6e41508..b56a710 100644
--- a/tests/debuginfo/basic-types-globals.rs
+++ b/tests/debuginfo/basic-types-globals.rs
@@ -13,7 +13,7 @@
 // lldb-command:v I
 // lldb-check: ::I = -1
 // lldb-command:v --format=d C
-// lldb-check: ::C = 97
+// lldb-check: ::C = 97 U+0x00000061 U'a'
 // lldb-command:v --format=d I8
 // lldb-check: ::I8 = 68
 // lldb-command:v I16
diff --git a/tests/debuginfo/borrowed-enum.rs b/tests/debuginfo/borrowed-enum.rs
index 48dce13..f376917 100644
--- a/tests/debuginfo/borrowed-enum.rs
+++ b/tests/debuginfo/borrowed-enum.rs
@@ -23,11 +23,11 @@
 // lldb-command:run
 
 // lldb-command:v *the_a_ref
-// lldb-check:(borrowed_enum::ABC) *the_a_ref = { TheA = { x = 0 y = 8970181431921507452 } }
+// lldb-check:(borrowed_enum::ABC) *the_a_ref = TheA{x:0, y:8970181431921507452} { x = 0 y = 8970181431921507452 }
 // lldb-command:v *the_b_ref
-// lldb-check:(borrowed_enum::ABC) *the_b_ref = { TheB = { 0 = 0 1 = 286331153 2 = 286331153 } }
+// lldb-check:(borrowed_enum::ABC) *the_b_ref = TheB(0, 286331153, 286331153) { 0 = 0 1 = 286331153 2 = 286331153 }
 // lldb-command:v *univariant_ref
-// lldb-check:(borrowed_enum::Univariant) *univariant_ref = { TheOnlyCase = { 0 = 4820353753753434 } }
+// lldb-check:(borrowed_enum::Univariant) *univariant_ref = TheOnlyCase(4820353753753434) { 0 = 4820353753753434 }
 
 #![allow(unused_variables)]
 
diff --git a/tests/debuginfo/c-style-enum-in-composite.rs b/tests/debuginfo/c-style-enum-in-composite.rs
index dd5e4f8..137cb26 100644
--- a/tests/debuginfo/c-style-enum-in-composite.rs
+++ b/tests/debuginfo/c-style-enum-in-composite.rs
@@ -35,7 +35,7 @@
 // lldb-check:[...] { 0 = 0 1 = OneHundred }
 
 // lldb-command:v tuple_padding_at_end
-// lldb-check:[...] { 0 = { 0 = 1 1 = OneThousand } 1 = 2 }
+// lldb-check:[...] ((1, OneThousand), 2) { 0 = (1, OneThousand) { 0 = 1 1 = OneThousand } 1 = 2 }
 
 // lldb-command:v tuple_different_enums
 // lldb-check:[...] { 0 = OneThousand 1 = MountainView 2 = OneMillion 3 = Vienna }
diff --git a/tests/debuginfo/coroutine-objects.rs b/tests/debuginfo/coroutine-objects.rs
index f1165de..b9d015e 100644
--- a/tests/debuginfo/coroutine-objects.rs
+++ b/tests/debuginfo/coroutine-objects.rs
@@ -27,7 +27,7 @@
 
 // lldb-command:run
 // lldb-command:v b
-// lldb-check:(coroutine_objects::main::{coroutine_env#0}) b = { value = { _ref__a = 0x[...] } $discr$ = [...] }
+// lldb-check:(coroutine_objects::main::{coroutine_env#0}) b = 0{_ref__a:0x[...]} { _ref__a = 0x[...] }
 
 // === CDB TESTS ===================================================================================
 
diff --git a/tests/debuginfo/enum-thinlto.rs b/tests/debuginfo/enum-thinlto.rs
index 7897554..9b77b1d 100644
--- a/tests/debuginfo/enum-thinlto.rs
+++ b/tests/debuginfo/enum-thinlto.rs
@@ -15,7 +15,7 @@
 // lldb-command:run
 
 // lldb-command:v *abc
-// lldb-check:(enum_thinlto::ABC) *abc = { value = { x = 0 y = 8970181431921507452 } $discr$ = 0 }
+// lldb-check:(enum_thinlto::ABC) *abc = TheA{x:0, y:8970181431921507452} { x = 0 y = 8970181431921507452 }
 
 #![allow(unused_variables)]
 
diff --git a/tests/debuginfo/generic-method-on-generic-struct.rs b/tests/debuginfo/generic-method-on-generic-struct.rs
index c549a14..9c1d87a 100644
--- a/tests/debuginfo/generic-method-on-generic-struct.rs
+++ b/tests/debuginfo/generic-method-on-generic-struct.rs
@@ -58,7 +58,7 @@
 
 // STACK BY REF
 // lldb-command:v *self
-// lldb-check:[...] { x = { 0 = 8888 1 = -8888 } }
+// lldb-check:[...] { x = (8888, -8888) { 0 = 8888 1 = -8888 } }
 // lldb-command:v arg1
 // lldb-check:[...] -1
 // lldb-command:v arg2
@@ -67,7 +67,7 @@
 
 // STACK BY VAL
 // lldb-command:v self
-// lldb-check:[...] { x = { 0 = 8888 1 = -8888 } }
+// lldb-check:[...] { x = (8888, -8888) { 0 = 8888 1 = -8888 } }
 // lldb-command:v arg1
 // lldb-check:[...] -3
 // lldb-command:v arg2
diff --git a/tests/debuginfo/issue-57822.rs b/tests/debuginfo/issue-57822.rs
index 59c0759..aa829df 100644
--- a/tests/debuginfo/issue-57822.rs
+++ b/tests/debuginfo/issue-57822.rs
@@ -24,7 +24,7 @@
 // lldb-check:(issue_57822::main::{closure_env#1}) g = { f = { x = 1 } }
 
 // lldb-command:v b
-// lldb-check:(issue_57822::main::{coroutine_env#3}) b = { value = { a = { value = { y = 2 } $discr$ = '\x02' } } $discr$ = '\x02' }
+// lldb-check:(issue_57822::main::{coroutine_env#3}) b = 2{a:2{y:2}} { a = 2{y:2} { y = 2 } }
 
 #![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
 
diff --git a/tests/debuginfo/method-on-generic-struct.rs b/tests/debuginfo/method-on-generic-struct.rs
index b9627f2..01e7ffc 100644
--- a/tests/debuginfo/method-on-generic-struct.rs
+++ b/tests/debuginfo/method-on-generic-struct.rs
@@ -58,7 +58,7 @@
 
 // STACK BY REF
 // lldb-command:v *self
-// lldb-check:[...]Struct<(u32, i32)>) *self = { x = { 0 = 8888 1 = -8888 } }
+// lldb-check:[...]Struct<(u32, i32)>) *self = { x = (8888, -8888) { 0 = 8888 1 = -8888 } }
 // lldb-command:v arg1
 // lldb-check:[...] -1
 // lldb-command:v arg2
@@ -67,7 +67,7 @@
 
 // STACK BY VAL
 // lldb-command:v self
-// lldb-check:[...]Struct<(u32, i32)>) self = { x = { 0 = 8888 1 = -8888 } }
+// lldb-check:[...]Struct<(u32, i32)>) self = { x = (8888, -8888) { 0 = 8888 1 = -8888 } }
 // lldb-command:v arg1
 // lldb-check:[...] -3
 // lldb-command:v arg2
diff --git a/tests/debuginfo/msvc-pretty-enums.rs b/tests/debuginfo/msvc-pretty-enums.rs
index aa6629e..7d9f867 100644
--- a/tests/debuginfo/msvc-pretty-enums.rs
+++ b/tests/debuginfo/msvc-pretty-enums.rs
@@ -1,3 +1,4 @@
+//@ only-msvc
 //@ min-lldb-version: 1800
 //@ ignore-gdb
 //@ compile-flags:-g
diff --git a/tests/debuginfo/option-like-enum.rs b/tests/debuginfo/option-like-enum.rs
index 5a55b14..0e41997 100644
--- a/tests/debuginfo/option-like-enum.rs
+++ b/tests/debuginfo/option-like-enum.rs
@@ -40,31 +40,31 @@
 // lldb-command:run
 
 // lldb-command:v some
-// lldb-check:[...] Some(&0x[...])
+// lldb-check:[...] Some(0x[...]) { 0 = 0x[...] }
 
 // lldb-command:v none
 // lldb-check:[...] None
 
 // lldb-command:v full
-// lldb-check:[...] Full(454545, &0x[...], 9988)
+// lldb-check:[...] Full(454545, 0x[...], 9988) { 0 = 454545 1 = 0x[...] 2 = 9988 }
 
 // lldb-command:v empty
 // lldb-check:[...] Empty
 
 // lldb-command:v droid
-// lldb-check:[...] Droid { id: 675675, range: 10000001, internals: &0x[...] }
+// lldb-check:[...] Droid{id:675675, range:10000001, internals:0x[...]} { id = 675675 range = 10000001 internals = 0x[...] }
 
 // lldb-command:v void_droid
 // lldb-check:[...] Void
 
 // lldb-command:v some_str
-// lldb-check:[...] Some("abc")
+// lldb-check:[...] Some("abc") [...]
 
 // lldb-command:v none_str
 // lldb-check:[...] None
 
 // lldb-command:v nested_non_zero_yep
-// lldb-check:[...] Yep(10.5, NestedNonZeroField { a: 10, b: 20, c: &[...] })
+// lldb-check:[...] Yep(10.5, {a:10, b:20, c:[...]}) { 0 = 10.5 1 = { a = 10 b = 20 c = [...] } }
 
 // lldb-command:v nested_non_zero_nope
 // lldb-check:[...] Nope
diff --git a/tests/debuginfo/pretty-std-collections.rs b/tests/debuginfo/pretty-std-collections.rs
index 568ca0f..3592122 100644
--- a/tests/debuginfo/pretty-std-collections.rs
+++ b/tests/debuginfo/pretty-std-collections.rs
@@ -59,7 +59,7 @@
 // lldb-check:[...] size=7 { [0] = 2 [1] = 3 [2] = 4 [3] = 5 [4] = 6 [5] = 7 [6] = 8 }
 
 // lldb-command:v hash_map
-// lldb-check:[...] size=4 { [0] = { 0 = 1 1 = 10 } [1] = { 0 = 2 1 = 20 } [2] = { 0 = 3 1 = 30 } [3] = { 0 = 4 1 = 40 } }
+// lldb-check:[...] size=4 { [0] = (1, 10) { 0 = 1 1 = 10 } [1] = (2, 20) { 0 = 2 1 = 20 } [2] = (3, 30) { 0 = 3 1 = 30 } [3] = (4, 40) { 0 = 4 1 = 40 } }
 
 // lldb-command:v hash_set
 // lldb-check:[...] size=4 { [0] = 1 [1] = 2 [2] = 3 [3] = 4 }
diff --git a/tests/debuginfo/strings-and-strs.rs b/tests/debuginfo/strings-and-strs.rs
index 392cf69..baeebda 100644
--- a/tests/debuginfo/strings-and-strs.rs
+++ b/tests/debuginfo/strings-and-strs.rs
@@ -31,15 +31,14 @@
 // lldb-check:(&str) plain_str = "Hello" { [0] = 'H' [1] = 'e' [2] = 'l' [3] = 'l' [4] = 'o' }
 
 // lldb-command:v str_in_struct
-// lldb-check:((&str, &str)) str_in_tuple = { 0 = "Hello" { [0] = 'H' [1] = 'e' [2] = 'l' [3] = 'l' [4] = 'o' } 1 = "World" { [0] = 'W' [1] = 'o' [2] = 'r' [3] = 'l' [4] = 'd' } }
+// lldb-check:(strings_and_strs::Foo) str_in_struct = { inner = "Hello" { [0] = 'H' [1] = 'e' [2] = 'l' [3] = 'l' [4] = 'o' } }
 
 // lldb-command:v str_in_tuple
-// lldb-check:((&str, &str)) str_in_tuple = { 0 = "Hello" { [0] = 'H' [1] = 'e' [2] = 'l' [3] = 'l' [4] = 'o' } 1 = "World" { [0] = 'W' [1] = 'o' [2] = 'r' [3] = 'l' [4] = 'd' } }
+// lldb-check:((&str, &str)) str_in_tuple = ("Hello", "World") { 0 = "Hello" { [0] = 'H' [1] = 'e' [2] = 'l' [3] = 'l' [4] = 'o' } 1 = "World" { [0] = 'W' [1] = 'o' [2] = 'r' [3] = 'l' [4] = 'd' } }
 
 // lldb-command:v str_in_rc
 // lldb-check:(alloc::rc::Rc<&str, alloc::alloc::Global>) str_in_rc = strong=1, weak=0 { value = "Hello" { [0] = 'H' [1] = 'e' [2] = 'l' [3] = 'l' [4] = 'o' } }
 
-
 #![allow(unused_variables)]
 
 pub struct Foo<'a> {
diff --git a/tests/debuginfo/struct-in-enum.rs b/tests/debuginfo/struct-in-enum.rs
index bd61e7c..640066b 100644
--- a/tests/debuginfo/struct-in-enum.rs
+++ b/tests/debuginfo/struct-in-enum.rs
@@ -24,12 +24,13 @@
 // lldb-command:run
 
 // lldb-command:v case1
-// lldb-check:[...] Case1(0, Struct { x: 2088533116, y: 2088533116, z: 31868 })
+// lldb-check:[...] Case1(0, {x:2088533116, y:2088533116, z:31868}) { 0 = 0 1 = { x = 2088533116 y = 2088533116 z = 31868 } }
+
 // lldb-command:v case2
-// lldb-check:[...] Case2(0, 1229782938247303441, 4369)
+// lldb-check:[...] Case2(0, 1229782938247303441, 4369) { 0 = 0 1 = 1229782938247303441 2 = 4369 }
 
 // lldb-command:v univariant
-// lldb-check:[...] TheOnlyCase(Struct { x: 123, y: 456, z: 789 })
+// lldb-check:[...] TheOnlyCase({x:123, y:456, z:789}) { 0 = { x = 123 y = 456 z = 789 } }
 
 #![allow(unused_variables)]
 
diff --git a/tests/debuginfo/struct-style-enum.rs b/tests/debuginfo/struct-style-enum.rs
index 48ef978..4bbd879 100644
--- a/tests/debuginfo/struct-style-enum.rs
+++ b/tests/debuginfo/struct-style-enum.rs
@@ -26,16 +26,16 @@
 // lldb-command:run
 
 // lldb-command:v case1
-// lldb-check:(struct_style_enum::Regular) case1 = { value = { a = 0 b = 31868 c = 31868 d = 31868 e = 31868 } $discr$ = 0 }
+// lldb-check:(struct_style_enum::Regular) case1 = Case1{a:0, b:31868, c:31868, d:31868, e:31868} { a = 0 b = 31868 c = 31868 d = 31868 e = 31868 }
 
 // lldb-command:v case2
-// lldb-check:(struct_style_enum::Regular) case2 = { value = { a = 0 b = 286331153 c = 286331153 } $discr$ = 1 }
+// lldb-check:(struct_style_enum::Regular) case2 = Case2{a:0, b:286331153, c:286331153} { a = 0 b = 286331153 c = 286331153 }
 
 // lldb-command:v case3
-// lldb-check:(struct_style_enum::Regular) case3 = { value = { a = 0 b = 6438275382588823897 } $discr$ = 2 }
+// lldb-check:(struct_style_enum::Regular) case3 = Case3{a:0, b:6438275382588823897} { a = 0 b = 6438275382588823897 }
 
 // lldb-command:v univariant
-// lldb-check:(struct_style_enum::Univariant) univariant = { value = { a = -1 } }
+// lldb-check:(struct_style_enum::Univariant) univariant = TheOnlyCase{a:-1} { a = -1 }
 
 #![allow(unused_variables)]
 
diff --git a/tests/debuginfo/tuple-in-tuple.rs b/tests/debuginfo/tuple-in-tuple.rs
index e906cbe..40e88f1 100644
--- a/tests/debuginfo/tuple-in-tuple.rs
+++ b/tests/debuginfo/tuple-in-tuple.rs
@@ -29,21 +29,21 @@
 // lldb-command:run
 
 // lldb-command:v no_padding1
-// lldb-check:[...] { 0 = { 0 = 0 1 = 1 } 1 = 2 2 = 3 }
+// lldb-check:[...] ((0, 1), 2, 3) { 0 = (0, 1) { 0 = 0 1 = 1 } 1 = 2 2 = 3 }
 // lldb-command:v no_padding2
-// lldb-check:[...] { 0 = 4 1 = { 0 = 5 1 = 6 } 2 = 7 }
+// lldb-check:[...] (4, (5, 6), 7) { 0 = 4 1 = (5, 6) { 0 = 5 1 = 6 } 2 = 7 }
 // lldb-command:v no_padding3
-// lldb-check:[...] { 0 = 8 1 = 9 2 = { 0 = 10 1 = 11 } }
+// lldb-check:[...] (8, 9, (10, 11)) { 0 = 8 1 = 9 2 = (10, 11) { 0 = 10 1 = 11 } }
 
 // lldb-command:v internal_padding1
-// lldb-check:[...] { 0 = 12 1 = { 0 = 13 1 = 14 } }
+// lldb-check:[...] (12, (13, 14)) { 0 = 12 1 = (13, 14) { 0 = 13 1 = 14 } }
 // lldb-command:v internal_padding2
-// lldb-check:[...] { 0 = 15 1 = { 0 = 16 1 = 17 } }
+// lldb-check:[...] (15, (16, 17)) { 0 = 15 1 = (16, 17) { 0 = 16 1 = 17 } }
 
 // lldb-command:v padding_at_end1
-// lldb-check:[...] { 0 = 18 1 = { 0 = 19 1 = 20 } }
+// lldb-check:[...] (18, (19, 20)) { 0 = 18 1 = (19, 20) { 0 = 19 1 = 20 } }
 // lldb-command:v padding_at_end2
-// lldb-check:[...] { 0 = { 0 = 21 1 = 22 } 1 = 23 }
+// lldb-check:[...] ((21, 22), 23) { 0 = (21, 22) { 0 = 21 1 = 22 } 1 = 23 }
 
 
 // === CDB TESTS ==================================================================================
diff --git a/tests/debuginfo/tuple-style-enum.rs b/tests/debuginfo/tuple-style-enum.rs
index a452c57..6568a2f 100644
--- a/tests/debuginfo/tuple-style-enum.rs
+++ b/tests/debuginfo/tuple-style-enum.rs
@@ -27,16 +27,16 @@
 // lldb-command:run
 
 // lldb-command:v case1
-// lldb-check:(tuple_style_enum::Regular) case1 = { value = { 0 = 0 1 = 31868 2 = 31868 3 = 31868 4 = 31868 } $discr$ = 0 }
+// lldb-check:(tuple_style_enum::Regular) case1 = Case1(0, 31868, 31868, 31868, 31868) { 0 = 0 1 = 31868 2 = 31868 3 = 31868 4 = 31868 }
 
 // lldb-command:v case2
-// lldb-check:(tuple_style_enum::Regular) case2 = { value = { 0 = 0 1 = 286331153 2 = 286331153 } $discr$ = 1 }
+// lldb-check:(tuple_style_enum::Regular) case2 = Case2(0, 286331153, 286331153) { 0 = 0 1 = 286331153 2 = 286331153 }
 
 // lldb-command:v case3
-// lldb-check:(tuple_style_enum::Regular) case3 = { value = { 0 = 0 1 = 6438275382588823897 } $discr$ = 2 }
+// lldb-check:(tuple_style_enum::Regular) case3 = Case3(0, 6438275382588823897) { 0 = 0 1 = 6438275382588823897 }
 
 // lldb-command:v univariant
-// lldb-check:(tuple_style_enum::Univariant) univariant = { value = { 0 = -1 } }
+// lldb-check:(tuple_style_enum::Univariant) univariant = TheOnlyCase(-1) { 0 = -1 }
 
 #![allow(unused_variables)]
 
diff --git a/tests/debuginfo/union-smoke.rs b/tests/debuginfo/union-smoke.rs
index beaf4c8..be6a9d5 100644
--- a/tests/debuginfo/union-smoke.rs
+++ b/tests/debuginfo/union-smoke.rs
@@ -14,10 +14,10 @@
 
 // lldb-command:run
 // lldb-command:v u
-// lldb-check:[...] { a = { 0 = '\x02' 1 = '\x02' } b = 514 }
+// lldb-check:[...] { a = ('\x02', '\x02') { 0 = '\x02' 1 = '\x02' } b = 514 }
 
 // lldb-command:print union_smoke::SU
-// lldb-check:[...] { a = { 0 = '\x01' 1 = '\x01' } b = 257 }
+// lldb-check:[...] { a = ('\x01', '\x01') { 0 = '\x01' 1 = '\x01' } b = 257 }
 
 #![allow(unused)]
 
diff --git a/tests/debuginfo/unique-enum.rs b/tests/debuginfo/unique-enum.rs
index a9b9284..6a742f9 100644
--- a/tests/debuginfo/unique-enum.rs
+++ b/tests/debuginfo/unique-enum.rs
@@ -23,13 +23,13 @@
 // lldb-command:run
 
 // lldb-command:v *the_a
-// lldb-check:(unique_enum::ABC) *the_a = { value = { x = 0 y = 8970181431921507452 } $discr$ = 0 }
+// lldb-check:(unique_enum::ABC) *the_a = TheA{x:0, y:8970181431921507452} { x = 0 y = 8970181431921507452 }
 
 // lldb-command:v *the_b
-// lldb-check:(unique_enum::ABC) *the_b = { value = { 0 = 0 1 = 286331153 2 = 286331153 } $discr$ = 1 }
+// lldb-check:(unique_enum::ABC) *the_b = TheB(0, 286331153, 286331153) { 0 = 0 1 = 286331153 2 = 286331153 }
 
 // lldb-command:v *univariant
-// lldb-check:(unique_enum::Univariant) *univariant = { value = { 0 = 123234 } }
+// lldb-check:(unique_enum::Univariant) *univariant = TheOnlyCase(123234) { 0 = 123234 }
 
 #![allow(unused_variables)]
 
diff --git a/tests/debuginfo/vec-slices.rs b/tests/debuginfo/vec-slices.rs
index 7ed6b0c..fdfc701 100644
--- a/tests/debuginfo/vec-slices.rs
+++ b/tests/debuginfo/vec-slices.rs
@@ -67,7 +67,7 @@
 // lldb-check:[...] size=2 { [0] = 3 [1] = 4 }
 
 // lldb-command:v padded_tuple
-// lldb-check:[...] size=2 { [0] = { 0 = 6 1 = 7 } [1] = { 0 = 8 1 = 9 } }
+// lldb-check:[...] size=2 { [0] = (6, 7) { 0 = 6 1 = 7 } [1] = (8, 9) { 0 = 8 1 = 9 } }
 
 // lldb-command:v padded_struct
 // lldb-check:[...] size=2 { [0] = { x = 10 y = 11 z = 12 } [1] = { x = 13 y = 14 z = 15 } }
diff --git a/tests/run-make/rustdoc-merge-directory/dep1.rs b/tests/run-make/rustdoc-merge-directory/dep1.rs
new file mode 100644
index 0000000..5a1238a
--- /dev/null
+++ b/tests/run-make/rustdoc-merge-directory/dep1.rs
@@ -0,0 +1,4 @@
+//@ hasraw crates.js 'dep1'
+//@ hasraw search.index/name/*.js 'Dep1'
+//@ has dep1/index.html
+pub struct Dep1;
diff --git a/tests/run-make/rustdoc-merge-directory/dep2.rs b/tests/run-make/rustdoc-merge-directory/dep2.rs
new file mode 100644
index 0000000..238ff2e
--- /dev/null
+++ b/tests/run-make/rustdoc-merge-directory/dep2.rs
@@ -0,0 +1,4 @@
+//@ hasraw crates.js 'dep1'
+//@ hasraw search.index/name/*.js 'Dep1'
+//@ has dep2/index.html
+pub struct Dep2;
diff --git a/tests/run-make/rustdoc-merge-directory/dep_missing.rs b/tests/run-make/rustdoc-merge-directory/dep_missing.rs
new file mode 100644
index 0000000..74236ae
--- /dev/null
+++ b/tests/run-make/rustdoc-merge-directory/dep_missing.rs
@@ -0,0 +1,4 @@
+//@ !hasraw crates.js 'dep_missing'
+//@ !hasraw search.index/name/*.js 'DepMissing'
+//@ has dep_missing/index.html
+pub struct DepMissing;
diff --git a/tests/run-make/rustdoc-merge-directory/rmake.rs b/tests/run-make/rustdoc-merge-directory/rmake.rs
new file mode 100644
index 0000000..e4695dd
--- /dev/null
+++ b/tests/run-make/rustdoc-merge-directory/rmake.rs
@@ -0,0 +1,46 @@
+// Running --merge=finalize without an input crate root should not trigger ICE.
+// Issue: https://github.com/rust-lang/rust/issues/146646
+
+//@ needs-target-std
+
+use run_make_support::{htmldocck, path, rustdoc};
+
+fn main() {
+    let out_dir = path("out");
+    let merged_dir = path("merged");
+    let parts_out_dir = path("parts");
+
+    rustdoc()
+        .input("dep1.rs")
+        .out_dir(&out_dir)
+        .arg("-Zunstable-options")
+        .arg(format!("--parts-out-dir={}", parts_out_dir.display()))
+        .arg("--merge=none")
+        .run();
+    assert!(parts_out_dir.join("dep1.json").exists());
+
+    rustdoc()
+        .input("dep2.rs")
+        .out_dir(&out_dir)
+        .arg("-Zunstable-options")
+        .arg(format!("--parts-out-dir={}", parts_out_dir.display()))
+        .arg("--merge=none")
+        .run();
+    assert!(parts_out_dir.join("dep2.json").exists());
+
+    // dep_missing is different, because --parts-out-dir is not supplied
+    rustdoc().input("dep_missing.rs").out_dir(&out_dir).run();
+    assert!(parts_out_dir.join("dep2.json").exists());
+
+    let output = rustdoc()
+        .arg("-Zunstable-options")
+        .out_dir(&out_dir)
+        .arg(format!("--include-parts-dir={}", parts_out_dir.display()))
+        .arg("--merge=finalize")
+        .run();
+    output.assert_stderr_not_contains("error: the compiler unexpectedly panicked. this is a bug.");
+
+    htmldocck().arg(&out_dir).arg("dep1.rs").run();
+    htmldocck().arg(&out_dir).arg("dep2.rs").run();
+    htmldocck().arg(out_dir).arg("dep_missing.rs").run();
+}
diff --git a/tests/run-make/rustdoc-merge-no-input-finalize/rmake.rs b/tests/run-make/rustdoc-merge-no-input-finalize/rmake.rs
index d750a36..4dad01f 100644
--- a/tests/run-make/rustdoc-merge-no-input-finalize/rmake.rs
+++ b/tests/run-make/rustdoc-merge-no-input-finalize/rmake.rs
@@ -16,7 +16,7 @@ fn main() {
         .arg(format!("--parts-out-dir={}", parts_out_dir.display()))
         .arg("--merge=none")
         .run();
-    assert!(parts_out_dir.join("crate-info").exists());
+    assert!(parts_out_dir.join("sierra.json").exists());
 
     let output = rustdoc()
         .arg("-Zunstable-options")
diff --git a/tests/rustdoc-ui/bad-render-options.rs b/tests/rustdoc-ui/bad-render-options.rs
new file mode 100644
index 0000000..f2cfd4b
--- /dev/null
+++ b/tests/rustdoc-ui/bad-render-options.rs
@@ -0,0 +1,11 @@
+// regression test for https://github.com/rust-lang/rust/issues/149187
+
+#![doc(html_favicon_url)] //~ ERROR: `doc(html_favicon_url)` expects a string value [invalid_doc_attributes]
+#![doc(html_logo_url)] //~ ERROR: `doc(html_logo_url)` expects a string value [invalid_doc_attributes]
+#![doc(html_playground_url)] //~ ERROR: `doc(html_playground_url)` expects a string value [invalid_doc_attributes]
+#![doc(issue_tracker_base_url)] //~ ERROR expects a string value
+#![doc(html_favicon_url = 1)] //~ ERROR expects a string value
+#![doc(html_logo_url = 2)] //~ ERROR expects a string value
+#![doc(html_playground_url = 3)] //~ ERROR expects a string value
+#![doc(issue_tracker_base_url = 4)] //~ ERROR expects a string value
+#![doc(html_no_source = "asdf")] //~ ERROR `doc(html_no_source)` does not accept a value [invalid_doc_attributes]
diff --git a/tests/rustdoc-ui/bad-render-options.stderr b/tests/rustdoc-ui/bad-render-options.stderr
new file mode 100644
index 0000000..9d50336
--- /dev/null
+++ b/tests/rustdoc-ui/bad-render-options.stderr
@@ -0,0 +1,58 @@
+error: `doc(html_favicon_url)` expects a string value
+  --> $DIR/bad-render-options.rs:3:8
+   |
+LL | #![doc(html_favicon_url)]
+   |        ^^^^^^^^^^^^^^^^
+   |
+   = note: `#[deny(invalid_doc_attributes)]` on by default
+
+error: `doc(html_logo_url)` expects a string value
+  --> $DIR/bad-render-options.rs:4:8
+   |
+LL | #![doc(html_logo_url)]
+   |        ^^^^^^^^^^^^^
+
+error: `doc(html_playground_url)` expects a string value
+  --> $DIR/bad-render-options.rs:5:8
+   |
+LL | #![doc(html_playground_url)]
+   |        ^^^^^^^^^^^^^^^^^^^
+
+error: `doc(issue_tracker_base_url)` expects a string value
+  --> $DIR/bad-render-options.rs:6:8
+   |
+LL | #![doc(issue_tracker_base_url)]
+   |        ^^^^^^^^^^^^^^^^^^^^^^
+
+error: `doc(html_favicon_url)` expects a string value
+  --> $DIR/bad-render-options.rs:7:8
+   |
+LL | #![doc(html_favicon_url = 1)]
+   |        ^^^^^^^^^^^^^^^^^^^^
+
+error: `doc(html_logo_url)` expects a string value
+  --> $DIR/bad-render-options.rs:8:8
+   |
+LL | #![doc(html_logo_url = 2)]
+   |        ^^^^^^^^^^^^^^^^^
+
+error: `doc(html_playground_url)` expects a string value
+  --> $DIR/bad-render-options.rs:9:8
+   |
+LL | #![doc(html_playground_url = 3)]
+   |        ^^^^^^^^^^^^^^^^^^^^^^^
+
+error: `doc(issue_tracker_base_url)` expects a string value
+  --> $DIR/bad-render-options.rs:10:8
+   |
+LL | #![doc(issue_tracker_base_url = 4)]
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: `doc(html_no_source)` does not accept a value
+  --> $DIR/bad-render-options.rs:11:8
+   |
+LL | #![doc(html_no_source = "asdf")]
+   |        ^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 9 previous errors
+
diff --git a/tests/ui/dyn-compatibility/no-duplicate-e0038.rs b/tests/ui/dyn-compatibility/no-duplicate-e0038.rs
new file mode 100644
index 0000000..2831645
--- /dev/null
+++ b/tests/ui/dyn-compatibility/no-duplicate-e0038.rs
@@ -0,0 +1,17 @@
+// Test that E0038 is not emitted twice for the same trait object coercion
+// regression test for issue <https://github.com/rust-lang/rust/issues/128705>
+
+#![allow(dead_code)]
+
+trait Tr {
+    const N: usize;
+}
+
+impl Tr for u8 {
+    const N: usize = 1;
+}
+
+fn main() {
+    let x: &dyn Tr = &0_u8;
+    //~^ ERROR E0038
+}
diff --git a/tests/ui/dyn-compatibility/no-duplicate-e0038.stderr b/tests/ui/dyn-compatibility/no-duplicate-e0038.stderr
new file mode 100644
index 0000000..9403738
--- /dev/null
+++ b/tests/ui/dyn-compatibility/no-duplicate-e0038.stderr
@@ -0,0 +1,20 @@
+error[E0038]: the trait `Tr` is not dyn compatible
+  --> $DIR/no-duplicate-e0038.rs:15:17
+   |
+LL |     let x: &dyn Tr = &0_u8;
+   |                 ^^ `Tr` is not dyn compatible
+   |
+note: for a trait to be dyn compatible it needs to allow building a vtable
+      for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
+  --> $DIR/no-duplicate-e0038.rs:7:11
+   |
+LL | trait Tr {
+   |       -- this trait is not dyn compatible...
+LL |     const N: usize;
+   |           ^ ...because it contains this associated `const`
+   = help: consider moving `N` to another trait
+   = help: only type `u8` implements `Tr`; consider using it directly instead.
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0038`.
diff --git a/tests/ui/parser/ascii-only-character-escape.stderr b/tests/ui/parser/ascii-only-character-escape.stderr
index b599b35..78844ae 100644
--- a/tests/ui/parser/ascii-only-character-escape.stderr
+++ b/tests/ui/parser/ascii-only-character-escape.stderr
@@ -3,18 +3,27 @@
    |
 LL |     let x = "\x80";
    |              ^^^^ must be a character in the range [\x00-\x7f]
+   |
+   = help: if you want to write a byte literal, use `b'\x80'`
+   = help: if you want to write a Unicode character, use `'\u{80}'`
 
 error: out of range hex escape
   --> $DIR/ascii-only-character-escape.rs:3:14
    |
 LL |     let y = "\xff";
    |              ^^^^ must be a character in the range [\x00-\x7f]
+   |
+   = help: if you want to write a byte literal, use `b'\xff'`
+   = help: if you want to write a Unicode character, use `'\u{FF}'`
 
 error: out of range hex escape
   --> $DIR/ascii-only-character-escape.rs:4:14
    |
 LL |     let z = "\xe2";
    |              ^^^^ must be a character in the range [\x00-\x7f]
+   |
+   = help: if you want to write a byte literal, use `b'\xe2'`
+   = help: if you want to write a Unicode character, use `'\u{E2}'`
 
 error: aborting due to 3 previous errors
 
diff --git a/tests/ui/parser/out-of-range-hex-escape-suggestions-148917.rs b/tests/ui/parser/out-of-range-hex-escape-suggestions-148917.rs
new file mode 100644
index 0000000..ee8489f
--- /dev/null
+++ b/tests/ui/parser/out-of-range-hex-escape-suggestions-148917.rs
@@ -0,0 +1,21 @@
+fn main() {
+    let _c = '\xFF'; //~ ERROR out of range hex escape
+    let _s = "\xFF"; //~ ERROR out of range hex escape
+
+    let _c2 = '\xff'; //~ ERROR out of range hex escape
+    let _s2 = "\xff"; //~ ERROR out of range hex escape
+
+    let _c3 = '\x80'; //~ ERROR out of range hex escape
+    let _s3 = "\x80"; //~ ERROR out of range hex escape
+
+    // Byte literals should not get suggestions (they're already valid)
+    let _b = b'\xFF'; // OK
+    let _bs = b"\xFF"; // OK
+
+    dbg!('\xFF'); //~ ERROR out of range hex escape
+
+    // do not suggest for out of range escapes that are too long
+    dbg!("\xFFFFF"); //~ ERROR out of range hex escape
+
+    dbg!("this is some kind of string \xa7"); //~ ERROR out of range hex escape
+}
diff --git a/tests/ui/parser/out-of-range-hex-escape-suggestions-148917.stderr b/tests/ui/parser/out-of-range-hex-escape-suggestions-148917.stderr
new file mode 100644
index 0000000..e0485bf
--- /dev/null
+++ b/tests/ui/parser/out-of-range-hex-escape-suggestions-148917.stderr
@@ -0,0 +1,77 @@
+error: out of range hex escape
+  --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:2:15
+   |
+LL |     let _c = '\xFF';
+   |               ^^^^ must be a character in the range [\x00-\x7f]
+   |
+   = help: if you want to write a byte literal, use `b'\xFF'`
+   = help: if you want to write a Unicode character, use `'\u{FF}'`
+
+error: out of range hex escape
+  --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:3:15
+   |
+LL |     let _s = "\xFF";
+   |               ^^^^ must be a character in the range [\x00-\x7f]
+   |
+   = help: if you want to write a byte literal, use `b'\xFF'`
+   = help: if you want to write a Unicode character, use `'\u{FF}'`
+
+error: out of range hex escape
+  --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:5:16
+   |
+LL |     let _c2 = '\xff';
+   |                ^^^^ must be a character in the range [\x00-\x7f]
+   |
+   = help: if you want to write a byte literal, use `b'\xff'`
+   = help: if you want to write a Unicode character, use `'\u{FF}'`
+
+error: out of range hex escape
+  --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:6:16
+   |
+LL |     let _s2 = "\xff";
+   |                ^^^^ must be a character in the range [\x00-\x7f]
+   |
+   = help: if you want to write a byte literal, use `b'\xff'`
+   = help: if you want to write a Unicode character, use `'\u{FF}'`
+
+error: out of range hex escape
+  --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:8:16
+   |
+LL |     let _c3 = '\x80';
+   |                ^^^^ must be a character in the range [\x00-\x7f]
+   |
+   = help: if you want to write a byte literal, use `b'\x80'`
+   = help: if you want to write a Unicode character, use `'\u{80}'`
+
+error: out of range hex escape
+  --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:9:16
+   |
+LL |     let _s3 = "\x80";
+   |                ^^^^ must be a character in the range [\x00-\x7f]
+   |
+   = help: if you want to write a byte literal, use `b'\x80'`
+   = help: if you want to write a Unicode character, use `'\u{80}'`
+
+error: out of range hex escape
+  --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:15:11
+   |
+LL |     dbg!('\xFF');
+   |           ^^^^ must be a character in the range [\x00-\x7f]
+   |
+   = help: if you want to write a byte literal, use `b'\xFF'`
+   = help: if you want to write a Unicode character, use `'\u{FF}'`
+
+error: out of range hex escape
+  --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:18:11
+   |
+LL |     dbg!("\xFFFFF");
+   |           ^^^^ must be a character in the range [\x00-\x7f]
+
+error: out of range hex escape
+  --> $DIR/out-of-range-hex-escape-suggestions-148917.rs:20:39
+   |
+LL |     dbg!("this is some kind of string \xa7");
+   |                                       ^^^^ must be a character in the range [\x00-\x7f]
+
+error: aborting due to 9 previous errors
+
diff --git a/tests/ui/traits/dispatch-from-dyn-blanket-impl.rs b/tests/ui/traits/dispatch-from-dyn-blanket-impl.rs
new file mode 100644
index 0000000..4e0e7ca
--- /dev/null
+++ b/tests/ui/traits/dispatch-from-dyn-blanket-impl.rs
@@ -0,0 +1,10 @@
+// Test that blanket impl of DispatchFromDyn is rejected.
+// regression test for issue <https://github.com/rust-lang/rust/issues/148062>
+
+#![feature(dispatch_from_dyn)]
+
+impl<T> std::ops::DispatchFromDyn<T> for T {}
+//~^ ERROR type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct<T>`)
+//~| ERROR the trait `DispatchFromDyn` may only be implemented for a coercion between structures
+
+fn main() {}
diff --git a/tests/ui/traits/dispatch-from-dyn-blanket-impl.stderr b/tests/ui/traits/dispatch-from-dyn-blanket-impl.stderr
new file mode 100644
index 0000000..69f3608
--- /dev/null
+++ b/tests/ui/traits/dispatch-from-dyn-blanket-impl.stderr
@@ -0,0 +1,19 @@
+error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct<T>`)
+  --> $DIR/dispatch-from-dyn-blanket-impl.rs:6:6
+   |
+LL | impl<T> std::ops::DispatchFromDyn<T> for T {}
+   |      ^ type parameter `T` must be used as the type parameter for some local type
+   |
+   = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local
+   = note: only traits defined in the current crate can be implemented for a type parameter
+
+error[E0377]: the trait `DispatchFromDyn` may only be implemented for a coercion between structures
+  --> $DIR/dispatch-from-dyn-blanket-impl.rs:6:1
+   |
+LL | impl<T> std::ops::DispatchFromDyn<T> for T {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 2 previous errors
+
+Some errors have detailed explanations: E0210, E0377.
+For more information about an error, try `rustc --explain E0210`.