Auto merge of #138058 - jieyouxu:rollup-skdt0oz, r=jieyouxu
Rollup of 20 pull requests Successful merges: - #134063 (dec2flt: Clean up float parsing modules) - #136581 (Retire the legacy `Makefile`-based `run-make` test infra) - #136662 (Count char width at most once in `Formatter::pad`) - #136764 (Make `ptr_cast_add_auto_to_object` lint into hard error) - #136798 (Added documentation for flushing per #74348) - #136865 (Perform deeper compiletest path normalization for `$TEST_BUILD_DIR` to account for compare-mode/debugger cases, and normalize long type file filename hashes) - #136975 (Look for `python3` first on MacOS, not `py`) - #136977 (Upload Datadog metrics with citool) - #137240 (Slightly reformat `std::fs::remove_dir_all` error docs) - #137298 (Check signature WF when lowering MIR body) - #137463 ([illumos] attempt to use posix_spawn to spawn processes) - #137477 (uefi: Add Service Binding Protocol abstraction) - #137569 (Stabilize `string_extend_from_within`) - #137633 (Only use implied bounds hack if bevy, and use deeply normalize in implied bounds hack) - #137679 (Various coretests improvements) - #137723 (Make `rust.description` more general-purpose and pass `CFG_VER_DESCRIPTION`) - #137728 (Remove unsizing coercions for tuples) - #137731 (Resume one waiter at once in deadlock handler) - #137875 (mir_build: Integrate "simplification" steps into match-pair-tree creation) - #138028 (compiler: add `ExternAbi::is_rustic_abi`) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
07b5eeebc9
254 changed files with 2856 additions and 8345 deletions
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
|
@ -58,8 +58,8 @@ jobs:
|
|||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
run: |
|
||||
cd src/ci/citool
|
||||
cargo test
|
||||
cargo run calculate-job-matrix >> $GITHUB_OUTPUT
|
||||
CARGO_INCREMENTAL=0 cargo test
|
||||
CARGO_INCREMENTAL=0 cargo run calculate-job-matrix >> $GITHUB_OUTPUT
|
||||
id: jobs
|
||||
job:
|
||||
name: ${{ matrix.full_name }}
|
||||
|
@ -183,11 +183,11 @@ jobs:
|
|||
run: src/ci/scripts/dump-environment.sh
|
||||
|
||||
# Pre-build citool before the following step uninstalls rustup
|
||||
# Build is into the build directory, to avoid modifying sources
|
||||
# Build it into the build directory, to avoid modifying sources
|
||||
- name: build citool
|
||||
run: |
|
||||
cd src/ci/citool
|
||||
CARGO_TARGET_DIR=../../../build/citool cargo build
|
||||
CARGO_INCREMENTAL=0 CARGO_TARGET_DIR=../../../build/citool cargo build
|
||||
|
||||
- name: run the build
|
||||
# Redirect stderr to stdout to avoid reordering the two streams in the GHA logs.
|
||||
|
@ -238,13 +238,9 @@ jobs:
|
|||
- name: upload job metrics to DataDog
|
||||
if: needs.calculate_matrix.outputs.run_type != 'pr'
|
||||
env:
|
||||
DATADOG_SITE: datadoghq.com
|
||||
DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }}
|
||||
DD_GITHUB_JOB_NAME: ${{ matrix.full_name }}
|
||||
run: |
|
||||
cd src/ci
|
||||
npm ci
|
||||
python3 scripts/upload-build-metrics.py ../../build/cpu-usage.csv
|
||||
run: ./build/citool/debug/citool upload-build-metrics build/cpu-usage.csv
|
||||
|
||||
# This job isused to tell bors the final status of the build, as there is no practical way to detect
|
||||
# when a workflow is successful listening to webhooks only in our current bors implementation (homu).
|
||||
|
|
|
@ -191,6 +191,17 @@ impl StableOrd for ExternAbi {
|
|||
}
|
||||
|
||||
impl ExternAbi {
|
||||
/// An ABI "like Rust"
|
||||
///
|
||||
/// These ABIs are fully controlled by the Rust compiler, which means they
|
||||
/// - support unwinding with `-Cpanic=unwind`, unlike `extern "C"`
|
||||
/// - often diverge from the C ABI
|
||||
/// - are subject to change between compiler versions
|
||||
pub fn is_rustic_abi(self) -> bool {
|
||||
use ExternAbi::*;
|
||||
matches!(self, Rust | RustCall | RustIntrinsic | RustCold)
|
||||
}
|
||||
|
||||
pub fn supports_varargs(self) -> bool {
|
||||
// * C and Cdecl obviously support varargs.
|
||||
// * C can be based on Aapcs, SysV64 or Win64, so they must support varargs.
|
||||
|
|
41
compiler/rustc_error_codes/src/error_codes/E0804.md
Normal file
41
compiler/rustc_error_codes/src/error_codes/E0804.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
An auto trait cannot be added to the bounds of a `dyn Trait` type via
|
||||
a pointer cast.
|
||||
|
||||
Erroneous code example:
|
||||
|
||||
```rust,edition2021,compile_fail,E0804
|
||||
let ptr: *const dyn core::any::Any = &();
|
||||
_ = ptr as *const (dyn core::any::Any + Send);
|
||||
```
|
||||
|
||||
Adding an auto trait can make the vtable invalid, potentially causing
|
||||
UB in safe code afterwards. For example:
|
||||
|
||||
```rust,edition2021,no_run
|
||||
use core::{mem::transmute, ptr::NonNull};
|
||||
|
||||
trait Trait {
|
||||
fn f(&self)
|
||||
where
|
||||
Self: Send;
|
||||
}
|
||||
|
||||
impl Trait for NonNull<()> {
|
||||
fn f(&self) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let unsend: &dyn Trait = &NonNull::dangling();
|
||||
let bad: &(dyn Trait + Send) = unsafe { transmute(unsend) };
|
||||
// This crashes, since the vtable for `NonNull as dyn Trait` does
|
||||
// not have an entry for `Trait::f`.
|
||||
bad.f();
|
||||
}
|
||||
```
|
||||
|
||||
To fix this error, you can use `transmute` rather than pointer casts,
|
||||
but you must ensure that the vtable is valid for the pointer's type
|
||||
before calling a method on the trait object or allowing other code to
|
||||
do so.
|
|
@ -547,6 +547,7 @@ E0800: 0800,
|
|||
E0801: 0801,
|
||||
E0802: 0802,
|
||||
E0803: 0803,
|
||||
E0804: 0804,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -244,6 +244,8 @@ declare_features! (
|
|||
/// Allows unnamed fields of struct and union type
|
||||
(removed, unnamed_fields, "1.83.0", Some(49804), Some("feature needs redesign")),
|
||||
(removed, unsafe_no_drop_flag, "1.0.0", None, None),
|
||||
(removed, unsized_tuple_coercion, "CURRENT_RUSTC_VERSION", Some(42877),
|
||||
Some("The feature restricts possible layouts for tuples, and this restriction is not worth it.")),
|
||||
/// Allows `union` fields that don't implement `Copy` as long as they don't have any drop glue.
|
||||
(removed, untagged_unions, "1.13.0", Some(55149),
|
||||
Some("unions with `Copy` and `ManuallyDrop` fields are stable; there is no intent to stabilize more")),
|
||||
|
|
|
@ -659,8 +659,6 @@ declare_features! (
|
|||
(internal, unsized_fn_params, "1.49.0", Some(48055)),
|
||||
/// Allows unsized rvalues at arguments and parameters.
|
||||
(incomplete, unsized_locals, "1.30.0", Some(48055)),
|
||||
/// Allows unsized tuple coercion.
|
||||
(unstable, unsized_tuple_coercion, "1.20.0", Some(42877)),
|
||||
/// Allows using the `#[used(linker)]` (or `#[used(compiler)]`) attribute.
|
||||
(unstable, used_with_arg, "1.60.0", Some(93798)),
|
||||
/// Allows use of attributes in `where` clauses.
|
||||
|
|
|
@ -745,14 +745,10 @@ fn check_static_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) {
|
|||
pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) {
|
||||
match tcx.def_kind(def_id) {
|
||||
DefKind::Static { .. } => {
|
||||
tcx.ensure_ok().typeck(def_id);
|
||||
maybe_check_static_with_link_section(tcx, def_id);
|
||||
check_static_inhabited(tcx, def_id);
|
||||
check_static_linkage(tcx, def_id);
|
||||
}
|
||||
DefKind::Const => {
|
||||
tcx.ensure_ok().typeck(def_id);
|
||||
}
|
||||
DefKind::Const => {}
|
||||
DefKind::Enum => {
|
||||
check_enum(tcx, def_id);
|
||||
}
|
||||
|
@ -766,7 +762,6 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) {
|
|||
ExternAbi::Rust,
|
||||
)
|
||||
}
|
||||
// Everything else is checked entirely within check_item_body
|
||||
}
|
||||
DefKind::Impl { of_trait } => {
|
||||
if of_trait && let Some(impl_trait_header) = tcx.impl_trait_header(def_id) {
|
||||
|
|
|
@ -145,7 +145,7 @@ pub fn forbid_intrinsic_abi(tcx: TyCtxt<'_>, sp: Span, abi: ExternAbi) {
|
|||
}
|
||||
}
|
||||
|
||||
fn maybe_check_static_with_link_section(tcx: TyCtxt<'_>, id: LocalDefId) {
|
||||
pub(super) fn maybe_check_static_with_link_section(tcx: TyCtxt<'_>, id: LocalDefId) {
|
||||
// Only restricted on wasm target for now
|
||||
if !tcx.sess.target.is_like_wasm {
|
||||
return;
|
||||
|
|
|
@ -126,13 +126,14 @@ where
|
|||
|
||||
let infcx_compat = infcx.fork();
|
||||
|
||||
// We specifically want to call the non-compat version of `implied_bounds_tys`; we do this always.
|
||||
// We specifically want to *disable* the implied bounds hack, first,
|
||||
// so we can detect when failures are due to bevy's implied bounds.
|
||||
let outlives_env = OutlivesEnvironment::new_with_implied_bounds_compat(
|
||||
&infcx,
|
||||
body_def_id,
|
||||
param_env,
|
||||
assumed_wf_types.iter().copied(),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
|
||||
lint_redundant_lifetimes(tcx, body_def_id, &outlives_env);
|
||||
|
@ -142,53 +143,22 @@ where
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let is_bevy = assumed_wf_types.visit_with(&mut ContainsBevyParamSet { tcx }).is_break();
|
||||
|
||||
// If we have set `no_implied_bounds_compat`, then do not attempt compatibility.
|
||||
// We could also just always enter if `is_bevy`, and call `implied_bounds_tys`,
|
||||
// but that does result in slightly more work when this option is set and
|
||||
// just obscures what we mean here anyways. Let's just be explicit.
|
||||
if is_bevy && !infcx.tcx.sess.opts.unstable_opts.no_implied_bounds_compat {
|
||||
let outlives_env = OutlivesEnvironment::new_with_implied_bounds_compat(
|
||||
&infcx,
|
||||
body_def_id,
|
||||
param_env,
|
||||
assumed_wf_types,
|
||||
true,
|
||||
);
|
||||
let errors_compat = infcx_compat.resolve_regions_with_outlives_env(&outlives_env);
|
||||
if errors_compat.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(infcx_compat.err_ctxt().report_region_errors(body_def_id, &errors_compat))
|
||||
}
|
||||
let outlives_env = OutlivesEnvironment::new_with_implied_bounds_compat(
|
||||
&infcx_compat,
|
||||
body_def_id,
|
||||
param_env,
|
||||
assumed_wf_types,
|
||||
// Don't *disable* the implied bounds hack; though this will only apply
|
||||
// the implied bounds hack if this contains `bevy_ecs`'s `ParamSet` type.
|
||||
false,
|
||||
);
|
||||
let errors_compat = infcx_compat.resolve_regions_with_outlives_env(&outlives_env);
|
||||
if errors_compat.is_empty() {
|
||||
// FIXME: Once we fix bevy, this would be the place to insert a warning
|
||||
// to upgrade bevy.
|
||||
Ok(())
|
||||
} else {
|
||||
Err(infcx.err_ctxt().report_region_errors(body_def_id, &errors))
|
||||
}
|
||||
}
|
||||
|
||||
struct ContainsBevyParamSet<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsBevyParamSet<'tcx> {
|
||||
type Result = ControlFlow<()>;
|
||||
|
||||
fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
|
||||
// We only care to match `ParamSet<T>` or `&ParamSet<T>`.
|
||||
match t.kind() {
|
||||
ty::Adt(def, _) => {
|
||||
if self.tcx.item_name(def.did()) == sym::ParamSet
|
||||
&& self.tcx.crate_name(def.did().krate) == sym::bevy_ecs
|
||||
{
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
}
|
||||
ty::Ref(_, ty, _) => ty.visit_with(self)?,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
ControlFlow::Continue(())
|
||||
Err(infcx_compat.err_ctxt().report_region_errors(body_def_id, &errors_compat))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,7 +171,7 @@ fn check_well_formed(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGua
|
|||
hir::Node::ImplItem(item) => check_impl_item(tcx, item),
|
||||
hir::Node::ForeignItem(item) => check_foreign_item(tcx, item),
|
||||
hir::Node::OpaqueTy(_) => Ok(crate::check::check::check_item_type(tcx, def_id)),
|
||||
_ => unreachable!(),
|
||||
_ => unreachable!("{node:?}"),
|
||||
};
|
||||
|
||||
if let Some(generics) = node.generics() {
|
||||
|
@ -1108,7 +1078,13 @@ fn check_associated_item(
|
|||
let ty = tcx.type_of(item.def_id).instantiate_identity();
|
||||
let ty = wfcx.normalize(span, Some(WellFormedLoc::Ty(item_id)), ty);
|
||||
wfcx.register_wf_obligation(span, loc, ty.into());
|
||||
check_sized_if_body(wfcx, item.def_id.expect_local(), ty, Some(span));
|
||||
check_sized_if_body(
|
||||
wfcx,
|
||||
item.def_id.expect_local(),
|
||||
ty,
|
||||
Some(span),
|
||||
ObligationCauseCode::SizedConstOrStatic,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
ty::AssocKind::Fn => {
|
||||
|
@ -1354,7 +1330,7 @@ fn check_item_type(
|
|||
traits::ObligationCause::new(
|
||||
ty_span,
|
||||
wfcx.body_def_id,
|
||||
ObligationCauseCode::WellFormed(None),
|
||||
ObligationCauseCode::SizedConstOrStatic,
|
||||
),
|
||||
wfcx.param_env,
|
||||
item_ty,
|
||||
|
@ -1698,6 +1674,7 @@ fn check_fn_or_method<'tcx>(
|
|||
hir::FnRetTy::Return(ty) => Some(ty.span),
|
||||
hir::FnRetTy::DefaultReturn(_) => None,
|
||||
},
|
||||
ObligationCauseCode::SizedReturnType,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1706,13 +1683,14 @@ fn check_sized_if_body<'tcx>(
|
|||
def_id: LocalDefId,
|
||||
ty: Ty<'tcx>,
|
||||
maybe_span: Option<Span>,
|
||||
code: ObligationCauseCode<'tcx>,
|
||||
) {
|
||||
let tcx = wfcx.tcx();
|
||||
if let Some(body) = tcx.hir_maybe_body_owned_by(def_id) {
|
||||
let span = maybe_span.unwrap_or(body.value.span);
|
||||
|
||||
wfcx.register_bound(
|
||||
ObligationCause::new(span, def_id, traits::ObligationCauseCode::SizedReturnType),
|
||||
ObligationCause::new(span, def_id, code),
|
||||
wfcx.param_env,
|
||||
ty,
|
||||
tcx.require_lang_item(LangItem::Sized, Some(span)),
|
||||
|
|
|
@ -212,7 +212,10 @@ pub fn check_crate(tcx: TyCtxt<'_>) {
|
|||
tcx.par_hir_body_owners(|item_def_id| {
|
||||
let def_kind = tcx.def_kind(item_def_id);
|
||||
match def_kind {
|
||||
DefKind::Static { .. } => tcx.ensure_ok().eval_static_initializer(item_def_id),
|
||||
DefKind::Static { .. } => {
|
||||
tcx.ensure_ok().eval_static_initializer(item_def_id);
|
||||
check::maybe_check_static_with_link_section(tcx, item_def_id);
|
||||
}
|
||||
DefKind::Const if tcx.generics_of(item_def_id).is_empty() => {
|
||||
let instance = ty::Instance::new(item_def_id.into(), ty::GenericArgs::empty());
|
||||
let cid = GlobalId { instance, promoted: None };
|
||||
|
@ -223,12 +226,9 @@ pub fn check_crate(tcx: TyCtxt<'_>) {
|
|||
}
|
||||
});
|
||||
|
||||
// FIXME: Remove this when we implement creating `DefId`s
|
||||
// for anon constants during their parents' typeck.
|
||||
// Typeck all body owners in parallel will produce queries
|
||||
// cycle errors because it may typeck on anon constants directly.
|
||||
tcx.par_hir_body_owners(|item_def_id| {
|
||||
let def_kind = tcx.def_kind(item_def_id);
|
||||
// Skip `AnonConst`s because we feed their `type_of`.
|
||||
if !matches!(def_kind, DefKind::AnonConst) {
|
||||
tcx.ensure_ok().typeck(item_def_id);
|
||||
}
|
||||
|
|
|
@ -171,10 +171,13 @@ hir_typeck_pass_to_variadic_function = can't pass `{$ty}` to variadic function
|
|||
.suggestion = cast the value to `{$cast_ty}`
|
||||
.teach_help = certain types, like `{$ty}`, must be casted before passing them to a variadic function, because of arcane ABI rules dictated by the C standard
|
||||
|
||||
hir_typeck_ptr_cast_add_auto_to_object = adding {$traits_len ->
|
||||
[1] an auto trait {$traits}
|
||||
hir_typeck_ptr_cast_add_auto_to_object = cannot add {$traits_len ->
|
||||
[1] auto trait {$traits}
|
||||
*[other] auto traits {$traits}
|
||||
} to a trait object in a pointer cast may cause UB later on
|
||||
} to dyn bound via pointer cast
|
||||
.note = this could allow UB elsewhere
|
||||
.help = use `transmute` if you're sure this is sound
|
||||
.label = unsupported cast
|
||||
|
||||
hir_typeck_remove_semi_for_coerce = you might have meant to return the `match` expression
|
||||
hir_typeck_remove_semi_for_coerce_expr = this could be implicitly returned but it is a statement, not a tail expression
|
||||
|
|
|
@ -940,23 +940,19 @@ impl<'a, 'tcx> CastCheck<'tcx> {
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
if !added.is_empty() {
|
||||
tcx.emit_node_span_lint(
|
||||
lint::builtin::PTR_CAST_ADD_AUTO_TO_OBJECT,
|
||||
self.expr.hir_id,
|
||||
self.span,
|
||||
errors::PtrCastAddAutoToObject {
|
||||
traits_len: added.len(),
|
||||
traits: {
|
||||
let mut traits: Vec<_> = added
|
||||
.into_iter()
|
||||
.map(|trait_did| tcx.def_path_str(trait_did))
|
||||
.collect();
|
||||
tcx.dcx().emit_err(errors::PtrCastAddAutoToObject {
|
||||
span: self.span,
|
||||
traits_len: added.len(),
|
||||
traits: {
|
||||
let mut traits: Vec<_> = added
|
||||
.into_iter()
|
||||
.map(|trait_did| tcx.def_path_str(trait_did))
|
||||
.collect();
|
||||
|
||||
traits.sort();
|
||||
traits.into()
|
||||
},
|
||||
traits.sort();
|
||||
traits.into()
|
||||
},
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
Ok(CastKind::PtrPtrCast)
|
||||
|
|
|
@ -51,15 +51,13 @@ use rustc_infer::traits::{
|
|||
PredicateObligations,
|
||||
};
|
||||
use rustc_middle::span_bug;
|
||||
use rustc_middle::traits::BuiltinImplSource;
|
||||
use rustc_middle::ty::adjustment::{
|
||||
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCoercion,
|
||||
};
|
||||
use rustc_middle::ty::error::TypeError;
|
||||
use rustc_middle::ty::visit::TypeVisitableExt;
|
||||
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt};
|
||||
use rustc_session::parse::feature_err;
|
||||
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span, sym};
|
||||
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span};
|
||||
use rustc_trait_selection::infer::InferCtxtExt as _;
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
||||
use rustc_trait_selection::traits::{
|
||||
|
@ -610,8 +608,6 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
|
|||
ty::TraitRef::new(self.tcx, coerce_unsized_did, [coerce_source, coerce_target])
|
||||
)];
|
||||
|
||||
let mut has_unsized_tuple_coercion = false;
|
||||
|
||||
// Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid
|
||||
// emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where
|
||||
// inference might unify those two inner type variables later.
|
||||
|
@ -690,31 +686,10 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
|
|||
// be silent, as it causes a type mismatch later.
|
||||
}
|
||||
|
||||
Ok(Some(impl_source)) => {
|
||||
// Some builtin coercions are still unstable so we detect
|
||||
// these here and emit a feature error if coercion doesn't fail
|
||||
// due to another reason.
|
||||
match impl_source {
|
||||
traits::ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
|
||||
has_unsized_tuple_coercion = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
queue.extend(impl_source.nested_obligations())
|
||||
}
|
||||
Ok(Some(impl_source)) => queue.extend(impl_source.nested_obligations()),
|
||||
}
|
||||
}
|
||||
|
||||
if has_unsized_tuple_coercion && !self.tcx.features().unsized_tuple_coercion() {
|
||||
feature_err(
|
||||
&self.tcx.sess,
|
||||
sym::unsized_tuple_coercion,
|
||||
self.cause.span,
|
||||
"unsized tuple coercion is not stable enough for use and is subject to change",
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
|
||||
Ok(coercion)
|
||||
}
|
||||
|
||||
|
|
|
@ -373,9 +373,14 @@ pub(crate) struct LossyProvenanceInt2Ptr<'tcx> {
|
|||
pub sugg: LossyProvenanceInt2PtrSuggestion,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(hir_typeck_ptr_cast_add_auto_to_object)]
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(hir_typeck_ptr_cast_add_auto_to_object, code = E0804)]
|
||||
#[note]
|
||||
#[help]
|
||||
pub(crate) struct PtrCastAddAutoToObject {
|
||||
#[primary_span]
|
||||
#[label]
|
||||
pub span: Span,
|
||||
pub traits_len: usize,
|
||||
pub traits: DiagSymbolList<String>,
|
||||
}
|
||||
|
|
|
@ -1806,7 +1806,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
crate::GatherLocalsVisitor::new(&fcx).visit_body(body);
|
||||
|
||||
let ty = fcx.check_expr_with_expectation(body.value, expected);
|
||||
fcx.require_type_is_sized(ty, body.value.span, ObligationCauseCode::ConstSized);
|
||||
fcx.require_type_is_sized(ty, body.value.span, ObligationCauseCode::SizedConstOrStatic);
|
||||
fcx.write_ty(block.hir_id, ty);
|
||||
ty
|
||||
}
|
||||
|
|
|
@ -599,6 +599,11 @@ fn register_builtins(store: &mut LintStore) {
|
|||
"converted into hard error, \
|
||||
see <https://github.com/rust-lang/rust/issues/73333> for more information",
|
||||
);
|
||||
store.register_removed(
|
||||
"ptr_cast_add_auto_to_object",
|
||||
"converted into hard error, see issue #127323 \
|
||||
<https://github.com/rust-lang/rust/issues/127323> for more information",
|
||||
);
|
||||
}
|
||||
|
||||
fn register_internals(store: &mut LintStore) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::iter;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use rustc_abi::{BackendRepr, ExternAbi, TagEncoding, VariantIdx, Variants, WrappingRange};
|
||||
use rustc_abi::{BackendRepr, TagEncoding, VariantIdx, Variants, WrappingRange};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::DiagMessage;
|
||||
use rustc_hir::intravisit::VisitorExt;
|
||||
|
@ -1349,7 +1349,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
|||
|
||||
ty::FnPtr(sig_tys, hdr) => {
|
||||
let sig = sig_tys.with(hdr);
|
||||
if self.is_internal_abi(sig.abi()) {
|
||||
if sig.abi().is_rustic_abi() {
|
||||
return FfiUnsafe {
|
||||
ty,
|
||||
reason: fluent::lint_improper_ctypes_fnptr_reason,
|
||||
|
@ -1552,13 +1552,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
|||
self.check_type_for_ffi_and_report_errors(span, ty, true, false);
|
||||
}
|
||||
|
||||
fn is_internal_abi(&self, abi: ExternAbi) -> bool {
|
||||
matches!(
|
||||
abi,
|
||||
ExternAbi::Rust | ExternAbi::RustCall | ExternAbi::RustCold | ExternAbi::RustIntrinsic
|
||||
)
|
||||
}
|
||||
|
||||
/// Find any fn-ptr types with external ABIs in `ty`.
|
||||
///
|
||||
/// For example, `Option<extern "C" fn()>` returns `extern "C" fn()`
|
||||
|
@ -1567,17 +1560,16 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
|||
hir_ty: &hir::Ty<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
) -> Vec<(Ty<'tcx>, Span)> {
|
||||
struct FnPtrFinder<'a, 'b, 'tcx> {
|
||||
visitor: &'a ImproperCTypesVisitor<'b, 'tcx>,
|
||||
struct FnPtrFinder<'tcx> {
|
||||
spans: Vec<Span>,
|
||||
tys: Vec<Ty<'tcx>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'a, 'b, 'tcx> {
|
||||
impl<'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'tcx> {
|
||||
fn visit_ty(&mut self, ty: &'_ hir::Ty<'_, AmbigArg>) {
|
||||
debug!(?ty);
|
||||
if let hir::TyKind::BareFn(hir::BareFnTy { abi, .. }) = ty.kind
|
||||
&& !self.visitor.is_internal_abi(*abi)
|
||||
&& !abi.is_rustic_abi()
|
||||
{
|
||||
self.spans.push(ty.span);
|
||||
}
|
||||
|
@ -1586,12 +1578,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> ty::visit::TypeVisitor<TyCtxt<'tcx>> for FnPtrFinder<'a, 'b, 'tcx> {
|
||||
impl<'tcx> ty::visit::TypeVisitor<TyCtxt<'tcx>> for FnPtrFinder<'tcx> {
|
||||
type Result = ();
|
||||
|
||||
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
|
||||
if let ty::FnPtr(_, hdr) = ty.kind()
|
||||
&& !self.visitor.is_internal_abi(hdr.abi)
|
||||
&& !hdr.abi.is_rustic_abi()
|
||||
{
|
||||
self.tys.push(ty);
|
||||
}
|
||||
|
@ -1600,7 +1592,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
let mut visitor = FnPtrFinder { visitor: self, spans: Vec::new(), tys: Vec::new() };
|
||||
let mut visitor = FnPtrFinder { spans: Vec::new(), tys: Vec::new() };
|
||||
ty.visit_with(&mut visitor);
|
||||
visitor.visit_ty_unambig(hir_ty);
|
||||
|
||||
|
@ -1615,13 +1607,13 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations {
|
|||
|
||||
match it.kind {
|
||||
hir::ForeignItemKind::Fn(sig, _, _) => {
|
||||
if vis.is_internal_abi(abi) {
|
||||
if abi.is_rustic_abi() {
|
||||
vis.check_fn(it.owner_id.def_id, sig.decl)
|
||||
} else {
|
||||
vis.check_foreign_fn(it.owner_id.def_id, sig.decl);
|
||||
}
|
||||
}
|
||||
hir::ForeignItemKind::Static(ty, _, _) if !vis.is_internal_abi(abi) => {
|
||||
hir::ForeignItemKind::Static(ty, _, _) if !abi.is_rustic_abi() => {
|
||||
vis.check_foreign_static(it.owner_id, ty.span);
|
||||
}
|
||||
hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (),
|
||||
|
@ -1775,7 +1767,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions {
|
|||
};
|
||||
|
||||
let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition };
|
||||
if vis.is_internal_abi(abi) {
|
||||
if abi.is_rustic_abi() {
|
||||
vis.check_fn(id, decl);
|
||||
} else {
|
||||
vis.check_foreign_fn(id, decl);
|
||||
|
|
|
@ -80,7 +80,6 @@ declare_lint_pass! {
|
|||
PRIVATE_BOUNDS,
|
||||
PRIVATE_INTERFACES,
|
||||
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
|
||||
PTR_CAST_ADD_AUTO_TO_OBJECT,
|
||||
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
|
||||
PUB_USE_OF_PRIVATE_EXTERN_CRATE,
|
||||
REDUNDANT_IMPORTS,
|
||||
|
@ -4827,58 +4826,6 @@ declare_lint! {
|
|||
};
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// The `ptr_cast_add_auto_to_object` lint detects casts of raw pointers to trait
|
||||
/// objects, which add auto traits.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```rust,edition2021,compile_fail
|
||||
/// let ptr: *const dyn core::any::Any = &();
|
||||
/// _ = ptr as *const dyn core::any::Any + Send;
|
||||
/// ```
|
||||
///
|
||||
/// {{produces}}
|
||||
///
|
||||
/// ### Explanation
|
||||
///
|
||||
/// Adding an auto trait can make the vtable invalid, potentially causing
|
||||
/// UB in safe code afterwards. For example:
|
||||
///
|
||||
/// ```ignore (causes a warning)
|
||||
/// #![feature(arbitrary_self_types)]
|
||||
///
|
||||
/// trait Trait {
|
||||
/// fn f(self: *const Self)
|
||||
/// where
|
||||
/// Self: Send;
|
||||
/// }
|
||||
///
|
||||
/// impl Trait for *const () {
|
||||
/// fn f(self: *const Self) {
|
||||
/// unreachable!()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let unsend: *const () = &();
|
||||
/// let unsend: *const dyn Trait = &unsend;
|
||||
/// let send_bad: *const (dyn Trait + Send) = unsend as _;
|
||||
/// send_bad.f(); // this crashes, since vtable for `*const ()` does not have an entry for `f`
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Generally you must ensure that vtable is right for the pointer's type,
|
||||
/// before passing the pointer to safe code.
|
||||
pub PTR_CAST_ADD_AUTO_TO_OBJECT,
|
||||
Warn,
|
||||
"detects `as` casts from pointers to `dyn Trait` to pointers to `dyn Trait + Auto`",
|
||||
@future_incompatible = FutureIncompatibleInfo {
|
||||
reason: FutureIncompatibilityReason::FutureReleaseErrorReportInDeps,
|
||||
reference: "issue #127323 <https://github.com/rust-lang/rust/issues/127323>",
|
||||
};
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// The `out_of_scope_macro_calls` lint detects `macro_rules` called when they are not in scope,
|
||||
/// above their definition, which may happen in key-value attributes.
|
||||
|
|
|
@ -508,6 +508,14 @@ impl<'tcx, T: Clone> Key for CanonicalQueryInput<'tcx, T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'tcx, T: Clone> Key for (CanonicalQueryInput<'tcx, T>, bool) {
|
||||
type Cache<V> = DefaultCache<Self, V>;
|
||||
|
||||
fn default_span(&self, _tcx: TyCtxt<'_>) -> Span {
|
||||
DUMMY_SP
|
||||
}
|
||||
}
|
||||
|
||||
impl Key for (Symbol, u32, u32) {
|
||||
type Cache<V> = DefaultCache<Self, V>;
|
||||
|
||||
|
|
|
@ -2262,22 +2262,13 @@ rustc_queries! {
|
|||
desc { "normalizing `{}`", goal.value }
|
||||
}
|
||||
|
||||
query implied_outlives_bounds_compat(
|
||||
goal: CanonicalImpliedOutlivesBoundsGoal<'tcx>
|
||||
) -> Result<
|
||||
&'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec<OutlivesBound<'tcx>>>>,
|
||||
NoSolution,
|
||||
> {
|
||||
desc { "computing implied outlives bounds for `{}`", goal.canonical.value.value.ty }
|
||||
}
|
||||
|
||||
query implied_outlives_bounds(
|
||||
goal: CanonicalImpliedOutlivesBoundsGoal<'tcx>
|
||||
key: (CanonicalImpliedOutlivesBoundsGoal<'tcx>, bool)
|
||||
) -> Result<
|
||||
&'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec<OutlivesBound<'tcx>>>>,
|
||||
NoSolution,
|
||||
> {
|
||||
desc { "computing implied outlives bounds v2 for `{}`", goal.canonical.value.value.ty }
|
||||
desc { "computing implied outlives bounds for `{}` (hack disabled = {:?})", key.0.canonical.value.value.ty, key.1 }
|
||||
}
|
||||
|
||||
/// Do not call this query directly:
|
||||
|
|
|
@ -266,7 +266,7 @@ pub enum ObligationCauseCode<'tcx> {
|
|||
},
|
||||
|
||||
/// Constant expressions must be sized.
|
||||
ConstSized,
|
||||
SizedConstOrStatic,
|
||||
|
||||
/// `static` items must have `Sync` type.
|
||||
SharedStatic,
|
||||
|
|
|
@ -6,27 +6,25 @@ use rustc_middle::ty::{self, Ty, TypeVisitableExt};
|
|||
|
||||
use crate::builder::Builder;
|
||||
use crate::builder::expr::as_place::{PlaceBase, PlaceBuilder};
|
||||
use crate::builder::matches::{FlatPat, MatchPairTree, TestCase};
|
||||
use crate::builder::matches::{FlatPat, MatchPairTree, PatternExtraData, TestCase};
|
||||
|
||||
impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
/// Builds and returns [`MatchPairTree`] subtrees, one for each pattern in
|
||||
/// Builds and pushes [`MatchPairTree`] subtrees, one for each pattern in
|
||||
/// `subpatterns`, representing the fields of a [`PatKind::Variant`] or
|
||||
/// [`PatKind::Leaf`].
|
||||
///
|
||||
/// Used internally by [`MatchPairTree::for_pattern`].
|
||||
fn field_match_pairs(
|
||||
&mut self,
|
||||
match_pairs: &mut Vec<MatchPairTree<'tcx>>,
|
||||
extra_data: &mut PatternExtraData<'tcx>,
|
||||
place: PlaceBuilder<'tcx>,
|
||||
subpatterns: &[FieldPat<'tcx>],
|
||||
) -> Vec<MatchPairTree<'tcx>> {
|
||||
subpatterns
|
||||
.iter()
|
||||
.map(|fieldpat| {
|
||||
let place =
|
||||
place.clone_project(PlaceElem::Field(fieldpat.field, fieldpat.pattern.ty));
|
||||
MatchPairTree::for_pattern(place, &fieldpat.pattern, self)
|
||||
})
|
||||
.collect()
|
||||
) {
|
||||
for fieldpat in subpatterns {
|
||||
let place = place.clone_project(PlaceElem::Field(fieldpat.field, fieldpat.pattern.ty));
|
||||
MatchPairTree::for_pattern(place, &fieldpat.pattern, self, match_pairs, extra_data);
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds [`MatchPairTree`] subtrees for the prefix/middle/suffix parts of an
|
||||
|
@ -36,6 +34,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
fn prefix_slice_suffix(
|
||||
&mut self,
|
||||
match_pairs: &mut Vec<MatchPairTree<'tcx>>,
|
||||
extra_data: &mut PatternExtraData<'tcx>,
|
||||
place: &PlaceBuilder<'tcx>,
|
||||
prefix: &[Pat<'tcx>],
|
||||
opt_slice: &Option<Box<Pat<'tcx>>>,
|
||||
|
@ -56,11 +55,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
((prefix.len() + suffix.len()).try_into().unwrap(), false)
|
||||
};
|
||||
|
||||
match_pairs.extend(prefix.iter().enumerate().map(|(idx, subpattern)| {
|
||||
for (idx, subpattern) in prefix.iter().enumerate() {
|
||||
let elem =
|
||||
ProjectionElem::ConstantIndex { offset: idx as u64, min_length, from_end: false };
|
||||
MatchPairTree::for_pattern(place.clone_project(elem), subpattern, self)
|
||||
}));
|
||||
let place = place.clone_project(elem);
|
||||
MatchPairTree::for_pattern(place, subpattern, self, match_pairs, extra_data)
|
||||
}
|
||||
|
||||
if let Some(subslice_pat) = opt_slice {
|
||||
let suffix_len = suffix.len() as u64;
|
||||
|
@ -69,10 +69,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
to: if exact_size { min_length - suffix_len } else { suffix_len },
|
||||
from_end: !exact_size,
|
||||
});
|
||||
match_pairs.push(MatchPairTree::for_pattern(subslice, subslice_pat, self));
|
||||
MatchPairTree::for_pattern(subslice, subslice_pat, self, match_pairs, extra_data);
|
||||
}
|
||||
|
||||
match_pairs.extend(suffix.iter().rev().enumerate().map(|(idx, subpattern)| {
|
||||
for (idx, subpattern) in suffix.iter().rev().enumerate() {
|
||||
let end_offset = (idx + 1) as u64;
|
||||
let elem = ProjectionElem::ConstantIndex {
|
||||
offset: if exact_size { min_length - end_offset } else { end_offset },
|
||||
|
@ -80,19 +80,21 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
from_end: !exact_size,
|
||||
};
|
||||
let place = place.clone_project(elem);
|
||||
MatchPairTree::for_pattern(place, subpattern, self)
|
||||
}));
|
||||
MatchPairTree::for_pattern(place, subpattern, self, match_pairs, extra_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> MatchPairTree<'tcx> {
|
||||
/// Recursively builds a match pair tree for the given pattern and its
|
||||
/// subpatterns.
|
||||
pub(in crate::builder) fn for_pattern(
|
||||
pub(super) fn for_pattern(
|
||||
mut place_builder: PlaceBuilder<'tcx>,
|
||||
pattern: &Pat<'tcx>,
|
||||
cx: &mut Builder<'_, 'tcx>,
|
||||
) -> MatchPairTree<'tcx> {
|
||||
match_pairs: &mut Vec<Self>, // Newly-created nodes are added to this vector
|
||||
extra_data: &mut PatternExtraData<'tcx>, // Bindings/ascriptions are added here
|
||||
) {
|
||||
// Force the place type to the pattern's type.
|
||||
// FIXME(oli-obk): can we use this to simplify slice/array pattern hacks?
|
||||
if let Some(resolved) = place_builder.resolve_upvar(cx) {
|
||||
|
@ -113,64 +115,102 @@ impl<'tcx> MatchPairTree<'tcx> {
|
|||
place_builder = place_builder.project(ProjectionElem::OpaqueCast(pattern.ty));
|
||||
}
|
||||
|
||||
// Place can be none if the pattern refers to a non-captured place in a closure.
|
||||
let place = place_builder.try_to_place(cx);
|
||||
let default_irrefutable = || TestCase::Irrefutable { binding: None, ascription: None };
|
||||
let mut subpairs = Vec::new();
|
||||
let test_case = match pattern.kind {
|
||||
PatKind::Wild | PatKind::Error(_) => default_irrefutable(),
|
||||
PatKind::Wild | PatKind::Error(_) => None,
|
||||
|
||||
PatKind::Or { ref pats } => TestCase::Or {
|
||||
PatKind::Or { ref pats } => Some(TestCase::Or {
|
||||
pats: pats.iter().map(|pat| FlatPat::new(place_builder.clone(), pat, cx)).collect(),
|
||||
},
|
||||
}),
|
||||
|
||||
PatKind::Range(ref range) => {
|
||||
if range.is_full_range(cx.tcx) == Some(true) {
|
||||
default_irrefutable()
|
||||
None
|
||||
} else {
|
||||
TestCase::Range(Arc::clone(range))
|
||||
Some(TestCase::Range(Arc::clone(range)))
|
||||
}
|
||||
}
|
||||
|
||||
PatKind::Constant { value } => TestCase::Constant { value },
|
||||
PatKind::Constant { value } => Some(TestCase::Constant { value }),
|
||||
|
||||
PatKind::AscribeUserType {
|
||||
ascription: Ascription { ref annotation, variance },
|
||||
ref subpattern,
|
||||
..
|
||||
} => {
|
||||
// Apply the type ascription to the value at `match_pair.place`
|
||||
let ascription = place.map(|source| super::Ascription {
|
||||
annotation: annotation.clone(),
|
||||
source,
|
||||
variance,
|
||||
});
|
||||
MatchPairTree::for_pattern(
|
||||
place_builder,
|
||||
subpattern,
|
||||
cx,
|
||||
&mut subpairs,
|
||||
extra_data,
|
||||
);
|
||||
|
||||
subpairs.push(MatchPairTree::for_pattern(place_builder, subpattern, cx));
|
||||
TestCase::Irrefutable { ascription, binding: None }
|
||||
// Apply the type ascription to the value at `match_pair.place`
|
||||
if let Some(source) = place {
|
||||
let annotation = annotation.clone();
|
||||
extra_data.ascriptions.push(super::Ascription { source, annotation, variance });
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
PatKind::Binding { mode, var, ref subpattern, .. } => {
|
||||
let binding = place.map(|source| super::Binding {
|
||||
span: pattern.span,
|
||||
source,
|
||||
var_id: var,
|
||||
binding_mode: mode,
|
||||
});
|
||||
// In order to please the borrow checker, when lowering a pattern
|
||||
// like `x @ subpat` we must establish any bindings in `subpat`
|
||||
// before establishing the binding for `x`.
|
||||
//
|
||||
// For example (from #69971):
|
||||
//
|
||||
// ```ignore (illustrative)
|
||||
// struct NonCopyStruct {
|
||||
// copy_field: u32,
|
||||
// }
|
||||
//
|
||||
// fn foo1(x: NonCopyStruct) {
|
||||
// let y @ NonCopyStruct { copy_field: z } = x;
|
||||
// // the above should turn into
|
||||
// let z = x.copy_field;
|
||||
// let y = x;
|
||||
// }
|
||||
// ```
|
||||
|
||||
// First, recurse into the subpattern, if any.
|
||||
if let Some(subpattern) = subpattern.as_ref() {
|
||||
// this is the `x @ P` case; have to keep matching against `P` now
|
||||
subpairs.push(MatchPairTree::for_pattern(place_builder, subpattern, cx));
|
||||
MatchPairTree::for_pattern(
|
||||
place_builder,
|
||||
subpattern,
|
||||
cx,
|
||||
&mut subpairs,
|
||||
extra_data,
|
||||
);
|
||||
}
|
||||
TestCase::Irrefutable { ascription: None, binding }
|
||||
|
||||
// Then push this binding, after any bindings in the subpattern.
|
||||
if let Some(source) = place {
|
||||
extra_data.bindings.push(super::Binding {
|
||||
span: pattern.span,
|
||||
source,
|
||||
var_id: var,
|
||||
binding_mode: mode,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
PatKind::ExpandedConstant { subpattern: ref pattern, def_id: _, is_inline: false } => {
|
||||
subpairs.push(MatchPairTree::for_pattern(place_builder, pattern, cx));
|
||||
default_irrefutable()
|
||||
MatchPairTree::for_pattern(place_builder, pattern, cx, &mut subpairs, extra_data);
|
||||
None
|
||||
}
|
||||
PatKind::ExpandedConstant { subpattern: ref pattern, def_id, is_inline: true } => {
|
||||
MatchPairTree::for_pattern(place_builder, pattern, cx, &mut subpairs, extra_data);
|
||||
|
||||
// Apply a type ascription for the inline constant to the value at `match_pair.place`
|
||||
let ascription = place.map(|source| {
|
||||
if let Some(source) = place {
|
||||
let span = pattern.span;
|
||||
let parent_id = cx.tcx.typeck_root_def_id(cx.def_id.to_def_id());
|
||||
let args = ty::InlineConstArgs::new(
|
||||
|
@ -189,33 +229,47 @@ impl<'tcx> MatchPairTree<'tcx> {
|
|||
span,
|
||||
user_ty: Box::new(user_ty),
|
||||
};
|
||||
super::Ascription { annotation, source, variance: ty::Contravariant }
|
||||
});
|
||||
let variance = ty::Contravariant;
|
||||
extra_data.ascriptions.push(super::Ascription { annotation, source, variance });
|
||||
}
|
||||
|
||||
subpairs.push(MatchPairTree::for_pattern(place_builder, pattern, cx));
|
||||
TestCase::Irrefutable { ascription, binding: None }
|
||||
None
|
||||
}
|
||||
|
||||
PatKind::Array { ref prefix, ref slice, ref suffix } => {
|
||||
cx.prefix_slice_suffix(&mut subpairs, &place_builder, prefix, slice, suffix);
|
||||
default_irrefutable()
|
||||
cx.prefix_slice_suffix(
|
||||
&mut subpairs,
|
||||
extra_data,
|
||||
&place_builder,
|
||||
prefix,
|
||||
slice,
|
||||
suffix,
|
||||
);
|
||||
None
|
||||
}
|
||||
PatKind::Slice { ref prefix, ref slice, ref suffix } => {
|
||||
cx.prefix_slice_suffix(&mut subpairs, &place_builder, prefix, slice, suffix);
|
||||
cx.prefix_slice_suffix(
|
||||
&mut subpairs,
|
||||
extra_data,
|
||||
&place_builder,
|
||||
prefix,
|
||||
slice,
|
||||
suffix,
|
||||
);
|
||||
|
||||
if prefix.is_empty() && slice.is_some() && suffix.is_empty() {
|
||||
default_irrefutable()
|
||||
None
|
||||
} else {
|
||||
TestCase::Slice {
|
||||
Some(TestCase::Slice {
|
||||
len: prefix.len() + suffix.len(),
|
||||
variable_length: slice.is_some(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
PatKind::Variant { adt_def, variant_index, args, ref subpatterns } => {
|
||||
let downcast_place = place_builder.downcast(adt_def, variant_index); // `(x as Variant)`
|
||||
subpairs = cx.field_match_pairs(downcast_place, subpatterns);
|
||||
cx.field_match_pairs(&mut subpairs, extra_data, downcast_place, subpatterns);
|
||||
|
||||
let irrefutable = adt_def.variants().iter_enumerated().all(|(i, v)| {
|
||||
i == variant_index
|
||||
|
@ -225,21 +279,23 @@ impl<'tcx> MatchPairTree<'tcx> {
|
|||
.apply_ignore_module(cx.tcx, cx.infcx.typing_env(cx.param_env))
|
||||
}) && (adt_def.did().is_local()
|
||||
|| !adt_def.is_variant_list_non_exhaustive());
|
||||
if irrefutable {
|
||||
default_irrefutable()
|
||||
} else {
|
||||
TestCase::Variant { adt_def, variant_index }
|
||||
}
|
||||
if irrefutable { None } else { Some(TestCase::Variant { adt_def, variant_index }) }
|
||||
}
|
||||
|
||||
PatKind::Leaf { ref subpatterns } => {
|
||||
subpairs = cx.field_match_pairs(place_builder, subpatterns);
|
||||
default_irrefutable()
|
||||
cx.field_match_pairs(&mut subpairs, extra_data, place_builder, subpatterns);
|
||||
None
|
||||
}
|
||||
|
||||
PatKind::Deref { ref subpattern } => {
|
||||
subpairs.push(MatchPairTree::for_pattern(place_builder.deref(), subpattern, cx));
|
||||
default_irrefutable()
|
||||
MatchPairTree::for_pattern(
|
||||
place_builder.deref(),
|
||||
subpattern,
|
||||
cx,
|
||||
&mut subpairs,
|
||||
extra_data,
|
||||
);
|
||||
None
|
||||
}
|
||||
|
||||
PatKind::DerefPattern { ref subpattern, mutability } => {
|
||||
|
@ -249,23 +305,32 @@ impl<'tcx> MatchPairTree<'tcx> {
|
|||
Ty::new_ref(cx.tcx, cx.tcx.lifetimes.re_erased, subpattern.ty, mutability),
|
||||
pattern.span,
|
||||
);
|
||||
subpairs.push(MatchPairTree::for_pattern(
|
||||
MatchPairTree::for_pattern(
|
||||
PlaceBuilder::from(temp).deref(),
|
||||
subpattern,
|
||||
cx,
|
||||
));
|
||||
TestCase::Deref { temp, mutability }
|
||||
&mut subpairs,
|
||||
extra_data,
|
||||
);
|
||||
Some(TestCase::Deref { temp, mutability })
|
||||
}
|
||||
|
||||
PatKind::Never => TestCase::Never,
|
||||
PatKind::Never => Some(TestCase::Never),
|
||||
};
|
||||
|
||||
MatchPairTree {
|
||||
place,
|
||||
test_case,
|
||||
subpairs,
|
||||
pattern_ty: pattern.ty,
|
||||
pattern_span: pattern.span,
|
||||
if let Some(test_case) = test_case {
|
||||
// This pattern is refutable, so push a new match-pair node.
|
||||
match_pairs.push(MatchPairTree {
|
||||
place: place.expect("refutable patterns should always have a place to inspect"),
|
||||
test_case,
|
||||
subpairs,
|
||||
pattern_ty: pattern.ty,
|
||||
pattern_span: pattern.span,
|
||||
})
|
||||
} else {
|
||||
// This pattern is irrefutable, so it doesn't need its own match-pair node.
|
||||
// Just push its refutable subpatterns instead, if any.
|
||||
match_pairs.extend(subpairs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ use crate::builder::{
|
|||
|
||||
// helper functions, broken out by category:
|
||||
mod match_pair;
|
||||
mod simplify;
|
||||
mod test;
|
||||
mod util;
|
||||
|
||||
|
@ -987,18 +986,16 @@ impl<'tcx> PatternExtraData<'tcx> {
|
|||
}
|
||||
|
||||
/// A pattern in a form suitable for lowering the match tree, with all irrefutable
|
||||
/// patterns simplified away, and or-patterns sorted to the end.
|
||||
/// patterns simplified away.
|
||||
///
|
||||
/// Here, "flat" indicates that the pattern's match pairs have been recursively
|
||||
/// simplified by [`Builder::simplify_match_pairs`]. They are not necessarily
|
||||
/// flat in an absolute sense.
|
||||
/// Here, "flat" indicates that irrefutable nodes in the pattern tree have been
|
||||
/// recursively replaced with their refutable subpatterns. They are not
|
||||
/// necessarily flat in an absolute sense.
|
||||
///
|
||||
/// Will typically be incorporated into a [`Candidate`].
|
||||
#[derive(Debug, Clone)]
|
||||
struct FlatPat<'tcx> {
|
||||
/// To match the pattern, all of these must be satisfied...
|
||||
// Invariant: all the match pairs are recursively simplified.
|
||||
// Invariant: or-patterns must be sorted to the end.
|
||||
match_pairs: Vec<MatchPairTree<'tcx>>,
|
||||
|
||||
extra_data: PatternExtraData<'tcx>,
|
||||
|
@ -1008,17 +1005,15 @@ impl<'tcx> FlatPat<'tcx> {
|
|||
/// Creates a `FlatPat` containing a simplified [`MatchPairTree`] list/forest
|
||||
/// for the given pattern.
|
||||
fn new(place: PlaceBuilder<'tcx>, pattern: &Pat<'tcx>, cx: &mut Builder<'_, 'tcx>) -> Self {
|
||||
// First, recursively build a tree of match pairs for the given pattern.
|
||||
let mut match_pairs = vec![MatchPairTree::for_pattern(place, pattern, cx)];
|
||||
// Recursively build a tree of match pairs for the given pattern.
|
||||
let mut match_pairs = vec![];
|
||||
let mut extra_data = PatternExtraData {
|
||||
span: pattern.span,
|
||||
bindings: Vec::new(),
|
||||
ascriptions: Vec::new(),
|
||||
is_never: pattern.is_never_pattern(),
|
||||
};
|
||||
// Recursively remove irrefutable match pairs, while recording their
|
||||
// bindings/ascriptions, and sort or-patterns after other match pairs.
|
||||
cx.simplify_match_pairs(&mut match_pairs, &mut extra_data);
|
||||
MatchPairTree::for_pattern(place, pattern, cx, &mut match_pairs, &mut extra_data);
|
||||
|
||||
Self { match_pairs, extra_data }
|
||||
}
|
||||
|
@ -1055,7 +1050,6 @@ struct Candidate<'tcx> {
|
|||
/// (see [`Builder::test_remaining_match_pairs_after_or`]).
|
||||
///
|
||||
/// Invariants:
|
||||
/// - All [`TestCase::Irrefutable`] patterns have been removed by simplification.
|
||||
/// - All or-patterns ([`TestCase::Or`]) have been sorted to the end.
|
||||
match_pairs: Vec<MatchPairTree<'tcx>>,
|
||||
|
||||
|
@ -1126,7 +1120,7 @@ impl<'tcx> Candidate<'tcx> {
|
|||
|
||||
/// Incorporates an already-simplified [`FlatPat`] into a new candidate.
|
||||
fn from_flat_pat(flat_pat: FlatPat<'tcx>, has_guard: bool) -> Self {
|
||||
Candidate {
|
||||
let mut this = Candidate {
|
||||
match_pairs: flat_pat.match_pairs,
|
||||
extra_data: flat_pat.extra_data,
|
||||
has_guard,
|
||||
|
@ -1135,7 +1129,14 @@ impl<'tcx> Candidate<'tcx> {
|
|||
otherwise_block: None,
|
||||
pre_binding_block: None,
|
||||
false_edge_start_block: None,
|
||||
}
|
||||
};
|
||||
this.sort_match_pairs();
|
||||
this
|
||||
}
|
||||
|
||||
/// Restores the invariant that or-patterns must be sorted to the end.
|
||||
fn sort_match_pairs(&mut self) {
|
||||
self.match_pairs.sort_by_key(|pair| matches!(pair.test_case, TestCase::Or { .. }));
|
||||
}
|
||||
|
||||
/// Returns whether the first match pair of this candidate is an or-pattern.
|
||||
|
@ -1227,17 +1228,11 @@ struct Ascription<'tcx> {
|
|||
/// - [`Builder::pick_test_for_match_pair`] (to choose a test)
|
||||
/// - [`Builder::sort_candidate`] (to see how the test interacts with a match pair)
|
||||
///
|
||||
/// Two variants are unlike the others and deserve special mention:
|
||||
///
|
||||
/// - [`Self::Irrefutable`] is only used temporarily when building a [`MatchPairTree`].
|
||||
/// They are then flattened away by [`Builder::simplify_match_pairs`], with any
|
||||
/// bindings/ascriptions incorporated into the enclosing [`FlatPat`].
|
||||
/// - [`Self::Or`] are not tested directly like the other variants. Instead they
|
||||
/// participate in or-pattern expansion, where they are transformed into subcandidates.
|
||||
/// - See [`Builder::expand_and_match_or_candidates`].
|
||||
/// Note that or-patterns are not tested directly like the other variants.
|
||||
/// Instead they participate in or-pattern expansion, where they are transformed into
|
||||
/// subcandidates. See [`Builder::expand_and_match_or_candidates`].
|
||||
#[derive(Debug, Clone)]
|
||||
enum TestCase<'tcx> {
|
||||
Irrefutable { binding: Option<Binding<'tcx>>, ascription: Option<Ascription<'tcx>> },
|
||||
Variant { adt_def: ty::AdtDef<'tcx>, variant_index: VariantIdx },
|
||||
Constant { value: mir::Const<'tcx> },
|
||||
Range(Arc<PatRange<'tcx>>),
|
||||
|
@ -1261,19 +1256,9 @@ impl<'tcx> TestCase<'tcx> {
|
|||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct MatchPairTree<'tcx> {
|
||||
/// This place...
|
||||
///
|
||||
/// ---
|
||||
/// This can be `None` if it referred to a non-captured place in a closure.
|
||||
///
|
||||
/// Invariant: Can only be `None` when `test_case` is `Irrefutable`.
|
||||
/// Therefore this must be `Some(_)` after simplification.
|
||||
place: Option<Place<'tcx>>,
|
||||
place: Place<'tcx>,
|
||||
|
||||
/// ... must pass this test...
|
||||
///
|
||||
/// ---
|
||||
/// Invariant: after creation and simplification in [`FlatPat::new`],
|
||||
/// this must not be [`TestCase::Irrefutable`].
|
||||
test_case: TestCase<'tcx>,
|
||||
|
||||
/// ... and these subpairs must match.
|
||||
|
@ -2091,11 +2076,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
// Extract the match-pair from the highest priority candidate
|
||||
let match_pair = &candidates[0].match_pairs[0];
|
||||
let test = self.pick_test_for_match_pair(match_pair);
|
||||
// Unwrap is ok after simplification.
|
||||
let match_place = match_pair.place.unwrap();
|
||||
debug!(?test, ?match_pair);
|
||||
|
||||
(match_place, test)
|
||||
(match_pair.place, test)
|
||||
}
|
||||
|
||||
/// Given a test, we partition the input candidates into several buckets.
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
//! Simplifying Candidates
|
||||
//!
|
||||
//! *Simplifying* a match pair `place @ pattern` means breaking it down
|
||||
//! into bindings or other, simpler match pairs. For example:
|
||||
//!
|
||||
//! - `place @ (P1, P2)` can be simplified to `[place.0 @ P1, place.1 @ P2]`
|
||||
//! - `place @ x` can be simplified to `[]` by binding `x` to `place`
|
||||
//!
|
||||
//! The `simplify_match_pairs` routine just repeatedly applies these
|
||||
//! sort of simplifications until there is nothing left to
|
||||
//! simplify. Match pairs cannot be simplified if they require some
|
||||
//! sort of test: for example, testing which variant an enum is, or
|
||||
//! testing a value against a constant.
|
||||
|
||||
use std::mem;
|
||||
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use crate::builder::Builder;
|
||||
use crate::builder::matches::{MatchPairTree, PatternExtraData, TestCase};
|
||||
|
||||
impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
/// Simplify a list of match pairs so they all require a test. Stores relevant bindings and
|
||||
/// ascriptions in `extra_data`.
|
||||
#[instrument(skip(self), level = "debug")]
|
||||
pub(super) fn simplify_match_pairs(
|
||||
&mut self,
|
||||
match_pairs: &mut Vec<MatchPairTree<'tcx>>,
|
||||
extra_data: &mut PatternExtraData<'tcx>,
|
||||
) {
|
||||
// In order to please the borrow checker, in a pattern like `x @ pat` we must lower the
|
||||
// bindings in `pat` before `x`. E.g. (#69971):
|
||||
//
|
||||
// struct NonCopyStruct {
|
||||
// copy_field: u32,
|
||||
// }
|
||||
//
|
||||
// fn foo1(x: NonCopyStruct) {
|
||||
// let y @ NonCopyStruct { copy_field: z } = x;
|
||||
// // the above should turn into
|
||||
// let z = x.copy_field;
|
||||
// let y = x;
|
||||
// }
|
||||
//
|
||||
// We therefore lower bindings from left-to-right, except we lower the `x` in `x @ pat`
|
||||
// after any bindings in `pat`. This doesn't work for or-patterns: the current structure of
|
||||
// match lowering forces us to lower bindings inside or-patterns last.
|
||||
for mut match_pair in mem::take(match_pairs) {
|
||||
self.simplify_match_pairs(&mut match_pair.subpairs, extra_data);
|
||||
if let TestCase::Irrefutable { binding, ascription } = match_pair.test_case {
|
||||
if let Some(binding) = binding {
|
||||
extra_data.bindings.push(binding);
|
||||
}
|
||||
if let Some(ascription) = ascription {
|
||||
extra_data.ascriptions.push(ascription);
|
||||
}
|
||||
// Simplifiable pattern; we replace it with its already simplified subpairs.
|
||||
match_pairs.append(&mut match_pair.subpairs);
|
||||
} else {
|
||||
// Unsimplifiable pattern; we keep it.
|
||||
match_pairs.push(match_pair);
|
||||
}
|
||||
}
|
||||
|
||||
// Move or-patterns to the end, because they can result in us
|
||||
// creating additional candidates, so we want to test them as
|
||||
// late as possible.
|
||||
match_pairs.sort_by_key(|pair| matches!(pair.test_case, TestCase::Or { .. }));
|
||||
debug!(simplified = ?match_pairs, "simplify_match_pairs");
|
||||
}
|
||||
}
|
|
@ -55,12 +55,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
// Or-patterns are not tested directly; instead they are expanded into subcandidates,
|
||||
// which are then distinguished by testing whatever non-or patterns they contain.
|
||||
TestCase::Or { .. } => bug!("or-patterns should have already been handled"),
|
||||
|
||||
TestCase::Irrefutable { .. } => span_bug!(
|
||||
match_pair.pattern_span,
|
||||
"simplifiable pattern found: {:?}",
|
||||
match_pair.pattern_span
|
||||
),
|
||||
};
|
||||
|
||||
Test { span: match_pair.pattern_span, kind }
|
||||
|
@ -546,11 +540,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
// than one, but it'd be very unusual to have two sides that
|
||||
// both require tests; you'd expect one side to be simplified
|
||||
// away.)
|
||||
let (match_pair_index, match_pair) = candidate
|
||||
.match_pairs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(_, mp)| mp.place == Some(test_place))?;
|
||||
let (match_pair_index, match_pair) =
|
||||
candidate.match_pairs.iter().enumerate().find(|&(_, mp)| mp.place == test_place)?;
|
||||
|
||||
// If true, the match pair is completely entailed by its corresponding test
|
||||
// branch, so it can be removed. If false, the match pair is _compatible_
|
||||
|
@ -593,7 +584,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
candidate
|
||||
.match_pairs
|
||||
.iter()
|
||||
.any(|mp| mp.place == Some(test_place) && is_covering_range(&mp.test_case))
|
||||
.any(|mp| mp.place == test_place && is_covering_range(&mp.test_case))
|
||||
};
|
||||
if sorted_candidates
|
||||
.get(&TestBranch::Failure)
|
||||
|
@ -769,7 +760,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
|||
let match_pair = candidate.match_pairs.remove(match_pair_index);
|
||||
candidate.match_pairs.extend(match_pair.subpairs);
|
||||
// Move or-patterns to the end.
|
||||
candidate.match_pairs.sort_by_key(|pair| matches!(pair.test_case, TestCase::Or { .. }));
|
||||
candidate.sort_match_pairs();
|
||||
}
|
||||
|
||||
ret
|
||||
|
|
|
@ -173,14 +173,10 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> {
|
|||
// }
|
||||
// ```
|
||||
// Hence we fake borrow using a deep borrow.
|
||||
if let Some(place) = match_pair.place {
|
||||
self.fake_borrow(place, FakeBorrowKind::Deep);
|
||||
}
|
||||
self.fake_borrow(match_pair.place, FakeBorrowKind::Deep);
|
||||
} else {
|
||||
// Insert a Shallow borrow of any place that is switched on.
|
||||
if let Some(place) = match_pair.place {
|
||||
self.fake_borrow(place, FakeBorrowKind::Shallow);
|
||||
}
|
||||
self.fake_borrow(match_pair.place, FakeBorrowKind::Shallow);
|
||||
|
||||
for subpair in &match_pair.subpairs {
|
||||
self.visit_match_pair(subpair);
|
||||
|
|
|
@ -53,11 +53,7 @@ fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool {
|
|||
|
||||
// Rust calls cannot themselves create foreign unwinds.
|
||||
// We assume this is true for intrinsics as well.
|
||||
if let ExternAbi::RustIntrinsic
|
||||
| ExternAbi::Rust
|
||||
| ExternAbi::RustCall
|
||||
| ExternAbi::RustCold = sig.abi()
|
||||
{
|
||||
if sig.abi().is_rustic_abi() {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
|
|
@ -513,6 +513,24 @@ fn mir_drops_elaborated_and_const_checked(tcx: TyCtxt<'_>, def: LocalDefId) -> &
|
|||
body.tainted_by_errors = Some(error_reported);
|
||||
}
|
||||
|
||||
// Also taint the body if it's within a top-level item that is not well formed.
|
||||
//
|
||||
// We do this check here and not during `mir_promoted` because that may result
|
||||
// in borrowck cycles if WF requires looking into an opaque hidden type.
|
||||
let root = tcx.typeck_root_def_id(def.to_def_id());
|
||||
match tcx.def_kind(root) {
|
||||
DefKind::Fn
|
||||
| DefKind::AssocFn
|
||||
| DefKind::Static { .. }
|
||||
| DefKind::Const
|
||||
| DefKind::AssocConst => {
|
||||
if let Err(guar) = tcx.check_well_formed(root.expect_local()) {
|
||||
body.tainted_by_errors = Some(guar);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
run_analysis_to_runtime_passes(tcx, &mut body);
|
||||
|
||||
tcx.alloc_steal_mir(body)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! This module ensures that if a function's ABI requires a particular target feature,
|
||||
//! that target feature is enabled both on the callee and all callers.
|
||||
use rustc_abi::{BackendRepr, ExternAbi, RegKind};
|
||||
use rustc_abi::{BackendRepr, RegKind};
|
||||
use rustc_hir::CRATE_HIR_ID;
|
||||
use rustc_middle::mir::{self, traversal};
|
||||
use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt};
|
||||
|
@ -115,8 +115,8 @@ fn check_call_site_abi<'tcx>(
|
|||
span: Span,
|
||||
caller: InstanceKind<'tcx>,
|
||||
) {
|
||||
if callee.fn_sig(tcx).abi() == ExternAbi::Rust {
|
||||
// "Rust" ABI never passes arguments in vector registers.
|
||||
if callee.fn_sig(tcx).abi().is_rustic_abi() {
|
||||
// we directly handle the soundness of Rust ABIs
|
||||
return;
|
||||
}
|
||||
let typing_env = ty::TypingEnv::fully_monomorphized();
|
||||
|
|
|
@ -786,13 +786,6 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
// `(A, B, T)` -> `(A, B, U)` where `T: Unsize<U>`
|
||||
(ty::Tuple(a_tys), ty::Tuple(b_tys))
|
||||
if a_tys.len() == b_tys.len() && !a_tys.is_empty() =>
|
||||
{
|
||||
result_to_single(ecx.consider_builtin_tuple_unsize(goal, a_tys, b_tys))
|
||||
}
|
||||
|
||||
_ => vec![],
|
||||
}
|
||||
})
|
||||
|
@ -1084,48 +1077,6 @@ where
|
|||
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
|
||||
}
|
||||
|
||||
/// We generate the following builtin impl for tuples of all sizes.
|
||||
///
|
||||
/// This impl is still unstable and we emit a feature error when it
|
||||
/// when it is used by a coercion.
|
||||
/// ```ignore (builtin impl example)
|
||||
/// impl<T: ?Sized, U: ?Sized, V: ?Sized> Unsize<(T, V)> for (T, U)
|
||||
/// where
|
||||
/// U: Unsize<V>,
|
||||
/// {}
|
||||
/// ```
|
||||
fn consider_builtin_tuple_unsize(
|
||||
&mut self,
|
||||
goal: Goal<I, (I::Ty, I::Ty)>,
|
||||
a_tys: I::Tys,
|
||||
b_tys: I::Tys,
|
||||
) -> Result<Candidate<I>, NoSolution> {
|
||||
let cx = self.cx();
|
||||
let Goal { predicate: (_a_ty, b_ty), .. } = goal;
|
||||
|
||||
let (&a_last_ty, a_rest_tys) = a_tys.split_last().unwrap();
|
||||
let b_last_ty = b_tys.last().unwrap();
|
||||
|
||||
// Instantiate just the tail field of B., and require that they're equal.
|
||||
let unsized_a_ty = Ty::new_tup_from_iter(cx, a_rest_tys.iter().copied().chain([b_last_ty]));
|
||||
self.eq(goal.param_env, unsized_a_ty, b_ty)?;
|
||||
|
||||
// Similar to ADTs, require that we can unsize the tail.
|
||||
self.add_goal(
|
||||
GoalSource::ImplWhereBound,
|
||||
goal.with(
|
||||
cx,
|
||||
ty::TraitRef::new(
|
||||
cx,
|
||||
cx.require_lang_item(TraitSolverLangItem::Unsize),
|
||||
[a_last_ty, b_last_ty],
|
||||
),
|
||||
),
|
||||
);
|
||||
self.probe_builtin_trait_candidate(BuiltinImplSource::TupleUnsizing)
|
||||
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
|
||||
}
|
||||
|
||||
// Return `Some` if there is an impl (built-in or user provided) that may
|
||||
// hold for the self type of the goal, which for coherence and soundness
|
||||
// purposes must disqualify the built-in auto impl assembled by considering
|
||||
|
|
|
@ -477,8 +477,8 @@ fn remove_cycle(
|
|||
/// Detects query cycles by using depth first search over all active query jobs.
|
||||
/// If a query cycle is found it will break the cycle by finding an edge which
|
||||
/// uses a query latch and then resuming that waiter.
|
||||
/// There may be multiple cycles involved in a deadlock, so this searches
|
||||
/// all active queries for cycles before finally resuming all the waiters at once.
|
||||
/// There may be multiple cycles involved in a deadlock, but we only search
|
||||
/// one cycle at a call and resume one waiter at once. See `FIXME` below.
|
||||
pub fn break_query_cycles(query_map: QueryMap, registry: &rayon_core::Registry) {
|
||||
let mut wakelist = Vec::new();
|
||||
let mut jobs: Vec<QueryJobId> = query_map.keys().cloned().collect();
|
||||
|
@ -488,6 +488,19 @@ pub fn break_query_cycles(query_map: QueryMap, registry: &rayon_core::Registry)
|
|||
while jobs.len() > 0 {
|
||||
if remove_cycle(&query_map, &mut jobs, &mut wakelist) {
|
||||
found_cycle = true;
|
||||
|
||||
// FIXME(#137731): Resume all the waiters at once may cause deadlocks,
|
||||
// so we resume one waiter at a call for now. It's still unclear whether
|
||||
// it's due to possible issues in rustc-rayon or instead in the handling
|
||||
// of query cycles.
|
||||
// This seem to only appear when multiple query cycles errors
|
||||
// are involved, so this reduction in parallelism, while suboptimal, is not
|
||||
// universal and only the deadlock handler will encounter these cases.
|
||||
// The workaround shows loss of potential gains, but there still are big
|
||||
// improvements in the common case, and no regressions compared to the
|
||||
// single-threaded case. More investigation is still needed, and once fixed,
|
||||
// we can wake up all the waiters up.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3126,8 +3126,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
|
|||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
ObligationCauseCode::ConstSized => {
|
||||
err.note("constant expressions must have a statically known size");
|
||||
ObligationCauseCode::SizedConstOrStatic => {
|
||||
err.note("statics and constants must have a statically known size");
|
||||
}
|
||||
ObligationCauseCode::InlineAsmSized => {
|
||||
err.note("all inline asm arguments must have a statically known size");
|
||||
|
|
|
@ -17,13 +17,7 @@ impl<'tcx> OutlivesEnvironment<'tcx> {
|
|||
param_env: ty::ParamEnv<'tcx>,
|
||||
assumed_wf_tys: impl IntoIterator<Item = Ty<'tcx>>,
|
||||
) -> Self {
|
||||
Self::new_with_implied_bounds_compat(
|
||||
infcx,
|
||||
body_id,
|
||||
param_env,
|
||||
assumed_wf_tys,
|
||||
!infcx.tcx.sess.opts.unstable_opts.no_implied_bounds_compat,
|
||||
)
|
||||
Self::new_with_implied_bounds_compat(infcx, body_id, param_env, assumed_wf_tys, false)
|
||||
}
|
||||
|
||||
fn new_with_implied_bounds_compat(
|
||||
|
@ -31,7 +25,7 @@ impl<'tcx> OutlivesEnvironment<'tcx> {
|
|||
body_id: LocalDefId,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
assumed_wf_tys: impl IntoIterator<Item = Ty<'tcx>>,
|
||||
implied_bounds_compat: bool,
|
||||
disable_implied_bounds_hack: bool,
|
||||
) -> Self {
|
||||
let mut bounds = vec![];
|
||||
|
||||
|
@ -59,11 +53,11 @@ impl<'tcx> OutlivesEnvironment<'tcx> {
|
|||
OutlivesEnvironment::from_normalized_bounds(
|
||||
param_env,
|
||||
bounds,
|
||||
infcx.implied_bounds_tys_with_compat(
|
||||
infcx.implied_bounds_tys(
|
||||
body_id,
|
||||
param_env,
|
||||
assumed_wf_tys,
|
||||
implied_bounds_compat,
|
||||
disable_implied_bounds_hack,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ fn implied_outlives_bounds<'a, 'tcx>(
|
|||
param_env: ty::ParamEnv<'tcx>,
|
||||
body_id: LocalDefId,
|
||||
ty: Ty<'tcx>,
|
||||
compat: bool,
|
||||
disable_implied_bounds_hack: bool,
|
||||
) -> Vec<OutlivesBound<'tcx>> {
|
||||
let ty = infcx.resolve_vars_if_possible(ty);
|
||||
let ty = OpportunisticRegionResolver::new(infcx).fold_ty(ty);
|
||||
|
@ -52,11 +52,8 @@ fn implied_outlives_bounds<'a, 'tcx>(
|
|||
let mut canonical_var_values = OriginalQueryValues::default();
|
||||
let input = ImpliedOutlivesBounds { ty };
|
||||
let canonical = infcx.canonicalize_query(param_env.and(input), &mut canonical_var_values);
|
||||
let implied_bounds_result = if compat {
|
||||
infcx.tcx.implied_outlives_bounds_compat(canonical)
|
||||
} else {
|
||||
infcx.tcx.implied_outlives_bounds(canonical)
|
||||
};
|
||||
let implied_bounds_result =
|
||||
infcx.tcx.implied_outlives_bounds((canonical, disable_implied_bounds_hack));
|
||||
let Ok(canonical_result) = implied_bounds_result else {
|
||||
return vec![];
|
||||
};
|
||||
|
@ -110,14 +107,15 @@ fn implied_outlives_bounds<'a, 'tcx>(
|
|||
impl<'tcx> InferCtxt<'tcx> {
|
||||
/// Do *NOT* call this directly. You probably want to construct a `OutlivesEnvironment`
|
||||
/// instead if you're interested in the implied bounds for a given signature.
|
||||
fn implied_bounds_tys_with_compat<Tys: IntoIterator<Item = Ty<'tcx>>>(
|
||||
fn implied_bounds_tys<Tys: IntoIterator<Item = Ty<'tcx>>>(
|
||||
&self,
|
||||
body_id: LocalDefId,
|
||||
param_env: ParamEnv<'tcx>,
|
||||
tys: Tys,
|
||||
compat: bool,
|
||||
disable_implied_bounds_hack: bool,
|
||||
) -> impl Iterator<Item = OutlivesBound<'tcx>> {
|
||||
tys.into_iter()
|
||||
.flat_map(move |ty| implied_outlives_bounds(self, param_env, body_id, ty, compat))
|
||||
tys.into_iter().flat_map(move |ty| {
|
||||
implied_outlives_bounds(self, param_env, body_id, ty, disable_implied_bounds_hack)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1232,8 +1232,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
|
|||
// why we special case object types.
|
||||
false
|
||||
}
|
||||
ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
|
||||
| ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
|
||||
ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => {
|
||||
// These traits have no associated types.
|
||||
selcx.tcx().dcx().span_delayed_bug(
|
||||
obligation.cause.span,
|
||||
|
@ -1325,8 +1324,7 @@ fn confirm_select_candidate<'cx, 'tcx>(
|
|||
}
|
||||
ImplSource::Builtin(BuiltinImplSource::Object { .. }, _)
|
||||
| ImplSource::Param(..)
|
||||
| ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
|
||||
| ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
|
||||
| ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => {
|
||||
// we don't create Select candidates with this kind of resolution
|
||||
span_bug!(
|
||||
obligation.cause.span,
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use rustc_infer::infer::RegionObligation;
|
||||
use rustc_infer::infer::canonical::CanonicalQueryInput;
|
||||
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
|
||||
use rustc_infer::traits::query::OutlivesBound;
|
||||
use rustc_infer::traits::query::type_op::ImpliedOutlivesBounds;
|
||||
use rustc_middle::infer::canonical::CanonicalQueryResponse;
|
||||
use rustc_middle::traits::ObligationCause;
|
||||
use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeFolder, TypeVisitableExt};
|
||||
use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeVisitable, TypeVisitor};
|
||||
use rustc_span::def_id::CRATE_DEF_ID;
|
||||
use rustc_span::{DUMMY_SP, Span};
|
||||
use rustc_span::{DUMMY_SP, Span, sym};
|
||||
use rustc_type_ir::outlives::{Component, push_outlives_components};
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::traits::query::NoSolution;
|
||||
use crate::traits::{ObligationCtxt, wf};
|
||||
|
@ -35,11 +36,7 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> {
|
|||
tcx: TyCtxt<'tcx>,
|
||||
canonicalized: CanonicalQueryInput<'tcx, ParamEnvAnd<'tcx, Self>>,
|
||||
) -> Result<CanonicalQueryResponse<'tcx, Self::QueryResponse>, NoSolution> {
|
||||
if tcx.sess.opts.unstable_opts.no_implied_bounds_compat {
|
||||
tcx.implied_outlives_bounds(canonicalized)
|
||||
} else {
|
||||
tcx.implied_outlives_bounds_compat(canonicalized)
|
||||
}
|
||||
tcx.implied_outlives_bounds((canonicalized, false))
|
||||
}
|
||||
|
||||
fn perform_locally_with_next_solver(
|
||||
|
@ -47,11 +44,7 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> {
|
|||
key: ParamEnvAnd<'tcx, Self>,
|
||||
span: Span,
|
||||
) -> Result<Self::QueryResponse, NoSolution> {
|
||||
if ocx.infcx.tcx.sess.opts.unstable_opts.no_implied_bounds_compat {
|
||||
compute_implied_outlives_bounds_inner(ocx, key.param_env, key.value.ty, span)
|
||||
} else {
|
||||
compute_implied_outlives_bounds_compat_inner(ocx, key.param_env, key.value.ty, span)
|
||||
}
|
||||
compute_implied_outlives_bounds_inner(ocx, key.param_env, key.value.ty, span, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,18 +53,15 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
|
|||
param_env: ty::ParamEnv<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
span: Span,
|
||||
disable_implied_bounds_hack: bool,
|
||||
) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
|
||||
let normalize_op = |ty| -> Result<_, NoSolution> {
|
||||
let normalize_ty = |ty| -> Result<_, NoSolution> {
|
||||
// We must normalize the type so we can compute the right outlives components.
|
||||
// for example, if we have some constrained param type like `T: Trait<Out = U>`,
|
||||
// and we know that `&'a T::Out` is WF, then we want to imply `U: 'a`.
|
||||
let ty = ocx
|
||||
.deeply_normalize(&ObligationCause::dummy_with_span(span), param_env, ty)
|
||||
.map_err(|_| NoSolution)?;
|
||||
if !ocx.select_all_or_error().is_empty() {
|
||||
return Err(NoSolution);
|
||||
}
|
||||
let ty = OpportunisticRegionResolver::new(&ocx.infcx).fold_ty(ty);
|
||||
Ok(ty)
|
||||
};
|
||||
|
||||
|
@ -81,7 +71,7 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
|
|||
// guaranteed to be a subset of the original type, so we need to store the
|
||||
// WF args we've computed in a set.
|
||||
let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default();
|
||||
let mut wf_args = vec![ty.into(), normalize_op(ty)?.into()];
|
||||
let mut wf_args = vec![ty.into(), normalize_ty(ty)?.into()];
|
||||
|
||||
let mut outlives_bounds: Vec<OutlivesBound<'tcx>> = vec![];
|
||||
|
||||
|
@ -96,8 +86,14 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
|
|||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
assert!(!obligation.has_escaping_bound_vars());
|
||||
let Some(pred) = obligation.predicate.kind().no_bound_vars() else {
|
||||
let pred = ocx
|
||||
.deeply_normalize(
|
||||
&ObligationCause::dummy_with_span(span),
|
||||
param_env,
|
||||
obligation.predicate,
|
||||
)
|
||||
.map_err(|_| NoSolution)?;
|
||||
let Some(pred) = pred.kind().no_bound_vars() else {
|
||||
continue;
|
||||
};
|
||||
match pred {
|
||||
|
@ -130,7 +126,6 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
|
|||
ty_a,
|
||||
r_b,
|
||||
))) => {
|
||||
let ty_a = normalize_op(ty_a)?;
|
||||
let mut components = smallvec![];
|
||||
push_outlives_components(ocx.infcx.tcx, ty_a, &mut components);
|
||||
outlives_bounds.extend(implied_bounds_from_components(r_b, components))
|
||||
|
@ -139,141 +134,48 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
|
|||
}
|
||||
}
|
||||
|
||||
// If we detect `bevy_ecs::*::ParamSet` in the WF args list (and `disable_implied_bounds_hack`
|
||||
// or `-Zno-implied-bounds-compat` are not set), then use the registered outlives obligations
|
||||
// as implied bounds.
|
||||
if !disable_implied_bounds_hack
|
||||
&& !ocx.infcx.tcx.sess.opts.unstable_opts.no_implied_bounds_compat
|
||||
&& ty.visit_with(&mut ContainsBevyParamSet { tcx: ocx.infcx.tcx }).is_break()
|
||||
{
|
||||
for RegionObligation { sup_type, sub_region, .. } in
|
||||
ocx.infcx.take_registered_region_obligations()
|
||||
{
|
||||
let mut components = smallvec![];
|
||||
push_outlives_components(ocx.infcx.tcx, sup_type, &mut components);
|
||||
outlives_bounds.extend(implied_bounds_from_components(sub_region, components));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(outlives_bounds)
|
||||
}
|
||||
|
||||
pub fn compute_implied_outlives_bounds_compat_inner<'tcx>(
|
||||
ocx: &ObligationCtxt<'_, 'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
span: Span,
|
||||
) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
|
||||
let tcx = ocx.infcx.tcx;
|
||||
struct ContainsBevyParamSet<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
}
|
||||
|
||||
// Sometimes when we ask what it takes for T: WF, we get back that
|
||||
// U: WF is required; in that case, we push U onto this stack and
|
||||
// process it next. Because the resulting predicates aren't always
|
||||
// guaranteed to be a subset of the original type, so we need to store the
|
||||
// WF args we've computed in a set.
|
||||
let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default();
|
||||
let mut wf_args = vec![ty.into()];
|
||||
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsBevyParamSet<'tcx> {
|
||||
type Result = ControlFlow<()>;
|
||||
|
||||
let mut outlives_bounds: Vec<ty::OutlivesPredicate<'tcx, ty::GenericArg<'tcx>>> = vec![];
|
||||
|
||||
while let Some(arg) = wf_args.pop() {
|
||||
if !checked_wf_args.insert(arg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compute the obligations for `arg` to be well-formed. If `arg` is
|
||||
// an unresolved inference variable, just instantiated an empty set
|
||||
// -- because the return type here is going to be things we *add*
|
||||
// to the environment, it's always ok for this set to be smaller
|
||||
// than the ultimate set. (Note: normally there won't be
|
||||
// unresolved inference variables here anyway, but there might be
|
||||
// during typeck under some circumstances.)
|
||||
//
|
||||
// FIXME(@lcnr): It's not really "always fine", having fewer implied
|
||||
// bounds can be backward incompatible, e.g. #101951 was caused by
|
||||
// us not dealing with inference vars in `TypeOutlives` predicates.
|
||||
let obligations =
|
||||
wf::obligations(ocx.infcx, param_env, CRATE_DEF_ID, 0, arg, span).unwrap_or_default();
|
||||
|
||||
for obligation in obligations {
|
||||
debug!(?obligation);
|
||||
assert!(!obligation.has_escaping_bound_vars());
|
||||
|
||||
// While these predicates should all be implied by other parts of
|
||||
// the program, they are still relevant as they may constrain
|
||||
// inference variables, which is necessary to add the correct
|
||||
// implied bounds in some cases, mostly when dealing with projections.
|
||||
//
|
||||
// Another important point here: we only register `Projection`
|
||||
// predicates, since otherwise we might register outlives
|
||||
// predicates containing inference variables, and we don't
|
||||
// learn anything new from those.
|
||||
if obligation.predicate.has_non_region_infer() {
|
||||
match obligation.predicate.kind().skip_binder() {
|
||||
ty::PredicateKind::Clause(ty::ClauseKind::Projection(..))
|
||||
| ty::PredicateKind::AliasRelate(..) => {
|
||||
ocx.register_obligation(obligation.clone());
|
||||
}
|
||||
_ => {}
|
||||
fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
|
||||
// We only care to match `ParamSet<T>` or `&ParamSet<T>`.
|
||||
match t.kind() {
|
||||
ty::Adt(def, _) => {
|
||||
if self.tcx.item_name(def.did()) == sym::ParamSet
|
||||
&& self.tcx.crate_name(def.did().krate) == sym::bevy_ecs
|
||||
{
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
}
|
||||
|
||||
let pred = match obligation.predicate.kind().no_bound_vars() {
|
||||
None => continue,
|
||||
Some(pred) => pred,
|
||||
};
|
||||
match pred {
|
||||
// FIXME(const_generics): Make sure that `<'a, 'b, const N: &'a &'b u32>` is sound
|
||||
// if we ever support that
|
||||
ty::PredicateKind::Clause(ty::ClauseKind::Trait(..))
|
||||
| ty::PredicateKind::Clause(ty::ClauseKind::HostEffect(..))
|
||||
| ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..))
|
||||
| ty::PredicateKind::Subtype(..)
|
||||
| ty::PredicateKind::Coerce(..)
|
||||
| ty::PredicateKind::Clause(ty::ClauseKind::Projection(..))
|
||||
| ty::PredicateKind::DynCompatible(..)
|
||||
| ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..))
|
||||
| ty::PredicateKind::ConstEquate(..)
|
||||
| ty::PredicateKind::Ambiguous
|
||||
| ty::PredicateKind::NormalizesTo(..)
|
||||
| ty::PredicateKind::AliasRelate(..) => {}
|
||||
|
||||
// We need to search through *all* WellFormed predicates
|
||||
ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => {
|
||||
wf_args.push(arg);
|
||||
}
|
||||
|
||||
// We need to register region relationships
|
||||
ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(
|
||||
ty::OutlivesPredicate(r_a, r_b),
|
||||
)) => outlives_bounds.push(ty::OutlivesPredicate(r_a.into(), r_b)),
|
||||
|
||||
ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(
|
||||
ty_a,
|
||||
r_b,
|
||||
))) => outlives_bounds.push(ty::OutlivesPredicate(ty_a.into(), r_b)),
|
||||
}
|
||||
ty::Ref(_, ty, _) => ty.visit_with(self)?,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// This call to `select_all_or_error` is necessary to constrain inference variables, which we
|
||||
// use further down when computing the implied bounds.
|
||||
match ocx.select_all_or_error().as_slice() {
|
||||
[] => (),
|
||||
_ => return Err(NoSolution),
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
// We lazily compute the outlives components as
|
||||
// `select_all_or_error` constrains inference variables.
|
||||
let mut implied_bounds = Vec::new();
|
||||
for ty::OutlivesPredicate(a, r_b) in outlives_bounds {
|
||||
match a.unpack() {
|
||||
ty::GenericArgKind::Lifetime(r_a) => {
|
||||
implied_bounds.push(OutlivesBound::RegionSubRegion(r_b, r_a))
|
||||
}
|
||||
ty::GenericArgKind::Type(ty_a) => {
|
||||
let mut ty_a = ocx.infcx.resolve_vars_if_possible(ty_a);
|
||||
// Need to manually normalize in the new solver as `wf::obligations` does not.
|
||||
if ocx.infcx.next_trait_solver() {
|
||||
ty_a = ocx
|
||||
.deeply_normalize(&ObligationCause::dummy_with_span(span), param_env, ty_a)
|
||||
.map_err(|_| NoSolution)?;
|
||||
}
|
||||
let mut components = smallvec![];
|
||||
push_outlives_components(tcx, ty_a, &mut components);
|
||||
implied_bounds.extend(implied_bounds_from_components(r_b, components))
|
||||
}
|
||||
ty::GenericArgKind::Const(_) => {
|
||||
unreachable!("consts do not participate in outlives bounds")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(implied_bounds)
|
||||
}
|
||||
|
||||
/// When we have an implied bound that `T: 'a`, we can further break
|
||||
|
|
|
@ -1017,13 +1017,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
// `(.., T)` -> `(.., U)`
|
||||
(&ty::Tuple(tys_a), &ty::Tuple(tys_b)) => {
|
||||
if tys_a.len() == tys_b.len() {
|
||||
candidates.vec.push(BuiltinUnsizeCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
//! [rustc dev guide]:
|
||||
//! https://rustc-dev-guide.rust-lang.org/traits/resolution.html#confirmation
|
||||
|
||||
use std::iter;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use rustc_ast::Mutability;
|
||||
|
@ -1315,34 +1314,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
ImplSource::Builtin(BuiltinImplSource::Misc, nested)
|
||||
}
|
||||
|
||||
// `(.., T)` -> `(.., U)`
|
||||
(&ty::Tuple(tys_a), &ty::Tuple(tys_b)) => {
|
||||
assert_eq!(tys_a.len(), tys_b.len());
|
||||
|
||||
// The last field of the tuple has to exist.
|
||||
let (&a_last, a_mid) = tys_a.split_last().ok_or(Unimplemented)?;
|
||||
let &b_last = tys_b.last().unwrap();
|
||||
|
||||
// Check that the source tuple with the target's
|
||||
// last element is equal to the target.
|
||||
let new_tuple =
|
||||
Ty::new_tup_from_iter(tcx, a_mid.iter().copied().chain(iter::once(b_last)));
|
||||
let InferOk { mut obligations, .. } = self
|
||||
.infcx
|
||||
.at(&obligation.cause, obligation.param_env)
|
||||
.eq(DefineOpaqueTypes::Yes, target, new_tuple)
|
||||
.map_err(|_| Unimplemented)?;
|
||||
|
||||
// Add a nested `T: Unsize<U>` predicate.
|
||||
let last_unsize_obligation = obligation.with(
|
||||
tcx,
|
||||
ty::TraitRef::new(tcx, obligation.predicate.def_id(), [a_last, b_last]),
|
||||
);
|
||||
obligations.push(last_unsize_obligation);
|
||||
|
||||
ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, obligations)
|
||||
}
|
||||
|
||||
_ => bug!("source: {source}, target: {target}"),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,38 +10,28 @@ use rustc_middle::query::Providers;
|
|||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_span::DUMMY_SP;
|
||||
use rustc_trait_selection::infer::InferCtxtBuilderExt;
|
||||
use rustc_trait_selection::traits::query::type_op::implied_outlives_bounds::{
|
||||
compute_implied_outlives_bounds_compat_inner, compute_implied_outlives_bounds_inner,
|
||||
};
|
||||
use rustc_trait_selection::traits::query::type_op::implied_outlives_bounds::compute_implied_outlives_bounds_inner;
|
||||
use rustc_trait_selection::traits::query::{CanonicalImpliedOutlivesBoundsGoal, NoSolution};
|
||||
|
||||
pub(crate) fn provide(p: &mut Providers) {
|
||||
*p = Providers { implied_outlives_bounds_compat, ..*p };
|
||||
*p = Providers { implied_outlives_bounds, ..*p };
|
||||
}
|
||||
|
||||
fn implied_outlives_bounds_compat<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
goal: CanonicalImpliedOutlivesBoundsGoal<'tcx>,
|
||||
) -> Result<
|
||||
&'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec<OutlivesBound<'tcx>>>>,
|
||||
NoSolution,
|
||||
> {
|
||||
tcx.infer_ctxt().enter_canonical_trait_query(&goal, |ocx, key| {
|
||||
let (param_env, ImpliedOutlivesBounds { ty }) = key.into_parts();
|
||||
compute_implied_outlives_bounds_compat_inner(ocx, param_env, ty, DUMMY_SP)
|
||||
})
|
||||
}
|
||||
|
||||
fn implied_outlives_bounds<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
goal: CanonicalImpliedOutlivesBoundsGoal<'tcx>,
|
||||
(goal, disable_implied_bounds_hack): (CanonicalImpliedOutlivesBoundsGoal<'tcx>, bool),
|
||||
) -> Result<
|
||||
&'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec<OutlivesBound<'tcx>>>>,
|
||||
NoSolution,
|
||||
> {
|
||||
tcx.infer_ctxt().enter_canonical_trait_query(&goal, |ocx, key| {
|
||||
let (param_env, ImpliedOutlivesBounds { ty }) = key.into_parts();
|
||||
compute_implied_outlives_bounds_inner(ocx, param_env, ty, DUMMY_SP)
|
||||
compute_implied_outlives_bounds_inner(
|
||||
ocx,
|
||||
param_env,
|
||||
ty,
|
||||
DUMMY_SP,
|
||||
disable_implied_bounds_hack,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -436,10 +436,7 @@ fn fn_abi_sanity_check<'tcx>(
|
|||
) {
|
||||
let tcx = cx.tcx();
|
||||
|
||||
if spec_abi == ExternAbi::Rust
|
||||
|| spec_abi == ExternAbi::RustCall
|
||||
|| spec_abi == ExternAbi::RustCold
|
||||
{
|
||||
if spec_abi.is_rustic_abi() {
|
||||
if arg.layout.is_zst() {
|
||||
// Casting closures to function pointers depends on ZST closure types being
|
||||
// omitted entirely in the calling convention.
|
||||
|
@ -687,7 +684,7 @@ fn fn_abi_adjust_for_abi<'tcx>(
|
|||
|
||||
let tcx = cx.tcx();
|
||||
|
||||
if abi == ExternAbi::Rust || abi == ExternAbi::RustCall || abi == ExternAbi::RustIntrinsic {
|
||||
if abi.is_rustic_abi() {
|
||||
fn_abi.adjust_for_rust_abi(cx, abi);
|
||||
|
||||
// Look up the deduced parameter attributes for this function, if we have its def ID and
|
||||
|
|
|
@ -381,8 +381,7 @@ fn resolve_associated_item<'tcx>(
|
|||
}
|
||||
}
|
||||
traits::ImplSource::Param(..)
|
||||
| traits::ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
|
||||
| traits::ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => None,
|
||||
| traits::ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => None,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -189,11 +189,6 @@ pub enum BuiltinImplSource {
|
|||
///
|
||||
/// The index is only used for winnowing.
|
||||
TraitUpcasting(usize),
|
||||
/// Unsizing a tuple like `(A, B, ..., X)` to `(A, B, ..., Y)` if `X` unsizes to `Y`.
|
||||
///
|
||||
/// This can be removed when `feature(tuple_unsizing)` is stabilized, since we only
|
||||
/// use it to detect when unsizing tuples in hir typeck.
|
||||
TupleUnsizing,
|
||||
}
|
||||
|
||||
#[derive_where(Clone, Copy, Hash, PartialEq, Eq, Debug; I: Interner)]
|
||||
|
|
|
@ -189,6 +189,17 @@
|
|||
# The default stage to use for the `bench` subcommand
|
||||
#bench-stage = 2
|
||||
|
||||
# A descriptive string to be appended to version output (e.g., `rustc --version`),
|
||||
# which is also used in places like debuginfo `DW_AT_producer`. This may be useful for
|
||||
# supplementary build information, like distro-specific package versions.
|
||||
#
|
||||
# The Rust compiler will differentiate between versions of itself, including
|
||||
# based on this string, which means that if you wish to be compatible with
|
||||
# upstream Rust you need to set this to "". However, note that if you set this to "" but
|
||||
# are not actually compatible -- for example if you've backported patches that change
|
||||
# behavior -- this may lead to miscompilations or other bugs.
|
||||
#description = ""
|
||||
|
||||
# Build triple for the pre-compiled snapshot compiler. If `rustc` is set, this must match its host
|
||||
# triple (see `rustc --version --verbose`; cross-compiling the rust build system itself is NOT
|
||||
# supported). If `rustc` is unset, this must be a platform with pre-compiled host tools
|
||||
|
@ -615,17 +626,6 @@
|
|||
# If using tarball sources, default value is "auto-detect", otherwise, it's "dev".
|
||||
#channel = if "is a tarball source" { "auto-detect" } else { "dev" }
|
||||
|
||||
# A descriptive string to be appended to `rustc --version` output, which is
|
||||
# also used in places like debuginfo `DW_AT_producer`. This may be useful for
|
||||
# supplementary build information, like distro-specific package versions.
|
||||
#
|
||||
# The Rust compiler will differentiate between versions of itself, including
|
||||
# based on this string, which means that if you wish to be compatible with
|
||||
# upstream Rust you need to set this to "". However, note that if you are not
|
||||
# actually compatible -- for example if you've backported patches that change
|
||||
# behavior -- this may lead to miscompilations or other bugs.
|
||||
#description = ""
|
||||
|
||||
# The root location of the musl installation directory. The library directory
|
||||
# will also need to contain libunwind.a for an unwinding implementation. Note
|
||||
# that this option only makes sense for musl targets that produce statically
|
||||
|
|
|
@ -151,9 +151,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
version = "0.2.170"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
||||
dependencies = [
|
||||
"rustc-std-workspace-core",
|
||||
]
|
||||
|
|
|
@ -1134,7 +1134,6 @@ impl String {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(string_extend_from_within)]
|
||||
/// let mut string = String::from("abcde");
|
||||
///
|
||||
/// string.extend_from_within(2..);
|
||||
|
@ -1147,7 +1146,7 @@ impl String {
|
|||
/// assert_eq!(string, "abcdecdeabecde");
|
||||
/// ```
|
||||
#[cfg(not(no_global_oom_handling))]
|
||||
#[unstable(feature = "string_extend_from_within", issue = "103806")]
|
||||
#[stable(feature = "string_extend_from_within", since = "CURRENT_RUSTC_VERSION")]
|
||||
pub fn extend_from_within<R>(&mut self, src: R)
|
||||
where
|
||||
R: RangeBounds<usize>,
|
||||
|
|
|
@ -1693,49 +1693,41 @@ impl<'a> Formatter<'a> {
|
|||
/// ```
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub fn pad(&mut self, s: &str) -> Result {
|
||||
// Make sure there's a fast path up front
|
||||
// Make sure there's a fast path up front.
|
||||
if self.options.width.is_none() && self.options.precision.is_none() {
|
||||
return self.buf.write_str(s);
|
||||
}
|
||||
// The `precision` field can be interpreted as a `max-width` for the
|
||||
|
||||
// The `precision` field can be interpreted as a maximum width for the
|
||||
// string being formatted.
|
||||
let s = if let Some(max) = self.options.precision {
|
||||
// If our string is longer that the precision, then we must have
|
||||
// truncation. However other flags like `fill`, `width` and `align`
|
||||
// must act as always.
|
||||
if let Some((i, _)) = s.char_indices().nth(max) {
|
||||
// LLVM here can't prove that `..i` won't panic `&s[..i]`, but
|
||||
// we know that it can't panic. Use `get` + `unwrap_or` to avoid
|
||||
// `unsafe` and otherwise don't emit any panic-related code
|
||||
// here.
|
||||
s.get(..i).unwrap_or(s)
|
||||
} else {
|
||||
&s
|
||||
}
|
||||
let (s, char_count) = if let Some(max_char_count) = self.options.precision {
|
||||
let mut iter = s.char_indices();
|
||||
let remaining = match iter.advance_by(max_char_count) {
|
||||
Ok(()) => 0,
|
||||
Err(remaining) => remaining.get(),
|
||||
};
|
||||
// SAFETY: The offset of `.char_indices()` is guaranteed to be
|
||||
// in-bounds and between character boundaries.
|
||||
let truncated = unsafe { s.get_unchecked(..iter.offset()) };
|
||||
(truncated, max_char_count - remaining)
|
||||
} else {
|
||||
&s
|
||||
// Use the optimized char counting algorithm for the full string.
|
||||
(s, s.chars().count())
|
||||
};
|
||||
// The `width` field is more of a `min-width` parameter at this point.
|
||||
match self.options.width {
|
||||
// If we're under the maximum length, and there's no minimum length
|
||||
// requirements, then we can just emit the string
|
||||
None => self.buf.write_str(s),
|
||||
Some(width) => {
|
||||
let chars_count = s.chars().count();
|
||||
// If we're under the maximum width, check if we're over the minimum
|
||||
// width, if so it's as easy as just emitting the string.
|
||||
if chars_count >= width {
|
||||
self.buf.write_str(s)
|
||||
}
|
||||
// If we're under both the maximum and the minimum width, then fill
|
||||
// up the minimum width with the specified string + some alignment.
|
||||
else {
|
||||
let align = Alignment::Left;
|
||||
let post_padding = self.padding(width - chars_count, align)?;
|
||||
self.buf.write_str(s)?;
|
||||
post_padding.write(self)
|
||||
}
|
||||
}
|
||||
|
||||
// The `width` field is more of a minimum width parameter at this point.
|
||||
if let Some(width) = self.options.width
|
||||
&& char_count < width
|
||||
{
|
||||
// If we're under the minimum width, then fill up the minimum width
|
||||
// with the specified string + some alignment.
|
||||
let post_padding = self.padding(width - char_count, Alignment::Left)?;
|
||||
self.buf.write_str(s)?;
|
||||
post_padding.write(self)
|
||||
} else {
|
||||
// If we're over the minimum width or there is no minimum width, we
|
||||
// can just emit the string.
|
||||
self.buf.write_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,12 +8,12 @@ pub(crate) trait ByteSlice {
|
|||
/// Writes a 64-bit integer as 8 bytes in little-endian order.
|
||||
fn write_u64(&mut self, value: u64);
|
||||
|
||||
/// Calculate the offset of a slice from another.
|
||||
/// Calculate the difference in length between two slices.
|
||||
fn offset_from(&self, other: &Self) -> isize;
|
||||
|
||||
/// Iteratively parse and consume digits from bytes.
|
||||
/// Returns the same bytes with consumed digits being
|
||||
/// elided.
|
||||
///
|
||||
/// Returns the same bytes with consumed digits being elided. Breaks on invalid digits.
|
||||
fn parse_digits(&self, func: impl FnMut(u8)) -> &Self;
|
||||
}
|
||||
|
||||
|
@ -39,11 +39,11 @@ impl ByteSlice for [u8] {
|
|||
fn parse_digits(&self, mut func: impl FnMut(u8)) -> &Self {
|
||||
let mut s = self;
|
||||
|
||||
while let Some((c, s_next)) = s.split_first() {
|
||||
while let Some((c, rest)) = s.split_first() {
|
||||
let c = c.wrapping_sub(b'0');
|
||||
if c < 10 {
|
||||
func(c);
|
||||
s = s_next;
|
||||
s = rest;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
@ -53,7 +53,9 @@ impl ByteSlice for [u8] {
|
|||
}
|
||||
}
|
||||
|
||||
/// Determine if 8 bytes are all decimal digits.
|
||||
/// Determine if all characters in an 8-byte byte string (represented as a `u64`) are all decimal
|
||||
/// digits.
|
||||
///
|
||||
/// This does not care about the order in which the bytes were loaded.
|
||||
pub(crate) fn is_8digits(v: u64) -> bool {
|
||||
let a = v.wrapping_add(0x4646_4646_4646_4646);
|
||||
|
@ -61,19 +63,20 @@ pub(crate) fn is_8digits(v: u64) -> bool {
|
|||
(a | b) & 0x8080_8080_8080_8080 == 0
|
||||
}
|
||||
|
||||
/// A custom 64-bit floating point type, representing `f * 2^e`.
|
||||
/// e is biased, so it be directly shifted into the exponent bits.
|
||||
/// A custom 64-bit floating point type, representing `m * 2^p`.
|
||||
/// p is biased, so it be directly shifted into the exponent bits.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||
pub struct BiasedFp {
|
||||
/// The significant digits.
|
||||
pub f: u64,
|
||||
pub m: u64,
|
||||
/// The biased, binary exponent.
|
||||
pub e: i32,
|
||||
pub p_biased: i32,
|
||||
}
|
||||
|
||||
impl BiasedFp {
|
||||
/// Represent `0 ^ p`
|
||||
#[inline]
|
||||
pub const fn zero_pow2(e: i32) -> Self {
|
||||
Self { f: 0, e }
|
||||
pub const fn zero_pow2(p_biased: i32) -> Self {
|
||||
Self { m: 0, p_biased }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,358 +1,87 @@
|
|||
//! Arbitrary-precision decimal class for fallback algorithms.
|
||||
//!
|
||||
//! This is only used if the fast-path (native floats) and
|
||||
//! the Eisel-Lemire algorithm are unable to unambiguously
|
||||
//! determine the float.
|
||||
//!
|
||||
//! The technique used is "Simple Decimal Conversion", developed
|
||||
//! by Nigel Tao and Ken Thompson. A detailed description of the
|
||||
//! algorithm can be found in "ParseNumberF64 by Simple Decimal Conversion",
|
||||
//! available online: <https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html>.
|
||||
//! Representation of a float as the significant digits and exponent.
|
||||
|
||||
use crate::num::dec2flt::common::{ByteSlice, is_8digits};
|
||||
use crate::num::dec2flt::float::RawFloat;
|
||||
use crate::num::dec2flt::fpu::set_precision;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Decimal {
|
||||
/// The number of significant digits in the decimal.
|
||||
pub num_digits: usize,
|
||||
/// The offset of the decimal point in the significant digits.
|
||||
pub decimal_point: i32,
|
||||
/// If the number of significant digits stored in the decimal is truncated.
|
||||
pub truncated: bool,
|
||||
/// Buffer of the raw digits, in the range [0, 9].
|
||||
pub digits: [u8; Self::MAX_DIGITS],
|
||||
}
|
||||
const INT_POW10: [u64; 16] = [
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000,
|
||||
10000000000,
|
||||
100000000000,
|
||||
1000000000000,
|
||||
10000000000000,
|
||||
100000000000000,
|
||||
1000000000000000,
|
||||
];
|
||||
|
||||
impl Default for Decimal {
|
||||
fn default() -> Self {
|
||||
Self { num_digits: 0, decimal_point: 0, truncated: false, digits: [0; Self::MAX_DIGITS] }
|
||||
}
|
||||
/// A floating point number with up to 64 bits of mantissa and an `i64` exponent.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Decimal {
|
||||
pub exponent: i64,
|
||||
pub mantissa: u64,
|
||||
pub negative: bool,
|
||||
pub many_digits: bool,
|
||||
}
|
||||
|
||||
impl Decimal {
|
||||
/// The maximum number of digits required to unambiguously round a float.
|
||||
///
|
||||
/// For a double-precision IEEE 754 float, this required 767 digits,
|
||||
/// so we store the max digits + 1.
|
||||
///
|
||||
/// We can exactly represent a float in radix `b` from radix 2 if
|
||||
/// `b` is divisible by 2. This function calculates the exact number of
|
||||
/// digits required to exactly represent that float.
|
||||
///
|
||||
/// According to the "Handbook of Floating Point Arithmetic",
|
||||
/// for IEEE754, with emin being the min exponent, p2 being the
|
||||
/// precision, and b being the radix, the number of digits follows as:
|
||||
///
|
||||
/// `−emin + p2 + ⌊(emin + 1) log(2, b) − log(1 − 2^(−p2), b)⌋`
|
||||
///
|
||||
/// For f32, this follows as:
|
||||
/// emin = -126
|
||||
/// p2 = 24
|
||||
///
|
||||
/// For f64, this follows as:
|
||||
/// emin = -1022
|
||||
/// p2 = 53
|
||||
///
|
||||
/// In Python:
|
||||
/// `-emin + p2 + math.floor((emin+ 1)*math.log(2, b)-math.log(1-2**(-p2), b))`
|
||||
pub(super) const MAX_DIGITS: usize = 768;
|
||||
/// The max digits that can be exactly represented in a 64-bit integer.
|
||||
pub(super) const MAX_DIGITS_WITHOUT_OVERFLOW: usize = 19;
|
||||
pub(super) const DECIMAL_POINT_RANGE: i32 = 2047;
|
||||
|
||||
/// Append a digit to the buffer.
|
||||
pub(super) fn try_add_digit(&mut self, digit: u8) {
|
||||
if self.num_digits < Self::MAX_DIGITS {
|
||||
self.digits[self.num_digits] = digit;
|
||||
}
|
||||
self.num_digits += 1;
|
||||
/// Detect if the float can be accurately reconstructed from native floats.
|
||||
#[inline]
|
||||
fn can_use_fast_path<F: RawFloat>(&self) -> bool {
|
||||
F::MIN_EXPONENT_FAST_PATH <= self.exponent
|
||||
&& self.exponent <= F::MAX_EXPONENT_DISGUISED_FAST_PATH
|
||||
&& self.mantissa <= F::MAX_MANTISSA_FAST_PATH
|
||||
&& !self.many_digits
|
||||
}
|
||||
|
||||
/// Trim trailing zeros from the buffer.
|
||||
pub(super) fn trim(&mut self) {
|
||||
// All of the following calls to `Decimal::trim` can't panic because:
|
||||
//
|
||||
// 1. `parse_decimal` sets `num_digits` to a max of `Decimal::MAX_DIGITS`.
|
||||
// 2. `right_shift` sets `num_digits` to `write_index`, which is bounded by `num_digits`.
|
||||
// 3. `left_shift` `num_digits` to a max of `Decimal::MAX_DIGITS`.
|
||||
//
|
||||
// Trim is only called in `right_shift` and `left_shift`.
|
||||
debug_assert!(self.num_digits <= Self::MAX_DIGITS);
|
||||
while self.num_digits != 0 && self.digits[self.num_digits - 1] == 0 {
|
||||
self.num_digits -= 1;
|
||||
}
|
||||
}
|
||||
/// Try turning the decimal into an exact float representation, using machine-sized integers
|
||||
/// and floats.
|
||||
///
|
||||
/// This is extracted into a separate function so that it can be attempted before constructing
|
||||
/// a Decimal. This only works if both the mantissa and the exponent
|
||||
/// can be exactly represented as a machine float, since IEE-754 guarantees
|
||||
/// no rounding will occur.
|
||||
///
|
||||
/// There is an exception: disguised fast-path cases, where we can shift
|
||||
/// powers-of-10 from the exponent to the significant digits.
|
||||
pub fn try_fast_path<F: RawFloat>(&self) -> Option<F> {
|
||||
// Here we need to work around <https://github.com/rust-lang/rust/issues/114479>.
|
||||
// The fast path crucially depends on arithmetic being rounded to the correct number of bits
|
||||
// without any intermediate rounding. On x86 (without SSE or SSE2) this requires the precision
|
||||
// of the x87 FPU stack to be changed so that it directly rounds to 64/32 bit.
|
||||
// The `set_precision` function takes care of setting the precision on architectures which
|
||||
// require setting it by changing the global state (like the control word of the x87 FPU).
|
||||
let _cw = set_precision::<F>();
|
||||
|
||||
pub(super) fn round(&self) -> u64 {
|
||||
if self.num_digits == 0 || self.decimal_point < 0 {
|
||||
return 0;
|
||||
} else if self.decimal_point > 18 {
|
||||
return 0xFFFF_FFFF_FFFF_FFFF_u64;
|
||||
if !self.can_use_fast_path::<F>() {
|
||||
return None;
|
||||
}
|
||||
let dp = self.decimal_point as usize;
|
||||
let mut n = 0_u64;
|
||||
for i in 0..dp {
|
||||
n *= 10;
|
||||
if i < self.num_digits {
|
||||
n += self.digits[i] as u64;
|
||||
}
|
||||
}
|
||||
let mut round_up = false;
|
||||
if dp < self.num_digits {
|
||||
round_up = self.digits[dp] >= 5;
|
||||
if self.digits[dp] == 5 && dp + 1 == self.num_digits {
|
||||
round_up = self.truncated || ((dp != 0) && (1 & self.digits[dp - 1] != 0))
|
||||
}
|
||||
}
|
||||
if round_up {
|
||||
n += 1;
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
/// Computes decimal * 2^shift.
|
||||
pub(super) fn left_shift(&mut self, shift: usize) {
|
||||
if self.num_digits == 0 {
|
||||
return;
|
||||
}
|
||||
let num_new_digits = number_of_digits_decimal_left_shift(self, shift);
|
||||
let mut read_index = self.num_digits;
|
||||
let mut write_index = self.num_digits + num_new_digits;
|
||||
let mut n = 0_u64;
|
||||
while read_index != 0 {
|
||||
read_index -= 1;
|
||||
write_index -= 1;
|
||||
n += (self.digits[read_index] as u64) << shift;
|
||||
let quotient = n / 10;
|
||||
let remainder = n - (10 * quotient);
|
||||
if write_index < Self::MAX_DIGITS {
|
||||
self.digits[write_index] = remainder as u8;
|
||||
} else if remainder > 0 {
|
||||
self.truncated = true;
|
||||
}
|
||||
n = quotient;
|
||||
}
|
||||
while n > 0 {
|
||||
write_index -= 1;
|
||||
let quotient = n / 10;
|
||||
let remainder = n - (10 * quotient);
|
||||
if write_index < Self::MAX_DIGITS {
|
||||
self.digits[write_index] = remainder as u8;
|
||||
} else if remainder > 0 {
|
||||
self.truncated = true;
|
||||
}
|
||||
n = quotient;
|
||||
}
|
||||
self.num_digits += num_new_digits;
|
||||
if self.num_digits > Self::MAX_DIGITS {
|
||||
self.num_digits = Self::MAX_DIGITS;
|
||||
}
|
||||
self.decimal_point += num_new_digits as i32;
|
||||
self.trim();
|
||||
}
|
||||
|
||||
/// Computes decimal * 2^-shift.
|
||||
pub(super) fn right_shift(&mut self, shift: usize) {
|
||||
let mut read_index = 0;
|
||||
let mut write_index = 0;
|
||||
let mut n = 0_u64;
|
||||
while (n >> shift) == 0 {
|
||||
if read_index < self.num_digits {
|
||||
n = (10 * n) + self.digits[read_index] as u64;
|
||||
read_index += 1;
|
||||
} else if n == 0 {
|
||||
return;
|
||||
let value = if self.exponent <= F::MAX_EXPONENT_FAST_PATH {
|
||||
// normal fast path
|
||||
let value = F::from_u64(self.mantissa);
|
||||
if self.exponent < 0 {
|
||||
value / F::pow10_fast_path((-self.exponent) as _)
|
||||
} else {
|
||||
while (n >> shift) == 0 {
|
||||
n *= 10;
|
||||
read_index += 1;
|
||||
}
|
||||
break;
|
||||
value * F::pow10_fast_path(self.exponent as _)
|
||||
}
|
||||
}
|
||||
self.decimal_point -= read_index as i32 - 1;
|
||||
if self.decimal_point < -Self::DECIMAL_POINT_RANGE {
|
||||
// `self = Self::Default()`, but without the overhead of clearing `digits`.
|
||||
self.num_digits = 0;
|
||||
self.decimal_point = 0;
|
||||
self.truncated = false;
|
||||
return;
|
||||
}
|
||||
let mask = (1_u64 << shift) - 1;
|
||||
while read_index < self.num_digits {
|
||||
let new_digit = (n >> shift) as u8;
|
||||
n = (10 * (n & mask)) + self.digits[read_index] as u64;
|
||||
read_index += 1;
|
||||
self.digits[write_index] = new_digit;
|
||||
write_index += 1;
|
||||
}
|
||||
while n > 0 {
|
||||
let new_digit = (n >> shift) as u8;
|
||||
n = 10 * (n & mask);
|
||||
if write_index < Self::MAX_DIGITS {
|
||||
self.digits[write_index] = new_digit;
|
||||
write_index += 1;
|
||||
} else if new_digit > 0 {
|
||||
self.truncated = true;
|
||||
}
|
||||
}
|
||||
self.num_digits = write_index;
|
||||
self.trim();
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a big integer representation of the float as a decimal.
|
||||
pub(super) fn parse_decimal(mut s: &[u8]) -> Decimal {
|
||||
let mut d = Decimal::default();
|
||||
let start = s;
|
||||
|
||||
while let Some((&b'0', s_next)) = s.split_first() {
|
||||
s = s_next;
|
||||
}
|
||||
|
||||
s = s.parse_digits(|digit| d.try_add_digit(digit));
|
||||
|
||||
if let Some((b'.', s_next)) = s.split_first() {
|
||||
s = s_next;
|
||||
let first = s;
|
||||
// Skip leading zeros.
|
||||
if d.num_digits == 0 {
|
||||
while let Some((&b'0', s_next)) = s.split_first() {
|
||||
s = s_next;
|
||||
}
|
||||
}
|
||||
while s.len() >= 8 && d.num_digits + 8 < Decimal::MAX_DIGITS {
|
||||
let v = s.read_u64();
|
||||
if !is_8digits(v) {
|
||||
break;
|
||||
}
|
||||
d.digits[d.num_digits..].write_u64(v - 0x3030_3030_3030_3030);
|
||||
d.num_digits += 8;
|
||||
s = &s[8..];
|
||||
}
|
||||
s = s.parse_digits(|digit| d.try_add_digit(digit));
|
||||
d.decimal_point = s.len() as i32 - first.len() as i32;
|
||||
}
|
||||
if d.num_digits != 0 {
|
||||
// Ignore the trailing zeros if there are any
|
||||
let mut n_trailing_zeros = 0;
|
||||
for &c in start[..(start.len() - s.len())].iter().rev() {
|
||||
if c == b'0' {
|
||||
n_trailing_zeros += 1;
|
||||
} else if c != b'.' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
d.decimal_point += n_trailing_zeros as i32;
|
||||
d.num_digits -= n_trailing_zeros;
|
||||
d.decimal_point += d.num_digits as i32;
|
||||
if d.num_digits > Decimal::MAX_DIGITS {
|
||||
d.truncated = true;
|
||||
d.num_digits = Decimal::MAX_DIGITS;
|
||||
}
|
||||
}
|
||||
if let Some((&ch, s_next)) = s.split_first() {
|
||||
if ch == b'e' || ch == b'E' {
|
||||
s = s_next;
|
||||
let mut neg_exp = false;
|
||||
if let Some((&ch, s_next)) = s.split_first() {
|
||||
neg_exp = ch == b'-';
|
||||
if ch == b'-' || ch == b'+' {
|
||||
s = s_next;
|
||||
}
|
||||
}
|
||||
let mut exp_num = 0_i32;
|
||||
|
||||
s.parse_digits(|digit| {
|
||||
if exp_num < 0x10000 {
|
||||
exp_num = 10 * exp_num + digit as i32;
|
||||
}
|
||||
});
|
||||
|
||||
d.decimal_point += if neg_exp { -exp_num } else { exp_num };
|
||||
}
|
||||
}
|
||||
for i in d.num_digits..Decimal::MAX_DIGITS_WITHOUT_OVERFLOW {
|
||||
d.digits[i] = 0;
|
||||
}
|
||||
d
|
||||
}
|
||||
|
||||
fn number_of_digits_decimal_left_shift(d: &Decimal, mut shift: usize) -> usize {
|
||||
#[rustfmt::skip]
|
||||
const TABLE: [u16; 65] = [
|
||||
0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, 0x181D, 0x2024,
|
||||
0x202B, 0x2033, 0x203C, 0x2846, 0x2850, 0x285B, 0x3067, 0x3073, 0x3080, 0x388E, 0x389C,
|
||||
0x38AB, 0x38BB, 0x40CC, 0x40DD, 0x40EF, 0x4902, 0x4915, 0x4929, 0x513E, 0x5153, 0x5169,
|
||||
0x5180, 0x5998, 0x59B0, 0x59C9, 0x61E3, 0x61FD, 0x6218, 0x6A34, 0x6A50, 0x6A6D, 0x6A8B,
|
||||
0x72AA, 0x72C9, 0x72E9, 0x7B0A, 0x7B2B, 0x7B4D, 0x8370, 0x8393, 0x83B7, 0x83DC, 0x8C02,
|
||||
0x8C28, 0x8C4F, 0x9477, 0x949F, 0x94C8, 0x9CF2, 0x051C, 0x051C, 0x051C, 0x051C,
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
const TABLE_POW5: [u8; 0x051C] = [
|
||||
5, 2, 5, 1, 2, 5, 6, 2, 5, 3, 1, 2, 5, 1, 5, 6, 2, 5, 7, 8, 1, 2, 5, 3, 9, 0, 6, 2, 5, 1,
|
||||
9, 5, 3, 1, 2, 5, 9, 7, 6, 5, 6, 2, 5, 4, 8, 8, 2, 8, 1, 2, 5, 2, 4, 4, 1, 4, 0, 6, 2, 5,
|
||||
1, 2, 2, 0, 7, 0, 3, 1, 2, 5, 6, 1, 0, 3, 5, 1, 5, 6, 2, 5, 3, 0, 5, 1, 7, 5, 7, 8, 1, 2,
|
||||
5, 1, 5, 2, 5, 8, 7, 8, 9, 0, 6, 2, 5, 7, 6, 2, 9, 3, 9, 4, 5, 3, 1, 2, 5, 3, 8, 1, 4, 6,
|
||||
9, 7, 2, 6, 5, 6, 2, 5, 1, 9, 0, 7, 3, 4, 8, 6, 3, 2, 8, 1, 2, 5, 9, 5, 3, 6, 7, 4, 3, 1,
|
||||
6, 4, 0, 6, 2, 5, 4, 7, 6, 8, 3, 7, 1, 5, 8, 2, 0, 3, 1, 2, 5, 2, 3, 8, 4, 1, 8, 5, 7, 9,
|
||||
1, 0, 1, 5, 6, 2, 5, 1, 1, 9, 2, 0, 9, 2, 8, 9, 5, 5, 0, 7, 8, 1, 2, 5, 5, 9, 6, 0, 4, 6,
|
||||
4, 4, 7, 7, 5, 3, 9, 0, 6, 2, 5, 2, 9, 8, 0, 2, 3, 2, 2, 3, 8, 7, 6, 9, 5, 3, 1, 2, 5, 1,
|
||||
4, 9, 0, 1, 1, 6, 1, 1, 9, 3, 8, 4, 7, 6, 5, 6, 2, 5, 7, 4, 5, 0, 5, 8, 0, 5, 9, 6, 9, 2,
|
||||
3, 8, 2, 8, 1, 2, 5, 3, 7, 2, 5, 2, 9, 0, 2, 9, 8, 4, 6, 1, 9, 1, 4, 0, 6, 2, 5, 1, 8, 6,
|
||||
2, 6, 4, 5, 1, 4, 9, 2, 3, 0, 9, 5, 7, 0, 3, 1, 2, 5, 9, 3, 1, 3, 2, 2, 5, 7, 4, 6, 1, 5,
|
||||
4, 7, 8, 5, 1, 5, 6, 2, 5, 4, 6, 5, 6, 6, 1, 2, 8, 7, 3, 0, 7, 7, 3, 9, 2, 5, 7, 8, 1, 2,
|
||||
5, 2, 3, 2, 8, 3, 0, 6, 4, 3, 6, 5, 3, 8, 6, 9, 6, 2, 8, 9, 0, 6, 2, 5, 1, 1, 6, 4, 1, 5,
|
||||
3, 2, 1, 8, 2, 6, 9, 3, 4, 8, 1, 4, 4, 5, 3, 1, 2, 5, 5, 8, 2, 0, 7, 6, 6, 0, 9, 1, 3, 4,
|
||||
6, 7, 4, 0, 7, 2, 2, 6, 5, 6, 2, 5, 2, 9, 1, 0, 3, 8, 3, 0, 4, 5, 6, 7, 3, 3, 7, 0, 3, 6,
|
||||
1, 3, 2, 8, 1, 2, 5, 1, 4, 5, 5, 1, 9, 1, 5, 2, 2, 8, 3, 6, 6, 8, 5, 1, 8, 0, 6, 6, 4, 0,
|
||||
6, 2, 5, 7, 2, 7, 5, 9, 5, 7, 6, 1, 4, 1, 8, 3, 4, 2, 5, 9, 0, 3, 3, 2, 0, 3, 1, 2, 5, 3,
|
||||
6, 3, 7, 9, 7, 8, 8, 0, 7, 0, 9, 1, 7, 1, 2, 9, 5, 1, 6, 6, 0, 1, 5, 6, 2, 5, 1, 8, 1, 8,
|
||||
9, 8, 9, 4, 0, 3, 5, 4, 5, 8, 5, 6, 4, 7, 5, 8, 3, 0, 0, 7, 8, 1, 2, 5, 9, 0, 9, 4, 9, 4,
|
||||
7, 0, 1, 7, 7, 2, 9, 2, 8, 2, 3, 7, 9, 1, 5, 0, 3, 9, 0, 6, 2, 5, 4, 5, 4, 7, 4, 7, 3, 5,
|
||||
0, 8, 8, 6, 4, 6, 4, 1, 1, 8, 9, 5, 7, 5, 1, 9, 5, 3, 1, 2, 5, 2, 2, 7, 3, 7, 3, 6, 7, 5,
|
||||
4, 4, 3, 2, 3, 2, 0, 5, 9, 4, 7, 8, 7, 5, 9, 7, 6, 5, 6, 2, 5, 1, 1, 3, 6, 8, 6, 8, 3, 7,
|
||||
7, 2, 1, 6, 1, 6, 0, 2, 9, 7, 3, 9, 3, 7, 9, 8, 8, 2, 8, 1, 2, 5, 5, 6, 8, 4, 3, 4, 1, 8,
|
||||
8, 6, 0, 8, 0, 8, 0, 1, 4, 8, 6, 9, 6, 8, 9, 9, 4, 1, 4, 0, 6, 2, 5, 2, 8, 4, 2, 1, 7, 0,
|
||||
9, 4, 3, 0, 4, 0, 4, 0, 0, 7, 4, 3, 4, 8, 4, 4, 9, 7, 0, 7, 0, 3, 1, 2, 5, 1, 4, 2, 1, 0,
|
||||
8, 5, 4, 7, 1, 5, 2, 0, 2, 0, 0, 3, 7, 1, 7, 4, 2, 2, 4, 8, 5, 3, 5, 1, 5, 6, 2, 5, 7, 1,
|
||||
0, 5, 4, 2, 7, 3, 5, 7, 6, 0, 1, 0, 0, 1, 8, 5, 8, 7, 1, 1, 2, 4, 2, 6, 7, 5, 7, 8, 1, 2,
|
||||
5, 3, 5, 5, 2, 7, 1, 3, 6, 7, 8, 8, 0, 0, 5, 0, 0, 9, 2, 9, 3, 5, 5, 6, 2, 1, 3, 3, 7, 8,
|
||||
9, 0, 6, 2, 5, 1, 7, 7, 6, 3, 5, 6, 8, 3, 9, 4, 0, 0, 2, 5, 0, 4, 6, 4, 6, 7, 7, 8, 1, 0,
|
||||
6, 6, 8, 9, 4, 5, 3, 1, 2, 5, 8, 8, 8, 1, 7, 8, 4, 1, 9, 7, 0, 0, 1, 2, 5, 2, 3, 2, 3, 3,
|
||||
8, 9, 0, 5, 3, 3, 4, 4, 7, 2, 6, 5, 6, 2, 5, 4, 4, 4, 0, 8, 9, 2, 0, 9, 8, 5, 0, 0, 6, 2,
|
||||
6, 1, 6, 1, 6, 9, 4, 5, 2, 6, 6, 7, 2, 3, 6, 3, 2, 8, 1, 2, 5, 2, 2, 2, 0, 4, 4, 6, 0, 4,
|
||||
9, 2, 5, 0, 3, 1, 3, 0, 8, 0, 8, 4, 7, 2, 6, 3, 3, 3, 6, 1, 8, 1, 6, 4, 0, 6, 2, 5, 1, 1,
|
||||
1, 0, 2, 2, 3, 0, 2, 4, 6, 2, 5, 1, 5, 6, 5, 4, 0, 4, 2, 3, 6, 3, 1, 6, 6, 8, 0, 9, 0, 8,
|
||||
2, 0, 3, 1, 2, 5, 5, 5, 5, 1, 1, 1, 5, 1, 2, 3, 1, 2, 5, 7, 8, 2, 7, 0, 2, 1, 1, 8, 1, 5,
|
||||
8, 3, 4, 0, 4, 5, 4, 1, 0, 1, 5, 6, 2, 5, 2, 7, 7, 5, 5, 5, 7, 5, 6, 1, 5, 6, 2, 8, 9, 1,
|
||||
3, 5, 1, 0, 5, 9, 0, 7, 9, 1, 7, 0, 2, 2, 7, 0, 5, 0, 7, 8, 1, 2, 5, 1, 3, 8, 7, 7, 7, 8,
|
||||
7, 8, 0, 7, 8, 1, 4, 4, 5, 6, 7, 5, 5, 2, 9, 5, 3, 9, 5, 8, 5, 1, 1, 3, 5, 2, 5, 3, 9, 0,
|
||||
6, 2, 5, 6, 9, 3, 8, 8, 9, 3, 9, 0, 3, 9, 0, 7, 2, 2, 8, 3, 7, 7, 6, 4, 7, 6, 9, 7, 9, 2,
|
||||
5, 5, 6, 7, 6, 2, 6, 9, 5, 3, 1, 2, 5, 3, 4, 6, 9, 4, 4, 6, 9, 5, 1, 9, 5, 3, 6, 1, 4, 1,
|
||||
8, 8, 8, 2, 3, 8, 4, 8, 9, 6, 2, 7, 8, 3, 8, 1, 3, 4, 7, 6, 5, 6, 2, 5, 1, 7, 3, 4, 7, 2,
|
||||
3, 4, 7, 5, 9, 7, 6, 8, 0, 7, 0, 9, 4, 4, 1, 1, 9, 2, 4, 4, 8, 1, 3, 9, 1, 9, 0, 6, 7, 3,
|
||||
8, 2, 8, 1, 2, 5, 8, 6, 7, 3, 6, 1, 7, 3, 7, 9, 8, 8, 4, 0, 3, 5, 4, 7, 2, 0, 5, 9, 6, 2,
|
||||
2, 4, 0, 6, 9, 5, 9, 5, 3, 3, 6, 9, 1, 4, 0, 6, 2, 5,
|
||||
];
|
||||
|
||||
shift &= 63;
|
||||
let x_a = TABLE[shift];
|
||||
let x_b = TABLE[shift + 1];
|
||||
let num_new_digits = (x_a >> 11) as _;
|
||||
let pow5_a = (0x7FF & x_a) as usize;
|
||||
let pow5_b = (0x7FF & x_b) as usize;
|
||||
let pow5 = &TABLE_POW5[pow5_a..];
|
||||
for (i, &p5) in pow5.iter().enumerate().take(pow5_b - pow5_a) {
|
||||
if i >= d.num_digits {
|
||||
return num_new_digits - 1;
|
||||
} else if d.digits[i] == p5 {
|
||||
continue;
|
||||
} else if d.digits[i] < p5 {
|
||||
return num_new_digits - 1;
|
||||
} else {
|
||||
return num_new_digits;
|
||||
}
|
||||
// disguised fast path
|
||||
let shift = self.exponent - F::MAX_EXPONENT_FAST_PATH;
|
||||
let mantissa = self.mantissa.checked_mul(INT_POW10[shift as usize])?;
|
||||
if mantissa > F::MAX_MANTISSA_FAST_PATH {
|
||||
return None;
|
||||
}
|
||||
F::from_u64(mantissa) * F::pow10_fast_path(F::MAX_EXPONENT_FAST_PATH as _)
|
||||
};
|
||||
|
||||
if self.negative { Some(-value) } else { Some(value) }
|
||||
}
|
||||
num_new_digits
|
||||
}
|
||||
|
|
379
library/core/src/num/dec2flt/decimal_seq.rs
Normal file
379
library/core/src/num/dec2flt/decimal_seq.rs
Normal file
|
@ -0,0 +1,379 @@
|
|||
//! Arbitrary-precision decimal type used by fallback algorithms.
|
||||
//!
|
||||
//! This is only used if the fast-path (native floats) and
|
||||
//! the Eisel-Lemire algorithm are unable to unambiguously
|
||||
//! determine the float.
|
||||
//!
|
||||
//! The technique used is "Simple Decimal Conversion", developed
|
||||
//! by Nigel Tao and Ken Thompson. A detailed description of the
|
||||
//! algorithm can be found in "ParseNumberF64 by Simple Decimal Conversion",
|
||||
//! available online: <https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html>.
|
||||
|
||||
use crate::num::dec2flt::common::{ByteSlice, is_8digits};
|
||||
|
||||
/// A decimal floating-point number, represented as a sequence of decimal digits.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DecimalSeq {
|
||||
/// The number of significant digits in the decimal.
|
||||
pub num_digits: usize,
|
||||
/// The offset of the decimal point in the significant digits.
|
||||
pub decimal_point: i32,
|
||||
/// If the number of significant digits stored in the decimal is truncated.
|
||||
pub truncated: bool,
|
||||
/// Buffer of the raw digits, in the range [0, 9].
|
||||
pub digits: [u8; Self::MAX_DIGITS],
|
||||
}
|
||||
|
||||
impl Default for DecimalSeq {
|
||||
fn default() -> Self {
|
||||
Self { num_digits: 0, decimal_point: 0, truncated: false, digits: [0; Self::MAX_DIGITS] }
|
||||
}
|
||||
}
|
||||
|
||||
impl DecimalSeq {
|
||||
/// The maximum number of digits required to unambiguously round up to a 64-bit float.
|
||||
///
|
||||
/// For an IEEE 754 binary64 float, this required 767 digits. So we store the max digits + 1.
|
||||
///
|
||||
/// We can exactly represent a float in radix `b` from radix 2 if
|
||||
/// `b` is divisible by 2. This function calculates the exact number of
|
||||
/// digits required to exactly represent that float.
|
||||
///
|
||||
/// According to the "Handbook of Floating Point Arithmetic",
|
||||
/// for IEEE754, with `emin` being the min exponent, `p2` being the
|
||||
/// precision, and `b` being the radix, the number of digits follows as:
|
||||
///
|
||||
/// `−emin + p2 + ⌊(emin + 1) log(2, b) − log(1 − 2^(−p2), b)⌋`
|
||||
///
|
||||
/// For f32, this follows as:
|
||||
/// emin = -126
|
||||
/// p2 = 24
|
||||
///
|
||||
/// For f64, this follows as:
|
||||
/// emin = -1022
|
||||
/// p2 = 53
|
||||
///
|
||||
/// In Python:
|
||||
/// `-emin + p2 + math.floor((emin+ 1)*math.log(2, b)-math.log(1-2**(-p2), b))`
|
||||
pub const MAX_DIGITS: usize = 768;
|
||||
|
||||
/// The max decimal digits that can be exactly represented in a 64-bit integer.
|
||||
pub(super) const MAX_DIGITS_WITHOUT_OVERFLOW: usize = 19;
|
||||
pub(super) const DECIMAL_POINT_RANGE: i32 = 2047;
|
||||
|
||||
/// Append a digit to the buffer if it fits.
|
||||
// FIXME(tgross35): it may be better for this to return an option
|
||||
// FIXME(tgross35): incrementing the digit counter even if we don't push anything
|
||||
// seems incorrect.
|
||||
pub(super) fn try_add_digit(&mut self, digit: u8) {
|
||||
if self.num_digits < Self::MAX_DIGITS {
|
||||
self.digits[self.num_digits] = digit;
|
||||
}
|
||||
self.num_digits += 1;
|
||||
}
|
||||
|
||||
/// Trim trailing zeros from the buffer.
|
||||
// FIXME(tgross35): this could be `.rev().position()` if perf is okay
|
||||
pub fn trim(&mut self) {
|
||||
// All of the following calls to `DecimalSeq::trim` can't panic because:
|
||||
//
|
||||
// 1. `parse_decimal` sets `num_digits` to a max of `DecimalSeq::MAX_DIGITS`.
|
||||
// 2. `right_shift` sets `num_digits` to `write_index`, which is bounded by `num_digits`.
|
||||
// 3. `left_shift` `num_digits` to a max of `DecimalSeq::MAX_DIGITS`.
|
||||
//
|
||||
// Trim is only called in `right_shift` and `left_shift`.
|
||||
debug_assert!(self.num_digits <= Self::MAX_DIGITS);
|
||||
while self.num_digits != 0 && self.digits[self.num_digits - 1] == 0 {
|
||||
self.num_digits -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn round(&self) -> u64 {
|
||||
if self.num_digits == 0 || self.decimal_point < 0 {
|
||||
return 0;
|
||||
} else if self.decimal_point >= Self::MAX_DIGITS_WITHOUT_OVERFLOW as i32 {
|
||||
return 0xFFFF_FFFF_FFFF_FFFF_u64;
|
||||
}
|
||||
|
||||
let dp = self.decimal_point as usize;
|
||||
let mut n = 0_u64;
|
||||
|
||||
for i in 0..dp {
|
||||
n *= 10;
|
||||
if i < self.num_digits {
|
||||
n += self.digits[i] as u64;
|
||||
}
|
||||
}
|
||||
|
||||
let mut round_up = false;
|
||||
|
||||
if dp < self.num_digits {
|
||||
round_up = self.digits[dp] >= 5;
|
||||
if self.digits[dp] == 5 && dp + 1 == self.num_digits {
|
||||
round_up = self.truncated || ((dp != 0) && (1 & self.digits[dp - 1] != 0))
|
||||
}
|
||||
}
|
||||
|
||||
if round_up {
|
||||
n += 1;
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
/// Computes decimal * 2^shift.
|
||||
pub(super) fn left_shift(&mut self, shift: usize) {
|
||||
if self.num_digits == 0 {
|
||||
return;
|
||||
}
|
||||
let num_new_digits = number_of_digits_decimal_left_shift(self, shift);
|
||||
let mut read_index = self.num_digits;
|
||||
let mut write_index = self.num_digits + num_new_digits;
|
||||
let mut n = 0_u64;
|
||||
|
||||
while read_index != 0 {
|
||||
read_index -= 1;
|
||||
write_index -= 1;
|
||||
n += (self.digits[read_index] as u64) << shift;
|
||||
let quotient = n / 10;
|
||||
let remainder = n - (10 * quotient);
|
||||
if write_index < Self::MAX_DIGITS {
|
||||
self.digits[write_index] = remainder as u8;
|
||||
} else if remainder > 0 {
|
||||
self.truncated = true;
|
||||
}
|
||||
n = quotient;
|
||||
}
|
||||
|
||||
while n > 0 {
|
||||
write_index -= 1;
|
||||
let quotient = n / 10;
|
||||
let remainder = n - (10 * quotient);
|
||||
if write_index < Self::MAX_DIGITS {
|
||||
self.digits[write_index] = remainder as u8;
|
||||
} else if remainder > 0 {
|
||||
self.truncated = true;
|
||||
}
|
||||
n = quotient;
|
||||
}
|
||||
|
||||
self.num_digits += num_new_digits;
|
||||
|
||||
if self.num_digits > Self::MAX_DIGITS {
|
||||
self.num_digits = Self::MAX_DIGITS;
|
||||
}
|
||||
|
||||
self.decimal_point += num_new_digits as i32;
|
||||
self.trim();
|
||||
}
|
||||
|
||||
/// Computes decimal * 2^-shift.
|
||||
pub(super) fn right_shift(&mut self, shift: usize) {
|
||||
let mut read_index = 0;
|
||||
let mut write_index = 0;
|
||||
let mut n = 0_u64;
|
||||
while (n >> shift) == 0 {
|
||||
if read_index < self.num_digits {
|
||||
n = (10 * n) + self.digits[read_index] as u64;
|
||||
read_index += 1;
|
||||
} else if n == 0 {
|
||||
return;
|
||||
} else {
|
||||
while (n >> shift) == 0 {
|
||||
n *= 10;
|
||||
read_index += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.decimal_point -= read_index as i32 - 1;
|
||||
if self.decimal_point < -Self::DECIMAL_POINT_RANGE {
|
||||
// `self = Self::Default()`, but without the overhead of clearing `digits`.
|
||||
self.num_digits = 0;
|
||||
self.decimal_point = 0;
|
||||
self.truncated = false;
|
||||
return;
|
||||
}
|
||||
let mask = (1_u64 << shift) - 1;
|
||||
while read_index < self.num_digits {
|
||||
let new_digit = (n >> shift) as u8;
|
||||
n = (10 * (n & mask)) + self.digits[read_index] as u64;
|
||||
read_index += 1;
|
||||
self.digits[write_index] = new_digit;
|
||||
write_index += 1;
|
||||
}
|
||||
while n > 0 {
|
||||
let new_digit = (n >> shift) as u8;
|
||||
n = 10 * (n & mask);
|
||||
if write_index < Self::MAX_DIGITS {
|
||||
self.digits[write_index] = new_digit;
|
||||
write_index += 1;
|
||||
} else if new_digit > 0 {
|
||||
self.truncated = true;
|
||||
}
|
||||
}
|
||||
self.num_digits = write_index;
|
||||
self.trim();
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a big integer representation of the float as a decimal.
|
||||
pub fn parse_decimal_seq(mut s: &[u8]) -> DecimalSeq {
|
||||
let mut d = DecimalSeq::default();
|
||||
let start = s;
|
||||
|
||||
while let Some((&b'0', s_next)) = s.split_first() {
|
||||
s = s_next;
|
||||
}
|
||||
|
||||
s = s.parse_digits(|digit| d.try_add_digit(digit));
|
||||
|
||||
if let Some((b'.', s_next)) = s.split_first() {
|
||||
s = s_next;
|
||||
let first = s;
|
||||
// Skip leading zeros.
|
||||
if d.num_digits == 0 {
|
||||
while let Some((&b'0', s_next)) = s.split_first() {
|
||||
s = s_next;
|
||||
}
|
||||
}
|
||||
while s.len() >= 8 && d.num_digits + 8 < DecimalSeq::MAX_DIGITS {
|
||||
let v = s.read_u64();
|
||||
if !is_8digits(v) {
|
||||
break;
|
||||
}
|
||||
d.digits[d.num_digits..].write_u64(v - 0x3030_3030_3030_3030);
|
||||
d.num_digits += 8;
|
||||
s = &s[8..];
|
||||
}
|
||||
s = s.parse_digits(|digit| d.try_add_digit(digit));
|
||||
d.decimal_point = s.len() as i32 - first.len() as i32;
|
||||
}
|
||||
|
||||
if d.num_digits != 0 {
|
||||
// Ignore the trailing zeros if there are any
|
||||
let mut n_trailing_zeros = 0;
|
||||
for &c in start[..(start.len() - s.len())].iter().rev() {
|
||||
if c == b'0' {
|
||||
n_trailing_zeros += 1;
|
||||
} else if c != b'.' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
d.decimal_point += n_trailing_zeros as i32;
|
||||
d.num_digits -= n_trailing_zeros;
|
||||
d.decimal_point += d.num_digits as i32;
|
||||
if d.num_digits > DecimalSeq::MAX_DIGITS {
|
||||
d.truncated = true;
|
||||
d.num_digits = DecimalSeq::MAX_DIGITS;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((&ch, s_next)) = s.split_first() {
|
||||
if ch == b'e' || ch == b'E' {
|
||||
s = s_next;
|
||||
let mut neg_exp = false;
|
||||
if let Some((&ch, s_next)) = s.split_first() {
|
||||
neg_exp = ch == b'-';
|
||||
if ch == b'-' || ch == b'+' {
|
||||
s = s_next;
|
||||
}
|
||||
}
|
||||
let mut exp_num = 0_i32;
|
||||
|
||||
s.parse_digits(|digit| {
|
||||
if exp_num < 0x10000 {
|
||||
exp_num = 10 * exp_num + digit as i32;
|
||||
}
|
||||
});
|
||||
|
||||
d.decimal_point += if neg_exp { -exp_num } else { exp_num };
|
||||
}
|
||||
}
|
||||
|
||||
for i in d.num_digits..DecimalSeq::MAX_DIGITS_WITHOUT_OVERFLOW {
|
||||
d.digits[i] = 0;
|
||||
}
|
||||
|
||||
d
|
||||
}
|
||||
|
||||
fn number_of_digits_decimal_left_shift(d: &DecimalSeq, mut shift: usize) -> usize {
|
||||
#[rustfmt::skip]
|
||||
const TABLE: [u16; 65] = [
|
||||
0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, 0x181D, 0x2024,
|
||||
0x202B, 0x2033, 0x203C, 0x2846, 0x2850, 0x285B, 0x3067, 0x3073, 0x3080, 0x388E, 0x389C,
|
||||
0x38AB, 0x38BB, 0x40CC, 0x40DD, 0x40EF, 0x4902, 0x4915, 0x4929, 0x513E, 0x5153, 0x5169,
|
||||
0x5180, 0x5998, 0x59B0, 0x59C9, 0x61E3, 0x61FD, 0x6218, 0x6A34, 0x6A50, 0x6A6D, 0x6A8B,
|
||||
0x72AA, 0x72C9, 0x72E9, 0x7B0A, 0x7B2B, 0x7B4D, 0x8370, 0x8393, 0x83B7, 0x83DC, 0x8C02,
|
||||
0x8C28, 0x8C4F, 0x9477, 0x949F, 0x94C8, 0x9CF2, 0x051C, 0x051C, 0x051C, 0x051C,
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
const TABLE_POW5: [u8; 0x051C] = [
|
||||
5, 2, 5, 1, 2, 5, 6, 2, 5, 3, 1, 2, 5, 1, 5, 6, 2, 5, 7, 8, 1, 2, 5, 3, 9, 0, 6, 2, 5, 1,
|
||||
9, 5, 3, 1, 2, 5, 9, 7, 6, 5, 6, 2, 5, 4, 8, 8, 2, 8, 1, 2, 5, 2, 4, 4, 1, 4, 0, 6, 2, 5,
|
||||
1, 2, 2, 0, 7, 0, 3, 1, 2, 5, 6, 1, 0, 3, 5, 1, 5, 6, 2, 5, 3, 0, 5, 1, 7, 5, 7, 8, 1, 2,
|
||||
5, 1, 5, 2, 5, 8, 7, 8, 9, 0, 6, 2, 5, 7, 6, 2, 9, 3, 9, 4, 5, 3, 1, 2, 5, 3, 8, 1, 4, 6,
|
||||
9, 7, 2, 6, 5, 6, 2, 5, 1, 9, 0, 7, 3, 4, 8, 6, 3, 2, 8, 1, 2, 5, 9, 5, 3, 6, 7, 4, 3, 1,
|
||||
6, 4, 0, 6, 2, 5, 4, 7, 6, 8, 3, 7, 1, 5, 8, 2, 0, 3, 1, 2, 5, 2, 3, 8, 4, 1, 8, 5, 7, 9,
|
||||
1, 0, 1, 5, 6, 2, 5, 1, 1, 9, 2, 0, 9, 2, 8, 9, 5, 5, 0, 7, 8, 1, 2, 5, 5, 9, 6, 0, 4, 6,
|
||||
4, 4, 7, 7, 5, 3, 9, 0, 6, 2, 5, 2, 9, 8, 0, 2, 3, 2, 2, 3, 8, 7, 6, 9, 5, 3, 1, 2, 5, 1,
|
||||
4, 9, 0, 1, 1, 6, 1, 1, 9, 3, 8, 4, 7, 6, 5, 6, 2, 5, 7, 4, 5, 0, 5, 8, 0, 5, 9, 6, 9, 2,
|
||||
3, 8, 2, 8, 1, 2, 5, 3, 7, 2, 5, 2, 9, 0, 2, 9, 8, 4, 6, 1, 9, 1, 4, 0, 6, 2, 5, 1, 8, 6,
|
||||
2, 6, 4, 5, 1, 4, 9, 2, 3, 0, 9, 5, 7, 0, 3, 1, 2, 5, 9, 3, 1, 3, 2, 2, 5, 7, 4, 6, 1, 5,
|
||||
4, 7, 8, 5, 1, 5, 6, 2, 5, 4, 6, 5, 6, 6, 1, 2, 8, 7, 3, 0, 7, 7, 3, 9, 2, 5, 7, 8, 1, 2,
|
||||
5, 2, 3, 2, 8, 3, 0, 6, 4, 3, 6, 5, 3, 8, 6, 9, 6, 2, 8, 9, 0, 6, 2, 5, 1, 1, 6, 4, 1, 5,
|
||||
3, 2, 1, 8, 2, 6, 9, 3, 4, 8, 1, 4, 4, 5, 3, 1, 2, 5, 5, 8, 2, 0, 7, 6, 6, 0, 9, 1, 3, 4,
|
||||
6, 7, 4, 0, 7, 2, 2, 6, 5, 6, 2, 5, 2, 9, 1, 0, 3, 8, 3, 0, 4, 5, 6, 7, 3, 3, 7, 0, 3, 6,
|
||||
1, 3, 2, 8, 1, 2, 5, 1, 4, 5, 5, 1, 9, 1, 5, 2, 2, 8, 3, 6, 6, 8, 5, 1, 8, 0, 6, 6, 4, 0,
|
||||
6, 2, 5, 7, 2, 7, 5, 9, 5, 7, 6, 1, 4, 1, 8, 3, 4, 2, 5, 9, 0, 3, 3, 2, 0, 3, 1, 2, 5, 3,
|
||||
6, 3, 7, 9, 7, 8, 8, 0, 7, 0, 9, 1, 7, 1, 2, 9, 5, 1, 6, 6, 0, 1, 5, 6, 2, 5, 1, 8, 1, 8,
|
||||
9, 8, 9, 4, 0, 3, 5, 4, 5, 8, 5, 6, 4, 7, 5, 8, 3, 0, 0, 7, 8, 1, 2, 5, 9, 0, 9, 4, 9, 4,
|
||||
7, 0, 1, 7, 7, 2, 9, 2, 8, 2, 3, 7, 9, 1, 5, 0, 3, 9, 0, 6, 2, 5, 4, 5, 4, 7, 4, 7, 3, 5,
|
||||
0, 8, 8, 6, 4, 6, 4, 1, 1, 8, 9, 5, 7, 5, 1, 9, 5, 3, 1, 2, 5, 2, 2, 7, 3, 7, 3, 6, 7, 5,
|
||||
4, 4, 3, 2, 3, 2, 0, 5, 9, 4, 7, 8, 7, 5, 9, 7, 6, 5, 6, 2, 5, 1, 1, 3, 6, 8, 6, 8, 3, 7,
|
||||
7, 2, 1, 6, 1, 6, 0, 2, 9, 7, 3, 9, 3, 7, 9, 8, 8, 2, 8, 1, 2, 5, 5, 6, 8, 4, 3, 4, 1, 8,
|
||||
8, 6, 0, 8, 0, 8, 0, 1, 4, 8, 6, 9, 6, 8, 9, 9, 4, 1, 4, 0, 6, 2, 5, 2, 8, 4, 2, 1, 7, 0,
|
||||
9, 4, 3, 0, 4, 0, 4, 0, 0, 7, 4, 3, 4, 8, 4, 4, 9, 7, 0, 7, 0, 3, 1, 2, 5, 1, 4, 2, 1, 0,
|
||||
8, 5, 4, 7, 1, 5, 2, 0, 2, 0, 0, 3, 7, 1, 7, 4, 2, 2, 4, 8, 5, 3, 5, 1, 5, 6, 2, 5, 7, 1,
|
||||
0, 5, 4, 2, 7, 3, 5, 7, 6, 0, 1, 0, 0, 1, 8, 5, 8, 7, 1, 1, 2, 4, 2, 6, 7, 5, 7, 8, 1, 2,
|
||||
5, 3, 5, 5, 2, 7, 1, 3, 6, 7, 8, 8, 0, 0, 5, 0, 0, 9, 2, 9, 3, 5, 5, 6, 2, 1, 3, 3, 7, 8,
|
||||
9, 0, 6, 2, 5, 1, 7, 7, 6, 3, 5, 6, 8, 3, 9, 4, 0, 0, 2, 5, 0, 4, 6, 4, 6, 7, 7, 8, 1, 0,
|
||||
6, 6, 8, 9, 4, 5, 3, 1, 2, 5, 8, 8, 8, 1, 7, 8, 4, 1, 9, 7, 0, 0, 1, 2, 5, 2, 3, 2, 3, 3,
|
||||
8, 9, 0, 5, 3, 3, 4, 4, 7, 2, 6, 5, 6, 2, 5, 4, 4, 4, 0, 8, 9, 2, 0, 9, 8, 5, 0, 0, 6, 2,
|
||||
6, 1, 6, 1, 6, 9, 4, 5, 2, 6, 6, 7, 2, 3, 6, 3, 2, 8, 1, 2, 5, 2, 2, 2, 0, 4, 4, 6, 0, 4,
|
||||
9, 2, 5, 0, 3, 1, 3, 0, 8, 0, 8, 4, 7, 2, 6, 3, 3, 3, 6, 1, 8, 1, 6, 4, 0, 6, 2, 5, 1, 1,
|
||||
1, 0, 2, 2, 3, 0, 2, 4, 6, 2, 5, 1, 5, 6, 5, 4, 0, 4, 2, 3, 6, 3, 1, 6, 6, 8, 0, 9, 0, 8,
|
||||
2, 0, 3, 1, 2, 5, 5, 5, 5, 1, 1, 1, 5, 1, 2, 3, 1, 2, 5, 7, 8, 2, 7, 0, 2, 1, 1, 8, 1, 5,
|
||||
8, 3, 4, 0, 4, 5, 4, 1, 0, 1, 5, 6, 2, 5, 2, 7, 7, 5, 5, 5, 7, 5, 6, 1, 5, 6, 2, 8, 9, 1,
|
||||
3, 5, 1, 0, 5, 9, 0, 7, 9, 1, 7, 0, 2, 2, 7, 0, 5, 0, 7, 8, 1, 2, 5, 1, 3, 8, 7, 7, 7, 8,
|
||||
7, 8, 0, 7, 8, 1, 4, 4, 5, 6, 7, 5, 5, 2, 9, 5, 3, 9, 5, 8, 5, 1, 1, 3, 5, 2, 5, 3, 9, 0,
|
||||
6, 2, 5, 6, 9, 3, 8, 8, 9, 3, 9, 0, 3, 9, 0, 7, 2, 2, 8, 3, 7, 7, 6, 4, 7, 6, 9, 7, 9, 2,
|
||||
5, 5, 6, 7, 6, 2, 6, 9, 5, 3, 1, 2, 5, 3, 4, 6, 9, 4, 4, 6, 9, 5, 1, 9, 5, 3, 6, 1, 4, 1,
|
||||
8, 8, 8, 2, 3, 8, 4, 8, 9, 6, 2, 7, 8, 3, 8, 1, 3, 4, 7, 6, 5, 6, 2, 5, 1, 7, 3, 4, 7, 2,
|
||||
3, 4, 7, 5, 9, 7, 6, 8, 0, 7, 0, 9, 4, 4, 1, 1, 9, 2, 4, 4, 8, 1, 3, 9, 1, 9, 0, 6, 7, 3,
|
||||
8, 2, 8, 1, 2, 5, 8, 6, 7, 3, 6, 1, 7, 3, 7, 9, 8, 8, 4, 0, 3, 5, 4, 7, 2, 0, 5, 9, 6, 2,
|
||||
2, 4, 0, 6, 9, 5, 9, 5, 3, 3, 6, 9, 1, 4, 0, 6, 2, 5,
|
||||
];
|
||||
|
||||
shift &= 63;
|
||||
let x_a = TABLE[shift];
|
||||
let x_b = TABLE[shift + 1];
|
||||
let num_new_digits = (x_a >> 11) as _;
|
||||
let pow5_a = (0x7FF & x_a) as usize;
|
||||
let pow5_b = (0x7FF & x_b) as usize;
|
||||
let pow5 = &TABLE_POW5[pow5_a..];
|
||||
|
||||
for (i, &p5) in pow5.iter().enumerate().take(pow5_b - pow5_a) {
|
||||
if i >= d.num_digits {
|
||||
return num_new_digits - 1;
|
||||
} else if d.digits[i] == p5 {
|
||||
continue;
|
||||
} else if d.digits[i] < p5 {
|
||||
return num_new_digits - 1;
|
||||
} else {
|
||||
return num_new_digits;
|
||||
}
|
||||
}
|
||||
|
||||
num_new_digits
|
||||
}
|
|
@ -1,14 +1,57 @@
|
|||
//! Helper trait for generic float types.
|
||||
|
||||
use core::f64;
|
||||
|
||||
use crate::fmt::{Debug, LowerExp};
|
||||
use crate::num::FpCategory;
|
||||
use crate::ops::{Add, Div, Mul, Neg};
|
||||
use crate::ops::{self, Add, Div, Mul, Neg};
|
||||
|
||||
/// A helper trait to avoid duplicating basically all the conversion code for `f32` and `f64`.
|
||||
/// Lossy `as` casting between two types.
|
||||
pub trait CastInto<T: Copy>: Copy {
|
||||
fn cast(self) -> T;
|
||||
}
|
||||
|
||||
/// Collection of traits that allow us to be generic over integer size.
|
||||
pub trait Integer:
|
||||
Sized
|
||||
+ Clone
|
||||
+ Copy
|
||||
+ Debug
|
||||
+ ops::Shr<u32, Output = Self>
|
||||
+ ops::Shl<u32, Output = Self>
|
||||
+ ops::BitAnd<Output = Self>
|
||||
+ ops::BitOr<Output = Self>
|
||||
+ PartialEq
|
||||
+ CastInto<i16>
|
||||
{
|
||||
const ZERO: Self;
|
||||
const ONE: Self;
|
||||
}
|
||||
|
||||
macro_rules! int {
|
||||
($($ty:ty),+) => {
|
||||
$(
|
||||
impl CastInto<i16> for $ty {
|
||||
fn cast(self) -> i16 {
|
||||
self as i16
|
||||
}
|
||||
}
|
||||
|
||||
impl Integer for $ty {
|
||||
const ZERO: Self = 0;
|
||||
const ONE: Self = 1;
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
int!(u32, u64);
|
||||
|
||||
/// A helper trait to avoid duplicating basically all the conversion code for IEEE floats.
|
||||
///
|
||||
/// See the parent module's doc comment for why this is necessary.
|
||||
///
|
||||
/// Should **never ever** be implemented for other types or be used outside the dec2flt module.
|
||||
/// Should **never ever** be implemented for other types or be used outside the `dec2flt` module.
|
||||
#[doc(hidden)]
|
||||
pub trait RawFloat:
|
||||
Sized
|
||||
|
@ -24,62 +67,107 @@ pub trait RawFloat:
|
|||
+ Copy
|
||||
+ Debug
|
||||
{
|
||||
/// The unsigned integer with the same size as the float
|
||||
type Int: Integer + Into<u64>;
|
||||
|
||||
/* general constants */
|
||||
|
||||
const INFINITY: Self;
|
||||
const NEG_INFINITY: Self;
|
||||
const NAN: Self;
|
||||
const NEG_NAN: Self;
|
||||
|
||||
/// The number of bits in the significand, *excluding* the hidden bit.
|
||||
const MANTISSA_EXPLICIT_BITS: usize;
|
||||
/// Bit width of the float
|
||||
const BITS: u32;
|
||||
|
||||
// Round-to-even only happens for negative values of q
|
||||
// when q ≥ −4 in the 64-bit case and when q ≥ −17 in
|
||||
// the 32-bitcase.
|
||||
//
|
||||
// When q ≥ 0,we have that 5^q ≤ 2m+1. In the 64-bit case,we
|
||||
// have 5^q ≤ 2m+1 ≤ 2^54 or q ≤ 23. In the 32-bit case,we have
|
||||
// 5^q ≤ 2m+1 ≤ 2^25 or q ≤ 10.
|
||||
//
|
||||
// When q < 0, we have w ≥ (2m+1)×5^−q. We must have that w < 2^64
|
||||
// so (2m+1)×5^−q < 2^64. We have that 2m+1 > 2^53 (64-bit case)
|
||||
// or 2m+1 > 2^24 (32-bit case). Hence,we must have 2^53×5^−q < 2^64
|
||||
// (64-bit) and 2^24×5^−q < 2^64 (32-bit). Hence we have 5^−q < 2^11
|
||||
// or q ≥ −4 (64-bit case) and 5^−q < 2^40 or q ≥ −17 (32-bitcase).
|
||||
//
|
||||
// Thus we have that we only need to round ties to even when
|
||||
// we have that q ∈ [−4,23](in the 64-bit case) or q∈[−17,10]
|
||||
// (in the 32-bit case). In both cases,the power of five(5^|q|)
|
||||
// fits in a 64-bit word.
|
||||
/// The number of bits in the significand, *including* the hidden bit.
|
||||
const SIG_TOTAL_BITS: u32;
|
||||
|
||||
const EXP_MASK: Self::Int;
|
||||
const SIG_MASK: Self::Int;
|
||||
|
||||
/// The number of bits in the significand, *excluding* the hidden bit.
|
||||
const SIG_BITS: u32 = Self::SIG_TOTAL_BITS - 1;
|
||||
|
||||
/// Number of bits in the exponent.
|
||||
const EXP_BITS: u32 = Self::BITS - Self::SIG_BITS - 1;
|
||||
|
||||
/// The saturated (maximum bitpattern) value of the exponent, i.e. the infinite
|
||||
/// representation.
|
||||
///
|
||||
/// This shifted fully right, use `EXP_MASK` for the shifted value.
|
||||
const EXP_SAT: u32 = (1 << Self::EXP_BITS) - 1;
|
||||
|
||||
/// Signed version of `EXP_SAT` since we convert a lot.
|
||||
const INFINITE_POWER: i32 = Self::EXP_SAT as i32;
|
||||
|
||||
/// The exponent bias value. This is also the maximum value of the exponent.
|
||||
const EXP_BIAS: u32 = Self::EXP_SAT >> 1;
|
||||
|
||||
/// Minimum exponent value of normal values.
|
||||
const EXP_MIN: i32 = -(Self::EXP_BIAS as i32 - 1);
|
||||
|
||||
/// Round-to-even only happens for negative values of q
|
||||
/// when q ≥ −4 in the 64-bit case and when q ≥ −17 in
|
||||
/// the 32-bitcase.
|
||||
///
|
||||
/// When q ≥ 0,we have that 5^q ≤ 2m+1. In the 64-bit case,we
|
||||
/// have 5^q ≤ 2m+1 ≤ 2^54 or q ≤ 23. In the 32-bit case,we have
|
||||
/// 5^q ≤ 2m+1 ≤ 2^25 or q ≤ 10.
|
||||
///
|
||||
/// When q < 0, we have w ≥ (2m+1)×5^−q. We must have that w < 2^64
|
||||
/// so (2m+1)×5^−q < 2^64. We have that 2m+1 > 2^53 (64-bit case)
|
||||
/// or 2m+1 > 2^24 (32-bit case). Hence,we must have 2^53×5^−q < 2^64
|
||||
/// (64-bit) and 2^24×5^−q < 2^64 (32-bit). Hence we have 5^−q < 2^11
|
||||
/// or q ≥ −4 (64-bit case) and 5^−q < 2^40 or q ≥ −17 (32-bitcase).
|
||||
///
|
||||
/// Thus we have that we only need to round ties to even when
|
||||
/// we have that q ∈ [−4,23](in the 64-bit case) or q∈[−17,10]
|
||||
/// (in the 32-bit case). In both cases,the power of five(5^|q|)
|
||||
/// fits in a 64-bit word.
|
||||
const MIN_EXPONENT_ROUND_TO_EVEN: i32;
|
||||
const MAX_EXPONENT_ROUND_TO_EVEN: i32;
|
||||
|
||||
// Minimum exponent that for a fast path case, or `-⌊(MANTISSA_EXPLICIT_BITS+1)/log2(5)⌋`
|
||||
const MIN_EXPONENT_FAST_PATH: i64;
|
||||
/* limits related to Fast pathing */
|
||||
|
||||
// Maximum exponent that for a fast path case, or `⌊(MANTISSA_EXPLICIT_BITS+1)/log2(5)⌋`
|
||||
const MAX_EXPONENT_FAST_PATH: i64;
|
||||
/// Largest decimal exponent for a non-infinite value.
|
||||
///
|
||||
/// This is the max exponent in binary converted to the max exponent in decimal. Allows fast
|
||||
/// pathing anything larger than `10^LARGEST_POWER_OF_TEN`, which will round to infinity.
|
||||
const LARGEST_POWER_OF_TEN: i32 = {
|
||||
let largest_pow2 = Self::EXP_BIAS + 1;
|
||||
pow2_to_pow10(largest_pow2 as i64) as i32
|
||||
};
|
||||
|
||||
// Maximum exponent that can be represented for a disguised-fast path case.
|
||||
// This is `MAX_EXPONENT_FAST_PATH + ⌊(MANTISSA_EXPLICIT_BITS+1)/log2(10)⌋`
|
||||
const MAX_EXPONENT_DISGUISED_FAST_PATH: i64;
|
||||
|
||||
// Minimum exponent value `-(1 << (EXP_BITS - 1)) + 1`.
|
||||
const MINIMUM_EXPONENT: i32;
|
||||
|
||||
// Largest exponent value `(1 << EXP_BITS) - 1`.
|
||||
const INFINITE_POWER: i32;
|
||||
|
||||
// Index (in bits) of the sign.
|
||||
const SIGN_INDEX: usize;
|
||||
|
||||
// Smallest decimal exponent for a non-zero value.
|
||||
/// Smallest decimal exponent for a non-zero value. This allows for fast pathing anything
|
||||
/// smaller than `10^SMALLEST_POWER_OF_TEN`, which will round to zero.
|
||||
///
|
||||
/// The smallest power of ten is represented by `⌊log10(2^-n / (2^64 - 1))⌋`, where `n` is
|
||||
/// the smallest power of two. The `2^64 - 1)` denomenator comes from the number of values
|
||||
/// that are representable by the intermediate storage format. I don't actually know _why_
|
||||
/// the storage format is relevant here.
|
||||
///
|
||||
/// The values may be calculated using the formula. Unfortunately we cannot calculate them at
|
||||
/// compile time since intermediates exceed the range of an `f64`.
|
||||
const SMALLEST_POWER_OF_TEN: i32;
|
||||
|
||||
// Largest decimal exponent for a non-infinite value.
|
||||
const LARGEST_POWER_OF_TEN: i32;
|
||||
/// Maximum exponent for a fast path case, or `⌊(SIG_BITS+1)/log2(5)⌋`
|
||||
// assuming FLT_EVAL_METHOD = 0
|
||||
const MAX_EXPONENT_FAST_PATH: i64 = {
|
||||
let log2_5 = f64::consts::LOG2_10 - 1.0;
|
||||
(Self::SIG_TOTAL_BITS as f64 / log2_5) as i64
|
||||
};
|
||||
|
||||
// Maximum mantissa for the fast-path (`1 << 53` for f64).
|
||||
const MAX_MANTISSA_FAST_PATH: u64 = 2_u64 << Self::MANTISSA_EXPLICIT_BITS;
|
||||
/// Minimum exponent for a fast path case, or `-⌊(SIG_BITS+1)/log2(5)⌋`
|
||||
const MIN_EXPONENT_FAST_PATH: i64 = -Self::MAX_EXPONENT_FAST_PATH;
|
||||
|
||||
/// Maximum exponent that can be represented for a disguised-fast path case.
|
||||
/// This is `MAX_EXPONENT_FAST_PATH + ⌊(SIG_BITS+1)/log2(10)⌋`
|
||||
const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 =
|
||||
Self::MAX_EXPONENT_FAST_PATH + (Self::SIG_TOTAL_BITS as f64 / f64::consts::LOG2_10) as i64;
|
||||
|
||||
/// Maximum mantissa for the fast-path (`1 << 53` for f64).
|
||||
const MAX_MANTISSA_FAST_PATH: u64 = 1 << Self::SIG_TOTAL_BITS;
|
||||
|
||||
/// Converts integer into float through an as cast.
|
||||
/// This is only called in the fast-path algorithm, and therefore
|
||||
|
@ -96,27 +184,51 @@ pub trait RawFloat:
|
|||
/// Returns the category that this number falls into.
|
||||
fn classify(self) -> FpCategory;
|
||||
|
||||
/// Transmute to the integer representation
|
||||
fn to_bits(self) -> Self::Int;
|
||||
|
||||
/// Returns the mantissa, exponent and sign as integers.
|
||||
fn integer_decode(self) -> (u64, i16, i8);
|
||||
///
|
||||
/// That is, this returns `(m, p, s)` such that `s * m * 2^p` represents the original float.
|
||||
/// For 0, the exponent will be `-(EXP_BIAS + SIG_BITS`, which is the
|
||||
/// minimum subnormal power.
|
||||
fn integer_decode(self) -> (u64, i16, i8) {
|
||||
let bits = self.to_bits();
|
||||
let sign: i8 = if bits >> (Self::BITS - 1) == Self::Int::ZERO { 1 } else { -1 };
|
||||
let mut exponent: i16 = ((bits & Self::EXP_MASK) >> Self::SIG_BITS).cast();
|
||||
let mantissa = if exponent == 0 {
|
||||
(bits & Self::SIG_MASK) << 1
|
||||
} else {
|
||||
(bits & Self::SIG_MASK) | (Self::Int::ONE << Self::SIG_BITS)
|
||||
};
|
||||
// Exponent bias + mantissa shift
|
||||
exponent -= (Self::EXP_BIAS + Self::SIG_BITS) as i16;
|
||||
(mantissa.into(), exponent, sign)
|
||||
}
|
||||
}
|
||||
|
||||
/// Solve for `b` in `10^b = 2^a`
|
||||
const fn pow2_to_pow10(a: i64) -> i64 {
|
||||
let res = (a as f64) / f64::consts::LOG2_10;
|
||||
res as i64
|
||||
}
|
||||
|
||||
impl RawFloat for f32 {
|
||||
type Int = u32;
|
||||
|
||||
const INFINITY: Self = f32::INFINITY;
|
||||
const NEG_INFINITY: Self = f32::NEG_INFINITY;
|
||||
const NAN: Self = f32::NAN;
|
||||
const NEG_NAN: Self = -f32::NAN;
|
||||
|
||||
const MANTISSA_EXPLICIT_BITS: usize = 23;
|
||||
const BITS: u32 = 32;
|
||||
const SIG_TOTAL_BITS: u32 = Self::MANTISSA_DIGITS;
|
||||
const EXP_MASK: Self::Int = Self::EXP_MASK;
|
||||
const SIG_MASK: Self::Int = Self::MAN_MASK;
|
||||
|
||||
const MIN_EXPONENT_ROUND_TO_EVEN: i32 = -17;
|
||||
const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 10;
|
||||
const MIN_EXPONENT_FAST_PATH: i64 = -10; // assuming FLT_EVAL_METHOD = 0
|
||||
const MAX_EXPONENT_FAST_PATH: i64 = 10;
|
||||
const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 = 17;
|
||||
const MINIMUM_EXPONENT: i32 = -127;
|
||||
const INFINITE_POWER: i32 = 0xFF;
|
||||
const SIGN_INDEX: usize = 31;
|
||||
const SMALLEST_POWER_OF_TEN: i32 = -65;
|
||||
const LARGEST_POWER_OF_TEN: i32 = 38;
|
||||
|
||||
#[inline]
|
||||
fn from_u64(v: u64) -> Self {
|
||||
|
@ -136,16 +248,8 @@ impl RawFloat for f32 {
|
|||
TABLE[exponent & 15]
|
||||
}
|
||||
|
||||
/// Returns the mantissa, exponent and sign as integers.
|
||||
fn integer_decode(self) -> (u64, i16, i8) {
|
||||
let bits = self.to_bits();
|
||||
let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 };
|
||||
let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
|
||||
let mantissa =
|
||||
if exponent == 0 { (bits & 0x7fffff) << 1 } else { (bits & 0x7fffff) | 0x800000 };
|
||||
// Exponent bias + mantissa shift
|
||||
exponent -= 127 + 23;
|
||||
(mantissa as u64, exponent, sign)
|
||||
fn to_bits(self) -> Self::Int {
|
||||
self.to_bits()
|
||||
}
|
||||
|
||||
fn classify(self) -> FpCategory {
|
||||
|
@ -154,22 +258,21 @@ impl RawFloat for f32 {
|
|||
}
|
||||
|
||||
impl RawFloat for f64 {
|
||||
const INFINITY: Self = f64::INFINITY;
|
||||
const NEG_INFINITY: Self = f64::NEG_INFINITY;
|
||||
const NAN: Self = f64::NAN;
|
||||
const NEG_NAN: Self = -f64::NAN;
|
||||
type Int = u64;
|
||||
|
||||
const INFINITY: Self = Self::INFINITY;
|
||||
const NEG_INFINITY: Self = Self::NEG_INFINITY;
|
||||
const NAN: Self = Self::NAN;
|
||||
const NEG_NAN: Self = -Self::NAN;
|
||||
|
||||
const BITS: u32 = 64;
|
||||
const SIG_TOTAL_BITS: u32 = Self::MANTISSA_DIGITS;
|
||||
const EXP_MASK: Self::Int = Self::EXP_MASK;
|
||||
const SIG_MASK: Self::Int = Self::MAN_MASK;
|
||||
|
||||
const MANTISSA_EXPLICIT_BITS: usize = 52;
|
||||
const MIN_EXPONENT_ROUND_TO_EVEN: i32 = -4;
|
||||
const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 23;
|
||||
const MIN_EXPONENT_FAST_PATH: i64 = -22; // assuming FLT_EVAL_METHOD = 0
|
||||
const MAX_EXPONENT_FAST_PATH: i64 = 22;
|
||||
const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 = 37;
|
||||
const MINIMUM_EXPONENT: i32 = -1023;
|
||||
const INFINITE_POWER: i32 = 0x7FF;
|
||||
const SIGN_INDEX: usize = 63;
|
||||
const SMALLEST_POWER_OF_TEN: i32 = -342;
|
||||
const LARGEST_POWER_OF_TEN: i32 = 308;
|
||||
|
||||
#[inline]
|
||||
fn from_u64(v: u64) -> Self {
|
||||
|
@ -190,19 +293,8 @@ impl RawFloat for f64 {
|
|||
TABLE[exponent & 31]
|
||||
}
|
||||
|
||||
/// Returns the mantissa, exponent and sign as integers.
|
||||
fn integer_decode(self) -> (u64, i16, i8) {
|
||||
let bits = self.to_bits();
|
||||
let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 };
|
||||
let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
|
||||
let mantissa = if exponent == 0 {
|
||||
(bits & 0xfffffffffffff) << 1
|
||||
} else {
|
||||
(bits & 0xfffffffffffff) | 0x10000000000000
|
||||
};
|
||||
// Exponent bias + mantissa shift
|
||||
exponent -= 1023 + 52;
|
||||
(mantissa, exponent, sign)
|
||||
fn to_bits(self) -> Self::Int {
|
||||
self.to_bits()
|
||||
}
|
||||
|
||||
fn classify(self) -> FpCategory {
|
||||
|
|
|
@ -38,7 +38,7 @@ pub fn compute_float<F: RawFloat>(q: i64, mut w: u64) -> BiasedFp {
|
|||
// Normalize our significant digits, so the most-significant bit is set.
|
||||
let lz = w.leading_zeros();
|
||||
w <<= lz;
|
||||
let (lo, hi) = compute_product_approx(q, w, F::MANTISSA_EXPLICIT_BITS + 3);
|
||||
let (lo, hi) = compute_product_approx(q, w, F::SIG_BITS as usize + 3);
|
||||
if lo == 0xFFFF_FFFF_FFFF_FFFF {
|
||||
// If we have failed to approximate w x 5^-q with our 128-bit value.
|
||||
// Since the addition of 1 could lead to an overflow which could then
|
||||
|
@ -61,8 +61,8 @@ pub fn compute_float<F: RawFloat>(q: i64, mut w: u64) -> BiasedFp {
|
|||
}
|
||||
}
|
||||
let upperbit = (hi >> 63) as i32;
|
||||
let mut mantissa = hi >> (upperbit + 64 - F::MANTISSA_EXPLICIT_BITS as i32 - 3);
|
||||
let mut power2 = power(q as i32) + upperbit - lz as i32 - F::MINIMUM_EXPONENT;
|
||||
let mut mantissa = hi >> (upperbit + 64 - F::SIG_BITS as i32 - 3);
|
||||
let mut power2 = power(q as i32) + upperbit - lz as i32 - F::EXP_MIN + 1;
|
||||
if power2 <= 0 {
|
||||
if -power2 + 1 >= 64 {
|
||||
// Have more than 64 bits below the minimum exponent, must be 0.
|
||||
|
@ -72,8 +72,8 @@ pub fn compute_float<F: RawFloat>(q: i64, mut w: u64) -> BiasedFp {
|
|||
mantissa >>= -power2 + 1;
|
||||
mantissa += mantissa & 1;
|
||||
mantissa >>= 1;
|
||||
power2 = (mantissa >= (1_u64 << F::MANTISSA_EXPLICIT_BITS)) as i32;
|
||||
return BiasedFp { f: mantissa, e: power2 };
|
||||
power2 = (mantissa >= (1_u64 << F::SIG_BITS)) as i32;
|
||||
return BiasedFp { m: mantissa, p_biased: power2 };
|
||||
}
|
||||
// Need to handle rounding ties. Normally, we need to round up,
|
||||
// but if we fall right in between and we have an even basis, we
|
||||
|
@ -89,8 +89,8 @@ pub fn compute_float<F: RawFloat>(q: i64, mut w: u64) -> BiasedFp {
|
|||
if lo <= 1
|
||||
&& q >= F::MIN_EXPONENT_ROUND_TO_EVEN as i64
|
||||
&& q <= F::MAX_EXPONENT_ROUND_TO_EVEN as i64
|
||||
&& mantissa & 3 == 1
|
||||
&& (mantissa << (upperbit + 64 - F::MANTISSA_EXPLICIT_BITS as i32 - 3)) == hi
|
||||
&& mantissa & 0b11 == 0b01
|
||||
&& (mantissa << (upperbit + 64 - F::SIG_BITS as i32 - 3)) == hi
|
||||
{
|
||||
// Zero the lowest bit, so we don't round up.
|
||||
mantissa &= !1_u64;
|
||||
|
@ -98,20 +98,20 @@ pub fn compute_float<F: RawFloat>(q: i64, mut w: u64) -> BiasedFp {
|
|||
// Round-to-even, then shift the significant digits into place.
|
||||
mantissa += mantissa & 1;
|
||||
mantissa >>= 1;
|
||||
if mantissa >= (2_u64 << F::MANTISSA_EXPLICIT_BITS) {
|
||||
if mantissa >= (2_u64 << F::SIG_BITS) {
|
||||
// Rounding up overflowed, so the carry bit is set. Set the
|
||||
// mantissa to 1 (only the implicit, hidden bit is set) and
|
||||
// increase the exponent.
|
||||
mantissa = 1_u64 << F::MANTISSA_EXPLICIT_BITS;
|
||||
mantissa = 1_u64 << F::SIG_BITS;
|
||||
power2 += 1;
|
||||
}
|
||||
// Zero out the hidden bit.
|
||||
mantissa &= !(1_u64 << F::MANTISSA_EXPLICIT_BITS);
|
||||
mantissa &= !(1_u64 << F::SIG_BITS);
|
||||
if power2 >= F::INFINITE_POWER {
|
||||
// Exponent is above largest normal value, must be infinite.
|
||||
return fp_inf;
|
||||
}
|
||||
BiasedFp { f: mantissa, e: power2 }
|
||||
BiasedFp { m: mantissa, p_biased: power2 }
|
||||
}
|
||||
|
||||
/// Calculate a base 2 exponent from a decimal exponent.
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
//! # Problem statement
|
||||
//!
|
||||
//! We are given a decimal string such as `12.34e56`. This string consists of integral (`12`),
|
||||
//! fractional (`34`), and exponent (`56`) parts. All parts are optional and interpreted as zero
|
||||
//! when missing.
|
||||
//! fractional (`34`), and exponent (`56`) parts. All parts are optional and interpreted as a
|
||||
//! default value (1 or 0) when missing.
|
||||
//!
|
||||
//! We seek the IEEE 754 floating point number that is closest to the exact value of the decimal
|
||||
//! string. It is well-known that many decimal strings do not have terminating representations in
|
||||
|
@ -67,6 +67,18 @@
|
|||
//! "such that the exponent +/- the number of decimal digits fits into a 64 bit integer".
|
||||
//! Larger exponents are accepted, but we don't do arithmetic with them, they are immediately
|
||||
//! turned into {positive,negative} {zero,infinity}.
|
||||
//!
|
||||
//! # Notation
|
||||
//!
|
||||
//! This module uses the same notation as the Lemire paper:
|
||||
//!
|
||||
//! - `m`: binary mantissa; always nonnegative
|
||||
//! - `p`: binary exponent; a signed integer
|
||||
//! - `w`: decimal significand; always nonnegative
|
||||
//! - `q`: decimal exponent; a signed integer
|
||||
//!
|
||||
//! This gives `m * 2^p` for the binary floating-point number, with `w * 10^q` as the decimal
|
||||
//! equivalent.
|
||||
|
||||
#![doc(hidden)]
|
||||
#![unstable(
|
||||
|
@ -85,14 +97,14 @@ use crate::fmt;
|
|||
use crate::str::FromStr;
|
||||
|
||||
mod common;
|
||||
mod decimal;
|
||||
pub mod decimal;
|
||||
pub mod decimal_seq;
|
||||
mod fpu;
|
||||
mod slow;
|
||||
mod table;
|
||||
// float is used in flt2dec, and all are used in unit tests.
|
||||
pub mod float;
|
||||
pub mod lemire;
|
||||
pub mod number;
|
||||
pub mod parse;
|
||||
|
||||
macro_rules! from_str_float_impl {
|
||||
|
@ -220,10 +232,10 @@ pub fn pfe_invalid() -> ParseFloatError {
|
|||
}
|
||||
|
||||
/// Converts a `BiasedFp` to the closest machine float type.
|
||||
fn biased_fp_to_float<T: RawFloat>(x: BiasedFp) -> T {
|
||||
let mut word = x.f;
|
||||
word |= (x.e as u64) << T::MANTISSA_EXPLICIT_BITS;
|
||||
T::from_u64_bits(word)
|
||||
fn biased_fp_to_float<F: RawFloat>(x: BiasedFp) -> F {
|
||||
let mut word = x.m;
|
||||
word |= (x.p_biased as u64) << F::SIG_BITS;
|
||||
F::from_u64_bits(word)
|
||||
}
|
||||
|
||||
/// Converts a decimal string into a floating point number.
|
||||
|
@ -260,12 +272,15 @@ pub fn dec2flt<F: RawFloat>(s: &str) -> Result<F, ParseFloatError> {
|
|||
// redundantly using the Eisel-Lemire algorithm if it was unable to
|
||||
// correctly round on the first pass.
|
||||
let mut fp = compute_float::<F>(num.exponent, num.mantissa);
|
||||
if num.many_digits && fp.e >= 0 && fp != compute_float::<F>(num.exponent, num.mantissa + 1) {
|
||||
fp.e = -1;
|
||||
if num.many_digits
|
||||
&& fp.p_biased >= 0
|
||||
&& fp != compute_float::<F>(num.exponent, num.mantissa + 1)
|
||||
{
|
||||
fp.p_biased = -1;
|
||||
}
|
||||
// Unable to correctly round the float using the Eisel-Lemire algorithm.
|
||||
// Fallback to a slower, but always correct algorithm.
|
||||
if fp.e < 0 {
|
||||
if fp.p_biased < 0 {
|
||||
fp = parse_long_mantissa::<F>(s);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
//! Representation of a float as the significant digits and exponent.
|
||||
|
||||
use crate::num::dec2flt::float::RawFloat;
|
||||
use crate::num::dec2flt::fpu::set_precision;
|
||||
|
||||
#[rustfmt::skip]
|
||||
const INT_POW10: [u64; 16] = [
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000,
|
||||
10000000000,
|
||||
100000000000,
|
||||
1000000000000,
|
||||
10000000000000,
|
||||
100000000000000,
|
||||
1000000000000000,
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Number {
|
||||
pub exponent: i64,
|
||||
pub mantissa: u64,
|
||||
pub negative: bool,
|
||||
pub many_digits: bool,
|
||||
}
|
||||
|
||||
impl Number {
|
||||
/// Detect if the float can be accurately reconstructed from native floats.
|
||||
#[inline]
|
||||
fn is_fast_path<F: RawFloat>(&self) -> bool {
|
||||
F::MIN_EXPONENT_FAST_PATH <= self.exponent
|
||||
&& self.exponent <= F::MAX_EXPONENT_DISGUISED_FAST_PATH
|
||||
&& self.mantissa <= F::MAX_MANTISSA_FAST_PATH
|
||||
&& !self.many_digits
|
||||
}
|
||||
|
||||
/// The fast path algorithm using machine-sized integers and floats.
|
||||
///
|
||||
/// This is extracted into a separate function so that it can be attempted before constructing
|
||||
/// a Decimal. This only works if both the mantissa and the exponent
|
||||
/// can be exactly represented as a machine float, since IEE-754 guarantees
|
||||
/// no rounding will occur.
|
||||
///
|
||||
/// There is an exception: disguised fast-path cases, where we can shift
|
||||
/// powers-of-10 from the exponent to the significant digits.
|
||||
pub fn try_fast_path<F: RawFloat>(&self) -> Option<F> {
|
||||
// Here we need to work around <https://github.com/rust-lang/rust/issues/114479>.
|
||||
// The fast path crucially depends on arithmetic being rounded to the correct number of bits
|
||||
// without any intermediate rounding. On x86 (without SSE or SSE2) this requires the precision
|
||||
// of the x87 FPU stack to be changed so that it directly rounds to 64/32 bit.
|
||||
// The `set_precision` function takes care of setting the precision on architectures which
|
||||
// require setting it by changing the global state (like the control word of the x87 FPU).
|
||||
let _cw = set_precision::<F>();
|
||||
|
||||
if self.is_fast_path::<F>() {
|
||||
let mut value = if self.exponent <= F::MAX_EXPONENT_FAST_PATH {
|
||||
// normal fast path
|
||||
let value = F::from_u64(self.mantissa);
|
||||
if self.exponent < 0 {
|
||||
value / F::pow10_fast_path((-self.exponent) as _)
|
||||
} else {
|
||||
value * F::pow10_fast_path(self.exponent as _)
|
||||
}
|
||||
} else {
|
||||
// disguised fast path
|
||||
let shift = self.exponent - F::MAX_EXPONENT_FAST_PATH;
|
||||
let mantissa = self.mantissa.checked_mul(INT_POW10[shift as usize])?;
|
||||
if mantissa > F::MAX_MANTISSA_FAST_PATH {
|
||||
return None;
|
||||
}
|
||||
F::from_u64(mantissa) * F::pow10_fast_path(F::MAX_EXPONENT_FAST_PATH as _)
|
||||
};
|
||||
if self.negative {
|
||||
value = -value;
|
||||
}
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
//! Functions to parse floating-point numbers.
|
||||
|
||||
use crate::num::dec2flt::common::{ByteSlice, is_8digits};
|
||||
use crate::num::dec2flt::decimal::Decimal;
|
||||
use crate::num::dec2flt::float::RawFloat;
|
||||
use crate::num::dec2flt::number::Number;
|
||||
|
||||
const MIN_19DIGIT_INT: u64 = 100_0000_0000_0000_0000;
|
||||
|
||||
|
@ -100,7 +100,7 @@ fn parse_scientific(s_ref: &mut &[u8]) -> Option<i64> {
|
|||
///
|
||||
/// This creates a representation of the float as the
|
||||
/// significant digits and the decimal exponent.
|
||||
fn parse_partial_number(mut s: &[u8]) -> Option<(Number, usize)> {
|
||||
fn parse_partial_number(mut s: &[u8]) -> Option<(Decimal, usize)> {
|
||||
debug_assert!(!s.is_empty());
|
||||
|
||||
// parse initial digits before dot
|
||||
|
@ -146,7 +146,7 @@ fn parse_partial_number(mut s: &[u8]) -> Option<(Number, usize)> {
|
|||
|
||||
// handle uncommon case with many digits
|
||||
if n_digits <= 19 {
|
||||
return Some((Number { exponent, mantissa, negative: false, many_digits: false }, len));
|
||||
return Some((Decimal { exponent, mantissa, negative: false, many_digits: false }, len));
|
||||
}
|
||||
|
||||
n_digits -= 19;
|
||||
|
@ -179,13 +179,13 @@ fn parse_partial_number(mut s: &[u8]) -> Option<(Number, usize)> {
|
|||
exponent += exp_number;
|
||||
}
|
||||
|
||||
Some((Number { exponent, mantissa, negative: false, many_digits }, len))
|
||||
Some((Decimal { exponent, mantissa, negative: false, many_digits }, len))
|
||||
}
|
||||
|
||||
/// Try to parse a non-special floating point number,
|
||||
/// as well as two slices with integer and fractional parts
|
||||
/// and the parsed exponent.
|
||||
pub fn parse_number(s: &[u8]) -> Option<Number> {
|
||||
pub fn parse_number(s: &[u8]) -> Option<Decimal> {
|
||||
if let Some((float, rest)) = parse_partial_number(s) {
|
||||
if rest == s.len() {
|
||||
return Some(float);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Slow, fallback algorithm for cases the Eisel-Lemire algorithm cannot round.
|
||||
|
||||
use crate::num::dec2flt::common::BiasedFp;
|
||||
use crate::num::dec2flt::decimal::{Decimal, parse_decimal};
|
||||
use crate::num::dec2flt::decimal_seq::{DecimalSeq, parse_decimal_seq};
|
||||
use crate::num::dec2flt::float::RawFloat;
|
||||
|
||||
/// Parse the significant digits and biased, binary exponent of a float.
|
||||
|
@ -36,7 +36,7 @@ pub(crate) fn parse_long_mantissa<F: RawFloat>(s: &[u8]) -> BiasedFp {
|
|||
let fp_zero = BiasedFp::zero_pow2(0);
|
||||
let fp_inf = BiasedFp::zero_pow2(F::INFINITE_POWER);
|
||||
|
||||
let mut d = parse_decimal(s);
|
||||
let mut d = parse_decimal_seq(s);
|
||||
|
||||
// Short-circuit if the value can only be a literal 0 or infinity.
|
||||
if d.num_digits == 0 || d.decimal_point < -324 {
|
||||
|
@ -50,7 +50,7 @@ pub(crate) fn parse_long_mantissa<F: RawFloat>(s: &[u8]) -> BiasedFp {
|
|||
let n = d.decimal_point as usize;
|
||||
let shift = get_shift(n);
|
||||
d.right_shift(shift);
|
||||
if d.decimal_point < -Decimal::DECIMAL_POINT_RANGE {
|
||||
if d.decimal_point < -DecimalSeq::DECIMAL_POINT_RANGE {
|
||||
return fp_zero;
|
||||
}
|
||||
exp2 += shift as i32;
|
||||
|
@ -67,43 +67,43 @@ pub(crate) fn parse_long_mantissa<F: RawFloat>(s: &[u8]) -> BiasedFp {
|
|||
get_shift((-d.decimal_point) as _)
|
||||
};
|
||||
d.left_shift(shift);
|
||||
if d.decimal_point > Decimal::DECIMAL_POINT_RANGE {
|
||||
if d.decimal_point > DecimalSeq::DECIMAL_POINT_RANGE {
|
||||
return fp_inf;
|
||||
}
|
||||
exp2 -= shift as i32;
|
||||
}
|
||||
// We are now in the range [1/2 ... 1] but the binary format uses [1 ... 2].
|
||||
exp2 -= 1;
|
||||
while (F::MINIMUM_EXPONENT + 1) > exp2 {
|
||||
let mut n = ((F::MINIMUM_EXPONENT + 1) - exp2) as usize;
|
||||
while F::EXP_MIN > exp2 {
|
||||
let mut n = (F::EXP_MIN - exp2) as usize;
|
||||
if n > MAX_SHIFT {
|
||||
n = MAX_SHIFT;
|
||||
}
|
||||
d.right_shift(n);
|
||||
exp2 += n as i32;
|
||||
}
|
||||
if (exp2 - F::MINIMUM_EXPONENT) >= F::INFINITE_POWER {
|
||||
if (exp2 - F::EXP_MIN + 1) >= F::INFINITE_POWER {
|
||||
return fp_inf;
|
||||
}
|
||||
// Shift the decimal to the hidden bit, and then round the value
|
||||
// to get the high mantissa+1 bits.
|
||||
d.left_shift(F::MANTISSA_EXPLICIT_BITS + 1);
|
||||
d.left_shift(F::SIG_BITS as usize + 1);
|
||||
let mut mantissa = d.round();
|
||||
if mantissa >= (1_u64 << (F::MANTISSA_EXPLICIT_BITS + 1)) {
|
||||
if mantissa >= (1_u64 << (F::SIG_BITS + 1)) {
|
||||
// Rounding up overflowed to the carry bit, need to
|
||||
// shift back to the hidden bit.
|
||||
d.right_shift(1);
|
||||
exp2 += 1;
|
||||
mantissa = d.round();
|
||||
if (exp2 - F::MINIMUM_EXPONENT) >= F::INFINITE_POWER {
|
||||
if (exp2 - F::EXP_MIN + 1) >= F::INFINITE_POWER {
|
||||
return fp_inf;
|
||||
}
|
||||
}
|
||||
let mut power2 = exp2 - F::MINIMUM_EXPONENT;
|
||||
if mantissa < (1_u64 << F::MANTISSA_EXPLICIT_BITS) {
|
||||
let mut power2 = exp2 - F::EXP_MIN + 1;
|
||||
if mantissa < (1_u64 << F::SIG_BITS) {
|
||||
power2 -= 1;
|
||||
}
|
||||
// Zero out all the bits above the explicit mantissa bits.
|
||||
mantissa &= (1_u64 << F::MANTISSA_EXPLICIT_BITS) - 1;
|
||||
BiasedFp { f: mantissa, e: power2 }
|
||||
mantissa &= (1_u64 << F::SIG_BITS) - 1;
|
||||
BiasedFp { m: mantissa, p_biased: power2 }
|
||||
}
|
||||
|
|
|
@ -493,13 +493,13 @@ impl f32 {
|
|||
pub const NEG_INFINITY: f32 = -1.0_f32 / 0.0_f32;
|
||||
|
||||
/// Sign bit
|
||||
const SIGN_MASK: u32 = 0x8000_0000;
|
||||
pub(crate) const SIGN_MASK: u32 = 0x8000_0000;
|
||||
|
||||
/// Exponent mask
|
||||
const EXP_MASK: u32 = 0x7f80_0000;
|
||||
pub(crate) const EXP_MASK: u32 = 0x7f80_0000;
|
||||
|
||||
/// Mantissa mask
|
||||
const MAN_MASK: u32 = 0x007f_ffff;
|
||||
pub(crate) const MAN_MASK: u32 = 0x007f_ffff;
|
||||
|
||||
/// Minimum representable positive value (min subnormal)
|
||||
const TINY_BITS: u32 = 0x1;
|
||||
|
|
|
@ -492,13 +492,13 @@ impl f64 {
|
|||
pub const NEG_INFINITY: f64 = -1.0_f64 / 0.0_f64;
|
||||
|
||||
/// Sign bit
|
||||
const SIGN_MASK: u64 = 0x8000_0000_0000_0000;
|
||||
pub(crate) const SIGN_MASK: u64 = 0x8000_0000_0000_0000;
|
||||
|
||||
/// Exponent mask
|
||||
const EXP_MASK: u64 = 0x7ff0_0000_0000_0000;
|
||||
pub(crate) const EXP_MASK: u64 = 0x7ff0_0000_0000_0000;
|
||||
|
||||
/// Mantissa mask
|
||||
const MAN_MASK: u64 = 0x000f_ffff_ffff_ffff;
|
||||
pub(crate) const MAN_MASK: u64 = 0x000f_ffff_ffff_ffff;
|
||||
|
||||
/// Minimum representable positive value (min subnormal)
|
||||
const TINY_BITS: u64 = 0x1;
|
||||
|
|
|
@ -87,7 +87,6 @@
|
|||
#![feature(try_find)]
|
||||
#![feature(try_trait_v2)]
|
||||
#![feature(unsize)]
|
||||
#![feature(unsized_tuple_coercion)]
|
||||
#![feature(unwrap_infallible)]
|
||||
// tidy-alphabetical-end
|
||||
#![allow(internal_features)]
|
||||
|
|
28
library/coretests/tests/num/dec2flt/decimal.rs
Normal file
28
library/coretests/tests/num/dec2flt/decimal.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use core::num::dec2flt::decimal::Decimal;
|
||||
|
||||
type FPath<F> = ((i64, u64, bool, bool), Option<F>);
|
||||
|
||||
const FPATHS_F32: &[FPath<f32>] =
|
||||
&[((0, 0, false, false), Some(0.0)), ((0, 0, false, false), Some(0.0))];
|
||||
const FPATHS_F64: &[FPath<f64>] =
|
||||
&[((0, 0, false, false), Some(0.0)), ((0, 0, false, false), Some(0.0))];
|
||||
|
||||
#[test]
|
||||
fn check_fast_path_f32() {
|
||||
for ((exponent, mantissa, negative, many_digits), expected) in FPATHS_F32.iter().copied() {
|
||||
let dec = Decimal { exponent, mantissa, negative, many_digits };
|
||||
let actual = dec.try_fast_path::<f32>();
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_fast_path_f64() {
|
||||
for ((exponent, mantissa, negative, many_digits), expected) in FPATHS_F64.iter().copied() {
|
||||
let dec = Decimal { exponent, mantissa, negative, many_digits };
|
||||
let actual = dec.try_fast_path::<f64>();
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
30
library/coretests/tests/num/dec2flt/decimal_seq.rs
Normal file
30
library/coretests/tests/num/dec2flt/decimal_seq.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use core::num::dec2flt::decimal_seq::{DecimalSeq, parse_decimal_seq};
|
||||
|
||||
#[test]
|
||||
fn test_trim() {
|
||||
let mut dec = DecimalSeq::default();
|
||||
let digits = [1, 2, 3, 4];
|
||||
|
||||
dec.digits[0..4].copy_from_slice(&digits);
|
||||
dec.num_digits = 8;
|
||||
dec.trim();
|
||||
|
||||
assert_eq!(dec.digits[0..4], digits);
|
||||
assert_eq!(dec.num_digits, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
let tests = [("1.234", [1, 2, 3, 4], 1)];
|
||||
|
||||
for (s, exp_digits, decimal_point) in tests {
|
||||
let actual = parse_decimal_seq(s.as_bytes());
|
||||
let mut digits = [0; DecimalSeq::MAX_DIGITS];
|
||||
digits[..exp_digits.len()].copy_from_slice(&exp_digits);
|
||||
|
||||
let expected =
|
||||
DecimalSeq { num_digits: exp_digits.len(), decimal_point, truncated: false, digits };
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
|
@ -12,8 +12,8 @@ fn test_f32_integer_decode() {
|
|||
|
||||
// Ignore the "sign" (quiet / signalling flag) of NAN.
|
||||
// It can vary between runtime operations and LLVM folding.
|
||||
let (nan_m, nan_e, _nan_s) = f32::NAN.integer_decode();
|
||||
assert_eq!((nan_m, nan_e), (12582912, 105));
|
||||
let (nan_m, nan_p, _nan_s) = f32::NAN.integer_decode();
|
||||
assert_eq!((nan_m, nan_p), (12582912, 105));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -28,6 +28,46 @@ fn test_f64_integer_decode() {
|
|||
|
||||
// Ignore the "sign" (quiet / signalling flag) of NAN.
|
||||
// It can vary between runtime operations and LLVM folding.
|
||||
let (nan_m, nan_e, _nan_s) = f64::NAN.integer_decode();
|
||||
assert_eq!((nan_m, nan_e), (6755399441055744, 972));
|
||||
let (nan_m, nan_p, _nan_s) = f64::NAN.integer_decode();
|
||||
assert_eq!((nan_m, nan_p), (6755399441055744, 972));
|
||||
}
|
||||
|
||||
/* Sanity checks of computed magic numbers */
|
||||
|
||||
#[test]
|
||||
fn test_f32_consts() {
|
||||
assert_eq!(<f32 as RawFloat>::INFINITY, f32::INFINITY);
|
||||
assert_eq!(<f32 as RawFloat>::NEG_INFINITY, -f32::INFINITY);
|
||||
assert_eq!(<f32 as RawFloat>::NAN.to_bits(), f32::NAN.to_bits());
|
||||
assert_eq!(<f32 as RawFloat>::NEG_NAN.to_bits(), (-f32::NAN).to_bits());
|
||||
assert_eq!(<f32 as RawFloat>::SIG_BITS, 23);
|
||||
assert_eq!(<f32 as RawFloat>::MIN_EXPONENT_ROUND_TO_EVEN, -17);
|
||||
assert_eq!(<f32 as RawFloat>::MAX_EXPONENT_ROUND_TO_EVEN, 10);
|
||||
assert_eq!(<f32 as RawFloat>::MIN_EXPONENT_FAST_PATH, -10);
|
||||
assert_eq!(<f32 as RawFloat>::MAX_EXPONENT_FAST_PATH, 10);
|
||||
assert_eq!(<f32 as RawFloat>::MAX_EXPONENT_DISGUISED_FAST_PATH, 17);
|
||||
assert_eq!(<f32 as RawFloat>::EXP_MIN, -126);
|
||||
assert_eq!(<f32 as RawFloat>::EXP_SAT, 0xff);
|
||||
assert_eq!(<f32 as RawFloat>::SMALLEST_POWER_OF_TEN, -65);
|
||||
assert_eq!(<f32 as RawFloat>::LARGEST_POWER_OF_TEN, 38);
|
||||
assert_eq!(<f32 as RawFloat>::MAX_MANTISSA_FAST_PATH, 16777216);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_f64_consts() {
|
||||
assert_eq!(<f64 as RawFloat>::INFINITY, f64::INFINITY);
|
||||
assert_eq!(<f64 as RawFloat>::NEG_INFINITY, -f64::INFINITY);
|
||||
assert_eq!(<f64 as RawFloat>::NAN.to_bits(), f64::NAN.to_bits());
|
||||
assert_eq!(<f64 as RawFloat>::NEG_NAN.to_bits(), (-f64::NAN).to_bits());
|
||||
assert_eq!(<f64 as RawFloat>::SIG_BITS, 52);
|
||||
assert_eq!(<f64 as RawFloat>::MIN_EXPONENT_ROUND_TO_EVEN, -4);
|
||||
assert_eq!(<f64 as RawFloat>::MAX_EXPONENT_ROUND_TO_EVEN, 23);
|
||||
assert_eq!(<f64 as RawFloat>::MIN_EXPONENT_FAST_PATH, -22);
|
||||
assert_eq!(<f64 as RawFloat>::MAX_EXPONENT_FAST_PATH, 22);
|
||||
assert_eq!(<f64 as RawFloat>::MAX_EXPONENT_DISGUISED_FAST_PATH, 37);
|
||||
assert_eq!(<f64 as RawFloat>::EXP_MIN, -1022);
|
||||
assert_eq!(<f64 as RawFloat>::EXP_SAT, 0x7ff);
|
||||
assert_eq!(<f64 as RawFloat>::SMALLEST_POWER_OF_TEN, -342);
|
||||
assert_eq!(<f64 as RawFloat>::LARGEST_POWER_OF_TEN, 308);
|
||||
assert_eq!(<f64 as RawFloat>::MAX_MANTISSA_FAST_PATH, 9007199254740992);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use core::num::dec2flt::float::RawFloat;
|
||||
use core::num::dec2flt::lemire::compute_float;
|
||||
|
||||
fn compute_float32(q: i64, w: u64) -> (i32, u64) {
|
||||
let fp = compute_float::<f32>(q, w);
|
||||
(fp.e, fp.f)
|
||||
(fp.p_biased, fp.m)
|
||||
}
|
||||
|
||||
fn compute_float64(q: i64, w: u64) -> (i32, u64) {
|
||||
let fp = compute_float::<f64>(q, w);
|
||||
(fp.e, fp.f)
|
||||
(fp.p_biased, fp.m)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -27,6 +28,11 @@ fn compute_float_f32_rounding() {
|
|||
// Let's check the lines to see if anything is different in table...
|
||||
assert_eq!(compute_float32(-10, 167772190000000000), (151, 2));
|
||||
assert_eq!(compute_float32(-10, 167772200000000000), (151, 2));
|
||||
|
||||
// Check the rounding point between infinity and the next representable number down
|
||||
assert_eq!(compute_float32(38, 3), (f32::INFINITE_POWER - 1, 6402534));
|
||||
assert_eq!(compute_float32(38, 4), (f32::INFINITE_POWER, 0)); // infinity
|
||||
assert_eq!(compute_float32(20, 3402823470000000000), (f32::INFINITE_POWER - 1, 8388607));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#![allow(overflowing_literals)]
|
||||
|
||||
mod decimal;
|
||||
mod decimal_seq;
|
||||
mod float;
|
||||
mod lemire;
|
||||
mod parse;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use core::num::dec2flt::number::Number;
|
||||
use core::num::dec2flt::decimal::Decimal;
|
||||
use core::num::dec2flt::parse::parse_number;
|
||||
use core::num::dec2flt::{dec2flt, pfe_invalid};
|
||||
|
||||
fn new_number(e: i64, m: u64) -> Number {
|
||||
Number { exponent: e, mantissa: m, negative: false, many_digits: false }
|
||||
fn new_dec(e: i64, m: u64) -> Decimal {
|
||||
Decimal { exponent: e, mantissa: m, negative: false, many_digits: false }
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -31,23 +31,23 @@ fn invalid_chars() {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_positive(s: &[u8]) -> Option<Number> {
|
||||
fn parse_positive(s: &[u8]) -> Option<Decimal> {
|
||||
parse_number(s)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid() {
|
||||
assert_eq!(parse_positive(b"123.456e789"), Some(new_number(786, 123456)));
|
||||
assert_eq!(parse_positive(b"123.456e+789"), Some(new_number(786, 123456)));
|
||||
assert_eq!(parse_positive(b"123.456e-789"), Some(new_number(-792, 123456)));
|
||||
assert_eq!(parse_positive(b".050"), Some(new_number(-3, 50)));
|
||||
assert_eq!(parse_positive(b"999"), Some(new_number(0, 999)));
|
||||
assert_eq!(parse_positive(b"1.e300"), Some(new_number(300, 1)));
|
||||
assert_eq!(parse_positive(b".1e300"), Some(new_number(299, 1)));
|
||||
assert_eq!(parse_positive(b"101e-33"), Some(new_number(-33, 101)));
|
||||
assert_eq!(parse_positive(b"123.456e789"), Some(new_dec(786, 123456)));
|
||||
assert_eq!(parse_positive(b"123.456e+789"), Some(new_dec(786, 123456)));
|
||||
assert_eq!(parse_positive(b"123.456e-789"), Some(new_dec(-792, 123456)));
|
||||
assert_eq!(parse_positive(b".050"), Some(new_dec(-3, 50)));
|
||||
assert_eq!(parse_positive(b"999"), Some(new_dec(0, 999)));
|
||||
assert_eq!(parse_positive(b"1.e300"), Some(new_dec(300, 1)));
|
||||
assert_eq!(parse_positive(b".1e300"), Some(new_dec(299, 1)));
|
||||
assert_eq!(parse_positive(b"101e-33"), Some(new_dec(-33, 101)));
|
||||
let zeros = "0".repeat(25);
|
||||
let s = format!("1.5e{zeros}");
|
||||
assert_eq!(parse_positive(s.as_bytes()), Some(new_number(-1, 15)));
|
||||
assert_eq!(parse_positive(s.as_bytes()), Some(new_dec(-1, 15)));
|
||||
}
|
||||
|
||||
macro_rules! assert_float_result_bits_eq {
|
||||
|
@ -57,6 +57,21 @@ macro_rules! assert_float_result_bits_eq {
|
|||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression() {
|
||||
// These showed up in fuzz tests when the minimum exponent was incorrect.
|
||||
assert_float_result_bits_eq!(
|
||||
0x0,
|
||||
f64,
|
||||
"3313756768023998018398807867233977556112078681253148176737587500333136120852692315608454494981109839693784033457129423181787087843504060087613228932431e-475"
|
||||
);
|
||||
assert_float_result_bits_eq!(
|
||||
0x0,
|
||||
f64,
|
||||
"5298127456259331337220.92759278003098321644501973966679724599271041396379712108366679824365568578569680024083293475291869842408884554511641179110778276695274832779269225510492006696321279587846006535230380114430977056662212751544508159333199129106162019382177820713609e-346"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue31109() {
|
||||
// Regression test for #31109.
|
||||
|
|
|
@ -525,31 +525,24 @@ fn ptr_metadata() {
|
|||
assert_eq!(metadata("foo"), 3_usize);
|
||||
assert_eq!(metadata(&[4, 7][..]), 2_usize);
|
||||
|
||||
let dst_tuple: &(bool, [u8]) = &(true, [0x66, 0x6F, 0x6F]);
|
||||
let dst_struct: &Pair<bool, [u8]> = &Pair(true, [0x66, 0x6F, 0x6F]);
|
||||
assert_eq!(metadata(dst_tuple), 3_usize);
|
||||
assert_eq!(metadata(dst_struct), 3_usize);
|
||||
unsafe {
|
||||
let dst_tuple: &(bool, str) = std::mem::transmute(dst_tuple);
|
||||
let dst_struct: &Pair<bool, str> = std::mem::transmute(dst_struct);
|
||||
assert_eq!(&dst_tuple.1, "foo");
|
||||
assert_eq!(&dst_struct.1, "foo");
|
||||
assert_eq!(metadata(dst_tuple), 3_usize);
|
||||
assert_eq!(metadata(dst_struct), 3_usize);
|
||||
}
|
||||
|
||||
let vtable_1: DynMetadata<dyn Debug> = metadata(&4_u16 as &dyn Debug);
|
||||
let vtable_2: DynMetadata<dyn Display> = metadata(&4_u16 as &dyn Display);
|
||||
let vtable_3: DynMetadata<dyn Display> = metadata(&4_u32 as &dyn Display);
|
||||
let vtable_4: DynMetadata<dyn Display> = metadata(&(true, 7_u32) as &(bool, dyn Display));
|
||||
let vtable_5: DynMetadata<dyn Display> =
|
||||
let vtable_4: DynMetadata<dyn Display> =
|
||||
metadata(&Pair(true, 7_u32) as &Pair<bool, dyn Display>);
|
||||
unsafe {
|
||||
let address_1: *const () = std::mem::transmute(vtable_1);
|
||||
let address_2: *const () = std::mem::transmute(vtable_2);
|
||||
let address_3: *const () = std::mem::transmute(vtable_3);
|
||||
let address_4: *const () = std::mem::transmute(vtable_4);
|
||||
let address_5: *const () = std::mem::transmute(vtable_5);
|
||||
// Different trait => different vtable pointer
|
||||
assert_ne!(address_1, address_2);
|
||||
// Different erased type => different vtable pointer
|
||||
|
@ -558,7 +551,6 @@ fn ptr_metadata() {
|
|||
// This is *not guaranteed*, so we skip it in Miri.
|
||||
if !cfg!(miri) {
|
||||
assert_eq!(address_3, address_4);
|
||||
assert_eq!(address_3, address_5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@ use core::num::NonZero;
|
|||
use core::ops::{Range, RangeInclusive};
|
||||
use core::slice;
|
||||
|
||||
use rand::seq::IndexedRandom;
|
||||
|
||||
#[test]
|
||||
fn test_position() {
|
||||
let b = [1, 2, 3, 5, 5];
|
||||
|
@ -1810,6 +1808,7 @@ fn select_nth_unstable() {
|
|||
use core::cmp::Ordering::{Equal, Greater, Less};
|
||||
|
||||
use rand::Rng;
|
||||
use rand::seq::IndexedRandom;
|
||||
|
||||
let mut rng = crate::test_rng();
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ miniz_oxide = { version = "0.8.0", optional = true, default-features = false }
|
|||
addr2line = { version = "0.24.0", optional = true, default-features = false }
|
||||
|
||||
[target.'cfg(not(all(windows, target_env = "msvc")))'.dependencies]
|
||||
libc = { version = "0.2.169", default-features = false, features = [
|
||||
libc = { version = "0.2.170", default-features = false, features = [
|
||||
'rustc-dep-of-std',
|
||||
], public = true }
|
||||
|
||||
|
|
|
@ -2857,9 +2857,11 @@ pub fn remove_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
|
|||
///
|
||||
/// See [`fs::remove_file`] and [`fs::remove_dir`].
|
||||
///
|
||||
/// `remove_dir_all` will fail if `remove_dir` or `remove_file` fail on any constituent paths, including the root `path`.
|
||||
/// As a result, the directory you are deleting must exist, meaning that this function is not idempotent.
|
||||
/// Additionally, `remove_dir_all` will also fail if the `path` is not a directory.
|
||||
/// [`remove_dir_all`] will fail if [`remove_dir`] or [`remove_file`] fail on *any* constituent
|
||||
/// paths, *including* the root `path`. Consequently,
|
||||
///
|
||||
/// - The directory you are deleting *must* exist, meaning that this function is *not idempotent*.
|
||||
/// - [`remove_dir_all`] will fail if the `path` is *not* a directory.
|
||||
///
|
||||
/// Consider ignoring the error if validating the removal is not required for your use case.
|
||||
///
|
||||
|
|
|
@ -575,6 +575,11 @@ impl fmt::Debug for StdinLock<'_> {
|
|||
/// output stream. Access is also synchronized via a lock and explicit control
|
||||
/// over locking is available via the [`lock`] method.
|
||||
///
|
||||
/// By default, the handle is line-buffered when connected to a terminal, meaning
|
||||
/// it flushes automatically when a newline (`\n`) is encountered. For immediate
|
||||
/// output, you can manually call the [`flush`] method. When the handle goes out
|
||||
/// of scope, the buffer is automatically flushed.
|
||||
///
|
||||
/// Created by the [`io::stdout`] method.
|
||||
///
|
||||
/// ### Note: Windows Portability Considerations
|
||||
|
@ -590,6 +595,7 @@ impl fmt::Debug for StdinLock<'_> {
|
|||
/// standard library or via raw Windows API calls, will fail.
|
||||
///
|
||||
/// [`lock`]: Stdout::lock
|
||||
/// [`flush`]: Write::flush
|
||||
/// [`io::stdout`]: stdout
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub struct Stdout {
|
||||
|
@ -604,6 +610,11 @@ pub struct Stdout {
|
|||
/// This handle implements the [`Write`] trait, and is constructed via
|
||||
/// the [`Stdout::lock`] method. See its documentation for more.
|
||||
///
|
||||
/// By default, the handle is line-buffered when connected to a terminal, meaning
|
||||
/// it flushes automatically when a newline (`\n`) is encountered. For immediate
|
||||
/// output, you can manually call the [`flush`] method. When the handle goes out
|
||||
/// of scope, the buffer is automatically flushed.
|
||||
///
|
||||
/// ### Note: Windows Portability Considerations
|
||||
///
|
||||
/// When operating in a console, the Windows implementation of this stream does not support
|
||||
|
@ -615,6 +626,8 @@ pub struct Stdout {
|
|||
/// the contained handle will be null. In such cases, the standard library's `Read` and
|
||||
/// `Write` will do nothing and silently succeed. All other I/O operations, via the
|
||||
/// standard library or via raw Windows API calls, will fail.
|
||||
///
|
||||
/// [`flush`]: Write::flush
|
||||
#[must_use = "if unused stdout will immediately unlock"]
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub struct StdoutLock<'a> {
|
||||
|
@ -629,6 +642,11 @@ static STDOUT: OnceLock<ReentrantLock<RefCell<LineWriter<StdoutRaw>>>> = OnceLoc
|
|||
/// is synchronized via a mutex. If you need more explicit control over
|
||||
/// locking, see the [`Stdout::lock`] method.
|
||||
///
|
||||
/// By default, the handle is line-buffered when connected to a terminal, meaning
|
||||
/// it flushes automatically when a newline (`\n`) is encountered. For immediate
|
||||
/// output, you can manually call the [`flush`] method. When the handle goes out
|
||||
/// of scope, the buffer is automatically flushed.
|
||||
///
|
||||
/// ### Note: Windows Portability Considerations
|
||||
///
|
||||
/// When operating in a console, the Windows implementation of this stream does not support
|
||||
|
@ -669,6 +687,22 @@ static STDOUT: OnceLock<ReentrantLock<RefCell<LineWriter<StdoutRaw>>>> = OnceLoc
|
|||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Ensuring output is flushed immediately:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use std::io::{self, Write};
|
||||
///
|
||||
/// fn main() -> io::Result<()> {
|
||||
/// let mut stdout = io::stdout();
|
||||
/// stdout.write_all(b"hello, ")?;
|
||||
/// stdout.flush()?; // Manual flush
|
||||
/// stdout.write_all(b"world!\n")?; // Automatically flushed
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`flush`]: Write::flush
|
||||
#[must_use]
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
#[cfg_attr(not(test), rustc_diagnostic_item = "io_stdout")]
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
//! - More information about protocols can be found [here](https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/3_foundation/36_protocols_and_handles)
|
||||
|
||||
use r_efi::efi::{self, Guid};
|
||||
use r_efi::protocols::{device_path, device_path_to_text, shell};
|
||||
use r_efi::protocols::{device_path, device_path_to_text, service_binding, shell};
|
||||
|
||||
use crate::ffi::{OsStr, OsString};
|
||||
use crate::io::{self, const_error};
|
||||
|
@ -500,3 +500,62 @@ pub(crate) fn get_device_path_from_map(map: &Path) -> io::Result<BorrowedDeviceP
|
|||
|
||||
Ok(BorrowedDevicePath::new(protocol))
|
||||
}
|
||||
|
||||
/// Helper for UEFI Protocols which are created and destroyed using
|
||||
/// [EFI_SERVICE_BINDING_PROTCOL](https://uefi.org/specs/UEFI/2.11/11_Protocols_UEFI_Driver_Model.html#efi-service-binding-protocol)
|
||||
pub(crate) struct ServiceProtocol {
|
||||
service_guid: r_efi::efi::Guid,
|
||||
handle: NonNull<crate::ffi::c_void>,
|
||||
child_handle: NonNull<crate::ffi::c_void>,
|
||||
}
|
||||
|
||||
impl ServiceProtocol {
|
||||
#[expect(dead_code)]
|
||||
pub(crate) fn open(service_guid: r_efi::efi::Guid) -> io::Result<Self> {
|
||||
let handles = locate_handles(service_guid)?;
|
||||
|
||||
for handle in handles {
|
||||
if let Ok(protocol) = open_protocol::<service_binding::Protocol>(handle, service_guid) {
|
||||
let Ok(child_handle) = Self::create_child(protocol) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
return Ok(Self { service_guid, handle, child_handle });
|
||||
}
|
||||
}
|
||||
|
||||
Err(io::const_error!(io::ErrorKind::NotFound, "no service binding protocol found"))
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub(crate) fn child_handle(&self) -> NonNull<crate::ffi::c_void> {
|
||||
self.child_handle
|
||||
}
|
||||
|
||||
fn create_child(
|
||||
sbp: NonNull<service_binding::Protocol>,
|
||||
) -> io::Result<NonNull<crate::ffi::c_void>> {
|
||||
let mut child_handle: r_efi::efi::Handle = crate::ptr::null_mut();
|
||||
// SAFETY: A new handle is allocated if a pointer to NULL is passed.
|
||||
let r = unsafe { ((*sbp.as_ptr()).create_child)(sbp.as_ptr(), &mut child_handle) };
|
||||
|
||||
if r.is_error() {
|
||||
Err(crate::io::Error::from_raw_os_error(r.as_usize()))
|
||||
} else {
|
||||
NonNull::new(child_handle)
|
||||
.ok_or(const_error!(io::ErrorKind::Other, "null child handle"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ServiceProtocol {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(sbp) = open_protocol::<service_binding::Protocol>(self.handle, self.service_guid)
|
||||
{
|
||||
// SAFETY: Child handle must be allocated by the current service binding protocol.
|
||||
let _ = unsafe {
|
||||
((*sbp.as_ptr()).destroy_child)(sbp.as_ptr(), self.child_handle.as_ptr())
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -410,6 +410,7 @@ impl Command {
|
|||
|
||||
#[cfg(not(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "illumos",
|
||||
all(target_os = "linux", target_env = "gnu"),
|
||||
all(target_os = "linux", target_env = "musl"),
|
||||
target_os = "nto",
|
||||
|
@ -427,6 +428,7 @@ impl Command {
|
|||
// directly.
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "illumos",
|
||||
all(target_os = "linux", target_env = "gnu"),
|
||||
all(target_os = "linux", target_env = "musl"),
|
||||
target_os = "nto",
|
||||
|
@ -584,6 +586,10 @@ impl Command {
|
|||
fn get_posix_spawn_addchdir() -> Option<PosixSpawnAddChdirFn> {
|
||||
use crate::sys::weak::weak;
|
||||
|
||||
// POSIX.1-2024 standardizes this function:
|
||||
// https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawn_file_actions_addchdir.html.
|
||||
// The _np version is more widely available, though, so try that first.
|
||||
|
||||
weak! {
|
||||
fn posix_spawn_file_actions_addchdir_np(
|
||||
*mut libc::posix_spawn_file_actions_t,
|
||||
|
@ -591,7 +597,16 @@ impl Command {
|
|||
) -> libc::c_int
|
||||
}
|
||||
|
||||
posix_spawn_file_actions_addchdir_np.get()
|
||||
weak! {
|
||||
fn posix_spawn_file_actions_addchdir(
|
||||
*mut libc::posix_spawn_file_actions_t,
|
||||
*const libc::c_char
|
||||
) -> libc::c_int
|
||||
}
|
||||
|
||||
posix_spawn_file_actions_addchdir_np
|
||||
.get()
|
||||
.or_else(|| posix_spawn_file_actions_addchdir.get())
|
||||
}
|
||||
|
||||
/// Get the function pointer for adding a chdir action to a
|
||||
|
|
|
@ -292,7 +292,7 @@ v(
|
|||
v("release-channel", "rust.channel", "the name of the release channel to build")
|
||||
v(
|
||||
"release-description",
|
||||
"rust.description",
|
||||
"build.description",
|
||||
"optional descriptive string for version output",
|
||||
)
|
||||
v("dist-compression-formats", None, "List of compression formats to use")
|
||||
|
|
|
@ -90,7 +90,7 @@ impl Step for CrateBootstrap {
|
|||
);
|
||||
|
||||
let crate_name = path.rsplit_once('/').unwrap().1;
|
||||
run_cargo_test(cargo, &[], &[], crate_name, crate_name, bootstrap_host, builder);
|
||||
run_cargo_test(cargo, &[], &[], crate_name, bootstrap_host, builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,15 +140,7 @@ You can skip linkcheck with --skip src/tools/linkchecker"
|
|||
SourceType::InTree,
|
||||
&[],
|
||||
);
|
||||
run_cargo_test(
|
||||
cargo,
|
||||
&[],
|
||||
&[],
|
||||
"linkchecker",
|
||||
"linkchecker self tests",
|
||||
bootstrap_host,
|
||||
builder,
|
||||
);
|
||||
run_cargo_test(cargo, &[], &[], "linkchecker self tests", bootstrap_host, builder);
|
||||
|
||||
if builder.doc_tests == DocTests::No {
|
||||
return;
|
||||
|
@ -337,7 +329,7 @@ impl Step for Cargo {
|
|||
);
|
||||
|
||||
// NOTE: can't use `run_cargo_test` because we need to overwrite `PATH`
|
||||
let mut cargo = prepare_cargo_test(cargo, &[], &[], "cargo", self.host, builder);
|
||||
let mut cargo = prepare_cargo_test(cargo, &[], &[], self.host, builder);
|
||||
|
||||
// Don't run cross-compile tests, we may not have cross-compiled libstd libs
|
||||
// available.
|
||||
|
@ -423,7 +415,7 @@ impl Step for RustAnalyzer {
|
|||
cargo.env("SKIP_SLOW_TESTS", "1");
|
||||
|
||||
cargo.add_rustc_lib_path(builder);
|
||||
run_cargo_test(cargo, &[], &[], "rust-analyzer", "rust-analyzer", host, builder);
|
||||
run_cargo_test(cargo, &[], &[], "rust-analyzer", host, builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,7 +464,7 @@ impl Step for Rustfmt {
|
|||
|
||||
cargo.add_rustc_lib_path(builder);
|
||||
|
||||
run_cargo_test(cargo, &[], &[], "rustfmt", "rustfmt", host, builder);
|
||||
run_cargo_test(cargo, &[], &[], "rustfmt", host, builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -588,7 +580,7 @@ impl Step for Miri {
|
|||
|
||||
// We can NOT use `run_cargo_test` since Miri's integration tests do not use the usual test
|
||||
// harness and therefore do not understand the flags added by `add_flags_and_try_run_test`.
|
||||
let mut cargo = prepare_cargo_test(cargo, &[], &[], "miri", host, builder);
|
||||
let mut cargo = prepare_cargo_test(cargo, &[], &[], host, builder);
|
||||
|
||||
// miri tests need to know about the stage sysroot
|
||||
cargo.env("MIRI_SYSROOT", &miri_sysroot);
|
||||
|
@ -736,7 +728,7 @@ impl Step for CompiletestTest {
|
|||
&[],
|
||||
);
|
||||
cargo.allow_features("test");
|
||||
run_cargo_test(cargo, &[], &[], "compiletest", "compiletest self test", host, builder);
|
||||
run_cargo_test(cargo, &[], &[], "compiletest self test", host, builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -797,7 +789,7 @@ impl Step for Clippy {
|
|||
cargo.env("HOST_LIBS", host_libs);
|
||||
|
||||
cargo.add_rustc_lib_path(builder);
|
||||
let cargo = prepare_cargo_test(cargo, &[], &[], "clippy", host, builder);
|
||||
let cargo = prepare_cargo_test(cargo, &[], &[], host, builder);
|
||||
|
||||
let _guard = builder.msg_sysroot_tool(Kind::Test, compiler.stage, "clippy", host, host);
|
||||
|
||||
|
@ -1277,15 +1269,7 @@ impl Step for CrateRunMakeSupport {
|
|||
&[],
|
||||
);
|
||||
cargo.allow_features("test");
|
||||
run_cargo_test(
|
||||
cargo,
|
||||
&[],
|
||||
&[],
|
||||
"run-make-support",
|
||||
"run-make-support self test",
|
||||
host,
|
||||
builder,
|
||||
);
|
||||
run_cargo_test(cargo, &[], &[], "run-make-support self test", host, builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1322,7 +1306,7 @@ impl Step for CrateBuildHelper {
|
|||
&[],
|
||||
);
|
||||
cargo.allow_features("test");
|
||||
run_cargo_test(cargo, &[], &[], "build_helper", "build_helper self test", host, builder);
|
||||
run_cargo_test(cargo, &[], &[], "build_helper self test", host, builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2507,13 +2491,12 @@ fn run_cargo_test<'a>(
|
|||
cargo: builder::Cargo,
|
||||
libtest_args: &[&str],
|
||||
crates: &[String],
|
||||
primary_crate: &str,
|
||||
description: impl Into<Option<&'a str>>,
|
||||
target: TargetSelection,
|
||||
builder: &Builder<'_>,
|
||||
) -> bool {
|
||||
let compiler = cargo.compiler();
|
||||
let mut cargo = prepare_cargo_test(cargo, libtest_args, crates, primary_crate, target, builder);
|
||||
let mut cargo = prepare_cargo_test(cargo, libtest_args, crates, target, builder);
|
||||
let _time = helpers::timeit(builder);
|
||||
let _group = description.into().and_then(|what| {
|
||||
builder.msg_sysroot_tool(Kind::Test, compiler.stage, what, compiler.host, target)
|
||||
|
@ -2537,7 +2520,6 @@ fn prepare_cargo_test(
|
|||
cargo: builder::Cargo,
|
||||
libtest_args: &[&str],
|
||||
crates: &[String],
|
||||
primary_crate: &str,
|
||||
target: TargetSelection,
|
||||
builder: &Builder<'_>,
|
||||
) -> BootstrapCommand {
|
||||
|
@ -2567,13 +2549,6 @@ fn prepare_cargo_test(
|
|||
cargo.arg("--doc");
|
||||
}
|
||||
DocTests::No => {
|
||||
let krate = &builder
|
||||
.crates
|
||||
.get(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 => {}
|
||||
|
@ -2748,15 +2723,15 @@ impl Step for Crate {
|
|||
_ => panic!("can only test libraries"),
|
||||
};
|
||||
|
||||
run_cargo_test(
|
||||
cargo,
|
||||
&[],
|
||||
&self.crates,
|
||||
&self.crates[0],
|
||||
&*crate_description(&self.crates),
|
||||
target,
|
||||
builder,
|
||||
);
|
||||
let mut crates = self.crates.clone();
|
||||
// The core crate can't directly be tested. We could silently
|
||||
// ignore it, but adding it's own test crate is less confusing
|
||||
// for users. We still keep core itself for doctests.
|
||||
if crates.iter().any(|crate_| crate_ == "core") {
|
||||
crates.push("coretests".to_owned());
|
||||
}
|
||||
|
||||
run_cargo_test(cargo, &[], &crates, &*crate_description(&self.crates), target, builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2849,15 +2824,7 @@ impl Step for CrateRustdoc {
|
|||
dylib_path.insert(0, PathBuf::from(&*libdir));
|
||||
cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
|
||||
|
||||
run_cargo_test(
|
||||
cargo,
|
||||
&[],
|
||||
&["rustdoc:0.0.0".to_string()],
|
||||
"rustdoc",
|
||||
"rustdoc",
|
||||
target,
|
||||
builder,
|
||||
);
|
||||
run_cargo_test(cargo, &[], &["rustdoc:0.0.0".to_string()], "rustdoc", target, builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2914,7 +2881,6 @@ impl Step for CrateRustdocJsonTypes {
|
|||
libtest_args,
|
||||
&["rustdoc-json-types".to_string()],
|
||||
"rustdoc-json-types",
|
||||
"rustdoc-json-types",
|
||||
target,
|
||||
builder,
|
||||
);
|
||||
|
@ -3094,7 +3060,7 @@ impl Step for Bootstrap {
|
|||
|
||||
// bootstrap 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(cargo, &["--test-threads=1"], &[], "bootstrap", None, host, builder);
|
||||
run_cargo_test(cargo, &["--test-threads=1"], &[], None, host, builder);
|
||||
}
|
||||
|
||||
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
|
||||
|
@ -3219,7 +3185,7 @@ impl Step for RustInstaller {
|
|||
bootstrap_host,
|
||||
bootstrap_host,
|
||||
);
|
||||
run_cargo_test(cargo, &[], &[], "installer", None, bootstrap_host, builder);
|
||||
run_cargo_test(cargo, &[], &[], None, 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
|
||||
|
@ -3610,7 +3576,7 @@ impl Step for TestFloatParse {
|
|||
&[],
|
||||
);
|
||||
|
||||
run_cargo_test(cargo_test, &[], &[], crate_name, crate_name, bootstrap_host, builder);
|
||||
run_cargo_test(cargo_test, &[], &[], crate_name, bootstrap_host, builder);
|
||||
|
||||
// Run the actual parse tests.
|
||||
let mut cargo_run = tool::prepare_tool_cargo(
|
||||
|
|
|
@ -248,23 +248,32 @@ pub fn prepare_tool_cargo(
|
|||
cargo.env("CFG_VERSION", builder.rust_version());
|
||||
cargo.env("CFG_RELEASE_NUM", &builder.version);
|
||||
cargo.env("DOC_RUST_LANG_ORG_CHANNEL", builder.doc_rust_lang_org_channel());
|
||||
|
||||
if let Some(ref ver_date) = builder.rust_info().commit_date() {
|
||||
cargo.env("CFG_VER_DATE", ver_date);
|
||||
}
|
||||
|
||||
if let Some(ref ver_hash) = builder.rust_info().sha() {
|
||||
cargo.env("CFG_VER_HASH", ver_hash);
|
||||
}
|
||||
|
||||
if let Some(description) = &builder.config.description {
|
||||
cargo.env("CFG_VER_DESCRIPTION", description);
|
||||
}
|
||||
|
||||
let info = GitInfo::new(builder.config.omit_git_hash, &dir);
|
||||
if let Some(sha) = info.sha() {
|
||||
cargo.env("CFG_COMMIT_HASH", sha);
|
||||
}
|
||||
|
||||
if let Some(sha_short) = info.sha_short() {
|
||||
cargo.env("CFG_SHORT_COMMIT_HASH", sha_short);
|
||||
}
|
||||
|
||||
if let Some(date) = info.commit_date() {
|
||||
cargo.env("CFG_COMMIT_DATE", date);
|
||||
}
|
||||
|
||||
if !features.is_empty() {
|
||||
cargo.arg("--features").arg(features.join(", "));
|
||||
}
|
||||
|
|
|
@ -894,6 +894,7 @@ define_config! {
|
|||
#[derive(Default)]
|
||||
struct Build {
|
||||
build: Option<String> = "build",
|
||||
description: Option<String> = "description",
|
||||
host: Option<Vec<String>> = "host",
|
||||
target: Option<Vec<String>> = "target",
|
||||
build_dir: Option<String> = "build-dir",
|
||||
|
@ -1176,6 +1177,7 @@ define_config! {
|
|||
incremental: Option<bool> = "incremental",
|
||||
default_linker: Option<String> = "default-linker",
|
||||
channel: Option<String> = "channel",
|
||||
// FIXME: Remove this field at Q2 2025, it has been replaced by build.description
|
||||
description: Option<String> = "description",
|
||||
musl_root: Option<String> = "musl-root",
|
||||
rpath: Option<bool> = "rpath",
|
||||
|
@ -1583,6 +1585,7 @@ impl Config {
|
|||
config.change_id = toml.change_id.inner;
|
||||
|
||||
let Build {
|
||||
mut description,
|
||||
build,
|
||||
host,
|
||||
target,
|
||||
|
@ -1831,7 +1834,7 @@ impl Config {
|
|||
randomize_layout,
|
||||
default_linker,
|
||||
channel: _, // already handled above
|
||||
description,
|
||||
description: rust_description,
|
||||
musl_root,
|
||||
rpath,
|
||||
verbose_tests,
|
||||
|
@ -1924,7 +1927,12 @@ impl Config {
|
|||
set(&mut config.jemalloc, jemalloc);
|
||||
set(&mut config.test_compare_mode, test_compare_mode);
|
||||
set(&mut config.backtrace, backtrace);
|
||||
config.description = description;
|
||||
if rust_description.is_some() {
|
||||
eprintln!(
|
||||
"Warning: rust.description is deprecated. Use build.description instead."
|
||||
);
|
||||
}
|
||||
description = description.or(rust_description);
|
||||
set(&mut config.rust_dist_src, dist_src);
|
||||
set(&mut config.verbose_tests, verbose_tests);
|
||||
// in the case "false" is set explicitly, do not overwrite the command line args
|
||||
|
@ -1990,6 +1998,7 @@ impl Config {
|
|||
}
|
||||
|
||||
config.reproducible_artifacts = flags.reproducible_artifact;
|
||||
config.description = description;
|
||||
|
||||
// We need to override `rust.channel` if it's manually specified when using the CI rustc.
|
||||
// This is because if the compiler uses a different channel than the one specified in config.toml,
|
||||
|
|
|
@ -28,7 +28,6 @@ struct Package {
|
|||
source: Option<String>,
|
||||
manifest_path: String,
|
||||
dependencies: Vec<Dependency>,
|
||||
targets: Vec<Target>,
|
||||
features: BTreeMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
|
@ -40,11 +39,6 @@ struct Dependency {
|
|||
source: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Target {
|
||||
kind: Vec<String>,
|
||||
}
|
||||
|
||||
/// Collects and stores package metadata of each workspace members into `build`,
|
||||
/// by executing `cargo metadata` commands.
|
||||
pub fn build(build: &mut Build) {
|
||||
|
@ -59,12 +53,10 @@ pub fn build(build: &mut Build) {
|
|||
.filter(|dep| dep.source.is_none())
|
||||
.map(|dep| dep.name)
|
||||
.collect();
|
||||
let has_lib = package.targets.iter().any(|t| t.kind.iter().any(|k| k == "lib"));
|
||||
let krate = Crate {
|
||||
name: name.clone(),
|
||||
deps,
|
||||
path,
|
||||
has_lib,
|
||||
features: package.features.keys().cloned().collect(),
|
||||
};
|
||||
let relative_path = krate.local_path(build);
|
||||
|
|
|
@ -185,7 +185,6 @@ struct Crate {
|
|||
name: String,
|
||||
deps: HashSet<String>,
|
||||
path: PathBuf,
|
||||
has_lib: bool,
|
||||
features: Vec<String>,
|
||||
}
|
||||
|
||||
|
|
|
@ -365,4 +365,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
|
|||
severity: ChangeSeverity::Info,
|
||||
summary: "`rust.channel` now supports \"auto-detect\" to load the channel from `src/ci/channel`",
|
||||
},
|
||||
ChangeInfo {
|
||||
change_id: 137723,
|
||||
severity: ChangeSeverity::Info,
|
||||
summary: "The rust.description option has moved to build.description and rust.description is now deprecated.",
|
||||
},
|
||||
];
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
|
@ -38,7 +44,7 @@ version = "1.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -49,7 +55,7 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
|
|||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell",
|
||||
"windows-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -58,6 +64,12 @@ version = "1.0.95"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "build_helper"
|
||||
version = "0.1.0"
|
||||
|
@ -66,6 +78,27 @@ dependencies = [
|
|||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "citool"
|
||||
version = "0.1.0"
|
||||
|
@ -73,10 +106,12 @@ dependencies = [
|
|||
"anyhow",
|
||||
"build_helper",
|
||||
"clap",
|
||||
"csv",
|
||||
"insta",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -134,7 +169,95 @@ dependencies = [
|
|||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"windows-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie_store"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
|
||||
dependencies = [
|
||||
"cookie",
|
||||
"document-features",
|
||||
"idna",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
|
||||
dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -149,6 +272,42 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
|
@ -161,6 +320,162 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid_transform"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locid",
|
||||
"icu_locid_transform_data",
|
||||
"icu_provider",
|
||||
"tinystr",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid_transform_data"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"utf16_iter",
|
||||
"utf8_iter",
|
||||
"write16",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_locid_transform",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"tinystr",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locid",
|
||||
"icu_provider_macros",
|
||||
"stable_deref_trait",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider_macros"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.7.1"
|
||||
|
@ -208,18 +523,57 @@ version = "0.5.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.9"
|
||||
|
@ -240,6 +594,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
|
@ -258,6 +618,61 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.102.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.19"
|
||||
|
@ -309,18 +724,42 @@ dependencies = [
|
|||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
|
@ -332,6 +771,58 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb041120f25f8fbe8fd2dbe4671c7c2ed74d83be2e7a77529bf7e0790ae3f472"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.16"
|
||||
|
@ -344,12 +835,110 @@ version = "0.2.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "3.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06f78313c985f2fba11100dd06d60dd402d0cabb458af4d94791b8e09c025323"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cookie_store",
|
||||
"flate2",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ureq-proto",
|
||||
"utf-8",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ureq-proto"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64adb55464bad1ab1aa9229133d0d59d2f679180f4d15f0d9debe616f541f25e"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf16_iter"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
|
@ -422,3 +1011,88 @@ name = "windows_x86_64_msvc"
|
|||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "write16"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
|
|
@ -6,9 +6,11 @@ edition = "2021"
|
|||
[dependencies]
|
||||
anyhow = "1"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
csv = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_yaml = "0.9"
|
||||
serde_json = "1"
|
||||
ureq = { version = "3", features = ["json"] }
|
||||
|
||||
build_helper = { path = "../../build_helper" }
|
||||
|
||||
|
@ -19,3 +21,7 @@ insta = "1"
|
|||
# If this is omitted, cargo will look for a workspace elsewhere.
|
||||
# We want to avoid this, since citool is independent of the other crates.
|
||||
[workspace]
|
||||
|
||||
# Make compilation faster
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
|
|
24
src/ci/citool/src/cpu_usage.rs
Normal file
24
src/ci/citool/src/cpu_usage.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use std::path::Path;
|
||||
|
||||
/// Loads CPU usage records from a CSV generated by the `src/ci/scripts/collect-cpu-stats.sh`
|
||||
/// script.
|
||||
pub fn load_cpu_usage(path: &Path) -> anyhow::Result<Vec<f64>> {
|
||||
let reader = csv::ReaderBuilder::new().flexible(true).from_path(path)?;
|
||||
|
||||
let mut entries = vec![];
|
||||
for row in reader.into_records() {
|
||||
let row = row?;
|
||||
let cols = row.into_iter().collect::<Vec<&str>>();
|
||||
|
||||
// The log might contain incomplete rows or some Python exception
|
||||
if cols.len() == 2 {
|
||||
if let Ok(idle) = cols[1].parse::<f64>() {
|
||||
entries.push(100.0 - idle);
|
||||
} else {
|
||||
eprintln!("Warning: cannot parse CPU CSV entry {}", cols[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(entries)
|
||||
}
|
43
src/ci/citool/src/datadog.rs
Normal file
43
src/ci/citool/src/datadog.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use anyhow::Context;
|
||||
|
||||
use crate::utils::load_env_var;
|
||||
|
||||
/// Uploads a custom CI pipeline metric to Datadog.
|
||||
/// Expects to be executed from within the context of a GitHub Actions job.
|
||||
pub fn upload_datadog_metric(name: &str, value: f64) -> anyhow::Result<()> {
|
||||
let datadog_api_key = load_env_var("DATADOG_API_KEY")?;
|
||||
let github_server_url = load_env_var("GITHUB_SERVER_URL")?;
|
||||
let github_repository = load_env_var("GITHUB_REPOSITORY")?;
|
||||
let github_run_id = load_env_var("GITHUB_RUN_ID")?;
|
||||
let github_run_attempt = load_env_var("GITHUB_RUN_ATTEMPT")?;
|
||||
let github_job = load_env_var("GITHUB_JOB")?;
|
||||
let dd_github_job_name = load_env_var("DD_GITHUB_JOB_NAME")?;
|
||||
|
||||
// This API endpoint is not documented in Datadog's API reference currently.
|
||||
// It was reverse-engineered from the `datadog-ci measure` npm command.
|
||||
ureq::post("https://api.datadoghq.com/api/v2/ci/pipeline/metrics")
|
||||
.header("DD-API-KEY", datadog_api_key)
|
||||
.send_json(serde_json::json!({
|
||||
"data": {
|
||||
"attributes": {
|
||||
"ci_env": {
|
||||
"GITHUB_SERVER_URL": github_server_url,
|
||||
"GITHUB_REPOSITORY": github_repository,
|
||||
"GITHUB_RUN_ID": github_run_id,
|
||||
"GITHUB_RUN_ATTEMPT": github_run_attempt,
|
||||
"GITHUB_JOB": github_job,
|
||||
"DD_GITHUB_JOB_NAME": dd_github_job_name
|
||||
},
|
||||
// Job level
|
||||
"ci_level": 1,
|
||||
"metrics": {
|
||||
name: value
|
||||
},
|
||||
"provider": "github"
|
||||
},
|
||||
"type": "ci_custom_metric"
|
||||
}
|
||||
}))
|
||||
.context("cannot send metric to DataDog")?;
|
||||
Ok(())
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
mod cpu_usage;
|
||||
mod datadog;
|
||||
mod metrics;
|
||||
mod utils;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -8,7 +11,10 @@ use anyhow::Context;
|
|||
use clap::Parser;
|
||||
use serde_yaml::Value;
|
||||
|
||||
use crate::cpu_usage::load_cpu_usage;
|
||||
use crate::datadog::upload_datadog_metric;
|
||||
use crate::metrics::postprocess_metrics;
|
||||
use crate::utils::load_env_var;
|
||||
|
||||
const CI_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/..");
|
||||
const DOCKER_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../docker");
|
||||
|
@ -75,7 +81,7 @@ impl JobDatabase {
|
|||
}
|
||||
|
||||
fn load_job_db(path: &Path) -> anyhow::Result<JobDatabase> {
|
||||
let db = read_to_string(path)?;
|
||||
let db = utils::read_to_string(path)?;
|
||||
let mut db: Value = serde_yaml::from_str(&db)?;
|
||||
|
||||
// We need to expand merge keys (<<), because serde_yaml can't deal with them
|
||||
|
@ -148,10 +154,6 @@ impl GitHubContext {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_env_var(name: &str) -> anyhow::Result<String> {
|
||||
std::env::var(name).with_context(|| format!("Cannot find variable {name}"))
|
||||
}
|
||||
|
||||
fn load_github_ctx() -> anyhow::Result<GitHubContext> {
|
||||
let event_name = load_env_var("GITHUB_EVENT_NAME")?;
|
||||
let commit_message =
|
||||
|
@ -325,6 +327,18 @@ fn run_workflow_locally(db: JobDatabase, job_type: JobType, name: String) -> any
|
|||
if !result.success() { Err(anyhow::anyhow!("Job failed")) } else { Ok(()) }
|
||||
}
|
||||
|
||||
fn upload_ci_metrics(cpu_usage_csv: &Path) -> anyhow::Result<()> {
|
||||
let usage = load_cpu_usage(cpu_usage_csv).context("Cannot load CPU usage from input CSV")?;
|
||||
eprintln!("CPU usage\n{usage:?}");
|
||||
|
||||
let avg = if !usage.is_empty() { usage.iter().sum::<f64>() / usage.len() as f64 } else { 0.0 };
|
||||
eprintln!("CPU usage average: {avg}");
|
||||
|
||||
upload_datadog_metric("avg-cpu-usage", avg).context("Cannot upload Datadog metric")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
enum Args {
|
||||
/// Calculate a list of jobs that should be executed on CI.
|
||||
|
@ -350,6 +364,11 @@ enum Args {
|
|||
/// Usually, this will be GITHUB_STEP_SUMMARY on CI.
|
||||
summary_path: PathBuf,
|
||||
},
|
||||
/// Upload CI metrics to Datadog.
|
||||
UploadBuildMetrics {
|
||||
/// Path to a CSV containing the CI job CPU usage.
|
||||
cpu_usage_csv: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Clone)]
|
||||
|
@ -370,7 +389,7 @@ fn main() -> anyhow::Result<()> {
|
|||
let jobs_path = jobs_file.as_deref().unwrap_or(default_jobs_file);
|
||||
let gh_ctx = load_github_ctx()
|
||||
.context("Cannot load environment variables from GitHub Actions")?;
|
||||
let channel = read_to_string(Path::new(CI_DIRECTORY).join("channel"))
|
||||
let channel = utils::read_to_string(Path::new(CI_DIRECTORY).join("channel"))
|
||||
.context("Cannot read channel file")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
@ -379,7 +398,10 @@ fn main() -> anyhow::Result<()> {
|
|||
.context("Failed to calculate job matrix")?;
|
||||
}
|
||||
Args::RunJobLocally { job_type, name } => {
|
||||
run_workflow_locally(load_db(default_jobs_file)?, job_type, name)?
|
||||
run_workflow_locally(load_db(default_jobs_file)?, job_type, name)?;
|
||||
}
|
||||
Args::UploadBuildMetrics { cpu_usage_csv } => {
|
||||
upload_ci_metrics(&cpu_usage_csv)?;
|
||||
}
|
||||
Args::PostprocessMetrics { metrics_path, summary_path } => {
|
||||
postprocess_metrics(&metrics_path, &summary_path)?;
|
||||
|
@ -388,8 +410,3 @@ fn main() -> anyhow::Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_to_string<P: AsRef<Path>>(path: P) -> anyhow::Result<String> {
|
||||
let error = format!("Cannot read file {:?}", path.as_ref());
|
||||
std::fs::read_to_string(path).context(error)
|
||||
}
|
||||
|
|
11
src/ci/citool/src/utils.rs
Normal file
11
src/ci/citool/src/utils.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
pub fn load_env_var(name: &str) -> anyhow::Result<String> {
|
||||
std::env::var(name).with_context(|| format!("Cannot find environment variable `{name}`"))
|
||||
}
|
||||
|
||||
pub fn read_to_string<P: AsRef<Path>>(path: P) -> anyhow::Result<String> {
|
||||
std::fs::read_to_string(&path).with_context(|| format!("Cannot read file {:?}", path.as_ref()))
|
||||
}
|
5004
src/ci/package-lock.json
generated
5004
src/ci/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@datadog/datadog-ci": "^2.45.1"
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
"""
|
||||
This script postprocesses data gathered during a CI run, computes certain metrics
|
||||
from them, and uploads these metrics to DataDog.
|
||||
|
||||
This script is expected to be executed from within a GitHub Actions job.
|
||||
|
||||
It expects the following environment variables:
|
||||
- DATADOG_SITE: path to the DataDog API endpoint
|
||||
- DATADOG_API_KEY: DataDog API token
|
||||
- DD_GITHUB_JOB_NAME: Name of the current GitHub Actions job
|
||||
|
||||
It expects the presence of a binary called `datadog-ci` inside `node_modules`.
|
||||
It can be installed with `npm ci` at `src/ci`.
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
$ python3 upload-build-metrics.py <path-to-CPU-usage-CSV>
|
||||
```
|
||||
|
||||
`path-to-CPU-usage-CSV` is a path to a CSV generated by the `src/ci/cpu-usage-over-time.py` script.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
|
||||
def load_cpu_usage(path: Path) -> List[float]:
|
||||
usage = []
|
||||
with open(path) as f:
|
||||
reader = csv.reader(f, delimiter=",")
|
||||
for row in reader:
|
||||
# The log might contain incomplete rows or some Python exception
|
||||
if len(row) == 2:
|
||||
try:
|
||||
idle = float(row[1])
|
||||
usage.append(100.0 - idle)
|
||||
except ValueError:
|
||||
pass
|
||||
return usage
|
||||
|
||||
|
||||
def upload_datadog_measure(name: str, value: float):
|
||||
"""
|
||||
Uploads a single numeric metric for the current GitHub Actions job to DataDog.
|
||||
"""
|
||||
print(f"Metric {name}: {value:.4f}")
|
||||
|
||||
cmd = "npx"
|
||||
if os.getenv("GITHUB_ACTIONS") is not None and sys.platform.lower().startswith(
|
||||
"win"
|
||||
):
|
||||
# Due to weird interaction of MSYS2 and Python, we need to use an absolute path,
|
||||
# and also specify the ".cmd" at the end. See https://github.com/rust-lang/rust/pull/125771.
|
||||
cmd = "C:\\Program Files\\nodejs\\npx.cmd"
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
cmd,
|
||||
"datadog-ci",
|
||||
"measure",
|
||||
"--level",
|
||||
"job",
|
||||
"--measures",
|
||||
f"{name}:{value}",
|
||||
],
|
||||
check=False,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(prog="DataDog metric uploader")
|
||||
parser.add_argument("cpu-usage-history-csv")
|
||||
args = parser.parse_args()
|
||||
|
||||
build_usage_csv = vars(args)["cpu-usage-history-csv"]
|
||||
usage_timeseries = load_cpu_usage(Path(build_usage_csv))
|
||||
if len(usage_timeseries) > 0:
|
||||
avg_cpu_usage = sum(usage_timeseries) / len(usage_timeseries)
|
||||
else:
|
||||
avg_cpu_usage = 0
|
||||
upload_datadog_measure("avg-cpu-usage", avg_cpu_usage)
|
|
@ -74,8 +74,7 @@ The following test suites are available, with links for more information:
|
|||
|
||||
### General purpose test suite
|
||||
|
||||
[`run-make`](#run-make-tests) are general purpose tests using Rust programs (or
|
||||
Makefiles (legacy)).
|
||||
[`run-make`](#run-make-tests) are general purpose tests using Rust programs.
|
||||
|
||||
### Rustdoc test suites
|
||||
|
||||
|
@ -396,14 +395,6 @@ your test, causing separate files to be generated for 32bit and 64bit systems.
|
|||
|
||||
### `run-make` tests
|
||||
|
||||
> **Note on phasing out `Makefile`s**
|
||||
>
|
||||
> We are planning to migrate all existing Makefile-based `run-make` tests
|
||||
> to Rust programs. You should not be adding new Makefile-based `run-make`
|
||||
> tests.
|
||||
>
|
||||
> See <https://github.com/rust-lang/rust/issues/121876>.
|
||||
|
||||
The tests in [`tests/run-make`] are general-purpose tests using Rust *recipes*,
|
||||
which are small programs (`rmake.rs`) allowing arbitrary Rust code such as
|
||||
`rustc` invocations, and is supported by a [`run_make_support`] library. Using
|
||||
|
@ -424,11 +415,6 @@ Compiletest directives like `//@ only-<target>` or `//@ ignore-<target>` are
|
|||
supported in `rmake.rs`, like in UI tests. However, revisions or building
|
||||
auxiliary via directives are not currently supported.
|
||||
|
||||
Two `run-make` tests are ported over to Rust recipes as examples:
|
||||
|
||||
- <https://github.com/rust-lang/rust/tree/master/tests/run-make/CURRENT_RUSTC_VERSION>
|
||||
- <https://github.com/rust-lang/rust/tree/master/tests/run-make/a-b-a-linker-guard>
|
||||
|
||||
#### Quickly check if `rmake.rs` tests can be compiled
|
||||
|
||||
You can quickly check if `rmake.rs` tests can be compiled without having to
|
||||
|
@ -481,20 +467,6 @@ Then add a corresponding entry to `"rust-analyzer.linkedProjects"`
|
|||
],
|
||||
```
|
||||
|
||||
#### Using Makefiles (legacy)
|
||||
|
||||
<div class="warning">
|
||||
You should avoid writing new Makefile-based `run-make` tests.
|
||||
</div>
|
||||
|
||||
Each test should be in a separate directory with a `Makefile` indicating the
|
||||
commands to run.
|
||||
|
||||
There is a [`tools.mk`] Makefile which you can include which provides a bunch of
|
||||
utilities to make it easier to run commands and compare outputs. Take a look at
|
||||
some of the other tests for some examples on how to get started.
|
||||
|
||||
[`tools.mk`]: https://github.com/rust-lang/rust/blob/master/tests/run-make/tools.mk
|
||||
[`tests/run-make`]: https://github.com/rust-lang/rust/tree/master/tests/run-make
|
||||
[`run_make_support`]: https://github.com/rust-lang/rust/tree/master/src/tools/run-make-support
|
||||
|
||||
|
|
|
@ -6,10 +6,7 @@
|
|||
FIXME(jieyouxu) completely revise this chapter.
|
||||
-->
|
||||
|
||||
Directives are special comments that tell compiletest how to build and interpret
|
||||
a test. They must appear before the Rust source in the test. They may also
|
||||
appear in `rmake.rs` or legacy Makefiles for [run-make
|
||||
tests](compiletest.md#run-make-tests).
|
||||
Directives are special comments that tell compiletest how to build and interpret a test. They must appear before the Rust source in the test. They may also appear in `rmake.rs` [run-make tests](compiletest.md#run-make-tests).
|
||||
|
||||
They are normally put after the short comment that explains the point of this
|
||||
test. Compiletest test suites use `//@` to signal that a comment is a directive.
|
||||
|
@ -222,8 +219,6 @@ The following directives will check LLVM support:
|
|||
[`aarch64-gnu-debug`]), which only runs a
|
||||
subset of `run-make` tests. Other tests with this directive will not
|
||||
run at all, which is usually not what you want.
|
||||
- Notably, the [`aarch64-gnu-debug`] CI job *currently* only runs `run-make`
|
||||
tests which additionally contain `clang` in their test name.
|
||||
|
||||
See also [Debuginfo tests](compiletest.md#debuginfo-tests) for directives for
|
||||
ignoring debuggers.
|
||||
|
|
|
@ -238,30 +238,6 @@ This is much faster, but doesn't always work. For example, some tests include
|
|||
directives that specify specific compiler flags, or which rely on other crates,
|
||||
and they may not run the same without those options.
|
||||
|
||||
## Running `run-make` tests
|
||||
|
||||
### Windows
|
||||
|
||||
Running the `run-make` test suite on Windows is a currently bit more involved.
|
||||
There are numerous prerequisites and environmental requirements:
|
||||
|
||||
- Install msys2: <https://www.msys2.org/>
|
||||
- Specify `MSYS2_PATH_TYPE=inherit` in `msys2.ini` in the msys2 installation directory, run the
|
||||
following with `MSYS2 MSYS`:
|
||||
- `pacman -Syuu`
|
||||
- `pacman -S make`
|
||||
- `pacman -S diffutils`
|
||||
- `pacman -S binutils`
|
||||
- `./x test run-make` (`./x test tests/run-make` doesn't work)
|
||||
|
||||
There is [on-going work][port-run-make] to not rely on `Makefile`s in the
|
||||
run-make test suite. Once this work is completed, you can run the entire
|
||||
`run-make` test suite on native Windows inside `cmd` or `PowerShell` without
|
||||
needing to install and use MSYS2. As of <!--date-check --> Oct 2024, it is
|
||||
already possible to run the vast majority of the `run-make` test suite outside
|
||||
of MSYS2, but there will be failures for the tests that still use `Makefile`s
|
||||
due to not finding `make`.
|
||||
|
||||
## Running tests on a remote machine
|
||||
|
||||
Tests may be run on a remote machine (e.g. to test builds for a different
|
||||
|
@ -406,4 +382,3 @@ If you encounter bugs or problems, don't hesitate to open issues on the
|
|||
repository](https://github.com/rust-lang/rustc_codegen_gcc/).
|
||||
|
||||
[`tests/ui`]: https://github.com/rust-lang/rust/tree/master/tests/ui
|
||||
[port-run-make]: https://github.com/rust-lang/rust/issues/121876
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
# `unsized_tuple_coercion`
|
||||
|
||||
The tracking issue for this feature is: [#42877]
|
||||
|
||||
[#42877]: https://github.com/rust-lang/rust/issues/42877
|
||||
|
||||
------------------------
|
||||
|
||||
This is a part of [RFC0401]. According to the RFC, there should be an implementation like this:
|
||||
|
||||
```rust,ignore (partial-example)
|
||||
impl<..., T, U: ?Sized> Unsized<(..., U)> for (..., T) where T: Unsized<U> {}
|
||||
```
|
||||
|
||||
This implementation is currently gated behind `#[feature(unsized_tuple_coercion)]` to avoid insta-stability. Therefore you can use it like this:
|
||||
|
||||
```rust
|
||||
#![feature(unsized_tuple_coercion)]
|
||||
|
||||
fn main() {
|
||||
let x : ([i32; 3], [i32; 3]) = ([1, 2, 3], [4, 5, 6]);
|
||||
let y : &([i32; 3], [i32]) = &x;
|
||||
assert_eq!(y.1[0], 4);
|
||||
}
|
||||
```
|
||||
|
||||
[RFC0401]: https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md
|
|
@ -147,12 +147,12 @@ pub trait Float:
|
|||
}
|
||||
|
||||
macro_rules! impl_float {
|
||||
($($fty:ty, $ity:ty, $bits:literal);+) => {
|
||||
($($fty:ty, $ity:ty);+) => {
|
||||
$(
|
||||
impl Float for $fty {
|
||||
type Int = $ity;
|
||||
type SInt = <Self::Int as Int>::Signed;
|
||||
const BITS: u32 = $bits;
|
||||
const BITS: u32 = <$ity>::BITS;
|
||||
const MAN_BITS: u32 = Self::MANTISSA_DIGITS - 1;
|
||||
const MAN_MASK: Self::Int = (Self::Int::ONE << Self::MAN_BITS) - Self::Int::ONE;
|
||||
const SIGN_MASK: Self::Int = Self::Int::ONE << (Self::BITS-1);
|
||||
|
@ -168,7 +168,7 @@ macro_rules! impl_float {
|
|||
}
|
||||
}
|
||||
|
||||
impl_float!(f32, u32, 32; f64, u64, 64);
|
||||
impl_float!(f32, u32; f64, u64);
|
||||
|
||||
/// A test generator. Should provide an iterator that produces unique patterns to parse.
|
||||
///
|
||||
|
|
|
@ -709,11 +709,11 @@ impl TestProps {
|
|||
/// returns a struct containing various parts of the directive.
|
||||
fn line_directive<'line>(
|
||||
line_number: usize,
|
||||
comment: &str,
|
||||
original_line: &'line str,
|
||||
) -> Option<DirectiveLine<'line>> {
|
||||
// Ignore lines that don't start with the comment prefix.
|
||||
let after_comment = original_line.trim_start().strip_prefix(comment)?.trim_start();
|
||||
let after_comment =
|
||||
original_line.trim_start().strip_prefix(COMPILETEST_DIRECTIVE_PREFIX)?.trim_start();
|
||||
|
||||
let revision;
|
||||
let raw_directive;
|
||||
|
@ -722,7 +722,7 @@ fn line_directive<'line>(
|
|||
// A comment like `//@[foo]` only applies to revision `foo`.
|
||||
let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else {
|
||||
panic!(
|
||||
"malformed condition directive: expected `{comment}[foo]`, found `{original_line}`"
|
||||
"malformed condition directive: expected `{COMPILETEST_DIRECTIVE_PREFIX}[foo]`, found `{original_line}`"
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -836,6 +836,8 @@ pub(crate) fn check_directive<'a>(
|
|||
CheckDirectiveResult { is_known_directive: is_known(&directive_name), trailing_directive }
|
||||
}
|
||||
|
||||
const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@";
|
||||
|
||||
fn iter_header(
|
||||
mode: Mode,
|
||||
_suite: &str,
|
||||
|
@ -849,8 +851,7 @@ fn iter_header(
|
|||
}
|
||||
|
||||
// Coverage tests in coverage-run mode always have these extra directives, without needing to
|
||||
// specify them manually in every test file. (Some of the comments below have been copied over
|
||||
// from the old `tests/run-make/coverage-reports/Makefile`, which no longer exists.)
|
||||
// specify them manually in every test file.
|
||||
//
|
||||
// FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later.
|
||||
if mode == Mode::CoverageRun {
|
||||
|
@ -867,9 +868,6 @@ fn iter_header(
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE(jieyouxu): once we get rid of `Makefile`s we can unconditionally check for `//@`.
|
||||
let comment = if testfile.extension().is_some_and(|e| e == "rs") { "//@" } else { "#" };
|
||||
|
||||
let mut rdr = BufReader::with_capacity(1024, rdr);
|
||||
let mut ln = String::new();
|
||||
let mut line_number = 0;
|
||||
|
@ -882,7 +880,7 @@ fn iter_header(
|
|||
}
|
||||
let ln = ln.trim();
|
||||
|
||||
let Some(directive_line) = line_directive(line_number, comment, ln) else {
|
||||
let Some(directive_line) = line_directive(line_number, ln) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
|
|
@ -239,11 +239,6 @@ fn check_ignore(config: &Config, contents: &str) -> bool {
|
|||
d.ignore
|
||||
}
|
||||
|
||||
fn parse_makefile(config: &Config, contents: &str) -> EarlyProps {
|
||||
let bytes = contents.as_bytes();
|
||||
EarlyProps::from_reader(config, Path::new("Makefile"), bytes)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail() {
|
||||
let config: Config = cfg().build();
|
||||
|
@ -261,10 +256,6 @@ fn revisions() {
|
|||
let config: Config = cfg().build();
|
||||
|
||||
assert_eq!(parse_rs(&config, "//@ revisions: a b c").revisions, vec!["a", "b", "c"],);
|
||||
assert_eq!(
|
||||
parse_makefile(&config, "# revisions: hello there").revisions,
|
||||
vec!["hello", "there"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -21,7 +21,7 @@ pub mod util;
|
|||
|
||||
use core::panic;
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::ffi::OsString;
|
||||
use std::io::{self, ErrorKind};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
|
@ -274,12 +274,8 @@ pub fn parse_config(args: Vec<String>) -> Config {
|
|||
let path = Path::new(f);
|
||||
let mut iter = path.iter().skip(1);
|
||||
|
||||
// We skip the test folder and check if the user passed `rmake.rs` or `Makefile`.
|
||||
if iter
|
||||
.next()
|
||||
.is_some_and(|s| s == OsStr::new("rmake.rs") || s == OsStr::new("Makefile"))
|
||||
&& iter.next().is_none()
|
||||
{
|
||||
// We skip the test folder and check if the user passed `rmake.rs`.
|
||||
if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
|
||||
path.parent().unwrap().to_str().unwrap().to_string()
|
||||
} else {
|
||||
f.to_string()
|
||||
|
@ -783,16 +779,9 @@ fn collect_tests_from_dir(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
// For run-make tests, a "test file" is actually a directory that contains
|
||||
// an `rmake.rs` or `Makefile`"
|
||||
// For run-make tests, a "test file" is actually a directory that contains an `rmake.rs`.
|
||||
if cx.config.mode == Mode::RunMake {
|
||||
if dir.join("Makefile").exists() && dir.join("rmake.rs").exists() {
|
||||
return Err(io::Error::other(
|
||||
"run-make tests cannot have both `Makefile` and `rmake.rs`",
|
||||
));
|
||||
}
|
||||
|
||||
if dir.join("Makefile").exists() || dir.join("rmake.rs").exists() {
|
||||
if dir.join("rmake.rs").exists() {
|
||||
let paths = TestPaths {
|
||||
file: dir.to_path_buf(),
|
||||
relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
|
||||
|
@ -861,24 +850,14 @@ pub fn is_test(file_name: &OsString) -> bool {
|
|||
!invalid_prefixes.iter().any(|p| file_name.starts_with(p))
|
||||
}
|
||||
|
||||
/// For a single test file, creates one or more test structures (one per revision)
|
||||
/// that can be handed over to libtest to run, possibly in parallel.
|
||||
/// For a single test file, creates one or more test structures (one per revision) that can be
|
||||
/// handed over to libtest to run, possibly in parallel.
|
||||
fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
|
||||
// For run-make tests, each "test file" is actually a _directory_ containing
|
||||
// an `rmake.rs` or `Makefile`. But for the purposes of directive parsing,
|
||||
// we want to look at that recipe file, not the directory itself.
|
||||
// For run-make tests, each "test file" is actually a _directory_ containing an `rmake.rs`. But
|
||||
// for the purposes of directive parsing, we want to look at that recipe file, not the directory
|
||||
// itself.
|
||||
let test_path = if cx.config.mode == Mode::RunMake {
|
||||
if testpaths.file.join("rmake.rs").exists() && testpaths.file.join("Makefile").exists() {
|
||||
panic!("run-make tests cannot have both `rmake.rs` and `Makefile`");
|
||||
}
|
||||
|
||||
if testpaths.file.join("rmake.rs").exists() {
|
||||
// Parse directives in rmake.rs.
|
||||
testpaths.file.join("rmake.rs")
|
||||
} else {
|
||||
// Parse directives in the Makefile.
|
||||
testpaths.file.join("Makefile")
|
||||
}
|
||||
testpaths.file.join("rmake.rs")
|
||||
} else {
|
||||
PathBuf::from(&testpaths.file)
|
||||
};
|
||||
|
|
|
@ -2412,8 +2412,9 @@ impl<'test> TestCx<'test> {
|
|||
let rust_src_dir = rust_src_dir.read_link().unwrap_or(rust_src_dir.to_path_buf());
|
||||
normalize_path(&rust_src_dir.join("library"), "$SRC_DIR_REAL");
|
||||
|
||||
// eg. /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui
|
||||
normalize_path(&self.config.build_test_suite_root, "$TEST_BUILD_DIR");
|
||||
// eg.
|
||||
// /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui/<test_dir>/$name.$revision.$mode/
|
||||
normalize_path(&self.output_base_dir(), "$TEST_BUILD_DIR");
|
||||
// eg. /home/user/rust/build
|
||||
normalize_path(&self.config.build_root, "$BUILD_DIR");
|
||||
|
||||
|
@ -2434,6 +2435,18 @@ impl<'test> TestCx<'test> {
|
|||
.into_owned();
|
||||
|
||||
normalized = Self::normalize_platform_differences(&normalized);
|
||||
|
||||
// Normalize long type name hash.
|
||||
normalized =
|
||||
static_regex!(r"\$TEST_BUILD_DIR/(?P<filename>[^\.]+).long-type-(?P<hash>\d+).txt")
|
||||
.replace_all(&normalized, |caps: &Captures<'_>| {
|
||||
format!(
|
||||
"$TEST_BUILD_DIR/{filename}.long-type-$LONG_TYPE_HASH.txt",
|
||||
filename = &caps["filename"]
|
||||
)
|
||||
})
|
||||
.into_owned();
|
||||
|
||||
normalized = normalized.replace("\t", "\\t"); // makes tabs visible
|
||||
|
||||
// Remove test annotations like `//~ ERROR text` from the output,
|
||||
|
|
|
@ -9,168 +9,6 @@ use crate::util::{copy_dir_all, dylib_env_var};
|
|||
|
||||
impl TestCx<'_> {
|
||||
pub(super) fn run_rmake_test(&self) {
|
||||
let test_dir = &self.testpaths.file;
|
||||
if test_dir.join("rmake.rs").exists() {
|
||||
self.run_rmake_v2_test();
|
||||
} else if test_dir.join("Makefile").exists() {
|
||||
self.run_rmake_legacy_test();
|
||||
} else {
|
||||
self.fatal("failed to find either `rmake.rs` or `Makefile`")
|
||||
}
|
||||
}
|
||||
|
||||
fn run_rmake_legacy_test(&self) {
|
||||
let cwd = env::current_dir().unwrap();
|
||||
|
||||
// FIXME(Zalathar): This should probably be `output_base_dir` to avoid
|
||||
// an unnecessary extra subdirectory, but since legacy Makefile tests
|
||||
// are hopefully going away, it seems safer to leave this perilous code
|
||||
// as-is until it can all be deleted.
|
||||
let tmpdir = cwd.join(self.output_base_name());
|
||||
ignore_not_found(|| recursive_remove(&tmpdir)).unwrap();
|
||||
|
||||
fs::create_dir_all(&tmpdir).unwrap();
|
||||
|
||||
let host = &self.config.host;
|
||||
let make = if host.contains("dragonfly")
|
||||
|| host.contains("freebsd")
|
||||
|| host.contains("netbsd")
|
||||
|| host.contains("openbsd")
|
||||
|| host.contains("aix")
|
||||
{
|
||||
"gmake"
|
||||
} else {
|
||||
"make"
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(make);
|
||||
cmd.current_dir(&self.testpaths.file)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.env("TARGET", &self.config.target)
|
||||
.env("PYTHON", &self.config.python)
|
||||
.env("S", &self.config.src_root)
|
||||
.env("RUST_BUILD_STAGE", &self.config.stage_id)
|
||||
.env("RUSTC", cwd.join(&self.config.rustc_path))
|
||||
.env("TMPDIR", &tmpdir)
|
||||
.env("LD_LIB_PATH_ENVVAR", dylib_env_var())
|
||||
.env("HOST_RPATH_DIR", cwd.join(&self.config.compile_lib_path))
|
||||
.env("TARGET_RPATH_DIR", cwd.join(&self.config.run_lib_path))
|
||||
.env("LLVM_COMPONENTS", &self.config.llvm_components)
|
||||
// We for sure don't want these tests to run in parallel, so make
|
||||
// sure they don't have access to these vars if we run via `make`
|
||||
// at the top level
|
||||
.env_remove("MAKEFLAGS")
|
||||
.env_remove("MFLAGS")
|
||||
.env_remove("CARGO_MAKEFLAGS");
|
||||
|
||||
if let Some(ref cargo) = self.config.cargo_path {
|
||||
cmd.env("CARGO", cwd.join(cargo));
|
||||
}
|
||||
|
||||
if let Some(ref rustdoc) = self.config.rustdoc_path {
|
||||
cmd.env("RUSTDOC", cwd.join(rustdoc));
|
||||
}
|
||||
|
||||
if let Some(ref node) = self.config.nodejs {
|
||||
cmd.env("NODE", node);
|
||||
}
|
||||
|
||||
if let Some(ref linker) = self.config.target_linker {
|
||||
cmd.env("RUSTC_LINKER", linker);
|
||||
}
|
||||
|
||||
if let Some(ref clang) = self.config.run_clang_based_tests_with {
|
||||
cmd.env("CLANG", clang);
|
||||
}
|
||||
|
||||
if let Some(ref filecheck) = self.config.llvm_filecheck {
|
||||
cmd.env("LLVM_FILECHECK", filecheck);
|
||||
}
|
||||
|
||||
if let Some(ref llvm_bin_dir) = self.config.llvm_bin_dir {
|
||||
cmd.env("LLVM_BIN_DIR", llvm_bin_dir);
|
||||
}
|
||||
|
||||
if let Some(ref remote_test_client) = self.config.remote_test_client {
|
||||
cmd.env("REMOTE_TEST_CLIENT", remote_test_client);
|
||||
}
|
||||
|
||||
// We don't want RUSTFLAGS set from the outside to interfere with
|
||||
// compiler flags set in the test cases:
|
||||
cmd.env_remove("RUSTFLAGS");
|
||||
|
||||
// Use dynamic musl for tests because static doesn't allow creating dylibs
|
||||
if self.config.host.contains("musl") {
|
||||
cmd.env("RUSTFLAGS", "-Ctarget-feature=-crt-static").env("IS_MUSL_HOST", "1");
|
||||
}
|
||||
|
||||
if self.config.bless {
|
||||
cmd.env("RUSTC_BLESS_TEST", "--bless");
|
||||
// Assume this option is active if the environment variable is "defined", with _any_ value.
|
||||
// As an example, a `Makefile` can use this option by:
|
||||
//
|
||||
// ifdef RUSTC_BLESS_TEST
|
||||
// cp "$(TMPDIR)"/actual_something.ext expected_something.ext
|
||||
// else
|
||||
// $(DIFF) expected_something.ext "$(TMPDIR)"/actual_something.ext
|
||||
// endif
|
||||
}
|
||||
|
||||
if self.config.target.contains("msvc") && !self.config.cc.is_empty() {
|
||||
// We need to pass a path to `lib.exe`, so assume that `cc` is `cl.exe`
|
||||
// and that `lib.exe` lives next to it.
|
||||
let lib = Path::new(&self.config.cc).parent().unwrap().join("lib.exe");
|
||||
|
||||
// MSYS doesn't like passing flags of the form `/foo` as it thinks it's
|
||||
// a path and instead passes `C:\msys64\foo`, so convert all
|
||||
// `/`-arguments to MSVC here to `-` arguments.
|
||||
let cflags = self
|
||||
.config
|
||||
.cflags
|
||||
.split(' ')
|
||||
.map(|s| s.replace("/", "-"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let cxxflags = self
|
||||
.config
|
||||
.cxxflags
|
||||
.split(' ')
|
||||
.map(|s| s.replace("/", "-"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
|
||||
cmd.env("IS_MSVC", "1")
|
||||
.env("IS_WINDOWS", "1")
|
||||
.env("MSVC_LIB", format!("'{}' -nologo", lib.display()))
|
||||
.env("MSVC_LIB_PATH", format!("{}", lib.display()))
|
||||
.env("CC", format!("'{}' {}", self.config.cc, cflags))
|
||||
.env("CXX", format!("'{}' {}", &self.config.cxx, cxxflags));
|
||||
} else {
|
||||
cmd.env("CC", format!("{} {}", self.config.cc, self.config.cflags))
|
||||
.env("CXX", format!("{} {}", self.config.cxx, self.config.cxxflags))
|
||||
.env("AR", &self.config.ar);
|
||||
|
||||
if self.config.target.contains("windows") {
|
||||
cmd.env("IS_WINDOWS", "1");
|
||||
}
|
||||
}
|
||||
|
||||
let (output, truncated) =
|
||||
self.read2_abbreviated(cmd.spawn().expect("failed to spawn `make`"));
|
||||
if !output.status.success() {
|
||||
let res = ProcRes {
|
||||
status: output.status,
|
||||
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
|
||||
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
|
||||
truncated,
|
||||
cmdline: format!("{:?}", cmd),
|
||||
};
|
||||
self.fatal_proc_rec("make failed", &res);
|
||||
}
|
||||
}
|
||||
|
||||
fn run_rmake_v2_test(&self) {
|
||||
// For `run-make` V2, we need to perform 2 steps to build and run a `run-make` V2 recipe
|
||||
// (`rmake.rs`) to run the actual tests. The support library is already built as a tool rust
|
||||
// library and is available under
|
||||
|
@ -192,8 +30,6 @@ impl TestCx<'_> {
|
|||
// recipes to `remove_dir_all($TMPDIR)` without running into issues related trying to remove
|
||||
// a currently running executable because the recipe executable is not under the
|
||||
// `rmake_out/` directory.
|
||||
//
|
||||
// This setup intentionally diverges from legacy Makefile run-make tests.
|
||||
let base_dir = self.output_base_dir();
|
||||
ignore_not_found(|| recursive_remove(&base_dir)).unwrap();
|
||||
|
||||
|
|
|
@ -1,22 +1,8 @@
|
|||
//@revisions: stack tree
|
||||
//@[tree]compile-flags: -Zmiri-tree-borrows
|
||||
#![feature(unsized_tuple_coercion)]
|
||||
#![feature(unsized_fn_params)]
|
||||
#![feature(custom_mir, core_intrinsics)]
|
||||
|
||||
use std::mem;
|
||||
|
||||
fn unsized_tuple() {
|
||||
let x: &(i32, i32, [i32]) = &(0, 1, [2, 3]);
|
||||
let y: &(i32, i32, [i32]) = &(0, 1, [2, 3, 4]);
|
||||
let mut a = [y, x];
|
||||
a.sort();
|
||||
assert_eq!(a, [x, y]);
|
||||
|
||||
assert_eq!(&format!("{:?}", a), "[(0, 1, [2, 3]), (0, 1, [2, 3, 4])]");
|
||||
assert_eq!(mem::size_of_val(x), 16);
|
||||
}
|
||||
|
||||
fn unsized_params() {
|
||||
pub fn f0(_f: dyn FnOnce()) {}
|
||||
pub fn f1(_s: str) {}
|
||||
|
@ -56,7 +42,6 @@ fn unsized_field_projection() {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
unsized_tuple();
|
||||
unsized_params();
|
||||
unsized_field_projection();
|
||||
}
|
||||
|
|
|
@ -8,23 +8,6 @@ use crate::targets::is_msvc;
|
|||
/// Construct the static library name based on the target.
|
||||
#[must_use]
|
||||
pub fn static_lib_name(name: &str) -> String {
|
||||
// See tools.mk (irrelevant lines omitted):
|
||||
//
|
||||
// ```makefile
|
||||
// ifeq ($(UNAME),Darwin)
|
||||
// STATICLIB = $(TMPDIR)/lib$(1).a
|
||||
// else
|
||||
// ifdef IS_WINDOWS
|
||||
// ifdef IS_MSVC
|
||||
// STATICLIB = $(TMPDIR)/$(1).lib
|
||||
// else
|
||||
// STATICLIB = $(TMPDIR)/lib$(1).a
|
||||
// endif
|
||||
// else
|
||||
// STATICLIB = $(TMPDIR)/lib$(1).a
|
||||
// endif
|
||||
// endif
|
||||
// ```
|
||||
assert!(!name.contains(char::is_whitespace), "static library name cannot contain whitespace");
|
||||
|
||||
if is_msvc() { format!("{name}.lib") } else { format!("lib{name}.a") }
|
||||
|
|
|
@ -80,17 +80,6 @@ impl Cc {
|
|||
|
||||
/// Specify `-o` or `-Fe`/`-Fo` depending on platform/compiler.
|
||||
pub fn out_exe(&mut self, name: &str) -> &mut Self {
|
||||
// Ref: tools.mk (irrelevant lines omitted):
|
||||
//
|
||||
// ```makefile
|
||||
// ifdef IS_MSVC
|
||||
// OUT_EXE=-Fe:`cygpath -w $(TMPDIR)/$(call BIN,$(1))` \
|
||||
// -Fo:`cygpath -w $(TMPDIR)/$(1).obj`
|
||||
// else
|
||||
// OUT_EXE=-o $(TMPDIR)/$(1)
|
||||
// endif
|
||||
// ```
|
||||
|
||||
let mut path = std::path::PathBuf::from(name);
|
||||
|
||||
if is_msvc() {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue