]> git.proxmox.com Git - rustc.git/blobdiff - src/librustdoc/core.rs
Merge tag 'debian/1.52.1+dfsg1-1_exp2' into proxmox/buster
[rustc.git] / src / librustdoc / core.rs
index a20e9dec33b36f188fb0c83dec69bc11595468c8..c5b5ab0f3d0ac4a72fd48a9b73fad87f7fc347a0 100644 (file)
@@ -22,54 +22,53 @@ use rustc_session::DiagnosticOutput;
 use rustc_session::Session;
 use rustc_span::source_map;
 use rustc_span::symbol::sym;
-use rustc_span::DUMMY_SP;
+use rustc_span::{Span, DUMMY_SP};
 
+use std::cell::RefCell;
+use std::collections::hash_map::Entry;
 use std::mem;
 use std::rc::Rc;
-use std::{
-    cell::{Cell, RefCell},
-    collections::hash_map::Entry,
-};
 
 use crate::clean;
-use crate::clean::{AttributesExt, MAX_DEF_IDX};
-use crate::config::{Options as RustdocOptions, RenderOptions};
-use crate::config::{OutputFormat, RenderInfo};
+use crate::clean::inline::build_external_trait;
+use crate::clean::{AttributesExt, TraitWithExtraInfo, MAX_DEF_IDX};
+use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions};
 use crate::formats::cache::Cache;
 use crate::passes::{self, Condition::*, ConditionalPass};
 
 crate use rustc_session::config::{DebuggingOptions, Input, Options};
 
-crate type ExternalPaths = FxHashMap<DefId, (Vec<String>, clean::TypeKind)>;
-
 crate struct DocContext<'tcx> {
     crate tcx: TyCtxt<'tcx>,
+    /// Name resolver. Used for intra-doc links.
+    ///
+    /// The `Rc<RefCell<...>>` wrapping is needed because that is what's returned by
+    /// [`Queries::expansion()`].
+    // FIXME: see if we can get rid of this RefCell somehow
     crate resolver: Rc<RefCell<interface::BoxedResolver>>,
     /// Used for normalization.
     ///
     /// Most of this logic is copied from rustc_lint::late.
-    crate param_env: Cell<ParamEnv<'tcx>>,
-    /// Later on moved into `cache`
-    crate renderinfo: RefCell<RenderInfo>,
+    crate param_env: ParamEnv<'tcx>,
     /// Later on moved through `clean::Crate` into `cache`
-    crate external_traits: Rc<RefCell<FxHashMap<DefId, clean::Trait>>>,
+    crate external_traits: Rc<RefCell<FxHashMap<DefId, clean::TraitWithExtraInfo>>>,
     /// Used while populating `external_traits` to ensure we don't process the same trait twice at
     /// the same time.
-    crate active_extern_traits: RefCell<FxHashSet<DefId>>,
+    crate active_extern_traits: FxHashSet<DefId>,
     // The current set of type and lifetime substitutions,
     // for expanding type aliases at the HIR level:
     /// Table `DefId` of type parameter -> substituted type
-    crate ty_substs: RefCell<FxHashMap<DefId, clean::Type>>,
+    crate ty_substs: FxHashMap<DefId, clean::Type>,
     /// Table `DefId` of lifetime parameter -> substituted lifetime
-    crate lt_substs: RefCell<FxHashMap<DefId, clean::Lifetime>>,
+    crate lt_substs: FxHashMap<DefId, clean::Lifetime>,
     /// Table `DefId` of const parameter -> substituted const
-    crate ct_substs: RefCell<FxHashMap<DefId, clean::Constant>>,
+    crate ct_substs: FxHashMap<DefId, clean::Constant>,
     /// Table synthetic type parameter for `impl Trait` in argument position -> bounds
-    crate impl_trait_bounds: RefCell<FxHashMap<ImplTraitParam, Vec<clean::GenericBound>>>,
-    crate fake_def_ids: RefCell<FxHashMap<CrateNum, DefIndex>>,
+    crate impl_trait_bounds: FxHashMap<ImplTraitParam, Vec<clean::GenericBound>>,
+    crate fake_def_ids: FxHashMap<CrateNum, DefIndex>,
     /// Auto-trait or blanket impls processed so far, as `(self_ty, trait_def_id)`.
     // FIXME(eddyb) make this a `ty::TraitRef<'tcx>` set.
-    crate generated_synthetics: RefCell<FxHashSet<(Ty<'tcx>, DefId)>>,
+    crate generated_synthetics: FxHashSet<(Ty<'tcx>, DefId)>,
     crate auto_traits: Vec<DefId>,
     /// The options given to rustdoc that could be relevant to a pass.
     crate render_options: RenderOptions,
@@ -77,20 +76,24 @@ crate struct DocContext<'tcx> {
     ///
     /// See `collect_intra_doc_links::traits_implemented_by` for more details.
     /// `map<module, set<trait>>`
-    crate module_trait_cache: RefCell<FxHashMap<DefId, FxHashSet<DefId>>>,
-    /// Fake empty cache used when cache is required as parameter.
+    crate module_trait_cache: FxHashMap<DefId, FxHashSet<DefId>>,
+    /// This same cache is used throughout rustdoc, including in [`crate::html::render`].
     crate cache: Cache,
+    /// Used by [`clean::inline`] to tell if an item has already been inlined.
+    crate inlined: FxHashSet<DefId>,
+    /// Used by `calculate_doc_coverage`.
+    crate output_format: OutputFormat,
 }
 
 impl<'tcx> DocContext<'tcx> {
-    crate fn sess(&self) -> &Session {
+    crate fn sess(&self) -> &'tcx Session {
         &self.tcx.sess
     }
 
-    crate fn with_param_env<T, F: FnOnce() -> T>(&self, def_id: DefId, f: F) -> T {
-        let old_param_env = self.param_env.replace(self.tcx.param_env(def_id));
-        let ret = f();
-        self.param_env.set(old_param_env);
+    crate fn with_param_env<T, F: FnOnce(&mut Self) -> T>(&mut self, def_id: DefId, f: F) -> T {
+        let old_param_env = mem::replace(&mut self.param_env, self.tcx.param_env(def_id));
+        let ret = f(self);
+        self.param_env = old_param_env;
         ret
     }
 
@@ -104,24 +107,24 @@ impl<'tcx> DocContext<'tcx> {
     /// Call the closure with the given parameters set as
     /// the substitutions for a type alias' RHS.
     crate fn enter_alias<F, R>(
-        &self,
+        &mut self,
         ty_substs: FxHashMap<DefId, clean::Type>,
         lt_substs: FxHashMap<DefId, clean::Lifetime>,
         ct_substs: FxHashMap<DefId, clean::Constant>,
         f: F,
     ) -> R
     where
-        F: FnOnce() -> R,
+        F: FnOnce(&mut Self) -> R,
     {
         let (old_tys, old_lts, old_cts) = (
-            mem::replace(&mut *self.ty_substs.borrow_mut(), ty_substs),
-            mem::replace(&mut *self.lt_substs.borrow_mut(), lt_substs),
-            mem::replace(&mut *self.ct_substs.borrow_mut(), ct_substs),
+            mem::replace(&mut self.ty_substs, ty_substs),
+            mem::replace(&mut self.lt_substs, lt_substs),
+            mem::replace(&mut self.ct_substs, ct_substs),
         );
-        let r = f();
-        *self.ty_substs.borrow_mut() = old_tys;
-        *self.lt_substs.borrow_mut() = old_lts;
-        *self.ct_substs.borrow_mut() = old_cts;
+        let r = f(self);
+        self.ty_substs = old_tys;
+        self.lt_substs = old_lts;
+        self.ct_substs = old_cts;
         r
     }
 
@@ -139,16 +142,14 @@ impl<'tcx> DocContext<'tcx> {
     /// [`RefCell`]: std::cell::RefCell
     /// [`Debug`]: std::fmt::Debug
     /// [`clean::Item`]: crate::clean::types::Item
-    crate fn next_def_id(&self, crate_num: CrateNum) -> DefId {
-        let mut fake_ids = self.fake_def_ids.borrow_mut();
-
-        let def_index = match fake_ids.entry(crate_num) {
+    crate fn next_def_id(&mut self, crate_num: CrateNum) -> DefId {
+        let def_index = match self.fake_def_ids.entry(crate_num) {
             Entry::Vacant(e) => {
                 let num_def_idx = {
                     let num_def_idx = if crate_num == LOCAL_CRATE {
                         self.tcx.hir().definitions().def_path_table().num_def_ids()
                     } else {
-                        self.enter_resolver(|r| r.cstore().num_def_ids(crate_num))
+                        self.resolver.borrow_mut().access(|r| r.cstore().num_def_ids(crate_num))
                     };
 
                     DefIndex::from_usize(num_def_idx)
@@ -161,20 +162,20 @@ impl<'tcx> DocContext<'tcx> {
             }
             Entry::Occupied(e) => e.into_mut(),
         };
-        *def_index = DefIndex::from(*def_index + 1);
+        *def_index = *def_index + 1;
 
         DefId { krate: crate_num, index: *def_index }
     }
 
     /// Like `hir().local_def_id_to_hir_id()`, but skips calling it on fake DefIds.
     /// (This avoids a slice-index-out-of-bounds panic.)
-    crate fn as_local_hir_id(&self, def_id: DefId) -> Option<HirId> {
+    crate fn as_local_hir_id(tcx: TyCtxt<'_>, def_id: DefId) -> Option<HirId> {
         if MAX_DEF_IDX.with(|m| {
             m.borrow().get(&def_id.krate).map(|&idx| idx <= def_id.index).unwrap_or(false)
         }) {
             None
         } else {
-            def_id.as_local().map(|def_id| self.tcx.hir().local_def_id_to_hir_id(def_id))
+            def_id.as_local().map(|def_id| tcx.hir().local_def_id_to_hir_id(def_id))
         }
     }
 }
@@ -227,64 +228,6 @@ crate fn new_handler(
     )
 }
 
-/// This function is used to setup the lint initialization. By default, in rustdoc, everything
-/// is "allowed". Depending if we run in test mode or not, we want some of them to be at their
-/// default level. For example, the "INVALID_CODEBLOCK_ATTRIBUTES" lint is activated in both
-/// modes.
-///
-/// A little detail easy to forget is that there is a way to set the lint level for all lints
-/// through the "WARNINGS" lint. To prevent this to happen, we set it back to its "normal" level
-/// inside this function.
-///
-/// It returns a tuple containing:
-///  * Vector of tuples of lints' name and their associated "max" level
-///  * HashMap of lint id with their associated "max" level
-pub(crate) fn init_lints<F>(
-    mut allowed_lints: Vec<String>,
-    lint_opts: Vec<(String, lint::Level)>,
-    filter_call: F,
-) -> (Vec<(String, lint::Level)>, FxHashMap<lint::LintId, lint::Level>)
-where
-    F: Fn(&lint::Lint) -> Option<(String, lint::Level)>,
-{
-    let warnings_lint_name = lint::builtin::WARNINGS.name;
-
-    allowed_lints.push(warnings_lint_name.to_owned());
-    allowed_lints.extend(lint_opts.iter().map(|(lint, _)| lint).cloned());
-
-    let lints = || {
-        lint::builtin::HardwiredLints::get_lints()
-            .into_iter()
-            .chain(rustc_lint::SoftLints::get_lints().into_iter())
-    };
-
-    let lint_opts = lints()
-        .filter_map(|lint| {
-            // Permit feature-gated lints to avoid feature errors when trying to
-            // allow all lints.
-            if lint.feature_gate.is_some() || allowed_lints.iter().any(|l| lint.name == l) {
-                None
-            } else {
-                filter_call(lint)
-            }
-        })
-        .chain(lint_opts.into_iter())
-        .collect::<Vec<_>>();
-
-    let lint_caps = lints()
-        .filter_map(|lint| {
-            // We don't want to allow *all* lints so let's ignore
-            // those ones.
-            if allowed_lints.iter().any(|l| lint.name == l) {
-                None
-            } else {
-                Some((lint::LintId::of(lint), lint::Allow))
-            }
-        })
-        .collect();
-    (lint_opts, lint_caps)
-}
-
 /// Parse, resolve, and typecheck the given crate.
 crate fn create_config(
     RustdocOptions {
@@ -313,37 +256,22 @@ crate fn create_config(
     let cpath = Some(input.clone());
     let input = Input::File(input);
 
-    let broken_intra_doc_links = lint::builtin::BROKEN_INTRA_DOC_LINKS.name;
-    let private_intra_doc_links = lint::builtin::PRIVATE_INTRA_DOC_LINKS.name;
-    let missing_docs = rustc_lint::builtin::MISSING_DOCS.name;
-    let missing_doc_example = rustc_lint::builtin::MISSING_DOC_CODE_EXAMPLES.name;
-    let private_doc_tests = rustc_lint::builtin::PRIVATE_DOC_TESTS.name;
-    let no_crate_level_docs = rustc_lint::builtin::MISSING_CRATE_LEVEL_DOCS.name;
-    let invalid_codeblock_attributes_name = rustc_lint::builtin::INVALID_CODEBLOCK_ATTRIBUTES.name;
-    let invalid_html_tags = rustc_lint::builtin::INVALID_HTML_TAGS.name;
-    let renamed_and_removed_lints = rustc_lint::builtin::RENAMED_AND_REMOVED_LINTS.name;
-    let non_autolinks = rustc_lint::builtin::NON_AUTOLINKS.name;
-    let unknown_lints = rustc_lint::builtin::UNKNOWN_LINTS.name;
-
-    // In addition to those specific lints, we also need to allow those given through
-    // command line, otherwise they'll get ignored and we don't want that.
-    let lints_to_show = vec![
-        broken_intra_doc_links.to_owned(),
-        private_intra_doc_links.to_owned(),
-        missing_docs.to_owned(),
-        missing_doc_example.to_owned(),
-        private_doc_tests.to_owned(),
-        no_crate_level_docs.to_owned(),
-        invalid_codeblock_attributes_name.to_owned(),
-        invalid_html_tags.to_owned(),
-        renamed_and_removed_lints.to_owned(),
-        unknown_lints.to_owned(),
-        non_autolinks.to_owned(),
+    // By default, rustdoc ignores all lints.
+    // Specifically unblock lints relevant to documentation or the lint machinery itself.
+    let mut lints_to_show = vec![
+        // it's unclear whether this should be part of rustdoc directly (#77364)
+        rustc_lint::builtin::MISSING_DOCS.name.to_string(),
+        // these are definitely not part of rustdoc, but we want to warn on them anyway.
+        rustc_lint::builtin::RENAMED_AND_REMOVED_LINTS.name.to_string(),
+        rustc_lint::builtin::UNKNOWN_LINTS.name.to_string(),
     ];
+    lints_to_show.extend(crate::lint::RUSTDOC_LINTS.iter().map(|lint| lint.name.to_string()));
 
-    let (lint_opts, lint_caps) = init_lints(lints_to_show, lint_opts, |lint| {
+    let (lint_opts, lint_caps) = crate::lint::init_lints(lints_to_show, lint_opts, |lint| {
         // FIXME: why is this necessary?
-        if lint.name == broken_intra_doc_links || lint.name == invalid_codeblock_attributes_name {
+        if lint.name == crate::lint::BROKEN_INTRA_DOC_LINKS.name
+            || lint.name == crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name
+        {
             None
         } else {
             Some((lint.name_lower(), lint::Allow))
@@ -383,7 +311,8 @@ crate fn create_config(
         diagnostic_output: DiagnosticOutput::Default,
         stderr: None,
         lint_caps,
-        register_lints: None,
+        parse_sess_created: None,
+        register_lints: Some(box crate::lint::register_lints),
         override_queries: Some(|_sess, providers, _external_providers| {
             // Most lints will require typechecking, so just don't run them.
             providers.lint_mod = |_, _| {};
@@ -461,10 +390,10 @@ crate fn run_global_ctxt(
     tcx: TyCtxt<'_>,
     resolver: Rc<RefCell<interface::BoxedResolver>>,
     mut default_passes: passes::DefaultPassOption,
-    mut manual_passes: Vec<String>,
+    manual_passes: Vec<String>,
     render_options: RenderOptions,
     output_format: OutputFormat,
-) -> (clean::Crate, RenderInfo, RenderOptions) {
+) -> (clean::Crate, RenderOptions, Cache) {
     // Certain queries assume that some checks were run elsewhere
     // (see https://github.com/rust-lang/rust/pull/73566#issuecomment-656954425),
     // so type-check everything other than function bodies in this crate before running lints.
@@ -479,7 +408,7 @@ crate fn run_global_ctxt(
     // NOTE: This is copy/pasted from typeck/lib.rs and should be kept in sync with those changes.
     tcx.sess.time("item_types_checking", || {
         for &module in tcx.hir().krate().modules.keys() {
-            tcx.ensure().check_mod_item_types(tcx.hir().local_def_id(module));
+            tcx.ensure().check_mod_item_types(module);
         }
     });
     tcx.sess.abort_if_errors();
@@ -488,10 +417,10 @@ crate fn run_global_ctxt(
     });
     tcx.sess.time("check_mod_attrs", || {
         for &module in tcx.hir().krate().modules.keys() {
-            let local_def_id = tcx.hir().local_def_id(module);
-            tcx.ensure().check_mod_attrs(local_def_id);
+            tcx.ensure().check_mod_attrs(module);
         }
     });
+    rustc_passes::stability::check_unused_or_stable_features(tcx);
 
     let access_levels = tcx.privacy_access_levels(LOCAL_CRATE);
     // Convert from a HirId set to a DefId set since we don't always have easy access
@@ -504,17 +433,12 @@ crate fn run_global_ctxt(
             .collect(),
     };
 
-    let mut renderinfo = RenderInfo::default();
-    renderinfo.access_levels = access_levels;
-    renderinfo.output_format = output_format;
-
     let mut ctxt = DocContext {
         tcx,
         resolver,
-        param_env: Cell::new(ParamEnv::empty()),
+        param_env: ParamEnv::empty(),
         external_traits: Default::default(),
         active_extern_traits: Default::default(),
-        renderinfo: RefCell::new(renderinfo),
         ty_substs: Default::default(),
         lt_substs: Default::default(),
         ct_substs: Default::default(),
@@ -527,10 +451,25 @@ crate fn run_global_ctxt(
             .cloned()
             .filter(|trait_def_id| tcx.trait_is_auto(*trait_def_id))
             .collect(),
+        module_trait_cache: FxHashMap::default(),
+        cache: Cache::new(access_levels, render_options.document_private),
+        inlined: FxHashSet::default(),
+        output_format,
         render_options,
-        module_trait_cache: RefCell::new(FxHashMap::default()),
-        cache: Cache::default(),
     };
+
+    // Small hack to force the Sized trait to be present.
+    //
+    // Note that in case of `#![no_core]`, the trait is not available.
+    if let Some(sized_trait_did) = ctxt.tcx.lang_items().sized_trait() {
+        let mut sized_trait = build_external_trait(&mut ctxt, sized_trait_did);
+        sized_trait.is_auto = true;
+        ctxt.external_traits.borrow_mut().insert(
+            sized_trait_did,
+            TraitWithExtraInfo { trait_: sized_trait, is_spotlight: false },
+        );
+    }
+
     debug!("crate: {:?}", tcx.hir().krate());
 
     let mut krate = tcx.sess.time("clean_crate", || clean::krate(&mut ctxt));
@@ -540,8 +479,8 @@ crate fn run_global_ctxt(
             let help = "The following guide may be of use:\n\
                 https://doc.rust-lang.org/nightly/rustdoc/how-to-write-documentation.html";
             tcx.struct_lint_node(
-                rustc_lint::builtin::MISSING_CRATE_LEVEL_DOCS,
-                ctxt.as_local_hir_id(m.def_id).unwrap(),
+                crate::lint::MISSING_CRATE_LEVEL_DOCS,
+                DocContext::as_local_hir_id(tcx, m.def_id).unwrap(),
                 |lint| {
                     let mut diag =
                         lint.build("no documentation found for this crate's top-level module");
@@ -552,21 +491,44 @@ crate fn run_global_ctxt(
         }
     }
 
-    fn report_deprecated_attr(name: &str, diag: &rustc_errors::Handler) {
-        let mut msg = diag
-            .struct_warn(&format!("the `#![doc({})]` attribute is considered deprecated", name));
-        msg.warn(
+    fn report_deprecated_attr(name: &str, diag: &rustc_errors::Handler, sp: Span) {
+        let mut msg =
+            diag.struct_span_warn(sp, &format!("the `#![doc({})]` attribute is deprecated", name));
+        msg.note(
             "see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \
              for more information",
         );
 
         if name == "no_default_passes" {
             msg.help("you may want to use `#![doc(document_private_items)]`");
+        } else if name.starts_with("plugins") {
+            msg.warn("`#![doc(plugins = \"...\")]` no longer functions; see CVE-2018-1000622 <https://nvd.nist.gov/vuln/detail/CVE-2018-1000622>");
         }
 
         msg.emit();
     }
 
+    let parse_pass = |name: &str, sp: Option<Span>| {
+        if let Some(pass) = passes::find_pass(name) {
+            Some(ConditionalPass::always(pass))
+        } else {
+            let msg = &format!("ignoring unknown pass `{}`", name);
+            let mut warning = if let Some(sp) = sp {
+                tcx.sess.struct_span_warn(sp, msg)
+            } else {
+                tcx.sess.struct_warn(msg)
+            };
+            if name == "collapse-docs" {
+                warning.note("the `collapse-docs` pass was removed in #80261 <https://github.com/rust-lang/rust/pull/80261>");
+            }
+            warning.emit();
+            None
+        }
+    };
+
+    let mut manual_passes: Vec<_> =
+        manual_passes.into_iter().flat_map(|name| parse_pass(&name, None)).collect();
+
     // Process all of the crate attributes, extracting plugin metadata along
     // with the passes which we are supposed to run.
     for attr in krate.module.as_ref().unwrap().attrs.lists(sym::doc) {
@@ -575,29 +537,25 @@ crate fn run_global_ctxt(
         let name = attr.name_or_empty();
         if attr.is_word() {
             if name == sym::no_default_passes {
-                report_deprecated_attr("no_default_passes", diag);
+                report_deprecated_attr("no_default_passes", diag, attr.span());
                 if default_passes == passes::DefaultPassOption::Default {
                     default_passes = passes::DefaultPassOption::None;
                 }
             }
         } else if let Some(value) = attr.value_str() {
-            let sink = match name {
+            match name {
                 sym::passes => {
-                    report_deprecated_attr("passes = \"...\"", diag);
-                    &mut manual_passes
+                    report_deprecated_attr("passes = \"...\"", diag, attr.span());
                 }
                 sym::plugins => {
-                    report_deprecated_attr("plugins = \"...\"", diag);
-                    eprintln!(
-                        "WARNING: `#![doc(plugins = \"...\")]` \
-                         no longer functions; see CVE-2018-1000622"
-                    );
+                    report_deprecated_attr("plugins = \"...\"", diag, attr.span());
                     continue;
                 }
                 _ => continue,
             };
             for name in value.as_str().split_whitespace() {
-                sink.push(name.to_string());
+                let span = attr.name_value_literal_span().unwrap_or(attr.span());
+                manual_passes.extend(parse_pass(name, Some(span)));
             }
         }
 
@@ -606,17 +564,7 @@ crate fn run_global_ctxt(
         }
     }
 
-    let passes = passes::defaults(default_passes).iter().copied().chain(
-        manual_passes.into_iter().flat_map(|name| {
-            if let Some(pass) = passes::find_pass(&name) {
-                Some(ConditionalPass::always(pass))
-            } else {
-                error!("unknown pass {}, skipping", name);
-                None
-            }
-        }),
-    );
-
+    let passes = passes::defaults(default_passes).iter().copied().chain(manual_passes);
     info!("Executing passes");
 
     for p in passes {
@@ -628,16 +576,22 @@ crate fn run_global_ctxt(
         };
         if run {
             debug!("running pass {}", p.pass.name);
-            krate = ctxt.tcx.sess.time(p.pass.name, || (p.pass.run)(krate, &ctxt));
+            krate = ctxt.tcx.sess.time(p.pass.name, || (p.pass.run)(krate, &mut ctxt));
         }
     }
 
     ctxt.sess().abort_if_errors();
 
+    let render_options = ctxt.render_options;
+    let mut cache = ctxt.cache;
+    krate = tcx.sess.time("create_format_cache", || {
+        cache.populate(krate, tcx, &render_options.extern_html_root_urls, &render_options.output)
+    });
+
     // The main crate doc comments are always collapsed.
     krate.collapsed = true;
 
-    (krate, ctxt.renderinfo.into_inner(), ctxt.render_options)
+    (krate, render_options, cache)
 }
 
 /// Due to <https://github.com/rust-lang/rust/pull/73566>,