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 }}
|
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||||
run: |
|
run: |
|
||||||
cd src/ci/citool
|
cd src/ci/citool
|
||||||
cargo test
|
CARGO_INCREMENTAL=0 cargo test
|
||||||
cargo run calculate-job-matrix >> $GITHUB_OUTPUT
|
CARGO_INCREMENTAL=0 cargo run calculate-job-matrix >> $GITHUB_OUTPUT
|
||||||
id: jobs
|
id: jobs
|
||||||
job:
|
job:
|
||||||
name: ${{ matrix.full_name }}
|
name: ${{ matrix.full_name }}
|
||||||
|
@ -183,11 +183,11 @@ jobs:
|
||||||
run: src/ci/scripts/dump-environment.sh
|
run: src/ci/scripts/dump-environment.sh
|
||||||
|
|
||||||
# Pre-build citool before the following step uninstalls rustup
|
# 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
|
- name: build citool
|
||||||
run: |
|
run: |
|
||||||
cd src/ci/citool
|
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
|
- name: run the build
|
||||||
# Redirect stderr to stdout to avoid reordering the two streams in the GHA logs.
|
# 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
|
- name: upload job metrics to DataDog
|
||||||
if: needs.calculate_matrix.outputs.run_type != 'pr'
|
if: needs.calculate_matrix.outputs.run_type != 'pr'
|
||||||
env:
|
env:
|
||||||
DATADOG_SITE: datadoghq.com
|
|
||||||
DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }}
|
DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }}
|
||||||
DD_GITHUB_JOB_NAME: ${{ matrix.full_name }}
|
DD_GITHUB_JOB_NAME: ${{ matrix.full_name }}
|
||||||
run: |
|
run: ./build/citool/debug/citool upload-build-metrics build/cpu-usage.csv
|
||||||
cd src/ci
|
|
||||||
npm ci
|
|
||||||
python3 scripts/upload-build-metrics.py ../../build/cpu-usage.csv
|
|
||||||
|
|
||||||
# This job isused to tell bors the final status of the build, as there is no practical way to detect
|
# 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).
|
# 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 {
|
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 {
|
pub fn supports_varargs(self) -> bool {
|
||||||
// * C and Cdecl obviously support varargs.
|
// * C and Cdecl obviously support varargs.
|
||||||
// * C can be based on Aapcs, SysV64 or Win64, so they must 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,
|
E0801: 0801,
|
||||||
E0802: 0802,
|
E0802: 0802,
|
||||||
E0803: 0803,
|
E0803: 0803,
|
||||||
|
E0804: 0804,
|
||||||
);
|
);
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -244,6 +244,8 @@ declare_features! (
|
||||||
/// Allows unnamed fields of struct and union type
|
/// Allows unnamed fields of struct and union type
|
||||||
(removed, unnamed_fields, "1.83.0", Some(49804), Some("feature needs redesign")),
|
(removed, unnamed_fields, "1.83.0", Some(49804), Some("feature needs redesign")),
|
||||||
(removed, unsafe_no_drop_flag, "1.0.0", None, None),
|
(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.
|
/// 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),
|
(removed, untagged_unions, "1.13.0", Some(55149),
|
||||||
Some("unions with `Copy` and `ManuallyDrop` fields are stable; there is no intent to stabilize more")),
|
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)),
|
(internal, unsized_fn_params, "1.49.0", Some(48055)),
|
||||||
/// Allows unsized rvalues at arguments and parameters.
|
/// Allows unsized rvalues at arguments and parameters.
|
||||||
(incomplete, unsized_locals, "1.30.0", Some(48055)),
|
(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.
|
/// Allows using the `#[used(linker)]` (or `#[used(compiler)]`) attribute.
|
||||||
(unstable, used_with_arg, "1.60.0", Some(93798)),
|
(unstable, used_with_arg, "1.60.0", Some(93798)),
|
||||||
/// Allows use of attributes in `where` clauses.
|
/// 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) {
|
pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) {
|
||||||
match tcx.def_kind(def_id) {
|
match tcx.def_kind(def_id) {
|
||||||
DefKind::Static { .. } => {
|
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_inhabited(tcx, def_id);
|
||||||
check_static_linkage(tcx, def_id);
|
check_static_linkage(tcx, def_id);
|
||||||
}
|
}
|
||||||
DefKind::Const => {
|
DefKind::Const => {}
|
||||||
tcx.ensure_ok().typeck(def_id);
|
|
||||||
}
|
|
||||||
DefKind::Enum => {
|
DefKind::Enum => {
|
||||||
check_enum(tcx, def_id);
|
check_enum(tcx, def_id);
|
||||||
}
|
}
|
||||||
|
@ -766,7 +762,6 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) {
|
||||||
ExternAbi::Rust,
|
ExternAbi::Rust,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Everything else is checked entirely within check_item_body
|
|
||||||
}
|
}
|
||||||
DefKind::Impl { of_trait } => {
|
DefKind::Impl { of_trait } => {
|
||||||
if of_trait && let Some(impl_trait_header) = tcx.impl_trait_header(def_id) {
|
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
|
// Only restricted on wasm target for now
|
||||||
if !tcx.sess.target.is_like_wasm {
|
if !tcx.sess.target.is_like_wasm {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -126,13 +126,14 @@ where
|
||||||
|
|
||||||
let infcx_compat = infcx.fork();
|
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(
|
let outlives_env = OutlivesEnvironment::new_with_implied_bounds_compat(
|
||||||
&infcx,
|
&infcx,
|
||||||
body_def_id,
|
body_def_id,
|
||||||
param_env,
|
param_env,
|
||||||
assumed_wf_types.iter().copied(),
|
assumed_wf_types.iter().copied(),
|
||||||
false,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
lint_redundant_lifetimes(tcx, body_def_id, &outlives_env);
|
lint_redundant_lifetimes(tcx, body_def_id, &outlives_env);
|
||||||
|
@ -142,54 +143,23 @@ where
|
||||||
return Ok(());
|
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(
|
let outlives_env = OutlivesEnvironment::new_with_implied_bounds_compat(
|
||||||
&infcx,
|
&infcx_compat,
|
||||||
body_def_id,
|
body_def_id,
|
||||||
param_env,
|
param_env,
|
||||||
assumed_wf_types,
|
assumed_wf_types,
|
||||||
true,
|
// 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);
|
let errors_compat = infcx_compat.resolve_regions_with_outlives_env(&outlives_env);
|
||||||
if errors_compat.is_empty() {
|
if errors_compat.is_empty() {
|
||||||
|
// FIXME: Once we fix bevy, this would be the place to insert a warning
|
||||||
|
// to upgrade bevy.
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(infcx_compat.err_ctxt().report_region_errors(body_def_id, &errors_compat))
|
Err(infcx_compat.err_ctxt().report_region_errors(body_def_id, &errors_compat))
|
||||||
}
|
}
|
||||||
} 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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_well_formed(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
|
fn check_well_formed(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
|
||||||
|
@ -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::ImplItem(item) => check_impl_item(tcx, item),
|
||||||
hir::Node::ForeignItem(item) => check_foreign_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)),
|
hir::Node::OpaqueTy(_) => Ok(crate::check::check::check_item_type(tcx, def_id)),
|
||||||
_ => unreachable!(),
|
_ => unreachable!("{node:?}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(generics) = node.generics() {
|
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 = tcx.type_of(item.def_id).instantiate_identity();
|
||||||
let ty = wfcx.normalize(span, Some(WellFormedLoc::Ty(item_id)), ty);
|
let ty = wfcx.normalize(span, Some(WellFormedLoc::Ty(item_id)), ty);
|
||||||
wfcx.register_wf_obligation(span, loc, ty.into());
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
ty::AssocKind::Fn => {
|
ty::AssocKind::Fn => {
|
||||||
|
@ -1354,7 +1330,7 @@ fn check_item_type(
|
||||||
traits::ObligationCause::new(
|
traits::ObligationCause::new(
|
||||||
ty_span,
|
ty_span,
|
||||||
wfcx.body_def_id,
|
wfcx.body_def_id,
|
||||||
ObligationCauseCode::WellFormed(None),
|
ObligationCauseCode::SizedConstOrStatic,
|
||||||
),
|
),
|
||||||
wfcx.param_env,
|
wfcx.param_env,
|
||||||
item_ty,
|
item_ty,
|
||||||
|
@ -1698,6 +1674,7 @@ fn check_fn_or_method<'tcx>(
|
||||||
hir::FnRetTy::Return(ty) => Some(ty.span),
|
hir::FnRetTy::Return(ty) => Some(ty.span),
|
||||||
hir::FnRetTy::DefaultReturn(_) => None,
|
hir::FnRetTy::DefaultReturn(_) => None,
|
||||||
},
|
},
|
||||||
|
ObligationCauseCode::SizedReturnType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1706,13 +1683,14 @@ fn check_sized_if_body<'tcx>(
|
||||||
def_id: LocalDefId,
|
def_id: LocalDefId,
|
||||||
ty: Ty<'tcx>,
|
ty: Ty<'tcx>,
|
||||||
maybe_span: Option<Span>,
|
maybe_span: Option<Span>,
|
||||||
|
code: ObligationCauseCode<'tcx>,
|
||||||
) {
|
) {
|
||||||
let tcx = wfcx.tcx();
|
let tcx = wfcx.tcx();
|
||||||
if let Some(body) = tcx.hir_maybe_body_owned_by(def_id) {
|
if let Some(body) = tcx.hir_maybe_body_owned_by(def_id) {
|
||||||
let span = maybe_span.unwrap_or(body.value.span);
|
let span = maybe_span.unwrap_or(body.value.span);
|
||||||
|
|
||||||
wfcx.register_bound(
|
wfcx.register_bound(
|
||||||
ObligationCause::new(span, def_id, traits::ObligationCauseCode::SizedReturnType),
|
ObligationCause::new(span, def_id, code),
|
||||||
wfcx.param_env,
|
wfcx.param_env,
|
||||||
ty,
|
ty,
|
||||||
tcx.require_lang_item(LangItem::Sized, Some(span)),
|
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| {
|
tcx.par_hir_body_owners(|item_def_id| {
|
||||||
let def_kind = tcx.def_kind(item_def_id);
|
let def_kind = tcx.def_kind(item_def_id);
|
||||||
match def_kind {
|
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() => {
|
DefKind::Const if tcx.generics_of(item_def_id).is_empty() => {
|
||||||
let instance = ty::Instance::new(item_def_id.into(), ty::GenericArgs::empty());
|
let instance = ty::Instance::new(item_def_id.into(), ty::GenericArgs::empty());
|
||||||
let cid = GlobalId { instance, promoted: None };
|
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| {
|
tcx.par_hir_body_owners(|item_def_id| {
|
||||||
let def_kind = tcx.def_kind(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) {
|
if !matches!(def_kind, DefKind::AnonConst) {
|
||||||
tcx.ensure_ok().typeck(item_def_id);
|
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}`
|
.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
|
.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 ->
|
hir_typeck_ptr_cast_add_auto_to_object = cannot add {$traits_len ->
|
||||||
[1] an auto trait {$traits}
|
[1] auto trait {$traits}
|
||||||
*[other] auto traits {$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 = 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
|
hir_typeck_remove_semi_for_coerce_expr = this could be implicitly returned but it is a statement, not a tail expression
|
||||||
|
|
|
@ -940,11 +940,8 @@ impl<'a, 'tcx> CastCheck<'tcx> {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if !added.is_empty() {
|
if !added.is_empty() {
|
||||||
tcx.emit_node_span_lint(
|
tcx.dcx().emit_err(errors::PtrCastAddAutoToObject {
|
||||||
lint::builtin::PTR_CAST_ADD_AUTO_TO_OBJECT,
|
span: self.span,
|
||||||
self.expr.hir_id,
|
|
||||||
self.span,
|
|
||||||
errors::PtrCastAddAutoToObject {
|
|
||||||
traits_len: added.len(),
|
traits_len: added.len(),
|
||||||
traits: {
|
traits: {
|
||||||
let mut traits: Vec<_> = added
|
let mut traits: Vec<_> = added
|
||||||
|
@ -955,8 +952,7 @@ impl<'a, 'tcx> CastCheck<'tcx> {
|
||||||
traits.sort();
|
traits.sort();
|
||||||
traits.into()
|
traits.into()
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(CastKind::PtrPtrCast)
|
Ok(CastKind::PtrPtrCast)
|
||||||
|
|
|
@ -51,15 +51,13 @@ use rustc_infer::traits::{
|
||||||
PredicateObligations,
|
PredicateObligations,
|
||||||
};
|
};
|
||||||
use rustc_middle::span_bug;
|
use rustc_middle::span_bug;
|
||||||
use rustc_middle::traits::BuiltinImplSource;
|
|
||||||
use rustc_middle::ty::adjustment::{
|
use rustc_middle::ty::adjustment::{
|
||||||
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCoercion,
|
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCoercion,
|
||||||
};
|
};
|
||||||
use rustc_middle::ty::error::TypeError;
|
use rustc_middle::ty::error::TypeError;
|
||||||
use rustc_middle::ty::visit::TypeVisitableExt;
|
use rustc_middle::ty::visit::TypeVisitableExt;
|
||||||
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt};
|
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt};
|
||||||
use rustc_session::parse::feature_err;
|
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span};
|
||||||
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span, sym};
|
|
||||||
use rustc_trait_selection::infer::InferCtxtExt as _;
|
use rustc_trait_selection::infer::InferCtxtExt as _;
|
||||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
||||||
use rustc_trait_selection::traits::{
|
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])
|
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
|
// Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid
|
||||||
// emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where
|
// emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where
|
||||||
// inference might unify those two inner type variables later.
|
// inference might unify those two inner type variables later.
|
||||||
|
@ -690,29 +686,8 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
|
||||||
// be silent, as it causes a type mismatch later.
|
// be silent, as it causes a type mismatch later.
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(impl_source)) => {
|
Ok(Some(impl_source)) => queue.extend(impl_source.nested_obligations()),
|
||||||
// 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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
Ok(coercion)
|
||||||
|
|
|
@ -373,9 +373,14 @@ pub(crate) struct LossyProvenanceInt2Ptr<'tcx> {
|
||||||
pub sugg: LossyProvenanceInt2PtrSuggestion,
|
pub sugg: LossyProvenanceInt2PtrSuggestion,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(LintDiagnostic)]
|
#[derive(Diagnostic)]
|
||||||
#[diag(hir_typeck_ptr_cast_add_auto_to_object)]
|
#[diag(hir_typeck_ptr_cast_add_auto_to_object, code = E0804)]
|
||||||
|
#[note]
|
||||||
|
#[help]
|
||||||
pub(crate) struct PtrCastAddAutoToObject {
|
pub(crate) struct PtrCastAddAutoToObject {
|
||||||
|
#[primary_span]
|
||||||
|
#[label]
|
||||||
|
pub span: Span,
|
||||||
pub traits_len: usize,
|
pub traits_len: usize,
|
||||||
pub traits: DiagSymbolList<String>,
|
pub traits: DiagSymbolList<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1806,7 +1806,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
crate::GatherLocalsVisitor::new(&fcx).visit_body(body);
|
crate::GatherLocalsVisitor::new(&fcx).visit_body(body);
|
||||||
|
|
||||||
let ty = fcx.check_expr_with_expectation(body.value, expected);
|
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);
|
fcx.write_ty(block.hir_id, ty);
|
||||||
ty
|
ty
|
||||||
}
|
}
|
||||||
|
|
|
@ -599,6 +599,11 @@ fn register_builtins(store: &mut LintStore) {
|
||||||
"converted into hard error, \
|
"converted into hard error, \
|
||||||
see <https://github.com/rust-lang/rust/issues/73333> for more information",
|
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) {
|
fn register_internals(store: &mut LintStore) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::ops::ControlFlow;
|
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_data_structures::fx::FxHashSet;
|
||||||
use rustc_errors::DiagMessage;
|
use rustc_errors::DiagMessage;
|
||||||
use rustc_hir::intravisit::VisitorExt;
|
use rustc_hir::intravisit::VisitorExt;
|
||||||
|
@ -1349,7 +1349,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
||||||
|
|
||||||
ty::FnPtr(sig_tys, hdr) => {
|
ty::FnPtr(sig_tys, hdr) => {
|
||||||
let sig = sig_tys.with(hdr);
|
let sig = sig_tys.with(hdr);
|
||||||
if self.is_internal_abi(sig.abi()) {
|
if sig.abi().is_rustic_abi() {
|
||||||
return FfiUnsafe {
|
return FfiUnsafe {
|
||||||
ty,
|
ty,
|
||||||
reason: fluent::lint_improper_ctypes_fnptr_reason,
|
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);
|
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`.
|
/// Find any fn-ptr types with external ABIs in `ty`.
|
||||||
///
|
///
|
||||||
/// For example, `Option<extern "C" fn()>` returns `extern "C" fn()`
|
/// 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>,
|
hir_ty: &hir::Ty<'tcx>,
|
||||||
ty: Ty<'tcx>,
|
ty: Ty<'tcx>,
|
||||||
) -> Vec<(Ty<'tcx>, Span)> {
|
) -> Vec<(Ty<'tcx>, Span)> {
|
||||||
struct FnPtrFinder<'a, 'b, 'tcx> {
|
struct FnPtrFinder<'tcx> {
|
||||||
visitor: &'a ImproperCTypesVisitor<'b, 'tcx>,
|
|
||||||
spans: Vec<Span>,
|
spans: Vec<Span>,
|
||||||
tys: Vec<Ty<'tcx>>,
|
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>) {
|
fn visit_ty(&mut self, ty: &'_ hir::Ty<'_, AmbigArg>) {
|
||||||
debug!(?ty);
|
debug!(?ty);
|
||||||
if let hir::TyKind::BareFn(hir::BareFnTy { abi, .. }) = ty.kind
|
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);
|
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 = ();
|
type Result = ();
|
||||||
|
|
||||||
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
|
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
|
||||||
if let ty::FnPtr(_, hdr) = ty.kind()
|
if let ty::FnPtr(_, hdr) = ty.kind()
|
||||||
&& !self.visitor.is_internal_abi(hdr.abi)
|
&& !hdr.abi.is_rustic_abi()
|
||||||
{
|
{
|
||||||
self.tys.push(ty);
|
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);
|
ty.visit_with(&mut visitor);
|
||||||
visitor.visit_ty_unambig(hir_ty);
|
visitor.visit_ty_unambig(hir_ty);
|
||||||
|
|
||||||
|
@ -1615,13 +1607,13 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations {
|
||||||
|
|
||||||
match it.kind {
|
match it.kind {
|
||||||
hir::ForeignItemKind::Fn(sig, _, _) => {
|
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)
|
vis.check_fn(it.owner_id.def_id, sig.decl)
|
||||||
} else {
|
} else {
|
||||||
vis.check_foreign_fn(it.owner_id.def_id, sig.decl);
|
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);
|
vis.check_foreign_static(it.owner_id, ty.span);
|
||||||
}
|
}
|
||||||
hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (),
|
hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (),
|
||||||
|
@ -1775,7 +1767,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition };
|
let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition };
|
||||||
if vis.is_internal_abi(abi) {
|
if abi.is_rustic_abi() {
|
||||||
vis.check_fn(id, decl);
|
vis.check_fn(id, decl);
|
||||||
} else {
|
} else {
|
||||||
vis.check_foreign_fn(id, decl);
|
vis.check_foreign_fn(id, decl);
|
||||||
|
|
|
@ -80,7 +80,6 @@ declare_lint_pass! {
|
||||||
PRIVATE_BOUNDS,
|
PRIVATE_BOUNDS,
|
||||||
PRIVATE_INTERFACES,
|
PRIVATE_INTERFACES,
|
||||||
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
|
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
|
||||||
PTR_CAST_ADD_AUTO_TO_OBJECT,
|
|
||||||
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
|
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
|
||||||
PUB_USE_OF_PRIVATE_EXTERN_CRATE,
|
PUB_USE_OF_PRIVATE_EXTERN_CRATE,
|
||||||
REDUNDANT_IMPORTS,
|
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! {
|
declare_lint! {
|
||||||
/// The `out_of_scope_macro_calls` lint detects `macro_rules` called when they are not in scope,
|
/// 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.
|
/// 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) {
|
impl Key for (Symbol, u32, u32) {
|
||||||
type Cache<V> = DefaultCache<Self, V>;
|
type Cache<V> = DefaultCache<Self, V>;
|
||||||
|
|
||||||
|
|
|
@ -2262,22 +2262,13 @@ rustc_queries! {
|
||||||
desc { "normalizing `{}`", goal.value }
|
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(
|
query implied_outlives_bounds(
|
||||||
goal: CanonicalImpliedOutlivesBoundsGoal<'tcx>
|
key: (CanonicalImpliedOutlivesBoundsGoal<'tcx>, bool)
|
||||||
) -> Result<
|
) -> Result<
|
||||||
&'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec<OutlivesBound<'tcx>>>>,
|
&'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec<OutlivesBound<'tcx>>>>,
|
||||||
NoSolution,
|
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:
|
/// Do not call this query directly:
|
||||||
|
|
|
@ -266,7 +266,7 @@ pub enum ObligationCauseCode<'tcx> {
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Constant expressions must be sized.
|
/// Constant expressions must be sized.
|
||||||
ConstSized,
|
SizedConstOrStatic,
|
||||||
|
|
||||||
/// `static` items must have `Sync` type.
|
/// `static` items must have `Sync` type.
|
||||||
SharedStatic,
|
SharedStatic,
|
||||||
|
|
|
@ -6,27 +6,25 @@ use rustc_middle::ty::{self, Ty, TypeVisitableExt};
|
||||||
|
|
||||||
use crate::builder::Builder;
|
use crate::builder::Builder;
|
||||||
use crate::builder::expr::as_place::{PlaceBase, PlaceBuilder};
|
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> {
|
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
|
/// `subpatterns`, representing the fields of a [`PatKind::Variant`] or
|
||||||
/// [`PatKind::Leaf`].
|
/// [`PatKind::Leaf`].
|
||||||
///
|
///
|
||||||
/// Used internally by [`MatchPairTree::for_pattern`].
|
/// Used internally by [`MatchPairTree::for_pattern`].
|
||||||
fn field_match_pairs(
|
fn field_match_pairs(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
match_pairs: &mut Vec<MatchPairTree<'tcx>>,
|
||||||
|
extra_data: &mut PatternExtraData<'tcx>,
|
||||||
place: PlaceBuilder<'tcx>,
|
place: PlaceBuilder<'tcx>,
|
||||||
subpatterns: &[FieldPat<'tcx>],
|
subpatterns: &[FieldPat<'tcx>],
|
||||||
) -> Vec<MatchPairTree<'tcx>> {
|
) {
|
||||||
subpatterns
|
for fieldpat in subpatterns {
|
||||||
.iter()
|
let place = place.clone_project(PlaceElem::Field(fieldpat.field, fieldpat.pattern.ty));
|
||||||
.map(|fieldpat| {
|
MatchPairTree::for_pattern(place, &fieldpat.pattern, self, match_pairs, extra_data);
|
||||||
let place =
|
}
|
||||||
place.clone_project(PlaceElem::Field(fieldpat.field, fieldpat.pattern.ty));
|
|
||||||
MatchPairTree::for_pattern(place, &fieldpat.pattern, self)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds [`MatchPairTree`] subtrees for the prefix/middle/suffix parts of an
|
/// 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(
|
fn prefix_slice_suffix(
|
||||||
&mut self,
|
&mut self,
|
||||||
match_pairs: &mut Vec<MatchPairTree<'tcx>>,
|
match_pairs: &mut Vec<MatchPairTree<'tcx>>,
|
||||||
|
extra_data: &mut PatternExtraData<'tcx>,
|
||||||
place: &PlaceBuilder<'tcx>,
|
place: &PlaceBuilder<'tcx>,
|
||||||
prefix: &[Pat<'tcx>],
|
prefix: &[Pat<'tcx>],
|
||||||
opt_slice: &Option<Box<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)
|
((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 =
|
let elem =
|
||||||
ProjectionElem::ConstantIndex { offset: idx as u64, min_length, from_end: false };
|
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 {
|
if let Some(subslice_pat) = opt_slice {
|
||||||
let suffix_len = suffix.len() as u64;
|
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 },
|
to: if exact_size { min_length - suffix_len } else { suffix_len },
|
||||||
from_end: !exact_size,
|
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 end_offset = (idx + 1) as u64;
|
||||||
let elem = ProjectionElem::ConstantIndex {
|
let elem = ProjectionElem::ConstantIndex {
|
||||||
offset: if exact_size { min_length - end_offset } else { end_offset },
|
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,
|
from_end: !exact_size,
|
||||||
};
|
};
|
||||||
let place = place.clone_project(elem);
|
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> {
|
impl<'tcx> MatchPairTree<'tcx> {
|
||||||
/// Recursively builds a match pair tree for the given pattern and its
|
/// Recursively builds a match pair tree for the given pattern and its
|
||||||
/// subpatterns.
|
/// subpatterns.
|
||||||
pub(in crate::builder) fn for_pattern(
|
pub(super) fn for_pattern(
|
||||||
mut place_builder: PlaceBuilder<'tcx>,
|
mut place_builder: PlaceBuilder<'tcx>,
|
||||||
pattern: &Pat<'tcx>,
|
pattern: &Pat<'tcx>,
|
||||||
cx: &mut Builder<'_, '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.
|
// Force the place type to the pattern's type.
|
||||||
// FIXME(oli-obk): can we use this to simplify slice/array pattern hacks?
|
// FIXME(oli-obk): can we use this to simplify slice/array pattern hacks?
|
||||||
if let Some(resolved) = place_builder.resolve_upvar(cx) {
|
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_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 place = place_builder.try_to_place(cx);
|
||||||
let default_irrefutable = || TestCase::Irrefutable { binding: None, ascription: None };
|
|
||||||
let mut subpairs = Vec::new();
|
let mut subpairs = Vec::new();
|
||||||
let test_case = match pattern.kind {
|
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(),
|
pats: pats.iter().map(|pat| FlatPat::new(place_builder.clone(), pat, cx)).collect(),
|
||||||
},
|
}),
|
||||||
|
|
||||||
PatKind::Range(ref range) => {
|
PatKind::Range(ref range) => {
|
||||||
if range.is_full_range(cx.tcx) == Some(true) {
|
if range.is_full_range(cx.tcx) == Some(true) {
|
||||||
default_irrefutable()
|
None
|
||||||
} else {
|
} 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 {
|
PatKind::AscribeUserType {
|
||||||
ascription: Ascription { ref annotation, variance },
|
ascription: Ascription { ref annotation, variance },
|
||||||
ref subpattern,
|
ref subpattern,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
// Apply the type ascription to the value at `match_pair.place`
|
MatchPairTree::for_pattern(
|
||||||
let ascription = place.map(|source| super::Ascription {
|
place_builder,
|
||||||
annotation: annotation.clone(),
|
subpattern,
|
||||||
source,
|
cx,
|
||||||
variance,
|
&mut subpairs,
|
||||||
});
|
extra_data,
|
||||||
|
);
|
||||||
|
|
||||||
subpairs.push(MatchPairTree::for_pattern(place_builder, subpattern, cx));
|
// Apply the type ascription to the value at `match_pair.place`
|
||||||
TestCase::Irrefutable { ascription, binding: None }
|
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, .. } => {
|
PatKind::Binding { mode, var, ref subpattern, .. } => {
|
||||||
let binding = place.map(|source| super::Binding {
|
// 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
|
||||||
|
MatchPairTree::for_pattern(
|
||||||
|
place_builder,
|
||||||
|
subpattern,
|
||||||
|
cx,
|
||||||
|
&mut subpairs,
|
||||||
|
extra_data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then push this binding, after any bindings in the subpattern.
|
||||||
|
if let Some(source) = place {
|
||||||
|
extra_data.bindings.push(super::Binding {
|
||||||
span: pattern.span,
|
span: pattern.span,
|
||||||
source,
|
source,
|
||||||
var_id: var,
|
var_id: var,
|
||||||
binding_mode: mode,
|
binding_mode: mode,
|
||||||
});
|
});
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
TestCase::Irrefutable { ascription: None, binding }
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
PatKind::ExpandedConstant { subpattern: ref pattern, def_id: _, is_inline: false } => {
|
PatKind::ExpandedConstant { subpattern: ref pattern, def_id: _, is_inline: false } => {
|
||||||
subpairs.push(MatchPairTree::for_pattern(place_builder, pattern, cx));
|
MatchPairTree::for_pattern(place_builder, pattern, cx, &mut subpairs, extra_data);
|
||||||
default_irrefutable()
|
None
|
||||||
}
|
}
|
||||||
PatKind::ExpandedConstant { subpattern: ref pattern, def_id, is_inline: true } => {
|
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`
|
// 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 span = pattern.span;
|
||||||
let parent_id = cx.tcx.typeck_root_def_id(cx.def_id.to_def_id());
|
let parent_id = cx.tcx.typeck_root_def_id(cx.def_id.to_def_id());
|
||||||
let args = ty::InlineConstArgs::new(
|
let args = ty::InlineConstArgs::new(
|
||||||
|
@ -189,33 +229,47 @@ impl<'tcx> MatchPairTree<'tcx> {
|
||||||
span,
|
span,
|
||||||
user_ty: Box::new(user_ty),
|
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));
|
None
|
||||||
TestCase::Irrefutable { ascription, binding: None }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PatKind::Array { ref prefix, ref slice, ref suffix } => {
|
PatKind::Array { ref prefix, ref slice, ref suffix } => {
|
||||||
cx.prefix_slice_suffix(&mut subpairs, &place_builder, prefix, slice, suffix);
|
cx.prefix_slice_suffix(
|
||||||
default_irrefutable()
|
&mut subpairs,
|
||||||
|
extra_data,
|
||||||
|
&place_builder,
|
||||||
|
prefix,
|
||||||
|
slice,
|
||||||
|
suffix,
|
||||||
|
);
|
||||||
|
None
|
||||||
}
|
}
|
||||||
PatKind::Slice { ref prefix, ref slice, ref suffix } => {
|
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() {
|
if prefix.is_empty() && slice.is_some() && suffix.is_empty() {
|
||||||
default_irrefutable()
|
None
|
||||||
} else {
|
} else {
|
||||||
TestCase::Slice {
|
Some(TestCase::Slice {
|
||||||
len: prefix.len() + suffix.len(),
|
len: prefix.len() + suffix.len(),
|
||||||
variable_length: slice.is_some(),
|
variable_length: slice.is_some(),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PatKind::Variant { adt_def, variant_index, args, ref subpatterns } => {
|
PatKind::Variant { adt_def, variant_index, args, ref subpatterns } => {
|
||||||
let downcast_place = place_builder.downcast(adt_def, variant_index); // `(x as Variant)`
|
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)| {
|
let irrefutable = adt_def.variants().iter_enumerated().all(|(i, v)| {
|
||||||
i == variant_index
|
i == variant_index
|
||||||
|
@ -225,21 +279,23 @@ impl<'tcx> MatchPairTree<'tcx> {
|
||||||
.apply_ignore_module(cx.tcx, cx.infcx.typing_env(cx.param_env))
|
.apply_ignore_module(cx.tcx, cx.infcx.typing_env(cx.param_env))
|
||||||
}) && (adt_def.did().is_local()
|
}) && (adt_def.did().is_local()
|
||||||
|| !adt_def.is_variant_list_non_exhaustive());
|
|| !adt_def.is_variant_list_non_exhaustive());
|
||||||
if irrefutable {
|
if irrefutable { None } else { Some(TestCase::Variant { adt_def, variant_index }) }
|
||||||
default_irrefutable()
|
|
||||||
} else {
|
|
||||||
TestCase::Variant { adt_def, variant_index }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PatKind::Leaf { ref subpatterns } => {
|
PatKind::Leaf { ref subpatterns } => {
|
||||||
subpairs = cx.field_match_pairs(place_builder, subpatterns);
|
cx.field_match_pairs(&mut subpairs, extra_data, place_builder, subpatterns);
|
||||||
default_irrefutable()
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
PatKind::Deref { ref subpattern } => {
|
PatKind::Deref { ref subpattern } => {
|
||||||
subpairs.push(MatchPairTree::for_pattern(place_builder.deref(), subpattern, cx));
|
MatchPairTree::for_pattern(
|
||||||
default_irrefutable()
|
place_builder.deref(),
|
||||||
|
subpattern,
|
||||||
|
cx,
|
||||||
|
&mut subpairs,
|
||||||
|
extra_data,
|
||||||
|
);
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
PatKind::DerefPattern { ref subpattern, mutability } => {
|
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),
|
Ty::new_ref(cx.tcx, cx.tcx.lifetimes.re_erased, subpattern.ty, mutability),
|
||||||
pattern.span,
|
pattern.span,
|
||||||
);
|
);
|
||||||
subpairs.push(MatchPairTree::for_pattern(
|
MatchPairTree::for_pattern(
|
||||||
PlaceBuilder::from(temp).deref(),
|
PlaceBuilder::from(temp).deref(),
|
||||||
subpattern,
|
subpattern,
|
||||||
cx,
|
cx,
|
||||||
));
|
&mut subpairs,
|
||||||
TestCase::Deref { temp, mutability }
|
extra_data,
|
||||||
|
);
|
||||||
|
Some(TestCase::Deref { temp, mutability })
|
||||||
}
|
}
|
||||||
|
|
||||||
PatKind::Never => TestCase::Never,
|
PatKind::Never => Some(TestCase::Never),
|
||||||
};
|
};
|
||||||
|
|
||||||
MatchPairTree {
|
if let Some(test_case) = test_case {
|
||||||
place,
|
// 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,
|
test_case,
|
||||||
subpairs,
|
subpairs,
|
||||||
pattern_ty: pattern.ty,
|
pattern_ty: pattern.ty,
|
||||||
pattern_span: pattern.span,
|
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:
|
// helper functions, broken out by category:
|
||||||
mod match_pair;
|
mod match_pair;
|
||||||
mod simplify;
|
|
||||||
mod test;
|
mod test;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
@ -987,18 +986,16 @@ impl<'tcx> PatternExtraData<'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A pattern in a form suitable for lowering the match tree, with all irrefutable
|
/// 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
|
/// Here, "flat" indicates that irrefutable nodes in the pattern tree have been
|
||||||
/// simplified by [`Builder::simplify_match_pairs`]. They are not necessarily
|
/// recursively replaced with their refutable subpatterns. They are not
|
||||||
/// flat in an absolute sense.
|
/// necessarily flat in an absolute sense.
|
||||||
///
|
///
|
||||||
/// Will typically be incorporated into a [`Candidate`].
|
/// Will typically be incorporated into a [`Candidate`].
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct FlatPat<'tcx> {
|
struct FlatPat<'tcx> {
|
||||||
/// To match the pattern, all of these must be satisfied...
|
/// 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>>,
|
match_pairs: Vec<MatchPairTree<'tcx>>,
|
||||||
|
|
||||||
extra_data: PatternExtraData<'tcx>,
|
extra_data: PatternExtraData<'tcx>,
|
||||||
|
@ -1008,17 +1005,15 @@ impl<'tcx> FlatPat<'tcx> {
|
||||||
/// Creates a `FlatPat` containing a simplified [`MatchPairTree`] list/forest
|
/// Creates a `FlatPat` containing a simplified [`MatchPairTree`] list/forest
|
||||||
/// for the given pattern.
|
/// for the given pattern.
|
||||||
fn new(place: PlaceBuilder<'tcx>, pattern: &Pat<'tcx>, cx: &mut Builder<'_, 'tcx>) -> Self {
|
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.
|
// Recursively build a tree of match pairs for the given pattern.
|
||||||
let mut match_pairs = vec![MatchPairTree::for_pattern(place, pattern, cx)];
|
let mut match_pairs = vec![];
|
||||||
let mut extra_data = PatternExtraData {
|
let mut extra_data = PatternExtraData {
|
||||||
span: pattern.span,
|
span: pattern.span,
|
||||||
bindings: Vec::new(),
|
bindings: Vec::new(),
|
||||||
ascriptions: Vec::new(),
|
ascriptions: Vec::new(),
|
||||||
is_never: pattern.is_never_pattern(),
|
is_never: pattern.is_never_pattern(),
|
||||||
};
|
};
|
||||||
// Recursively remove irrefutable match pairs, while recording their
|
MatchPairTree::for_pattern(place, pattern, cx, &mut match_pairs, &mut extra_data);
|
||||||
// bindings/ascriptions, and sort or-patterns after other match pairs.
|
|
||||||
cx.simplify_match_pairs(&mut match_pairs, &mut extra_data);
|
|
||||||
|
|
||||||
Self { match_pairs, extra_data }
|
Self { match_pairs, extra_data }
|
||||||
}
|
}
|
||||||
|
@ -1055,7 +1050,6 @@ struct Candidate<'tcx> {
|
||||||
/// (see [`Builder::test_remaining_match_pairs_after_or`]).
|
/// (see [`Builder::test_remaining_match_pairs_after_or`]).
|
||||||
///
|
///
|
||||||
/// Invariants:
|
/// Invariants:
|
||||||
/// - All [`TestCase::Irrefutable`] patterns have been removed by simplification.
|
|
||||||
/// - All or-patterns ([`TestCase::Or`]) have been sorted to the end.
|
/// - All or-patterns ([`TestCase::Or`]) have been sorted to the end.
|
||||||
match_pairs: Vec<MatchPairTree<'tcx>>,
|
match_pairs: Vec<MatchPairTree<'tcx>>,
|
||||||
|
|
||||||
|
@ -1126,7 +1120,7 @@ impl<'tcx> Candidate<'tcx> {
|
||||||
|
|
||||||
/// Incorporates an already-simplified [`FlatPat`] into a new candidate.
|
/// Incorporates an already-simplified [`FlatPat`] into a new candidate.
|
||||||
fn from_flat_pat(flat_pat: FlatPat<'tcx>, has_guard: bool) -> Self {
|
fn from_flat_pat(flat_pat: FlatPat<'tcx>, has_guard: bool) -> Self {
|
||||||
Candidate {
|
let mut this = Candidate {
|
||||||
match_pairs: flat_pat.match_pairs,
|
match_pairs: flat_pat.match_pairs,
|
||||||
extra_data: flat_pat.extra_data,
|
extra_data: flat_pat.extra_data,
|
||||||
has_guard,
|
has_guard,
|
||||||
|
@ -1135,7 +1129,14 @@ impl<'tcx> Candidate<'tcx> {
|
||||||
otherwise_block: None,
|
otherwise_block: None,
|
||||||
pre_binding_block: None,
|
pre_binding_block: None,
|
||||||
false_edge_start_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.
|
/// 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::pick_test_for_match_pair`] (to choose a test)
|
||||||
/// - [`Builder::sort_candidate`] (to see how the test interacts with a match pair)
|
/// - [`Builder::sort_candidate`] (to see how the test interacts with a match pair)
|
||||||
///
|
///
|
||||||
/// Two variants are unlike the others and deserve special mention:
|
/// Note that or-patterns are not tested directly like the other variants.
|
||||||
///
|
/// Instead they participate in or-pattern expansion, where they are transformed into
|
||||||
/// - [`Self::Irrefutable`] is only used temporarily when building a [`MatchPairTree`].
|
/// subcandidates. See [`Builder::expand_and_match_or_candidates`].
|
||||||
/// 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`].
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum TestCase<'tcx> {
|
enum TestCase<'tcx> {
|
||||||
Irrefutable { binding: Option<Binding<'tcx>>, ascription: Option<Ascription<'tcx>> },
|
|
||||||
Variant { adt_def: ty::AdtDef<'tcx>, variant_index: VariantIdx },
|
Variant { adt_def: ty::AdtDef<'tcx>, variant_index: VariantIdx },
|
||||||
Constant { value: mir::Const<'tcx> },
|
Constant { value: mir::Const<'tcx> },
|
||||||
Range(Arc<PatRange<'tcx>>),
|
Range(Arc<PatRange<'tcx>>),
|
||||||
|
@ -1261,19 +1256,9 @@ impl<'tcx> TestCase<'tcx> {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct MatchPairTree<'tcx> {
|
pub(crate) struct MatchPairTree<'tcx> {
|
||||||
/// This place...
|
/// This place...
|
||||||
///
|
place: Place<'tcx>,
|
||||||
/// ---
|
|
||||||
/// 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>>,
|
|
||||||
|
|
||||||
/// ... must pass this test...
|
/// ... must pass this test...
|
||||||
///
|
|
||||||
/// ---
|
|
||||||
/// Invariant: after creation and simplification in [`FlatPat::new`],
|
|
||||||
/// this must not be [`TestCase::Irrefutable`].
|
|
||||||
test_case: TestCase<'tcx>,
|
test_case: TestCase<'tcx>,
|
||||||
|
|
||||||
/// ... and these subpairs must match.
|
/// ... and these subpairs must match.
|
||||||
|
@ -2091,11 +2076,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
// Extract the match-pair from the highest priority candidate
|
// Extract the match-pair from the highest priority candidate
|
||||||
let match_pair = &candidates[0].match_pairs[0];
|
let match_pair = &candidates[0].match_pairs[0];
|
||||||
let test = self.pick_test_for_match_pair(match_pair);
|
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);
|
debug!(?test, ?match_pair);
|
||||||
|
|
||||||
(match_place, test)
|
(match_pair.place, test)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a test, we partition the input candidates into several buckets.
|
/// 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,
|
// Or-patterns are not tested directly; instead they are expanded into subcandidates,
|
||||||
// which are then distinguished by testing whatever non-or patterns they contain.
|
// which are then distinguished by testing whatever non-or patterns they contain.
|
||||||
TestCase::Or { .. } => bug!("or-patterns should have already been handled"),
|
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 }
|
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
|
// than one, but it'd be very unusual to have two sides that
|
||||||
// both require tests; you'd expect one side to be simplified
|
// both require tests; you'd expect one side to be simplified
|
||||||
// away.)
|
// away.)
|
||||||
let (match_pair_index, match_pair) = candidate
|
let (match_pair_index, match_pair) =
|
||||||
.match_pairs
|
candidate.match_pairs.iter().enumerate().find(|&(_, mp)| mp.place == test_place)?;
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|&(_, mp)| mp.place == Some(test_place))?;
|
|
||||||
|
|
||||||
// If true, the match pair is completely entailed by its corresponding test
|
// 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_
|
// branch, so it can be removed. If false, the match pair is _compatible_
|
||||||
|
@ -593,7 +584,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
candidate
|
candidate
|
||||||
.match_pairs
|
.match_pairs
|
||||||
.iter()
|
.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
|
if sorted_candidates
|
||||||
.get(&TestBranch::Failure)
|
.get(&TestBranch::Failure)
|
||||||
|
@ -769,7 +760,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
let match_pair = candidate.match_pairs.remove(match_pair_index);
|
let match_pair = candidate.match_pairs.remove(match_pair_index);
|
||||||
candidate.match_pairs.extend(match_pair.subpairs);
|
candidate.match_pairs.extend(match_pair.subpairs);
|
||||||
// Move or-patterns to the end.
|
// Move or-patterns to the end.
|
||||||
candidate.match_pairs.sort_by_key(|pair| matches!(pair.test_case, TestCase::Or { .. }));
|
candidate.sort_match_pairs();
|
||||||
}
|
}
|
||||||
|
|
||||||
ret
|
ret
|
||||||
|
|
|
@ -173,14 +173,10 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> {
|
||||||
// }
|
// }
|
||||||
// ```
|
// ```
|
||||||
// Hence we fake borrow using a deep borrow.
|
// Hence we fake borrow using a deep borrow.
|
||||||
if let Some(place) = match_pair.place {
|
self.fake_borrow(match_pair.place, FakeBorrowKind::Deep);
|
||||||
self.fake_borrow(place, FakeBorrowKind::Deep);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Insert a Shallow borrow of any place that is switched on.
|
// Insert a Shallow borrow of any place that is switched on.
|
||||||
if let Some(place) = match_pair.place {
|
self.fake_borrow(match_pair.place, FakeBorrowKind::Shallow);
|
||||||
self.fake_borrow(place, FakeBorrowKind::Shallow);
|
|
||||||
}
|
|
||||||
|
|
||||||
for subpair in &match_pair.subpairs {
|
for subpair in &match_pair.subpairs {
|
||||||
self.visit_match_pair(subpair);
|
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.
|
// Rust calls cannot themselves create foreign unwinds.
|
||||||
// We assume this is true for intrinsics as well.
|
// We assume this is true for intrinsics as well.
|
||||||
if let ExternAbi::RustIntrinsic
|
if sig.abi().is_rustic_abi() {
|
||||||
| ExternAbi::Rust
|
|
||||||
| ExternAbi::RustCall
|
|
||||||
| ExternAbi::RustCold = sig.abi()
|
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -513,6 +513,24 @@ fn mir_drops_elaborated_and_const_checked(tcx: TyCtxt<'_>, def: LocalDefId) -> &
|
||||||
body.tainted_by_errors = Some(error_reported);
|
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);
|
run_analysis_to_runtime_passes(tcx, &mut body);
|
||||||
|
|
||||||
tcx.alloc_steal_mir(body)
|
tcx.alloc_steal_mir(body)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! This module ensures that if a function's ABI requires a particular target feature,
|
//! 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.
|
//! 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_hir::CRATE_HIR_ID;
|
||||||
use rustc_middle::mir::{self, traversal};
|
use rustc_middle::mir::{self, traversal};
|
||||||
use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt};
|
use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt};
|
||||||
|
@ -115,8 +115,8 @@ fn check_call_site_abi<'tcx>(
|
||||||
span: Span,
|
span: Span,
|
||||||
caller: InstanceKind<'tcx>,
|
caller: InstanceKind<'tcx>,
|
||||||
) {
|
) {
|
||||||
if callee.fn_sig(tcx).abi() == ExternAbi::Rust {
|
if callee.fn_sig(tcx).abi().is_rustic_abi() {
|
||||||
// "Rust" ABI never passes arguments in vector registers.
|
// we directly handle the soundness of Rust ABIs
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let typing_env = ty::TypingEnv::fully_monomorphized();
|
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![],
|
_ => vec![],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1084,48 +1077,6 @@ where
|
||||||
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
|
.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
|
// 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
|
// hold for the self type of the goal, which for coherence and soundness
|
||||||
// purposes must disqualify the built-in auto impl assembled by considering
|
// 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.
|
/// 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
|
/// 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.
|
/// uses a query latch and then resuming that waiter.
|
||||||
/// There may be multiple cycles involved in a deadlock, so this searches
|
/// There may be multiple cycles involved in a deadlock, but we only search
|
||||||
/// all active queries for cycles before finally resuming all the waiters at once.
|
/// 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) {
|
pub fn break_query_cycles(query_map: QueryMap, registry: &rayon_core::Registry) {
|
||||||
let mut wakelist = Vec::new();
|
let mut wakelist = Vec::new();
|
||||||
let mut jobs: Vec<QueryJobId> = query_map.keys().cloned().collect();
|
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 {
|
while jobs.len() > 0 {
|
||||||
if remove_cycle(&query_map, &mut jobs, &mut wakelist) {
|
if remove_cycle(&query_map, &mut jobs, &mut wakelist) {
|
||||||
found_cycle = true;
|
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,
|
Applicability::MachineApplicable,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ObligationCauseCode::ConstSized => {
|
ObligationCauseCode::SizedConstOrStatic => {
|
||||||
err.note("constant expressions must have a statically known size");
|
err.note("statics and constants must have a statically known size");
|
||||||
}
|
}
|
||||||
ObligationCauseCode::InlineAsmSized => {
|
ObligationCauseCode::InlineAsmSized => {
|
||||||
err.note("all inline asm arguments must have a statically known size");
|
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>,
|
param_env: ty::ParamEnv<'tcx>,
|
||||||
assumed_wf_tys: impl IntoIterator<Item = Ty<'tcx>>,
|
assumed_wf_tys: impl IntoIterator<Item = Ty<'tcx>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new_with_implied_bounds_compat(
|
Self::new_with_implied_bounds_compat(infcx, body_id, param_env, assumed_wf_tys, false)
|
||||||
infcx,
|
|
||||||
body_id,
|
|
||||||
param_env,
|
|
||||||
assumed_wf_tys,
|
|
||||||
!infcx.tcx.sess.opts.unstable_opts.no_implied_bounds_compat,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_with_implied_bounds_compat(
|
fn new_with_implied_bounds_compat(
|
||||||
|
@ -31,7 +25,7 @@ impl<'tcx> OutlivesEnvironment<'tcx> {
|
||||||
body_id: LocalDefId,
|
body_id: LocalDefId,
|
||||||
param_env: ty::ParamEnv<'tcx>,
|
param_env: ty::ParamEnv<'tcx>,
|
||||||
assumed_wf_tys: impl IntoIterator<Item = Ty<'tcx>>,
|
assumed_wf_tys: impl IntoIterator<Item = Ty<'tcx>>,
|
||||||
implied_bounds_compat: bool,
|
disable_implied_bounds_hack: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut bounds = vec![];
|
let mut bounds = vec![];
|
||||||
|
|
||||||
|
@ -59,11 +53,11 @@ impl<'tcx> OutlivesEnvironment<'tcx> {
|
||||||
OutlivesEnvironment::from_normalized_bounds(
|
OutlivesEnvironment::from_normalized_bounds(
|
||||||
param_env,
|
param_env,
|
||||||
bounds,
|
bounds,
|
||||||
infcx.implied_bounds_tys_with_compat(
|
infcx.implied_bounds_tys(
|
||||||
body_id,
|
body_id,
|
||||||
param_env,
|
param_env,
|
||||||
assumed_wf_tys,
|
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>,
|
param_env: ty::ParamEnv<'tcx>,
|
||||||
body_id: LocalDefId,
|
body_id: LocalDefId,
|
||||||
ty: Ty<'tcx>,
|
ty: Ty<'tcx>,
|
||||||
compat: bool,
|
disable_implied_bounds_hack: bool,
|
||||||
) -> Vec<OutlivesBound<'tcx>> {
|
) -> Vec<OutlivesBound<'tcx>> {
|
||||||
let ty = infcx.resolve_vars_if_possible(ty);
|
let ty = infcx.resolve_vars_if_possible(ty);
|
||||||
let ty = OpportunisticRegionResolver::new(infcx).fold_ty(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 mut canonical_var_values = OriginalQueryValues::default();
|
||||||
let input = ImpliedOutlivesBounds { ty };
|
let input = ImpliedOutlivesBounds { ty };
|
||||||
let canonical = infcx.canonicalize_query(param_env.and(input), &mut canonical_var_values);
|
let canonical = infcx.canonicalize_query(param_env.and(input), &mut canonical_var_values);
|
||||||
let implied_bounds_result = if compat {
|
let implied_bounds_result =
|
||||||
infcx.tcx.implied_outlives_bounds_compat(canonical)
|
infcx.tcx.implied_outlives_bounds((canonical, disable_implied_bounds_hack));
|
||||||
} else {
|
|
||||||
infcx.tcx.implied_outlives_bounds(canonical)
|
|
||||||
};
|
|
||||||
let Ok(canonical_result) = implied_bounds_result else {
|
let Ok(canonical_result) = implied_bounds_result else {
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
|
@ -110,14 +107,15 @@ fn implied_outlives_bounds<'a, 'tcx>(
|
||||||
impl<'tcx> InferCtxt<'tcx> {
|
impl<'tcx> InferCtxt<'tcx> {
|
||||||
/// Do *NOT* call this directly. You probably want to construct a `OutlivesEnvironment`
|
/// 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.
|
/// 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,
|
&self,
|
||||||
body_id: LocalDefId,
|
body_id: LocalDefId,
|
||||||
param_env: ParamEnv<'tcx>,
|
param_env: ParamEnv<'tcx>,
|
||||||
tys: Tys,
|
tys: Tys,
|
||||||
compat: bool,
|
disable_implied_bounds_hack: bool,
|
||||||
) -> impl Iterator<Item = OutlivesBound<'tcx>> {
|
) -> impl Iterator<Item = OutlivesBound<'tcx>> {
|
||||||
tys.into_iter()
|
tys.into_iter().flat_map(move |ty| {
|
||||||
.flat_map(move |ty| implied_outlives_bounds(self, param_env, body_id, ty, compat))
|
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.
|
// why we special case object types.
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
|
ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => {
|
||||||
| ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
|
|
||||||
// These traits have no associated types.
|
// These traits have no associated types.
|
||||||
selcx.tcx().dcx().span_delayed_bug(
|
selcx.tcx().dcx().span_delayed_bug(
|
||||||
obligation.cause.span,
|
obligation.cause.span,
|
||||||
|
@ -1325,8 +1324,7 @@ fn confirm_select_candidate<'cx, 'tcx>(
|
||||||
}
|
}
|
||||||
ImplSource::Builtin(BuiltinImplSource::Object { .. }, _)
|
ImplSource::Builtin(BuiltinImplSource::Object { .. }, _)
|
||||||
| ImplSource::Param(..)
|
| ImplSource::Param(..)
|
||||||
| ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
|
| ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => {
|
||||||
| ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
|
|
||||||
// we don't create Select candidates with this kind of resolution
|
// we don't create Select candidates with this kind of resolution
|
||||||
span_bug!(
|
span_bug!(
|
||||||
obligation.cause.span,
|
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::canonical::CanonicalQueryInput;
|
||||||
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
|
|
||||||
use rustc_infer::traits::query::OutlivesBound;
|
use rustc_infer::traits::query::OutlivesBound;
|
||||||
use rustc_infer::traits::query::type_op::ImpliedOutlivesBounds;
|
use rustc_infer::traits::query::type_op::ImpliedOutlivesBounds;
|
||||||
use rustc_middle::infer::canonical::CanonicalQueryResponse;
|
use rustc_middle::infer::canonical::CanonicalQueryResponse;
|
||||||
use rustc_middle::traits::ObligationCause;
|
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::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 rustc_type_ir::outlives::{Component, push_outlives_components};
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
use crate::traits::query::NoSolution;
|
use crate::traits::query::NoSolution;
|
||||||
use crate::traits::{ObligationCtxt, wf};
|
use crate::traits::{ObligationCtxt, wf};
|
||||||
|
@ -35,11 +36,7 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> {
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
canonicalized: CanonicalQueryInput<'tcx, ParamEnvAnd<'tcx, Self>>,
|
canonicalized: CanonicalQueryInput<'tcx, ParamEnvAnd<'tcx, Self>>,
|
||||||
) -> Result<CanonicalQueryResponse<'tcx, Self::QueryResponse>, NoSolution> {
|
) -> Result<CanonicalQueryResponse<'tcx, Self::QueryResponse>, NoSolution> {
|
||||||
if tcx.sess.opts.unstable_opts.no_implied_bounds_compat {
|
tcx.implied_outlives_bounds((canonicalized, false))
|
||||||
tcx.implied_outlives_bounds(canonicalized)
|
|
||||||
} else {
|
|
||||||
tcx.implied_outlives_bounds_compat(canonicalized)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_locally_with_next_solver(
|
fn perform_locally_with_next_solver(
|
||||||
|
@ -47,11 +44,7 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> {
|
||||||
key: ParamEnvAnd<'tcx, Self>,
|
key: ParamEnvAnd<'tcx, Self>,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<Self::QueryResponse, NoSolution> {
|
) -> 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, false)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,18 +53,15 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
|
||||||
param_env: ty::ParamEnv<'tcx>,
|
param_env: ty::ParamEnv<'tcx>,
|
||||||
ty: Ty<'tcx>,
|
ty: Ty<'tcx>,
|
||||||
span: Span,
|
span: Span,
|
||||||
|
disable_implied_bounds_hack: bool,
|
||||||
) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
|
) -> 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.
|
// 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>`,
|
// 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`.
|
// and we know that `&'a T::Out` is WF, then we want to imply `U: 'a`.
|
||||||
let ty = ocx
|
let ty = ocx
|
||||||
.deeply_normalize(&ObligationCause::dummy_with_span(span), param_env, ty)
|
.deeply_normalize(&ObligationCause::dummy_with_span(span), param_env, ty)
|
||||||
.map_err(|_| NoSolution)?;
|
.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)
|
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
|
// guaranteed to be a subset of the original type, so we need to store the
|
||||||
// WF args we've computed in a set.
|
// WF args we've computed in a set.
|
||||||
let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default();
|
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![];
|
let mut outlives_bounds: Vec<OutlivesBound<'tcx>> = vec![];
|
||||||
|
|
||||||
|
@ -96,8 +86,14 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
assert!(!obligation.has_escaping_bound_vars());
|
let pred = ocx
|
||||||
let Some(pred) = obligation.predicate.kind().no_bound_vars() else {
|
.deeply_normalize(
|
||||||
|
&ObligationCause::dummy_with_span(span),
|
||||||
|
param_env,
|
||||||
|
obligation.predicate,
|
||||||
|
)
|
||||||
|
.map_err(|_| NoSolution)?;
|
||||||
|
let Some(pred) = pred.kind().no_bound_vars() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
match pred {
|
match pred {
|
||||||
|
@ -130,7 +126,6 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
|
||||||
ty_a,
|
ty_a,
|
||||||
r_b,
|
r_b,
|
||||||
))) => {
|
))) => {
|
||||||
let ty_a = normalize_op(ty_a)?;
|
|
||||||
let mut components = smallvec![];
|
let mut components = smallvec![];
|
||||||
push_outlives_components(ocx.infcx.tcx, ty_a, &mut components);
|
push_outlives_components(ocx.infcx.tcx, ty_a, &mut components);
|
||||||
outlives_bounds.extend(implied_bounds_from_components(r_b, 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)
|
Ok(outlives_bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute_implied_outlives_bounds_compat_inner<'tcx>(
|
struct ContainsBevyParamSet<'tcx> {
|
||||||
ocx: &ObligationCtxt<'_, 'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
param_env: ty::ParamEnv<'tcx>,
|
|
||||||
ty: Ty<'tcx>,
|
|
||||||
span: Span,
|
|
||||||
) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
|
|
||||||
let tcx = ocx.infcx.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()];
|
|
||||||
|
|
||||||
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
|
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsBevyParamSet<'tcx> {
|
||||||
// an unresolved inference variable, just instantiated an empty set
|
type Result = ControlFlow<()>;
|
||||||
// -- 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 {
|
fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
|
||||||
debug!(?obligation);
|
// We only care to match `ParamSet<T>` or `&ParamSet<T>`.
|
||||||
assert!(!obligation.has_escaping_bound_vars());
|
match t.kind() {
|
||||||
|
ty::Adt(def, _) => {
|
||||||
// While these predicates should all be implied by other parts of
|
if self.tcx.item_name(def.did()) == sym::ParamSet
|
||||||
// the program, they are still relevant as they may constrain
|
&& self.tcx.crate_name(def.did().krate) == sym::bevy_ecs
|
||||||
// inference variables, which is necessary to add the correct
|
{
|
||||||
// implied bounds in some cases, mostly when dealing with projections.
|
return ControlFlow::Break(());
|
||||||
//
|
|
||||||
// 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());
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ty::Ref(_, ty, _) => ty.visit_with(self)?,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let pred = match obligation.predicate.kind().no_bound_vars() {
|
ControlFlow::Continue(())
|
||||||
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)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
/// 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]:
|
//! [rustc dev guide]:
|
||||||
//! https://rustc-dev-guide.rust-lang.org/traits/resolution.html#confirmation
|
//! https://rustc-dev-guide.rust-lang.org/traits/resolution.html#confirmation
|
||||||
|
|
||||||
use std::iter;
|
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
use rustc_ast::Mutability;
|
use rustc_ast::Mutability;
|
||||||
|
@ -1315,34 +1314,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
ImplSource::Builtin(BuiltinImplSource::Misc, nested)
|
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}"),
|
_ => bug!("source: {source}, target: {target}"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,38 +10,28 @@ use rustc_middle::query::Providers;
|
||||||
use rustc_middle::ty::TyCtxt;
|
use rustc_middle::ty::TyCtxt;
|
||||||
use rustc_span::DUMMY_SP;
|
use rustc_span::DUMMY_SP;
|
||||||
use rustc_trait_selection::infer::InferCtxtBuilderExt;
|
use rustc_trait_selection::infer::InferCtxtBuilderExt;
|
||||||
use rustc_trait_selection::traits::query::type_op::implied_outlives_bounds::{
|
use rustc_trait_selection::traits::query::type_op::implied_outlives_bounds::compute_implied_outlives_bounds_inner;
|
||||||
compute_implied_outlives_bounds_compat_inner, compute_implied_outlives_bounds_inner,
|
|
||||||
};
|
|
||||||
use rustc_trait_selection::traits::query::{CanonicalImpliedOutlivesBoundsGoal, NoSolution};
|
use rustc_trait_selection::traits::query::{CanonicalImpliedOutlivesBoundsGoal, NoSolution};
|
||||||
|
|
||||||
pub(crate) fn provide(p: &mut Providers) {
|
pub(crate) fn provide(p: &mut Providers) {
|
||||||
*p = Providers { implied_outlives_bounds_compat, ..*p };
|
|
||||||
*p = Providers { implied_outlives_bounds, ..*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>(
|
fn implied_outlives_bounds<'tcx>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
goal: CanonicalImpliedOutlivesBoundsGoal<'tcx>,
|
(goal, disable_implied_bounds_hack): (CanonicalImpliedOutlivesBoundsGoal<'tcx>, bool),
|
||||||
) -> Result<
|
) -> Result<
|
||||||
&'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec<OutlivesBound<'tcx>>>>,
|
&'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec<OutlivesBound<'tcx>>>>,
|
||||||
NoSolution,
|
NoSolution,
|
||||||
> {
|
> {
|
||||||
tcx.infer_ctxt().enter_canonical_trait_query(&goal, |ocx, key| {
|
tcx.infer_ctxt().enter_canonical_trait_query(&goal, |ocx, key| {
|
||||||
let (param_env, ImpliedOutlivesBounds { ty }) = key.into_parts();
|
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();
|
let tcx = cx.tcx();
|
||||||
|
|
||||||
if spec_abi == ExternAbi::Rust
|
if spec_abi.is_rustic_abi() {
|
||||||
|| spec_abi == ExternAbi::RustCall
|
|
||||||
|| spec_abi == ExternAbi::RustCold
|
|
||||||
{
|
|
||||||
if arg.layout.is_zst() {
|
if arg.layout.is_zst() {
|
||||||
// Casting closures to function pointers depends on ZST closure types being
|
// Casting closures to function pointers depends on ZST closure types being
|
||||||
// omitted entirely in the calling convention.
|
// omitted entirely in the calling convention.
|
||||||
|
@ -687,7 +684,7 @@ fn fn_abi_adjust_for_abi<'tcx>(
|
||||||
|
|
||||||
let tcx = cx.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);
|
fn_abi.adjust_for_rust_abi(cx, abi);
|
||||||
|
|
||||||
// Look up the deduced parameter attributes for this function, if we have its def ID and
|
// 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::Param(..)
|
||||||
| traits::ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
|
| traits::ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => None,
|
||||||
| traits::ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -189,11 +189,6 @@ pub enum BuiltinImplSource {
|
||||||
///
|
///
|
||||||
/// The index is only used for winnowing.
|
/// The index is only used for winnowing.
|
||||||
TraitUpcasting(usize),
|
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)]
|
#[derive_where(Clone, Copy, Hash, PartialEq, Eq, Debug; I: Interner)]
|
||||||
|
|
|
@ -189,6 +189,17 @@
|
||||||
# The default stage to use for the `bench` subcommand
|
# The default stage to use for the `bench` subcommand
|
||||||
#bench-stage = 2
|
#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
|
# 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
|
# 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
|
# 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".
|
# If using tarball sources, default value is "auto-detect", otherwise, it's "dev".
|
||||||
#channel = if "is a tarball source" { "auto-detect" } else { "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
|
# The root location of the musl installation directory. The library directory
|
||||||
# will also need to contain libunwind.a for an unwinding implementation. Note
|
# will also need to contain libunwind.a for an unwinding implementation. Note
|
||||||
# that this option only makes sense for musl targets that produce statically
|
# that this option only makes sense for musl targets that produce statically
|
||||||
|
|
|
@ -151,9 +151,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.169"
|
version = "0.2.170"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustc-std-workspace-core",
|
"rustc-std-workspace-core",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1134,7 +1134,6 @@ impl String {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// #![feature(string_extend_from_within)]
|
|
||||||
/// let mut string = String::from("abcde");
|
/// let mut string = String::from("abcde");
|
||||||
///
|
///
|
||||||
/// string.extend_from_within(2..);
|
/// string.extend_from_within(2..);
|
||||||
|
@ -1147,7 +1146,7 @@ impl String {
|
||||||
/// assert_eq!(string, "abcdecdeabecde");
|
/// assert_eq!(string, "abcdecdeabecde");
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(not(no_global_oom_handling))]
|
#[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)
|
pub fn extend_from_within<R>(&mut self, src: R)
|
||||||
where
|
where
|
||||||
R: RangeBounds<usize>,
|
R: RangeBounds<usize>,
|
||||||
|
|
|
@ -1693,49 +1693,41 @@ impl<'a> Formatter<'a> {
|
||||||
/// ```
|
/// ```
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub fn pad(&mut self, s: &str) -> Result {
|
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() {
|
if self.options.width.is_none() && self.options.precision.is_none() {
|
||||||
return self.buf.write_str(s);
|
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.
|
// string being formatted.
|
||||||
let s = if let Some(max) = self.options.precision {
|
let (s, char_count) = if let Some(max_char_count) = self.options.precision {
|
||||||
// If our string is longer that the precision, then we must have
|
let mut iter = s.char_indices();
|
||||||
// truncation. However other flags like `fill`, `width` and `align`
|
let remaining = match iter.advance_by(max_char_count) {
|
||||||
// must act as always.
|
Ok(()) => 0,
|
||||||
if let Some((i, _)) = s.char_indices().nth(max) {
|
Err(remaining) => remaining.get(),
|
||||||
// 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
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
&s
|
|
||||||
};
|
};
|
||||||
// The `width` field is more of a `min-width` parameter at this point.
|
// SAFETY: The offset of `.char_indices()` is guaranteed to be
|
||||||
match self.options.width {
|
// in-bounds and between character boundaries.
|
||||||
// If we're under the maximum length, and there's no minimum length
|
let truncated = unsafe { s.get_unchecked(..iter.offset()) };
|
||||||
// requirements, then we can just emit the string
|
(truncated, max_char_count - remaining)
|
||||||
None => self.buf.write_str(s),
|
} else {
|
||||||
Some(width) => {
|
// Use the optimized char counting algorithm for the full string.
|
||||||
let chars_count = s.chars().count();
|
(s, 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 {
|
// The `width` field is more of a minimum width parameter at this point.
|
||||||
self.buf.write_str(s)
|
if let Some(width) = self.options.width
|
||||||
}
|
&& char_count < width
|
||||||
// If we're under both the maximum and the minimum width, then fill
|
{
|
||||||
// up the minimum width with the specified string + some alignment.
|
// If we're under the minimum width, then fill up the minimum width
|
||||||
else {
|
// with the specified string + some alignment.
|
||||||
let align = Alignment::Left;
|
let post_padding = self.padding(width - char_count, Alignment::Left)?;
|
||||||
let post_padding = self.padding(width - chars_count, align)?;
|
|
||||||
self.buf.write_str(s)?;
|
self.buf.write_str(s)?;
|
||||||
post_padding.write(self)
|
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.
|
/// Writes a 64-bit integer as 8 bytes in little-endian order.
|
||||||
fn write_u64(&mut self, value: u64);
|
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;
|
fn offset_from(&self, other: &Self) -> isize;
|
||||||
|
|
||||||
/// Iteratively parse and consume digits from bytes.
|
/// 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;
|
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 {
|
fn parse_digits(&self, mut func: impl FnMut(u8)) -> &Self {
|
||||||
let mut s = 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');
|
let c = c.wrapping_sub(b'0');
|
||||||
if c < 10 {
|
if c < 10 {
|
||||||
func(c);
|
func(c);
|
||||||
s = s_next;
|
s = rest;
|
||||||
} else {
|
} else {
|
||||||
break;
|
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.
|
/// This does not care about the order in which the bytes were loaded.
|
||||||
pub(crate) fn is_8digits(v: u64) -> bool {
|
pub(crate) fn is_8digits(v: u64) -> bool {
|
||||||
let a = v.wrapping_add(0x4646_4646_4646_4646);
|
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 | b) & 0x8080_8080_8080_8080 == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A custom 64-bit floating point type, representing `f * 2^e`.
|
/// A custom 64-bit floating point type, representing `m * 2^p`.
|
||||||
/// e is biased, so it be directly shifted into the exponent bits.
|
/// p is biased, so it be directly shifted into the exponent bits.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||||
pub struct BiasedFp {
|
pub struct BiasedFp {
|
||||||
/// The significant digits.
|
/// The significant digits.
|
||||||
pub f: u64,
|
pub m: u64,
|
||||||
/// The biased, binary exponent.
|
/// The biased, binary exponent.
|
||||||
pub e: i32,
|
pub p_biased: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BiasedFp {
|
impl BiasedFp {
|
||||||
|
/// Represent `0 ^ p`
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn zero_pow2(e: i32) -> Self {
|
pub const fn zero_pow2(p_biased: i32) -> Self {
|
||||||
Self { f: 0, e }
|
Self { m: 0, p_biased }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,358 +1,87 @@
|
||||||
//! Arbitrary-precision decimal class for fallback algorithms.
|
//! Representation of a float as the significant digits and exponent.
|
||||||
//!
|
|
||||||
//! 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};
|
use crate::num::dec2flt::float::RawFloat;
|
||||||
|
use crate::num::dec2flt::fpu::set_precision;
|
||||||
|
|
||||||
#[derive(Clone)]
|
const INT_POW10: [u64; 16] = [
|
||||||
pub(super) struct Decimal {
|
1,
|
||||||
/// The number of significant digits in the decimal.
|
10,
|
||||||
pub num_digits: usize,
|
100,
|
||||||
/// The offset of the decimal point in the significant digits.
|
1000,
|
||||||
pub decimal_point: i32,
|
10000,
|
||||||
/// If the number of significant digits stored in the decimal is truncated.
|
100000,
|
||||||
pub truncated: bool,
|
1000000,
|
||||||
/// Buffer of the raw digits, in the range [0, 9].
|
10000000,
|
||||||
pub digits: [u8; Self::MAX_DIGITS],
|
100000000,
|
||||||
}
|
1000000000,
|
||||||
|
10000000000,
|
||||||
|
100000000000,
|
||||||
|
1000000000000,
|
||||||
|
10000000000000,
|
||||||
|
100000000000000,
|
||||||
|
1000000000000000,
|
||||||
|
];
|
||||||
|
|
||||||
impl Default for Decimal {
|
/// A floating point number with up to 64 bits of mantissa and an `i64` exponent.
|
||||||
fn default() -> Self {
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
Self { num_digits: 0, decimal_point: 0, truncated: false, digits: [0; Self::MAX_DIGITS] }
|
pub struct Decimal {
|
||||||
}
|
pub exponent: i64,
|
||||||
|
pub mantissa: u64,
|
||||||
|
pub negative: bool,
|
||||||
|
pub many_digits: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decimal {
|
impl Decimal {
|
||||||
/// The maximum number of digits required to unambiguously round a float.
|
/// Detect if the float can be accurately reconstructed from native floats.
|
||||||
///
|
#[inline]
|
||||||
/// For a double-precision IEEE 754 float, this required 767 digits,
|
fn can_use_fast_path<F: RawFloat>(&self) -> bool {
|
||||||
/// so we store the max digits + 1.
|
F::MIN_EXPONENT_FAST_PATH <= self.exponent
|
||||||
///
|
&& self.exponent <= F::MAX_EXPONENT_DISGUISED_FAST_PATH
|
||||||
/// We can exactly represent a float in radix `b` from radix 2 if
|
&& self.mantissa <= F::MAX_MANTISSA_FAST_PATH
|
||||||
/// `b` is divisible by 2. This function calculates the exact number of
|
&& !self.many_digits
|
||||||
/// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trim trailing zeros from the buffer.
|
/// Try turning the decimal into an exact float representation, using machine-sized integers
|
||||||
pub(super) fn trim(&mut self) {
|
/// and floats.
|
||||||
// All of the following calls to `Decimal::trim` can't panic because:
|
///
|
||||||
//
|
/// This is extracted into a separate function so that it can be attempted before constructing
|
||||||
// 1. `parse_decimal` sets `num_digits` to a max of `Decimal::MAX_DIGITS`.
|
/// a Decimal. This only works if both the mantissa and the exponent
|
||||||
// 2. `right_shift` sets `num_digits` to `write_index`, which is bounded by `num_digits`.
|
/// can be exactly represented as a machine float, since IEE-754 guarantees
|
||||||
// 3. `left_shift` `num_digits` to a max of `Decimal::MAX_DIGITS`.
|
/// no rounding will occur.
|
||||||
//
|
///
|
||||||
// Trim is only called in `right_shift` and `left_shift`.
|
/// There is an exception: disguised fast-path cases, where we can shift
|
||||||
debug_assert!(self.num_digits <= Self::MAX_DIGITS);
|
/// powers-of-10 from the exponent to the significant digits.
|
||||||
while self.num_digits != 0 && self.digits[self.num_digits - 1] == 0 {
|
pub fn try_fast_path<F: RawFloat>(&self) -> Option<F> {
|
||||||
self.num_digits -= 1;
|
// 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.can_use_fast_path::<F>() {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn round(&self) -> u64 {
|
let value = if self.exponent <= F::MAX_EXPONENT_FAST_PATH {
|
||||||
if self.num_digits == 0 || self.decimal_point < 0 {
|
// normal fast path
|
||||||
return 0;
|
let value = F::from_u64(self.mantissa);
|
||||||
} else if self.decimal_point > 18 {
|
if self.exponent < 0 {
|
||||||
return 0xFFFF_FFFF_FFFF_FFFF_u64;
|
value / F::pow10_fast_path((-self.exponent) as _)
|
||||||
}
|
|
||||||
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 {
|
} else {
|
||||||
while (n >> shift) == 0 {
|
value * F::pow10_fast_path(self.exponent as _)
|
||||||
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(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 {
|
} 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.
|
//! Helper trait for generic float types.
|
||||||
|
|
||||||
|
use core::f64;
|
||||||
|
|
||||||
use crate::fmt::{Debug, LowerExp};
|
use crate::fmt::{Debug, LowerExp};
|
||||||
use crate::num::FpCategory;
|
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.
|
/// 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)]
|
#[doc(hidden)]
|
||||||
pub trait RawFloat:
|
pub trait RawFloat:
|
||||||
Sized
|
Sized
|
||||||
|
@ -24,62 +67,107 @@ pub trait RawFloat:
|
||||||
+ Copy
|
+ Copy
|
||||||
+ Debug
|
+ Debug
|
||||||
{
|
{
|
||||||
|
/// The unsigned integer with the same size as the float
|
||||||
|
type Int: Integer + Into<u64>;
|
||||||
|
|
||||||
|
/* general constants */
|
||||||
|
|
||||||
const INFINITY: Self;
|
const INFINITY: Self;
|
||||||
const NEG_INFINITY: Self;
|
const NEG_INFINITY: Self;
|
||||||
const NAN: Self;
|
const NAN: Self;
|
||||||
const NEG_NAN: Self;
|
const NEG_NAN: Self;
|
||||||
|
|
||||||
/// The number of bits in the significand, *excluding* the hidden bit.
|
/// Bit width of the float
|
||||||
const MANTISSA_EXPLICIT_BITS: usize;
|
const BITS: u32;
|
||||||
|
|
||||||
// Round-to-even only happens for negative values of q
|
/// The number of bits in the significand, *including* the hidden bit.
|
||||||
// when q ≥ −4 in the 64-bit case and when q ≥ −17 in
|
const SIG_TOTAL_BITS: u32;
|
||||||
// the 32-bitcase.
|
|
||||||
//
|
const EXP_MASK: Self::Int;
|
||||||
// When q ≥ 0,we have that 5^q ≤ 2m+1. In the 64-bit case,we
|
const SIG_MASK: Self::Int;
|
||||||
// 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.
|
/// The number of bits in the significand, *excluding* the hidden bit.
|
||||||
//
|
const SIG_BITS: u32 = Self::SIG_TOTAL_BITS - 1;
|
||||||
// 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)
|
/// Number of bits in the exponent.
|
||||||
// or 2m+1 > 2^24 (32-bit case). Hence,we must have 2^53×5^−q < 2^64
|
const EXP_BITS: u32 = Self::BITS - Self::SIG_BITS - 1;
|
||||||
// (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).
|
/// The saturated (maximum bitpattern) value of the exponent, i.e. the infinite
|
||||||
//
|
/// representation.
|
||||||
// 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]
|
/// This shifted fully right, use `EXP_MASK` for the shifted value.
|
||||||
// (in the 32-bit case). In both cases,the power of five(5^|q|)
|
const EXP_SAT: u32 = (1 << Self::EXP_BITS) - 1;
|
||||||
// fits in a 64-bit word.
|
|
||||||
|
/// 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 MIN_EXPONENT_ROUND_TO_EVEN: i32;
|
||||||
const MAX_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)⌋`
|
/* limits related to Fast pathing */
|
||||||
const MIN_EXPONENT_FAST_PATH: i64;
|
|
||||||
|
|
||||||
// Maximum exponent that for a fast path case, or `⌊(MANTISSA_EXPLICIT_BITS+1)/log2(5)⌋`
|
/// Largest decimal exponent for a non-infinite value.
|
||||||
const MAX_EXPONENT_FAST_PATH: i64;
|
///
|
||||||
|
/// 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.
|
/// Smallest decimal exponent for a non-zero value. This allows for fast pathing anything
|
||||||
// This is `MAX_EXPONENT_FAST_PATH + ⌊(MANTISSA_EXPLICIT_BITS+1)/log2(10)⌋`
|
/// smaller than `10^SMALLEST_POWER_OF_TEN`, which will round to zero.
|
||||||
const MAX_EXPONENT_DISGUISED_FAST_PATH: i64;
|
///
|
||||||
|
/// The smallest power of ten is represented by `⌊log10(2^-n / (2^64 - 1))⌋`, where `n` is
|
||||||
// Minimum exponent value `-(1 << (EXP_BITS - 1)) + 1`.
|
/// the smallest power of two. The `2^64 - 1)` denomenator comes from the number of values
|
||||||
const MINIMUM_EXPONENT: i32;
|
/// that are representable by the intermediate storage format. I don't actually know _why_
|
||||||
|
/// the storage format is relevant here.
|
||||||
// Largest exponent value `(1 << EXP_BITS) - 1`.
|
///
|
||||||
const INFINITE_POWER: i32;
|
/// The values may be calculated using the formula. Unfortunately we cannot calculate them at
|
||||||
|
/// compile time since intermediates exceed the range of an `f64`.
|
||||||
// Index (in bits) of the sign.
|
|
||||||
const SIGN_INDEX: usize;
|
|
||||||
|
|
||||||
// Smallest decimal exponent for a non-zero value.
|
|
||||||
const SMALLEST_POWER_OF_TEN: i32;
|
const SMALLEST_POWER_OF_TEN: i32;
|
||||||
|
|
||||||
// Largest decimal exponent for a non-infinite value.
|
/// Maximum exponent for a fast path case, or `⌊(SIG_BITS+1)/log2(5)⌋`
|
||||||
const LARGEST_POWER_OF_TEN: i32;
|
// 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).
|
/// Minimum exponent for a fast path case, or `-⌊(SIG_BITS+1)/log2(5)⌋`
|
||||||
const MAX_MANTISSA_FAST_PATH: u64 = 2_u64 << Self::MANTISSA_EXPLICIT_BITS;
|
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.
|
/// Converts integer into float through an as cast.
|
||||||
/// This is only called in the fast-path algorithm, and therefore
|
/// 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.
|
/// Returns the category that this number falls into.
|
||||||
fn classify(self) -> FpCategory;
|
fn classify(self) -> FpCategory;
|
||||||
|
|
||||||
|
/// Transmute to the integer representation
|
||||||
|
fn to_bits(self) -> Self::Int;
|
||||||
|
|
||||||
/// Returns the mantissa, exponent and sign as integers.
|
/// 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 {
|
impl RawFloat for f32 {
|
||||||
|
type Int = u32;
|
||||||
|
|
||||||
const INFINITY: Self = f32::INFINITY;
|
const INFINITY: Self = f32::INFINITY;
|
||||||
const NEG_INFINITY: Self = f32::NEG_INFINITY;
|
const NEG_INFINITY: Self = f32::NEG_INFINITY;
|
||||||
const NAN: Self = f32::NAN;
|
const NAN: Self = f32::NAN;
|
||||||
const NEG_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 MIN_EXPONENT_ROUND_TO_EVEN: i32 = -17;
|
||||||
const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 10;
|
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 SMALLEST_POWER_OF_TEN: i32 = -65;
|
||||||
const LARGEST_POWER_OF_TEN: i32 = 38;
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_u64(v: u64) -> Self {
|
fn from_u64(v: u64) -> Self {
|
||||||
|
@ -136,16 +248,8 @@ impl RawFloat for f32 {
|
||||||
TABLE[exponent & 15]
|
TABLE[exponent & 15]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the mantissa, exponent and sign as integers.
|
fn to_bits(self) -> Self::Int {
|
||||||
fn integer_decode(self) -> (u64, i16, i8) {
|
self.to_bits()
|
||||||
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 classify(self) -> FpCategory {
|
fn classify(self) -> FpCategory {
|
||||||
|
@ -154,22 +258,21 @@ impl RawFloat for f32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RawFloat for f64 {
|
impl RawFloat for f64 {
|
||||||
const INFINITY: Self = f64::INFINITY;
|
type Int = u64;
|
||||||
const NEG_INFINITY: Self = f64::NEG_INFINITY;
|
|
||||||
const NAN: Self = f64::NAN;
|
const INFINITY: Self = Self::INFINITY;
|
||||||
const NEG_NAN: Self = -f64::NAN;
|
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 MIN_EXPONENT_ROUND_TO_EVEN: i32 = -4;
|
||||||
const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 23;
|
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 SMALLEST_POWER_OF_TEN: i32 = -342;
|
||||||
const LARGEST_POWER_OF_TEN: i32 = 308;
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_u64(v: u64) -> Self {
|
fn from_u64(v: u64) -> Self {
|
||||||
|
@ -190,19 +293,8 @@ impl RawFloat for f64 {
|
||||||
TABLE[exponent & 31]
|
TABLE[exponent & 31]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the mantissa, exponent and sign as integers.
|
fn to_bits(self) -> Self::Int {
|
||||||
fn integer_decode(self) -> (u64, i16, i8) {
|
self.to_bits()
|
||||||
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 classify(self) -> FpCategory {
|
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.
|
// Normalize our significant digits, so the most-significant bit is set.
|
||||||
let lz = w.leading_zeros();
|
let lz = w.leading_zeros();
|
||||||
w <<= lz;
|
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 lo == 0xFFFF_FFFF_FFFF_FFFF {
|
||||||
// If we have failed to approximate w x 5^-q with our 128-bit value.
|
// 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
|
// 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 upperbit = (hi >> 63) as i32;
|
||||||
let mut mantissa = hi >> (upperbit + 64 - F::MANTISSA_EXPLICIT_BITS as i32 - 3);
|
let mut mantissa = hi >> (upperbit + 64 - F::SIG_BITS as i32 - 3);
|
||||||
let mut power2 = power(q as i32) + upperbit - lz as i32 - F::MINIMUM_EXPONENT;
|
let mut power2 = power(q as i32) + upperbit - lz as i32 - F::EXP_MIN + 1;
|
||||||
if power2 <= 0 {
|
if power2 <= 0 {
|
||||||
if -power2 + 1 >= 64 {
|
if -power2 + 1 >= 64 {
|
||||||
// Have more than 64 bits below the minimum exponent, must be 0.
|
// 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 >>= -power2 + 1;
|
||||||
mantissa += mantissa & 1;
|
mantissa += mantissa & 1;
|
||||||
mantissa >>= 1;
|
mantissa >>= 1;
|
||||||
power2 = (mantissa >= (1_u64 << F::MANTISSA_EXPLICIT_BITS)) as i32;
|
power2 = (mantissa >= (1_u64 << F::SIG_BITS)) as i32;
|
||||||
return BiasedFp { f: mantissa, e: power2 };
|
return BiasedFp { m: mantissa, p_biased: power2 };
|
||||||
}
|
}
|
||||||
// Need to handle rounding ties. Normally, we need to round up,
|
// 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
|
// 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
|
if lo <= 1
|
||||||
&& q >= F::MIN_EXPONENT_ROUND_TO_EVEN as i64
|
&& q >= F::MIN_EXPONENT_ROUND_TO_EVEN as i64
|
||||||
&& q <= F::MAX_EXPONENT_ROUND_TO_EVEN as i64
|
&& q <= F::MAX_EXPONENT_ROUND_TO_EVEN as i64
|
||||||
&& mantissa & 3 == 1
|
&& mantissa & 0b11 == 0b01
|
||||||
&& (mantissa << (upperbit + 64 - F::MANTISSA_EXPLICIT_BITS as i32 - 3)) == hi
|
&& (mantissa << (upperbit + 64 - F::SIG_BITS as i32 - 3)) == hi
|
||||||
{
|
{
|
||||||
// Zero the lowest bit, so we don't round up.
|
// Zero the lowest bit, so we don't round up.
|
||||||
mantissa &= !1_u64;
|
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.
|
// Round-to-even, then shift the significant digits into place.
|
||||||
mantissa += mantissa & 1;
|
mantissa += mantissa & 1;
|
||||||
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
|
// Rounding up overflowed, so the carry bit is set. Set the
|
||||||
// mantissa to 1 (only the implicit, hidden bit is set) and
|
// mantissa to 1 (only the implicit, hidden bit is set) and
|
||||||
// increase the exponent.
|
// increase the exponent.
|
||||||
mantissa = 1_u64 << F::MANTISSA_EXPLICIT_BITS;
|
mantissa = 1_u64 << F::SIG_BITS;
|
||||||
power2 += 1;
|
power2 += 1;
|
||||||
}
|
}
|
||||||
// Zero out the hidden bit.
|
// Zero out the hidden bit.
|
||||||
mantissa &= !(1_u64 << F::MANTISSA_EXPLICIT_BITS);
|
mantissa &= !(1_u64 << F::SIG_BITS);
|
||||||
if power2 >= F::INFINITE_POWER {
|
if power2 >= F::INFINITE_POWER {
|
||||||
// Exponent is above largest normal value, must be infinite.
|
// Exponent is above largest normal value, must be infinite.
|
||||||
return fp_inf;
|
return fp_inf;
|
||||||
}
|
}
|
||||||
BiasedFp { f: mantissa, e: power2 }
|
BiasedFp { m: mantissa, p_biased: power2 }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate a base 2 exponent from a decimal exponent.
|
/// Calculate a base 2 exponent from a decimal exponent.
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
//! # Problem statement
|
//! # Problem statement
|
||||||
//!
|
//!
|
||||||
//! We are given a decimal string such as `12.34e56`. This string consists of integral (`12`),
|
//! 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
|
//! fractional (`34`), and exponent (`56`) parts. All parts are optional and interpreted as a
|
||||||
//! when missing.
|
//! 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
|
//! 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
|
//! 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".
|
//! "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
|
//! Larger exponents are accepted, but we don't do arithmetic with them, they are immediately
|
||||||
//! turned into {positive,negative} {zero,infinity}.
|
//! 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)]
|
#![doc(hidden)]
|
||||||
#![unstable(
|
#![unstable(
|
||||||
|
@ -85,14 +97,14 @@ use crate::fmt;
|
||||||
use crate::str::FromStr;
|
use crate::str::FromStr;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
mod decimal;
|
pub mod decimal;
|
||||||
|
pub mod decimal_seq;
|
||||||
mod fpu;
|
mod fpu;
|
||||||
mod slow;
|
mod slow;
|
||||||
mod table;
|
mod table;
|
||||||
// float is used in flt2dec, and all are used in unit tests.
|
// float is used in flt2dec, and all are used in unit tests.
|
||||||
pub mod float;
|
pub mod float;
|
||||||
pub mod lemire;
|
pub mod lemire;
|
||||||
pub mod number;
|
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
|
|
||||||
macro_rules! from_str_float_impl {
|
macro_rules! from_str_float_impl {
|
||||||
|
@ -220,10 +232,10 @@ pub fn pfe_invalid() -> ParseFloatError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a `BiasedFp` to the closest machine float type.
|
/// Converts a `BiasedFp` to the closest machine float type.
|
||||||
fn biased_fp_to_float<T: RawFloat>(x: BiasedFp) -> T {
|
fn biased_fp_to_float<F: RawFloat>(x: BiasedFp) -> F {
|
||||||
let mut word = x.f;
|
let mut word = x.m;
|
||||||
word |= (x.e as u64) << T::MANTISSA_EXPLICIT_BITS;
|
word |= (x.p_biased as u64) << F::SIG_BITS;
|
||||||
T::from_u64_bits(word)
|
F::from_u64_bits(word)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a decimal string into a floating point number.
|
/// 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
|
// redundantly using the Eisel-Lemire algorithm if it was unable to
|
||||||
// correctly round on the first pass.
|
// correctly round on the first pass.
|
||||||
let mut fp = compute_float::<F>(num.exponent, num.mantissa);
|
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) {
|
if num.many_digits
|
||||||
fp.e = -1;
|
&& 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.
|
// Unable to correctly round the float using the Eisel-Lemire algorithm.
|
||||||
// Fallback to a slower, but always correct algorithm.
|
// Fallback to a slower, but always correct algorithm.
|
||||||
if fp.e < 0 {
|
if fp.p_biased < 0 {
|
||||||
fp = parse_long_mantissa::<F>(s);
|
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.
|
//! Functions to parse floating-point numbers.
|
||||||
|
|
||||||
use crate::num::dec2flt::common::{ByteSlice, is_8digits};
|
use crate::num::dec2flt::common::{ByteSlice, is_8digits};
|
||||||
|
use crate::num::dec2flt::decimal::Decimal;
|
||||||
use crate::num::dec2flt::float::RawFloat;
|
use crate::num::dec2flt::float::RawFloat;
|
||||||
use crate::num::dec2flt::number::Number;
|
|
||||||
|
|
||||||
const MIN_19DIGIT_INT: u64 = 100_0000_0000_0000_0000;
|
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
|
/// This creates a representation of the float as the
|
||||||
/// significant digits and the decimal exponent.
|
/// 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());
|
debug_assert!(!s.is_empty());
|
||||||
|
|
||||||
// parse initial digits before dot
|
// 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
|
// handle uncommon case with many digits
|
||||||
if n_digits <= 19 {
|
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;
|
n_digits -= 19;
|
||||||
|
@ -179,13 +179,13 @@ fn parse_partial_number(mut s: &[u8]) -> Option<(Number, usize)> {
|
||||||
exponent += exp_number;
|
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,
|
/// Try to parse a non-special floating point number,
|
||||||
/// as well as two slices with integer and fractional parts
|
/// as well as two slices with integer and fractional parts
|
||||||
/// and the parsed exponent.
|
/// 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 let Some((float, rest)) = parse_partial_number(s) {
|
||||||
if rest == s.len() {
|
if rest == s.len() {
|
||||||
return Some(float);
|
return Some(float);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Slow, fallback algorithm for cases the Eisel-Lemire algorithm cannot round.
|
//! Slow, fallback algorithm for cases the Eisel-Lemire algorithm cannot round.
|
||||||
|
|
||||||
use crate::num::dec2flt::common::BiasedFp;
|
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;
|
use crate::num::dec2flt::float::RawFloat;
|
||||||
|
|
||||||
/// Parse the significant digits and biased, binary exponent of a float.
|
/// 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_zero = BiasedFp::zero_pow2(0);
|
||||||
let fp_inf = BiasedFp::zero_pow2(F::INFINITE_POWER);
|
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.
|
// Short-circuit if the value can only be a literal 0 or infinity.
|
||||||
if d.num_digits == 0 || d.decimal_point < -324 {
|
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 n = d.decimal_point as usize;
|
||||||
let shift = get_shift(n);
|
let shift = get_shift(n);
|
||||||
d.right_shift(shift);
|
d.right_shift(shift);
|
||||||
if d.decimal_point < -Decimal::DECIMAL_POINT_RANGE {
|
if d.decimal_point < -DecimalSeq::DECIMAL_POINT_RANGE {
|
||||||
return fp_zero;
|
return fp_zero;
|
||||||
}
|
}
|
||||||
exp2 += shift as i32;
|
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 _)
|
get_shift((-d.decimal_point) as _)
|
||||||
};
|
};
|
||||||
d.left_shift(shift);
|
d.left_shift(shift);
|
||||||
if d.decimal_point > Decimal::DECIMAL_POINT_RANGE {
|
if d.decimal_point > DecimalSeq::DECIMAL_POINT_RANGE {
|
||||||
return fp_inf;
|
return fp_inf;
|
||||||
}
|
}
|
||||||
exp2 -= shift as i32;
|
exp2 -= shift as i32;
|
||||||
}
|
}
|
||||||
// We are now in the range [1/2 ... 1] but the binary format uses [1 ... 2].
|
// We are now in the range [1/2 ... 1] but the binary format uses [1 ... 2].
|
||||||
exp2 -= 1;
|
exp2 -= 1;
|
||||||
while (F::MINIMUM_EXPONENT + 1) > exp2 {
|
while F::EXP_MIN > exp2 {
|
||||||
let mut n = ((F::MINIMUM_EXPONENT + 1) - exp2) as usize;
|
let mut n = (F::EXP_MIN - exp2) as usize;
|
||||||
if n > MAX_SHIFT {
|
if n > MAX_SHIFT {
|
||||||
n = MAX_SHIFT;
|
n = MAX_SHIFT;
|
||||||
}
|
}
|
||||||
d.right_shift(n);
|
d.right_shift(n);
|
||||||
exp2 += n as i32;
|
exp2 += n as i32;
|
||||||
}
|
}
|
||||||
if (exp2 - F::MINIMUM_EXPONENT) >= F::INFINITE_POWER {
|
if (exp2 - F::EXP_MIN + 1) >= F::INFINITE_POWER {
|
||||||
return fp_inf;
|
return fp_inf;
|
||||||
}
|
}
|
||||||
// Shift the decimal to the hidden bit, and then round the value
|
// Shift the decimal to the hidden bit, and then round the value
|
||||||
// to get the high mantissa+1 bits.
|
// 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();
|
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
|
// Rounding up overflowed to the carry bit, need to
|
||||||
// shift back to the hidden bit.
|
// shift back to the hidden bit.
|
||||||
d.right_shift(1);
|
d.right_shift(1);
|
||||||
exp2 += 1;
|
exp2 += 1;
|
||||||
mantissa = d.round();
|
mantissa = d.round();
|
||||||
if (exp2 - F::MINIMUM_EXPONENT) >= F::INFINITE_POWER {
|
if (exp2 - F::EXP_MIN + 1) >= F::INFINITE_POWER {
|
||||||
return fp_inf;
|
return fp_inf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut power2 = exp2 - F::MINIMUM_EXPONENT;
|
let mut power2 = exp2 - F::EXP_MIN + 1;
|
||||||
if mantissa < (1_u64 << F::MANTISSA_EXPLICIT_BITS) {
|
if mantissa < (1_u64 << F::SIG_BITS) {
|
||||||
power2 -= 1;
|
power2 -= 1;
|
||||||
}
|
}
|
||||||
// Zero out all the bits above the explicit mantissa bits.
|
// Zero out all the bits above the explicit mantissa bits.
|
||||||
mantissa &= (1_u64 << F::MANTISSA_EXPLICIT_BITS) - 1;
|
mantissa &= (1_u64 << F::SIG_BITS) - 1;
|
||||||
BiasedFp { f: mantissa, e: power2 }
|
BiasedFp { m: mantissa, p_biased: power2 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -493,13 +493,13 @@ impl f32 {
|
||||||
pub const NEG_INFINITY: f32 = -1.0_f32 / 0.0_f32;
|
pub const NEG_INFINITY: f32 = -1.0_f32 / 0.0_f32;
|
||||||
|
|
||||||
/// Sign bit
|
/// Sign bit
|
||||||
const SIGN_MASK: u32 = 0x8000_0000;
|
pub(crate) const SIGN_MASK: u32 = 0x8000_0000;
|
||||||
|
|
||||||
/// Exponent mask
|
/// Exponent mask
|
||||||
const EXP_MASK: u32 = 0x7f80_0000;
|
pub(crate) const EXP_MASK: u32 = 0x7f80_0000;
|
||||||
|
|
||||||
/// Mantissa mask
|
/// Mantissa mask
|
||||||
const MAN_MASK: u32 = 0x007f_ffff;
|
pub(crate) const MAN_MASK: u32 = 0x007f_ffff;
|
||||||
|
|
||||||
/// Minimum representable positive value (min subnormal)
|
/// Minimum representable positive value (min subnormal)
|
||||||
const TINY_BITS: u32 = 0x1;
|
const TINY_BITS: u32 = 0x1;
|
||||||
|
|
|
@ -492,13 +492,13 @@ impl f64 {
|
||||||
pub const NEG_INFINITY: f64 = -1.0_f64 / 0.0_f64;
|
pub const NEG_INFINITY: f64 = -1.0_f64 / 0.0_f64;
|
||||||
|
|
||||||
/// Sign bit
|
/// Sign bit
|
||||||
const SIGN_MASK: u64 = 0x8000_0000_0000_0000;
|
pub(crate) const SIGN_MASK: u64 = 0x8000_0000_0000_0000;
|
||||||
|
|
||||||
/// Exponent mask
|
/// Exponent mask
|
||||||
const EXP_MASK: u64 = 0x7ff0_0000_0000_0000;
|
pub(crate) const EXP_MASK: u64 = 0x7ff0_0000_0000_0000;
|
||||||
|
|
||||||
/// Mantissa mask
|
/// 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)
|
/// Minimum representable positive value (min subnormal)
|
||||||
const TINY_BITS: u64 = 0x1;
|
const TINY_BITS: u64 = 0x1;
|
||||||
|
|
|
@ -87,7 +87,6 @@
|
||||||
#![feature(try_find)]
|
#![feature(try_find)]
|
||||||
#![feature(try_trait_v2)]
|
#![feature(try_trait_v2)]
|
||||||
#![feature(unsize)]
|
#![feature(unsize)]
|
||||||
#![feature(unsized_tuple_coercion)]
|
|
||||||
#![feature(unwrap_infallible)]
|
#![feature(unwrap_infallible)]
|
||||||
// tidy-alphabetical-end
|
// tidy-alphabetical-end
|
||||||
#![allow(internal_features)]
|
#![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.
|
// Ignore the "sign" (quiet / signalling flag) of NAN.
|
||||||
// It can vary between runtime operations and LLVM folding.
|
// It can vary between runtime operations and LLVM folding.
|
||||||
let (nan_m, nan_e, _nan_s) = f32::NAN.integer_decode();
|
let (nan_m, nan_p, _nan_s) = f32::NAN.integer_decode();
|
||||||
assert_eq!((nan_m, nan_e), (12582912, 105));
|
assert_eq!((nan_m, nan_p), (12582912, 105));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -28,6 +28,46 @@ fn test_f64_integer_decode() {
|
||||||
|
|
||||||
// Ignore the "sign" (quiet / signalling flag) of NAN.
|
// Ignore the "sign" (quiet / signalling flag) of NAN.
|
||||||
// It can vary between runtime operations and LLVM folding.
|
// It can vary between runtime operations and LLVM folding.
|
||||||
let (nan_m, nan_e, _nan_s) = f64::NAN.integer_decode();
|
let (nan_m, nan_p, _nan_s) = f64::NAN.integer_decode();
|
||||||
assert_eq!((nan_m, nan_e), (6755399441055744, 972));
|
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;
|
use core::num::dec2flt::lemire::compute_float;
|
||||||
|
|
||||||
fn compute_float32(q: i64, w: u64) -> (i32, u64) {
|
fn compute_float32(q: i64, w: u64) -> (i32, u64) {
|
||||||
let fp = compute_float::<f32>(q, w);
|
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) {
|
fn compute_float64(q: i64, w: u64) -> (i32, u64) {
|
||||||
let fp = compute_float::<f64>(q, w);
|
let fp = compute_float::<f64>(q, w);
|
||||||
(fp.e, fp.f)
|
(fp.p_biased, fp.m)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -27,6 +28,11 @@ fn compute_float_f32_rounding() {
|
||||||
// Let's check the lines to see if anything is different in table...
|
// 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, 167772190000000000), (151, 2));
|
||||||
assert_eq!(compute_float32(-10, 167772200000000000), (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]
|
#[test]
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#![allow(overflowing_literals)]
|
#![allow(overflowing_literals)]
|
||||||
|
|
||||||
|
mod decimal;
|
||||||
|
mod decimal_seq;
|
||||||
mod float;
|
mod float;
|
||||||
mod lemire;
|
mod lemire;
|
||||||
mod parse;
|
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::parse::parse_number;
|
||||||
use core::num::dec2flt::{dec2flt, pfe_invalid};
|
use core::num::dec2flt::{dec2flt, pfe_invalid};
|
||||||
|
|
||||||
fn new_number(e: i64, m: u64) -> Number {
|
fn new_dec(e: i64, m: u64) -> Decimal {
|
||||||
Number { exponent: e, mantissa: m, negative: false, many_digits: false }
|
Decimal { exponent: e, mantissa: m, negative: false, many_digits: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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)
|
parse_number(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn valid() {
|
fn valid() {
|
||||||
assert_eq!(parse_positive(b"123.456e789"), Some(new_number(786, 123456)));
|
assert_eq!(parse_positive(b"123.456e789"), Some(new_dec(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_dec(786, 123456)));
|
||||||
assert_eq!(parse_positive(b"123.456e-789"), Some(new_number(-792, 123456)));
|
assert_eq!(parse_positive(b"123.456e-789"), Some(new_dec(-792, 123456)));
|
||||||
assert_eq!(parse_positive(b".050"), Some(new_number(-3, 50)));
|
assert_eq!(parse_positive(b".050"), Some(new_dec(-3, 50)));
|
||||||
assert_eq!(parse_positive(b"999"), Some(new_number(0, 999)));
|
assert_eq!(parse_positive(b"999"), Some(new_dec(0, 999)));
|
||||||
assert_eq!(parse_positive(b"1.e300"), Some(new_number(300, 1)));
|
assert_eq!(parse_positive(b"1.e300"), Some(new_dec(300, 1)));
|
||||||
assert_eq!(parse_positive(b".1e300"), Some(new_number(299, 1)));
|
assert_eq!(parse_positive(b".1e300"), Some(new_dec(299, 1)));
|
||||||
assert_eq!(parse_positive(b"101e-33"), Some(new_number(-33, 101)));
|
assert_eq!(parse_positive(b"101e-33"), Some(new_dec(-33, 101)));
|
||||||
let zeros = "0".repeat(25);
|
let zeros = "0".repeat(25);
|
||||||
let s = format!("1.5e{zeros}");
|
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 {
|
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]
|
#[test]
|
||||||
fn issue31109() {
|
fn issue31109() {
|
||||||
// Regression test for #31109.
|
// Regression test for #31109.
|
||||||
|
|
|
@ -525,31 +525,24 @@ fn ptr_metadata() {
|
||||||
assert_eq!(metadata("foo"), 3_usize);
|
assert_eq!(metadata("foo"), 3_usize);
|
||||||
assert_eq!(metadata(&[4, 7][..]), 2_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]);
|
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);
|
assert_eq!(metadata(dst_struct), 3_usize);
|
||||||
unsafe {
|
unsafe {
|
||||||
let dst_tuple: &(bool, str) = std::mem::transmute(dst_tuple);
|
|
||||||
let dst_struct: &Pair<bool, str> = std::mem::transmute(dst_struct);
|
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!(&dst_struct.1, "foo");
|
||||||
assert_eq!(metadata(dst_tuple), 3_usize);
|
|
||||||
assert_eq!(metadata(dst_struct), 3_usize);
|
assert_eq!(metadata(dst_struct), 3_usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
let vtable_1: DynMetadata<dyn Debug> = metadata(&4_u16 as &dyn Debug);
|
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_2: DynMetadata<dyn Display> = metadata(&4_u16 as &dyn Display);
|
||||||
let vtable_3: DynMetadata<dyn Display> = metadata(&4_u32 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_4: DynMetadata<dyn Display> =
|
||||||
let vtable_5: DynMetadata<dyn Display> =
|
|
||||||
metadata(&Pair(true, 7_u32) as &Pair<bool, dyn Display>);
|
metadata(&Pair(true, 7_u32) as &Pair<bool, dyn Display>);
|
||||||
unsafe {
|
unsafe {
|
||||||
let address_1: *const () = std::mem::transmute(vtable_1);
|
let address_1: *const () = std::mem::transmute(vtable_1);
|
||||||
let address_2: *const () = std::mem::transmute(vtable_2);
|
let address_2: *const () = std::mem::transmute(vtable_2);
|
||||||
let address_3: *const () = std::mem::transmute(vtable_3);
|
let address_3: *const () = std::mem::transmute(vtable_3);
|
||||||
let address_4: *const () = std::mem::transmute(vtable_4);
|
let address_4: *const () = std::mem::transmute(vtable_4);
|
||||||
let address_5: *const () = std::mem::transmute(vtable_5);
|
|
||||||
// Different trait => different vtable pointer
|
// Different trait => different vtable pointer
|
||||||
assert_ne!(address_1, address_2);
|
assert_ne!(address_1, address_2);
|
||||||
// Different erased type => different vtable pointer
|
// Different erased type => different vtable pointer
|
||||||
|
@ -558,7 +551,6 @@ fn ptr_metadata() {
|
||||||
// This is *not guaranteed*, so we skip it in Miri.
|
// This is *not guaranteed*, so we skip it in Miri.
|
||||||
if !cfg!(miri) {
|
if !cfg!(miri) {
|
||||||
assert_eq!(address_3, address_4);
|
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::ops::{Range, RangeInclusive};
|
||||||
use core::slice;
|
use core::slice;
|
||||||
|
|
||||||
use rand::seq::IndexedRandom;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_position() {
|
fn test_position() {
|
||||||
let b = [1, 2, 3, 5, 5];
|
let b = [1, 2, 3, 5, 5];
|
||||||
|
@ -1810,6 +1808,7 @@ fn select_nth_unstable() {
|
||||||
use core::cmp::Ordering::{Equal, Greater, Less};
|
use core::cmp::Ordering::{Equal, Greater, Less};
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
use rand::seq::IndexedRandom;
|
||||||
|
|
||||||
let mut rng = crate::test_rng();
|
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 }
|
addr2line = { version = "0.24.0", optional = true, default-features = false }
|
||||||
|
|
||||||
[target.'cfg(not(all(windows, target_env = "msvc")))'.dependencies]
|
[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',
|
'rustc-dep-of-std',
|
||||||
], public = true }
|
], 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`].
|
/// 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`.
|
/// [`remove_dir_all`] will fail if [`remove_dir`] or [`remove_file`] fail on *any* constituent
|
||||||
/// As a result, the directory you are deleting must exist, meaning that this function is not idempotent.
|
/// paths, *including* the root `path`. Consequently,
|
||||||
/// Additionally, `remove_dir_all` will also fail if the `path` is not a directory.
|
///
|
||||||
|
/// - 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.
|
/// 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
|
/// output stream. Access is also synchronized via a lock and explicit control
|
||||||
/// over locking is available via the [`lock`] method.
|
/// 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.
|
/// Created by the [`io::stdout`] method.
|
||||||
///
|
///
|
||||||
/// ### Note: Windows Portability Considerations
|
/// ### Note: Windows Portability Considerations
|
||||||
|
@ -590,6 +595,7 @@ impl fmt::Debug for StdinLock<'_> {
|
||||||
/// standard library or via raw Windows API calls, will fail.
|
/// standard library or via raw Windows API calls, will fail.
|
||||||
///
|
///
|
||||||
/// [`lock`]: Stdout::lock
|
/// [`lock`]: Stdout::lock
|
||||||
|
/// [`flush`]: Write::flush
|
||||||
/// [`io::stdout`]: stdout
|
/// [`io::stdout`]: stdout
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub struct Stdout {
|
pub struct Stdout {
|
||||||
|
@ -604,6 +610,11 @@ pub struct Stdout {
|
||||||
/// This handle implements the [`Write`] trait, and is constructed via
|
/// This handle implements the [`Write`] trait, and is constructed via
|
||||||
/// the [`Stdout::lock`] method. See its documentation for more.
|
/// 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
|
/// ### Note: Windows Portability Considerations
|
||||||
///
|
///
|
||||||
/// When operating in a console, the Windows implementation of this stream does not support
|
/// 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
|
/// 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
|
/// `Write` will do nothing and silently succeed. All other I/O operations, via the
|
||||||
/// standard library or via raw Windows API calls, will fail.
|
/// standard library or via raw Windows API calls, will fail.
|
||||||
|
///
|
||||||
|
/// [`flush`]: Write::flush
|
||||||
#[must_use = "if unused stdout will immediately unlock"]
|
#[must_use = "if unused stdout will immediately unlock"]
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub struct StdoutLock<'a> {
|
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
|
/// is synchronized via a mutex. If you need more explicit control over
|
||||||
/// locking, see the [`Stdout::lock`] method.
|
/// 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
|
/// ### Note: Windows Portability Considerations
|
||||||
///
|
///
|
||||||
/// When operating in a console, the Windows implementation of this stream does not support
|
/// 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(())
|
/// 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]
|
#[must_use]
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
#[cfg_attr(not(test), rustc_diagnostic_item = "io_stdout")]
|
#[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)
|
//! - 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::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::ffi::{OsStr, OsString};
|
||||||
use crate::io::{self, const_error};
|
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))
|
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(
|
#[cfg(not(any(
|
||||||
target_os = "freebsd",
|
target_os = "freebsd",
|
||||||
|
target_os = "illumos",
|
||||||
all(target_os = "linux", target_env = "gnu"),
|
all(target_os = "linux", target_env = "gnu"),
|
||||||
all(target_os = "linux", target_env = "musl"),
|
all(target_os = "linux", target_env = "musl"),
|
||||||
target_os = "nto",
|
target_os = "nto",
|
||||||
|
@ -427,6 +428,7 @@ impl Command {
|
||||||
// directly.
|
// directly.
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
target_os = "freebsd",
|
target_os = "freebsd",
|
||||||
|
target_os = "illumos",
|
||||||
all(target_os = "linux", target_env = "gnu"),
|
all(target_os = "linux", target_env = "gnu"),
|
||||||
all(target_os = "linux", target_env = "musl"),
|
all(target_os = "linux", target_env = "musl"),
|
||||||
target_os = "nto",
|
target_os = "nto",
|
||||||
|
@ -584,6 +586,10 @@ impl Command {
|
||||||
fn get_posix_spawn_addchdir() -> Option<PosixSpawnAddChdirFn> {
|
fn get_posix_spawn_addchdir() -> Option<PosixSpawnAddChdirFn> {
|
||||||
use crate::sys::weak::weak;
|
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! {
|
weak! {
|
||||||
fn posix_spawn_file_actions_addchdir_np(
|
fn posix_spawn_file_actions_addchdir_np(
|
||||||
*mut libc::posix_spawn_file_actions_t,
|
*mut libc::posix_spawn_file_actions_t,
|
||||||
|
@ -591,7 +597,16 @@ impl Command {
|
||||||
) -> libc::c_int
|
) -> 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
|
/// 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-channel", "rust.channel", "the name of the release channel to build")
|
||||||
v(
|
v(
|
||||||
"release-description",
|
"release-description",
|
||||||
"rust.description",
|
"build.description",
|
||||||
"optional descriptive string for version output",
|
"optional descriptive string for version output",
|
||||||
)
|
)
|
||||||
v("dist-compression-formats", None, "List of compression formats to use")
|
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;
|
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,
|
SourceType::InTree,
|
||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
run_cargo_test(
|
run_cargo_test(cargo, &[], &[], "linkchecker self tests", bootstrap_host, builder);
|
||||||
cargo,
|
|
||||||
&[],
|
|
||||||
&[],
|
|
||||||
"linkchecker",
|
|
||||||
"linkchecker self tests",
|
|
||||||
bootstrap_host,
|
|
||||||
builder,
|
|
||||||
);
|
|
||||||
|
|
||||||
if builder.doc_tests == DocTests::No {
|
if builder.doc_tests == DocTests::No {
|
||||||
return;
|
return;
|
||||||
|
@ -337,7 +329,7 @@ impl Step for Cargo {
|
||||||
);
|
);
|
||||||
|
|
||||||
// NOTE: can't use `run_cargo_test` because we need to overwrite `PATH`
|
// 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
|
// Don't run cross-compile tests, we may not have cross-compiled libstd libs
|
||||||
// available.
|
// available.
|
||||||
|
@ -423,7 +415,7 @@ impl Step for RustAnalyzer {
|
||||||
cargo.env("SKIP_SLOW_TESTS", "1");
|
cargo.env("SKIP_SLOW_TESTS", "1");
|
||||||
|
|
||||||
cargo.add_rustc_lib_path(builder);
|
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);
|
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
|
// 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`.
|
// 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
|
// miri tests need to know about the stage sysroot
|
||||||
cargo.env("MIRI_SYSROOT", &miri_sysroot);
|
cargo.env("MIRI_SYSROOT", &miri_sysroot);
|
||||||
|
@ -736,7 +728,7 @@ impl Step for CompiletestTest {
|
||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
cargo.allow_features("test");
|
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.env("HOST_LIBS", host_libs);
|
||||||
|
|
||||||
cargo.add_rustc_lib_path(builder);
|
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);
|
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");
|
cargo.allow_features("test");
|
||||||
run_cargo_test(
|
run_cargo_test(cargo, &[], &[], "run-make-support self test", host, builder);
|
||||||
cargo,
|
|
||||||
&[],
|
|
||||||
&[],
|
|
||||||
"run-make-support",
|
|
||||||
"run-make-support self test",
|
|
||||||
host,
|
|
||||||
builder,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1322,7 +1306,7 @@ impl Step for CrateBuildHelper {
|
||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
cargo.allow_features("test");
|
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,
|
cargo: builder::Cargo,
|
||||||
libtest_args: &[&str],
|
libtest_args: &[&str],
|
||||||
crates: &[String],
|
crates: &[String],
|
||||||
primary_crate: &str,
|
|
||||||
description: impl Into<Option<&'a str>>,
|
description: impl Into<Option<&'a str>>,
|
||||||
target: TargetSelection,
|
target: TargetSelection,
|
||||||
builder: &Builder<'_>,
|
builder: &Builder<'_>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let compiler = cargo.compiler();
|
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 _time = helpers::timeit(builder);
|
||||||
let _group = description.into().and_then(|what| {
|
let _group = description.into().and_then(|what| {
|
||||||
builder.msg_sysroot_tool(Kind::Test, compiler.stage, what, compiler.host, target)
|
builder.msg_sysroot_tool(Kind::Test, compiler.stage, what, compiler.host, target)
|
||||||
|
@ -2537,7 +2520,6 @@ fn prepare_cargo_test(
|
||||||
cargo: builder::Cargo,
|
cargo: builder::Cargo,
|
||||||
libtest_args: &[&str],
|
libtest_args: &[&str],
|
||||||
crates: &[String],
|
crates: &[String],
|
||||||
primary_crate: &str,
|
|
||||||
target: TargetSelection,
|
target: TargetSelection,
|
||||||
builder: &Builder<'_>,
|
builder: &Builder<'_>,
|
||||||
) -> BootstrapCommand {
|
) -> BootstrapCommand {
|
||||||
|
@ -2567,13 +2549,6 @@ fn prepare_cargo_test(
|
||||||
cargo.arg("--doc");
|
cargo.arg("--doc");
|
||||||
}
|
}
|
||||||
DocTests::No => {
|
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"]);
|
cargo.args(["--bins", "--examples", "--tests", "--benches"]);
|
||||||
}
|
}
|
||||||
DocTests::Yes => {}
|
DocTests::Yes => {}
|
||||||
|
@ -2748,15 +2723,15 @@ impl Step for Crate {
|
||||||
_ => panic!("can only test libraries"),
|
_ => panic!("can only test libraries"),
|
||||||
};
|
};
|
||||||
|
|
||||||
run_cargo_test(
|
let mut crates = self.crates.clone();
|
||||||
cargo,
|
// The core crate can't directly be tested. We could silently
|
||||||
&[],
|
// ignore it, but adding it's own test crate is less confusing
|
||||||
&self.crates,
|
// for users. We still keep core itself for doctests.
|
||||||
&self.crates[0],
|
if crates.iter().any(|crate_| crate_ == "core") {
|
||||||
&*crate_description(&self.crates),
|
crates.push("coretests".to_owned());
|
||||||
target,
|
}
|
||||||
builder,
|
|
||||||
);
|
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));
|
dylib_path.insert(0, PathBuf::from(&*libdir));
|
||||||
cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
|
cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
|
||||||
|
|
||||||
run_cargo_test(
|
run_cargo_test(cargo, &[], &["rustdoc:0.0.0".to_string()], "rustdoc", target, builder);
|
||||||
cargo,
|
|
||||||
&[],
|
|
||||||
&["rustdoc:0.0.0".to_string()],
|
|
||||||
"rustdoc",
|
|
||||||
"rustdoc",
|
|
||||||
target,
|
|
||||||
builder,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2914,7 +2881,6 @@ impl Step for CrateRustdocJsonTypes {
|
||||||
libtest_args,
|
libtest_args,
|
||||||
&["rustdoc-json-types".to_string()],
|
&["rustdoc-json-types".to_string()],
|
||||||
"rustdoc-json-types",
|
"rustdoc-json-types",
|
||||||
"rustdoc-json-types",
|
|
||||||
target,
|
target,
|
||||||
builder,
|
builder,
|
||||||
);
|
);
|
||||||
|
@ -3094,7 +3060,7 @@ impl Step for Bootstrap {
|
||||||
|
|
||||||
// bootstrap tests are racy on directory creation so just run them one at a time.
|
// 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.
|
// 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<'_> {
|
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
|
||||||
|
@ -3219,7 +3185,7 @@ impl Step for RustInstaller {
|
||||||
bootstrap_host,
|
bootstrap_host,
|
||||||
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.
|
// 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
|
// 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.
|
// Run the actual parse tests.
|
||||||
let mut cargo_run = tool::prepare_tool_cargo(
|
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_VERSION", builder.rust_version());
|
||||||
cargo.env("CFG_RELEASE_NUM", &builder.version);
|
cargo.env("CFG_RELEASE_NUM", &builder.version);
|
||||||
cargo.env("DOC_RUST_LANG_ORG_CHANNEL", builder.doc_rust_lang_org_channel());
|
cargo.env("DOC_RUST_LANG_ORG_CHANNEL", builder.doc_rust_lang_org_channel());
|
||||||
|
|
||||||
if let Some(ref ver_date) = builder.rust_info().commit_date() {
|
if let Some(ref ver_date) = builder.rust_info().commit_date() {
|
||||||
cargo.env("CFG_VER_DATE", ver_date);
|
cargo.env("CFG_VER_DATE", ver_date);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref ver_hash) = builder.rust_info().sha() {
|
if let Some(ref ver_hash) = builder.rust_info().sha() {
|
||||||
cargo.env("CFG_VER_HASH", ver_hash);
|
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);
|
let info = GitInfo::new(builder.config.omit_git_hash, &dir);
|
||||||
if let Some(sha) = info.sha() {
|
if let Some(sha) = info.sha() {
|
||||||
cargo.env("CFG_COMMIT_HASH", sha);
|
cargo.env("CFG_COMMIT_HASH", sha);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sha_short) = info.sha_short() {
|
if let Some(sha_short) = info.sha_short() {
|
||||||
cargo.env("CFG_SHORT_COMMIT_HASH", sha_short);
|
cargo.env("CFG_SHORT_COMMIT_HASH", sha_short);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(date) = info.commit_date() {
|
if let Some(date) = info.commit_date() {
|
||||||
cargo.env("CFG_COMMIT_DATE", date);
|
cargo.env("CFG_COMMIT_DATE", date);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !features.is_empty() {
|
if !features.is_empty() {
|
||||||
cargo.arg("--features").arg(features.join(", "));
|
cargo.arg("--features").arg(features.join(", "));
|
||||||
}
|
}
|
||||||
|
|
|
@ -894,6 +894,7 @@ define_config! {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Build {
|
struct Build {
|
||||||
build: Option<String> = "build",
|
build: Option<String> = "build",
|
||||||
|
description: Option<String> = "description",
|
||||||
host: Option<Vec<String>> = "host",
|
host: Option<Vec<String>> = "host",
|
||||||
target: Option<Vec<String>> = "target",
|
target: Option<Vec<String>> = "target",
|
||||||
build_dir: Option<String> = "build-dir",
|
build_dir: Option<String> = "build-dir",
|
||||||
|
@ -1176,6 +1177,7 @@ define_config! {
|
||||||
incremental: Option<bool> = "incremental",
|
incremental: Option<bool> = "incremental",
|
||||||
default_linker: Option<String> = "default-linker",
|
default_linker: Option<String> = "default-linker",
|
||||||
channel: Option<String> = "channel",
|
channel: Option<String> = "channel",
|
||||||
|
// FIXME: Remove this field at Q2 2025, it has been replaced by build.description
|
||||||
description: Option<String> = "description",
|
description: Option<String> = "description",
|
||||||
musl_root: Option<String> = "musl-root",
|
musl_root: Option<String> = "musl-root",
|
||||||
rpath: Option<bool> = "rpath",
|
rpath: Option<bool> = "rpath",
|
||||||
|
@ -1583,6 +1585,7 @@ impl Config {
|
||||||
config.change_id = toml.change_id.inner;
|
config.change_id = toml.change_id.inner;
|
||||||
|
|
||||||
let Build {
|
let Build {
|
||||||
|
mut description,
|
||||||
build,
|
build,
|
||||||
host,
|
host,
|
||||||
target,
|
target,
|
||||||
|
@ -1831,7 +1834,7 @@ impl Config {
|
||||||
randomize_layout,
|
randomize_layout,
|
||||||
default_linker,
|
default_linker,
|
||||||
channel: _, // already handled above
|
channel: _, // already handled above
|
||||||
description,
|
description: rust_description,
|
||||||
musl_root,
|
musl_root,
|
||||||
rpath,
|
rpath,
|
||||||
verbose_tests,
|
verbose_tests,
|
||||||
|
@ -1924,7 +1927,12 @@ impl Config {
|
||||||
set(&mut config.jemalloc, jemalloc);
|
set(&mut config.jemalloc, jemalloc);
|
||||||
set(&mut config.test_compare_mode, test_compare_mode);
|
set(&mut config.test_compare_mode, test_compare_mode);
|
||||||
set(&mut config.backtrace, backtrace);
|
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.rust_dist_src, dist_src);
|
||||||
set(&mut config.verbose_tests, verbose_tests);
|
set(&mut config.verbose_tests, verbose_tests);
|
||||||
// in the case "false" is set explicitly, do not overwrite the command line args
|
// 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.reproducible_artifacts = flags.reproducible_artifact;
|
||||||
|
config.description = description;
|
||||||
|
|
||||||
// We need to override `rust.channel` if it's manually specified when using the CI rustc.
|
// 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,
|
// 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>,
|
source: Option<String>,
|
||||||
manifest_path: String,
|
manifest_path: String,
|
||||||
dependencies: Vec<Dependency>,
|
dependencies: Vec<Dependency>,
|
||||||
targets: Vec<Target>,
|
|
||||||
features: BTreeMap<String, Vec<String>>,
|
features: BTreeMap<String, Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,11 +39,6 @@ struct Dependency {
|
||||||
source: Option<String>,
|
source: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Target {
|
|
||||||
kind: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collects and stores package metadata of each workspace members into `build`,
|
/// Collects and stores package metadata of each workspace members into `build`,
|
||||||
/// by executing `cargo metadata` commands.
|
/// by executing `cargo metadata` commands.
|
||||||
pub fn build(build: &mut Build) {
|
pub fn build(build: &mut Build) {
|
||||||
|
@ -59,12 +53,10 @@ pub fn build(build: &mut Build) {
|
||||||
.filter(|dep| dep.source.is_none())
|
.filter(|dep| dep.source.is_none())
|
||||||
.map(|dep| dep.name)
|
.map(|dep| dep.name)
|
||||||
.collect();
|
.collect();
|
||||||
let has_lib = package.targets.iter().any(|t| t.kind.iter().any(|k| k == "lib"));
|
|
||||||
let krate = Crate {
|
let krate = Crate {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
deps,
|
deps,
|
||||||
path,
|
path,
|
||||||
has_lib,
|
|
||||||
features: package.features.keys().cloned().collect(),
|
features: package.features.keys().cloned().collect(),
|
||||||
};
|
};
|
||||||
let relative_path = krate.local_path(build);
|
let relative_path = krate.local_path(build);
|
||||||
|
|
|
@ -185,7 +185,6 @@ struct Crate {
|
||||||
name: String,
|
name: String,
|
||||||
deps: HashSet<String>,
|
deps: HashSet<String>,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
has_lib: bool,
|
|
||||||
features: Vec<String>,
|
features: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -365,4 +365,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
|
||||||
severity: ChangeSeverity::Info,
|
severity: ChangeSeverity::Info,
|
||||||
summary: "`rust.channel` now supports \"auto-detect\" to load the channel from `src/ci/channel`",
|
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.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.18"
|
version = "0.6.18"
|
||||||
|
@ -38,7 +44,7 @@ version = "1.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -49,7 +55,7 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"windows-sys",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -58,6 +64,12 @@ version = "1.0.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "build_helper"
|
name = "build_helper"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -66,6 +78,27 @@ dependencies = [
|
||||||
"serde_derive",
|
"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]]
|
[[package]]
|
||||||
name = "citool"
|
name = "citool"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -73,10 +106,12 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"build_helper",
|
"build_helper",
|
||||||
"clap",
|
"clap",
|
||||||
|
"csv",
|
||||||
"insta",
|
"insta",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
|
"ureq",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -134,7 +169,95 @@ dependencies = [
|
||||||
"encode_unicode",
|
"encode_unicode",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"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]]
|
[[package]]
|
||||||
|
@ -149,6 +272,42 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
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]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.2"
|
version = "0.15.2"
|
||||||
|
@ -161,6 +320,162 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
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]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.7.1"
|
version = "2.7.1"
|
||||||
|
@ -208,18 +523,57 @@ version = "0.5.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
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]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
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]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.3"
|
version = "1.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.9"
|
version = "1.1.9"
|
||||||
|
@ -240,6 +594,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.93"
|
version = "1.0.93"
|
||||||
|
@ -258,6 +618,61 @@ dependencies = [
|
||||||
"proc-macro2",
|
"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]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.19"
|
version = "1.0.19"
|
||||||
|
@ -309,18 +724,42 @@ dependencies = [
|
||||||
"unsafe-libyaml",
|
"unsafe-libyaml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "similar"
|
name = "similar"
|
||||||
version = "2.7.0"
|
version = "2.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
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]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.98"
|
version = "2.0.98"
|
||||||
|
@ -332,6 +771,58 @@ dependencies = [
|
||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.16"
|
version = "1.0.16"
|
||||||
|
@ -344,12 +835,110 @@ version = "0.2.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
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]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
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]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
|
@ -422,3 +1011,88 @@ name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
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]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
|
csv = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
ureq = { version = "3", features = ["json"] }
|
||||||
|
|
||||||
build_helper = { path = "../../build_helper" }
|
build_helper = { path = "../../build_helper" }
|
||||||
|
|
||||||
|
@ -19,3 +21,7 @@ insta = "1"
|
||||||
# If this is omitted, cargo will look for a workspace elsewhere.
|
# If this is omitted, cargo will look for a workspace elsewhere.
|
||||||
# We want to avoid this, since citool is independent of the other crates.
|
# We want to avoid this, since citool is independent of the other crates.
|
||||||
[workspace]
|
[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 metrics;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -8,7 +11,10 @@ use anyhow::Context;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use serde_yaml::Value;
|
use serde_yaml::Value;
|
||||||
|
|
||||||
|
use crate::cpu_usage::load_cpu_usage;
|
||||||
|
use crate::datadog::upload_datadog_metric;
|
||||||
use crate::metrics::postprocess_metrics;
|
use crate::metrics::postprocess_metrics;
|
||||||
|
use crate::utils::load_env_var;
|
||||||
|
|
||||||
const CI_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/..");
|
const CI_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/..");
|
||||||
const DOCKER_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../docker");
|
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> {
|
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)?;
|
let mut db: Value = serde_yaml::from_str(&db)?;
|
||||||
|
|
||||||
// We need to expand merge keys (<<), because serde_yaml can't deal with them
|
// 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> {
|
fn load_github_ctx() -> anyhow::Result<GitHubContext> {
|
||||||
let event_name = load_env_var("GITHUB_EVENT_NAME")?;
|
let event_name = load_env_var("GITHUB_EVENT_NAME")?;
|
||||||
let commit_message =
|
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(()) }
|
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)]
|
#[derive(clap::Parser)]
|
||||||
enum Args {
|
enum Args {
|
||||||
/// Calculate a list of jobs that should be executed on CI.
|
/// 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.
|
/// Usually, this will be GITHUB_STEP_SUMMARY on CI.
|
||||||
summary_path: PathBuf,
|
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)]
|
#[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 jobs_path = jobs_file.as_deref().unwrap_or(default_jobs_file);
|
||||||
let gh_ctx = load_github_ctx()
|
let gh_ctx = load_github_ctx()
|
||||||
.context("Cannot load environment variables from GitHub Actions")?;
|
.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")?
|
.context("Cannot read channel file")?
|
||||||
.trim()
|
.trim()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
@ -379,7 +398,10 @@ fn main() -> anyhow::Result<()> {
|
||||||
.context("Failed to calculate job matrix")?;
|
.context("Failed to calculate job matrix")?;
|
||||||
}
|
}
|
||||||
Args::RunJobLocally { job_type, name } => {
|
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 } => {
|
Args::PostprocessMetrics { metrics_path, summary_path } => {
|
||||||
postprocess_metrics(&metrics_path, &summary_path)?;
|
postprocess_metrics(&metrics_path, &summary_path)?;
|
||||||
|
@ -388,8 +410,3 @@ fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
Ok(())
|
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
|
### General purpose test suite
|
||||||
|
|
||||||
[`run-make`](#run-make-tests) are general purpose tests using Rust programs (or
|
[`run-make`](#run-make-tests) are general purpose tests using Rust programs.
|
||||||
Makefiles (legacy)).
|
|
||||||
|
|
||||||
### Rustdoc test suites
|
### Rustdoc test suites
|
||||||
|
|
||||||
|
@ -396,14 +395,6 @@ your test, causing separate files to be generated for 32bit and 64bit systems.
|
||||||
|
|
||||||
### `run-make` tests
|
### `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*,
|
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
|
which are small programs (`rmake.rs`) allowing arbitrary Rust code such as
|
||||||
`rustc` invocations, and is supported by a [`run_make_support`] library. Using
|
`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
|
supported in `rmake.rs`, like in UI tests. However, revisions or building
|
||||||
auxiliary via directives are not currently supported.
|
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
|
#### Quickly check if `rmake.rs` tests can be compiled
|
||||||
|
|
||||||
You can quickly check if `rmake.rs` tests can be compiled without having to
|
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
|
[`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
|
[`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.
|
FIXME(jieyouxu) completely revise this chapter.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
Directives are special comments that tell compiletest how to build and interpret
|
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).
|
||||||
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).
|
|
||||||
|
|
||||||
They are normally put after the short comment that explains the point of this
|
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.
|
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
|
[`aarch64-gnu-debug`]), which only runs a
|
||||||
subset of `run-make` tests. Other tests with this directive will not
|
subset of `run-make` tests. Other tests with this directive will not
|
||||||
run at all, which is usually not what you want.
|
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
|
See also [Debuginfo tests](compiletest.md#debuginfo-tests) for directives for
|
||||||
ignoring debuggers.
|
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,
|
directives that specify specific compiler flags, or which rely on other crates,
|
||||||
and they may not run the same without those options.
|
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
|
## Running tests on a remote machine
|
||||||
|
|
||||||
Tests may be run on a remote machine (e.g. to test builds for a different
|
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/).
|
repository](https://github.com/rust-lang/rustc_codegen_gcc/).
|
||||||
|
|
||||||
[`tests/ui`]: https://github.com/rust-lang/rust/tree/master/tests/ui
|
[`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 {
|
macro_rules! impl_float {
|
||||||
($($fty:ty, $ity:ty, $bits:literal);+) => {
|
($($fty:ty, $ity:ty);+) => {
|
||||||
$(
|
$(
|
||||||
impl Float for $fty {
|
impl Float for $fty {
|
||||||
type Int = $ity;
|
type Int = $ity;
|
||||||
type SInt = <Self::Int as Int>::Signed;
|
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_BITS: u32 = Self::MANTISSA_DIGITS - 1;
|
||||||
const MAN_MASK: Self::Int = (Self::Int::ONE << Self::MAN_BITS) - Self::Int::ONE;
|
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);
|
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.
|
/// 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.
|
/// returns a struct containing various parts of the directive.
|
||||||
fn line_directive<'line>(
|
fn line_directive<'line>(
|
||||||
line_number: usize,
|
line_number: usize,
|
||||||
comment: &str,
|
|
||||||
original_line: &'line str,
|
original_line: &'line str,
|
||||||
) -> Option<DirectiveLine<'line>> {
|
) -> Option<DirectiveLine<'line>> {
|
||||||
// Ignore lines that don't start with the comment prefix.
|
// 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 revision;
|
||||||
let raw_directive;
|
let raw_directive;
|
||||||
|
@ -722,7 +722,7 @@ fn line_directive<'line>(
|
||||||
// A comment like `//@[foo]` only applies to revision `foo`.
|
// A comment like `//@[foo]` only applies to revision `foo`.
|
||||||
let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else {
|
let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else {
|
||||||
panic!(
|
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 }
|
CheckDirectiveResult { is_known_directive: is_known(&directive_name), trailing_directive }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@";
|
||||||
|
|
||||||
fn iter_header(
|
fn iter_header(
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
_suite: &str,
|
_suite: &str,
|
||||||
|
@ -849,8 +851,7 @@ fn iter_header(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coverage tests in coverage-run mode always have these extra directives, without needing to
|
// 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
|
// specify them manually in every test file.
|
||||||
// from the old `tests/run-make/coverage-reports/Makefile`, which no longer exists.)
|
|
||||||
//
|
//
|
||||||
// FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later.
|
// FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later.
|
||||||
if mode == Mode::CoverageRun {
|
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 rdr = BufReader::with_capacity(1024, rdr);
|
||||||
let mut ln = String::new();
|
let mut ln = String::new();
|
||||||
let mut line_number = 0;
|
let mut line_number = 0;
|
||||||
|
@ -882,7 +880,7 @@ fn iter_header(
|
||||||
}
|
}
|
||||||
let ln = ln.trim();
|
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;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -239,11 +239,6 @@ fn check_ignore(config: &Config, contents: &str) -> bool {
|
||||||
d.ignore
|
d.ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_makefile(config: &Config, contents: &str) -> EarlyProps {
|
|
||||||
let bytes = contents.as_bytes();
|
|
||||||
EarlyProps::from_reader(config, Path::new("Makefile"), bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_fail() {
|
fn should_fail() {
|
||||||
let config: Config = cfg().build();
|
let config: Config = cfg().build();
|
||||||
|
@ -261,10 +256,6 @@ fn revisions() {
|
||||||
let config: Config = cfg().build();
|
let config: Config = cfg().build();
|
||||||
|
|
||||||
assert_eq!(parse_rs(&config, "//@ revisions: a b c").revisions, vec!["a", "b", "c"],);
|
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]
|
#[test]
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub mod util;
|
||||||
|
|
||||||
use core::panic;
|
use core::panic;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::OsString;
|
||||||
use std::io::{self, ErrorKind};
|
use std::io::{self, ErrorKind};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
@ -274,12 +274,8 @@ pub fn parse_config(args: Vec<String>) -> Config {
|
||||||
let path = Path::new(f);
|
let path = Path::new(f);
|
||||||
let mut iter = path.iter().skip(1);
|
let mut iter = path.iter().skip(1);
|
||||||
|
|
||||||
// We skip the test folder and check if the user passed `rmake.rs` or `Makefile`.
|
// We skip the test folder and check if the user passed `rmake.rs`.
|
||||||
if iter
|
if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
|
||||||
.next()
|
|
||||||
.is_some_and(|s| s == OsStr::new("rmake.rs") || s == OsStr::new("Makefile"))
|
|
||||||
&& iter.next().is_none()
|
|
||||||
{
|
|
||||||
path.parent().unwrap().to_str().unwrap().to_string()
|
path.parent().unwrap().to_str().unwrap().to_string()
|
||||||
} else {
|
} else {
|
||||||
f.to_string()
|
f.to_string()
|
||||||
|
@ -783,16 +779,9 @@ fn collect_tests_from_dir(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// For run-make tests, a "test file" is actually a directory that contains
|
// For run-make tests, a "test file" is actually a directory that contains an `rmake.rs`.
|
||||||
// an `rmake.rs` or `Makefile`"
|
|
||||||
if cx.config.mode == Mode::RunMake {
|
if cx.config.mode == Mode::RunMake {
|
||||||
if dir.join("Makefile").exists() && dir.join("rmake.rs").exists() {
|
if 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() {
|
|
||||||
let paths = TestPaths {
|
let paths = TestPaths {
|
||||||
file: dir.to_path_buf(),
|
file: dir.to_path_buf(),
|
||||||
relative_dir: relative_dir_path.parent().unwrap().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))
|
!invalid_prefixes.iter().any(|p| file_name.starts_with(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For a single test file, creates one or more test structures (one per revision)
|
/// For a single test file, creates one or more test structures (one per revision) that can be
|
||||||
/// that can be handed over to libtest to run, possibly in parallel.
|
/// handed over to libtest to run, possibly in parallel.
|
||||||
fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
|
fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
|
||||||
// For run-make tests, each "test file" is actually a _directory_ containing
|
// For run-make tests, each "test file" is actually a _directory_ containing an `rmake.rs`. But
|
||||||
// an `rmake.rs` or `Makefile`. But for the purposes of directive parsing,
|
// for the purposes of directive parsing, we want to look at that recipe file, not the directory
|
||||||
// we want to look at that recipe file, not the directory itself.
|
// itself.
|
||||||
let test_path = if cx.config.mode == Mode::RunMake {
|
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")
|
testpaths.file.join("rmake.rs")
|
||||||
} else {
|
|
||||||
// Parse directives in the Makefile.
|
|
||||||
testpaths.file.join("Makefile")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
PathBuf::from(&testpaths.file)
|
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());
|
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");
|
normalize_path(&rust_src_dir.join("library"), "$SRC_DIR_REAL");
|
||||||
|
|
||||||
// eg. /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui
|
// eg.
|
||||||
normalize_path(&self.config.build_test_suite_root, "$TEST_BUILD_DIR");
|
// /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
|
// eg. /home/user/rust/build
|
||||||
normalize_path(&self.config.build_root, "$BUILD_DIR");
|
normalize_path(&self.config.build_root, "$BUILD_DIR");
|
||||||
|
|
||||||
|
@ -2434,6 +2435,18 @@ impl<'test> TestCx<'test> {
|
||||||
.into_owned();
|
.into_owned();
|
||||||
|
|
||||||
normalized = Self::normalize_platform_differences(&normalized);
|
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
|
normalized = normalized.replace("\t", "\\t"); // makes tabs visible
|
||||||
|
|
||||||
// Remove test annotations like `//~ ERROR text` from the output,
|
// Remove test annotations like `//~ ERROR text` from the output,
|
||||||
|
|
|
@ -9,168 +9,6 @@ use crate::util::{copy_dir_all, dylib_env_var};
|
||||||
|
|
||||||
impl TestCx<'_> {
|
impl TestCx<'_> {
|
||||||
pub(super) fn run_rmake_test(&self) {
|
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
|
// 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
|
// (`rmake.rs`) to run the actual tests. The support library is already built as a tool rust
|
||||||
// library and is available under
|
// 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
|
// 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
|
// a currently running executable because the recipe executable is not under the
|
||||||
// `rmake_out/` directory.
|
// `rmake_out/` directory.
|
||||||
//
|
|
||||||
// This setup intentionally diverges from legacy Makefile run-make tests.
|
|
||||||
let base_dir = self.output_base_dir();
|
let base_dir = self.output_base_dir();
|
||||||
ignore_not_found(|| recursive_remove(&base_dir)).unwrap();
|
ignore_not_found(|| recursive_remove(&base_dir)).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,8 @@
|
||||||
//@revisions: stack tree
|
//@revisions: stack tree
|
||||||
//@[tree]compile-flags: -Zmiri-tree-borrows
|
//@[tree]compile-flags: -Zmiri-tree-borrows
|
||||||
#![feature(unsized_tuple_coercion)]
|
|
||||||
#![feature(unsized_fn_params)]
|
#![feature(unsized_fn_params)]
|
||||||
#![feature(custom_mir, core_intrinsics)]
|
#![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() {
|
fn unsized_params() {
|
||||||
pub fn f0(_f: dyn FnOnce()) {}
|
pub fn f0(_f: dyn FnOnce()) {}
|
||||||
pub fn f1(_s: str) {}
|
pub fn f1(_s: str) {}
|
||||||
|
@ -56,7 +42,6 @@ fn unsized_field_projection() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
unsized_tuple();
|
|
||||||
unsized_params();
|
unsized_params();
|
||||||
unsized_field_projection();
|
unsized_field_projection();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,23 +8,6 @@ use crate::targets::is_msvc;
|
||||||
/// Construct the static library name based on the target.
|
/// Construct the static library name based on the target.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn static_lib_name(name: &str) -> String {
|
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");
|
assert!(!name.contains(char::is_whitespace), "static library name cannot contain whitespace");
|
||||||
|
|
||||||
if is_msvc() { format!("{name}.lib") } else { format!("lib{name}.a") }
|
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.
|
/// Specify `-o` or `-Fe`/`-Fo` depending on platform/compiler.
|
||||||
pub fn out_exe(&mut self, name: &str) -> &mut Self {
|
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);
|
let mut path = std::path::PathBuf::from(name);
|
||||||
|
|
||||||
if is_msvc() {
|
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