From f7581d8d219613d6aa2a017d31d230587a6076f6 Mon Sep 17 00:00:00 2001 From: clubby789 Date: Wed, 12 Apr 2023 21:45:21 +0100 Subject: [PATCH 01/20] Suggest using integration tests for proc-macros --- compiler/rustc_resolve/messages.ftl | 3 +++ compiler/rustc_resolve/src/errors.rs | 9 +++++++++ compiler/rustc_resolve/src/macros.rs | 10 +++++----- tests/ui/proc-macro/test-same-crate.rs | 16 ++++++++++++++++ tests/ui/proc-macro/test-same-crate.stderr | 10 ++++++++++ 5 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 tests/ui/proc-macro/test-same-crate.rs create mode 100644 tests/ui/proc-macro/test-same-crate.stderr diff --git a/compiler/rustc_resolve/messages.ftl b/compiler/rustc_resolve/messages.ftl index 2628f247c5411..950caaa5ebe2d 100644 --- a/compiler/rustc_resolve/messages.ftl +++ b/compiler/rustc_resolve/messages.ftl @@ -223,3 +223,6 @@ resolve_remove_surrounding_derive = resolve_add_as_non_derive = add as non-Derive macro `#[{$macro_path}]` + +resolve_proc_macro_same_crate = can't use a procedural macro from the same crate that defines it + .help = you can define integration tests in a directory named `tests` diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index afa796cb6453b..a0b403828edc1 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -508,3 +508,12 @@ pub(crate) struct RemoveSurroundingDerive { pub(crate) struct AddAsNonDerive<'a> { pub(crate) macro_path: &'a str, } + +#[derive(Diagnostic)] +#[diag(resolve_proc_macro_same_crate)] +pub(crate) struct ProcMacroSameCrate { + #[primary_span] + pub(crate) span: Span, + #[help] + pub(crate) is_test: bool, +} diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 22b014c0651c2..2211fb56ccda1 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -1,7 +1,7 @@ //! A bunch of methods and structures more or less related to resolving macros and //! interface provided by `Resolver` to macro expander. -use crate::errors::{AddAsNonDerive, MacroExpectedFound, RemoveSurroundingDerive}; +use crate::errors::{self, AddAsNonDerive, MacroExpectedFound, RemoveSurroundingDerive}; use crate::Namespace::*; use crate::{BuiltinMacroState, Determinacy}; use crate::{DeriveData, Finalize, ParentScope, ResolutionError, Resolver, ScopeSet}; @@ -513,10 +513,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { if let Some(def_id) = def_id.as_local() { self.unused_macros.remove(&def_id); if self.proc_macro_stubs.contains(&def_id) { - self.tcx.sess.span_err( - path.span, - "can't use a procedural macro from the same crate that defines it", - ); + self.tcx.sess.emit_err(errors::ProcMacroSameCrate { + span: path.span, + is_test: self.tcx.sess.is_test_crate(), + }); } } } diff --git a/tests/ui/proc-macro/test-same-crate.rs b/tests/ui/proc-macro/test-same-crate.rs new file mode 100644 index 0000000000000..c13f384fa3ae1 --- /dev/null +++ b/tests/ui/proc-macro/test-same-crate.rs @@ -0,0 +1,16 @@ +// compile-flags: --test +#![crate_type = "proc-macro"] + +extern crate proc_macro; +use proc_macro::TokenStream; + +#[proc_macro] +pub fn mac(input: TokenStream) -> TokenStream { loop {} } + +#[cfg(test)] +mod test { + #[test] + fn t() { crate::mac!(A) } + //~^ ERROR can't use a procedural macro from the same crate that defines it + //~| HELP you can define integration tests in a directory named `tests` +} diff --git a/tests/ui/proc-macro/test-same-crate.stderr b/tests/ui/proc-macro/test-same-crate.stderr new file mode 100644 index 0000000000000..5d12e149c3c4c --- /dev/null +++ b/tests/ui/proc-macro/test-same-crate.stderr @@ -0,0 +1,10 @@ +error: can't use a procedural macro from the same crate that defines it + --> $DIR/test-same-crate.rs:13:14 + | +LL | fn t() { crate::mac!(A) } + | ^^^^^^^^^^ + | + = help: you can define integration tests in a directory named `tests` + +error: aborting due to previous error + From 14678778dc50ce5886856826f8cd35dc080ead7e Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 19 Apr 2023 01:41:01 +0000 Subject: [PATCH 02/20] Remove find_map_relevant_impl --- compiler/rustc_middle/src/ty/trait_def.rs | 62 +++----- compiler/rustc_middle/src/ty/util.rs | 37 +++-- .../src/solve/trait_goals.rs | 10 +- .../src/traits/error_reporting/mod.rs | 145 ++++++++++-------- .../src/traits/select/candidate_assembly.rs | 22 ++- .../passes/collect_intra_doc_links.rs | 20 +-- 6 files changed, 154 insertions(+), 142 deletions(-) diff --git a/compiler/rustc_middle/src/ty/trait_def.rs b/compiler/rustc_middle/src/ty/trait_def.rs index 6747da7abd3af..e61037e5ea86f 100644 --- a/compiler/rustc_middle/src/ty/trait_def.rs +++ b/compiler/rustc_middle/src/ty/trait_def.rs @@ -139,40 +139,6 @@ impl<'tcx> TyCtxt<'tcx> { treat_projections: TreatProjections, mut f: impl FnMut(DefId), ) { - let _: Option<()> = - self.find_map_relevant_impl(trait_def_id, self_ty, treat_projections, |did| { - f(did); - None - }); - } - - /// `trait_def_id` MUST BE the `DefId` of a trait. - pub fn non_blanket_impls_for_ty( - self, - trait_def_id: DefId, - self_ty: Ty<'tcx>, - ) -> impl Iterator + 'tcx { - let impls = self.trait_impls_of(trait_def_id); - if let Some(simp) = fast_reject::simplify_type(self, self_ty, TreatParams::AsCandidateKey) { - if let Some(impls) = impls.non_blanket_impls.get(&simp) { - return impls.iter().copied(); - } - } - - [].iter().copied() - } - - /// Applies function to every impl that could possibly match the self type `self_ty` and returns - /// the first non-none value. - /// - /// `trait_def_id` MUST BE the `DefId` of a trait. - pub fn find_map_relevant_impl( - self, - trait_def_id: DefId, - self_ty: Ty<'tcx>, - treat_projections: TreatProjections, - mut f: impl FnMut(DefId) -> Option, - ) -> Option { // FIXME: This depends on the set of all impls for the trait. That is // unfortunate wrt. incremental compilation. // @@ -181,9 +147,7 @@ impl<'tcx> TyCtxt<'tcx> { let impls = self.trait_impls_of(trait_def_id); for &impl_def_id in impls.blanket_impls.iter() { - if let result @ Some(_) = f(impl_def_id) { - return result; - } + f(impl_def_id); } // Note that we're using `TreatParams::ForLookup` to query `non_blanket_impls` while using @@ -199,20 +163,30 @@ impl<'tcx> TyCtxt<'tcx> { if let Some(simp) = fast_reject::simplify_type(self, self_ty, treat_params) { if let Some(impls) = impls.non_blanket_impls.get(&simp) { for &impl_def_id in impls { - if let result @ Some(_) = f(impl_def_id) { - return result; - } + f(impl_def_id); } } } else { for &impl_def_id in impls.non_blanket_impls.values().flatten() { - if let result @ Some(_) = f(impl_def_id) { - return result; - } + f(impl_def_id); } } + } - None + /// `trait_def_id` MUST BE the `DefId` of a trait. + pub fn non_blanket_impls_for_ty( + self, + trait_def_id: DefId, + self_ty: Ty<'tcx>, + ) -> impl Iterator + 'tcx { + let impls = self.trait_impls_of(trait_def_id); + if let Some(simp) = fast_reject::simplify_type(self, self_ty, TreatParams::AsCandidateKey) { + if let Some(impls) = impls.non_blanket_impls.get(&simp) { + return impls.iter().copied(); + } + } + + [].iter().copied() } /// Returns an iterator containing all impls for `trait_def_id`. diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index c8a78ec03d947..f076ea7830bc3 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -2,7 +2,6 @@ use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags; use crate::mir; -use crate::ty::fast_reject::TreatProjections; use crate::ty::layout::IntegerExt; use crate::ty::{ self, FallibleTypeFolder, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, @@ -359,21 +358,29 @@ impl<'tcx> TyCtxt<'tcx> { self.ensure().coherent_trait(drop_trait); let ty = self.type_of(adt_did).subst_identity(); - let (did, constness) = self.find_map_relevant_impl( - drop_trait, - ty, - // FIXME: This could also be some other mode, like "unexpected" - TreatProjections::ForLookup, - |impl_did| { - if let Some(item_id) = self.associated_item_def_ids(impl_did).first() { - if validate(self, impl_did).is_ok() { - return Some((*item_id, self.constness(impl_did))); - } - } - None - }, - )?; + let mut dtor_candidate = None; + self.for_each_relevant_impl(drop_trait, ty, |impl_did| { + let Some(item_id) = self.associated_item_def_ids(impl_did).first() else { + self.sess.delay_span_bug(self.def_span(impl_did), "Drop impl without drop function"); + return; + }; + + if validate(self, impl_did).is_err() { + // Already `ErrorGuaranteed`, no need to delay a span bug here. + return; + } + + if let Some((old_item_id, _)) = dtor_candidate { + self.sess + .struct_span_err(self.def_span(item_id), "multiple drop impls found") + .span_note(self.def_span(old_item_id), "other impl here") + .delay_as_bug(); + } + + dtor_candidate = Some((*item_id, self.constness(impl_did))); + }); + let (did, constness) = dtor_candidate?; Some(ty::Destructor { did, constness }) } diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs index abd11a15ac23a..acfc8e98fcb89 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs @@ -645,12 +645,16 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { // FIXME: Handling opaques here is kinda sus. Especially because we // simplify them to PlaceholderSimplifiedType. | ty::Alias(ty::Opaque, _) => { - if let Some(def_id) = self.tcx().find_map_relevant_impl( + let mut disqualifying_impl = None; + self.tcx().for_each_relevant_impl_treating_projections( goal.predicate.def_id(), goal.predicate.self_ty(), TreatProjections::NextSolverLookup, - Some, - ) { + |impl_def_id| { + disqualifying_impl = Some(impl_def_id); + }, + ); + if let Some(def_id) = disqualifying_impl { debug!(?def_id, ?goal, "disqualified auto-trait implementation"); // No need to actually consider the candidate here, // since we do that in `consider_impl_candidate`. diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index 0352f0f380db1..660f78509d3ef 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -32,7 +32,6 @@ use rustc_infer::infer::{InferOk, TypeTrace}; use rustc_middle::traits::select::OverflowError; use rustc_middle::ty::abstract_const::NotConstEvaluatable; use rustc_middle::ty::error::{ExpectedFound, TypeError}; -use rustc_middle::ty::fast_reject::TreatProjections; use rustc_middle::ty::fold::{TypeFolder, TypeSuperFoldable}; use rustc_middle::ty::print::{with_forced_trimmed_paths, FmtPrinter, Print}; use rustc_middle::ty::{ @@ -1836,57 +1835,61 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { }); let mut diag = struct_span_err!(self.tcx.sess, obligation.cause.span, E0271, "{msg}"); - let secondary_span = match predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Projection(proj)) => self - .tcx - .opt_associated_item(proj.projection_ty.def_id) - .and_then(|trait_assoc_item| { - self.tcx - .trait_of_item(proj.projection_ty.def_id) - .map(|id| (trait_assoc_item, id)) - }) - .and_then(|(trait_assoc_item, id)| { - let trait_assoc_ident = trait_assoc_item.ident(self.tcx); - self.tcx.find_map_relevant_impl( - id, - proj.projection_ty.self_ty(), - TreatProjections::ForLookup, - |did| { - self.tcx - .associated_items(did) - .in_definition_order() - .find(|assoc| assoc.ident(self.tcx) == trait_assoc_ident) - }, - ) - }) - .and_then(|item| match self.tcx.hir().get_if_local(item.def_id) { - Some( - hir::Node::TraitItem(hir::TraitItem { - kind: hir::TraitItemKind::Type(_, Some(ty)), - .. - }) - | hir::Node::ImplItem(hir::ImplItem { - kind: hir::ImplItemKind::Type(ty), - .. - }), - ) => Some(( - ty.span, - with_forced_trimmed_paths!(format!( - "type mismatch resolving `{}`", - self.resolve_vars_if_possible(predicate) - .print(FmtPrinter::new_with_limit( - self.tcx, - Namespace::TypeNS, - rustc_session::Limit(5), - )) - .unwrap() - .into_buffer() - )), + let secondary_span = (|| { + let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = + predicate.kind().skip_binder() + else { + return None; + }; + + let trait_assoc_item = self.tcx.opt_associated_item(proj.projection_ty.def_id)?; + let trait_assoc_ident = trait_assoc_item.ident(self.tcx); + + let mut associated_items = vec![]; + self.tcx.for_each_relevant_impl( + self.tcx.trait_of_item(proj.projection_ty.def_id)?, + proj.projection_ty.self_ty(), + |impl_def_id| { + associated_items.extend( + self.tcx + .associated_items(impl_def_id) + .in_definition_order() + .find(|assoc| assoc.ident(self.tcx) == trait_assoc_ident), + ); + }, + ); + + let [associated_item]: &[ty::AssocItem] = &associated_items[..] else { + return None; + }; + match self.tcx.hir().get_if_local(associated_item.def_id) { + Some( + hir::Node::TraitItem(hir::TraitItem { + kind: hir::TraitItemKind::Type(_, Some(ty)), + .. + }) + | hir::Node::ImplItem(hir::ImplItem { + kind: hir::ImplItemKind::Type(ty), + .. + }), + ) => Some(( + ty.span, + with_forced_trimmed_paths!(format!( + "type mismatch resolving `{}`", + self.resolve_vars_if_possible(predicate) + .print(FmtPrinter::new_with_limit( + self.tcx, + Namespace::TypeNS, + rustc_session::Limit(5), + )) + .unwrap() + .into_buffer() )), - _ => None, - }), - _ => None, - }; + )), + _ => None, + } + })(); + self.note_type_err( &mut diag, &obligation.cause, @@ -2228,14 +2231,18 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { err: &mut Diagnostic, trait_ref: &ty::PolyTraitRef<'tcx>, ) -> bool { - let get_trait_impl = |trait_def_id| { - self.tcx.find_map_relevant_impl( + let get_trait_impls = |trait_def_id| { + let mut trait_impls = vec![]; + self.tcx.for_each_relevant_impl( trait_def_id, trait_ref.skip_binder().self_ty(), - TreatProjections::ForLookup, - Some, - ) + |impl_def_id| { + trait_impls.push(impl_def_id); + }, + ); + trait_impls }; + let required_trait_path = self.tcx.def_path_str(trait_ref.def_id()); let traits_with_same_path: std::collections::BTreeSet<_> = self .tcx @@ -2245,17 +2252,23 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { .collect(); let mut suggested = false; for trait_with_same_path in traits_with_same_path { - if let Some(impl_def_id) = get_trait_impl(trait_with_same_path) { - let impl_span = self.tcx.def_span(impl_def_id); - err.span_help(impl_span, "trait impl with same name found"); - let trait_crate = self.tcx.crate_name(trait_with_same_path.krate); - let crate_msg = format!( - "perhaps two different versions of crate `{}` are being used?", - trait_crate - ); - err.note(&crate_msg); - suggested = true; + let trait_impls = get_trait_impls(trait_with_same_path); + if trait_impls.is_empty() { + continue; } + let impl_spans: Vec<_> = + trait_impls.iter().map(|impl_def_id| self.tcx.def_span(*impl_def_id)).collect(); + err.span_help( + impl_spans, + format!("trait impl{} with same name found", pluralize!(trait_impls.len())), + ); + let trait_crate = self.tcx.crate_name(trait_with_same_path.krate); + let crate_msg = format!( + "perhaps two different versions of crate `{}` are being used?", + trait_crate + ); + err.note(&crate_msg); + suggested = true; } suggested } diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index 1f5bbc178f7d7..403593a47c100 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -11,7 +11,7 @@ use hir::LangItem; use rustc_hir as hir; use rustc_infer::traits::ObligationCause; use rustc_infer::traits::{Obligation, SelectionError, TraitObligation}; -use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams, TreatProjections}; +use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; use crate::traits; @@ -875,12 +875,24 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } ty::Adt(..) => { - // Find a custom `impl Drop` impl, if it exists - let relevant_impl = self.tcx().find_map_relevant_impl( + let mut relevant_impl = None; + self.tcx().for_each_relevant_impl( self.tcx().require_lang_item(LangItem::Drop, None), obligation.predicate.skip_binder().trait_ref.self_ty(), - TreatProjections::ForLookup, - Some, + |impl_def_id| { + if let Some(old_impl_def_id) = relevant_impl { + self.tcx() + .sess + .struct_span_err( + self.tcx().def_span(impl_def_id), + "multiple drop impls found", + ) + .span_note(self.tcx().def_span(old_impl_def_id), "other impl here") + .delay_as_bug(); + } + + relevant_impl = Some(impl_def_id); + }, ); if let Some(impl_def_id) = relevant_impl { diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 33e80df9ed77b..f2486abaaa77e 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -13,7 +13,7 @@ use rustc_hir::def::Namespace::*; use rustc_hir::def::{DefKind, Namespace, PerNS}; use rustc_hir::def_id::{DefId, CRATE_DEF_ID}; use rustc_hir::Mutability; -use rustc_middle::ty::{fast_reject::TreatProjections, Ty, TyCtxt}; +use rustc_middle::ty::{Ty, TyCtxt}; use rustc_middle::{bug, ty}; use rustc_resolve::rustdoc::{has_primitive_or_keyword_docs, prepare_to_doc_link_resolution}; use rustc_resolve::rustdoc::{strip_generics_from_path, MalformedGenerics}; @@ -772,11 +772,10 @@ fn trait_impls_for<'a>( module: DefId, ) -> FxHashSet<(DefId, DefId)> { let tcx = cx.tcx; - let iter = tcx.doc_link_traits_in_scope(module).iter().flat_map(|&trait_| { - trace!("considering explicit impl for trait {:?}", trait_); + let mut impls = FxHashSet::default(); - // Look at each trait implementation to see if it's an impl for `did` - tcx.find_map_relevant_impl(trait_, ty, TreatProjections::ForLookup, |impl_| { + for &trait_ in tcx.doc_link_traits_in_scope(module) { + tcx.for_each_relevant_impl(trait_, ty, |impl_| { let trait_ref = tcx.impl_trait_ref(impl_).expect("this is not an inherent impl"); // Check if these are the same type. let impl_type = trait_ref.skip_binder().self_ty(); @@ -800,10 +799,13 @@ fn trait_impls_for<'a>( _ => false, }; - if saw_impl { Some((impl_, trait_)) } else { None } - }) - }); - iter.collect() + if saw_impl { + impls.insert((impl_, trait_)); + } + }); + } + + impls } /// Check for resolve collisions between a trait and its derive. From a1780a7e6f19ddf9aae04dca3c135644e4065a0e Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 19 Apr 2023 21:05:02 -0500 Subject: [PATCH 03/20] Remove unnecessary `test_kind` field and `TestKind` struct --- src/bootstrap/builder.rs | 8 ++++ src/bootstrap/builder/tests.rs | 1 - src/bootstrap/test.rs | 85 ++++++++-------------------------- 3 files changed, 27 insertions(+), 67 deletions(-) diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index 3d3f991bffaa7..01c7a25d08c91 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -634,6 +634,14 @@ impl Kind { Kind::Suggest => "suggest", } } + + pub fn test_description(&self) -> &'static str { + match self { + Kind::Test => "Testing", + Kind::Bench => "Benchmarking", + _ => panic!("not a test command: {}!", self.as_str()), + } + } } impl<'a> Builder<'a> { diff --git a/src/bootstrap/builder/tests.rs b/src/bootstrap/builder/tests.rs index 3574f11189ee9..72ac46b6bfddd 100644 --- a/src/bootstrap/builder/tests.rs +++ b/src/bootstrap/builder/tests.rs @@ -578,7 +578,6 @@ mod dist { compiler: Compiler { host, stage: 0 }, target: host, mode: Mode::Std, - test_kind: test::TestKind::Test, crates: vec![INTERNER.intern_str("std")], },] ); diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index ccf83974b8c2f..348fd3f874dba 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -5,7 +5,6 @@ use std::env; use std::ffi::OsString; -use std::fmt; use std::fs; use std::iter; use std::path::{Path, PathBuf}; @@ -28,44 +27,6 @@ use crate::{envify, CLang, DocTests, GitRepo, Mode}; const ADB_TEST_DIR: &str = "/data/local/tmp/work"; -/// The two modes of the test runner; tests or benchmarks. -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord)] -pub enum TestKind { - /// Run `cargo test`. - Test, - /// Run `cargo bench`. - Bench, -} - -impl From for TestKind { - fn from(kind: Kind) -> Self { - match kind { - Kind::Test => TestKind::Test, - Kind::Bench => TestKind::Bench, - _ => panic!("unexpected kind in crate: {:?}", kind), - } - } -} - -impl TestKind { - // Return the cargo subcommand for this test kind - fn subcommand(self) -> &'static str { - match self { - TestKind::Test => "test", - TestKind::Bench => "bench", - } - } -} - -impl fmt::Display for TestKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - TestKind::Test => "Testing", - TestKind::Bench => "Benchmarking", - }) - } -} - fn try_run(builder: &Builder<'_>, cmd: &mut Command) -> bool { if !builder.fail_fast { if !builder.try_run(cmd) { @@ -2105,7 +2066,6 @@ impl Step for RustcGuide { pub struct CrateLibrustc { compiler: Compiler, target: TargetSelection, - test_kind: TestKind, crates: Vec>, } @@ -2127,9 +2087,8 @@ impl Step for CrateLibrustc { .iter() .map(|p| builder.crate_paths[&p.assert_single_path().path].clone()) .collect(); - let test_kind = builder.kind.into(); - builder.ensure(CrateLibrustc { compiler, target: run.target, test_kind, crates }); + builder.ensure(CrateLibrustc { compiler, target: run.target, crates }); } fn run(self, builder: &Builder<'_>) { @@ -2137,7 +2096,6 @@ impl Step for CrateLibrustc { compiler: self.compiler, target: self.target, mode: Mode::Rustc, - test_kind: self.test_kind, crates: self.crates, }); } @@ -2148,7 +2106,6 @@ pub struct Crate { pub compiler: Compiler, pub target: TargetSelection, pub mode: Mode, - pub test_kind: TestKind, pub crates: Vec>, } @@ -2164,14 +2121,13 @@ impl Step for Crate { let builder = run.builder; let host = run.build_triple(); let compiler = builder.compiler_for(builder.top_stage, host, host); - let test_kind = builder.kind.into(); let crates = run .paths .iter() .map(|p| builder.crate_paths[&p.assert_single_path().path].clone()) .collect(); - builder.ensure(Crate { compiler, target: run.target, mode: Mode::Std, test_kind, crates }); + builder.ensure(Crate { compiler, target: run.target, mode: Mode::Std, crates }); } /// Runs all unit tests plus documentation tests for a given crate defined @@ -2186,7 +2142,6 @@ impl Step for Crate { let compiler = self.compiler; let target = self.target; let mode = self.mode; - let test_kind = self.test_kind; builder.ensure(compile::Std::new(compiler, target)); builder.ensure(RemoteCopyLibs { compiler, target }); @@ -2198,7 +2153,7 @@ impl Step for Crate { let compiler = builder.compiler_for(compiler.stage, compiler.host, target); let mut cargo = - builder.cargo(compiler, mode, SourceType::InTree, target, test_kind.subcommand()); + builder.cargo(compiler, mode, SourceType::InTree, target, builder.kind.as_str()); match mode { Mode::Std => { compile::std_cargo(builder, target, compiler.stage, &mut cargo); @@ -2214,7 +2169,7 @@ impl Step for Crate { // Pass in some standard flags then iterate over the graph we've discovered // in `cargo metadata` with the maps above and figure out what `-p` // arguments need to get passed. - if test_kind.subcommand() == "test" && !builder.fail_fast { + if builder.kind == Kind::Test && !builder.fail_fast { cargo.arg("--no-fail-fast"); } match builder.doc_tests { @@ -2265,7 +2220,7 @@ impl Step for Crate { builder.info(&format!( "{}{} stage{} ({} -> {})", - test_kind, + builder.kind.test_description(), crate_description(&self.crates), compiler.stage, &compiler.host, @@ -2280,7 +2235,6 @@ impl Step for Crate { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct CrateRustdoc { host: TargetSelection, - test_kind: TestKind, } impl Step for CrateRustdoc { @@ -2295,13 +2249,10 @@ impl Step for CrateRustdoc { fn make_run(run: RunConfig<'_>) { let builder = run.builder; - let test_kind = builder.kind.into(); - - builder.ensure(CrateRustdoc { host: run.target, test_kind }); + builder.ensure(CrateRustdoc { host: run.target }); } fn run(self, builder: &Builder<'_>) { - let test_kind = self.test_kind; let target = self.host; let compiler = if builder.download_rustc() { @@ -2320,12 +2271,12 @@ impl Step for CrateRustdoc { compiler, Mode::ToolRustc, target, - test_kind.subcommand(), + builder.kind.as_str(), "src/tools/rustdoc", SourceType::InTree, &[], ); - if test_kind.subcommand() == "test" && !builder.fail_fast { + if builder.kind == Kind::Test && !builder.fail_fast { cargo.arg("--no-fail-fast"); } match builder.doc_tests { @@ -2388,7 +2339,10 @@ impl Step for CrateRustdoc { builder.info(&format!( "{} rustdoc stage{} ({} -> {})", - test_kind, compiler.stage, &compiler.host, target + builder.kind.test_description(), + compiler.stage, + &compiler.host, + target )); let _time = util::timeit(&builder); @@ -2399,7 +2353,6 @@ impl Step for CrateRustdoc { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct CrateRustdocJsonTypes { host: TargetSelection, - test_kind: TestKind, } impl Step for CrateRustdocJsonTypes { @@ -2414,13 +2367,10 @@ impl Step for CrateRustdocJsonTypes { fn make_run(run: RunConfig<'_>) { let builder = run.builder; - let test_kind = builder.kind.into(); - - builder.ensure(CrateRustdocJsonTypes { host: run.target, test_kind }); + builder.ensure(CrateRustdocJsonTypes { host: run.target }); } fn run(self, builder: &Builder<'_>) { - let test_kind = self.test_kind; let target = self.host; // Use the previous stage compiler to reuse the artifacts that are @@ -2435,12 +2385,12 @@ impl Step for CrateRustdocJsonTypes { compiler, Mode::ToolRustc, target, - test_kind.subcommand(), + builder.kind.as_str(), "src/rustdoc-json-types", SourceType::InTree, &[], ); - if test_kind.subcommand() == "test" && !builder.fail_fast { + if builder.kind == Kind::Test && !builder.fail_fast { cargo.arg("--no-fail-fast"); } @@ -2455,7 +2405,10 @@ impl Step for CrateRustdocJsonTypes { builder.info(&format!( "{} rustdoc-json-types stage{} ({} -> {})", - test_kind, compiler.stage, &compiler.host, target + builder.kind.test_description(), + compiler.stage, + &compiler.host, + target )); let _time = util::timeit(&builder); From d49c7d666112df5dbf3a1d5d1e8657b20685d991 Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 19 Apr 2023 20:58:45 -0500 Subject: [PATCH 04/20] [wip] separate out a test_crate_args fn --- src/bootstrap/test.rs | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index 348fd3f874dba..2f486b8b75eff 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -2101,6 +2101,25 @@ impl Step for CrateLibrustc { } } +// Given a `cargo test` subcommand, pass it the appropriate test flags given a `builder`. +fn cargo_test_args(cargo: &mut Command, libtest_args: &[&str], _crates: &[&str], builder: &Builder<'_>) { + if !builder.fail_fast { + cargo.arg("--no-fail-fast"); + } + match builder.doc_tests { + DocTests::Only => { + cargo.arg("--doc"); + } + DocTests::No => { + cargo.args(&["--lib", "--bins", "--examples", "--tests", "--benches"]); + } + DocTests::Yes => {} + } + + cargo.arg("--").args(&builder.config.cmd.test_args()).args(libtest_args); + add_flags_and_try_run_tests(builder, cargo); +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Crate { pub compiler: Compiler, @@ -2564,24 +2583,9 @@ impl Step for Bootstrap { // https://github.com/rust-lang/rust/issues/49215 cmd.env("RUSTFLAGS", flags); } - if !builder.fail_fast { - cmd.arg("--no-fail-fast"); - } - match builder.doc_tests { - DocTests::Only => { - cmd.arg("--doc"); - } - DocTests::No => { - cmd.args(&["--lib", "--bins", "--examples", "--tests", "--benches"]); - } - DocTests::Yes => {} - } - - cmd.arg("--").args(&builder.config.cmd.test_args()); // rustbuild tests are racy on directory creation so just run them one at a time. // Since there's not many this shouldn't be a problem. - cmd.arg("--test-threads=1"); - add_flags_and_try_run_tests(builder, &mut cmd); + cargo_test_args(&mut cmd, &["--test-threads=1"], &[], builder); } fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { From 6348fac4816bec7e7b887209a576fb28749ba6d3 Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 19 Apr 2023 21:21:13 -0500 Subject: [PATCH 05/20] switch Crate to run_cargo_test --- src/bootstrap/test.rs | 104 +++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 61 deletions(-) diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index 2f486b8b75eff..c947345d98d8d 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -2102,8 +2102,13 @@ impl Step for CrateLibrustc { } // Given a `cargo test` subcommand, pass it the appropriate test flags given a `builder`. -fn cargo_test_args(cargo: &mut Command, libtest_args: &[&str], _crates: &[&str], builder: &Builder<'_>) { - if !builder.fail_fast { +fn run_cargo_test(cargo: impl Into, libtest_args: &[&str], crates: &[Interned], compiler: Compiler, target: TargetSelection, builder: &Builder<'_>) { + let mut cargo = cargo.into(); + + // Pass in some standard flags then iterate over the graph we've discovered + // in `cargo metadata` with the maps above and figure out what `-p` + // arguments need to get passed. + if builder.kind == Kind::Test && !builder.fail_fast { cargo.arg("--no-fail-fast"); } match builder.doc_tests { @@ -2116,8 +2121,38 @@ fn cargo_test_args(cargo: &mut Command, libtest_args: &[&str], _crates: &[&str], DocTests::Yes => {} } + for &krate in crates { + cargo.arg("-p").arg(krate); + } + + // The tests are going to run with the *target* libraries, so we need to + // ensure that those libraries show up in the LD_LIBRARY_PATH equivalent. + // + // Note that to run the compiler we need to run with the *host* libraries, + // but our wrapper scripts arrange for that to be the case anyway. + let mut dylib_path = dylib_path(); + dylib_path.insert(0, PathBuf::from(&*builder.sysroot_libdir(compiler, target))); + cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap()); + + if target.contains("emscripten") { + cargo.env( + format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), + builder.config.nodejs.as_ref().expect("nodejs not configured"), + ); + } else if target.starts_with("wasm32") { + let node = builder.config.nodejs.as_ref().expect("nodejs not configured"); + let runner = format!("{} {}/src/etc/wasm32-shim.js", node.display(), builder.src.display()); + cargo.env(format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), &runner); + } else if builder.remote_tested(target) { + cargo.env( + format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), + format!("{} run 0", builder.tool_exe(Tool::RemoteTestClient).display()), + ); + } + cargo.arg("--").args(&builder.config.cmd.test_args()).args(libtest_args); - add_flags_and_try_run_tests(builder, cargo); + let _time = util::timeit(&builder); + add_flags_and_try_run_tests(builder, &mut cargo); } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -2183,60 +2218,6 @@ impl Step for Crate { _ => panic!("can only test libraries"), }; - // Build up the base `cargo test` command. - // - // Pass in some standard flags then iterate over the graph we've discovered - // in `cargo metadata` with the maps above and figure out what `-p` - // arguments need to get passed. - if builder.kind == Kind::Test && !builder.fail_fast { - cargo.arg("--no-fail-fast"); - } - match builder.doc_tests { - DocTests::Only => { - cargo.arg("--doc"); - } - DocTests::No => { - cargo.args(&["--lib", "--bins", "--examples", "--tests", "--benches"]); - } - DocTests::Yes => {} - } - - for krate in &self.crates { - cargo.arg("-p").arg(krate); - } - - // The tests are going to run with the *target* libraries, so we need to - // ensure that those libraries show up in the LD_LIBRARY_PATH equivalent. - // - // Note that to run the compiler we need to run with the *host* libraries, - // but our wrapper scripts arrange for that to be the case anyway. - let mut dylib_path = dylib_path(); - dylib_path.insert(0, PathBuf::from(&*builder.sysroot_libdir(compiler, target))); - cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap()); - - cargo.arg("--"); - cargo.args(&builder.config.cmd.test_args()); - - cargo.arg("-Z").arg("unstable-options"); - cargo.arg("--format").arg("json"); - - if target.contains("emscripten") { - cargo.env( - format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), - builder.config.nodejs.as_ref().expect("nodejs not configured"), - ); - } else if target.starts_with("wasm32") { - let node = builder.config.nodejs.as_ref().expect("nodejs not configured"); - let runner = - format!("{} {}/src/etc/wasm32-shim.js", node.display(), builder.src.display()); - cargo.env(format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), &runner); - } else if builder.remote_tested(target) { - cargo.env( - format!("CARGO_TARGET_{}_RUNNER", envify(&target.triple)), - format!("{} run 0", builder.tool_exe(Tool::RemoteTestClient).display()), - ); - } - builder.info(&format!( "{}{} stage{} ({} -> {})", builder.kind.test_description(), @@ -2245,8 +2226,7 @@ impl Step for Crate { &compiler.host, target )); - let _time = util::timeit(&builder); - crate::render_tests::try_run_tests(builder, &mut cargo.into()); + run_cargo_test(cargo, &[], &self.crates, compiler, target, builder); } } @@ -2569,13 +2549,15 @@ impl Step for Bootstrap { check_bootstrap.arg("bootstrap_test.py").current_dir(builder.src.join("src/bootstrap/")); try_run(builder, &mut check_bootstrap); + let host = builder.config.build; + let compiler = builder.compiler(0, host); let mut cmd = Command::new(&builder.initial_cargo); cmd.arg("test") .current_dir(builder.src.join("src/bootstrap")) .env("RUSTFLAGS", "-Cdebuginfo=2") .env("CARGO_TARGET_DIR", builder.out.join("bootstrap")) .env("RUSTC_BOOTSTRAP", "1") - .env("RUSTDOC", builder.rustdoc(builder.compiler(0, builder.build.build))) + .env("RUSTDOC", builder.rustdoc(compiler)) .env("RUSTC", &builder.initial_rustc); if let Some(flags) = option_env!("RUSTFLAGS") { // Use the same rustc flags for testing as for "normal" compilation, @@ -2585,7 +2567,7 @@ impl Step for Bootstrap { } // rustbuild tests are racy on directory creation so just run them one at a time. // Since there's not many this shouldn't be a problem. - cargo_test_args(&mut cmd, &["--test-threads=1"], &[], builder); + run_cargo_test(cmd, &["--test-threads=1"], &[], compiler, host, builder); } fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { From 84e7253a4ff1fbf12282b34fb4c5d9cc9f3bb3cb Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 19 Apr 2023 21:34:30 -0500 Subject: [PATCH 06/20] Convert the rest of the `test` Steps to run_cargo_test --- src/bootstrap/test.rs | 159 ++++++++++++++++++++---------------------- 1 file changed, 75 insertions(+), 84 deletions(-) diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index c947345d98d8d..36daee6b3ee42 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -13,6 +13,7 @@ use std::process::{Command, Stdio}; use crate::builder::crate_description; use crate::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step}; use crate::cache::Interned; +use crate::cache::INTERNER; use crate::compile; use crate::config::TargetSelection; use crate::dist; @@ -85,7 +86,7 @@ impl Step for CrateJsonDocLint { SourceType::InTree, &[], ); - add_flags_and_try_run_tests(builder, &mut cargo.into()); + run_cargo_test(cargo, &[], &[], compiler, bootstrap_host, builder); } } @@ -111,7 +112,7 @@ impl Step for SuggestTestsCrate { let bootstrap_host = builder.config.build; let compiler = builder.compiler(0, bootstrap_host); - let suggest_tests = tool::prepare_tool_cargo( + let cargo = tool::prepare_tool_cargo( builder, compiler, Mode::ToolBootstrap, @@ -121,7 +122,7 @@ impl Step for SuggestTestsCrate { SourceType::InTree, &[], ); - add_flags_and_try_run_tests(builder, &mut suggest_tests.into()); + run_cargo_test(cargo, &[], &[], compiler, bootstrap_host, builder); } } @@ -170,7 +171,7 @@ You can skip linkcheck with --exclude src/tools/linkchecker" SourceType::InTree, &[], ); - add_flags_and_try_run_tests(builder, &mut cargo.into()); + run_cargo_test(cargo, &[], &[], compiler, bootstrap_host, builder); // Build all the default documentation. builder.default_doc(&[]); @@ -317,21 +318,15 @@ impl Step for Cargo { &[], ); - if !builder.fail_fast { - cargo.arg("--no-fail-fast"); - } - cargo.arg("--").args(builder.config.cmd.test_args()); - // Don't run cross-compile tests, we may not have cross-compiled libstd libs // available. cargo.env("CFG_DISABLE_CROSS_TESTS", "1"); // Forcibly disable tests using nightly features since any changes to // those features won't be able to land. cargo.env("CARGO_TEST_DISABLE_NIGHTLY", "1"); - cargo.env("PATH", &path_for_cargo(builder, compiler)); - add_flags_and_try_run_tests(builder, &mut cargo.into()); + run_cargo_test(cargo, &[], &[], compiler, self.host, builder); } } @@ -388,9 +383,7 @@ impl Step for RustAnalyzer { cargo.env("SKIP_SLOW_TESTS", "1"); cargo.add_rustc_lib_path(builder, compiler); - cargo.arg("--").args(builder.config.cmd.test_args()); - - add_flags_and_try_run_tests(builder, &mut cargo.into()); + run_cargo_test(cargo, &[], &[], compiler, host, builder); } } @@ -433,17 +426,13 @@ impl Step for Rustfmt { &[], ); - if !builder.fail_fast { - cargo.arg("--no-fail-fast"); - } - let dir = testdir(builder, compiler.host); t!(fs::create_dir_all(&dir)); cargo.env("RUSTFMT_TEST_DIR", dir); cargo.add_rustc_lib_path(builder, compiler); - add_flags_and_try_run_tests(builder, &mut cargo.into()); + run_cargo_test(cargo, &[], &[], compiler, host, builder); } } @@ -489,12 +478,9 @@ impl Step for RustDemangler { t!(fs::create_dir_all(&dir)); cargo.env("RUST_DEMANGLER_DRIVER_PATH", rust_demangler); - - cargo.arg("--").args(builder.config.cmd.test_args()); - cargo.add_rustc_lib_path(builder, compiler); - add_flags_and_try_run_tests(builder, &mut cargo.into()); + run_cargo_test(cargo, &[], &[], compiler, host, builder); } } @@ -617,10 +603,6 @@ impl Step for Miri { ); cargo.add_rustc_lib_path(builder, compiler); - if !builder.fail_fast { - cargo.arg("--no-fail-fast"); - } - // miri tests need to know about the stage sysroot cargo.env("MIRI_SYSROOT", &miri_sysroot); cargo.env("MIRI_HOST_SYSROOT", sysroot); @@ -632,13 +614,14 @@ impl Step for Miri { // Set the target. cargo.env("MIRI_TEST_TARGET", target.rustc_target_arg()); - // Forward test filters. - cargo.arg("--").args(builder.config.cmd.test_args()); - // This can NOT be `add_flags_and_try_run_tests` since the Miri test runner - // does not understand those flags! - let mut cargo = Command::from(cargo); - builder.run(&mut cargo); + // This can NOT be `run_cargo_test` since the Miri test runner + // does not understand the flags added by `add_flags_and_try_run_test`. + let mut cargo = prepare_cargo_test(cargo, &[], &[], compiler, target, builder); + { + let _time = util::timeit(&builder); + builder.run(&mut cargo); + } // # Run `cargo miri test`. // This is just a smoke test (Miri's own CI invokes this in a bunch of different ways and ensures @@ -671,6 +654,7 @@ impl Step for Miri { cargo.env("RUST_BACKTRACE", "1"); let mut cargo = Command::from(cargo); + let _time = util::timeit(&builder); builder.run(&mut cargo); } } @@ -710,8 +694,7 @@ impl Step for CompiletestTest { &[], ); cargo.allow_features("test"); - - add_flags_and_try_run_tests(builder, &mut cargo.into()); + run_cargo_test(cargo, &[], &[], compiler, host, builder); } } @@ -763,11 +746,10 @@ impl Step for Clippy { let host_libs = builder.stage_out(compiler, Mode::ToolRustc).join(builder.cargo_dir()); cargo.env("HOST_LIBS", host_libs); - cargo.arg("--").args(builder.config.cmd.test_args()); - cargo.add_rustc_lib_path(builder, compiler); + let mut cargo = prepare_cargo_test(cargo, &[], &[], compiler, host, builder); - if builder.try_run(&mut cargo.into()) { + if builder.try_run(&mut cargo) { // The tests succeeded; nothing to do. return; } @@ -1195,7 +1177,7 @@ impl Step for TidySelfTest { SourceType::InTree, &[], ); - add_flags_and_try_run_tests(builder, &mut cargo.into()); + run_cargo_test(cargo, &[], &[], compiler, bootstrap_host, builder); } } @@ -2101,8 +2083,31 @@ impl Step for CrateLibrustc { } } -// Given a `cargo test` subcommand, pass it the appropriate test flags given a `builder`. -fn run_cargo_test(cargo: impl Into, libtest_args: &[&str], crates: &[Interned], compiler: Compiler, target: TargetSelection, builder: &Builder<'_>) { +/// Given a `cargo test` subcommand, add the appropriate flags and run it. +/// +/// Returns whether the test succeeded. +fn run_cargo_test( + cargo: impl Into, + libtest_args: &[&str], + crates: &[Interned], + compiler: Compiler, + target: TargetSelection, + builder: &Builder<'_>, +) -> bool { + let mut cargo = prepare_cargo_test(cargo, libtest_args, crates, compiler, target, builder); + let _time = util::timeit(&builder); + add_flags_and_try_run_tests(builder, &mut cargo) +} + +/// Given a `cargo test` subcommand, pass it the appropriate test flags given a `builder`. +fn prepare_cargo_test( + cargo: impl Into, + libtest_args: &[&str], + crates: &[Interned], + compiler: Compiler, + target: TargetSelection, + builder: &Builder<'_>, +) -> Command { let mut cargo = cargo.into(); // Pass in some standard flags then iterate over the graph we've discovered @@ -2125,6 +2130,11 @@ fn run_cargo_test(cargo: impl Into, libtest_args: &[&str], crates: &[In cargo.arg("-p").arg(krate); } + cargo.arg("--").args(&builder.config.cmd.test_args()).args(libtest_args); + if !builder.config.verbose_tests { + cargo.arg("--quiet"); + } + // The tests are going to run with the *target* libraries, so we need to // ensure that those libraries show up in the LD_LIBRARY_PATH equivalent. // @@ -2150,9 +2160,7 @@ fn run_cargo_test(cargo: impl Into, libtest_args: &[&str], crates: &[In ); } - cargo.arg("--").args(&builder.config.cmd.test_args()).args(libtest_args); - let _time = util::timeit(&builder); - add_flags_and_try_run_tests(builder, &mut cargo); + cargo } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -2275,24 +2283,6 @@ impl Step for CrateRustdoc { SourceType::InTree, &[], ); - if builder.kind == Kind::Test && !builder.fail_fast { - cargo.arg("--no-fail-fast"); - } - match builder.doc_tests { - DocTests::Only => { - cargo.arg("--doc"); - } - DocTests::No => { - cargo.args(&["--lib", "--bins", "--examples", "--tests", "--benches"]); - } - DocTests::Yes => {} - } - - cargo.arg("-p").arg("rustdoc:0.0.0"); - - cargo.arg("--"); - cargo.args(&builder.config.cmd.test_args()); - if self.host.contains("musl") { cargo.arg("'-Ctarget-feature=-crt-static'"); } @@ -2332,10 +2322,6 @@ impl Step for CrateRustdoc { dylib_path.insert(0, PathBuf::from(&*libdir)); cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap()); - if !builder.config.verbose_tests { - cargo.arg("--quiet"); - } - builder.info(&format!( "{} rustdoc stage{} ({} -> {})", builder.kind.test_description(), @@ -2343,9 +2329,14 @@ impl Step for CrateRustdoc { &compiler.host, target )); - let _time = util::timeit(&builder); - - add_flags_and_try_run_tests(builder, &mut cargo.into()); + run_cargo_test( + cargo, + &[], + &[INTERNER.intern_str("rustdoc:0.0.0")], + compiler, + target, + builder, + ); } } @@ -2379,7 +2370,7 @@ impl Step for CrateRustdocJsonTypes { let compiler = builder.compiler_for(builder.top_stage, target, target); builder.ensure(compile::Rustc::new(compiler, target)); - let mut cargo = tool::prepare_tool_cargo( + let cargo = tool::prepare_tool_cargo( builder, compiler, Mode::ToolRustc, @@ -2389,18 +2380,13 @@ impl Step for CrateRustdocJsonTypes { SourceType::InTree, &[], ); - if builder.kind == Kind::Test && !builder.fail_fast { - cargo.arg("--no-fail-fast"); - } - - cargo.arg("-p").arg("rustdoc-json-types"); - cargo.arg("--"); - cargo.args(&builder.config.cmd.test_args()); - - if self.host.contains("musl") { - cargo.arg("'-Ctarget-feature=-crt-static'"); - } + // FIXME: this looks very wrong, libtest doesn't accept `-C` arguments and the quotes are fishy. + let libtest_args = if self.host.contains("musl") { + ["'-Ctarget-feature=-crt-static'"].as_slice() + } else { + &[] + }; builder.info(&format!( "{} rustdoc-json-types stage{} ({} -> {})", @@ -2409,9 +2395,14 @@ impl Step for CrateRustdocJsonTypes { &compiler.host, target )); - let _time = util::timeit(&builder); - - add_flags_and_try_run_tests(builder, &mut cargo.into()); + run_cargo_test( + cargo, + libtest_args, + &[INTERNER.intern_str("rustdoc-json-types")], + compiler, + target, + builder, + ); } } From c7ebddb4a3746552c5de63886e3483ea558352ae Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 19 Apr 2023 22:35:53 -0500 Subject: [PATCH 07/20] Combine several `Step`s into a single step with multiple paths --- src/bootstrap/builder.rs | 5 +- src/bootstrap/test.rs | 130 ++++++--------------------------------- src/bootstrap/tool.rs | 2 +- 3 files changed, 22 insertions(+), 115 deletions(-) diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index 01c7a25d08c91..608b4c4d245da 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -703,7 +703,6 @@ impl<'a> Builder<'a> { crate::toolstate::ToolStateCheck, test::ExpandYamlAnchors, test::Tidy, - test::TidySelfTest, test::Ui, test::RunPassValgrind, test::MirOpt, @@ -719,11 +718,9 @@ impl<'a> Builder<'a> { test::CrateLibrustc, test::CrateRustdoc, test::CrateRustdocJsonTypes, - test::CrateJsonDocLint, - test::SuggestTestsCrate, + test::CrateBootstrap, test::Linkcheck, test::TierCheck, - test::ReplacePlaceholderTest, test::Cargotest, test::Cargo, test::RustAnalyzer, diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index 36daee6b3ee42..e1ba61fca8e57 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -55,26 +55,37 @@ fn try_run_quiet(builder: &Builder<'_>, cmd: &mut Command) -> bool { } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct CrateJsonDocLint { +pub struct CrateBootstrap { + path: Interned, host: TargetSelection, } -impl Step for CrateJsonDocLint { +impl Step for CrateBootstrap { type Output = (); const ONLY_HOSTS: bool = true; const DEFAULT: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/jsondoclint") + .path("src/tools/suggest-tests") + .path("src/tools/replace-version-placeholder") + .alias("tidyselftest") } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(CrateJsonDocLint { host: run.target }); + for path in run.paths { + let path = INTERNER.intern_path(path.assert_single_path().path.clone()); + run.builder.ensure(CrateBootstrap { host: run.target, path }); + } } fn run(self, builder: &Builder<'_>) { let bootstrap_host = builder.config.build; let compiler = builder.compiler(0, bootstrap_host); + let mut path = self.path.to_str().unwrap(); + if path == "tidyselftest" { + path = "src/tools/tidy"; + } let cargo = tool::prepare_tool_cargo( builder, @@ -82,46 +93,16 @@ impl Step for CrateJsonDocLint { Mode::ToolBootstrap, bootstrap_host, "test", - "src/tools/jsondoclint", + path, SourceType::InTree, &[], ); - run_cargo_test(cargo, &[], &[], compiler, bootstrap_host, builder); - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct SuggestTestsCrate { - host: TargetSelection, -} - -impl Step for SuggestTestsCrate { - type Output = (); - const ONLY_HOSTS: bool = true; - const DEFAULT: bool = true; - - fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.path("src/tools/suggest-tests") - } - - fn make_run(run: RunConfig<'_>) { - run.builder.ensure(SuggestTestsCrate { host: run.target }); - } - - fn run(self, builder: &Builder<'_>) { - let bootstrap_host = builder.config.build; - let compiler = builder.compiler(0, bootstrap_host); - - let cargo = tool::prepare_tool_cargo( - builder, - compiler, - Mode::ToolBootstrap, + builder.info(&format!( + "{} {} stage0 ({})", + builder.kind.test_description(), + path, bootstrap_host, - "test", - "src/tools/suggest-tests", - SourceType::InTree, - &[], - ); + )); run_cargo_test(cargo, &[], &[], compiler, bootstrap_host, builder); } } @@ -1147,40 +1128,6 @@ help: to skip test's attempt to check tidiness, pass `--exclude src/tools/tidy` } } -/// Runs tidy's own tests. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct TidySelfTest; - -impl Step for TidySelfTest { - type Output = (); - const DEFAULT: bool = true; - const ONLY_HOSTS: bool = true; - - fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.alias("tidyselftest") - } - - fn make_run(run: RunConfig<'_>) { - run.builder.ensure(TidySelfTest); - } - - fn run(self, builder: &Builder<'_>) { - let bootstrap_host = builder.config.build; - let compiler = builder.compiler(0, bootstrap_host); - let cargo = tool::prepare_tool_cargo( - builder, - compiler, - Mode::ToolBootstrap, - bootstrap_host, - "test", - "src/tools/tidy", - SourceType::InTree, - &[], - ); - run_cargo_test(cargo, &[], &[], compiler, bootstrap_host, builder); - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct ExpandYamlAnchors; @@ -2614,43 +2561,6 @@ impl Step for TierCheck { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct ReplacePlaceholderTest; - -impl Step for ReplacePlaceholderTest { - type Output = (); - const ONLY_HOSTS: bool = true; - const DEFAULT: bool = true; - - /// Ensure the version placeholder replacement tool builds - fn run(self, builder: &Builder<'_>) { - builder.info("build check for version replacement placeholder"); - - // Test the version placeholder replacement tool itself. - let bootstrap_host = builder.config.build; - let compiler = builder.compiler(0, bootstrap_host); - let cargo = tool::prepare_tool_cargo( - builder, - compiler, - Mode::ToolBootstrap, - bootstrap_host, - "test", - "src/tools/replace-version-placeholder", - SourceType::InTree, - &[], - ); - add_flags_and_try_run_tests(builder, &mut cargo.into()); - } - - fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.path("src/tools/replace-version-placeholder") - } - - fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Self); - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct LintDocs { pub compiler: Compiler, diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index 79eec6f848c27..04ddde2acde5f 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -155,7 +155,7 @@ pub fn prepare_tool_cargo( mode: Mode, target: TargetSelection, command: &'static str, - path: &'static str, + path: &str, source_type: SourceType, extra_features: &[String], ) -> CargoCommand { From 41e3cc4c963450111eea9132c97258d9644e3f1e Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Thu, 20 Apr 2023 09:35:04 +0000 Subject: [PATCH 08/20] Add some tests around (lack of) object safety of associated types and consts --- tests/ui/object-safety/assoc_const_bounds.rs | 13 ++++++++++++ .../object-safety/assoc_const_bounds.stderr | 15 +++++++++++++ .../object-safety/assoc_const_bounds_sized.rs | 9 ++++++++ .../assoc_const_bounds_sized.stderr | 15 +++++++++++++ tests/ui/object-safety/assoc_type_bounds.rs | 13 ++++++++++++ .../ui/object-safety/assoc_type_bounds.stderr | 21 +++++++++++++++++++ tests/ui/object-safety/assoc_type_bounds2.rs | 13 ++++++++++++ .../object-safety/assoc_type_bounds2.stderr | 21 +++++++++++++++++++ .../object-safety/assoc_type_bounds_sized.rs | 9 ++++++++ .../assoc_type_bounds_sized.stderr | 12 +++++++++++ 10 files changed, 141 insertions(+) create mode 100644 tests/ui/object-safety/assoc_const_bounds.rs create mode 100644 tests/ui/object-safety/assoc_const_bounds.stderr create mode 100644 tests/ui/object-safety/assoc_const_bounds_sized.rs create mode 100644 tests/ui/object-safety/assoc_const_bounds_sized.stderr create mode 100644 tests/ui/object-safety/assoc_type_bounds.rs create mode 100644 tests/ui/object-safety/assoc_type_bounds.stderr create mode 100644 tests/ui/object-safety/assoc_type_bounds2.rs create mode 100644 tests/ui/object-safety/assoc_type_bounds2.stderr create mode 100644 tests/ui/object-safety/assoc_type_bounds_sized.rs create mode 100644 tests/ui/object-safety/assoc_type_bounds_sized.stderr diff --git a/tests/ui/object-safety/assoc_const_bounds.rs b/tests/ui/object-safety/assoc_const_bounds.rs new file mode 100644 index 0000000000000..94b1f63165ba2 --- /dev/null +++ b/tests/ui/object-safety/assoc_const_bounds.rs @@ -0,0 +1,13 @@ +trait Foo { + const BAR: bool + where //~ ERROR: expected one of `!`, `(`, `+`, `::`, `;`, `<`, or `=`, found keyword `where` + Self: Sized; +} + +trait Cake {} +impl Cake for () {} + +fn foo(_: &dyn Foo<()>) {} +fn bar(_: &dyn Foo) {} + +fn main() {} diff --git a/tests/ui/object-safety/assoc_const_bounds.stderr b/tests/ui/object-safety/assoc_const_bounds.stderr new file mode 100644 index 0000000000000..09bc11e178a45 --- /dev/null +++ b/tests/ui/object-safety/assoc_const_bounds.stderr @@ -0,0 +1,15 @@ +error: expected one of `!`, `(`, `+`, `::`, `;`, `<`, or `=`, found keyword `where` + --> $DIR/assoc_const_bounds.rs:3:9 + | +LL | trait Foo { + | - while parsing this item list starting here +LL | const BAR: bool + | - expected one of 7 possible tokens +LL | where + | ^^^^^ unexpected token +LL | Self: Sized; +LL | } + | - the item list ends here + +error: aborting due to previous error + diff --git a/tests/ui/object-safety/assoc_const_bounds_sized.rs b/tests/ui/object-safety/assoc_const_bounds_sized.rs new file mode 100644 index 0000000000000..2a76e5dce2b49 --- /dev/null +++ b/tests/ui/object-safety/assoc_const_bounds_sized.rs @@ -0,0 +1,9 @@ +trait Foo { + const BAR: bool + where //~ ERROR: expected one of `!`, `(`, `+`, `::`, `;`, `<`, or `=`, found keyword `where` + Self: Sized; +} + +fn foo(_: &dyn Foo) {} + +fn main() {} diff --git a/tests/ui/object-safety/assoc_const_bounds_sized.stderr b/tests/ui/object-safety/assoc_const_bounds_sized.stderr new file mode 100644 index 0000000000000..e1f57f677956b --- /dev/null +++ b/tests/ui/object-safety/assoc_const_bounds_sized.stderr @@ -0,0 +1,15 @@ +error: expected one of `!`, `(`, `+`, `::`, `;`, `<`, or `=`, found keyword `where` + --> $DIR/assoc_const_bounds_sized.rs:3:9 + | +LL | trait Foo { + | - while parsing this item list starting here +LL | const BAR: bool + | - expected one of 7 possible tokens +LL | where + | ^^^^^ unexpected token +LL | Self: Sized; +LL | } + | - the item list ends here + +error: aborting due to previous error + diff --git a/tests/ui/object-safety/assoc_type_bounds.rs b/tests/ui/object-safety/assoc_type_bounds.rs new file mode 100644 index 0000000000000..9abf7939c4302 --- /dev/null +++ b/tests/ui/object-safety/assoc_type_bounds.rs @@ -0,0 +1,13 @@ +trait Foo { + type Bar + where + T: Cake; +} + +trait Cake {} +impl Cake for () {} + +fn foo(_: &dyn Foo<()>) {} //~ ERROR: the value of the associated type `Bar` (from trait `Foo`) must be specified +fn bar(_: &dyn Foo) {} //~ ERROR: the value of the associated type `Bar` (from trait `Foo`) must be specified + +fn main() {} diff --git a/tests/ui/object-safety/assoc_type_bounds.stderr b/tests/ui/object-safety/assoc_type_bounds.stderr new file mode 100644 index 0000000000000..a1396dc3ad40e --- /dev/null +++ b/tests/ui/object-safety/assoc_type_bounds.stderr @@ -0,0 +1,21 @@ +error[E0191]: the value of the associated type `Bar` (from trait `Foo`) must be specified + --> $DIR/assoc_type_bounds.rs:10:16 + | +LL | type Bar + | -------- `Bar` defined here +... +LL | fn foo(_: &dyn Foo<()>) {} + | ^^^^^^^ help: specify the associated type: `Foo<(), Bar = Type>` + +error[E0191]: the value of the associated type `Bar` (from trait `Foo`) must be specified + --> $DIR/assoc_type_bounds.rs:11:16 + | +LL | type Bar + | -------- `Bar` defined here +... +LL | fn bar(_: &dyn Foo) {} + | ^^^^^^^^ help: specify the associated type: `Foo` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0191`. diff --git a/tests/ui/object-safety/assoc_type_bounds2.rs b/tests/ui/object-safety/assoc_type_bounds2.rs new file mode 100644 index 0000000000000..0112123fd42cd --- /dev/null +++ b/tests/ui/object-safety/assoc_type_bounds2.rs @@ -0,0 +1,13 @@ +trait Foo { + type Bar + where + Self: Foo<()>; +} + +trait Cake {} +impl Cake for () {} + +fn foo(_: &dyn Foo<()>) {} //~ ERROR: the value of the associated type `Bar` (from trait `Foo`) must be specified +fn bar(_: &dyn Foo) {} //~ ERROR: the value of the associated type `Bar` (from trait `Foo`) must be specified + +fn main() {} diff --git a/tests/ui/object-safety/assoc_type_bounds2.stderr b/tests/ui/object-safety/assoc_type_bounds2.stderr new file mode 100644 index 0000000000000..7a3c0e02d485d --- /dev/null +++ b/tests/ui/object-safety/assoc_type_bounds2.stderr @@ -0,0 +1,21 @@ +error[E0191]: the value of the associated type `Bar` (from trait `Foo`) must be specified + --> $DIR/assoc_type_bounds2.rs:10:16 + | +LL | type Bar + | -------- `Bar` defined here +... +LL | fn foo(_: &dyn Foo<()>) {} + | ^^^^^^^ help: specify the associated type: `Foo<(), Bar = Type>` + +error[E0191]: the value of the associated type `Bar` (from trait `Foo`) must be specified + --> $DIR/assoc_type_bounds2.rs:11:16 + | +LL | type Bar + | -------- `Bar` defined here +... +LL | fn bar(_: &dyn Foo) {} + | ^^^^^^^^ help: specify the associated type: `Foo` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0191`. diff --git a/tests/ui/object-safety/assoc_type_bounds_sized.rs b/tests/ui/object-safety/assoc_type_bounds_sized.rs new file mode 100644 index 0000000000000..61ad3cf9dc6dc --- /dev/null +++ b/tests/ui/object-safety/assoc_type_bounds_sized.rs @@ -0,0 +1,9 @@ +trait Foo { + type Bar + where + Self: Sized; +} + +fn foo(_: &dyn Foo) {} //~ ERROR: the value of the associated type `Bar` (from trait `Foo`) must be specified + +fn main() {} diff --git a/tests/ui/object-safety/assoc_type_bounds_sized.stderr b/tests/ui/object-safety/assoc_type_bounds_sized.stderr new file mode 100644 index 0000000000000..49d624f9b1d26 --- /dev/null +++ b/tests/ui/object-safety/assoc_type_bounds_sized.stderr @@ -0,0 +1,12 @@ +error[E0191]: the value of the associated type `Bar` (from trait `Foo`) must be specified + --> $DIR/assoc_type_bounds_sized.rs:7:16 + | +LL | type Bar + | -------- `Bar` defined here +... +LL | fn foo(_: &dyn Foo) {} + | ^^^ help: specify the associated type: `Foo` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0191`. From b3ee81a536353e9622ef7fcf246fdb63ea9f4067 Mon Sep 17 00:00:00 2001 From: jyn Date: Thu, 20 Apr 2023 21:30:48 -0500 Subject: [PATCH 09/20] Fix `x test --no-deps` - Use `cargo metadata` to determine whether a crate has a library package or not - Collect metadata for all workspaces, not just the root workspace and cargo - Don't pass `--lib` for crates without a library - Use `run_cargo_test` for rust-installer - Don't build documentation in `lint-docs` if `--no-doc` is passed --- src/bootstrap/lib.rs | 1 + src/bootstrap/metadata.rs | 42 ++++++++++++++++++++------------- src/bootstrap/test.rs | 49 +++++++++++++++++++++++++-------------- 3 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index bfdb029951f29..488e8efe66eb3 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -248,6 +248,7 @@ struct Crate { name: Interned, deps: HashSet>, path: PathBuf, + has_lib: bool, } impl Crate { diff --git a/src/bootstrap/metadata.rs b/src/bootstrap/metadata.rs index 597aefadcfe4c..8f2c3faca3a48 100644 --- a/src/bootstrap/metadata.rs +++ b/src/bootstrap/metadata.rs @@ -5,7 +5,7 @@ use serde_derive::Deserialize; use crate::cache::INTERNER; use crate::util::output; -use crate::{Build, Crate}; +use crate::{t, Build, Crate}; /// For more information, see the output of /// @@ -22,6 +22,7 @@ struct Package { source: Option, manifest_path: String, dependencies: Vec, + targets: Vec, } /// For more information, see the output of @@ -32,6 +33,11 @@ struct Dependency { source: Option, } +#[derive(Debug, Deserialize)] +struct Target { + kind: Vec, +} + /// Collects and stores package metadata of each workspace members into `build`, /// by executing `cargo metadata` commands. pub fn build(build: &mut Build) { @@ -46,11 +52,16 @@ pub fn build(build: &mut Build) { .filter(|dep| dep.source.is_none()) .map(|dep| INTERNER.intern_string(dep.name)) .collect(); - let krate = Crate { name, deps, path }; + let has_lib = package.targets.iter().any(|t| t.kind.iter().any(|k| k == "lib")); + let krate = Crate { name, deps, path, has_lib }; let relative_path = krate.local_path(build); build.crates.insert(name, krate); let existing_path = build.crate_paths.insert(relative_path, name); - assert!(existing_path.is_none(), "multiple crates with the same path"); + assert!( + existing_path.is_none(), + "multiple crates with the same path: {}", + existing_path.unwrap() + ); } } } @@ -60,7 +71,7 @@ pub fn build(build: &mut Build) { /// Note that `src/tools/cargo` is no longer a workspace member but we still /// treat it as one here, by invoking an additional `cargo metadata` command. fn workspace_members(build: &Build) -> impl Iterator { - let cmd_metadata = |manifest_path| { + let collect_metadata = |manifest_path| { let mut cargo = Command::new(&build.initial_cargo); cargo .arg("metadata") @@ -68,21 +79,20 @@ fn workspace_members(build: &Build) -> impl Iterator { .arg("1") .arg("--no-deps") .arg("--manifest-path") - .arg(manifest_path); - cargo + .arg(build.src.join(manifest_path)); + let metadata_output = output(&mut cargo); + let Output { packages, .. } = t!(serde_json::from_str(&metadata_output)); + packages }; - // Collects `metadata.packages` from the root workspace. - let root_manifest_path = build.src.join("Cargo.toml"); - let root_output = output(&mut cmd_metadata(&root_manifest_path)); - let Output { packages, .. } = serde_json::from_str(&root_output).unwrap(); - - // Collects `metadata.packages` from src/tools/cargo separately. - let cargo_manifest_path = build.src.join("src/tools/cargo/Cargo.toml"); - let cargo_output = output(&mut cmd_metadata(&cargo_manifest_path)); - let Output { packages: cargo_packages, .. } = serde_json::from_str(&cargo_output).unwrap(); + // Collects `metadata.packages` from all workspaces. + let packages = collect_metadata("Cargo.toml"); + let cargo_packages = collect_metadata("src/tools/cargo/Cargo.toml"); + let ra_packages = collect_metadata("src/tools/rust-analyzer/Cargo.toml"); + let bootstrap_packages = collect_metadata("src/bootstrap/Cargo.toml"); // We only care about the root package from `src/tool/cargo` workspace. let cargo_package = cargo_packages.into_iter().find(|pkg| pkg.name == "cargo").into_iter(); - packages.into_iter().chain(cargo_package) + + packages.into_iter().chain(cargo_package).chain(ra_packages).chain(bootstrap_packages) } diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index e1ba61fca8e57..9540adea18973 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -103,7 +103,8 @@ impl Step for CrateBootstrap { path, bootstrap_host, )); - run_cargo_test(cargo, &[], &[], compiler, bootstrap_host, builder); + let crate_name = path.rsplit_once('/').unwrap().1; + run_cargo_test(cargo, &[], &[], crate_name, compiler, bootstrap_host, builder); } } @@ -152,7 +153,11 @@ You can skip linkcheck with --exclude src/tools/linkchecker" SourceType::InTree, &[], ); - run_cargo_test(cargo, &[], &[], compiler, bootstrap_host, builder); + run_cargo_test(cargo, &[], &[], "linkchecker", compiler, bootstrap_host, builder); + + if builder.doc_tests == DocTests::No { + return; + } // Build all the default documentation. builder.default_doc(&[]); @@ -307,7 +312,7 @@ impl Step for Cargo { cargo.env("CARGO_TEST_DISABLE_NIGHTLY", "1"); cargo.env("PATH", &path_for_cargo(builder, compiler)); - run_cargo_test(cargo, &[], &[], compiler, self.host, builder); + run_cargo_test(cargo, &[], &[], "cargo", compiler, self.host, builder); } } @@ -364,7 +369,7 @@ impl Step for RustAnalyzer { cargo.env("SKIP_SLOW_TESTS", "1"); cargo.add_rustc_lib_path(builder, compiler); - run_cargo_test(cargo, &[], &[], compiler, host, builder); + run_cargo_test(cargo, &[], &[], "rust-analyzer", compiler, host, builder); } } @@ -413,7 +418,7 @@ impl Step for Rustfmt { cargo.add_rustc_lib_path(builder, compiler); - run_cargo_test(cargo, &[], &[], compiler, host, builder); + run_cargo_test(cargo, &[], &[], "rustfmt", compiler, host, builder); } } @@ -461,7 +466,7 @@ impl Step for RustDemangler { cargo.env("RUST_DEMANGLER_DRIVER_PATH", rust_demangler); cargo.add_rustc_lib_path(builder, compiler); - run_cargo_test(cargo, &[], &[], compiler, host, builder); + run_cargo_test(cargo, &[], &[], "rust-demangler", compiler, host, builder); } } @@ -598,7 +603,7 @@ impl Step for Miri { // This can NOT be `run_cargo_test` since the Miri test runner // does not understand the flags added by `add_flags_and_try_run_test`. - let mut cargo = prepare_cargo_test(cargo, &[], &[], compiler, target, builder); + let mut cargo = prepare_cargo_test(cargo, &[], &[], "miri", compiler, target, builder); { let _time = util::timeit(&builder); builder.run(&mut cargo); @@ -675,7 +680,7 @@ impl Step for CompiletestTest { &[], ); cargo.allow_features("test"); - run_cargo_test(cargo, &[], &[], compiler, host, builder); + run_cargo_test(cargo, &[], &[], "compiletest", compiler, host, builder); } } @@ -718,17 +723,13 @@ impl Step for Clippy { &[], ); - if !builder.fail_fast { - cargo.arg("--no-fail-fast"); - } - cargo.env("RUSTC_TEST_SUITE", builder.rustc(compiler)); cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(compiler)); let host_libs = builder.stage_out(compiler, Mode::ToolRustc).join(builder.cargo_dir()); cargo.env("HOST_LIBS", host_libs); cargo.add_rustc_lib_path(builder, compiler); - let mut cargo = prepare_cargo_test(cargo, &[], &[], compiler, host, builder); + let mut cargo = prepare_cargo_test(cargo, &[], &[], "clippy", compiler, host, builder); if builder.try_run(&mut cargo) { // The tests succeeded; nothing to do. @@ -2037,11 +2038,13 @@ fn run_cargo_test( cargo: impl Into, libtest_args: &[&str], crates: &[Interned], + primary_crate: &str, compiler: Compiler, target: TargetSelection, builder: &Builder<'_>, ) -> bool { - let mut cargo = prepare_cargo_test(cargo, libtest_args, crates, compiler, target, builder); + let mut cargo = + prepare_cargo_test(cargo, libtest_args, crates, primary_crate, compiler, target, builder); let _time = util::timeit(&builder); add_flags_and_try_run_tests(builder, &mut cargo) } @@ -2051,6 +2054,7 @@ fn prepare_cargo_test( cargo: impl Into, libtest_args: &[&str], crates: &[Interned], + primary_crate: &str, compiler: Compiler, target: TargetSelection, builder: &Builder<'_>, @@ -2068,7 +2072,14 @@ fn prepare_cargo_test( cargo.arg("--doc"); } DocTests::No => { - cargo.args(&["--lib", "--bins", "--examples", "--tests", "--benches"]); + let krate = &builder + .crates + .get(&INTERNER.intern_str(primary_crate)) + .unwrap_or_else(|| panic!("missing crate {primary_crate}")); + if krate.has_lib { + cargo.arg("--lib"); + } + cargo.args(&["--bins", "--examples", "--tests", "--benches"]); } DocTests::Yes => {} } @@ -2181,7 +2192,7 @@ impl Step for Crate { &compiler.host, target )); - run_cargo_test(cargo, &[], &self.crates, compiler, target, builder); + run_cargo_test(cargo, &[], &self.crates, &self.crates[0], compiler, target, builder); } } @@ -2280,6 +2291,7 @@ impl Step for CrateRustdoc { cargo, &[], &[INTERNER.intern_str("rustdoc:0.0.0")], + "rustdoc", compiler, target, builder, @@ -2346,6 +2358,7 @@ impl Step for CrateRustdocJsonTypes { cargo, libtest_args, &[INTERNER.intern_str("rustdoc-json-types")], + "rustdoc-json-types", compiler, target, builder, @@ -2505,7 +2518,7 @@ impl Step for Bootstrap { } // rustbuild tests are racy on directory creation so just run them one at a time. // Since there's not many this shouldn't be a problem. - run_cargo_test(cmd, &["--test-threads=1"], &[], compiler, host, builder); + run_cargo_test(cmd, &["--test-threads=1"], &[], "bootstrap", compiler, host, builder); } fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -2618,7 +2631,7 @@ impl Step for RustInstaller { SourceType::InTree, &[], ); - try_run(builder, &mut cargo.into()); + run_cargo_test(cargo, &[], &[], "installer", compiler, bootstrap_host, builder); // We currently don't support running the test.sh script outside linux(?) environments. // Eventually this should likely migrate to #[test]s in rust-installer proper rather than a From adb5ded7a71911f66e20dd3a85dd39fb236c476d Mon Sep 17 00:00:00 2001 From: whtahy Date: Tue, 18 Apr 2023 00:29:01 -0400 Subject: [PATCH 10/20] add known-bug test for unsound issue 25860 --- ...-bounds-on-nested-references-plus-variance.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance.rs diff --git a/tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance.rs b/tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance.rs new file mode 100644 index 0000000000000..1f5562497c12e --- /dev/null +++ b/tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance.rs @@ -0,0 +1,16 @@ +// check-pass +// known-bug: #25860 + +// Should fail. The combination of variance and implied bounds for nested +// references allows us to infer a longer lifetime than we can prove. + +static UNIT: &'static &'static () = &&(); + +fn foo<'a, 'b, T>(_: &'a &'b (), v: &'b T) -> &'a T { v } + +fn bad<'a, T>(x: &'a T) -> &'static T { + let f: fn(_, &'a T) -> &'static T = foo; + f(UNIT, x) +} + +fn main() {} From 2fb20985a000a8f1cd891ef484f7265a55c1612f Mon Sep 17 00:00:00 2001 From: whtahy Date: Tue, 18 Apr 2023 19:42:48 -0400 Subject: [PATCH 11/20] add known-bug test for unsound issue 49206 --- .../ui/consts/non-sync-references-in-const.rs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/ui/consts/non-sync-references-in-const.rs diff --git a/tests/ui/consts/non-sync-references-in-const.rs b/tests/ui/consts/non-sync-references-in-const.rs new file mode 100644 index 0000000000000..0f668b8d46903 --- /dev/null +++ b/tests/ui/consts/non-sync-references-in-const.rs @@ -0,0 +1,38 @@ +// check-pass +// known-bug: #49206 + +// Should fail. Compiles and prints 2 identical addresses, which shows 2 threads +// with the same `'static` reference to non-`Sync` struct. The problem is that +// promotion to static does not check if the type is `Sync`. + +#[allow(dead_code)] +#[derive(Debug)] +struct Foo { + value: u32, +} + +// stable negative impl trick from https://crates.io/crates/negative-impl +// see https://github.com/taiki-e/pin-project/issues/102#issuecomment-540472282 +// for details. +struct Wrapper<'a, T>(::std::marker::PhantomData<&'a ()>, T); +unsafe impl Sync for Wrapper<'_, T> where T: Sync {} +unsafe impl<'a> std::marker::Sync for Foo where Wrapper<'a, *const ()>: Sync {} +fn _assert_sync() {} + +fn inspect() { + let foo: &'static Foo = &Foo { value: 1 }; + println!( + "I am in thread {:?}, address: {:p}", + std::thread::current().id(), + foo as *const Foo, + ); +} + +fn main() { + // _assert_sync::(); // uncomment this line causes compile error + // "`*const ()` cannot be shared between threads safely" + + let handle = std::thread::spawn(inspect); + inspect(); + handle.join().unwrap(); +} From 232d685e6149227abb28c8fef05b00c4f50bbd28 Mon Sep 17 00:00:00 2001 From: whtahy Date: Tue, 18 Apr 2023 21:30:21 -0400 Subject: [PATCH 12/20] add known-bug test for unsound issue 57893 --- .../indirect-impl-for-trait-obj-coherence.rs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/ui/coherence/indirect-impl-for-trait-obj-coherence.rs diff --git a/tests/ui/coherence/indirect-impl-for-trait-obj-coherence.rs b/tests/ui/coherence/indirect-impl-for-trait-obj-coherence.rs new file mode 100644 index 0000000000000..bb46498f90eba --- /dev/null +++ b/tests/ui/coherence/indirect-impl-for-trait-obj-coherence.rs @@ -0,0 +1,25 @@ +// check-pass +// known-bug: #57893 + +// Should fail. Because we see an impl that uses a certain associated type, we +// type-check assuming that impl is used. However, this conflicts with the +// "implicit impl" that we get for trait objects, violating coherence. + +trait Object { + type Output; +} + +impl Object for T { + type Output = U; +} + +fn foo(x: >::Output) -> U { + x +} + +#[allow(dead_code)] +fn transmute(x: T) -> U { + foo::, U>(x) +} + +fn main() {} From fbfb620de8afdabddfa819e698cdbfc7a7b10797 Mon Sep 17 00:00:00 2001 From: whtahy Date: Tue, 18 Apr 2023 23:11:43 -0400 Subject: [PATCH 13/20] add known-bug test for unsound issue 84366 --- .../static-closures-with-nonstatic-return.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/ui/closures/static-closures-with-nonstatic-return.rs diff --git a/tests/ui/closures/static-closures-with-nonstatic-return.rs b/tests/ui/closures/static-closures-with-nonstatic-return.rs new file mode 100644 index 0000000000000..b5f0684bae92b --- /dev/null +++ b/tests/ui/closures/static-closures-with-nonstatic-return.rs @@ -0,0 +1,15 @@ +// check-pass +// known-bug: #84366 + +// Should fail. Associated types of 'static types should be `'static`, but +// argument-free closures can be `'static` and return non-`'static` types. + +#[allow(dead_code)] +fn foo<'a>() { + let closure = || -> &'a str { "" }; + assert_static(closure); +} + +fn assert_static(_: T) {} + +fn main() {} From cac62ab2dde20adca6e1a9070211afbc784c1d64 Mon Sep 17 00:00:00 2001 From: whtahy Date: Tue, 18 Apr 2023 23:55:20 -0400 Subject: [PATCH 14/20] add known-bug test for unsound issue 84533 --- tests/ui/fn/fn-item-lifetime-bounds.rs | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/ui/fn/fn-item-lifetime-bounds.rs diff --git a/tests/ui/fn/fn-item-lifetime-bounds.rs b/tests/ui/fn/fn-item-lifetime-bounds.rs new file mode 100644 index 0000000000000..68a1d0ce9b0b2 --- /dev/null +++ b/tests/ui/fn/fn-item-lifetime-bounds.rs @@ -0,0 +1,37 @@ +// check-pass +// known-bug: #84533 + +// Should fail. Lifetimes are checked correctly when `foo` is called, but NOT +// when only the lifetime parameters are instantiated. + +use std::marker::PhantomData; + +#[allow(dead_code)] +fn foo<'b, 'a>() -> PhantomData<&'b &'a ()> { + PhantomData +} + +#[allow(dead_code)] +#[allow(path_statements)] +fn caller<'b, 'a>() { + foo::<'b, 'a>; +} + +// In contrast to above, below code correctly does NOT compile. +// fn caller<'b, 'a>() { +// foo::<'b, 'a>(); +// } + +// error: lifetime may not live long enough +// --> src/main.rs:22:5 +// | +// 21 | fn caller<'b, 'a>() { +// | -- -- lifetime `'a` defined here +// | | +// | lifetime `'b` defined here +// 22 | foo::<'b, 'a>(); +// | ^^^^^^^^^^^^^^^ requires that `'a` must outlive `'b` +// | +// = help: consider adding the following bound: `'a: 'b` + +fn main() {} From be68c69e7123fbe513c1f5689f61018e3f94220e Mon Sep 17 00:00:00 2001 From: whtahy Date: Wed, 19 Apr 2023 00:44:07 -0400 Subject: [PATCH 15/20] add known-bug test for unsound issue 84591 --- .../implied-bounds-on-trait-hierarchy.rs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/ui/implied-bounds/implied-bounds-on-trait-hierarchy.rs diff --git a/tests/ui/implied-bounds/implied-bounds-on-trait-hierarchy.rs b/tests/ui/implied-bounds/implied-bounds-on-trait-hierarchy.rs new file mode 100644 index 0000000000000..9c26cd59d100a --- /dev/null +++ b/tests/ui/implied-bounds/implied-bounds-on-trait-hierarchy.rs @@ -0,0 +1,39 @@ +// check-pass +// known-bug: #84591 + +// Should fail. Subtrait can incorrectly extend supertrait lifetimes even when +// supertrait has weaker implied bounds than subtrait. Strongly related to +// issue #25860. + +trait Subtrait: Supertrait {} +trait Supertrait { + fn action(self); +} + +fn subs_to_soup(x: T) +where + T: Subtrait, +{ + soup(x) +} + +fn soup(x: T) { + x.action(); +} + +impl<'a, 'b: 'a> Supertrait for (&'b str, &mut &'a str) { + fn action(self) { + *self.1 = self.0; + } +} + +impl<'a, 'b> Subtrait<&'a &'b str> for (&'b str, &mut &'a str) {} + +fn main() { + let mut d = "hi"; + { + let x = "Hello World".to_string(); + subs_to_soup((x.as_str(), &mut d)); + } + println!("{}", d); +} From 3c5de9a2e85e4490fad8c30a8078ac7117a01836 Mon Sep 17 00:00:00 2001 From: whtahy Date: Thu, 20 Apr 2023 01:16:21 -0400 Subject: [PATCH 16/20] add known-bug test for unsound issue 85099 --- .../pin-unsound-issue-85099-derefmut.rs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/ui/typeck/pin-unsound-issue-85099-derefmut.rs diff --git a/tests/ui/typeck/pin-unsound-issue-85099-derefmut.rs b/tests/ui/typeck/pin-unsound-issue-85099-derefmut.rs new file mode 100644 index 0000000000000..03602144e5001 --- /dev/null +++ b/tests/ui/typeck/pin-unsound-issue-85099-derefmut.rs @@ -0,0 +1,68 @@ +// check-pass +// known-bug: #85099 + +// Should fail. Can coerce `Pin` into `Pin` where +// `T: Deref` and `U: Deref`, using the +// `CoerceUnsized` impl on `Pin` and an unorthodox `DerefMut` impl for +// `Pin<&_>`. + +// This should not be allowed, since one can unpin `T::Target` (since it is +// `Unpin`) to gain unpinned access to the previously pinned `U::Target` (which +// is `!Unpin`) and then move it. + +use std::{ + cell::{RefCell, RefMut}, + future::Future, + ops::DerefMut, + pin::Pin, +}; + +struct SomeLocalStruct<'a, Fut>(&'a RefCell); + +trait SomeTrait<'a, Fut> { + #[allow(clippy::mut_from_ref)] + fn deref_helper(&self) -> &mut (dyn SomeTrait<'a, Fut> + 'a) { + unimplemented!() + } + fn downcast(self: Pin<&mut Self>) -> Pin<&mut Fut> { + unimplemented!() + } +} + +impl<'a, Fut: Future> SomeTrait<'a, Fut> for SomeLocalStruct<'a, Fut> { + fn deref_helper(&self) -> &mut (dyn SomeTrait<'a, Fut> + 'a) { + let x = Box::new(self.0.borrow_mut()); + let x: &'a mut RefMut<'a, Fut> = Box::leak(x); + &mut **x + } +} +impl<'a, Fut: Future> SomeTrait<'a, Fut> for Fut { + fn downcast(self: Pin<&mut Self>) -> Pin<&mut Fut> { + self + } +} + +impl<'b, 'a, Fut> DerefMut for Pin<&'b dyn SomeTrait<'a, Fut>> { + fn deref_mut<'c>( + self: &'c mut Pin<&'b dyn SomeTrait<'a, Fut>>, + ) -> &'c mut (dyn SomeTrait<'a, Fut> + 'b) { + self.deref_helper() + } +} + +// obviously a "working" function with this signature is problematic +pub fn unsound_pin>( + fut: Fut, + callback: impl FnOnce(Pin<&mut Fut>), +) -> Fut { + let cell = RefCell::new(fut); + let s: &SomeLocalStruct<'_, Fut> = &SomeLocalStruct(&cell); + let p: Pin>> = Pin::new(Pin::new(s)); + let mut p: Pin>> = p; + let r: Pin<&mut dyn SomeTrait<'_, Fut>> = p.as_mut(); + let f: Pin<&mut Fut> = r.downcast(); + callback(f); + cell.into_inner() +} + +fn main() {} From 314126257d3d20e3e788325f3b88b7e635bd6bb8 Mon Sep 17 00:00:00 2001 From: whtahy Date: Sat, 22 Apr 2023 13:31:00 -0400 Subject: [PATCH 17/20] add known-bug test for unsound issue 98117 --- tests/ui/wf/wf-in-where-clause-static.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tests/ui/wf/wf-in-where-clause-static.rs diff --git a/tests/ui/wf/wf-in-where-clause-static.rs b/tests/ui/wf/wf-in-where-clause-static.rs new file mode 100644 index 0000000000000..86722afdf9fdd --- /dev/null +++ b/tests/ui/wf/wf-in-where-clause-static.rs @@ -0,0 +1,23 @@ +// check-pass +// known-bug: #98117 + +// Should fail. Functions are responsible for checking the well-formedness of +// their own where clauses, so this should fail and require an explicit bound +// `T: 'static`. + +use std::fmt::Display; + +trait Static: 'static {} +impl Static for &'static T {} + +fn foo(x: S) -> Box +where + &'static S: Static, +{ + Box::new(x) +} + +fn main() { + let s = foo(&String::from("blah blah blah")); + println!("{}", s); +} From cff6c0e0c8d33efe4b64b08751008ac353c9b232 Mon Sep 17 00:00:00 2001 From: whtahy Date: Sat, 22 Apr 2023 13:37:13 -0400 Subject: [PATCH 18/20] add known-bug test for unsound issue 100041 --- tests/ui/wf/wf-normalization-sized.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/ui/wf/wf-normalization-sized.rs diff --git a/tests/ui/wf/wf-normalization-sized.rs b/tests/ui/wf/wf-normalization-sized.rs new file mode 100644 index 0000000000000..473fc79a8a39d --- /dev/null +++ b/tests/ui/wf/wf-normalization-sized.rs @@ -0,0 +1,19 @@ +// check-pass +// known-bug: #100041 + +// Should fail. Normalization can bypass well-formedness checking. +// `[[[[[[u8]]]]]]` is not a well-formed type since size of type `[u8]` cannot +// be known at compile time (since `Sized` is not implemented for `[u8]`). + +trait WellUnformed { + type RequestNormalize; +} + +impl WellUnformed for T { + type RequestNormalize = (); +} + +const _: <[[[[[[u8]]]]]] as WellUnformed>::RequestNormalize = (); +const _: as WellUnformed>::RequestNormalize = (); + +fn main() {} From 6f6550f156eb6ea18e90db1712b30460613ad4a8 Mon Sep 17 00:00:00 2001 From: whtahy Date: Sat, 22 Apr 2023 13:41:53 -0400 Subject: [PATCH 19/20] add known-bug test for unsound issue 100051 --- .../implied-bounds-impl-header-projections.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/ui/fn/implied-bounds-impl-header-projections.rs diff --git a/tests/ui/fn/implied-bounds-impl-header-projections.rs b/tests/ui/fn/implied-bounds-impl-header-projections.rs new file mode 100644 index 0000000000000..28cec8050327f --- /dev/null +++ b/tests/ui/fn/implied-bounds-impl-header-projections.rs @@ -0,0 +1,31 @@ +// check-pass +// known-bug: #100051 + +// Should fail. Implied bounds from projections in impl headers can create +// improper lifetimes. Variant of issue #98543 which was fixed by #99217. + +trait Trait { + type Type; +} + +impl Trait for T { + type Type = (); +} + +trait Extend<'a, 'b> { + fn extend(self, s: &'a str) -> &'b str; +} + +impl<'a, 'b> Extend<'a, 'b> for <&'b &'a () as Trait>::Type +where + for<'what, 'ever> &'what &'ever (): Trait, +{ + fn extend(self, s: &'a str) -> &'b str { + s + } +} + +fn main() { + let y = <() as Extend<'_, '_>>::extend((), &String::from("Hello World")); + println!("{}", y); +} From ebe61cefc4fa30f0a719892d544abcaee25da44c Mon Sep 17 00:00:00 2001 From: whtahy Date: Sat, 22 Apr 2023 13:57:34 -0400 Subject: [PATCH 20/20] add known-bug test for unsound issue 104005 --- tests/ui/wf/wf-in-fn-type-implicit.rs | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/ui/wf/wf-in-fn-type-implicit.rs diff --git a/tests/ui/wf/wf-in-fn-type-implicit.rs b/tests/ui/wf/wf-in-fn-type-implicit.rs new file mode 100644 index 0000000000000..c5ff92c88754a --- /dev/null +++ b/tests/ui/wf/wf-in-fn-type-implicit.rs @@ -0,0 +1,37 @@ +// check-pass +// known-bug: #104005 + +// Should fail. Function type parameters with implicit type annotations are not +// checked for well-formedness, which allows incorrect borrowing. + +// In contrast, user annotations are always checked for well-formedness, and the +// commented code below is correctly rejected by the borrow checker. + +use std::fmt::Display; + +trait Displayable { + fn display(self) -> Box; +} + +impl Displayable for (T, Option<&'static T>) { + fn display(self) -> Box { + Box::new(self.0) + } +} + +fn extend_lt(val: T) -> Box +where + (T, Option): Displayable, +{ + Displayable::display((val, None)) +} + +fn main() { + // *incorrectly* compiles + let val = extend_lt(&String::from("blah blah blah")); + println!("{}", val); + + // *correctly* fails to compile + // let val = extend_lt::<_, &_>(&String::from("blah blah blah")); + // println!("{}", val); +}