From edc97a0df2fbff05dd03483938425d1590440c82 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Fri, 17 May 2024 08:22:29 +0000 Subject: [PATCH 01/12] Fix verbatim paths used with include! When using `concat!` to join paths, the Unix path separator (`/`) is often used. This breaks on Windows if the base path is a verbatim path (i.e. starts with `\\?\`). --- compiler/rustc_expand/src/base.rs | 8 +++++++- .../import-macro-verbatim/include/include.txt | 1 + tests/run-make/import-macro-verbatim/rmake.rs | 8 ++++++++ tests/run-make/import-macro-verbatim/verbatim.rs | 12 ++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/run-make/import-macro-verbatim/include/include.txt create mode 100644 tests/run-make/import-macro-verbatim/rmake.rs create mode 100644 tests/run-make/import-macro-verbatim/verbatim.rs diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index c195d69258899..23ff735aac8cb 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -1,5 +1,6 @@ use std::default::Default; use std::iter; +use std::path::Component::Prefix; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -1293,7 +1294,12 @@ pub fn resolve_path(sess: &Session, path: impl Into, span: Span) -> PRe base_path.push(path); Ok(base_path) } else { - Ok(path) + // This ensures that Windows verbatim paths are fixed if mixed path separators are used, + // which can happen when `concat!` is used to join paths. + match path.components().next() { + Some(Prefix(prefix)) if prefix.kind().is_verbatim() => Ok(path.components().collect()), + _ => Ok(path), + } } } diff --git a/tests/run-make/import-macro-verbatim/include/include.txt b/tests/run-make/import-macro-verbatim/include/include.txt new file mode 100644 index 0000000000000..63d71b14c1df1 --- /dev/null +++ b/tests/run-make/import-macro-verbatim/include/include.txt @@ -0,0 +1 @@ +static TEST: &str = "Hello World!"; diff --git a/tests/run-make/import-macro-verbatim/rmake.rs b/tests/run-make/import-macro-verbatim/rmake.rs new file mode 100644 index 0000000000000..d2bf626e0aaa7 --- /dev/null +++ b/tests/run-make/import-macro-verbatim/rmake.rs @@ -0,0 +1,8 @@ +//@ only-windows other platforms do not have Windows verbatim paths +use run_make_support::rustc; +fn main() { + // Canonicalizing the path ensures that it's verbatim (i.e. starts with `\\?\`) + let mut path = std::fs::canonicalize(file!()).unwrap(); + path.pop(); + rustc().input("verbatim.rs").env("VERBATIM_DIR", path).run(); +} diff --git a/tests/run-make/import-macro-verbatim/verbatim.rs b/tests/run-make/import-macro-verbatim/verbatim.rs new file mode 100644 index 0000000000000..56a83673c1f57 --- /dev/null +++ b/tests/run-make/import-macro-verbatim/verbatim.rs @@ -0,0 +1,12 @@ +//! Include a file by concating the verbatim path using `/` instead of `\` + +include!(concat!(env!("VERBATIM_DIR"), "/include/include.txt")); +fn main() { + assert_eq!(TEST, "Hello World!"); + + let s = include_str!(concat!(env!("VERBATIM_DIR"), "/include/include.txt")); + assert_eq!(s, "static TEST: &str = \"Hello World!\";\n"); + + let b = include_bytes!(concat!(env!("VERBATIM_DIR"), "/include/include.txt")); + assert_eq!(b, b"static TEST: &str = \"Hello World!\";\n"); +} From 9368b9f57e7e3a82b5becdfd17a1eeb606e6fd5f Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 30 Sep 2024 00:34:58 -0400 Subject: [PATCH 02/12] Debug assert that unevaluated consts have the right substs --- compiler/rustc_middle/src/ty/consts.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_middle/src/ty/consts.rs b/compiler/rustc_middle/src/ty/consts.rs index 73d0acf95f4f5..ebc6cac3c429c 100644 --- a/compiler/rustc_middle/src/ty/consts.rs +++ b/compiler/rustc_middle/src/ty/consts.rs @@ -109,6 +109,7 @@ impl<'tcx> Const<'tcx> { #[inline] pub fn new_unevaluated(tcx: TyCtxt<'tcx>, uv: ty::UnevaluatedConst<'tcx>) -> Const<'tcx> { + tcx.debug_assert_args_compatible(uv.def, uv.args); Const::new(tcx, ty::ConstKind::Unevaluated(uv)) } From 2239f1c5cd6da232b5d92765deace77519bdd209 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 30 Sep 2024 01:00:38 -0400 Subject: [PATCH 03/12] Validate ExistentialPredicate args --- .../src/hir_ty_lowering/dyn_compatibility.rs | 3 +- compiler/rustc_middle/src/ty/context.rs | 20 +++++++++ compiler/rustc_passes/src/reachable.rs | 2 +- compiler/rustc_privacy/src/lib.rs | 8 ++-- .../cfi/typeid/itanium_cxx_abi/transform.rs | 21 +++++---- .../rustc_smir/src/rustc_internal/internal.rs | 20 +++++---- .../rustc_smir/src/rustc_smir/convert/ty.rs | 4 +- compiler/rustc_type_ir/src/interner.rs | 8 ++-- compiler/rustc_type_ir/src/predicate.rs | 45 ++++++++++++++++++- compiler/rustc_type_ir/src/relate.rs | 4 +- 10 files changed, 103 insertions(+), 32 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs index e7b8e6e69b0c6..ff90f89f66681 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs @@ -250,7 +250,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } }) .collect(); - let args = tcx.mk_args(&args); let span = i.bottom().1; let empty_generic_args = hir_trait_bounds.iter().any(|(hir_bound, _)| { @@ -283,7 +282,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .emit(); } - ty::ExistentialTraitRef { def_id: trait_ref.def_id, args } + ty::ExistentialTraitRef::new(tcx, trait_ref.def_id, args) }) }); diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 2ffb273cb6fc9..711dc80e933e3 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -279,6 +279,26 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.debug_assert_args_compatible(def_id, args); } + /// Assert that the args from an `ExistentialTraitRef` or `ExistentialProjection` + /// are compatible with the `DefId`. Since we're missing a `Self` type, stick on + /// a dummy self type and forward to `debug_assert_args_compatible`. + fn debug_assert_existential_args_compatible( + self, + def_id: Self::DefId, + args: Self::GenericArgs, + ) { + // FIXME: We could perhaps add a `skip: usize` to `debug_assert_args_compatible` + // to avoid needing to reintern the set of args... + if cfg!(debug_assertions) { + self.debug_assert_args_compatible( + def_id, + self.mk_args_from_iter( + [self.types.trait_object_dummy_self.into()].into_iter().chain(args.iter()), + ), + ); + } + } + fn mk_type_list_from_iter(self, args: I) -> T::Output where I: Iterator, diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs index 925ee26202283..0cb0e961c0e46 100644 --- a/compiler/rustc_passes/src/reachable.rs +++ b/compiler/rustc_passes/src/reachable.rs @@ -322,7 +322,7 @@ impl<'tcx> ReachableContext<'tcx> { self.visit(ty); // Manually visit to actually see the trait's `DefId`. Type visitors won't see it if let Some(trait_ref) = dyn_ty.principal() { - let ExistentialTraitRef { def_id, args } = trait_ref.skip_binder(); + let ExistentialTraitRef { def_id, args, .. } = trait_ref.skip_binder(); self.visit_def_id(def_id, "", &""); self.visit(args); } diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index 9094b00fbfb44..0f820654ec810 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -32,8 +32,8 @@ use rustc_middle::middle::privacy::{EffectiveVisibilities, EffectiveVisibility, use rustc_middle::query::Providers; use rustc_middle::ty::print::PrintTraitRefExt as _; use rustc_middle::ty::{ - self, Const, GenericArgs, GenericParamDefKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, - TypeVisitable, TypeVisitor, + self, Const, GenericParamDefKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, + TypeVisitor, }; use rustc_middle::{bug, span_bug}; use rustc_session::lint; @@ -246,10 +246,10 @@ where ty::ExistentialPredicate::Trait(trait_ref) => trait_ref, ty::ExistentialPredicate::Projection(proj) => proj.trait_ref(tcx), ty::ExistentialPredicate::AutoTrait(def_id) => { - ty::ExistentialTraitRef { def_id, args: GenericArgs::empty() } + ty::ExistentialTraitRef::new(tcx, def_id, ty::GenericArgs::empty()) } }; - let ty::ExistentialTraitRef { def_id, args: _ } = trait_ref; + let ty::ExistentialTraitRef { def_id, .. } = trait_ref; try_visit!(self.def_id_visitor.visit_def_id(def_id, "trait", &trait_ref)); } } diff --git a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs index 5f7184a42407c..a97a00f6ac086 100644 --- a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs +++ b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs @@ -245,11 +245,15 @@ fn trait_object_ty<'tcx>(tcx: TyCtxt<'tcx>, poly_trait_ref: ty::PolyTraitRef<'tc alias_ty.to_ty(tcx), ); debug!("Resolved {:?} -> {resolved}", alias_ty.to_ty(tcx)); - ty::ExistentialPredicate::Projection(ty::ExistentialProjection { - def_id: assoc_ty.def_id, - args: ty::ExistentialTraitRef::erase_self_ty(tcx, super_trait_ref).args, - term: resolved.into(), - }) + ty::ExistentialPredicate::Projection( + ty::ExistentialProjection::erase_self_ty( + tcx, + ty::ProjectionPredicate { + projection_term: alias_ty.into(), + term: resolved.into(), + }, + ), + ) }) }) }) @@ -318,10 +322,11 @@ pub fn transform_instance<'tcx>( .lang_items() .drop_trait() .unwrap_or_else(|| bug!("typeid_for_instance: couldn't get drop_trait lang item")); - let predicate = ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef { + let predicate = ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::new_from_args( + tcx, def_id, - args: List::empty(), - }); + ty::List::empty(), + )); let predicates = tcx.mk_poly_existential_predicates(&[ty::Binder::dummy(predicate)]); let self_ty = Ty::new_dynamic(tcx, predicates, tcx.lifetimes.re_erased, ty::Dyn); instance.args = tcx.mk_args_trait(self_ty, List::empty()); diff --git a/compiler/rustc_smir/src/rustc_internal/internal.rs b/compiler/rustc_smir/src/rustc_internal/internal.rs index e9c3b3ffc1ddd..7be7db1fd3d33 100644 --- a/compiler/rustc_smir/src/rustc_internal/internal.rs +++ b/compiler/rustc_smir/src/rustc_internal/internal.rs @@ -380,11 +380,12 @@ impl RustcInternal for ExistentialProjection { type T<'tcx> = rustc_ty::ExistentialProjection<'tcx>; fn internal<'tcx>(&self, tables: &mut Tables<'_>, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { - rustc_ty::ExistentialProjection { - def_id: self.def_id.0.internal(tables, tcx), - args: self.generic_args.internal(tables, tcx), - term: self.term.internal(tables, tcx), - } + rustc_ty::ExistentialProjection::new_from_args( + tcx, + self.def_id.0.internal(tables, tcx), + self.generic_args.internal(tables, tcx), + self.term.internal(tables, tcx), + ) } } @@ -403,10 +404,11 @@ impl RustcInternal for ExistentialTraitRef { type T<'tcx> = rustc_ty::ExistentialTraitRef<'tcx>; fn internal<'tcx>(&self, tables: &mut Tables<'_>, tcx: TyCtxt<'tcx>) -> Self::T<'tcx> { - rustc_ty::ExistentialTraitRef { - def_id: self.def_id.0.internal(tables, tcx), - args: self.generic_args.internal(tables, tcx), - } + rustc_ty::ExistentialTraitRef::new_from_args( + tcx, + self.def_id.0.internal(tables, tcx), + self.generic_args.internal(tables, tcx), + ) } } diff --git a/compiler/rustc_smir/src/rustc_smir/convert/ty.rs b/compiler/rustc_smir/src/rustc_smir/convert/ty.rs index b9372283febf8..ef2ee9a166aff 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/ty.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/ty.rs @@ -68,7 +68,7 @@ impl<'tcx> Stable<'tcx> for ty::ExistentialTraitRef<'tcx> { type T = stable_mir::ty::ExistentialTraitRef; fn stable(&self, tables: &mut Tables<'_>) -> Self::T { - let ty::ExistentialTraitRef { def_id, args } = self; + let ty::ExistentialTraitRef { def_id, args, .. } = self; stable_mir::ty::ExistentialTraitRef { def_id: tables.trait_def(*def_id), generic_args: args.stable(tables), @@ -95,7 +95,7 @@ impl<'tcx> Stable<'tcx> for ty::ExistentialProjection<'tcx> { type T = stable_mir::ty::ExistentialProjection; fn stable(&self, tables: &mut Tables<'_>) -> Self::T { - let ty::ExistentialProjection { def_id, args, term } = self; + let ty::ExistentialProjection { def_id, args, term, .. } = self; stable_mir::ty::ExistentialProjection { def_id: tables.trait_def(*def_id), generic_args: args.stable(tables), diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index b2ac67efef611..3f5d19108f229 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -15,9 +15,7 @@ use crate::solve::{ CanonicalInput, ExternalConstraintsData, PredefinedOpaquesData, QueryResult, SolverMode, }; use crate::visit::{Flags, TypeSuperVisitable, TypeVisitable}; -use crate::{ - search_graph, {self as ty}, -}; +use crate::{self as ty, search_graph}; pub trait Interner: Sized @@ -173,6 +171,10 @@ pub trait Interner: fn debug_assert_args_compatible(self, def_id: Self::DefId, args: Self::GenericArgs); + /// Assert that the args from an `ExistentialTraitRef` or `ExistentialProjection` + /// are compatible with the `DefId`. + fn debug_assert_existential_args_compatible(self, def_id: Self::DefId, args: Self::GenericArgs); + fn mk_type_list_from_iter(self, args: I) -> T::Output where I: Iterator, diff --git a/compiler/rustc_type_ir/src/predicate.rs b/compiler/rustc_type_ir/src/predicate.rs index 76065c10d190e..fed689652a59b 100644 --- a/compiler/rustc_type_ir/src/predicate.rs +++ b/compiler/rustc_type_ir/src/predicate.rs @@ -289,9 +289,26 @@ impl ty::Binder> { pub struct ExistentialTraitRef { pub def_id: I::DefId, pub args: I::GenericArgs, + /// This field exists to prevent the creation of `ExistentialTraitRef` without + /// calling [`ExistentialTraitRef::new_from_args`]. + _use_existential_trait_ref_new_instead: (), } impl ExistentialTraitRef { + pub fn new_from_args(interner: I, trait_def_id: I::DefId, args: I::GenericArgs) -> Self { + interner.debug_assert_existential_args_compatible(trait_def_id, args); + Self { def_id: trait_def_id, args, _use_existential_trait_ref_new_instead: () } + } + + pub fn new( + interner: I, + trait_def_id: I::DefId, + args: impl IntoIterator>, + ) -> Self { + let args = interner.mk_args_from_iter(args.into_iter().map(Into::into)); + Self::new_from_args(interner, trait_def_id, args) + } + pub fn erase_self_ty(interner: I, trait_ref: TraitRef) -> ExistentialTraitRef { // Assert there is a Self. trait_ref.args.type_at(0); @@ -299,6 +316,7 @@ impl ExistentialTraitRef { ExistentialTraitRef { def_id: trait_ref.def_id, args: interner.mk_args(&trait_ref.args.as_slice()[1..]), + _use_existential_trait_ref_new_instead: (), } } @@ -336,9 +354,33 @@ pub struct ExistentialProjection { pub def_id: I::DefId, pub args: I::GenericArgs, pub term: I::Term, + + /// This field exists to prevent the creation of `ExistentialProjection` + /// without using [`ExistentialProjection::new_from_args`]. + use_existential_projection_new_instead: (), } impl ExistentialProjection { + pub fn new_from_args( + interner: I, + def_id: I::DefId, + args: I::GenericArgs, + term: I::Term, + ) -> ExistentialProjection { + interner.debug_assert_existential_args_compatible(def_id, args); + Self { def_id, args, term, use_existential_projection_new_instead: () } + } + + pub fn new( + interner: I, + def_id: I::DefId, + args: impl IntoIterator>, + term: I::Term, + ) -> ExistentialProjection { + let args = interner.mk_args_from_iter(args.into_iter().map(Into::into)); + Self::new_from_args(interner, def_id, args, term) + } + /// Extracts the underlying existential trait reference from this projection. /// For example, if this is a projection of `exists T. ::Item == X`, /// then this function would return an `exists T. T: Iterator` existential trait @@ -347,7 +389,7 @@ impl ExistentialProjection { let def_id = interner.parent(self.def_id); let args_count = interner.generics_of(def_id).count() - 1; let args = interner.mk_args(&self.args.as_slice()[..args_count]); - ExistentialTraitRef { def_id, args } + ExistentialTraitRef { def_id, args, _use_existential_trait_ref_new_instead: () } } pub fn with_self_ty(&self, interner: I, self_ty: I::Ty) -> ProjectionPredicate { @@ -372,6 +414,7 @@ impl ExistentialProjection { def_id: projection_predicate.projection_term.def_id, args: interner.mk_args(&projection_predicate.projection_term.args.as_slice()[1..]), term: projection_predicate.term, + use_existential_projection_new_instead: (), } } } diff --git a/compiler/rustc_type_ir/src/relate.rs b/compiler/rustc_type_ir/src/relate.rs index 9c725f34d8e5b..61dd2390c8035 100644 --- a/compiler/rustc_type_ir/src/relate.rs +++ b/compiler/rustc_type_ir/src/relate.rs @@ -308,7 +308,7 @@ impl Relate for ty::ExistentialProjection { a.args, b.args, )?; - Ok(ty::ExistentialProjection { def_id: a.def_id, args, term }) + Ok(ty::ExistentialProjection::new_from_args(relation.cx(), a.def_id, args, term)) } } } @@ -348,7 +348,7 @@ impl Relate for ty::ExistentialTraitRef { })) } else { let args = relate_args_invariantly(relation, a.args, b.args)?; - Ok(ty::ExistentialTraitRef { def_id: a.def_id, args }) + Ok(ty::ExistentialTraitRef::new_from_args(relation.cx(), a.def_id, args)) } } } From 5e8820caaa91f3d7740303e9e7047fbfb82281ce Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 11 Oct 2024 10:59:09 -0400 Subject: [PATCH 04/12] Add a note for ? on future in sync function --- .../src/error_reporting/traits/suggestions.rs | 98 +++++++++++-------- tests/ui/async-await/try-in-sync.rs | 9 ++ tests/ui/async-await/try-in-sync.stderr | 18 ++++ 3 files changed, 82 insertions(+), 43 deletions(-) create mode 100644 tests/ui/async-await/try-in-sync.rs create mode 100644 tests/ui/async-await/try-in-sync.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 87834c329e19f..8dcdee736ff3c 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -3594,52 +3594,64 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { trait_pred: ty::PolyTraitPredicate<'tcx>, span: Span, ) { - if let Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)) = - self.tcx.coroutine_kind(obligation.cause.body_id) - { - let future_trait = self.tcx.require_lang_item(LangItem::Future, None); + let future_trait = self.tcx.require_lang_item(LangItem::Future, None); - let self_ty = self.resolve_vars_if_possible(trait_pred.self_ty()); - let impls_future = self.type_implements_trait( - future_trait, - [self.tcx.instantiate_bound_regions_with_erased(self_ty)], - obligation.param_env, - ); - if !impls_future.must_apply_modulo_regions() { - return; - } + let self_ty = self.resolve_vars_if_possible(trait_pred.self_ty()); + let impls_future = self.type_implements_trait( + future_trait, + [self.tcx.instantiate_bound_regions_with_erased(self_ty)], + obligation.param_env, + ); + if !impls_future.must_apply_modulo_regions() { + return; + } - let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0]; - // `::Output` - let projection_ty = trait_pred.map_bound(|trait_pred| { - Ty::new_projection( - self.tcx, - item_def_id, - // Future::Output has no args - [trait_pred.self_ty()], - ) - }); - let InferOk { value: projection_ty, .. } = - self.at(&obligation.cause, obligation.param_env).normalize(projection_ty); + let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0]; + // `::Output` + let projection_ty = trait_pred.map_bound(|trait_pred| { + Ty::new_projection( + self.tcx, + item_def_id, + // Future::Output has no args + [trait_pred.self_ty()], + ) + }); + let InferOk { value: projection_ty, .. } = + self.at(&obligation.cause, obligation.param_env).normalize(projection_ty); - debug!( - normalized_projection_type = ?self.resolve_vars_if_possible(projection_ty) - ); - let try_obligation = self.mk_trait_obligation_with_new_self_ty( - obligation.param_env, - trait_pred.map_bound(|trait_pred| (trait_pred, projection_ty.skip_binder())), - ); - debug!(try_trait_obligation = ?try_obligation); - if self.predicate_may_hold(&try_obligation) - && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) - && snippet.ends_with('?') - { - err.span_suggestion_verbose( - span.with_hi(span.hi() - BytePos(1)).shrink_to_hi(), - "consider `await`ing on the `Future`", - ".await", - Applicability::MaybeIncorrect, - ); + debug!( + normalized_projection_type = ?self.resolve_vars_if_possible(projection_ty) + ); + let try_obligation = self.mk_trait_obligation_with_new_self_ty( + obligation.param_env, + trait_pred.map_bound(|trait_pred| (trait_pred, projection_ty.skip_binder())), + ); + debug!(try_trait_obligation = ?try_obligation); + if self.predicate_may_hold(&try_obligation) + && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) + && snippet.ends_with('?') + { + match self.tcx.coroutine_kind(obligation.cause.body_id) { + Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)) => { + err.span_suggestion_verbose( + span.with_hi(span.hi() - BytePos(1)).shrink_to_hi(), + "consider `await`ing on the `Future`", + ".await", + Applicability::MaybeIncorrect, + ); + } + _ => { + let mut span: MultiSpan = span.with_lo(span.hi() - BytePos(1)).into(); + span.push_span_label( + self.tcx.def_span(obligation.cause.body_id), + "this is not `async`", + ); + err.span_note( + span, + "this implements `Future` and its output type supports \ + `?`, but the future cannot be awaited in a synchronous function", + ); + } } } } diff --git a/tests/ui/async-await/try-in-sync.rs b/tests/ui/async-await/try-in-sync.rs new file mode 100644 index 0000000000000..81d72c3fb9a2a --- /dev/null +++ b/tests/ui/async-await/try-in-sync.rs @@ -0,0 +1,9 @@ +//@ edition: 2021 + +async fn foo() -> Result<(), ()> { todo!() } + +fn main() -> Result<(), ()> { + foo()?; + //~^ ERROR the `?` operator can only be applied to values that implement `Try` + Ok(()) +} diff --git a/tests/ui/async-await/try-in-sync.stderr b/tests/ui/async-await/try-in-sync.stderr new file mode 100644 index 0000000000000..bc7a6bd015129 --- /dev/null +++ b/tests/ui/async-await/try-in-sync.stderr @@ -0,0 +1,18 @@ +error[E0277]: the `?` operator can only be applied to values that implement `Try` + --> $DIR/try-in-sync.rs:6:5 + | +LL | foo()?; + | ^^^^^^ the `?` operator cannot be applied to type `impl Future>` + | + = help: the trait `Try` is not implemented for `impl Future>` +note: this implements `Future` and its output type supports `?`, but the future cannot be awaited in a synchronous function + --> $DIR/try-in-sync.rs:6:10 + | +LL | fn main() -> Result<(), ()> { + | --------------------------- this is not `async` +LL | foo()?; + | ^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. From c8b71ef3dd83f1d6b99d128ff2fc3b99c9ce28ab Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 12 Oct 2024 06:11:11 -0400 Subject: [PATCH 05/12] Also note for fields --- compiler/rustc_hir_typeck/src/expr.rs | 28 +++++++++++++++++------ tests/ui/async-await/field-in-sync.rs | 13 +++++++++++ tests/ui/async-await/field-in-sync.stderr | 17 ++++++++++++++ tests/ui/async-await/issue-61076.stderr | 4 ++-- 4 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 tests/ui/async-await/field-in-sync.rs create mode 100644 tests/ui/async-await/field-in-sync.stderr diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index b310398a0f1ba..bd35a3a8e8891 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -10,7 +10,8 @@ use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_data_structures::unord::UnordMap; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, ErrorGuaranteed, StashKey, Subdiagnostic, pluralize, struct_span_code_err, + Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, Subdiagnostic, pluralize, + struct_span_code_err, }; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefId; @@ -2757,12 +2758,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { field_ident.span, "field not available in `impl Future`, but it is available in its `Output`", ); - err.span_suggestion_verbose( - base.span.shrink_to_hi(), - "consider `await`ing on the `Future` and access the field of its `Output`", - ".await", - Applicability::MaybeIncorrect, - ); + match self.tcx.coroutine_kind(self.body_id) { + Some(hir::CoroutineKind::Desugared(hir::CoroutineDesugaring::Async, _)) => { + err.span_suggestion_verbose( + base.span.shrink_to_hi(), + "consider `await`ing on the `Future` to access the field", + ".await", + Applicability::MaybeIncorrect, + ); + } + _ => { + let mut span: MultiSpan = base.span.into(); + span.push_span_label(self.tcx.def_span(self.body_id), "this is not `async`"); + err.span_note( + span, + "this implements `Future` and its output type has the field, \ + but the future cannot be awaited in a synchronous function", + ); + } + } } fn ban_nonexisting_field( diff --git a/tests/ui/async-await/field-in-sync.rs b/tests/ui/async-await/field-in-sync.rs new file mode 100644 index 0000000000000..586980c6e2be6 --- /dev/null +++ b/tests/ui/async-await/field-in-sync.rs @@ -0,0 +1,13 @@ +//@ edition: 2021 + +struct S { + field: (), +} + +async fn foo() -> S { todo!() } + +fn main() -> Result<(), ()> { + foo().field; + //~^ ERROR no field `field` on type `impl Future` + Ok(()) +} diff --git a/tests/ui/async-await/field-in-sync.stderr b/tests/ui/async-await/field-in-sync.stderr new file mode 100644 index 0000000000000..7be30339c275b --- /dev/null +++ b/tests/ui/async-await/field-in-sync.stderr @@ -0,0 +1,17 @@ +error[E0609]: no field `field` on type `impl Future` + --> $DIR/field-in-sync.rs:10:11 + | +LL | foo().field; + | ^^^^^ field not available in `impl Future`, but it is available in its `Output` + | +note: this implements `Future` and its output type has the field, but the future cannot be awaited in a synchronous function + --> $DIR/field-in-sync.rs:10:5 + | +LL | fn main() -> Result<(), ()> { + | --------------------------- this is not `async` +LL | foo().field; + | ^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0609`. diff --git a/tests/ui/async-await/issue-61076.stderr b/tests/ui/async-await/issue-61076.stderr index 44de282988baa..b8478c8d1388d 100644 --- a/tests/ui/async-await/issue-61076.stderr +++ b/tests/ui/async-await/issue-61076.stderr @@ -28,7 +28,7 @@ error[E0609]: no field `0` on type `impl Future` LL | let _: i32 = tuple().0; | ^ field not available in `impl Future`, but it is available in its `Output` | -help: consider `await`ing on the `Future` and access the field of its `Output` +help: consider `await`ing on the `Future` to access the field | LL | let _: i32 = tuple().await.0; | ++++++ @@ -39,7 +39,7 @@ error[E0609]: no field `a` on type `impl Future` LL | let _: i32 = struct_().a; | ^ field not available in `impl Future`, but it is available in its `Output` | -help: consider `await`ing on the `Future` and access the field of its `Output` +help: consider `await`ing on the `Future` to access the field | LL | let _: i32 = struct_().await.a; | ++++++ From d11a9702ab560ebd54dca1823d6106e1a8811d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ma=C4=87kowski?= Date: Tue, 15 Oct 2024 13:26:09 +0200 Subject: [PATCH 06/12] Add doc(plugins), doc(passes), etc. to INVALID_DOC_ATTRIBUTES --- compiler/rustc_passes/messages.ftl | 13 ++++++++ compiler/rustc_passes/src/check_attr.rs | 26 ++++++++++------ compiler/rustc_passes/src/errors.rs | 21 +++++++++++++ src/librustdoc/core.rs | 39 ++---------------------- tests/rustdoc-ui/deprecated-attrs.rs | 19 +++++++----- tests/rustdoc-ui/deprecated-attrs.stderr | 35 +++++++++++---------- 6 files changed, 84 insertions(+), 69 deletions(-) diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index e5a14f6a15658..cd866edaabdc6 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -245,6 +245,19 @@ passes_doc_test_unknown_include = unknown `doc` attribute `{$path}` .suggestion = use `doc = include_str!` instead +passes_doc_test_unknown_passes = + unknown `doc` attribute `{$path}` + .note = `doc` attribute `{$path}` no longer functions; see issue #44136 + .label = no longer functions + .help = you may want to use `doc(document_private_items)` + .no_op_note = `doc({$path})` is now a no-op + +passes_doc_test_unknown_plugins = + unknown `doc` attribute `{$path}` + .note = `doc` attribute `{$path}` no longer functions; see issue #44136 and CVE-2018-1000622 + .label = no longer functions + .no_op_note = `doc({$path})` is now a no-op + passes_doc_test_unknown_spotlight = unknown `doc` attribute `{$path}` .note = `doc(spotlight)` was renamed to `doc(notable_trait)` diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 7ce29260e3676..f8cb34f791b8e 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1187,15 +1187,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { sym::masked => self.check_doc_masked(attr, meta, hir_id, target), - // no_default_passes: deprecated - // passes: deprecated - // plugins: removed, but rustdoc warns about it itself - sym::cfg - | sym::hidden - | sym::no_default_passes - | sym::notable_trait - | sym::passes - | sym::plugins => {} + sym::cfg | sym::hidden | sym::notable_trait => {} sym::rust_logo => { if self.check_attr_crate_level(attr, meta, hir_id) @@ -1244,6 +1236,22 @@ impl<'tcx> CheckAttrVisitor<'tcx> { sugg: (attr.meta().unwrap().span, applicability), }, ); + } else if i_meta.has_name(sym::passes) + || i_meta.has_name(sym::no_default_passes) + { + self.tcx.emit_node_span_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + i_meta.span, + errors::DocTestUnknownPasses { path, span: i_meta.span }, + ); + } else if i_meta.has_name(sym::plugins) { + self.tcx.emit_node_span_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + i_meta.span, + errors::DocTestUnknownPlugins { path, span: i_meta.span }, + ); } else { self.tcx.emit_node_span_lint( INVALID_DOC_ATTRIBUTES, diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 6dc3dfba58f06..630fb80bb7ff7 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -317,6 +317,27 @@ pub(crate) struct DocTestUnknownSpotlight { pub span: Span, } +#[derive(LintDiagnostic)] +#[diag(passes_doc_test_unknown_passes)] +#[note] +#[help] +#[note(passes_no_op_note)] +pub(crate) struct DocTestUnknownPasses { + pub path: String, + #[label] + pub span: Span, +} + +#[derive(LintDiagnostic)] +#[diag(passes_doc_test_unknown_plugins)] +#[note] +#[note(passes_no_op_note)] +pub(crate) struct DocTestUnknownPlugins { + pub path: String, + #[label] + pub span: Span, +} + #[derive(LintDiagnostic)] #[diag(passes_doc_test_unknown_include)] pub(crate) struct DocTestUnknownInclude { diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index aaf4c80f99763..63cd055796b02 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -8,7 +8,7 @@ use rustc_data_structures::unord::UnordSet; use rustc_errors::codes::*; use rustc_errors::emitter::{DynEmitter, HumanEmitter, stderr_destination}; use rustc_errors::json::JsonEmitter; -use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed, TerminalUrl}; +use rustc_errors::{ErrorGuaranteed, TerminalUrl}; use rustc_feature::UnstableFeatures; use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId}; @@ -21,8 +21,8 @@ use rustc_middle::ty::{ParamEnv, Ty, TyCtxt}; use rustc_session::config::{self, CrateType, ErrorOutputType, Input, ResolveDocLinks}; pub(crate) use rustc_session::config::{Options, UnstableOptions}; use rustc_session::{Session, lint}; +use rustc_span::source_map; use rustc_span::symbol::sym; -use rustc_span::{Span, source_map}; use tracing::{debug, info}; use crate::clean::inline::build_external_trait; @@ -380,45 +380,10 @@ pub(crate) fn run_global_ctxt( ); } - fn report_deprecated_attr(name: &str, dcx: DiagCtxtHandle<'_>, sp: Span) { - let mut msg = - dcx.struct_span_warn(sp, format!("the `#![doc({name})]` attribute is deprecated")); - msg.note( - "see issue #44136 \ - for more information", - ); - - if name == "no_default_passes" { - msg.help("`#![doc(no_default_passes)]` no longer functions; you may want to use `#![doc(document_private_items)]`"); - } else if name.starts_with("passes") { - msg.help("`#![doc(passes = \"...\")]` no longer functions; 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 "); - } - - msg.emit(); - } - // Process all of the crate attributes, extracting plugin metadata along // with the passes which we are supposed to run. for attr in krate.module.attrs.lists(sym::doc) { - let dcx = ctxt.sess().dcx(); - let name = attr.name_or_empty(); - // `plugins = "..."`, `no_default_passes`, and `passes = "..."` have no effect - if attr.is_word() && name == sym::no_default_passes { - report_deprecated_attr("no_default_passes", dcx, attr.span()); - } else if attr.value_str().is_some() { - match name { - sym::passes => { - report_deprecated_attr("passes = \"...\"", dcx, attr.span()); - } - sym::plugins => { - report_deprecated_attr("plugins = \"...\"", dcx, attr.span()); - } - _ => (), - } - } if attr.is_word() && name == sym::document_private_items { ctxt.render_options.document_private = true; diff --git a/tests/rustdoc-ui/deprecated-attrs.rs b/tests/rustdoc-ui/deprecated-attrs.rs index e4802ee25182f..3b59e05a012e6 100644 --- a/tests/rustdoc-ui/deprecated-attrs.rs +++ b/tests/rustdoc-ui/deprecated-attrs.rs @@ -1,16 +1,21 @@ -//@ check-pass //@ compile-flags: --passes unknown-pass //@ error-pattern: the `passes` flag no longer functions #![doc(no_default_passes)] -//~^ WARNING attribute is deprecated +//~^ ERROR unknown `doc` attribute `no_default_passes` +//~| NOTE no longer functions //~| NOTE see issue #44136 -//~| HELP no longer functions; you may want to use `#![doc(document_private_items)]` +//~| HELP you may want to use `doc(document_private_items)` +//~| NOTE `doc(no_default_passes)` is now a no-op +//~| NOTE `#[deny(invalid_doc_attributes)]` on by default #![doc(passes = "collapse-docs unindent-comments")] -//~^ WARNING attribute is deprecated +//~^ ERROR unknown `doc` attribute `passes` +//~| NOTE no longer functions //~| NOTE see issue #44136 -//~| HELP no longer functions; you may want to use `#![doc(document_private_items)]` +//~| HELP you may want to use `doc(document_private_items)` +//~| NOTE `doc(passes)` is now a no-op #![doc(plugins = "xxx")] -//~^ WARNING attribute is deprecated +//~^ ERROR unknown `doc` attribute `plugins` //~| NOTE see issue #44136 -//~| WARNING no longer functions; see CVE +//~| NOTE no longer functions +//~| NOTE `doc(plugins)` is now a no-op diff --git a/tests/rustdoc-ui/deprecated-attrs.stderr b/tests/rustdoc-ui/deprecated-attrs.stderr index 45b20ce70ef0f..a30523e732971 100644 --- a/tests/rustdoc-ui/deprecated-attrs.stderr +++ b/tests/rustdoc-ui/deprecated-attrs.stderr @@ -3,32 +3,35 @@ warning: the `passes` flag no longer functions = note: see issue #44136 for more information = help: you may want to use --document-private-items -warning: the `#![doc(no_default_passes)]` attribute is deprecated - --> $DIR/deprecated-attrs.rs:5:8 +error: unknown `doc` attribute `no_default_passes` + --> $DIR/deprecated-attrs.rs:4:8 | LL | #![doc(no_default_passes)] - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^ no longer functions | - = note: see issue #44136 for more information - = help: `#![doc(no_default_passes)]` no longer functions; you may want to use `#![doc(document_private_items)]` + = note: `doc` attribute `no_default_passes` no longer functions; see issue #44136 + = help: you may want to use `doc(document_private_items)` + = note: `doc(no_default_passes)` is now a no-op + = note: `#[deny(invalid_doc_attributes)]` on by default -warning: the `#![doc(passes = "...")]` attribute is deprecated - --> $DIR/deprecated-attrs.rs:9:8 +error: unknown `doc` attribute `passes` + --> $DIR/deprecated-attrs.rs:11:8 | LL | #![doc(passes = "collapse-docs unindent-comments")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no longer functions | - = note: see issue #44136 for more information - = help: `#![doc(passes = "...")]` no longer functions; you may want to use `#![doc(document_private_items)]` + = note: `doc` attribute `passes` no longer functions; see issue #44136 + = help: you may want to use `doc(document_private_items)` + = note: `doc(passes)` is now a no-op -warning: the `#![doc(plugins = "...")]` attribute is deprecated - --> $DIR/deprecated-attrs.rs:13:8 +error: unknown `doc` attribute `plugins` + --> $DIR/deprecated-attrs.rs:17:8 | LL | #![doc(plugins = "xxx")] - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ no longer functions | - = note: see issue #44136 for more information - = warning: `#![doc(plugins = "...")]` no longer functions; see CVE-2018-1000622 + = note: `doc` attribute `plugins` no longer functions; see issue #44136 and CVE-2018-1000622 + = note: `doc(plugins)` is now a no-op -warning: 3 warnings emitted +error: aborting due to 3 previous errors From bc9fc714fb18519c66a10f048c137aba89862b7b Mon Sep 17 00:00:00 2001 From: lucarlig Date: Tue, 15 Oct 2024 11:37:32 +0100 Subject: [PATCH 07/12] add TestFloatParse to tools for bootstrap --- src/bootstrap/src/core/build_steps/test.rs | 4 +-- src/bootstrap/src/core/build_steps/tool.rs | 32 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index e25b571acbac5..0a160ce418213 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -3549,9 +3549,7 @@ impl Step for TestFloatParse { let path = self.path.to_str().unwrap(); let crate_name = self.path.components().last().unwrap().as_os_str().to_str().unwrap(); - if !builder.download_rustc() { - builder.ensure(compile::Std::new(compiler, self.host)); - } + builder.ensure(tool::TestFloatParse { host: self.host }); // Run any unit tests in the crate let cargo_test = tool::prepare_tool_cargo( diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index f1a10c3296e91..44f60e1868503 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -1096,6 +1096,38 @@ tool_extended!((self, builder), Rustfmt, "src/tools/rustfmt", "rustfmt", stable=true, add_bins_to_sysroot = ["rustfmt", "cargo-fmt"]; ); +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TestFloatParse { + pub host: TargetSelection, +} + +impl Step for TestFloatParse { + type Output = (); + const ONLY_HOSTS: bool = true; + const DEFAULT: bool = false; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.path("src/etc/test-float-parse") + } + + fn run(self, builder: &Builder<'_>) { + let bootstrap_host = builder.config.build; + let compiler = builder.compiler(builder.top_stage, bootstrap_host); + + builder.ensure(ToolBuild { + compiler, + target: bootstrap_host, + tool: "test-float-parse", + mode: Mode::ToolStd, + path: "src/etc/test-float-parse", + source_type: SourceType::InTree, + extra_features: Vec::new(), + allow_features: "", + cargo_args: Vec::new(), + }); + } +} + impl Builder<'_> { /// Gets a `BootstrapCommand` which is ready to run `tool` in `stage` built for /// `host`. From 774201e3b631c488c8dff4ddb20f3988c2c7d21a Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Mon, 21 Oct 2024 19:34:41 +0300 Subject: [PATCH 08/12] don't stage-off to previous compiler when CI rustc is available Signed-off-by: onur-ozkan --- src/bootstrap/src/core/build_steps/test.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index e25b571acbac5..c3c2b526586eb 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -1734,13 +1734,15 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the } else { // We need to properly build cargo using the suitable stage compiler. - // HACK: currently tool stages are off-by-one compared to compiler stages, i.e. if - // you give `tool::Cargo` a stage 1 rustc, it will cause stage 2 rustc to be built - // and produce a cargo built with stage 2 rustc. To fix this, we need to chop off - // the compiler stage by 1 to align with expected `./x test run-make --stage N` - // behavior, i.e. we need to pass `N - 1` compiler stage to cargo. See also Miri - // which does a similar hack. - let compiler = builder.compiler(builder.top_stage - 1, compiler.host); + let compiler = builder.download_rustc().then_some(compiler).unwrap_or_else(|| + // HACK: currently tool stages are off-by-one compared to compiler stages, i.e. if + // you give `tool::Cargo` a stage 1 rustc, it will cause stage 2 rustc to be built + // and produce a cargo built with stage 2 rustc. To fix this, we need to chop off + // the compiler stage by 1 to align with expected `./x test run-make --stage N` + // behavior, i.e. we need to pass `N - 1` compiler stage to cargo. See also Miri + // which does a similar hack. + builder.compiler(builder.top_stage - 1, compiler.host)); + builder.ensure(tool::Cargo { compiler, target: compiler.host }) }; From 7f4dd9bb815182a1b60242d82890f9b0b3273c4b Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 22 Oct 2024 13:31:54 +1100 Subject: [PATCH 09/12] Move `cmp_in_dominator_order` out of graph dominator computation Dominator-order information is only needed for coverage graphs, and is easy enough to collect by just traversing the graph again. This avoids wasted work when computing graph dominators for any other purpose. --- .../src/graph/dominators/mod.rs | 23 +---------------- .../rustc_mir_transform/src/coverage/graph.rs | 25 +++++++++++++++++-- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_data_structures/src/graph/dominators/mod.rs b/compiler/rustc_data_structures/src/graph/dominators/mod.rs index 7cb013fdbd837..85b030c1a4853 100644 --- a/compiler/rustc_data_structures/src/graph/dominators/mod.rs +++ b/compiler/rustc_data_structures/src/graph/dominators/mod.rs @@ -9,8 +9,6 @@ //! Thomas Lengauer and Robert Endre Tarjan. //! -use std::cmp::Ordering; - use rustc_index::{Idx, IndexSlice, IndexVec}; use super::ControlFlowGraph; @@ -64,9 +62,6 @@ fn is_small_path_graph(g: &G) -> bool { } fn dominators_impl(graph: &G) -> Inner { - // compute the post order index (rank) for each node - let mut post_order_rank = IndexVec::from_elem_n(0, graph.num_nodes()); - // We allocate capacity for the full set of nodes, because most of the time // most of the nodes *are* reachable. let mut parent: IndexVec = @@ -83,12 +78,10 @@ fn dominators_impl(graph: &G) -> Inner { pre_order_to_real.push(graph.start_node()); parent.push(PreorderIndex::ZERO); // the parent of the root node is the root for now. real_to_pre_order[graph.start_node()] = Some(PreorderIndex::ZERO); - let mut post_order_idx = 0; // Traverse the graph, collecting a number of things: // // * Preorder mapping (to it, and back to the actual ordering) - // * Postorder mapping (used exclusively for `cmp_in_dominator_order` on the final product) // * Parents for each vertex in the preorder tree // // These are all done here rather than through one of the 'standard' @@ -104,8 +97,6 @@ fn dominators_impl(graph: &G) -> Inner { continue 'recurse; } } - post_order_rank[pre_order_to_real[frame.pre_order_idx]] = post_order_idx; - post_order_idx += 1; stack.pop(); } @@ -282,7 +273,7 @@ fn dominators_impl(graph: &G) -> Inner { let time = compute_access_time(start_node, &immediate_dominators); - Inner { post_order_rank, immediate_dominators, time } + Inner { immediate_dominators, time } } /// Evaluate the link-eval virtual forest, providing the currently minimum semi @@ -348,7 +339,6 @@ fn compress( /// Tracks the list of dominators for each node. #[derive(Clone, Debug)] struct Inner { - post_order_rank: IndexVec, // Even though we track only the immediate dominator of each node, it's // possible to get its full list of dominators by looking up the dominator // of each dominator. @@ -379,17 +369,6 @@ impl Dominators { } } - /// Provide deterministic ordering of nodes such that, if any two nodes have a dominator - /// relationship, the dominator will always precede the dominated. (The relative ordering - /// of two unrelated nodes will also be consistent, but otherwise the order has no - /// meaning.) This method cannot be used to determine if either Node dominates the other. - pub fn cmp_in_dominator_order(&self, lhs: Node, rhs: Node) -> Ordering { - match &self.kind { - Kind::Path => lhs.index().cmp(&rhs.index()), - Kind::General(g) => g.post_order_rank[rhs].cmp(&g.post_order_rank[lhs]), - } - } - /// Returns true if `a` dominates `b`. /// /// # Panics diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index d839f46cfbd55..930fa129ef2a1 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -21,6 +21,10 @@ pub(crate) struct CoverageGraph { pub(crate) successors: IndexVec>, pub(crate) predecessors: IndexVec>, dominators: Option>, + /// Allows nodes to be compared in some total order such that _if_ + /// `a` dominates `b`, then `a < b`. If neither node dominates the other, + /// their relative order is consistent but arbitrary. + dominator_order_rank: IndexVec, } impl CoverageGraph { @@ -54,10 +58,27 @@ impl CoverageGraph { } } - let mut this = Self { bcbs, bb_to_bcb, successors, predecessors, dominators: None }; + let num_nodes = bcbs.len(); + let mut this = Self { + bcbs, + bb_to_bcb, + successors, + predecessors, + dominators: None, + dominator_order_rank: IndexVec::from_elem_n(0, num_nodes), + }; + assert_eq!(num_nodes, this.num_nodes()); this.dominators = Some(dominators::dominators(&this)); + // The dominator rank of each node is just its index in a reverse-postorder traversal. + let reverse_post_order = graph::iterate::reverse_post_order(&this, this.start_node()); + // The coverage graph is created by traversal, so all nodes are reachable. + assert_eq!(reverse_post_order.len(), this.num_nodes()); + for (rank, bcb) in (0u32..).zip(reverse_post_order) { + this.dominator_order_rank[bcb] = rank; + } + // The coverage graph's entry-point node (bcb0) always starts with bb0, // which never has predecessors. Any other blocks merged into bcb0 can't // have multiple (coverage-relevant) predecessors, so bcb0 always has @@ -162,7 +183,7 @@ impl CoverageGraph { a: BasicCoverageBlock, b: BasicCoverageBlock, ) -> Ordering { - self.dominators.as_ref().unwrap().cmp_in_dominator_order(a, b) + self.dominator_order_rank[a].cmp(&self.dominator_order_rank[b]) } /// Returns the source of this node's sole in-edge, if it has exactly one. From 1467deea64d5b4b638679679aab8fcd2997a81ad Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 13 Oct 2024 12:05:59 +1100 Subject: [PATCH 10/12] Stop using `line_directive` in `runtest::debugger` This also removes unused support for `[rev]` in debugger commands, and makes breakpoint detection slightly more sensible. --- src/tools/compiletest/src/runtest/debugger.rs | 15 +++++------ .../compiletest/src/runtest/debuginfo.rs | 27 +++++-------------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/src/tools/compiletest/src/runtest/debugger.rs b/src/tools/compiletest/src/runtest/debugger.rs index 985fe6381e8d4..c15422fb6f684 100644 --- a/src/tools/compiletest/src/runtest/debugger.rs +++ b/src/tools/compiletest/src/runtest/debugger.rs @@ -4,7 +4,6 @@ use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use crate::common::Config; -use crate::header::line_directive; use crate::runtest::ProcRes; /// Representation of information to invoke a debugger and check its output @@ -24,7 +23,6 @@ impl DebuggerCommands { file: &Path, config: &Config, debugger_prefixes: &[&str], - rev: Option<&str>, ) -> Result { let directives = debugger_prefixes .iter() @@ -39,18 +37,17 @@ impl DebuggerCommands { for (line_no, line) in reader.lines().enumerate() { counter += 1; let line = line.map_err(|e| format!("Error while parsing debugger commands: {}", e))?; - let (lnrev, line) = line_directive("//", &line).unwrap_or((None, &line)); - - // Skip any revision specific directive that doesn't match the current - // revision being tested - if lnrev.is_some() && lnrev != rev { - continue; - } + // Breakpoints appear on lines with actual code, typically at the end of the line. if line.contains("#break") { breakpoint_lines.push(counter); + continue; } + let Some(line) = line.trim_start().strip_prefix("//").map(str::trim_start) else { + continue; + }; + for &(ref command_directive, ref check_directive) in &directives { config .parse_name_value_directive(&line, command_directive) diff --git a/src/tools/compiletest/src/runtest/debuginfo.rs b/src/tools/compiletest/src/runtest/debuginfo.rs index bd0845b45241e..c621c22ac993c 100644 --- a/src/tools/compiletest/src/runtest/debuginfo.rs +++ b/src/tools/compiletest/src/runtest/debuginfo.rs @@ -66,13 +66,8 @@ impl TestCx<'_> { }; // Parse debugger commands etc from test files - let dbg_cmds = DebuggerCommands::parse_from( - &self.testpaths.file, - self.config, - prefixes, - self.revision, - ) - .unwrap_or_else(|e| self.fatal(&e)); + let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, self.config, prefixes) + .unwrap_or_else(|e| self.fatal(&e)); // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-commands let mut script_str = String::with_capacity(2048); @@ -142,13 +137,8 @@ impl TestCx<'_> { } fn run_debuginfo_gdb_test_no_opt(&self) { - let dbg_cmds = DebuggerCommands::parse_from( - &self.testpaths.file, - self.config, - &["gdb"], - self.revision, - ) - .unwrap_or_else(|e| self.fatal(&e)); + let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, self.config, &["gdb"]) + .unwrap_or_else(|e| self.fatal(&e)); let mut cmds = dbg_cmds.commands.join("\n"); // compile test file (it should have 'compile-flags:-g' in the header) @@ -413,13 +403,8 @@ impl TestCx<'_> { } // Parse debugger commands etc from test files - let dbg_cmds = DebuggerCommands::parse_from( - &self.testpaths.file, - self.config, - &["lldb"], - self.revision, - ) - .unwrap_or_else(|e| self.fatal(&e)); + let dbg_cmds = DebuggerCommands::parse_from(&self.testpaths.file, self.config, &["lldb"]) + .unwrap_or_else(|e| self.fatal(&e)); // Write debugger script: // We don't want to hang when calling `quit` while the process is still running From c4016ea455c28574b6a5a4413d9c8950fefcb64c Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 13 Oct 2024 13:11:59 +1100 Subject: [PATCH 11/12] Rename some fields of `DirectiveLine` --- src/tools/compiletest/src/header.rs | 50 ++++++++++++++++------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 099e620ffe035..fe1a500e1774b 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -57,7 +57,7 @@ impl EarlyProps { &mut poisoned, testfile, rdr, - &mut |DirectiveLine { directive: ln, .. }| { + &mut |DirectiveLine { raw_directive: ln, .. }| { parse_and_update_aux(config, ln, &mut props.aux); config.parse_and_update_revisions(testfile, ln, &mut props.revisions); }, @@ -344,8 +344,8 @@ impl TestProps { &mut poisoned, testfile, file, - &mut |DirectiveLine { header_revision, directive: ln, .. }| { - if header_revision.is_some() && header_revision != test_revision { + &mut |directive @ DirectiveLine { raw_directive: ln, .. }| { + if !directive.applies_to_test_revision(test_revision) { return; } @@ -730,28 +730,37 @@ const KNOWN_HTMLDOCCK_DIRECTIVE_NAMES: &[&str] = &[ const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] = &["count", "!count", "has", "!has", "is", "!is", "ismany", "!ismany", "set", "!set"]; -/// The broken-down contents of a line containing a test header directive, +/// The (partly) broken-down contents of a line containing a test directive, /// which [`iter_header`] passes to its callback function. /// /// For example: /// /// ```text /// //@ compile-flags: -O -/// ^^^^^^^^^^^^^^^^^ directive +/// ^^^^^^^^^^^^^^^^^ raw_directive /// /// //@ [foo] compile-flags: -O -/// ^^^ header_revision -/// ^^^^^^^^^^^^^^^^^ directive +/// ^^^ revision +/// ^^^^^^^^^^^^^^^^^ raw_directive /// ``` struct DirectiveLine<'ln> { line_number: usize, - /// Some header directives start with a revision name in square brackets + /// Some test directives start with a revision name in square brackets /// (e.g. `[foo]`), and only apply to that revision of the test. /// If present, this field contains the revision name (e.g. `foo`). - header_revision: Option<&'ln str>, - /// The main part of the header directive, after removing the comment prefix + revision: Option<&'ln str>, + /// The main part of the directive, after removing the comment prefix /// and the optional revision specifier. - directive: &'ln str, + /// + /// This is "raw" because the directive's name and colon-separated value + /// (if present) have not yet been extracted or checked. + raw_directive: &'ln str, +} + +impl<'ln> DirectiveLine<'ln> { + fn applies_to_test_revision(&self, test_revision: Option<&str>) -> bool { + self.revision.is_none() || self.revision == test_revision + } } pub(crate) struct CheckDirectiveResult<'ln> { @@ -819,8 +828,8 @@ fn iter_header( "ignore-cross-compile", ]; // Process the extra implied directives, with a dummy line number of 0. - for directive in extra_directives { - it(DirectiveLine { line_number: 0, header_revision: None, directive }); + for raw_directive in extra_directives { + it(DirectiveLine { line_number: 0, revision: None, raw_directive }); } } @@ -847,14 +856,13 @@ fn iter_header( return; } - let Some((header_revision, non_revisioned_directive_line)) = line_directive(comment, ln) - else { + let Some((revision, raw_directive)) = line_directive(comment, ln) else { continue; }; // Perform unknown directive check on Rust files. if testfile.extension().map(|e| e == "rs").unwrap_or(false) { - let directive_ln = non_revisioned_directive_line.trim(); + let directive_ln = raw_directive.trim(); let CheckDirectiveResult { is_known_directive, trailing_directive } = check_directive(directive_ln, mode, ln); @@ -888,11 +896,7 @@ fn iter_header( } } - it(DirectiveLine { - line_number, - header_revision, - directive: non_revisioned_directive_line, - }); + it(DirectiveLine { line_number, revision, raw_directive }); } } @@ -1292,8 +1296,8 @@ pub fn make_test_description( &mut local_poisoned, path, src, - &mut |DirectiveLine { header_revision, directive: ln, line_number }| { - if header_revision.is_some() && header_revision != test_revision { + &mut |directive @ DirectiveLine { line_number, raw_directive: ln, .. }| { + if !directive.applies_to_test_revision(test_revision) { return; } From 997b7a6ed0630f097f83119d0ddc4e0d8f8d6ea2 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 13 Oct 2024 12:35:52 +1100 Subject: [PATCH 12/12] Make `line_directive` return a `DirectiveLine` This reduces the need to juggle raw tuples, and opens up the possibility of moving more parts of directive parsing into `line_directive`. --- src/tools/compiletest/src/header.rs | 35 ++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index fe1a500e1774b..d75cdefe635bf 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -678,28 +678,35 @@ impl TestProps { } } -/// Extract an `(Option, directive)` directive from a line if comment is present. -/// -/// See [`DirectiveLine`] for a diagram. -pub fn line_directive<'line>( +/// If the given line begins with the appropriate comment prefix for a directive, +/// returns a struct containing various parts of the directive. +fn line_directive<'line>( + line_number: usize, comment: &str, original_line: &'line str, -) -> Option<(Option<&'line str>, &'line str)> { +) -> Option> { // Ignore lines that don't start with the comment prefix. let after_comment = original_line.trim_start().strip_prefix(comment)?.trim_start(); + let revision; + let raw_directive; + if let Some(after_open_bracket) = after_comment.strip_prefix('[') { // A comment like `//@[foo]` only applies to revision `foo`. - let Some((line_revision, directive)) = after_open_bracket.split_once(']') else { + let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else { panic!( "malformed condition directive: expected `{comment}[foo]`, found `{original_line}`" ) }; - Some((Some(line_revision), directive.trim_start())) + revision = Some(line_revision); + raw_directive = after_close_bracket.trim_start(); } else { - Some((None, after_comment)) - } + revision = None; + raw_directive = after_comment; + }; + + Some(DirectiveLine { line_number, revision, raw_directive }) } // To prevent duplicating the list of commmands between `compiletest`,`htmldocck` and `jsondocck`, @@ -856,23 +863,21 @@ fn iter_header( return; } - let Some((revision, raw_directive)) = line_directive(comment, ln) else { + let Some(directive_line) = line_directive(line_number, comment, ln) else { continue; }; // Perform unknown directive check on Rust files. if testfile.extension().map(|e| e == "rs").unwrap_or(false) { - let directive_ln = raw_directive.trim(); - let CheckDirectiveResult { is_known_directive, trailing_directive } = - check_directive(directive_ln, mode, ln); + check_directive(directive_line.raw_directive, mode, ln); if !is_known_directive { *poisoned = true; eprintln!( "error: detected unknown compiletest test directive `{}` in {}:{}", - directive_ln, + directive_line.raw_directive, testfile.display(), line_number, ); @@ -896,7 +901,7 @@ fn iter_header( } } - it(DirectiveLine { line_number, revision, raw_directive }); + it(directive_line); } }