From 73fc00ff8c497af386cce222fe467371dc335885 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 14 Oct 2024 17:51:08 +0200 Subject: [PATCH 01/16] Delay ambiguous intra-doc link resolution after `Cache` has been populated --- src/librustdoc/clean/types.rs | 13 +- src/librustdoc/core.rs | 24 +- .../passes/calculate_doc_coverage.rs | 2 +- .../passes/check_doc_test_visibility.rs | 2 +- .../passes/collect_intra_doc_links.rs | 260 +++++++++++++++--- src/librustdoc/passes/collect_trait_impls.rs | 2 +- src/librustdoc/passes/lint.rs | 2 +- src/librustdoc/passes/mod.rs | 2 +- src/librustdoc/passes/propagate_doc_cfg.rs | 2 +- src/librustdoc/passes/propagate_stability.rs | 2 +- .../passes/strip_aliased_non_local.rs | 2 +- src/librustdoc/passes/strip_hidden.rs | 2 +- src/librustdoc/passes/strip_priv_imports.rs | 2 +- src/librustdoc/passes/strip_private.rs | 2 +- 14 files changed, 263 insertions(+), 56 deletions(-) diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index bc5bf4c05838..44d9a5fdbe8a 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1228,15 +1228,14 @@ impl Attributes { for attr in self.other_attrs.lists(sym::doc).filter(|a| a.has_name(sym::alias)) { if let Some(values) = attr.meta_item_list() { for l in values { - match l.lit().unwrap().kind { - ast::LitKind::Str(s, _) => { - aliases.insert(s); - } - _ => unreachable!(), + if let Some(lit) = l.lit() + && let ast::LitKind::Str(s, _) = lit.kind + { + aliases.insert(s); } } - } else { - aliases.insert(attr.value_str().unwrap()); + } else if let Some(value) = attr.value_str() { + aliases.insert(value); } } aliases.into_iter().collect::>().into() diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index aaf4c80f9976..9bebe1fb4c1d 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -29,8 +29,9 @@ use crate::clean::inline::build_external_trait; use crate::clean::{self, ItemId}; use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions}; use crate::formats::cache::Cache; +use crate::passes; use crate::passes::Condition::*; -use crate::passes::{self}; +use crate::passes::collect_intra_doc_links::LinkCollector; pub(crate) struct DocContext<'tcx> { pub(crate) tcx: TyCtxt<'tcx>, @@ -427,6 +428,9 @@ pub(crate) fn run_global_ctxt( info!("Executing passes"); + let mut visited = FxHashMap::default(); + let mut ambiguous = FxIndexMap::default(); + for p in passes::defaults(show_coverage) { let run = match p.condition { Always => true, @@ -436,18 +440,30 @@ pub(crate) fn run_global_ctxt( }; if run { debug!("running pass {}", p.pass.name); - krate = tcx.sess.time(p.pass.name, || (p.pass.run)(krate, &mut ctxt)); + if let Some(run_fn) = p.pass.run { + krate = tcx.sess.time(p.pass.name, || run_fn(krate, &mut ctxt)); + } else { + let (k, LinkCollector { visited_links, ambiguous_links, .. }) = + passes::collect_intra_doc_links::collect_intra_doc_links(krate, &mut ctxt); + krate = k; + visited = visited_links; + ambiguous = ambiguous_links; + } } } tcx.sess.time("check_lint_expectations", || tcx.check_expectations(Some(sym::rustdoc))); + krate = tcx.sess.time("create_format_cache", || Cache::populate(&mut ctxt, krate)); + + let mut collector = + LinkCollector { cx: &mut ctxt, visited_links: visited, ambiguous_links: ambiguous }; + collector.resolve_ambiguities(); + if let Some(guar) = tcx.dcx().has_errors() { return Err(guar); } - krate = tcx.sess.time("create_format_cache", || Cache::populate(&mut ctxt, krate)); - Ok((krate, ctxt.render_options, ctxt.cache)) } diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs index abea5bcbc51e..d27e737764dc 100644 --- a/src/librustdoc/passes/calculate_doc_coverage.rs +++ b/src/librustdoc/passes/calculate_doc_coverage.rs @@ -20,7 +20,7 @@ use crate::visit::DocVisitor; pub(crate) const CALCULATE_DOC_COVERAGE: Pass = Pass { name: "calculate-doc-coverage", - run: calculate_doc_coverage, + run: Some(calculate_doc_coverage), description: "counts the number of items with and without documentation", }; diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs index 1dc9af7ebe59..f4579d855315 100644 --- a/src/librustdoc/passes/check_doc_test_visibility.rs +++ b/src/librustdoc/passes/check_doc_test_visibility.rs @@ -20,7 +20,7 @@ use crate::visit::DocVisitor; pub(crate) const CHECK_DOC_TEST_VISIBILITY: Pass = Pass { name: "check_doc_test_visibility", - run: check_doc_test_visibility, + run: Some(check_doc_test_visibility), description: "run various visibility-related lints on doctests", }; diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index db235786cf49..81e7bcbd7d87 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -9,12 +9,12 @@ use std::ops::Range; use pulldown_cmark::LinkType; use rustc_ast::util::comments::may_have_doc_links; -use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::intern::Interned; use rustc_errors::{Applicability, Diag, DiagMessage}; use rustc_hir::def::Namespace::*; use rustc_hir::def::{DefKind, Namespace, PerNS}; -use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; +use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE}; use rustc_hir::{Mutability, Safety}; use rustc_middle::ty::{Ty, TyCtxt}; use rustc_middle::{bug, span_bug, ty}; @@ -30,23 +30,27 @@ use smallvec::{SmallVec, smallvec}; use tracing::{debug, info, instrument, trace}; use crate::clean::utils::find_nearest_parent_module; -use crate::clean::{self, Crate, Item, ItemLink, PrimitiveType}; +use crate::clean::{self, Crate, Item, ItemId, ItemLink, PrimitiveType}; use crate::core::DocContext; use crate::html::markdown::{MarkdownLink, MarkdownLinkRange, markdown_links}; use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS}; use crate::passes::Pass; use crate::visit::DocVisitor; -pub(crate) const COLLECT_INTRA_DOC_LINKS: Pass = Pass { - name: "collect-intra-doc-links", - run: collect_intra_doc_links, - description: "resolves intra-doc links", -}; +pub(crate) const COLLECT_INTRA_DOC_LINKS: Pass = + Pass { name: "collect-intra-doc-links", run: None, description: "resolves intra-doc links" }; -fn collect_intra_doc_links(krate: Crate, cx: &mut DocContext<'_>) -> Crate { - let mut collector = LinkCollector { cx, visited_links: FxHashMap::default() }; +pub(crate) fn collect_intra_doc_links<'a, 'tcx>( + krate: Crate, + cx: &'a mut DocContext<'tcx>, +) -> (Crate, LinkCollector<'a, 'tcx>) { + let mut collector = LinkCollector { + cx, + visited_links: FxHashMap::default(), + ambiguous_links: FxIndexMap::default(), + }; collector.visit_crate(&krate); - krate + (krate, collector) } fn filter_assoc_items_by_name_and_namespace<'a>( @@ -61,7 +65,7 @@ fn filter_assoc_items_by_name_and_namespace<'a>( } #[derive(Copy, Clone, Debug, Hash, PartialEq)] -enum Res { +pub(crate) enum Res { Def(DefKind, DefId), Primitive(PrimitiveType), } @@ -234,7 +238,7 @@ impl UrlFragment { } #[derive(Clone, Debug, Hash, PartialEq, Eq)] -struct ResolutionInfo { +pub(crate) struct ResolutionInfo { item_id: DefId, module_id: DefId, dis: Option, @@ -243,18 +247,63 @@ struct ResolutionInfo { } #[derive(Clone)] -struct DiagnosticInfo<'a> { +pub(crate) struct DiagnosticInfo<'a> { item: &'a Item, dox: &'a str, ori_link: &'a str, link_range: MarkdownLinkRange, } -struct LinkCollector<'a, 'tcx> { - cx: &'a mut DocContext<'tcx>, +pub(crate) struct OwnedDiagnosticInfo { + item: Item, + dox: String, + ori_link: String, + link_range: MarkdownLinkRange, +} + +impl From> for OwnedDiagnosticInfo { + fn from(f: DiagnosticInfo<'_>) -> Self { + Self { + item: f.item.clone(), + dox: f.dox.to_string(), + ori_link: f.ori_link.to_string(), + link_range: f.link_range.clone(), + } + } +} + +impl OwnedDiagnosticInfo { + pub(crate) fn into_info(&self) -> DiagnosticInfo<'_> { + DiagnosticInfo { + item: &self.item, + ori_link: &self.ori_link, + dox: &self.dox, + link_range: self.link_range.clone(), + } + } +} + +pub(crate) struct LinkCollector<'a, 'tcx> { + pub(crate) cx: &'a mut DocContext<'tcx>, /// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link. /// The link will be `None` if it could not be resolved (i.e. the error was cached). - visited_links: FxHashMap)>>, + pub(crate) visited_links: FxHashMap)>>, + /// These links are ambiguous. We need for the cache to have its paths filled. Unfortunately, + /// if we run the `LinkCollector` pass after `Cache::populate`, a lot of items that we need + /// to go through will be removed, making a lot of intra-doc links to not be inferred. + /// + /// So instead, we store the ambiguous links and we wait for cache paths to be filled before + /// inferring them (if possible). + /// + /// Key is `(item ID, path str)`. + pub(crate) ambiguous_links: FxIndexMap<(ItemId, String), Vec>, +} + +pub(crate) struct AmbiguousLinks { + disambiguator: Option, + link_text: Box, + diag_info: OwnedDiagnosticInfo, + resolved: Vec<(Res, Option)>, } impl<'a, 'tcx> LinkCollector<'a, 'tcx> { @@ -1001,6 +1050,10 @@ impl LinkCollector<'_, '_> { } } + pub(crate) fn save_link(&mut self, item_id: ItemId, link: ItemLink) { + self.cx.cache.intra_doc_links.entry(item_id).or_default().insert(link); + } + /// This is the entry point for resolving an intra-doc link. /// /// FIXME(jynelson): this is way too many arguments @@ -1024,7 +1077,7 @@ impl LinkCollector<'_, '_> { pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?; let disambiguator = *disambiguator; - let (mut res, fragment) = self.resolve_with_disambiguator_cached( + let mut resolved = self.resolve_with_disambiguator_cached( ResolutionInfo { item_id, module_id, @@ -1040,6 +1093,142 @@ impl LinkCollector<'_, '_> { false, )?; + if resolved.len() > 1 { + let links = AmbiguousLinks { + disambiguator, + link_text: link_text.clone(), + diag_info: diag_info.into(), + resolved, + }; + + self.ambiguous_links + .entry((item.item_id, path_str.to_string())) + .or_default() + .push(links); + None + } else if let Some((res, fragment)) = resolved.pop() { + self.compute_link(res, fragment, path_str, disambiguator, diag_info, link_text) + } else { + None + } + } + + /// Returns `true` if a link could be generated from the given intra-doc information. + /// + /// This is a very light version of `format::href_with_root_path` since we're only interested + /// about whether we can generate a link to an item or not. + /// + /// * If `original_did` is local, then we check if the item is reexported or public. + /// * If `original_did` is not local, then we check if the crate it comes from is a direct + /// public dependency. + fn validate_link(&self, original_did: DefId) -> bool { + let tcx = self.cx.tcx; + let def_kind = tcx.def_kind(original_did); + let did = match def_kind { + DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => { + // documented on their parent's page + tcx.parent(original_did) + } + // If this a constructor, we get the parent (either a struct or a variant) and then + // generate the link for this item. + DefKind::Ctor(..) => return self.validate_link(tcx.parent(original_did)), + DefKind::ExternCrate => { + // Link to the crate itself, not the `extern crate` item. + if let Some(local_did) = original_did.as_local() { + tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id() + } else { + original_did + } + } + _ => original_did, + }; + + let cache = &self.cx.cache; + if !original_did.is_local() + && !cache.effective_visibilities.is_directly_public(tcx, did) + && !cache.document_private + && !cache.primitive_locations.values().any(|&id| id == did) + { + return false; + } + + cache.paths.get(&did).is_some() + || cache.external_paths.get(&did).is_some() + || !did.is_local() + } + + #[allow(rustc::potential_query_instability)] + pub(crate) fn resolve_ambiguities(&mut self) { + let mut ambiguous_links = mem::take(&mut self.ambiguous_links); + + for ((item_id, path_str), info_items) in ambiguous_links.iter_mut() { + for info in info_items { + info.resolved.retain(|(res, _)| match res { + Res::Def(_, def_id) => self.validate_link(*def_id), + // Primitive types are always valid. + Res::Primitive(_) => true, + }); + let diag_info = info.diag_info.into_info(); + match info.resolved.len() { + 1 => { + let (res, fragment) = info.resolved.pop().unwrap(); + if let Some(link) = self.compute_link( + res, + fragment, + path_str, + info.disambiguator, + diag_info, + &info.link_text, + ) { + self.save_link(*item_id, link); + } + } + 0 => { + report_diagnostic( + self.cx.tcx, + BROKEN_INTRA_DOC_LINKS, + format!( + "all items matching `{path_str}` are either private or doc(hidden)" + ), + &diag_info, + |diag, sp, _| { + if let Some(sp) = sp { + diag.span_label(sp, "unresolved link"); + } else { + diag.note("unresolved link"); + } + }, + ); + } + _ => { + let candidates = info + .resolved + .iter() + .map(|(res, fragment)| { + let def_id = if let Some(UrlFragment::Item(def_id)) = fragment { + Some(*def_id) + } else { + None + }; + (*res, def_id) + }) + .collect::>(); + ambiguity_error(self.cx, &diag_info, path_str, &candidates, true); + } + } + } + } + } + + fn compute_link( + &mut self, + mut res: Res, + fragment: Option, + path_str: &str, + disambiguator: Option, + diag_info: DiagnosticInfo<'_>, + link_text: &Box, + ) -> Option { // Check for a primitive which might conflict with a module // Report the ambiguity and require that the user specify which one they meant. // FIXME: could there ever be a primitive not in the type namespace? @@ -1055,7 +1244,7 @@ impl LinkCollector<'_, '_> { } else { // `[char]` when a `char` module is in scope let candidates = &[(res, res.def_id(self.cx.tcx)), (prim, None)]; - ambiguity_error(self.cx, &diag_info, path_str, candidates); + ambiguity_error(self.cx, &diag_info, path_str, candidates, true); return None; } } @@ -1085,7 +1274,7 @@ impl LinkCollector<'_, '_> { } res.def_id(self.cx.tcx).map(|page_id| ItemLink { - link: Box::::from(&*ori_link.link), + link: Box::::from(&*diag_info.ori_link), link_text: link_text.clone(), page_id, fragment, @@ -1107,7 +1296,7 @@ impl LinkCollector<'_, '_> { let page_id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id)); Some(ItemLink { - link: Box::::from(&*ori_link.link), + link: Box::::from(&*diag_info.ori_link), link_text: link_text.clone(), page_id, fragment, @@ -1220,10 +1409,10 @@ impl LinkCollector<'_, '_> { // If this call is intended to be recoverable, then pass true to silence. // This is only recoverable when path is failed to resolved. recoverable: bool, - ) -> Option<(Res, Option)> { + ) -> Option)>> { if let Some(res) = self.visited_links.get(&key) { if res.is_some() || cache_errors { - return res.clone(); + return res.clone().map(|r| vec![r]); } } @@ -1248,13 +1437,14 @@ impl LinkCollector<'_, '_> { // and after removing duplicated kinds, only one remains, the `ambiguity_error` function // won't emit an error. So at this point, we can just take the first candidate as it was // the first retrieved and use it to generate the link. - if let [candidate, _candidate2, ..] = *candidates - && !ambiguity_error(self.cx, &diag, &key.path_str, &candidates) - { - candidates = vec![candidate]; + if let [candidate, _candidate2, ..] = *candidates { + if !ambiguity_error(self.cx, &diag, &key.path_str, &candidates, false) { + candidates = vec![candidate]; + } } - if let &[(res, def_id)] = candidates.as_slice() { + let mut out = Vec::with_capacity(candidates.len()); + for (res, def_id) in candidates { let fragment = match (&key.extra_fragment, def_id) { (Some(_), Some(def_id)) => { report_anchor_conflict(self.cx, diag, def_id); @@ -1264,15 +1454,14 @@ impl LinkCollector<'_, '_> { (None, Some(def_id)) => Some(UrlFragment::Item(def_id)), (None, None) => None, }; - let r = Some((res, fragment)); - self.visited_links.insert(key, r.clone()); - return r; + out.push((res, fragment)); } - - if cache_errors { + if let [r] = out.as_slice() { + self.visited_links.insert(key, Some(r.clone())); + } else if cache_errors { self.visited_links.insert(key, None); } - None + Some(out) } /// After parsing the disambiguator, resolve the main part of the link. @@ -2046,6 +2235,7 @@ fn ambiguity_error( diag_info: &DiagnosticInfo<'_>, path_str: &str, candidates: &[(Res, Option)], + emit_error: bool, ) -> bool { let mut descrs = FxHashSet::default(); let kinds = candidates @@ -2061,6 +2251,8 @@ fn ambiguity_error( // There is no way for users to disambiguate at this point, so better return the first // candidate and not show a warning. return false; + } else if !emit_error { + return true; } let mut msg = format!("`{path_str}` is "); diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index d1a1f0df3e7e..f35890803228 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -16,7 +16,7 @@ use crate::visit::DocVisitor; pub(crate) const COLLECT_TRAIT_IMPLS: Pass = Pass { name: "collect-trait-impls", - run: collect_trait_impls, + run: Some(collect_trait_impls), description: "retrieves trait impls for items in the crate", }; diff --git a/src/librustdoc/passes/lint.rs b/src/librustdoc/passes/lint.rs index 593027ef7d29..35b62370abb2 100644 --- a/src/librustdoc/passes/lint.rs +++ b/src/librustdoc/passes/lint.rs @@ -14,7 +14,7 @@ use crate::core::DocContext; use crate::visit::DocVisitor; pub(crate) const RUN_LINTS: Pass = - Pass { name: "run-lints", run: run_lints, description: "runs some of rustdoc's lints" }; + Pass { name: "run-lints", run: Some(run_lints), description: "runs some of rustdoc's lints" }; struct Linter<'a, 'tcx> { cx: &'a mut DocContext<'tcx>, diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index f5b780237214..9ba63d34144a 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -47,7 +47,7 @@ pub(crate) use self::lint::RUN_LINTS; #[derive(Copy, Clone)] pub(crate) struct Pass { pub(crate) name: &'static str, - pub(crate) run: fn(clean::Crate, &mut DocContext<'_>) -> clean::Crate, + pub(crate) run: Option) -> clean::Crate>, pub(crate) description: &'static str, } diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs index 6be51dd15606..350be37f553d 100644 --- a/src/librustdoc/passes/propagate_doc_cfg.rs +++ b/src/librustdoc/passes/propagate_doc_cfg.rs @@ -13,7 +13,7 @@ use crate::passes::Pass; pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass { name: "propagate-doc-cfg", - run: propagate_doc_cfg, + run: Some(propagate_doc_cfg), description: "propagates `#[doc(cfg(...))]` to child items", }; diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs index f51e993bfa5d..f55479687f8e 100644 --- a/src/librustdoc/passes/propagate_stability.rs +++ b/src/librustdoc/passes/propagate_stability.rs @@ -16,7 +16,7 @@ use crate::passes::Pass; pub(crate) const PROPAGATE_STABILITY: Pass = Pass { name: "propagate-stability", - run: propagate_stability, + run: Some(propagate_stability), description: "propagates stability to child items", }; diff --git a/src/librustdoc/passes/strip_aliased_non_local.rs b/src/librustdoc/passes/strip_aliased_non_local.rs index 155ad5438314..a078eec048ec 100644 --- a/src/librustdoc/passes/strip_aliased_non_local.rs +++ b/src/librustdoc/passes/strip_aliased_non_local.rs @@ -8,7 +8,7 @@ use crate::passes::Pass; pub(crate) const STRIP_ALIASED_NON_LOCAL: Pass = Pass { name: "strip-aliased-non-local", - run: strip_aliased_non_local, + run: Some(strip_aliased_non_local), description: "strips all non-local private aliased items from the output", }; diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs index 430f3d8a574c..aba04283e59d 100644 --- a/src/librustdoc/passes/strip_hidden.rs +++ b/src/librustdoc/passes/strip_hidden.rs @@ -16,7 +16,7 @@ use crate::passes::{ImplStripper, Pass}; pub(crate) const STRIP_HIDDEN: Pass = Pass { name: "strip-hidden", - run: strip_hidden, + run: Some(strip_hidden), description: "strips all `#[doc(hidden)]` items from the output", }; diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs index 7b05756ae215..2e9f06bd0a30 100644 --- a/src/librustdoc/passes/strip_priv_imports.rs +++ b/src/librustdoc/passes/strip_priv_imports.rs @@ -8,7 +8,7 @@ use crate::passes::{ImportStripper, Pass}; pub(crate) const STRIP_PRIV_IMPORTS: Pass = Pass { name: "strip-priv-imports", - run: strip_priv_imports, + run: Some(strip_priv_imports), description: "strips all private import statements (`use`, `extern crate`) from a crate", }; diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs index 1bafa450be90..78f0ad277408 100644 --- a/src/librustdoc/passes/strip_private.rs +++ b/src/librustdoc/passes/strip_private.rs @@ -8,7 +8,7 @@ use crate::passes::{ImplStripper, ImportStripper, Pass, Stripper}; pub(crate) const STRIP_PRIVATE: Pass = Pass { name: "strip-private", - run: strip_private, + run: Some(strip_private), description: "strips all private items from a crate which cannot be seen externally, \ implies strip-priv-imports", }; From d540e7285ca14393d2b51f486ecc0f26d2f986c7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 14 Oct 2024 17:51:37 +0200 Subject: [PATCH 02/16] Add regression tests for #130233 --- .../intra-doc/filter-out-private-2.rs | 15 +++++++++++ .../intra-doc/filter-out-private-2.stderr | 14 ++++++++++ .../intra-doc/filter-out-private.rs | 13 ++++++++++ .../intra-doc/filter-out-private.stderr | 22 ++++++++++++++++ tests/rustdoc/intra-doc/filter-out-private.rs | 26 +++++++++++++++++++ 5 files changed, 90 insertions(+) create mode 100644 tests/rustdoc-ui/intra-doc/filter-out-private-2.rs create mode 100644 tests/rustdoc-ui/intra-doc/filter-out-private-2.stderr create mode 100644 tests/rustdoc-ui/intra-doc/filter-out-private.rs create mode 100644 tests/rustdoc-ui/intra-doc/filter-out-private.stderr create mode 100644 tests/rustdoc/intra-doc/filter-out-private.rs diff --git a/tests/rustdoc-ui/intra-doc/filter-out-private-2.rs b/tests/rustdoc-ui/intra-doc/filter-out-private-2.rs new file mode 100644 index 000000000000..9209203c99ee --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/filter-out-private-2.rs @@ -0,0 +1,15 @@ +// This test ensures that ambiguities (not) resolved at a later stage still emit an error. + +#![deny(rustdoc::broken_intra_doc_links)] +#![crate_name = "foo"] + +#[doc(hidden)] +pub struct Thing {} + +#[allow(non_snake_case)] +#[doc(hidden)] +pub fn Thing() {} + +/// Do stuff with [`Thing`]. +//~^ ERROR all items matching `Thing` are either private or doc(hidden) +pub fn repro(_: Thing) {} diff --git a/tests/rustdoc-ui/intra-doc/filter-out-private-2.stderr b/tests/rustdoc-ui/intra-doc/filter-out-private-2.stderr new file mode 100644 index 000000000000..394f919de943 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/filter-out-private-2.stderr @@ -0,0 +1,14 @@ +error: all items matching `Thing` are either private or doc(hidden) + --> $DIR/filter-out-private-2.rs:13:21 + | +LL | /// Do stuff with [`Thing`]. + | ^^^^^ unresolved link + | +note: the lint level is defined here + --> $DIR/filter-out-private-2.rs:3:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/rustdoc-ui/intra-doc/filter-out-private.rs b/tests/rustdoc-ui/intra-doc/filter-out-private.rs new file mode 100644 index 000000000000..f481b51dad06 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/filter-out-private.rs @@ -0,0 +1,13 @@ +// This test ensures that ambiguities resolved at a later stage still emit an error. + +#![deny(rustdoc::broken_intra_doc_links)] +#![crate_name = "foo"] + +pub struct Thing {} + +#[allow(non_snake_case)] +pub fn Thing() {} + +/// Do stuff with [`Thing`]. +//~^ ERROR `Thing` is both a function and a struct +pub fn repro(_: Thing) {} diff --git a/tests/rustdoc-ui/intra-doc/filter-out-private.stderr b/tests/rustdoc-ui/intra-doc/filter-out-private.stderr new file mode 100644 index 000000000000..1d1830b1f1c3 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/filter-out-private.stderr @@ -0,0 +1,22 @@ +error: `Thing` is both a function and a struct + --> $DIR/filter-out-private.rs:11:21 + | +LL | /// Do stuff with [`Thing`]. + | ^^^^^ ambiguous link + | +note: the lint level is defined here + --> $DIR/filter-out-private.rs:3:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: to link to the function, add parentheses + | +LL | /// Do stuff with [`Thing()`]. + | ++ +help: to link to the struct, prefix with `struct@` + | +LL | /// Do stuff with [`struct@Thing`]. + | +++++++ + +error: aborting due to 1 previous error + diff --git a/tests/rustdoc/intra-doc/filter-out-private.rs b/tests/rustdoc/intra-doc/filter-out-private.rs new file mode 100644 index 000000000000..70591b120d82 --- /dev/null +++ b/tests/rustdoc/intra-doc/filter-out-private.rs @@ -0,0 +1,26 @@ +// This test ensures that private/hidden items don't create ambiguity. +// This is a regression test for . + +#![deny(rustdoc::broken_intra_doc_links)] +#![crate_name = "foo"] + +pub struct Thing {} + +#[doc(hidden)] +#[allow(non_snake_case)] +pub fn Thing() {} + +pub struct Bar {} + +#[allow(non_snake_case)] +fn Bar() {} + +//@ has 'foo/fn.repro.html' +//@ has - '//*[@class="toggle top-doc"]/*[@class="docblock"]//a/@href' 'struct.Thing.html' +/// Do stuff with [`Thing`]. +pub fn repro(_: Thing) {} + +//@ has 'foo/fn.repro2.html' +//@ has - '//*[@class="toggle top-doc"]/*[@class="docblock"]//a/@href' 'struct.Bar.html' +/// Do stuff with [`Bar`]. +pub fn repro2(_: Bar) {} From 918dc38733985770d1cdcea76169340c620c1f32 Mon Sep 17 00:00:00 2001 From: zlfn Date: Tue, 15 Oct 2024 18:23:39 +0900 Subject: [PATCH 03/16] Combine impl_int and impl_uint Two macros are exactly the same. --- library/core/src/fmt/num.rs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/library/core/src/fmt/num.rs b/library/core/src/fmt/num.rs index aecd725eca56..cc94c392de58 100644 --- a/library/core/src/fmt/num.rs +++ b/library/core/src/fmt/num.rs @@ -31,22 +31,11 @@ macro_rules! impl_int { })* ) } -macro_rules! impl_uint { - ($($t:ident)*) => ( - $(impl DisplayInt for $t { - fn zero() -> Self { 0 } - fn from_u8(u: u8) -> Self { u as Self } - fn to_u8(&self) -> u8 { *self as u8 } - #[cfg(not(any(target_pointer_width = "64", target_arch = "wasm32")))] - fn to_u32(&self) -> u32 { *self as u32 } - fn to_u64(&self) -> u64 { *self as u64 } - fn to_u128(&self) -> u128 { *self as u128 } - })* - ) -} -impl_int! { i8 i16 i32 i64 i128 isize } -impl_uint! { u8 u16 u32 u64 u128 usize } +impl_int! { + i8 i16 i32 i64 i128 isize + u8 u16 u32 u64 u128 usize +} /// A type that represents a specific radix /// From 0637517da6802bcdc8df9a73a75edb8c41033f11 Mon Sep 17 00:00:00 2001 From: zlfn Date: Tue, 15 Oct 2024 18:32:21 +0900 Subject: [PATCH 04/16] Rename debug! macro to impl_Debug! --- library/core/src/fmt/num.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/library/core/src/fmt/num.rs b/library/core/src/fmt/num.rs index cc94c392de58..0f33d730a68e 100644 --- a/library/core/src/fmt/num.rs +++ b/library/core/src/fmt/num.rs @@ -167,7 +167,8 @@ integer! { i16, u16 } integer! { i32, u32 } integer! { i64, u64 } integer! { i128, u128 } -macro_rules! debug { + +macro_rules! impl_Debug { ($($T:ident)*) => {$( #[stable(feature = "rust1", since = "1.0.0")] impl fmt::Debug for $T { @@ -184,10 +185,6 @@ macro_rules! debug { } )*}; } -debug! { - i8 i16 i32 i64 i128 isize - u8 u16 u32 u64 u128 usize -} // 2 digit decimal look up table static DEC_DIGITS_LUT: &[u8; 200] = b"0001020304050607080910111213141516171819\ @@ -510,6 +507,11 @@ macro_rules! impl_Exp { }; } +impl_Debug! { + i8 i16 i32 i64 i128 isize + u8 u16 u32 u64 u128 usize +} + // Include wasm32 in here since it doesn't reflect the native pointer size, and // often cares strongly about getting a smaller code size. #[cfg(any(target_pointer_width = "64", target_arch = "wasm32"))] From 99af761632420b2778e2b44465a396e28adf6103 Mon Sep 17 00:00:00 2001 From: zlfn Date: Tue, 15 Oct 2024 18:39:30 +0900 Subject: [PATCH 05/16] Refactor `floating` macro and nofloat panic message --- library/core/src/fmt/float.rs | 47 ++++++++++++++++--------------- library/core/src/fmt/nofloat.rs | 21 +++++++------- library/core/src/fmt/num.rs | 50 +++++++++++++++++---------------- 3 files changed, 60 insertions(+), 58 deletions(-) diff --git a/library/core/src/fmt/float.rs b/library/core/src/fmt/float.rs index 20ea0352c2dc..c70dbf54304d 100644 --- a/library/core/src/fmt/float.rs +++ b/library/core/src/fmt/float.rs @@ -196,39 +196,40 @@ where } macro_rules! floating { - ($ty:ident) => { - #[stable(feature = "rust1", since = "1.0.0")] - impl Debug for $ty { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { - float_to_general_debug(fmt, self) + ($($ty:ident)*) => { + $( + #[stable(feature = "rust1", since = "1.0.0")] + impl Debug for $ty { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { + float_to_general_debug(fmt, self) + } } - } - #[stable(feature = "rust1", since = "1.0.0")] - impl Display for $ty { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { - float_to_decimal_display(fmt, self) + #[stable(feature = "rust1", since = "1.0.0")] + impl Display for $ty { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { + float_to_decimal_display(fmt, self) + } } - } - #[stable(feature = "rust1", since = "1.0.0")] - impl LowerExp for $ty { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { - float_to_exponential_common(fmt, self, false) + #[stable(feature = "rust1", since = "1.0.0")] + impl LowerExp for $ty { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { + float_to_exponential_common(fmt, self, false) + } } - } - #[stable(feature = "rust1", since = "1.0.0")] - impl UpperExp for $ty { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { - float_to_exponential_common(fmt, self, true) + #[stable(feature = "rust1", since = "1.0.0")] + impl UpperExp for $ty { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { + float_to_exponential_common(fmt, self, true) + } } - } + )* }; } -floating! { f32 } -floating! { f64 } +floating! { f32 f64 } #[stable(feature = "rust1", since = "1.0.0")] impl Debug for f16 { diff --git a/library/core/src/fmt/nofloat.rs b/library/core/src/fmt/nofloat.rs index 6b07236f1da1..29aaee75d22b 100644 --- a/library/core/src/fmt/nofloat.rs +++ b/library/core/src/fmt/nofloat.rs @@ -1,18 +1,17 @@ use crate::fmt::{Debug, Formatter, Result}; macro_rules! floating { - ($ty:ident) => { - #[stable(feature = "rust1", since = "1.0.0")] - impl Debug for $ty { - #[inline] - fn fmt(&self, _fmt: &mut Formatter<'_>) -> Result { - panic!("floating point support is turned off"); + ($($ty:ident)*) => { + $( + #[stable(feature = "rust1", since = "1.0.0")] + impl Debug for $ty { + #[inline] + fn fmt(&self, _fmt: &mut Formatter<'_>) -> Result { + panic!("floating point fmt support is turned off"); + } } - } + )* }; } -floating! { f16 } -floating! { f32 } -floating! { f64 } -floating! { f128 } +floating! { f16 f32 f64 f128 } diff --git a/library/core/src/fmt/num.rs b/library/core/src/fmt/num.rs index 0f33d730a68e..f1540803f978 100644 --- a/library/core/src/fmt/num.rs +++ b/library/core/src/fmt/num.rs @@ -20,15 +20,15 @@ trait DisplayInt: macro_rules! impl_int { ($($t:ident)*) => ( - $(impl DisplayInt for $t { - fn zero() -> Self { 0 } - fn from_u8(u: u8) -> Self { u as Self } - fn to_u8(&self) -> u8 { *self as u8 } - #[cfg(not(any(target_pointer_width = "64", target_arch = "wasm32")))] - fn to_u32(&self) -> u32 { *self as u32 } - fn to_u64(&self) -> u64 { *self as u64 } - fn to_u128(&self) -> u128 { *self as u128 } - })* + $(impl DisplayInt for $t { + fn zero() -> Self { 0 } + fn from_u8(u: u8) -> Self { u as Self } + fn to_u8(&self) -> u8 { *self as u8 } + #[cfg(not(any(target_pointer_width = "64", target_arch = "wasm32")))] + fn to_u32(&self) -> u32 { *self as u32 } + fn to_u64(&self) -> u64 { *self as u64 } + fn to_u128(&self) -> u128 { *self as u128 } + })* ) } @@ -169,21 +169,23 @@ integer! { i64, u64 } integer! { i128, u128 } macro_rules! impl_Debug { - ($($T:ident)*) => {$( - #[stable(feature = "rust1", since = "1.0.0")] - impl fmt::Debug for $T { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if f.debug_lower_hex() { - fmt::LowerHex::fmt(self, f) - } else if f.debug_upper_hex() { - fmt::UpperHex::fmt(self, f) - } else { - fmt::Display::fmt(self, f) + ($($T:ident)*) => { + $( + #[stable(feature = "rust1", since = "1.0.0")] + impl fmt::Debug for $T { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.debug_lower_hex() { + fmt::LowerHex::fmt(self, f) + } else if f.debug_upper_hex() { + fmt::UpperHex::fmt(self, f) + } else { + fmt::Display::fmt(self, f) + } } } - } - )*}; + )* + }; } // 2 digit decimal look up table @@ -508,8 +510,8 @@ macro_rules! impl_Exp { } impl_Debug! { - i8 i16 i32 i64 i128 isize - u8 u16 u32 u64 u128 usize + i8 i16 i32 i64 i128 isize + u8 u16 u32 u64 u128 usize } // Include wasm32 in here since it doesn't reflect the native pointer size, and From 2b9e41c1288a5e9f015be4f5da4c90d5a4d9c158 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 15 Oct 2024 01:58:27 +0200 Subject: [PATCH 06/16] Improve documentation for intra-doc links computation --- .../passes/collect_intra_doc_links.rs | 18 +++++++++--------- .../intra-doc/filter-out-private-2.rs | 2 +- .../intra-doc/filter-out-private-2.stderr | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 81e7bcbd7d87..10026e9f05d2 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -288,14 +288,16 @@ pub(crate) struct LinkCollector<'a, 'tcx> { /// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link. /// The link will be `None` if it could not be resolved (i.e. the error was cached). pub(crate) visited_links: FxHashMap)>>, - /// These links are ambiguous. We need for the cache to have its paths filled. Unfortunately, - /// if we run the `LinkCollector` pass after `Cache::populate`, a lot of items that we need - /// to go through will be removed, making a lot of intra-doc links to not be inferred. + /// According to `rustc_resolve`, these links are ambiguous. /// - /// So instead, we store the ambiguous links and we wait for cache paths to be filled before - /// inferring them (if possible). + /// However, we cannot link to an item that has been stripped from the documentation. If all + /// but one of the "possibilities" are stripped, then there is no real ambiguity. To determine + /// if an ambiguity is real, we delay resolving them until after `Cache::populate`, then filter + /// every item that doesn't have a cached path. /// - /// Key is `(item ID, path str)`. + /// We could get correct results by simply delaying everything. This would have fewer happy + /// codepaths, but we want to distinguish different kinds of error conditions, and this is easy + /// to do by resolving links as soon as possible. pub(crate) ambiguous_links: FxIndexMap<(ItemId, String), Vec>, } @@ -1187,9 +1189,7 @@ impl LinkCollector<'_, '_> { report_diagnostic( self.cx.tcx, BROKEN_INTRA_DOC_LINKS, - format!( - "all items matching `{path_str}` are either private or doc(hidden)" - ), + format!("all items matching `{path_str}` are private or doc(hidden)"), &diag_info, |diag, sp, _| { if let Some(sp) = sp { diff --git a/tests/rustdoc-ui/intra-doc/filter-out-private-2.rs b/tests/rustdoc-ui/intra-doc/filter-out-private-2.rs index 9209203c99ee..9d8edbf6b5dd 100644 --- a/tests/rustdoc-ui/intra-doc/filter-out-private-2.rs +++ b/tests/rustdoc-ui/intra-doc/filter-out-private-2.rs @@ -11,5 +11,5 @@ pub struct Thing {} pub fn Thing() {} /// Do stuff with [`Thing`]. -//~^ ERROR all items matching `Thing` are either private or doc(hidden) +//~^ ERROR all items matching `Thing` are private or doc(hidden) pub fn repro(_: Thing) {} diff --git a/tests/rustdoc-ui/intra-doc/filter-out-private-2.stderr b/tests/rustdoc-ui/intra-doc/filter-out-private-2.stderr index 394f919de943..1a49c90a1728 100644 --- a/tests/rustdoc-ui/intra-doc/filter-out-private-2.stderr +++ b/tests/rustdoc-ui/intra-doc/filter-out-private-2.stderr @@ -1,4 +1,4 @@ -error: all items matching `Thing` are either private or doc(hidden) +error: all items matching `Thing` are private or doc(hidden) --> $DIR/filter-out-private-2.rs:13:21 | LL | /// Do stuff with [`Thing`]. From 10f2395c9ee5849e3a769ce21d3e704191ec8085 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 15 Oct 2024 15:42:47 +0200 Subject: [PATCH 07/16] Remove `AmbiguousLinks::disambiguator` --- src/librustdoc/passes/collect_intra_doc_links.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 10026e9f05d2..cbc6e351fac5 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -302,7 +302,6 @@ pub(crate) struct LinkCollector<'a, 'tcx> { } pub(crate) struct AmbiguousLinks { - disambiguator: Option, link_text: Box, diag_info: OwnedDiagnosticInfo, resolved: Vec<(Res, Option)>, @@ -1097,7 +1096,6 @@ impl LinkCollector<'_, '_> { if resolved.len() > 1 { let links = AmbiguousLinks { - disambiguator, link_text: link_text.clone(), diag_info: diag_info.into(), resolved, @@ -1178,7 +1176,7 @@ impl LinkCollector<'_, '_> { res, fragment, path_str, - info.disambiguator, + None, diag_info, &info.link_text, ) { From dca646ab0107fd311427b7f7c4eea448e9f4ee9e Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sun, 13 Oct 2024 10:09:42 -0400 Subject: [PATCH 08/16] Rewrite for<..> async correctly --- src/tools/rustfmt/src/types.rs | 50 +++++++++++---------- src/tools/rustfmt/tests/target/asyncness.rs | 2 + 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/tools/rustfmt/src/types.rs b/src/tools/rustfmt/src/types.rs index 99b3fe60ee2f..e237662f5aa2 100644 --- a/src/tools/rustfmt/src/types.rs +++ b/src/tools/rustfmt/src/types.rs @@ -613,26 +613,8 @@ impl Rewrite for ast::GenericBound { ast::GenericBound::Trait(ref poly_trait_ref) => { let snippet = context.snippet(self.span()); let has_paren = snippet.starts_with('(') && snippet.ends_with(')'); - let ast::TraitBoundModifiers { - constness, - asyncness, - polarity, - } = poly_trait_ref.modifiers; - let mut constness = constness.as_str().to_string(); - if !constness.is_empty() { - constness.push(' '); - } - let mut asyncness = asyncness.as_str().to_string(); - if !asyncness.is_empty() { - asyncness.push(' '); - } - let polarity = polarity.as_str(); - let shape = shape - .offset_left(constness.len() + polarity.len()) - .max_width_error(shape.width, self.span())?; poly_trait_ref .rewrite_result(context, shape) - .map(|s| format!("{constness}{asyncness}{polarity}{s}")) .map(|s| if has_paren { format!("({})", s) } else { s }) } ast::GenericBound::Use(ref args, span) => { @@ -756,19 +738,41 @@ impl Rewrite for ast::PolyTraitRef { } fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult { - if let Some(lifetime_str) = rewrite_bound_params(context, shape, &self.bound_generic_params) + let (binder, shape) = if let Some(lifetime_str) = + rewrite_bound_params(context, shape, &self.bound_generic_params) { // 6 is "for<> ".len() let extra_offset = lifetime_str.len() + 6; let shape = shape .offset_left(extra_offset) .max_width_error(shape.width, self.span)?; - let path_str = self.trait_ref.rewrite_result(context, shape)?; - - Ok(format!("for<{lifetime_str}> {path_str}")) + (format!("for<{lifetime_str}> "), shape) } else { - self.trait_ref.rewrite_result(context, shape) + (String::new(), shape) + }; + + let ast::TraitBoundModifiers { + constness, + asyncness, + polarity, + } = self.modifiers; + let mut constness = constness.as_str().to_string(); + if !constness.is_empty() { + constness.push(' '); } + let mut asyncness = asyncness.as_str().to_string(); + if !asyncness.is_empty() { + asyncness.push(' '); + } + let polarity = polarity.as_str(); + let shape = shape + .offset_left(constness.len() + polarity.len()) + .max_width_error(shape.width, self.span)?; + + let path_str = self.trait_ref.rewrite_result(context, shape)?; + Ok(format!( + "{binder}{constness}{asyncness}{polarity}{path_str}" + )) } } diff --git a/src/tools/rustfmt/tests/target/asyncness.rs b/src/tools/rustfmt/tests/target/asyncness.rs index d91ac960499b..dd651ed6a620 100644 --- a/src/tools/rustfmt/tests/target/asyncness.rs +++ b/src/tools/rustfmt/tests/target/asyncness.rs @@ -1,3 +1,5 @@ // rustfmt-edition: 2018 fn foo() -> impl async Fn() {} + +fn bar() -> impl for<'a> async Fn(&'a ()) {} From 4886e9a134aa7c8c9bb1fed42ebfe22d7f5b2a63 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 15 Oct 2024 14:39:49 -0400 Subject: [PATCH 09/16] Unify secondary_span and swap_secondary_and_primary --- .../src/check/compare_impl_item.rs | 9 +++------ compiler/rustc_hir_analysis/src/check/mod.rs | 1 - .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 1 - compiler/rustc_passes/src/check_attr.rs | 1 - .../src/error_reporting/infer/mod.rs | 18 +++++++----------- .../traits/fulfillment_errors.rs | 3 +-- 6 files changed, 11 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs index 9ca5f25447b3..75956165e873 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs @@ -592,14 +592,13 @@ pub(super) fn collect_return_position_impl_trait_in_trait_tys<'tcx>( &cause, hir.get_if_local(impl_m.def_id) .and_then(|node| node.fn_decl()) - .map(|decl| (decl.output.span(), Cow::from("return type in trait"))), + .map(|decl| (decl.output.span(), Cow::from("return type in trait"), false)), Some(infer::ValuePairs::Terms(ExpectedFound { expected: trait_return_ty.into(), found: impl_return_ty.into(), })), terr, false, - false, ); return Err(diag.emit()); } @@ -1018,14 +1017,13 @@ fn report_trait_method_mismatch<'tcx>( infcx.err_ctxt().note_type_err( &mut diag, &cause, - trait_err_span.map(|sp| (sp, Cow::from("type in trait"))), + trait_err_span.map(|sp| (sp, Cow::from("type in trait"), false)), Some(infer::ValuePairs::PolySigs(ExpectedFound { expected: ty::Binder::dummy(trait_sig), found: ty::Binder::dummy(impl_sig), })), terr, false, - false, ); diag.emit() @@ -1825,14 +1823,13 @@ fn compare_const_predicate_entailment<'tcx>( infcx.err_ctxt().note_type_err( &mut diag, &cause, - trait_c_span.map(|span| (span, Cow::from("type in trait"))), + trait_c_span.map(|span| (span, Cow::from("type in trait"), false)), Some(infer::ValuePairs::Terms(ExpectedFound { expected: trait_ty.into(), found: impl_ty.into(), })), terr, false, - false, ); return Err(diag.emit()); }; diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index 004540b26430..959b17b2d407 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -652,7 +652,6 @@ pub fn check_function_signature<'tcx>( })), err, false, - false, ); return Err(diag.emit()); } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index fa471647d02d..44c0da0744a2 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -1114,7 +1114,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { None, Some(trace.values), e, - false, true, ); } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 7ce29260e367..4516ea94cad7 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -2302,7 +2302,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { })), terr, false, - false, ); diag.emit(); self.abort.set(true); diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index bd78a6ee3aff..df5800ab58a6 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -1127,18 +1127,14 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { /// the message in `secondary_span` as the primary label, and apply the message that would /// otherwise be used for the primary label on the `secondary_span` `Span`. This applies on /// E0271, like `tests/ui/issues/issue-39970.stderr`. - #[instrument( - level = "debug", - skip(self, diag, secondary_span, swap_secondary_and_primary, prefer_label) - )] + #[instrument(level = "debug", skip(self, diag, secondary_span, prefer_label))] pub fn note_type_err( &self, diag: &mut Diag<'_>, cause: &ObligationCause<'tcx>, - secondary_span: Option<(Span, Cow<'static, str>)>, + secondary_span: Option<(Span, Cow<'static, str>, bool)>, mut values: Option>, terr: TypeError<'tcx>, - swap_secondary_and_primary: bool, prefer_label: bool, ) { let span = cause.span(); @@ -1304,7 +1300,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { diag.span_note(span, msg); } }; - if let Some((sp, msg)) = secondary_span { + if let Some((secondary_span, secondary_msg, swap_secondary_and_primary)) = secondary_span { if swap_secondary_and_primary { let terr = if let Some(infer::ValuePairs::Terms(ExpectedFound { expected, .. @@ -1314,11 +1310,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } else { terr.to_string(self.tcx) }; - label_or_note(sp, terr); - label_or_note(span, msg); + label_or_note(secondary_span, terr); + label_or_note(span, secondary_msg); } else { label_or_note(span, terr.to_string(self.tcx)); - label_or_note(sp, msg); + label_or_note(secondary_span, secondary_msg); } } else if let Some(values) = values && let Some((e, f)) = values.ty() @@ -1788,7 +1784,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { self.type_error_additional_suggestions(&trace, terr), ); let mut diag = self.dcx().create_err(failure_code); - self.note_type_err(&mut diag, &trace.cause, None, Some(trace.values), terr, false, false); + self.note_type_err(&mut diag, &trace.cause, None, Some(trace.values), terr, false); diag } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 824c25db07d2..8a4c22fdf863 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -710,7 +710,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { None, TypeError::Sorts(ty::error::ExpectedFound::new(true, expected_ty, ct_ty)), false, - false, ); diag } @@ -1435,6 +1434,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { cx.into_buffer() } ))), + true, )), _ => None, } @@ -1452,7 +1452,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { )) }), err, - true, false, ); self.note_obligation_cause(&mut diag, obligation); From b4e9aad137800936b4fbc3c36da6ff2819b385b5 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 15 Oct 2024 13:20:41 -0400 Subject: [PATCH 10/16] Rename can_coerce to may_coerce --- compiler/rustc_hir_typeck/src/_match.rs | 4 +-- compiler/rustc_hir_typeck/src/cast.rs | 8 ++--- compiler/rustc_hir_typeck/src/coercion.rs | 17 ++++++---- compiler/rustc_hir_typeck/src/expr.rs | 10 +++--- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 10 +++--- .../src/fn_ctxt/suggestions.rs | 34 +++++++++---------- .../rustc_hir_typeck/src/method/suggest.rs | 4 +-- compiler/rustc_hir_typeck/src/pat.rs | 2 +- 8 files changed, 46 insertions(+), 43 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/_match.rs b/compiler/rustc_hir_typeck/src/_match.rs index 0d9d1910ae0c..e68caa3e2e35 100644 --- a/compiler/rustc_hir_typeck/src/_match.rs +++ b/compiler/rustc_hir_typeck/src/_match.rs @@ -235,8 +235,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Some(ret_coercion) => { let ret_ty = ret_coercion.borrow().expected_ty(); let ret_ty = self.infcx.shallow_resolve(ret_ty); - self.can_coerce(arm_ty, ret_ty) - && prior_arm.is_none_or(|(_, ty, _)| self.can_coerce(ty, ret_ty)) + self.may_coerce(arm_ty, ret_ty) + && prior_arm.is_none_or(|(_, ty, _)| self.may_coerce(ty, ret_ty)) // The match arms need to unify for the case of `impl Trait`. && !matches!(ret_ty.kind(), ty::Alias(ty::Opaque, ..)) } diff --git a/compiler/rustc_hir_typeck/src/cast.rs b/compiler/rustc_hir_typeck/src/cast.rs index 407191661a4b..8fa6ab8503d0 100644 --- a/compiler/rustc_hir_typeck/src/cast.rs +++ b/compiler/rustc_hir_typeck/src/cast.rs @@ -409,7 +409,7 @@ impl<'a, 'tcx> CastCheck<'tcx> { let mut sugg_mutref = false; if let ty::Ref(reg, cast_ty, mutbl) = *self.cast_ty.kind() { if let ty::RawPtr(expr_ty, _) = *self.expr_ty.kind() - && fcx.can_coerce( + && fcx.may_coerce( Ty::new_ref(fcx.tcx, fcx.tcx.lifetimes.re_erased, expr_ty, mutbl), self.cast_ty, ) @@ -418,14 +418,14 @@ impl<'a, 'tcx> CastCheck<'tcx> { } else if let ty::Ref(expr_reg, expr_ty, expr_mutbl) = *self.expr_ty.kind() && expr_mutbl == Mutability::Not && mutbl == Mutability::Mut - && fcx.can_coerce(Ty::new_mut_ref(fcx.tcx, expr_reg, expr_ty), self.cast_ty) + && fcx.may_coerce(Ty::new_mut_ref(fcx.tcx, expr_reg, expr_ty), self.cast_ty) { sugg_mutref = true; } if !sugg_mutref && sugg == None - && fcx.can_coerce( + && fcx.may_coerce( Ty::new_ref(fcx.tcx, reg, self.expr_ty, mutbl), self.cast_ty, ) @@ -433,7 +433,7 @@ impl<'a, 'tcx> CastCheck<'tcx> { sugg = Some((format!("&{}", mutbl.prefix_str()), false)); } } else if let ty::RawPtr(_, mutbl) = *self.cast_ty.kind() - && fcx.can_coerce( + && fcx.may_coerce( Ty::new_ref(fcx.tcx, fcx.tcx.lifetimes.re_erased, self.expr_ty, mutbl), self.cast_ty, ) diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index bd0b98702983..a6df9e45db50 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1083,21 +1083,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) } - /// Same as `coerce()`, but without side-effects. + /// Probe whether `expr_ty` can be coerced to `target_ty`. This has no side-effects, + /// and may return false positives if types are not yet fully constrained by inference. /// - /// Returns false if the coercion creates any obligations that result in - /// errors. - pub(crate) fn can_coerce(&self, expr_ty: Ty<'tcx>, target: Ty<'tcx>) -> bool { + /// Returns false if the coercion is not possible, or if the coercion creates any + /// sub-obligations that result in errors. + /// + /// This should only be used for diagnostics. + pub(crate) fn may_coerce(&self, expr_ty: Ty<'tcx>, target_ty: Ty<'tcx>) -> bool { // FIXME(-Znext-solver): We need to structurally resolve both types here. let source = self.resolve_vars_with_obligations(expr_ty); - debug!("coercion::can_with_predicates({:?} -> {:?})", source, target); + debug!("coercion::can_with_predicates({:?} -> {:?})", source, target_ty); let cause = self.cause(DUMMY_SP, ObligationCauseCode::ExprAssignable); // We don't ever need two-phase here since we throw out the result of the coercion. // We also just always set `coerce_never` to true, since this is a heuristic. let coerce = Coerce::new(self, cause, AllowTwoPhase::No, true); self.probe(|_| { - let Ok(ok) = coerce.coerce(source, target) else { + let Ok(ok) = coerce.coerce(source, target_ty) else { return false; }; let ocx = ObligationCtxt::new(self); @@ -1369,7 +1372,7 @@ pub fn can_coerce<'tcx>( ) -> bool { let root_ctxt = crate::typeck_root_ctxt::TypeckRootCtxt::new(tcx, body_id); let fn_ctxt = FnCtxt::new(&root_ctxt, param_env, body_id); - fn_ctxt.can_coerce(ty, output_ty) + fn_ctxt.may_coerce(ty, output_ty) } /// CoerceMany encapsulates the pattern you should use when you have diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index b310398a0f1b..7bdd3c95ad18 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1330,9 +1330,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let refs_can_coerce = |lhs: Ty<'tcx>, rhs: Ty<'tcx>| { let lhs = Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_erased, lhs.peel_refs()); let rhs = Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_erased, rhs.peel_refs()); - self.can_coerce(rhs, lhs) + self.may_coerce(rhs, lhs) }; - let (applicability, eq) = if self.can_coerce(rhs_ty, lhs_ty) { + let (applicability, eq) = if self.may_coerce(rhs_ty, lhs_ty) { (Applicability::MachineApplicable, true) } else if refs_can_coerce(rhs_ty, lhs_ty) { // The lhs and rhs are likely missing some references in either side. Subsequent @@ -1349,7 +1349,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let actual_lhs_ty = self.check_expr(rhs_expr); ( Applicability::MaybeIncorrect, - self.can_coerce(rhs_ty, actual_lhs_ty) + self.may_coerce(rhs_ty, actual_lhs_ty) || refs_can_coerce(rhs_ty, actual_lhs_ty), ) } else if let ExprKind::Binary( @@ -1363,7 +1363,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let actual_rhs_ty = self.check_expr(lhs_expr); ( Applicability::MaybeIncorrect, - self.can_coerce(actual_rhs_ty, lhs_ty) + self.may_coerce(actual_rhs_ty, lhs_ty) || refs_can_coerce(actual_rhs_ty, lhs_ty), ) } else { @@ -1414,7 +1414,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.param_env, ) .may_apply(); - if lhs_deref_ty_is_sized && self.can_coerce(rhs_ty, lhs_deref_ty) { + if lhs_deref_ty_is_sized && self.may_coerce(rhs_ty, lhs_deref_ty) { err.span_suggestion_verbose( lhs.span.shrink_to_lo(), "consider dereferencing here to assign to the mutably borrowed value", diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index fa471647d02d..660cbc29d4cd 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -658,7 +658,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { && fn_sig.inputs()[1..] .iter() .zip(input_types.iter()) - .all(|(expected, found)| self.can_coerce(*expected, *found)) + .all(|(expected, found)| self.may_coerce(*expected, *found)) && fn_sig.inputs()[1..].len() == input_types.len() { err.span_suggestion_verbose( @@ -722,7 +722,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let expectation = Expectation::rvalue_hint(self, expected_input_ty); let coerced_ty = expectation.only_has_type(self).unwrap_or(formal_input_ty); - let can_coerce = self.can_coerce(arg_ty, coerced_ty); + let can_coerce = self.may_coerce(arg_ty, coerced_ty); if !can_coerce { return Compatibility::Incompatible(Some(ty::error::TypeError::Sorts( ty::error::ExpectedFound::new(true, coerced_ty, arg_ty), @@ -802,7 +802,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_arg_tys.iter().map(|(ty, _)| *ty).skip(mismatch_idx + tys.len()), ), ) { - if !self.can_coerce(provided_ty, *expected_ty) { + if !self.may_coerce(provided_ty, *expected_ty) { satisfied = false; break; } @@ -1023,7 +1023,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { std::iter::zip(formal_and_expected_inputs.iter(), removed_arg_tys.iter()).all( |((expected_ty, _), (provided_ty, _))| { !provided_ty.references_error() - && self.can_coerce(*provided_ty, *expected_ty) + && self.may_coerce(*provided_ty, *expected_ty) }, ) }; @@ -2124,7 +2124,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let expr_ty = self.typeck_results.borrow().expr_ty(expr); let return_ty = fn_sig.output(); if !matches!(expr.kind, hir::ExprKind::Ret(..)) - && self.can_coerce(expr_ty, return_ty) + && self.may_coerce(expr_ty, return_ty) { found_semi = true; } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 3e9e53261562..3f703c2fcf69 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -261,7 +261,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if let hir::ExprKind::MethodCall(hir::PathSegment { ident: method, .. }, recv_expr, &[], _) = expr.kind && let Some(recv_ty) = self.typeck_results.borrow().expr_ty_opt(recv_expr) - && self.can_coerce(recv_ty, expected) + && self.may_coerce(recv_ty, expected) && let name = method.name.as_str() && (name.starts_with("to_") || name.starts_with("as_") || name == "into") { @@ -349,7 +349,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return true; } - if self.suggest_fn_call(err, expr, found, |output| self.can_coerce(output, expected)) + if self.suggest_fn_call(err, expr, found, |output| self.may_coerce(output, expected)) && let ty::FnDef(def_id, ..) = *found.kind() && let Some(sp) = self.tcx.hir().span_if_local(def_id) { @@ -568,7 +568,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if self.tcx.hir().is_inside_const_context(hir_id) || !expected.is_box() || found.is_box() { return false; } - if self.can_coerce(Ty::new_box(self.tcx, found), expected) { + if self.may_coerce(Ty::new_box(self.tcx, found), expected) { let suggest_boxing = match found.kind() { ty::Tuple(tuple) if tuple.is_empty() => { errors::SuggestBoxing::Unit { start: span.shrink_to_lo(), end: span } @@ -663,7 +663,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; match expected.kind() { ty::Adt(def, _) if Some(def.did()) == pin_did => { - if self.can_coerce(pin_box_found, expected) { + if self.may_coerce(pin_box_found, expected) { debug!("can coerce {:?} to {:?}, suggesting Box::pin", pin_box_found, expected); match found.kind() { ty::Adt(def, _) if def.is_box() => { @@ -689,7 +689,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } true - } else if self.can_coerce(pin_found, expected) { + } else if self.may_coerce(pin_found, expected) { match found.kind() { ty::Adt(def, _) if def.is_box() => { err.help("use `Box::pin`"); @@ -701,7 +701,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { false } } - ty::Adt(def, _) if def.is_box() && self.can_coerce(box_found, expected) => { + ty::Adt(def, _) if def.is_box() && self.may_coerce(box_found, expected) => { // Check if the parent expression is a call to Pin::new. If it // is and we were expecting a Box, ergo Pin>, we // can suggest Box::pin. @@ -884,7 +884,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let ty = Binder::bind_with_vars(ty, bound_vars); let ty = self.normalize(hir_ty.span, ty); let ty = self.tcx.instantiate_bound_regions_with_erased(ty); - if self.can_coerce(expected, ty) { + if self.may_coerce(expected, ty) { err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other { span: hir_ty.span, expected, @@ -1141,12 +1141,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty::Asyncness::No => ty, }; let ty = self.normalize(expr.span, ty); - self.can_coerce(found, ty) + self.may_coerce(found, ty) } hir::FnRetTy::DefaultReturn(_) if in_closure => { self.ret_coercion.as_ref().map_or(false, |ret| { let ret_ty = ret.borrow().expected_ty(); - self.can_coerce(found, ret_ty) + self.may_coerce(found, ret_ty) }) } _ => false, @@ -1510,7 +1510,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_ty }; - if !self.can_coerce(expected_ty, dummy_ty) { + if !self.may_coerce(expected_ty, dummy_ty) { return; } let msg = format!("use `{adt_name}::map_or` to deref inner value of `{adt_name}`"); @@ -1534,7 +1534,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected_ty: Ty<'tcx>, ) { if let ty::Slice(elem_ty) | ty::Array(elem_ty, _) = expected_ty.kind() { - if self.can_coerce(blk_ty, *elem_ty) + if self.may_coerce(blk_ty, *elem_ty) && blk.stmts.is_empty() && blk.rules == hir::BlockCheckMode::DefaultBlock { @@ -1744,7 +1744,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if item_ty.has_param() { return false; } - if self.can_coerce(item_ty, expected_ty) { + if self.may_coerce(item_ty, expected_ty) { err.span_suggestion_verbose( segment.ident.span, format!("try referring to the associated const `{capitalized_name}` instead",), @@ -1804,7 +1804,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // diagnostic in cases where we have `(&&T).clone()` and we expect `T`). && !results.expr_adjustments(callee_expr).iter().any(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(..))) // Check that we're in fact trying to clone into the expected type - && self.can_coerce(*pointee_ty, expected_ty) + && self.may_coerce(*pointee_ty, expected_ty) && let trait_ref = ty::TraitRef::new(self.tcx, clone_trait_did, [expected_ty]) // And the expected type doesn't implement `Clone` && !self.predicate_must_hold_considering_regions(&traits::Obligation::new( @@ -2022,7 +2022,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { return false; }; - if is_ctor || !self.can_coerce(args.type_at(0), expected) { + if is_ctor || !self.may_coerce(args.type_at(0), expected) { return false; } @@ -2293,7 +2293,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .then(|| " (its field is private, but it's local to this crate and its privacy can be changed)".to_string()); let sole_field_ty = sole_field.ty(self.tcx, args); - if self.can_coerce(expr_ty, sole_field_ty) { + if self.may_coerce(expr_ty, sole_field_ty) { let variant_path = with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id)); // FIXME #56861: DRYer prelude filtering @@ -2401,7 +2401,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } let int_type = args.type_at(0); - if !self.can_coerce(expr_ty, int_type) { + if !self.may_coerce(expr_ty, int_type) { return false; } @@ -2585,7 +2585,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_static, checked_ty) } }; - if self.can_coerce(ref_ty, expected) { + if self.may_coerce(ref_ty, expected) { let mut sugg_sp = sp; if let hir::ExprKind::MethodCall(segment, receiver, args, _) = expr.kind { let clone_trait = diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 7f68e19a3139..9dd595411486 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -1934,7 +1934,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { && fn_sig.inputs()[1..] .iter() .zip(args.into_iter()) - .all(|(expected, found)| self.can_coerce(*expected, *found)) + .all(|(expected, found)| self.may_coerce(*expected, *found)) && fn_sig.inputs()[1..].len() == args.len() { err.span_suggestion_verbose( @@ -4148,7 +4148,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return false; }; - if !self.can_coerce(output, expected) { + if !self.may_coerce(output, expected) { return false; } diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index fb78da0a86c6..5ce9bae81c1c 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -1767,7 +1767,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else if inexistent_fields.len() == 1 { match pat_field.pat.kind { PatKind::Lit(expr) - if !self.can_coerce( + if !self.may_coerce( self.typeck_results.borrow().expr_ty(expr), self.field_ty(field.span, field_def, args), ) => {} From e3eba2d9204e9af2ac7a089f61996b25cfecf2d8 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 15 Oct 2024 13:26:56 -0400 Subject: [PATCH 11/16] Don't structurally resolve in may_coerce --- compiler/rustc_hir_typeck/src/coercion.rs | 6 +---- .../lazy_subtyping_of_opaques.stderr | 24 ++++++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index a6df9e45db50..bb2f1f0493a7 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1091,16 +1091,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// /// This should only be used for diagnostics. pub(crate) fn may_coerce(&self, expr_ty: Ty<'tcx>, target_ty: Ty<'tcx>) -> bool { - // FIXME(-Znext-solver): We need to structurally resolve both types here. - let source = self.resolve_vars_with_obligations(expr_ty); - debug!("coercion::can_with_predicates({:?} -> {:?})", source, target_ty); - let cause = self.cause(DUMMY_SP, ObligationCauseCode::ExprAssignable); // We don't ever need two-phase here since we throw out the result of the coercion. // We also just always set `coerce_never` to true, since this is a heuristic. let coerce = Coerce::new(self, cause, AllowTwoPhase::No, true); self.probe(|_| { - let Ok(ok) = coerce.coerce(source, target_ty) else { + let Ok(ok) = coerce.coerce(expr_ty, target_ty) else { return false; }; let ocx = ObligationCtxt::new(self); diff --git a/tests/ui/type-alias-impl-trait/lazy_subtyping_of_opaques.stderr b/tests/ui/type-alias-impl-trait/lazy_subtyping_of_opaques.stderr index 7bc2fa1b09ea..921667f577b4 100644 --- a/tests/ui/type-alias-impl-trait/lazy_subtyping_of_opaques.stderr +++ b/tests/ui/type-alias-impl-trait/lazy_subtyping_of_opaques.stderr @@ -1,3 +1,15 @@ +error[E0308]: mismatched types + --> $DIR/lazy_subtyping_of_opaques.rs:11:5 + | +LL | fn reify_as_tait() -> Thunk { + | ----------- expected `Thunk<_>` because of return type +LL | +LL | Thunk::new(|cont| cont) + | ^^^^^^^^^^^^^^^^^^^^^^^ expected `Thunk<_>`, found `()` + | + = note: expected struct `Thunk<_>` + found unit type `()` + error[E0277]: expected a `FnOnce()` closure, found `()` --> $DIR/lazy_subtyping_of_opaques.rs:11:23 | @@ -12,19 +24,13 @@ error[E0277]: expected a `FnOnce()` closure, found `()` | LL | fn reify_as_tait() -> Thunk { | ^^^^^^^^^^^ expected an `FnOnce()` closure, found `()` +LL | +LL | Thunk::new(|cont| cont) + | ----------------------- return type was inferred to be `{type error}` here | = help: the trait `FnOnce()` is not implemented for `()` = note: wrap the `()` in a closure with no arguments: `|| { /* code */ }` -error[E0308]: mismatched types - --> $DIR/lazy_subtyping_of_opaques.rs:11:5 - | -LL | Thunk::new(|cont| cont) - | ^^^^^^^^^^^^^^^^^^^^^^^ expected `Thunk<_>`, found `()` - | - = note: expected struct `Thunk<_>` - found unit type `()` - error: aborting due to 3 previous errors Some errors have detailed explanations: E0277, E0308. From 9070abab4b6862e42a8ce25eeb4a810301b76dd7 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 15 Oct 2024 13:47:43 -0400 Subject: [PATCH 12/16] Structurally resolve in may_coerce --- compiler/rustc_hir_typeck/src/coercion.rs | 23 +++++++++++++++++-- .../diagnostics/coerce-in-may-coerce.rs | 19 +++++++++++++++ .../diagnostics/coerce-in-may-coerce.stderr | 21 +++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tests/ui/traits/next-solver/diagnostics/coerce-in-may-coerce.rs create mode 100644 tests/ui/traits/next-solver/diagnostics/coerce-in-may-coerce.stderr diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index bb2f1f0493a7..fb9262a59b9c 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1094,12 +1094,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let cause = self.cause(DUMMY_SP, ObligationCauseCode::ExprAssignable); // We don't ever need two-phase here since we throw out the result of the coercion. // We also just always set `coerce_never` to true, since this is a heuristic. - let coerce = Coerce::new(self, cause, AllowTwoPhase::No, true); + let coerce = Coerce::new(self, cause.clone(), AllowTwoPhase::No, true); self.probe(|_| { + // Make sure to structurally resolve the types, since we use + // the `TyKind`s heavily in coercion. + let ocx = ObligationCtxt::new(self); + let structurally_resolve = |ty| { + let ty = self.shallow_resolve(ty); + if self.next_trait_solver() + && let ty::Alias(..) = ty.kind() + { + ocx.structurally_normalize(&cause, self.param_env, ty) + } else { + Ok(ty) + } + }; + let Ok(expr_ty) = structurally_resolve(expr_ty) else { + return false; + }; + let Ok(target_ty) = structurally_resolve(target_ty) else { + return false; + }; + let Ok(ok) = coerce.coerce(expr_ty, target_ty) else { return false; }; - let ocx = ObligationCtxt::new(self); ocx.register_obligations(ok.obligations); ocx.select_where_possible().is_empty() }) diff --git a/tests/ui/traits/next-solver/diagnostics/coerce-in-may-coerce.rs b/tests/ui/traits/next-solver/diagnostics/coerce-in-may-coerce.rs new file mode 100644 index 000000000000..bd3dccad1528 --- /dev/null +++ b/tests/ui/traits/next-solver/diagnostics/coerce-in-may-coerce.rs @@ -0,0 +1,19 @@ +//@ compile-flags: -Znext-solver + +trait Mirror { + type Assoc; +} +impl Mirror for T { + type Assoc = T; +} + +fn arg() -> &'static [i32; 1] { todo!() } + +fn arg_error(x: ::Assoc, y: ()) { todo!() } + +fn main() { + // Should suggest to reverse the args... + // but if we don't normalize the expected, then we don't. + arg_error((), || ()); + //~^ ERROR arguments to this function are incorrect +} diff --git a/tests/ui/traits/next-solver/diagnostics/coerce-in-may-coerce.stderr b/tests/ui/traits/next-solver/diagnostics/coerce-in-may-coerce.stderr new file mode 100644 index 000000000000..1938b3375a51 --- /dev/null +++ b/tests/ui/traits/next-solver/diagnostics/coerce-in-may-coerce.stderr @@ -0,0 +1,21 @@ +error[E0308]: arguments to this function are incorrect + --> $DIR/coerce-in-may-coerce.rs:17:5 + | +LL | arg_error((), || ()); + | ^^^^^^^^^ -- ----- expected `()`, found `{closure@$DIR/coerce-in-may-coerce.rs:17:19: 17:21}` + | | + | expected `::Assoc`, found `()` + | +note: function defined here + --> $DIR/coerce-in-may-coerce.rs:12:4 + | +LL | fn arg_error(x: ::Assoc, y: ()) { todo!() } + | ^^^^^^^^^ -------------------------- ----- +help: swap these arguments + | +LL | arg_error(|| (), ()); + | ~~~~~~~~~~~ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. From c7730989de17b0cbdc1d48481062745d31d5c5a0 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 28 Sep 2024 13:21:39 -0400 Subject: [PATCH 13/16] Don't check unsize goal in MIR validation when opaques remain --- compiler/rustc_mir_transform/src/validate.rs | 11 +++++++++++ tests/crashes/130921.rs | 10 ---------- tests/ui/impl-trait/unsize-cast-validation-rpit.rs | 12 ++++++++++++ 3 files changed, 23 insertions(+), 10 deletions(-) delete mode 100644 tests/crashes/130921.rs create mode 100644 tests/ui/impl-trait/unsize-cast-validation-rpit.rs diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index e353be6a105f..25e68f44456b 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -595,6 +595,17 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { &self, pred: impl Upcast, ty::Predicate<'tcx>>, ) -> bool { + let pred: ty::Predicate<'tcx> = pred.upcast(self.tcx); + + // We sometimes have to use `defining_opaque_types` for predicates + // to succeed here and figuring out how exactly that should work + // is annoying. It is harmless enough to just not validate anything + // in that case. We still check this after analysis as all opaque + // types have been revealed at this point. + if pred.has_opaque_types() { + return true; + } + let infcx = self.tcx.infer_ctxt().build(); let ocx = ObligationCtxt::new(&infcx); ocx.register_obligation(Obligation::new( diff --git a/tests/crashes/130921.rs b/tests/crashes/130921.rs deleted file mode 100644 index b7cb1303937e..000000000000 --- a/tests/crashes/130921.rs +++ /dev/null @@ -1,10 +0,0 @@ -//@ known-bug: #130921 -//@ compile-flags: -Zvalidate-mir -Copt-level=0 --crate-type lib - -pub fn hello() -> [impl Sized; 2] { - if false { - let x = hello(); - let _: &[i32] = &x; - } - todo!() -} diff --git a/tests/ui/impl-trait/unsize-cast-validation-rpit.rs b/tests/ui/impl-trait/unsize-cast-validation-rpit.rs new file mode 100644 index 000000000000..cace30aca8a6 --- /dev/null +++ b/tests/ui/impl-trait/unsize-cast-validation-rpit.rs @@ -0,0 +1,12 @@ +//@ check-pass +//@ compile-flags: -Zvalidate-mir + +fn hello() -> &'static [impl Sized; 0] { + if false { + let x = hello(); + let _: &[i32] = x; + } + &[] +} + +fn main() {} From 476ea45c68223e4b2f9674d20f1aff8dd52fe1fb Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Wed, 16 Oct 2024 12:22:14 +0200 Subject: [PATCH 14/16] Emscripten: Xfail backtrace ui tests It is possible to link libunwind and use the normal backtrace code, but it fails to symbolize stack traces. I investigated and could get the list of instruction pointers and symbol names, but I'm not sure how to use the dwarf info to map from instruction pointer to source location. In any case, fixing this is probably not a high priority. See https://github.com/rust-lang/rust/issues/131738 --- tests/ui/backtrace/dylib-dep.rs | 1 + tests/ui/backtrace/line-tables-only.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/ui/backtrace/dylib-dep.rs b/tests/ui/backtrace/dylib-dep.rs index fcd1f92e28eb..a41931ad5489 100644 --- a/tests/ui/backtrace/dylib-dep.rs +++ b/tests/ui/backtrace/dylib-dep.rs @@ -9,6 +9,7 @@ //@ ignore-musl musl doesn't support dynamic libraries (at least when the original test was written). //@ needs-unwind //@ compile-flags: -g -Copt-level=0 -Cstrip=none -Cforce-frame-pointers=yes +//@ ignore-emscripten Requires custom symbolization code //@ aux-crate: dylib_dep_helper=dylib-dep-helper.rs //@ aux-crate: auxiliary=dylib-dep-helper-aux.rs //@ run-pass diff --git a/tests/ui/backtrace/line-tables-only.rs b/tests/ui/backtrace/line-tables-only.rs index 044f59e483ab..6624c71e184b 100644 --- a/tests/ui/backtrace/line-tables-only.rs +++ b/tests/ui/backtrace/line-tables-only.rs @@ -10,6 +10,7 @@ //@ compile-flags: -Cstrip=none -Cdebuginfo=line-tables-only //@ ignore-android FIXME #17520 //@ ignore-fuchsia Backtraces not symbolized +//@ ignore-emscripten Requires custom symbolization code //@ needs-unwind //@ aux-build: line-tables-only-helper.rs From 89ea9e44f073a67a2b92de2ede5985acb1f810ec Mon Sep 17 00:00:00 2001 From: Michal Piotrowski Date: Wed, 16 Oct 2024 12:36:44 +0200 Subject: [PATCH 15/16] Fix trivially_copy_pass_by_ref in stable_mir --- compiler/stable_mir/src/mir/body.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/stable_mir/src/mir/body.rs b/compiler/stable_mir/src/mir/body.rs index 742469a1c932..f96487cc53c9 100644 --- a/compiler/stable_mir/src/mir/body.rs +++ b/compiler/stable_mir/src/mir/body.rs @@ -1028,7 +1028,7 @@ impl ProjectionElem { ProjectionElem::Field(_idx, fty) => Ok(*fty), ProjectionElem::Index(_) | ProjectionElem::ConstantIndex { .. } => Self::index_ty(ty), ProjectionElem::Subslice { from, to, from_end } => { - Self::subslice_ty(ty, from, to, from_end) + Self::subslice_ty(ty, *from, *to, *from_end) } ProjectionElem::Downcast(_) => Ok(ty), ProjectionElem::OpaqueCast(ty) | ProjectionElem::Subtype(ty) => Ok(*ty), @@ -1039,13 +1039,13 @@ impl ProjectionElem { ty.kind().builtin_index().ok_or_else(|| error!("Cannot index non-array type: {ty:?}")) } - fn subslice_ty(ty: Ty, from: &u64, to: &u64, from_end: &bool) -> Result { + fn subslice_ty(ty: Ty, from: u64, to: u64, from_end: bool) -> Result { let ty_kind = ty.kind(); match ty_kind { TyKind::RigidTy(RigidTy::Slice(..)) => Ok(ty), TyKind::RigidTy(RigidTy::Array(inner, _)) if !from_end => Ty::try_new_array( inner, - to.checked_sub(*from).ok_or_else(|| error!("Subslice overflow: {from}..{to}"))?, + to.checked_sub(from).ok_or_else(|| error!("Subslice overflow: {from}..{to}"))?, ), TyKind::RigidTy(RigidTy::Array(inner, size)) => { let size = size.eval_target_usize()?; From a10a44956e4efddf4fdf0f7a8b10796b9ae965ea Mon Sep 17 00:00:00 2001 From: Michal Piotrowski Date: Wed, 16 Oct 2024 13:31:23 +0200 Subject: [PATCH 16/16] Fix needless_lifetimes in stable_mir --- compiler/stable_mir/src/mir/visit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/stable_mir/src/mir/visit.rs b/compiler/stable_mir/src/mir/visit.rs index e2d1ff7fdd3a..85205528979c 100644 --- a/compiler/stable_mir/src/mir/visit.rs +++ b/compiler/stable_mir/src/mir/visit.rs @@ -487,7 +487,7 @@ pub struct PlaceRef<'a> { pub projection: &'a [ProjectionElem], } -impl<'a> PlaceRef<'a> { +impl PlaceRef<'_> { /// Get the type of this place. pub fn ty(&self, locals: &[LocalDecl]) -> Result { self.projection.iter().fold(Ok(locals[self.local].ty), |place_ty, elem| elem.ty(place_ty?))