mirror of
https://github.com/rust-lang/rust.git
synced 2025-02-16 17:03:35 +00:00
Auto merge of #118134 - Nilstrieb:rollup-kyo1l6e, r=Nilstrieb
Rollup of 6 pull requests Successful merges: - #116085 (rustdoc-search: add support for traits and associated types) - #117522 (Remove `--check-cfg` checking of command line `--cfg` args) - #118029 (Expand Miri's BorTag GC to a Provenance GC) - #118035 (Fix early param lifetimes in generic_const_exprs) - #118083 (Remove i686-apple-darwin cross-testing) - #118091 (Remove now deprecated target x86_64-sun-solaris.) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
0ff8610964
@ -35,7 +35,7 @@ use crate::session_diagnostics::{
|
||||
LifetimeReturnCategoryErr, RequireStaticErr, VarHereDenote,
|
||||
};
|
||||
|
||||
use super::{OutlivesSuggestionBuilder, RegionName};
|
||||
use super::{OutlivesSuggestionBuilder, RegionName, RegionNameSource};
|
||||
use crate::region_infer::{BlameConstraint, ExtraConstraintInfo};
|
||||
use crate::{
|
||||
nll::ConstraintDescription,
|
||||
@ -763,7 +763,14 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
|
||||
let err = LifetimeOutliveErr { span: *span };
|
||||
let mut diag = self.infcx.tcx.sess.create_err(err);
|
||||
|
||||
let fr_name = self.give_region_a_name(*fr).unwrap();
|
||||
// In certain scenarios, such as the one described in issue #118021,
|
||||
// we might encounter a lifetime that cannot be named.
|
||||
// These situations are bound to result in errors.
|
||||
// To prevent an immediate ICE, we opt to create a dummy name instead.
|
||||
let fr_name = self.give_region_a_name(*fr).unwrap_or(RegionName {
|
||||
name: kw::UnderscoreLifetime,
|
||||
source: RegionNameSource::Static,
|
||||
});
|
||||
fr_name.highlight_region_name(&mut diag);
|
||||
let outlived_fr_name = self.give_region_a_name(*outlived_fr).unwrap();
|
||||
outlived_fr_name.highlight_region_name(&mut diag);
|
||||
|
@ -107,6 +107,14 @@ impl<K: Hash + Eq, V> interpret::AllocMap<K, V> for FxIndexMap<K, V> {
|
||||
FxIndexMap::contains_key(self, k)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn contains_key_ref<Q: ?Sized + Hash + Eq>(&self, k: &Q) -> bool
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
{
|
||||
FxIndexMap::contains_key(self, k)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn insert(&mut self, k: K, v: V) -> Option<V> {
|
||||
FxIndexMap::insert(self, k, v)
|
||||
|
@ -49,6 +49,14 @@ pub trait AllocMap<K: Hash + Eq, V> {
|
||||
where
|
||||
K: Borrow<Q>;
|
||||
|
||||
/// Callers should prefer [`AllocMap::contains_key`] when it is possible to call because it may
|
||||
/// be more efficient. This function exists for callers that only have a shared reference
|
||||
/// (which might make it slightly less efficient than `contains_key`, e.g. if
|
||||
/// the data is stored inside a `RefCell`).
|
||||
fn contains_key_ref<Q: ?Sized + Hash + Eq>(&self, k: &Q) -> bool
|
||||
where
|
||||
K: Borrow<Q>;
|
||||
|
||||
/// Inserts a new entry into the map.
|
||||
fn insert(&mut self, k: K, v: V) -> Option<V>;
|
||||
|
||||
|
@ -692,6 +692,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
Ok((&mut alloc.extra, machine))
|
||||
}
|
||||
|
||||
/// Check whether an allocation is live. This is faster than calling
|
||||
/// [`InterpCx::get_alloc_info`] if all you need to check is whether the kind is
|
||||
/// [`AllocKind::Dead`] because it doesn't have to look up the type and layout of statics.
|
||||
pub fn is_alloc_live(&self, id: AllocId) -> bool {
|
||||
self.tcx.try_get_global_alloc(id).is_some()
|
||||
|| self.memory.alloc_map.contains_key_ref(&id)
|
||||
|| self.memory.extra_fn_ptr_map.contains_key(&id)
|
||||
}
|
||||
|
||||
/// Obtain the size and alignment of an allocation, even if that allocation has
|
||||
/// been deallocated.
|
||||
pub fn get_alloc_info(&self, id: AllocId) -> (Size, Align, AllocKind) {
|
||||
|
@ -128,12 +128,6 @@ lint_builtin_type_alias_generic_bounds = bounds on generic parameters are not en
|
||||
lint_builtin_type_alias_where_clause = where clauses are not enforced in type aliases
|
||||
.suggestion = the clause will not be checked when the type alias is used, and should be removed
|
||||
|
||||
lint_builtin_unexpected_cli_config_name = unexpected `{$name}` as condition name
|
||||
.help = was set with `--cfg` but isn't in the `--check-cfg` expected names
|
||||
|
||||
lint_builtin_unexpected_cli_config_value = unexpected condition value `{$value}` for condition name `{$name}`
|
||||
.help = was set with `--cfg` but isn't in the `--check-cfg` expected values
|
||||
|
||||
lint_builtin_unpermitted_type_init_label = this code causes undefined behavior when executed
|
||||
lint_builtin_unpermitted_type_init_label_suggestion = help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done
|
||||
|
||||
|
@ -33,7 +33,6 @@ use crate::{
|
||||
BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns,
|
||||
BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasGenericBounds,
|
||||
BuiltinTypeAliasGenericBoundsSuggestion, BuiltinTypeAliasWhereClause,
|
||||
BuiltinUnexpectedCliConfigName, BuiltinUnexpectedCliConfigValue,
|
||||
BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
|
||||
BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe,
|
||||
BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
|
||||
@ -60,7 +59,6 @@ use rustc_middle::ty::GenericArgKind;
|
||||
use rustc_middle::ty::ToPredicate;
|
||||
use rustc_middle::ty::TypeVisitableExt;
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef};
|
||||
use rustc_session::config::ExpectedValues;
|
||||
use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason};
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::source_map::Spanned;
|
||||
@ -2889,26 +2887,3 @@ impl EarlyLintPass for SpecialModuleName {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use rustc_session::lint::builtin::UNEXPECTED_CFGS;
|
||||
|
||||
declare_lint_pass!(UnexpectedCfgs => [UNEXPECTED_CFGS]);
|
||||
|
||||
impl EarlyLintPass for UnexpectedCfgs {
|
||||
fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
|
||||
let cfg = &cx.sess().parse_sess.config;
|
||||
let check_cfg = &cx.sess().parse_sess.check_config;
|
||||
for &(name, value) in cfg {
|
||||
match check_cfg.expecteds.get(&name) {
|
||||
Some(ExpectedValues::Some(values)) if !values.contains(&value) => {
|
||||
let value = value.unwrap_or(kw::Empty);
|
||||
cx.emit_lint(UNEXPECTED_CFGS, BuiltinUnexpectedCliConfigValue { name, value });
|
||||
}
|
||||
None if check_cfg.exhaustive_names => {
|
||||
cx.emit_lint(UNEXPECTED_CFGS, BuiltinUnexpectedCliConfigName { name });
|
||||
}
|
||||
_ => { /* expected */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +179,6 @@ early_lint_methods!(
|
||||
IncompleteInternalFeatures: IncompleteInternalFeatures,
|
||||
RedundantSemicolons: RedundantSemicolons,
|
||||
UnusedDocComment: UnusedDocComment,
|
||||
UnexpectedCfgs: UnexpectedCfgs,
|
||||
]
|
||||
]
|
||||
);
|
||||
|
@ -553,21 +553,6 @@ pub enum BuiltinSpecialModuleNameUsed {
|
||||
Main,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(lint_builtin_unexpected_cli_config_name)]
|
||||
#[help]
|
||||
pub struct BuiltinUnexpectedCliConfigName {
|
||||
pub name: Symbol,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(lint_builtin_unexpected_cli_config_value)]
|
||||
#[help]
|
||||
pub struct BuiltinUnexpectedCliConfigValue {
|
||||
pub name: Symbol,
|
||||
pub value: Symbol,
|
||||
}
|
||||
|
||||
// deref_into_dyn_supertrait.rs
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(lint_supertrait_as_deref_target)]
|
||||
|
@ -3439,6 +3439,7 @@ declare_lint_pass! {
|
||||
UNCONDITIONAL_PANIC,
|
||||
UNCONDITIONAL_RECURSION,
|
||||
UNDEFINED_NAKED_FUNCTION_ABI,
|
||||
UNEXPECTED_CFGS,
|
||||
UNFULFILLED_LINT_EXPECTATIONS,
|
||||
UNINHABITED_STATIC,
|
||||
UNKNOWN_CRATE_TYPES,
|
||||
|
@ -525,13 +525,6 @@ impl<'tcx> TyCtxt<'tcx> {
|
||||
self.alloc_map.lock().reserve()
|
||||
}
|
||||
|
||||
/// Miri's provenance GC needs to see all live allocations. The interpreter manages most
|
||||
/// allocations but some are managed by [`TyCtxt`] and without this method the interpreter
|
||||
/// doesn't know their [`AllocId`]s are in use.
|
||||
pub fn iter_allocs<F: FnMut(AllocId)>(self, func: F) {
|
||||
self.alloc_map.lock().alloc_map.keys().copied().for_each(func)
|
||||
}
|
||||
|
||||
/// Reserves a new ID *if* this allocation has not been dedup-reserved before.
|
||||
/// Should only be used for "symbolic" allocations (function pointers, vtables, statics), we
|
||||
/// don't want to dedup IDs for "real" memory!
|
||||
|
@ -1587,7 +1587,6 @@ supported_targets! {
|
||||
("armv7r-none-eabihf", armv7r_none_eabihf),
|
||||
|
||||
("x86_64-pc-solaris", x86_64_pc_solaris),
|
||||
("x86_64-sun-solaris", x86_64_sun_solaris),
|
||||
("sparcv9-sun-solaris", sparcv9_sun_solaris),
|
||||
|
||||
("x86_64-unknown-illumos", x86_64_unknown_illumos),
|
||||
|
@ -1,20 +0,0 @@
|
||||
use crate::spec::{base, Cc, LinkerFlavor, StackProbeType, Target};
|
||||
|
||||
pub fn target() -> Target {
|
||||
let mut base = base::solaris::opts();
|
||||
base.add_pre_link_args(LinkerFlavor::Unix(Cc::Yes), &["-m64"]);
|
||||
base.cpu = "x86-64".into();
|
||||
base.plt_by_default = false;
|
||||
base.vendor = "sun".into();
|
||||
base.max_atomic_width = Some(64);
|
||||
base.stack_probes = StackProbeType::X86;
|
||||
|
||||
Target {
|
||||
llvm_target: "x86_64-pc-solaris".into(),
|
||||
pointer_width: 64,
|
||||
data_layout: "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
.into(),
|
||||
arch: "x86_64".into(),
|
||||
options: base,
|
||||
}
|
||||
}
|
@ -49,9 +49,9 @@ const MIR_OPT_BLESS_TARGET_MAPPING: &[(&str, &str)] = &[
|
||||
("i686-unknown-linux-musl", "x86_64-unknown-linux-musl"),
|
||||
("i686-pc-windows-msvc", "x86_64-pc-windows-msvc"),
|
||||
("i686-pc-windows-gnu", "x86_64-pc-windows-gnu"),
|
||||
("i686-apple-darwin", "x86_64-apple-darwin"),
|
||||
// ARM Macs don't have a corresponding 32-bit target that they can (easily)
|
||||
// build for, so there is no entry for "aarch64-apple-darwin" here.
|
||||
// Likewise, i686 for macOS is no longer possible to build.
|
||||
];
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
|
@ -48,9 +48,6 @@ ENV \
|
||||
AR_x86_64_pc_solaris=x86_64-pc-solaris2.10-ar \
|
||||
CC_x86_64_pc_solaris=x86_64-pc-solaris2.10-gcc \
|
||||
CXX_x86_64_pc_solaris=x86_64-pc-solaris2.10-g++ \
|
||||
AR_x86_64_sun_solaris=x86_64-sun-solaris2.10-ar \
|
||||
CC_x86_64_sun_solaris=x86_64-sun-solaris2.10-gcc \
|
||||
CXX_x86_64_sun_solaris=x86_64-sun-solaris2.10-g++ \
|
||||
CC_armv7_unknown_linux_gnueabi=arm-linux-gnueabi-gcc-9 \
|
||||
CXX_armv7_unknown_linux_gnueabi=arm-linux-gnueabi-g++-9 \
|
||||
AR_x86_64_fortanix_unknown_sgx=ar \
|
||||
@ -84,8 +81,6 @@ COPY host-x86_64/dist-various-2/build-fuchsia-toolchain.sh /tmp/
|
||||
RUN /tmp/build-fuchsia-toolchain.sh
|
||||
COPY host-x86_64/dist-various-2/build-solaris-toolchain.sh /tmp/
|
||||
RUN /tmp/build-solaris-toolchain.sh x86_64 amd64 solaris-i386 pc
|
||||
# Build deprecated target 'x86_64-sun-solaris2.10' until removed
|
||||
RUN /tmp/build-solaris-toolchain.sh x86_64 amd64 solaris-i386 sun
|
||||
RUN /tmp/build-solaris-toolchain.sh sparcv9 sparcv9 solaris-sparc sun
|
||||
COPY host-x86_64/dist-various-2/build-x86_64-fortanix-unknown-sgx-toolchain.sh /tmp/
|
||||
RUN /tmp/build-x86_64-fortanix-unknown-sgx-toolchain.sh
|
||||
@ -120,7 +115,6 @@ ENV TARGETS=$TARGETS,wasm32-wasi
|
||||
ENV TARGETS=$TARGETS,wasm32-wasi-preview1-threads
|
||||
ENV TARGETS=$TARGETS,sparcv9-sun-solaris
|
||||
ENV TARGETS=$TARGETS,x86_64-pc-solaris
|
||||
ENV TARGETS=$TARGETS,x86_64-sun-solaris
|
||||
ENV TARGETS=$TARGETS,x86_64-unknown-linux-gnux32
|
||||
ENV TARGETS=$TARGETS,x86_64-fortanix-unknown-sgx
|
||||
ENV TARGETS=$TARGETS,nvptx64-nvidia-cuda
|
||||
|
@ -25,7 +25,16 @@ cat /tmp/toolstate/toolstates.json
|
||||
python3 "$X_PY" test --stage 2 check-tools
|
||||
python3 "$X_PY" test --stage 2 src/tools/clippy
|
||||
python3 "$X_PY" test --stage 2 src/tools/rustfmt
|
||||
python3 "$X_PY" test --stage 2 src/tools/miri
|
||||
|
||||
# Testing Miri is a bit more complicated.
|
||||
# We set the GC interval to the shortest possible value (0 would be off) to increase the chance
|
||||
# that bugs which only surface when the GC runs at a specific time are more likely to cause CI to fail.
|
||||
# This significantly increases the runtime of our test suite, or we'd do this in PR CI too.
|
||||
if [[ -z "${PR_CI_JOB:-}" ]]; then
|
||||
MIRIFLAGS=-Zmiri-provenance-gc=1 python3 "$X_PY" test --stage 2 src/tools/miri
|
||||
else
|
||||
python3 "$X_PY" test --stage 2 src/tools/miri
|
||||
fi
|
||||
# We natively run this script on x86_64-unknown-linux-gnu and x86_64-pc-windows-msvc.
|
||||
# Also cover some other targets via cross-testing, in particular all tier 1 targets.
|
||||
export BOOTSTRAP_SKIP_TARGET_SANITY=1 # we don't need `cc` for these targets
|
||||
|
@ -165,7 +165,7 @@ target | std | notes
|
||||
`riscv64gc-unknown-none-elf` | * | Bare RISC-V (RV64IMAFDC ISA)
|
||||
`riscv64imac-unknown-none-elf` | * | Bare RISC-V (RV64IMAC ISA)
|
||||
`sparc64-unknown-linux-gnu` | ✓ | SPARC Linux (kernel 4.4, glibc 2.23)
|
||||
`sparcv9-sun-solaris` | ✓ | SPARC Solaris 10/11, illumos
|
||||
`sparcv9-sun-solaris` | ✓ | SPARC Solaris 11, illumos
|
||||
`thumbv6m-none-eabi` | * | Bare ARMv6-M
|
||||
`thumbv7em-none-eabi` | * | Bare ARMv7E-M
|
||||
`thumbv7em-none-eabihf` | * | Bare ARMV7E-M, hardfloat
|
||||
@ -184,7 +184,7 @@ target | std | notes
|
||||
`x86_64-fuchsia` | ✓ | Alias for `x86_64-unknown-fuchsia`
|
||||
[`x86_64-unknown-fuchsia`](platform-support/fuchsia.md) | ✓ | 64-bit x86 Fuchsia
|
||||
[`x86_64-linux-android`](platform-support/android.md) | ✓ | 64-bit x86 Android
|
||||
`x86_64-pc-solaris` | ✓ | 64-bit Solaris 10/11, illumos
|
||||
`x86_64-pc-solaris` | ✓ | 64-bit Solaris 11, illumos
|
||||
`x86_64-unknown-linux-gnux32` | ✓ | 64-bit Linux (x32 ABI) (kernel 4.15, glibc 2.27)
|
||||
[`x86_64-unknown-none`](platform-support/x86_64-unknown-none.md) | * | Freestanding/bare-metal x86_64, softfloat
|
||||
`x86_64-unknown-redox` | ✓ | Redox OS
|
||||
@ -342,7 +342,6 @@ target | std | host | notes
|
||||
[`x86_64-pc-nto-qnx710`](platform-support/nto-qnx.md) | ✓ | | x86 64-bit QNX Neutrino 7.1 RTOS |
|
||||
[`x86_64-pc-windows-gnullvm`](platform-support/pc-windows-gnullvm.md) | ✓ | ✓ |
|
||||
`x86_64-pc-windows-msvc` | * | | 64-bit Windows XP support
|
||||
`x86_64-sun-solaris` | ? | | Deprecated target for 64-bit Solaris 10/11, illumos
|
||||
[`x86_64-unikraft-linux-musl`](platform-support/unikraft-linux-musl.md) | ✓ | | 64-bit Unikraft with musl
|
||||
`x86_64-unknown-dragonfly` | ✓ | ✓ | 64-bit DragonFlyBSD
|
||||
`x86_64-unknown-haiku` | ✓ | ✓ | 64-bit Haiku
|
||||
|
@ -72,6 +72,7 @@ the standard library and functions that are included in the results list:
|
||||
| [`stdout, [u8]`][stdoutu8] | `Stdout::write` |
|
||||
| [`any -> !`][] | `panic::panic_any` |
|
||||
| [`vec::intoiter<T> -> [T]`][iterasslice] | `IntoIter::as_slice` and `IntoIter::next_chunk` |
|
||||
| [`iterator<T>, fnmut -> T`][iterreduce] | `Iterator::reduce` and `Iterator::find` |
|
||||
|
||||
[`usize -> vec`]: ../../std/vec/struct.Vec.html?search=usize%20-%3E%20vec&filter-crate=std
|
||||
[`vec, vec -> bool`]: ../../std/vec/struct.Vec.html?search=vec,%20vec%20-%3E%20bool&filter-crate=std
|
||||
@ -81,6 +82,7 @@ the standard library and functions that are included in the results list:
|
||||
[`any -> !`]: ../../std/vec/struct.Vec.html?search=any%20-%3E%20!&filter-crate=std
|
||||
[stdoutu8]: ../../std/vec/struct.Vec.html?search=stdout%2C%20[u8]&filter-crate=std
|
||||
[iterasslice]: ../../std/vec/struct.Vec.html?search=vec%3A%3Aintoiter<T>%20->%20[T]&filter-crate=std
|
||||
[iterreduce]: ../../std/index.html?search=iterator<T>%2C%20fnmut%20->%20T&filter-crate=std
|
||||
|
||||
### How type-based search works
|
||||
|
||||
@ -95,7 +97,9 @@ After deciding which items are type parameters and which are actual types, it
|
||||
then searches by matching up the function parameters (written before the `->`)
|
||||
and the return types (written after the `->`). Type matching is order-agnostic,
|
||||
and allows items to be left out of the query, but items that are present in the
|
||||
query must be present in the function for it to match.
|
||||
query must be present in the function for it to match. The `self` parameter is
|
||||
treated the same as any other parameter, and `Self` is resolved to the
|
||||
underlying type's name.
|
||||
|
||||
Function signature searches can query generics, wrapped in angle brackets, and
|
||||
traits will be normalized like types in the search engine if no type parameters
|
||||
@ -103,8 +107,37 @@ match them. For example, a function with the signature
|
||||
`fn my_function<I: Iterator<Item=u32>>(input: I) -> usize`
|
||||
can be matched with the following queries:
|
||||
|
||||
* `Iterator<u32> -> usize`
|
||||
* `Iterator -> usize`
|
||||
* `Iterator<Item=u32> -> usize`
|
||||
* `Iterator<u32> -> usize` (you can leave out the `Item=` part)
|
||||
* `Iterator -> usize` (you can leave out iterator's generic entirely)
|
||||
* `T -> usize` (you can match with a generic parameter)
|
||||
|
||||
Each of the above queries is progressively looser, except the last one
|
||||
would not match `dyn Iterator`, since that's not a type parameter.
|
||||
|
||||
If a bound has multiple associated types, specifying the name allows you to
|
||||
pick which one gets matched. If no name is specified, then the query will
|
||||
match of any of them. For example,
|
||||
|
||||
```rust
|
||||
pub trait MyTrait {
|
||||
type First;
|
||||
type Second;
|
||||
}
|
||||
|
||||
/// This function can be found using the following search queries:
|
||||
///
|
||||
/// MyTrait<First=u8, Second=u32> -> bool
|
||||
/// MyTrait<u32, First=u8> -> bool
|
||||
/// MyTrait<Second=u32> -> bool
|
||||
/// MyTrait<u32, u8> -> bool
|
||||
///
|
||||
/// The following queries, however, will *not* match it:
|
||||
///
|
||||
/// MyTrait<First=u32> -> bool
|
||||
/// MyTrait<u32, u32> -> bool
|
||||
pub fn my_fn(x: impl MyTrait<First=u8, Second=u32>) -> bool { true }
|
||||
```
|
||||
|
||||
Generics and function parameters are order-agnostic, but sensitive to nesting
|
||||
and number of matches. For example, a function with the signature
|
||||
@ -134,6 +167,10 @@ Most of these limitations should be addressed in future version of Rustdoc.
|
||||
with that bound, it'll match, but `option<T> -> T where T: Default`
|
||||
cannot be precisely searched for (use `option<Default> -> Default`).
|
||||
|
||||
* Supertraits, type aliases, and Deref are all ignored. Search mostly
|
||||
operates on type signatures *as written*, and not as they are
|
||||
represented within the compiler.
|
||||
|
||||
* Type parameters match type parameters, such that `Option<A>` matches
|
||||
`Option<T>`, but never match concrete types in function signatures.
|
||||
A trait named as if it were a type, such as `Option<Read>`, will match
|
||||
@ -183,7 +220,8 @@ slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
|
||||
arg = [type-filter *WS COLON *WS] (path [generics] / slice / [!])
|
||||
type-sep = COMMA/WS *(COMMA/WS)
|
||||
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
|
||||
generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
|
||||
generic-arg-list = *(type-sep) arg [ EQUAL arg ] *(type-sep arg [ EQUAL arg ]) *(type-sep)
|
||||
generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep)
|
||||
CLOSE-ANGLE-BRACKET
|
||||
return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
|
||||
|
||||
@ -230,6 +268,7 @@ DOUBLE-COLON = "::"
|
||||
QUOTE = %x22
|
||||
COMMA = ","
|
||||
RETURN-ARROW = "->"
|
||||
EQUAL = "="
|
||||
|
||||
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
|
||||
DIGIT = %x30-39
|
||||
|
@ -35,6 +35,9 @@ and `cfg!(name = "value")` call. It will check that the `"value"` specified is p
|
||||
list of expected values. If `"value"` is not in it, then `rustc` will report an `unexpected_cfgs`
|
||||
lint diagnostic. The default diagnostic level for this lint is `Warn`.
|
||||
|
||||
The command line `--cfg` arguments are currently *NOT* checked but may very well be checked in
|
||||
the future.
|
||||
|
||||
To enable checking of values, but to provide an empty set of expected values, use these forms:
|
||||
|
||||
```bash
|
||||
|
@ -1651,6 +1651,13 @@ impl Type {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generic_args(&self) -> Option<&GenericArgs> {
|
||||
match self {
|
||||
Type::Path { path, .. } => path.generic_args(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generics(&self) -> Option<Vec<&Type>> {
|
||||
match self {
|
||||
Type::Path { path, .. } => path.generics(),
|
||||
@ -2191,6 +2198,10 @@ impl Path {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generic_args(&self) -> Option<&GenericArgs> {
|
||||
self.segments.last().map(|seg| &seg.args)
|
||||
}
|
||||
|
||||
pub(crate) fn generics(&self) -> Option<Vec<&Type>> {
|
||||
self.segments.last().and_then(|seg| {
|
||||
if let GenericArgs::AngleBracketed { ref args, .. } = seg.args {
|
||||
@ -2232,6 +2243,39 @@ impl GenericArgs {
|
||||
GenericArgs::Parenthesized { inputs, output } => inputs.is_empty() && output.is_none(),
|
||||
}
|
||||
}
|
||||
pub(crate) fn bindings<'a>(&'a self) -> Box<dyn Iterator<Item = TypeBinding> + 'a> {
|
||||
match self {
|
||||
&GenericArgs::AngleBracketed { ref bindings, .. } => Box::new(bindings.iter().cloned()),
|
||||
&GenericArgs::Parenthesized { ref output, .. } => Box::new(
|
||||
output
|
||||
.as_ref()
|
||||
.map(|ty| TypeBinding {
|
||||
assoc: PathSegment {
|
||||
name: sym::Output,
|
||||
args: GenericArgs::AngleBracketed {
|
||||
args: Vec::new().into_boxed_slice(),
|
||||
bindings: ThinVec::new(),
|
||||
},
|
||||
},
|
||||
kind: TypeBindingKind::Equality { term: Term::Type((**ty).clone()) },
|
||||
})
|
||||
.into_iter(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a GenericArgs {
|
||||
type IntoIter = Box<dyn Iterator<Item = GenericArg> + 'a>;
|
||||
type Item = GenericArg;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
match self {
|
||||
&GenericArgs::AngleBracketed { ref args, .. } => Box::new(args.iter().cloned()),
|
||||
&GenericArgs::Parenthesized { ref inputs, .. } => {
|
||||
Box::new(inputs.iter().cloned().map(GenericArg::Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
|
@ -369,6 +369,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
|
||||
&item,
|
||||
self.tcx,
|
||||
clean_impl_generics(self.cache.parent_stack.last()).as_ref(),
|
||||
parent,
|
||||
self.cache,
|
||||
),
|
||||
aliases: item.attrs.get_doc_aliases(),
|
||||
|
@ -49,6 +49,8 @@ pub(crate) enum ItemType {
|
||||
ProcAttribute = 23,
|
||||
ProcDerive = 24,
|
||||
TraitAlias = 25,
|
||||
// This number is reserved for use in JavaScript
|
||||
// Generic = 26,
|
||||
}
|
||||
|
||||
impl Serialize for ItemType {
|
||||
|
@ -113,6 +113,7 @@ pub(crate) struct IndexItem {
|
||||
pub(crate) struct RenderType {
|
||||
id: Option<RenderTypeId>,
|
||||
generics: Option<Vec<RenderType>>,
|
||||
bindings: Option<Vec<(RenderTypeId, Vec<RenderType>)>>,
|
||||
}
|
||||
|
||||
impl Serialize for RenderType {
|
||||
@ -129,10 +130,15 @@ impl Serialize for RenderType {
|
||||
Some(RenderTypeId::Index(idx)) => *idx,
|
||||
_ => panic!("must convert render types to indexes before serializing"),
|
||||
};
|
||||
if let Some(generics) = &self.generics {
|
||||
if self.generics.is_some() || self.bindings.is_some() {
|
||||
let mut seq = serializer.serialize_seq(None)?;
|
||||
seq.serialize_element(&id)?;
|
||||
seq.serialize_element(generics)?;
|
||||
seq.serialize_element(self.generics.as_ref().map(Vec::as_slice).unwrap_or_default())?;
|
||||
if self.bindings.is_some() {
|
||||
seq.serialize_element(
|
||||
self.bindings.as_ref().map(Vec::as_slice).unwrap_or_default(),
|
||||
)?;
|
||||
}
|
||||
seq.end()
|
||||
} else {
|
||||
id.serialize(serializer)
|
||||
@ -140,13 +146,31 @@ impl Serialize for RenderType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum RenderTypeId {
|
||||
DefId(DefId),
|
||||
Primitive(clean::PrimitiveType),
|
||||
AssociatedType(Symbol),
|
||||
Index(isize),
|
||||
}
|
||||
|
||||
impl Serialize for RenderTypeId {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let id = match &self {
|
||||
// 0 is a sentinel, everything else is one-indexed
|
||||
// concrete type
|
||||
RenderTypeId::Index(idx) if *idx >= 0 => idx + 1,
|
||||
// generic type parameter
|
||||
RenderTypeId::Index(idx) => *idx,
|
||||
_ => panic!("must convert render types to indexes before serializing"),
|
||||
};
|
||||
id.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Full type of functions/methods in the search index.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct IndexItemFunctionType {
|
||||
@ -171,17 +195,22 @@ impl Serialize for IndexItemFunctionType {
|
||||
} else {
|
||||
let mut seq = serializer.serialize_seq(None)?;
|
||||
match &self.inputs[..] {
|
||||
[one] if one.generics.is_none() => seq.serialize_element(one)?,
|
||||
[one] if one.generics.is_none() && one.bindings.is_none() => {
|
||||
seq.serialize_element(one)?
|
||||
}
|
||||
_ => seq.serialize_element(&self.inputs)?,
|
||||
}
|
||||
match &self.output[..] {
|
||||
[] if self.where_clause.is_empty() => {}
|
||||
[one] if one.generics.is_none() => seq.serialize_element(one)?,
|
||||
[one] if one.generics.is_none() && one.bindings.is_none() => {
|
||||
seq.serialize_element(one)?
|
||||
}
|
||||
_ => seq.serialize_element(&self.output)?,
|
||||
}
|
||||
for constraint in &self.where_clause {
|
||||
if let [one] = &constraint[..]
|
||||
&& one.generics.is_none()
|
||||
&& one.bindings.is_none()
|
||||
{
|
||||
seq.serialize_element(one)?;
|
||||
} else {
|
||||
|
@ -3,8 +3,10 @@ use std::collections::BTreeMap;
|
||||
|
||||
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::symbol::Symbol;
|
||||
use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
|
||||
use thin_vec::ThinVec;
|
||||
|
||||
use crate::clean;
|
||||
use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate};
|
||||
@ -22,6 +24,7 @@ pub(crate) fn build_index<'tcx>(
|
||||
) -> String {
|
||||
let mut itemid_to_pathid = FxHashMap::default();
|
||||
let mut primitives = FxHashMap::default();
|
||||
let mut associated_types = FxHashMap::default();
|
||||
let mut crate_paths = vec![];
|
||||
|
||||
// Attach all orphan items to the type's definition if the type
|
||||
@ -38,7 +41,13 @@ pub(crate) fn build_index<'tcx>(
|
||||
parent: Some(parent),
|
||||
parent_idx: None,
|
||||
impl_id,
|
||||
search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache),
|
||||
search_type: get_function_type_for_search(
|
||||
item,
|
||||
tcx,
|
||||
impl_generics.as_ref(),
|
||||
Some(parent),
|
||||
cache,
|
||||
),
|
||||
aliases: item.attrs.get_doc_aliases(),
|
||||
deprecation: item.deprecation(tcx),
|
||||
});
|
||||
@ -76,31 +85,81 @@ pub(crate) fn build_index<'tcx>(
|
||||
let mut search_index = std::mem::replace(&mut cache.search_index, Vec::new());
|
||||
for item in search_index.iter_mut() {
|
||||
fn insert_into_map<F: std::hash::Hash + Eq>(
|
||||
ty: &mut RenderType,
|
||||
map: &mut FxHashMap<F, isize>,
|
||||
itemid: F,
|
||||
lastpathid: &mut isize,
|
||||
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
|
||||
item_type: ItemType,
|
||||
path: &[Symbol],
|
||||
) {
|
||||
) -> RenderTypeId {
|
||||
match map.entry(itemid) {
|
||||
Entry::Occupied(entry) => ty.id = Some(RenderTypeId::Index(*entry.get())),
|
||||
Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()),
|
||||
Entry::Vacant(entry) => {
|
||||
let pathid = *lastpathid;
|
||||
entry.insert(pathid);
|
||||
*lastpathid += 1;
|
||||
crate_paths.push((item_type, path.to_vec()));
|
||||
ty.id = Some(RenderTypeId::Index(pathid));
|
||||
RenderTypeId::Index(pathid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_render_type_id(
|
||||
id: RenderTypeId,
|
||||
cache: &mut Cache,
|
||||
itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
|
||||
primitives: &mut FxHashMap<Symbol, isize>,
|
||||
associated_types: &mut FxHashMap<Symbol, isize>,
|
||||
lastpathid: &mut isize,
|
||||
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
|
||||
) -> Option<RenderTypeId> {
|
||||
let Cache { ref paths, ref external_paths, .. } = *cache;
|
||||
match id {
|
||||
RenderTypeId::DefId(defid) => {
|
||||
if let Some(&(ref fqp, item_type)) =
|
||||
paths.get(&defid).or_else(|| external_paths.get(&defid))
|
||||
{
|
||||
Some(insert_into_map(
|
||||
itemid_to_pathid,
|
||||
ItemId::DefId(defid),
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
item_type,
|
||||
fqp,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
RenderTypeId::Primitive(primitive) => {
|
||||
let sym = primitive.as_sym();
|
||||
Some(insert_into_map(
|
||||
primitives,
|
||||
sym,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
ItemType::Primitive,
|
||||
&[sym],
|
||||
))
|
||||
}
|
||||
RenderTypeId::Index(_) => Some(id),
|
||||
RenderTypeId::AssociatedType(sym) => Some(insert_into_map(
|
||||
associated_types,
|
||||
sym,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
ItemType::AssocType,
|
||||
&[sym],
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_render_type(
|
||||
ty: &mut RenderType,
|
||||
cache: &mut Cache,
|
||||
itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
|
||||
primitives: &mut FxHashMap<Symbol, isize>,
|
||||
associated_types: &mut FxHashMap<Symbol, isize>,
|
||||
lastpathid: &mut isize,
|
||||
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
|
||||
) {
|
||||
@ -111,48 +170,54 @@ pub(crate) fn build_index<'tcx>(
|
||||
cache,
|
||||
itemid_to_pathid,
|
||||
primitives,
|
||||
associated_types,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
);
|
||||
}
|
||||
}
|
||||
let Cache { ref paths, ref external_paths, .. } = *cache;
|
||||
if let Some(bindings) = &mut ty.bindings {
|
||||
bindings.retain_mut(|(associated_type, constraints)| {
|
||||
let converted_associated_type = convert_render_type_id(
|
||||
*associated_type,
|
||||
cache,
|
||||
itemid_to_pathid,
|
||||
primitives,
|
||||
associated_types,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
);
|
||||
let Some(converted_associated_type) = converted_associated_type else {
|
||||
return false;
|
||||
};
|
||||
*associated_type = converted_associated_type;
|
||||
for constraint in constraints {
|
||||
convert_render_type(
|
||||
constraint,
|
||||
cache,
|
||||
itemid_to_pathid,
|
||||
primitives,
|
||||
associated_types,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
);
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
let Some(id) = ty.id.clone() else {
|
||||
assert!(ty.generics.is_some());
|
||||
return;
|
||||
};
|
||||
match id {
|
||||
RenderTypeId::DefId(defid) => {
|
||||
if let Some(&(ref fqp, item_type)) =
|
||||
paths.get(&defid).or_else(|| external_paths.get(&defid))
|
||||
{
|
||||
insert_into_map(
|
||||
ty,
|
||||
itemid_to_pathid,
|
||||
ItemId::DefId(defid),
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
item_type,
|
||||
fqp,
|
||||
);
|
||||
} else {
|
||||
ty.id = None;
|
||||
}
|
||||
}
|
||||
RenderTypeId::Primitive(primitive) => {
|
||||
let sym = primitive.as_sym();
|
||||
insert_into_map(
|
||||
ty,
|
||||
primitives,
|
||||
sym,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
ItemType::Primitive,
|
||||
&[sym],
|
||||
);
|
||||
}
|
||||
RenderTypeId::Index(_) => {}
|
||||
}
|
||||
ty.id = convert_render_type_id(
|
||||
id,
|
||||
cache,
|
||||
itemid_to_pathid,
|
||||
primitives,
|
||||
associated_types,
|
||||
lastpathid,
|
||||
crate_paths,
|
||||
);
|
||||
}
|
||||
if let Some(search_type) = &mut item.search_type {
|
||||
for item in &mut search_type.inputs {
|
||||
@ -161,6 +226,7 @@ pub(crate) fn build_index<'tcx>(
|
||||
cache,
|
||||
&mut itemid_to_pathid,
|
||||
&mut primitives,
|
||||
&mut associated_types,
|
||||
&mut lastpathid,
|
||||
&mut crate_paths,
|
||||
);
|
||||
@ -171,6 +237,7 @@ pub(crate) fn build_index<'tcx>(
|
||||
cache,
|
||||
&mut itemid_to_pathid,
|
||||
&mut primitives,
|
||||
&mut associated_types,
|
||||
&mut lastpathid,
|
||||
&mut crate_paths,
|
||||
);
|
||||
@ -182,6 +249,7 @@ pub(crate) fn build_index<'tcx>(
|
||||
cache,
|
||||
&mut itemid_to_pathid,
|
||||
&mut primitives,
|
||||
&mut associated_types,
|
||||
&mut lastpathid,
|
||||
&mut crate_paths,
|
||||
);
|
||||
@ -442,12 +510,39 @@ pub(crate) fn get_function_type_for_search<'tcx>(
|
||||
item: &clean::Item,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
impl_generics: Option<&(clean::Type, clean::Generics)>,
|
||||
parent: Option<DefId>,
|
||||
cache: &Cache,
|
||||
) -> Option<IndexItemFunctionType> {
|
||||
let mut trait_info = None;
|
||||
let impl_or_trait_generics = impl_generics.or_else(|| {
|
||||
if let Some(def_id) = parent
|
||||
&& let Some(trait_) = cache.traits.get(&def_id)
|
||||
&& let Some((path, _)) =
|
||||
cache.paths.get(&def_id).or_else(|| cache.external_paths.get(&def_id))
|
||||
{
|
||||
let path = clean::Path {
|
||||
res: rustc_hir::def::Res::Def(rustc_hir::def::DefKind::Trait, def_id),
|
||||
segments: path
|
||||
.iter()
|
||||
.map(|name| clean::PathSegment {
|
||||
name: *name,
|
||||
args: clean::GenericArgs::AngleBracketed {
|
||||
args: Vec::new().into_boxed_slice(),
|
||||
bindings: ThinVec::new(),
|
||||
},
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
trait_info = Some((clean::Type::Path { path }, trait_.generics.clone()));
|
||||
Some(trait_info.as_ref().unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let (mut inputs, mut output, where_clause) = match *item.kind {
|
||||
clean::FunctionItem(ref f) => get_fn_inputs_and_outputs(f, tcx, impl_generics, cache),
|
||||
clean::MethodItem(ref m, _) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache),
|
||||
clean::TyMethodItem(ref m) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache),
|
||||
clean::FunctionItem(ref f) | clean::MethodItem(ref f, _) | clean::TyMethodItem(ref f) => {
|
||||
get_fn_inputs_and_outputs(f, tcx, impl_or_trait_generics, cache)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
@ -457,14 +552,23 @@ pub(crate) fn get_function_type_for_search<'tcx>(
|
||||
Some(IndexItemFunctionType { inputs, output, where_clause })
|
||||
}
|
||||
|
||||
fn get_index_type(clean_type: &clean::Type, generics: Vec<RenderType>) -> RenderType {
|
||||
fn get_index_type(
|
||||
clean_type: &clean::Type,
|
||||
generics: Vec<RenderType>,
|
||||
rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
|
||||
) -> RenderType {
|
||||
RenderType {
|
||||
id: get_index_type_id(clean_type),
|
||||
id: get_index_type_id(clean_type, rgen),
|
||||
generics: if generics.is_empty() { None } else { Some(generics) },
|
||||
bindings: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> {
|
||||
fn get_index_type_id(
|
||||
clean_type: &clean::Type,
|
||||
rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
|
||||
) -> Option<RenderTypeId> {
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
match *clean_type {
|
||||
clean::Type::Path { ref path, .. } => Some(RenderTypeId::DefId(path.def_id())),
|
||||
clean::DynTrait(ref bounds, _) => {
|
||||
@ -472,18 +576,27 @@ fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> {
|
||||
}
|
||||
clean::Primitive(p) => Some(RenderTypeId::Primitive(p)),
|
||||
clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => {
|
||||
get_index_type_id(type_)
|
||||
get_index_type_id(type_, rgen)
|
||||
}
|
||||
// The type parameters are converted to generics in `simplify_fn_type`
|
||||
clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)),
|
||||
clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)),
|
||||
clean::Tuple(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Tuple)),
|
||||
clean::QPath(ref data) => {
|
||||
if data.self_type.is_self_type()
|
||||
&& let Some(clean::Path { res: Res::Def(DefKind::Trait, trait_), .. }) = data.trait_
|
||||
{
|
||||
let idx = -isize::try_from(rgen.len() + 1).unwrap();
|
||||
let (idx, _) = rgen
|
||||
.entry(SimplifiedParam::AssociatedType(trait_, data.assoc.name))
|
||||
.or_insert_with(|| (idx, Vec::new()));
|
||||
Some(RenderTypeId::Index(*idx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
// Not supported yet
|
||||
clean::BareFunction(_)
|
||||
| clean::Generic(_)
|
||||
| clean::ImplTrait(_)
|
||||
| clean::QPath { .. }
|
||||
| clean::Infer => None,
|
||||
clean::BareFunction(_) | clean::Generic(_) | clean::ImplTrait(_) | clean::Infer => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,6 +606,9 @@ enum SimplifiedParam {
|
||||
Symbol(Symbol),
|
||||
// every argument-position impl trait is its own type parameter
|
||||
Anonymous(isize),
|
||||
// in a trait definition, the associated types are all bound to
|
||||
// their own type parameter
|
||||
AssociatedType(DefId, Symbol),
|
||||
}
|
||||
|
||||
/// The point of this function is to lower generics and types into the simplified form that the
|
||||
@ -523,10 +639,17 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
}
|
||||
|
||||
// First, check if it's "Self".
|
||||
let mut is_self = false;
|
||||
let mut arg = if let Some(self_) = self_ {
|
||||
match &*arg {
|
||||
Type::BorrowedRef { type_, .. } if type_.is_self_type() => self_,
|
||||
type_ if type_.is_self_type() => self_,
|
||||
Type::BorrowedRef { type_, .. } if type_.is_self_type() => {
|
||||
is_self = true;
|
||||
self_
|
||||
}
|
||||
type_ if type_.is_self_type() => {
|
||||
is_self = true;
|
||||
self_
|
||||
}
|
||||
arg => arg,
|
||||
}
|
||||
} else {
|
||||
@ -585,11 +708,19 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
}
|
||||
}
|
||||
if let Some((idx, _)) = rgen.get(&SimplifiedParam::Symbol(arg_s)) {
|
||||
res.push(RenderType { id: Some(RenderTypeId::Index(*idx)), generics: None });
|
||||
res.push(RenderType {
|
||||
id: Some(RenderTypeId::Index(*idx)),
|
||||
generics: None,
|
||||
bindings: None,
|
||||
});
|
||||
} else {
|
||||
let idx = -isize::try_from(rgen.len() + 1).unwrap();
|
||||
rgen.insert(SimplifiedParam::Symbol(arg_s), (idx, type_bounds));
|
||||
res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None });
|
||||
res.push(RenderType {
|
||||
id: Some(RenderTypeId::Index(idx)),
|
||||
generics: None,
|
||||
bindings: None,
|
||||
});
|
||||
}
|
||||
} else if let Type::ImplTrait(ref bounds) = *arg {
|
||||
let mut type_bounds = Vec::new();
|
||||
@ -611,12 +742,16 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
}
|
||||
if is_return && !type_bounds.is_empty() {
|
||||
// In parameter position, `impl Trait` is a unique thing.
|
||||
res.push(RenderType { id: None, generics: Some(type_bounds) });
|
||||
res.push(RenderType { id: None, generics: Some(type_bounds), bindings: None });
|
||||
} else {
|
||||
// In parameter position, `impl Trait` is the same as an unnamed generic parameter.
|
||||
let idx = -isize::try_from(rgen.len() + 1).unwrap();
|
||||
rgen.insert(SimplifiedParam::Anonymous(idx), (idx, type_bounds));
|
||||
res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None });
|
||||
res.push(RenderType {
|
||||
id: Some(RenderTypeId::Index(idx)),
|
||||
generics: None,
|
||||
bindings: None,
|
||||
});
|
||||
}
|
||||
} else if let Type::Slice(ref ty) = *arg {
|
||||
let mut ty_generics = Vec::new();
|
||||
@ -631,7 +766,7 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
res.push(get_index_type(arg, ty_generics));
|
||||
res.push(get_index_type(arg, ty_generics, rgen));
|
||||
} else if let Type::Array(ref ty, _) = *arg {
|
||||
let mut ty_generics = Vec::new();
|
||||
simplify_fn_type(
|
||||
@ -645,7 +780,7 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
res.push(get_index_type(arg, ty_generics));
|
||||
res.push(get_index_type(arg, ty_generics, rgen));
|
||||
} else if let Type::Tuple(ref tys) = *arg {
|
||||
let mut ty_generics = Vec::new();
|
||||
for ty in tys {
|
||||
@ -661,7 +796,7 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
cache,
|
||||
);
|
||||
}
|
||||
res.push(get_index_type(arg, ty_generics));
|
||||
res.push(get_index_type(arg, ty_generics, rgen));
|
||||
} else {
|
||||
// This is not a type parameter. So for example if we have `T, U: Option<T>`, and we're
|
||||
// looking at `Option`, we enter this "else" condition, otherwise if it's `T`, we don't.
|
||||
@ -669,12 +804,16 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
// So in here, we can add it directly and look for its own type parameters (so for `Option`,
|
||||
// we will look for them but not for `T`).
|
||||
let mut ty_generics = Vec::new();
|
||||
if let Some(arg_generics) = arg.generics() {
|
||||
for gen in arg_generics.iter() {
|
||||
let mut ty_bindings = Vec::new();
|
||||
if let Some(arg_generics) = arg.generic_args() {
|
||||
for ty in arg_generics.into_iter().filter_map(|gen| match gen {
|
||||
clean::GenericArg::Type(ty) => Some(ty),
|
||||
_ => None,
|
||||
}) {
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
gen,
|
||||
&ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_generics,
|
||||
@ -683,17 +822,180 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
cache,
|
||||
);
|
||||
}
|
||||
for binding in arg_generics.bindings() {
|
||||
simplify_fn_binding(
|
||||
self_,
|
||||
generics,
|
||||
&binding,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_bindings,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
let id = get_index_type_id(&arg);
|
||||
// Every trait associated type on self gets assigned to a type parameter index
|
||||
// this same one is used later for any appearances of these types
|
||||
//
|
||||
// for example, Iterator::next is:
|
||||
//
|
||||
// trait Iterator {
|
||||
// fn next(&mut self) -> Option<Self::Item>
|
||||
// }
|
||||
//
|
||||
// Self is technically just Iterator, but we want to pretend it's more like this:
|
||||
//
|
||||
// fn next<T>(self: Iterator<Item=T>) -> Option<T>
|
||||
if is_self
|
||||
&& let Type::Path { path } = arg
|
||||
&& let def_id = path.def_id()
|
||||
&& let Some(trait_) = cache.traits.get(&def_id)
|
||||
&& trait_.items.iter().any(|at| at.is_ty_associated_type())
|
||||
{
|
||||
for assoc_ty in &trait_.items {
|
||||
if let clean::ItemKind::TyAssocTypeItem(_generics, bounds) = &*assoc_ty.kind
|
||||
&& let Some(name) = assoc_ty.name
|
||||
{
|
||||
let idx = -isize::try_from(rgen.len() + 1).unwrap();
|
||||
let (idx, stored_bounds) = rgen
|
||||
.entry(SimplifiedParam::AssociatedType(def_id, name))
|
||||
.or_insert_with(|| (idx, Vec::new()));
|
||||
let idx = *idx;
|
||||
if stored_bounds.is_empty() {
|
||||
// Can't just pass stored_bounds to simplify_fn_type,
|
||||
// because it also accepts rgen as a parameter.
|
||||
// Instead, have it fill in this local, then copy it into the map afterward.
|
||||
let mut type_bounds = Vec::new();
|
||||
for bound in bounds {
|
||||
if let Some(path) = bound.get_trait_path() {
|
||||
let ty = Type::Path { path };
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut type_bounds,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
let stored_bounds = &mut rgen
|
||||
.get_mut(&SimplifiedParam::AssociatedType(def_id, name))
|
||||
.unwrap()
|
||||
.1;
|
||||
if stored_bounds.is_empty() {
|
||||
*stored_bounds = type_bounds;
|
||||
}
|
||||
}
|
||||
ty_bindings.push((
|
||||
RenderTypeId::AssociatedType(name),
|
||||
vec![RenderType {
|
||||
id: Some(RenderTypeId::Index(idx)),
|
||||
generics: None,
|
||||
bindings: None,
|
||||
}],
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
let id = get_index_type_id(&arg, rgen);
|
||||
if id.is_some() || !ty_generics.is_empty() {
|
||||
res.push(RenderType {
|
||||
id,
|
||||
bindings: if ty_bindings.is_empty() { None } else { Some(ty_bindings) },
|
||||
generics: if ty_generics.is_empty() { None } else { Some(ty_generics) },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn simplify_fn_binding<'tcx, 'a>(
|
||||
self_: Option<&'a Type>,
|
||||
generics: &Generics,
|
||||
binding: &'a clean::TypeBinding,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
recurse: usize,
|
||||
res: &mut Vec<(RenderTypeId, Vec<RenderType>)>,
|
||||
rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
|
||||
is_return: bool,
|
||||
cache: &Cache,
|
||||
) {
|
||||
let mut ty_binding_constraints = Vec::new();
|
||||
let ty_binding_assoc = RenderTypeId::AssociatedType(binding.assoc.name);
|
||||
for gen in &binding.assoc.args {
|
||||
match gen {
|
||||
clean::GenericArg::Type(arg) => simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&arg,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_binding_constraints,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
),
|
||||
clean::GenericArg::Lifetime(_)
|
||||
| clean::GenericArg::Const(_)
|
||||
| clean::GenericArg::Infer => {}
|
||||
}
|
||||
}
|
||||
for binding in binding.assoc.args.bindings() {
|
||||
simplify_fn_binding(
|
||||
self_,
|
||||
generics,
|
||||
&binding,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
res,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
match &binding.kind {
|
||||
clean::TypeBindingKind::Equality { term } => {
|
||||
if let clean::Term::Type(arg) = &term {
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
arg,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_binding_constraints,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
clean::TypeBindingKind::Constraint { bounds } => {
|
||||
for bound in &bounds[..] {
|
||||
if let Some(path) = bound.get_trait_path() {
|
||||
let ty = Type::Path { path };
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_binding_constraints,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res.push((ty_binding_assoc, ty_binding_constraints));
|
||||
}
|
||||
|
||||
/// Return the full list of types when bounds have been resolved.
|
||||
///
|
||||
/// i.e. `fn foo<A: Display, B: Option<A>>(x: u32, y: B)` will return
|
||||
@ -701,13 +1003,15 @@ fn simplify_fn_type<'tcx, 'a>(
|
||||
fn get_fn_inputs_and_outputs<'tcx>(
|
||||
func: &Function,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
impl_generics: Option<&(clean::Type, clean::Generics)>,
|
||||
impl_or_trait_generics: Option<&(clean::Type, clean::Generics)>,
|
||||
cache: &Cache,
|
||||
) -> (Vec<RenderType>, Vec<RenderType>, Vec<Vec<RenderType>>) {
|
||||
let decl = &func.decl;
|
||||
|
||||
let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default();
|
||||
|
||||
let combined_generics;
|
||||
let (self_, generics) = if let Some((impl_self, impl_generics)) = impl_generics {
|
||||
let (self_, generics) = if let Some((impl_self, impl_generics)) = impl_or_trait_generics {
|
||||
match (impl_generics.is_empty(), func.generics.is_empty()) {
|
||||
(true, _) => (Some(impl_self), &func.generics),
|
||||
(_, true) => (Some(impl_self), impl_generics),
|
||||
@ -729,8 +1033,6 @@ fn get_fn_inputs_and_outputs<'tcx>(
|
||||
(None, &func.generics)
|
||||
};
|
||||
|
||||
let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default();
|
||||
|
||||
let mut arg_types = Vec::new();
|
||||
for arg in decl.inputs.values.iter() {
|
||||
simplify_fn_type(
|
||||
|
@ -14,6 +14,7 @@ function initSearch(searchIndex){}
|
||||
* pathWithoutLast: Array<string>,
|
||||
* pathLast: string,
|
||||
* generics: Array<QueryElement>,
|
||||
* bindings: Map<(string|integer), Array<QueryElement>>,
|
||||
* }}
|
||||
*/
|
||||
let QueryElement;
|
||||
@ -24,6 +25,7 @@ let QueryElement;
|
||||
* totalElems: number,
|
||||
* typeFilter: (null|string),
|
||||
* userQuery: string,
|
||||
* isInBinding: (null|string),
|
||||
* }}
|
||||
*/
|
||||
let ParserState;
|
||||
@ -191,8 +193,9 @@ let FunctionSearchType;
|
||||
/**
|
||||
* @typedef {{
|
||||
* id: (null|number),
|
||||
* ty: (null|number),
|
||||
* ty: number,
|
||||
* generics: Array<FunctionType>,
|
||||
* bindings: Map<integer, Array<FunctionType>>,
|
||||
* }}
|
||||
*/
|
||||
let FunctionType;
|
||||
|
@ -23,27 +23,27 @@ const itemTypes = [
|
||||
"import",
|
||||
"struct",
|
||||
"enum",
|
||||
"fn",
|
||||
"fn", // 5
|
||||
"type",
|
||||
"static",
|
||||
"trait",
|
||||
"impl",
|
||||
"tymethod",
|
||||
"tymethod", // 10
|
||||
"method",
|
||||
"structfield",
|
||||
"variant",
|
||||
"macro",
|
||||
"primitive",
|
||||
"primitive", // 15
|
||||
"associatedtype",
|
||||
"constant",
|
||||
"associatedconstant",
|
||||
"union",
|
||||
"foreigntype",
|
||||
"foreigntype", // 20
|
||||
"keyword",
|
||||
"existential",
|
||||
"attr",
|
||||
"derive",
|
||||
"traitalias",
|
||||
"traitalias", // 25
|
||||
"generic",
|
||||
];
|
||||
|
||||
@ -298,7 +298,7 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
|
||||
function isEndCharacter(c) {
|
||||
return ",>-]".indexOf(c) !== -1;
|
||||
return "=,>-]".indexOf(c) !== -1;
|
||||
}
|
||||
|
||||
function isStopCharacter(c) {
|
||||
@ -398,7 +398,7 @@ function initSearch(rawSearchIndex) {
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isSeparatorCharacter(c) {
|
||||
return c === ",";
|
||||
return c === "," || c === "=";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -500,6 +500,8 @@ function initSearch(rawSearchIndex) {
|
||||
" does not accept generic parameters",
|
||||
];
|
||||
}
|
||||
const bindingName = parserState.isInBinding;
|
||||
parserState.isInBinding = null;
|
||||
return {
|
||||
name: "never",
|
||||
id: null,
|
||||
@ -507,7 +509,9 @@ function initSearch(rawSearchIndex) {
|
||||
pathWithoutLast: [],
|
||||
pathLast: "never",
|
||||
generics: [],
|
||||
bindings: new Map(),
|
||||
typeFilter: "primitive",
|
||||
bindingName,
|
||||
};
|
||||
}
|
||||
if (path.startsWith("::")) {
|
||||
@ -542,14 +546,27 @@ function initSearch(rawSearchIndex) {
|
||||
if (isInGenerics) {
|
||||
parserState.genericsElems += 1;
|
||||
}
|
||||
const bindingName = parserState.isInBinding;
|
||||
parserState.isInBinding = null;
|
||||
const bindings = new Map();
|
||||
return {
|
||||
name: name.trim(),
|
||||
id: null,
|
||||
fullPath: pathSegments,
|
||||
pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
|
||||
pathLast: pathSegments[pathSegments.length - 1],
|
||||
generics: generics,
|
||||
generics: generics.filter(gen => {
|
||||
// Syntactically, bindings are parsed as generics,
|
||||
// but the query engine treats them differently.
|
||||
if (gen.bindingName !== null) {
|
||||
bindings.set(gen.bindingName.name, [gen, ...gen.bindingName.generics]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
bindings,
|
||||
typeFilter,
|
||||
bindingName,
|
||||
};
|
||||
}
|
||||
|
||||
@ -608,6 +625,7 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
} else if (
|
||||
c === "[" ||
|
||||
c === "=" ||
|
||||
isStopCharacter(c) ||
|
||||
isSpecialStartCharacter(c) ||
|
||||
isSeparatorCharacter(c)
|
||||
@ -657,6 +675,7 @@ function initSearch(rawSearchIndex) {
|
||||
parserState.pos += 1;
|
||||
getItemsBefore(query, parserState, generics, "]");
|
||||
const typeFilter = parserState.typeFilter;
|
||||
const isInBinding = parserState.isInBinding;
|
||||
if (typeFilter !== null && typeFilter !== "primitive") {
|
||||
throw [
|
||||
"Invalid search type: primitive ",
|
||||
@ -667,10 +686,16 @@ function initSearch(rawSearchIndex) {
|
||||
];
|
||||
}
|
||||
parserState.typeFilter = null;
|
||||
parserState.isInBinding = null;
|
||||
parserState.totalElems += 1;
|
||||
if (isInGenerics) {
|
||||
parserState.genericsElems += 1;
|
||||
}
|
||||
for (const gen of generics) {
|
||||
if (gen.bindingName !== null) {
|
||||
throw ["Type parameter ", "=", " cannot be within slice ", "[]"];
|
||||
}
|
||||
}
|
||||
elems.push({
|
||||
name: "[]",
|
||||
id: null,
|
||||
@ -679,6 +704,8 @@ function initSearch(rawSearchIndex) {
|
||||
pathLast: "[]",
|
||||
generics,
|
||||
typeFilter: "primitive",
|
||||
bindingName: isInBinding,
|
||||
bindings: new Map(),
|
||||
});
|
||||
} else {
|
||||
const isStringElem = parserState.userQuery[start] === "\"";
|
||||
@ -705,15 +732,38 @@ function initSearch(rawSearchIndex) {
|
||||
if (start >= end && generics.length === 0) {
|
||||
return;
|
||||
}
|
||||
elems.push(
|
||||
createQueryElement(
|
||||
query,
|
||||
parserState,
|
||||
parserState.userQuery.slice(start, end),
|
||||
generics,
|
||||
isInGenerics
|
||||
)
|
||||
);
|
||||
if (parserState.userQuery[parserState.pos] === "=") {
|
||||
if (parserState.isInBinding) {
|
||||
throw ["Cannot write ", "=", " twice in a binding"];
|
||||
}
|
||||
if (!isInGenerics) {
|
||||
throw ["Type parameter ", "=", " must be within generics list"];
|
||||
}
|
||||
const name = parserState.userQuery.slice(start, end).trim();
|
||||
if (name === "!") {
|
||||
throw ["Type parameter ", "=", " key cannot be ", "!", " never type"];
|
||||
}
|
||||
if (name.includes("!")) {
|
||||
throw ["Type parameter ", "=", " key cannot be ", "!", " macro"];
|
||||
}
|
||||
if (name.includes("::")) {
|
||||
throw ["Type parameter ", "=", " key cannot contain ", "::", " path"];
|
||||
}
|
||||
if (name.includes(":")) {
|
||||
throw ["Type parameter ", "=", " key cannot contain ", ":", " type"];
|
||||
}
|
||||
parserState.isInBinding = { name, generics };
|
||||
} else {
|
||||
elems.push(
|
||||
createQueryElement(
|
||||
query,
|
||||
parserState,
|
||||
parserState.userQuery.slice(start, end),
|
||||
generics,
|
||||
isInGenerics
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -737,6 +787,8 @@ function initSearch(rawSearchIndex) {
|
||||
// If this is a generic, keep the outer item's type filter around.
|
||||
const oldTypeFilter = parserState.typeFilter;
|
||||
parserState.typeFilter = null;
|
||||
const oldIsInBinding = parserState.isInBinding;
|
||||
parserState.isInBinding = null;
|
||||
|
||||
let extra = "";
|
||||
if (endChar === ">") {
|
||||
@ -752,6 +804,9 @@ function initSearch(rawSearchIndex) {
|
||||
while (parserState.pos < parserState.length) {
|
||||
const c = parserState.userQuery[parserState.pos];
|
||||
if (c === endChar) {
|
||||
if (parserState.isInBinding) {
|
||||
throw ["Unexpected ", endChar, " after ", "="];
|
||||
}
|
||||
break;
|
||||
} else if (isSeparatorCharacter(c)) {
|
||||
parserState.pos += 1;
|
||||
@ -791,7 +846,9 @@ function initSearch(rawSearchIndex) {
|
||||
throw [
|
||||
"Expected ",
|
||||
",",
|
||||
" or ",
|
||||
", ",
|
||||
"=",
|
||||
", or ",
|
||||
endChar,
|
||||
...extra,
|
||||
", found ",
|
||||
@ -801,6 +858,8 @@ function initSearch(rawSearchIndex) {
|
||||
throw [
|
||||
"Expected ",
|
||||
",",
|
||||
" or ",
|
||||
"=",
|
||||
...extra,
|
||||
", found ",
|
||||
c,
|
||||
@ -828,6 +887,7 @@ function initSearch(rawSearchIndex) {
|
||||
parserState.pos += 1;
|
||||
|
||||
parserState.typeFilter = oldTypeFilter;
|
||||
parserState.isInBinding = oldIsInBinding;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1054,6 +1114,11 @@ function initSearch(rawSearchIndex) {
|
||||
for (const elem2 of elem.generics) {
|
||||
convertTypeFilterOnElem(elem2);
|
||||
}
|
||||
for (const constraints of elem.bindings.values()) {
|
||||
for (const constraint of constraints) {
|
||||
convertTypeFilterOnElem(constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
userQuery = userQuery.trim();
|
||||
const parserState = {
|
||||
@ -1063,6 +1128,7 @@ function initSearch(rawSearchIndex) {
|
||||
totalElems: 0,
|
||||
genericsElems: 0,
|
||||
typeFilter: null,
|
||||
isInBinding: null,
|
||||
userQuery: userQuery.toLowerCase(),
|
||||
};
|
||||
let query = newParsedQuery(userQuery);
|
||||
@ -1342,7 +1408,8 @@ function initSearch(rawSearchIndex) {
|
||||
const fl = fnTypesIn.length;
|
||||
|
||||
// One element fast path / base case
|
||||
if (ql === 1 && queryElems[0].generics.length === 0) {
|
||||
if (ql === 1 && queryElems[0].generics.length === 0
|
||||
&& queryElems[0].bindings.size === 0) {
|
||||
const queryElem = queryElems[0];
|
||||
for (const fnType of fnTypesIn) {
|
||||
if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) {
|
||||
@ -1453,16 +1520,33 @@ function initSearch(rawSearchIndex) {
|
||||
whereClause,
|
||||
mgensScratch,
|
||||
mgensScratch => {
|
||||
if (fnType.generics.length === 0 && queryElem.generics.length === 0) {
|
||||
if (fnType.generics.length === 0 && queryElem.generics.length === 0
|
||||
&& fnType.bindings.size === 0 && queryElem.bindings.size === 0) {
|
||||
return !solutionCb || solutionCb(mgensScratch);
|
||||
}
|
||||
return unifyFunctionTypes(
|
||||
fnType.generics,
|
||||
queryElem.generics,
|
||||
const solution = unifyFunctionTypeCheckBindings(
|
||||
fnType,
|
||||
queryElem,
|
||||
whereClause,
|
||||
mgensScratch,
|
||||
solutionCb
|
||||
mgensScratch
|
||||
);
|
||||
if (!solution) {
|
||||
return false;
|
||||
}
|
||||
const simplifiedGenerics = solution.simplifiedGenerics;
|
||||
for (const simplifiedMgens of solution.mgens) {
|
||||
const passesUnification = unifyFunctionTypes(
|
||||
simplifiedGenerics,
|
||||
queryElem.generics,
|
||||
whereClause,
|
||||
simplifiedMgens,
|
||||
solutionCb
|
||||
);
|
||||
if (passesUnification) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
if (passesUnification) {
|
||||
@ -1491,8 +1575,11 @@ function initSearch(rawSearchIndex) {
|
||||
const generics = fnType.id < 0 ?
|
||||
whereClause[(-fnType.id) - 1] :
|
||||
fnType.generics;
|
||||
const bindings = fnType.bindings ?
|
||||
Array.from(fnType.bindings.values()).flat() :
|
||||
[];
|
||||
const passesUnification = unifyFunctionTypes(
|
||||
fnTypes.toSpliced(i, 1, ...generics),
|
||||
fnTypes.toSpliced(i, 1, ...generics, ...bindings),
|
||||
queryElems,
|
||||
whereClause,
|
||||
mgensScratch,
|
||||
@ -1504,22 +1591,37 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens) {
|
||||
/**
|
||||
* Check if this function is a match candidate.
|
||||
*
|
||||
* This function is all the fast checks that don't require backtracking.
|
||||
* It checks that two items are not named differently, and is load-bearing for that.
|
||||
* It also checks that, if the query has generics, the function type must have generics
|
||||
* or associated type bindings: that's not load-bearing, but it prevents unnecessary
|
||||
* backtracking later.
|
||||
*
|
||||
* @param {FunctionType} fnType
|
||||
* @param {QueryElement} queryElem
|
||||
* @param {[FunctionSearchType]} whereClause - Trait bounds for generic items.
|
||||
* @param {Map<number,number>|null} mgensIn - Map functions generics to query generics.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgensIn) {
|
||||
// type filters look like `trait:Read` or `enum:Result`
|
||||
if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) {
|
||||
return false;
|
||||
}
|
||||
// fnType.id < 0 means generic
|
||||
// queryElem.id < 0 does too
|
||||
// mgens[fnType.id] = queryElem.id
|
||||
// or, if mgens[fnType.id] = 0, then we've matched this generic with a bare trait
|
||||
// mgensIn[fnType.id] = queryElem.id
|
||||
// or, if mgensIn[fnType.id] = 0, then we've matched this generic with a bare trait
|
||||
// and should make that same decision everywhere it appears
|
||||
if (fnType.id < 0 && queryElem.id < 0) {
|
||||
if (mgens !== null) {
|
||||
if (mgens.has(fnType.id) && mgens.get(fnType.id) !== queryElem.id) {
|
||||
if (mgensIn) {
|
||||
if (mgensIn.has(fnType.id) && mgensIn.get(fnType.id) !== queryElem.id) {
|
||||
return false;
|
||||
}
|
||||
for (const [fid, qid] of mgens.entries()) {
|
||||
for (const [fid, qid] of mgensIn.entries()) {
|
||||
if (fnType.id !== fid && queryElem.id === qid) {
|
||||
return false;
|
||||
}
|
||||
@ -1528,6 +1630,7 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (queryElem.id === typeNameIdOfArrayOrSlice &&
|
||||
(fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray)
|
||||
@ -1539,7 +1642,12 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
// If the query elem has generics, and the function doesn't,
|
||||
// it can't match.
|
||||
if (fnType.generics.length === 0 && queryElem.generics.length !== 0) {
|
||||
if ((fnType.generics.length + fnType.bindings.size) === 0 &&
|
||||
queryElem.generics.length !== 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (fnType.bindings.size < queryElem.bindings.size) {
|
||||
return false;
|
||||
}
|
||||
// If the query element is a path (it contains `::`), we need to check if this
|
||||
@ -1568,9 +1676,87 @@ function initSearch(rawSearchIndex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* This function checks the associated type bindings. Any that aren't matched get converted
|
||||
* to generics, and this function returns an array of the function's generics with these
|
||||
* simplified bindings added to them. That is, it takes a path like this:
|
||||
*
|
||||
* Iterator<Item=u32>
|
||||
*
|
||||
* ... if queryElem itself has an `Item=` in it, then this function returns an empty array.
|
||||
* But if queryElem contains no Item=, then this function returns a one-item array with the
|
||||
* ID of u32 in it, and the rest of the matching engine acts as if `Iterator<u32>` were
|
||||
* the type instead.
|
||||
*
|
||||
* @param {FunctionType} fnType
|
||||
* @param {QueryElement} queryElem
|
||||
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
|
||||
* @param {Map<number,number>} mgensIn - Map functions generics to query generics.
|
||||
* Never modified.
|
||||
* @returns {false|{mgens: [Map<number,number>], simplifiedGenerics: [FunctionType]}}
|
||||
*/
|
||||
function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensIn) {
|
||||
if (fnType.bindings.size < queryElem.bindings.size) {
|
||||
return false;
|
||||
}
|
||||
let simplifiedGenerics = fnType.generics || [];
|
||||
if (fnType.bindings.size > 0) {
|
||||
let mgensSolutionSet = [mgensIn];
|
||||
for (const [name, constraints] of queryElem.bindings.entries()) {
|
||||
if (mgensSolutionSet.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (!fnType.bindings.has(name)) {
|
||||
return false;
|
||||
}
|
||||
const fnTypeBindings = fnType.bindings.get(name);
|
||||
mgensSolutionSet = mgensSolutionSet.flatMap(mgens => {
|
||||
const newSolutions = [];
|
||||
unifyFunctionTypes(
|
||||
fnTypeBindings,
|
||||
constraints,
|
||||
whereClause,
|
||||
mgens,
|
||||
newMgens => {
|
||||
newSolutions.push(newMgens);
|
||||
// return `false` makes unifyFunctionTypes return the full set of
|
||||
// possible solutions
|
||||
return false;
|
||||
}
|
||||
);
|
||||
return newSolutions;
|
||||
});
|
||||
}
|
||||
if (mgensSolutionSet.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const binds = Array.from(fnType.bindings.entries()).flatMap(entry => {
|
||||
const [name, constraints] = entry;
|
||||
if (queryElem.bindings.has(name)) {
|
||||
return [];
|
||||
} else {
|
||||
return constraints;
|
||||
}
|
||||
});
|
||||
if (simplifiedGenerics.length > 0) {
|
||||
simplifiedGenerics = [...simplifiedGenerics, ...binds];
|
||||
} else {
|
||||
simplifiedGenerics = binds;
|
||||
}
|
||||
return { simplifiedGenerics, mgens: mgensSolutionSet };
|
||||
}
|
||||
return { simplifiedGenerics, mgens: [mgensIn] };
|
||||
}
|
||||
/**
|
||||
* @param {FunctionType} fnType
|
||||
* @param {QueryElement} queryElem
|
||||
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
|
||||
* @param {Map<number,number>|null} mgens - Map functions generics to query generics.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens) {
|
||||
if (fnType.id < 0 && queryElem.id >= 0) {
|
||||
if (!whereClause) {
|
||||
@ -1578,7 +1764,7 @@ function initSearch(rawSearchIndex) {
|
||||
}
|
||||
// mgens[fnType.id] === 0 indicates that we committed to unboxing this generic
|
||||
// mgens[fnType.id] === null indicates that we haven't decided yet
|
||||
if (mgens !== null && mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) {
|
||||
if (mgens && mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) {
|
||||
return false;
|
||||
}
|
||||
// This is only a potential unbox if the search query appears in the where clause
|
||||
@ -1586,8 +1772,12 @@ function initSearch(rawSearchIndex) {
|
||||
// `fn read_all<R: Read>(R) -> Result<usize>`
|
||||
// generic `R` is considered "unboxed"
|
||||
return checkIfInList(whereClause[(-fnType.id) - 1], queryElem, whereClause);
|
||||
} else if (fnType.generics && fnType.generics.length > 0) {
|
||||
return checkIfInList(fnType.generics, queryElem, whereClause);
|
||||
} else if (fnType.generics.length > 0 || fnType.bindings.size > 0) {
|
||||
const simplifiedGenerics = [
|
||||
...fnType.generics,
|
||||
...Array.from(fnType.bindings.values()).flat(),
|
||||
];
|
||||
return checkIfInList(simplifiedGenerics, queryElem, whereClause);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1622,15 +1812,17 @@ function initSearch(rawSearchIndex) {
|
||||
* @return {boolean} - Returns true if the type matches, false otherwise.
|
||||
*/
|
||||
function checkType(row, elem, whereClause) {
|
||||
if (elem.id < 0) {
|
||||
return row.id < 0 || checkIfInList(row.generics, elem, whereClause);
|
||||
}
|
||||
if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 &&
|
||||
typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
|
||||
// special case
|
||||
elem.id !== typeNameIdOfArrayOrSlice
|
||||
) {
|
||||
return row.id === elem.id || checkIfInList(row.generics, elem, whereClause);
|
||||
if (row.bindings.size === 0 && elem.bindings.size === 0) {
|
||||
if (elem.id < 0) {
|
||||
return row.id < 0 || checkIfInList(row.generics, elem, whereClause);
|
||||
}
|
||||
if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 &&
|
||||
typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
|
||||
// special case
|
||||
elem.id !== typeNameIdOfArrayOrSlice
|
||||
) {
|
||||
return row.id === elem.id || checkIfInList(row.generics, elem, whereClause);
|
||||
}
|
||||
}
|
||||
return unifyFunctionTypes([row], [elem], whereClause);
|
||||
}
|
||||
@ -1977,7 +2169,7 @@ function initSearch(rawSearchIndex) {
|
||||
elem.id = match;
|
||||
}
|
||||
if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1
|
||||
&& elem.generics.length === 0)
|
||||
&& elem.generics.length === 0 && elem.bindings.size === 0)
|
||||
|| elem.typeFilter === TY_GENERIC) {
|
||||
if (genericSymbols.has(elem.name)) {
|
||||
elem.id = genericSymbols.get(elem.name);
|
||||
@ -2020,6 +2212,23 @@ function initSearch(rawSearchIndex) {
|
||||
for (const elem2 of elem.generics) {
|
||||
convertNameToId(elem2);
|
||||
}
|
||||
elem.bindings = new Map(Array.from(elem.bindings.entries())
|
||||
.map(entry => {
|
||||
const [name, constraints] = entry;
|
||||
if (!typeNameIdMap.has(name)) {
|
||||
parsedQuery.error = [
|
||||
"Type parameter ",
|
||||
name,
|
||||
" does not exist",
|
||||
];
|
||||
}
|
||||
for (const elem2 of constraints) {
|
||||
convertNameToId(elem2);
|
||||
}
|
||||
|
||||
return [typeNameIdMap.get(name), constraints];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
for (const elem of parsedQuery.elems) {
|
||||
@ -2536,16 +2745,39 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
||||
function buildItemSearchType(type, lowercasePaths) {
|
||||
const PATH_INDEX_DATA = 0;
|
||||
const GENERICS_DATA = 1;
|
||||
let pathIndex, generics;
|
||||
const BINDINGS_DATA = 2;
|
||||
let pathIndex, generics, bindings;
|
||||
if (typeof type === "number") {
|
||||
pathIndex = type;
|
||||
generics = [];
|
||||
bindings = new Map();
|
||||
} else {
|
||||
pathIndex = type[PATH_INDEX_DATA];
|
||||
generics = buildItemSearchTypeAll(
|
||||
type[GENERICS_DATA],
|
||||
lowercasePaths
|
||||
);
|
||||
if (type.length > BINDINGS_DATA) {
|
||||
bindings = new Map(type[BINDINGS_DATA].map(binding => {
|
||||
const [assocType, constraints] = binding;
|
||||
// Associated type constructors are represented sloppily in rustdoc's
|
||||
// type search, to make the engine simpler.
|
||||
//
|
||||
// MyType<Output<T>=Result<T>> is equivalent to MyType<Output<Result<T>>=T>
|
||||
// and both are, essentially
|
||||
// MyType<Output=(T, Result<T>)>, except the tuple isn't actually there.
|
||||
// It's more like the value of a type binding is naturally an array,
|
||||
// which rustdoc calls "constraints".
|
||||
//
|
||||
// As a result, the key should never have generics on it.
|
||||
return [
|
||||
buildItemSearchType(assocType, lowercasePaths).id,
|
||||
buildItemSearchTypeAll(constraints, lowercasePaths),
|
||||
];
|
||||
}));
|
||||
} else {
|
||||
bindings = new Map();
|
||||
}
|
||||
}
|
||||
if (pathIndex < 0) {
|
||||
// types less than 0 are generic parameters
|
||||
@ -2555,6 +2787,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
||||
ty: TY_GENERIC,
|
||||
path: null,
|
||||
generics,
|
||||
bindings,
|
||||
};
|
||||
}
|
||||
if (pathIndex === 0) {
|
||||
@ -2564,6 +2797,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
||||
ty: null,
|
||||
path: null,
|
||||
generics,
|
||||
bindings,
|
||||
};
|
||||
}
|
||||
const item = lowercasePaths[pathIndex - 1];
|
||||
@ -2572,6 +2806,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
||||
ty: item.ty,
|
||||
path: item.path,
|
||||
generics,
|
||||
bindings,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,6 @@ static TARGETS: &[&str] = &[
|
||||
"x86_64-linux-android",
|
||||
"x86_64-pc-windows-gnu",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"x86_64-sun-solaris",
|
||||
"x86_64-pc-solaris",
|
||||
"x86_64-unikraft-linux-musl",
|
||||
"x86_64-unknown-freebsd",
|
||||
|
2
src/tools/miri/.github/workflows/ci.yml
vendored
2
src/tools/miri/.github/workflows/ci.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
- name: Set the tag GC interval to 1 on linux
|
||||
if: runner.os == 'Linux'
|
||||
run: echo "MIRIFLAGS=-Zmiri-tag-gc=1" >> $GITHUB_ENV
|
||||
run: echo "MIRIFLAGS=-Zmiri-provenance-gc=1" >> $GITHUB_ENV
|
||||
|
||||
# Cache the global cargo directory, but NOT the local `target` directory which
|
||||
# we cannot reuse anyway when the nightly changes (and it grows quite large
|
||||
|
@ -411,10 +411,10 @@ to Miri failing to detect cases of undefined behavior in a program.
|
||||
without an explicit value), `none` means it never recurses, `scalar` means it only recurses for
|
||||
types where we would also emit `noalias` annotations in the generated LLVM IR (types passed as
|
||||
individual scalars or pairs of scalars). Setting this to `none` is **unsound**.
|
||||
* `-Zmiri-tag-gc=<blocks>` configures how often the pointer tag garbage collector runs. The default
|
||||
is to search for and remove unreachable tags once every `10000` basic blocks. Setting this to
|
||||
`0` disables the garbage collector, which causes some programs to have explosive memory usage
|
||||
and/or super-linear runtime.
|
||||
* `-Zmiri-provenance-gc=<blocks>` configures how often the pointer provenance garbage collector runs.
|
||||
The default is to search for and remove unreachable provenance once every `10000` basic blocks. Setting
|
||||
this to `0` disables the garbage collector, which causes some programs to have explosive memory
|
||||
usage and/or super-linear runtime.
|
||||
* `-Zmiri-track-alloc-id=<id1>,<id2>,...` shows a backtrace when the given allocations are
|
||||
being allocated or freed. This helps in debugging memory leaks and
|
||||
use after free bugs. Specifying this argument multiple times does not overwrite the previous
|
||||
|
@ -531,10 +531,10 @@ fn main() {
|
||||
Err(err) => show_error!("-Zmiri-report-progress requires a `u32`: {}", err),
|
||||
};
|
||||
miri_config.report_progress = Some(interval);
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-tag-gc=") {
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-provenance-gc=") {
|
||||
let interval = match param.parse::<u32>() {
|
||||
Ok(i) => i,
|
||||
Err(err) => show_error!("-Zmiri-tag-gc requires a `u32`: {}", err),
|
||||
Err(err) => show_error!("-Zmiri-provenance-gc requires a `u32`: {}", err),
|
||||
};
|
||||
miri_config.gc_interval = interval;
|
||||
} else if let Some(param) = arg.strip_prefix("-Zmiri-measureme=") {
|
||||
|
@ -75,8 +75,8 @@ pub struct FrameState {
|
||||
protected_tags: SmallVec<[(AllocId, BorTag); 2]>,
|
||||
}
|
||||
|
||||
impl VisitTags for FrameState {
|
||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for FrameState {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
// `protected_tags` are already recorded by `GlobalStateInner`.
|
||||
}
|
||||
}
|
||||
@ -110,10 +110,10 @@ pub struct GlobalStateInner {
|
||||
unique_is_unique: bool,
|
||||
}
|
||||
|
||||
impl VisitTags for GlobalStateInner {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for GlobalStateInner {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
for &tag in self.protected_tags.keys() {
|
||||
visit(tag);
|
||||
visit(None, Some(tag));
|
||||
}
|
||||
// The only other candidate is base_ptr_tags, and that does not need visiting since we don't ever
|
||||
// GC the bottommost/root tag.
|
||||
@ -236,6 +236,10 @@ impl GlobalStateInner {
|
||||
tag
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove_unreachable_allocs(&mut self, allocs: &LiveAllocs<'_, '_, '_>) {
|
||||
self.base_ptr_tags.retain(|id, _| allocs.is_live(*id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Which borrow tracking method to use
|
||||
@ -503,11 +507,11 @@ impl AllocState {
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for AllocState {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for AllocState {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
match self {
|
||||
AllocState::StackedBorrows(sb) => sb.visit_tags(visit),
|
||||
AllocState::TreeBorrows(tb) => tb.visit_tags(visit),
|
||||
AllocState::StackedBorrows(sb) => sb.visit_provenance(visit),
|
||||
AllocState::TreeBorrows(tb) => tb.visit_provenance(visit),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -462,10 +462,10 @@ impl Stacks {
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for Stacks {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for Stacks {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
for tag in self.exposed_tags.iter().copied() {
|
||||
visit(tag);
|
||||
visit(None, Some(tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ impl<'tcx> Tree {
|
||||
/// Climb the tree to get the tag of a distant ancestor.
|
||||
/// Allows operations on tags that are unreachable by the program
|
||||
/// but still exist in the tree. Not guaranteed to perform consistently
|
||||
/// if `tag-gc=1`.
|
||||
/// if `provenance-gc=1`.
|
||||
fn nth_parent(&self, tag: BorTag, nth_parent: u8) -> Option<BorTag> {
|
||||
let mut idx = self.tag_mapping.get(&tag).unwrap();
|
||||
for _ in 0..nth_parent {
|
||||
|
@ -742,11 +742,11 @@ impl Tree {
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for Tree {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for Tree {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
// To ensure that the root never gets removed, we visit it
|
||||
// (the `root` node of `Tree` is not an `Option<_>`)
|
||||
visit(self.nodes.get(self.root).unwrap().tag)
|
||||
visit(None, Some(self.nodes.get(self.root).unwrap().tag))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -790,9 +790,9 @@ pub struct VClockAlloc {
|
||||
alloc_ranges: RefCell<RangeMap<MemoryCellClocks>>,
|
||||
}
|
||||
|
||||
impl VisitTags for VClockAlloc {
|
||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
|
||||
// No tags here.
|
||||
impl VisitProvenance for VClockAlloc {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
// No tags or allocIds here.
|
||||
}
|
||||
}
|
||||
|
||||
@ -1404,8 +1404,8 @@ pub struct GlobalState {
|
||||
pub track_outdated_loads: bool,
|
||||
}
|
||||
|
||||
impl VisitTags for GlobalState {
|
||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for GlobalState {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
// We don't have any tags.
|
||||
}
|
||||
}
|
||||
|
@ -45,10 +45,10 @@ pub(super) struct InitOnce<'mir, 'tcx> {
|
||||
data_race: VClock,
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx> VisitTags for InitOnce<'mir, 'tcx> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl<'mir, 'tcx> VisitProvenance for InitOnce<'mir, 'tcx> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
for waiter in self.waiters.iter() {
|
||||
waiter.callback.visit_tags(visit);
|
||||
waiter.callback.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,10 +181,10 @@ pub(crate) struct SynchronizationState<'mir, 'tcx> {
|
||||
pub(super) init_onces: IndexVec<InitOnceId, InitOnce<'mir, 'tcx>>,
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx> VisitTags for SynchronizationState<'mir, 'tcx> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl<'mir, 'tcx> VisitProvenance for SynchronizationState<'mir, 'tcx> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
for init_once in self.init_onces.iter() {
|
||||
init_once.visit_tags(visit);
|
||||
init_once.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ pub enum TlsAllocAction {
|
||||
}
|
||||
|
||||
/// Trait for callbacks that can be executed when some event happens, such as after a timeout.
|
||||
pub trait MachineCallback<'mir, 'tcx>: VisitTags {
|
||||
pub trait MachineCallback<'mir, 'tcx>: VisitProvenance {
|
||||
fn call(&self, ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>) -> InterpResult<'tcx>;
|
||||
}
|
||||
|
||||
@ -228,8 +228,8 @@ impl<'mir, 'tcx> Thread<'mir, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for Thread<'_, '_> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for Thread<'_, '_> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let Thread {
|
||||
panic_payloads: panic_payload,
|
||||
last_error,
|
||||
@ -242,17 +242,17 @@ impl VisitTags for Thread<'_, '_> {
|
||||
} = self;
|
||||
|
||||
for payload in panic_payload {
|
||||
payload.visit_tags(visit);
|
||||
payload.visit_provenance(visit);
|
||||
}
|
||||
last_error.visit_tags(visit);
|
||||
last_error.visit_provenance(visit);
|
||||
for frame in stack {
|
||||
frame.visit_tags(visit)
|
||||
frame.visit_provenance(visit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for Frame<'_, '_, Provenance, FrameExtra<'_>> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for Frame<'_, '_, Provenance, FrameExtra<'_>> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let Frame {
|
||||
return_place,
|
||||
locals,
|
||||
@ -266,22 +266,22 @@ impl VisitTags for Frame<'_, '_, Provenance, FrameExtra<'_>> {
|
||||
} = self;
|
||||
|
||||
// Return place.
|
||||
return_place.visit_tags(visit);
|
||||
return_place.visit_provenance(visit);
|
||||
// Locals.
|
||||
for local in locals.iter() {
|
||||
match local.as_mplace_or_imm() {
|
||||
None => {}
|
||||
Some(Either::Left((ptr, meta))) => {
|
||||
ptr.visit_tags(visit);
|
||||
meta.visit_tags(visit);
|
||||
ptr.visit_provenance(visit);
|
||||
meta.visit_provenance(visit);
|
||||
}
|
||||
Some(Either::Right(imm)) => {
|
||||
imm.visit_tags(visit);
|
||||
imm.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extra.visit_tags(visit);
|
||||
extra.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
@ -341,8 +341,8 @@ pub struct ThreadManager<'mir, 'tcx> {
|
||||
timeout_callbacks: FxHashMap<ThreadId, TimeoutCallbackInfo<'mir, 'tcx>>,
|
||||
}
|
||||
|
||||
impl VisitTags for ThreadManager<'_, '_> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for ThreadManager<'_, '_> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let ThreadManager {
|
||||
threads,
|
||||
thread_local_alloc_ids,
|
||||
@ -353,15 +353,15 @@ impl VisitTags for ThreadManager<'_, '_> {
|
||||
} = self;
|
||||
|
||||
for thread in threads {
|
||||
thread.visit_tags(visit);
|
||||
thread.visit_provenance(visit);
|
||||
}
|
||||
for ptr in thread_local_alloc_ids.borrow().values() {
|
||||
ptr.visit_tags(visit);
|
||||
ptr.visit_provenance(visit);
|
||||
}
|
||||
for callback in timeout_callbacks.values() {
|
||||
callback.callback.visit_tags(visit);
|
||||
callback.callback.visit_provenance(visit);
|
||||
}
|
||||
sync.visit_tags(visit);
|
||||
sync.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,15 +108,15 @@ pub struct StoreBufferAlloc {
|
||||
store_buffers: RefCell<RangeObjectMap<StoreBuffer>>,
|
||||
}
|
||||
|
||||
impl VisitTags for StoreBufferAlloc {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for StoreBufferAlloc {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let Self { store_buffers } = self;
|
||||
for val in store_buffers
|
||||
.borrow()
|
||||
.iter()
|
||||
.flat_map(|buf| buf.buffer.iter().map(|element| &element.val))
|
||||
{
|
||||
val.visit_tags(visit);
|
||||
val.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,9 +46,21 @@ pub struct GlobalStateInner {
|
||||
provenance_mode: ProvenanceMode,
|
||||
}
|
||||
|
||||
impl VisitTags for GlobalStateInner {
|
||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
|
||||
// Nothing to visit here.
|
||||
impl VisitProvenance for GlobalStateInner {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
let GlobalStateInner {
|
||||
int_to_ptr_map: _,
|
||||
base_addr: _,
|
||||
exposed: _,
|
||||
next_base_addr: _,
|
||||
provenance_mode: _,
|
||||
} = self;
|
||||
// Though base_addr, int_to_ptr_map, and exposed contain AllocIds, we do not want to visit them.
|
||||
// int_to_ptr_map and exposed must contain only live allocations, and those
|
||||
// are never garbage collected.
|
||||
// base_addr is only relevant if we have a pointer to an AllocId and need to look up its
|
||||
// base address; so if an AllocId is not reachable from somewhere else we can remove it
|
||||
// here.
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +74,12 @@ impl GlobalStateInner {
|
||||
provenance_mode: config.provenance_mode,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_unreachable_allocs(&mut self, allocs: &LiveAllocs<'_, '_, '_>) {
|
||||
// `exposed` and `int_to_ptr_map` are cleared immediately when an allocation
|
||||
// is freed, so `base_addr` is the only one we have to clean up based on the GC.
|
||||
self.base_addr.retain(|id, _| allocs.is_live(*id));
|
||||
}
|
||||
}
|
||||
|
||||
/// Shifts `addr` to make it aligned with `align` by rounding `addr` to the smallest multiple
|
||||
@ -107,7 +125,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
// We only use this provenance if it has been exposed.
|
||||
if global_state.exposed.contains(&alloc_id) {
|
||||
// This must still be live, since we remove allocations from `int_to_ptr_map` when they get freed.
|
||||
debug_assert!(!matches!(ecx.get_alloc_info(alloc_id).2, AllocKind::Dead));
|
||||
debug_assert!(ecx.is_alloc_live(alloc_id));
|
||||
Some(alloc_id)
|
||||
} else {
|
||||
None
|
||||
@ -181,12 +199,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
let ecx = self.eval_context_mut();
|
||||
let global_state = ecx.machine.intptrcast.get_mut();
|
||||
// In strict mode, we don't need this, so we can save some cycles by not tracking it.
|
||||
if global_state.provenance_mode != ProvenanceMode::Strict {
|
||||
trace!("Exposing allocation id {alloc_id:?}");
|
||||
global_state.exposed.insert(alloc_id);
|
||||
if ecx.machine.borrow_tracker.is_some() {
|
||||
ecx.expose_tag(alloc_id, tag)?;
|
||||
}
|
||||
if global_state.provenance_mode == ProvenanceMode::Strict {
|
||||
return Ok(());
|
||||
}
|
||||
// Exposing a dead alloc is a no-op, because it's not possible to get a dead allocation
|
||||
// via int2ptr.
|
||||
if !ecx.is_alloc_live(alloc_id) {
|
||||
return Ok(());
|
||||
}
|
||||
trace!("Exposing allocation id {alloc_id:?}");
|
||||
let global_state = ecx.machine.intptrcast.get_mut();
|
||||
global_state.exposed.insert(alloc_id);
|
||||
if ecx.machine.borrow_tracker.is_some() {
|
||||
ecx.expose_tag(alloc_id, tag)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -77,9 +77,9 @@ mod intptrcast;
|
||||
mod machine;
|
||||
mod mono_hash_map;
|
||||
mod operator;
|
||||
mod provenance_gc;
|
||||
mod range_map;
|
||||
mod shims;
|
||||
mod tag_gc;
|
||||
|
||||
// Establish a "crate-wide prelude": we often import `crate::*`.
|
||||
|
||||
@ -125,8 +125,8 @@ pub use crate::machine::{
|
||||
};
|
||||
pub use crate::mono_hash_map::MonoHashMap;
|
||||
pub use crate::operator::EvalContextExt as _;
|
||||
pub use crate::provenance_gc::{EvalContextExt as _, VisitProvenance, VisitWith, LiveAllocs};
|
||||
pub use crate::range_map::RangeMap;
|
||||
pub use crate::tag_gc::{EvalContextExt as _, VisitTags};
|
||||
|
||||
/// Insert rustc arguments at the beginning of the argument list that Miri wants to be
|
||||
/// set per default, for maximal validation power.
|
||||
|
@ -77,12 +77,12 @@ impl<'tcx> std::fmt::Debug for FrameExtra<'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for FrameExtra<'_> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for FrameExtra<'_> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let FrameExtra { catch_unwind, borrow_tracker, timing: _, is_user_relevant: _ } = self;
|
||||
|
||||
catch_unwind.visit_tags(visit);
|
||||
borrow_tracker.visit_tags(visit);
|
||||
catch_unwind.visit_provenance(visit);
|
||||
borrow_tracker.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
@ -311,13 +311,13 @@ pub struct AllocExtra<'tcx> {
|
||||
pub backtrace: Option<Vec<FrameInfo<'tcx>>>,
|
||||
}
|
||||
|
||||
impl VisitTags for AllocExtra<'_> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for AllocExtra<'_> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let AllocExtra { borrow_tracker, data_race, weak_memory, backtrace: _ } = self;
|
||||
|
||||
borrow_tracker.visit_tags(visit);
|
||||
data_race.visit_tags(visit);
|
||||
weak_memory.visit_tags(visit);
|
||||
borrow_tracker.visit_provenance(visit);
|
||||
data_race.visit_provenance(visit);
|
||||
weak_memory.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
@ -793,8 +793,8 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for MiriMachine<'_, '_> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for MiriMachine<'_, '_> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
#[rustfmt::skip]
|
||||
let MiriMachine {
|
||||
threads,
|
||||
@ -843,20 +843,20 @@ impl VisitTags for MiriMachine<'_, '_> {
|
||||
allocation_spans: _,
|
||||
} = self;
|
||||
|
||||
threads.visit_tags(visit);
|
||||
tls.visit_tags(visit);
|
||||
env_vars.visit_tags(visit);
|
||||
dir_handler.visit_tags(visit);
|
||||
file_handler.visit_tags(visit);
|
||||
data_race.visit_tags(visit);
|
||||
borrow_tracker.visit_tags(visit);
|
||||
intptrcast.visit_tags(visit);
|
||||
main_fn_ret_place.visit_tags(visit);
|
||||
argc.visit_tags(visit);
|
||||
argv.visit_tags(visit);
|
||||
cmd_line.visit_tags(visit);
|
||||
threads.visit_provenance(visit);
|
||||
tls.visit_provenance(visit);
|
||||
env_vars.visit_provenance(visit);
|
||||
dir_handler.visit_provenance(visit);
|
||||
file_handler.visit_provenance(visit);
|
||||
data_race.visit_provenance(visit);
|
||||
borrow_tracker.visit_provenance(visit);
|
||||
intptrcast.visit_provenance(visit);
|
||||
main_fn_ret_place.visit_provenance(visit);
|
||||
argc.visit_provenance(visit);
|
||||
argv.visit_provenance(visit);
|
||||
cmd_line.visit_provenance(visit);
|
||||
for ptr in extern_statics.values() {
|
||||
ptr.visit_tags(visit);
|
||||
ptr.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1380,7 +1380,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
|
||||
// where it mistakenly removes an important tag become visible.
|
||||
if ecx.machine.gc_interval > 0 && ecx.machine.since_gc >= ecx.machine.gc_interval {
|
||||
ecx.machine.since_gc = 0;
|
||||
ecx.garbage_collect_tags()?;
|
||||
ecx.run_provenance_gc();
|
||||
}
|
||||
|
||||
// These are our preemption points.
|
||||
|
@ -46,6 +46,14 @@ impl<K: Hash + Eq, V> AllocMap<K, V> for MonoHashMap<K, V> {
|
||||
self.0.get_mut().contains_key(k)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn contains_key_ref<Q: ?Sized + Hash + Eq>(&self, k: &Q) -> bool
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
{
|
||||
self.0.borrow().contains_key(k)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn insert(&mut self, k: K, v: V) -> Option<V> {
|
||||
self.0.get_mut().insert(k, Box::new(v)).map(|x| *x)
|
||||
|
209
src/tools/miri/src/provenance_gc.rs
Normal file
209
src/tools/miri/src/provenance_gc.rs
Normal file
@ -0,0 +1,209 @@
|
||||
use either::Either;
|
||||
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub type VisitWith<'a> = dyn FnMut(Option<AllocId>, Option<BorTag>) + 'a;
|
||||
|
||||
pub trait VisitProvenance {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>);
|
||||
}
|
||||
|
||||
impl<T: VisitProvenance> VisitProvenance for Option<T> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
if let Some(x) = self {
|
||||
x.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: VisitProvenance> VisitProvenance for std::cell::RefCell<T> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
self.borrow().visit_provenance(visit)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for BorTag {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
visit(None, Some(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for AllocId {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
visit(Some(*self), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for Provenance {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
if let Provenance::Concrete { alloc_id, tag, .. } = self {
|
||||
visit(Some(*alloc_id), Some(*tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for Pointer<Provenance> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let (prov, _offset) = self.into_parts();
|
||||
prov.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for Pointer<Option<Provenance>> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let (prov, _offset) = self.into_parts();
|
||||
prov.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for Scalar<Provenance> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
match self {
|
||||
Scalar::Ptr(ptr, _) => ptr.visit_provenance(visit),
|
||||
Scalar::Int(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for Immediate<Provenance> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
match self {
|
||||
Immediate::Scalar(s) => {
|
||||
s.visit_provenance(visit);
|
||||
}
|
||||
Immediate::ScalarPair(s1, s2) => {
|
||||
s1.visit_provenance(visit);
|
||||
s2.visit_provenance(visit);
|
||||
}
|
||||
Immediate::Uninit => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for MemPlaceMeta<Provenance> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
match self {
|
||||
MemPlaceMeta::Meta(m) => m.visit_provenance(visit),
|
||||
MemPlaceMeta::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for ImmTy<'_, Provenance> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
(**self).visit_provenance(visit)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for MPlaceTy<'_, Provenance> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
self.ptr().visit_provenance(visit);
|
||||
self.meta().visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for PlaceTy<'_, Provenance> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
match self.as_mplace_or_local() {
|
||||
Either::Left(mplace) => mplace.visit_provenance(visit),
|
||||
Either::Right(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for OpTy<'_, Provenance> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
match self.as_mplace_or_imm() {
|
||||
Either::Left(mplace) => mplace.visit_provenance(visit),
|
||||
Either::Right(imm) => imm.visit_provenance(visit),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for Allocation<Provenance, AllocExtra<'_>> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
for prov in self.provenance().provenances() {
|
||||
prov.visit_provenance(visit);
|
||||
}
|
||||
|
||||
self.extra.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitProvenance for crate::MiriInterpCx<'_, '_> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
// Visit the contents of the allocations and the IDs themselves, to account for all
|
||||
// live allocation IDs and all provenance in the allocation bytes, even if they are leaked.
|
||||
// We do *not* visit all the `AllocId` of the live allocations; we tried that and adding
|
||||
// them all to the live set is too expensive. Instead we later do liveness check by
|
||||
// checking both "is this alloc id live" and "is it mentioned anywhere else in
|
||||
// the interpreter state".
|
||||
self.memory.alloc_map().iter(|it| {
|
||||
for (_id, (_kind, alloc)) in it {
|
||||
alloc.visit_provenance(visit);
|
||||
}
|
||||
});
|
||||
// And all the other machine values.
|
||||
self.machine.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LiveAllocs<'a, 'mir, 'tcx> {
|
||||
collected: FxHashSet<AllocId>,
|
||||
ecx: &'a MiriInterpCx<'mir, 'tcx>,
|
||||
}
|
||||
|
||||
impl LiveAllocs<'_, '_, '_> {
|
||||
pub fn is_live(&self, id: AllocId) -> bool {
|
||||
self.collected.contains(&id) ||
|
||||
self.ecx.is_alloc_live(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
|
||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
|
||||
fn run_provenance_gc(&mut self) {
|
||||
|
||||
// We collect all tags from various parts of the interpreter, but also
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
let mut tags = FxHashSet::default();
|
||||
let mut alloc_ids = FxHashSet::default();
|
||||
this.visit_provenance(&mut |id, tag| {
|
||||
if let Some(id) = id {
|
||||
alloc_ids.insert(id);
|
||||
}
|
||||
if let Some(tag) = tag {
|
||||
tags.insert(tag);
|
||||
}
|
||||
});
|
||||
self.remove_unreachable_tags(tags);
|
||||
self.remove_unreachable_allocs(alloc_ids);
|
||||
}
|
||||
|
||||
fn remove_unreachable_tags(&mut self, tags: FxHashSet<BorTag>) {
|
||||
let this = self.eval_context_mut();
|
||||
this.memory.alloc_map().iter(|it| {
|
||||
for (_id, (_kind, alloc)) in it {
|
||||
if let Some(bt) = &alloc.extra.borrow_tracker {
|
||||
bt.remove_unreachable_tags(&tags);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn remove_unreachable_allocs(&mut self, allocs: FxHashSet<AllocId>) {
|
||||
let this = self.eval_context_ref();
|
||||
let allocs = LiveAllocs {
|
||||
ecx: this,
|
||||
collected: allocs,
|
||||
};
|
||||
this.machine.allocation_spans.borrow_mut().retain(|id, _| allocs.is_live(*id));
|
||||
this.machine.intptrcast.borrow_mut().remove_unreachable_allocs(&allocs);
|
||||
if let Some(borrow_tracker) = &this.machine.borrow_tracker {
|
||||
borrow_tracker.borrow_mut().remove_unreachable_allocs(&allocs);
|
||||
}
|
||||
}
|
||||
}
|
@ -37,13 +37,13 @@ pub struct EnvVars<'tcx> {
|
||||
pub(crate) environ: Option<MPlaceTy<'tcx, Provenance>>,
|
||||
}
|
||||
|
||||
impl VisitTags for EnvVars<'_> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for EnvVars<'_> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let EnvVars { map, environ } = self;
|
||||
|
||||
environ.visit_tags(visit);
|
||||
environ.visit_provenance(visit);
|
||||
for ptr in map.values() {
|
||||
ptr.visit_tags(visit);
|
||||
ptr.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -459,6 +459,10 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
// shim, add it to the corresponding submodule.
|
||||
match link_name.as_str() {
|
||||
// Miri-specific extern functions
|
||||
"miri_run_provenance_gc" => {
|
||||
let [] = this.check_shim(abi, Abi::Rust, link_name, args)?;
|
||||
this.run_provenance_gc();
|
||||
}
|
||||
"miri_get_alloc_id" => {
|
||||
let [ptr] = this.check_shim(abi, Abi::Rust, link_name, args)?;
|
||||
let ptr = this.read_pointer(ptr)?;
|
||||
|
@ -35,12 +35,12 @@ pub struct CatchUnwindData<'tcx> {
|
||||
ret: mir::BasicBlock,
|
||||
}
|
||||
|
||||
impl VisitTags for CatchUnwindData<'_> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for CatchUnwindData<'_> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let CatchUnwindData { catch_fn, data, dest, ret: _ } = self;
|
||||
catch_fn.visit_tags(visit);
|
||||
data.visit_tags(visit);
|
||||
dest.visit_tags(visit);
|
||||
catch_fn.visit_provenance(visit);
|
||||
data.visit_provenance(visit);
|
||||
dest.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,8 +274,8 @@ struct UnblockCallback {
|
||||
thread_to_unblock: ThreadId,
|
||||
}
|
||||
|
||||
impl VisitTags for UnblockCallback {
|
||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {}
|
||||
impl VisitProvenance for UnblockCallback {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx: 'mir> MachineCallback<'mir, 'tcx> for UnblockCallback {
|
||||
|
@ -207,15 +207,15 @@ impl<'tcx> TlsData<'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for TlsData<'_> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for TlsData<'_> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let TlsData { keys, macos_thread_dtors, next_key: _ } = self;
|
||||
|
||||
for scalar in keys.values().flat_map(|v| v.data.values()) {
|
||||
scalar.visit_tags(visit);
|
||||
scalar.visit_provenance(visit);
|
||||
}
|
||||
for (_, scalar) in macos_thread_dtors.values() {
|
||||
scalar.visit_tags(visit);
|
||||
scalar.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -288,8 +288,8 @@ pub struct FileHandler {
|
||||
pub handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
|
||||
}
|
||||
|
||||
impl VisitTags for FileHandler {
|
||||
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for FileHandler {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
// All our FileDescriptor do not have any tags.
|
||||
}
|
||||
}
|
||||
@ -490,12 +490,12 @@ impl Default for DirHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for DirHandler {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl VisitProvenance for DirHandler {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let DirHandler { streams, next_id: _ } = self;
|
||||
|
||||
for dir in streams.values() {
|
||||
dir.entry.visit_tags(visit);
|
||||
dir.entry.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,10 +182,10 @@ pub fn futex<'tcx>(
|
||||
dest: PlaceTy<'tcx, Provenance>,
|
||||
}
|
||||
|
||||
impl<'tcx> VisitTags for Callback<'tcx> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl<'tcx> VisitProvenance for Callback<'tcx> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let Callback { thread: _, addr_usize: _, dest } = self;
|
||||
dest.visit_tags(visit);
|
||||
dest.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -886,10 +886,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
dest: PlaceTy<'tcx, Provenance>,
|
||||
}
|
||||
|
||||
impl<'tcx> VisitTags for Callback<'tcx> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl<'tcx> VisitProvenance for Callback<'tcx> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let Callback { active_thread: _, mutex_id: _, id: _, dest } = self;
|
||||
dest.visit_tags(visit);
|
||||
dest.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,10 +204,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
pending_place: PlaceTy<'tcx, Provenance>,
|
||||
}
|
||||
|
||||
impl<'tcx> VisitTags for Callback<'tcx> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl<'tcx> VisitProvenance for Callback<'tcx> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let Callback { init_once_id: _, pending_place } = self;
|
||||
pending_place.visit_tags(visit);
|
||||
pending_place.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
@ -337,10 +337,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
dest: PlaceTy<'tcx, Provenance>,
|
||||
}
|
||||
|
||||
impl<'tcx> VisitTags for Callback<'tcx> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl<'tcx> VisitProvenance for Callback<'tcx> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let Callback { thread: _, addr: _, dest } = self;
|
||||
dest.visit_tags(visit);
|
||||
dest.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
@ -441,10 +441,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
dest: PlaceTy<'tcx, Provenance>,
|
||||
}
|
||||
|
||||
impl<'tcx> VisitTags for Callback<'tcx> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
impl<'tcx> VisitProvenance for Callback<'tcx> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let Callback { thread: _, condvar_id: _, lock_id: _, mode: _, dest } = self;
|
||||
dest.visit_tags(visit);
|
||||
dest.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,169 +0,0 @@
|
||||
use either::Either;
|
||||
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub trait VisitTags {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag));
|
||||
}
|
||||
|
||||
impl<T: VisitTags> VisitTags for Option<T> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
if let Some(x) = self {
|
||||
x.visit_tags(visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: VisitTags> VisitTags for std::cell::RefCell<T> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
self.borrow().visit_tags(visit)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for BorTag {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
visit(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for Provenance {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
if let Provenance::Concrete { tag, .. } = self {
|
||||
visit(*tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for Pointer<Provenance> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
let (prov, _offset) = self.into_parts();
|
||||
prov.visit_tags(visit);
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for Pointer<Option<Provenance>> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
let (prov, _offset) = self.into_parts();
|
||||
prov.visit_tags(visit);
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for Scalar<Provenance> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
match self {
|
||||
Scalar::Ptr(ptr, _) => ptr.visit_tags(visit),
|
||||
Scalar::Int(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for Immediate<Provenance> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
match self {
|
||||
Immediate::Scalar(s) => {
|
||||
s.visit_tags(visit);
|
||||
}
|
||||
Immediate::ScalarPair(s1, s2) => {
|
||||
s1.visit_tags(visit);
|
||||
s2.visit_tags(visit);
|
||||
}
|
||||
Immediate::Uninit => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for MemPlaceMeta<Provenance> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
match self {
|
||||
MemPlaceMeta::Meta(m) => m.visit_tags(visit),
|
||||
MemPlaceMeta::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for ImmTy<'_, Provenance> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
(**self).visit_tags(visit)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for MPlaceTy<'_, Provenance> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
self.ptr().visit_tags(visit);
|
||||
self.meta().visit_tags(visit);
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for PlaceTy<'_, Provenance> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
match self.as_mplace_or_local() {
|
||||
Either::Left(mplace) => mplace.visit_tags(visit),
|
||||
Either::Right(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for OpTy<'_, Provenance> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
match self.as_mplace_or_imm() {
|
||||
Either::Left(mplace) => mplace.visit_tags(visit),
|
||||
Either::Right(imm) => imm.visit_tags(visit),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for Allocation<Provenance, AllocExtra<'_>> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
for prov in self.provenance().provenances() {
|
||||
prov.visit_tags(visit);
|
||||
}
|
||||
|
||||
self.extra.visit_tags(visit);
|
||||
}
|
||||
}
|
||||
|
||||
impl VisitTags for crate::MiriInterpCx<'_, '_> {
|
||||
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
|
||||
// Memory.
|
||||
self.memory.alloc_map().iter(|it| {
|
||||
for (_id, (_kind, alloc)) in it {
|
||||
alloc.visit_tags(visit);
|
||||
}
|
||||
});
|
||||
|
||||
// And all the other machine values.
|
||||
self.machine.visit_tags(visit);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
|
||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
|
||||
fn garbage_collect_tags(&mut self) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
// No reason to do anything at all if stacked borrows is off.
|
||||
if this.machine.borrow_tracker.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut tags = FxHashSet::default();
|
||||
this.visit_tags(&mut |tag| {
|
||||
tags.insert(tag);
|
||||
});
|
||||
self.remove_unreachable_tags(tags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_unreachable_tags(&mut self, tags: FxHashSet<BorTag>) {
|
||||
let this = self.eval_context_mut();
|
||||
this.memory.alloc_map().iter(|it| {
|
||||
for (_id, (_kind, alloc)) in it {
|
||||
if let Some(bt) = &alloc.extra.borrow_tracker {
|
||||
bt.remove_unreachable_tags(&tags);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||
|
||||
// Check how a Reserved with interior mutability
|
||||
// responds to a Foreign Write under a Protector
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||
|
||||
#[path = "../../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
|
21
src/tools/miri/tests/pass-dep/extra_fn_ptr_gc.rs
Normal file
21
src/tools/miri/tests/pass-dep/extra_fn_ptr_gc.rs
Normal file
@ -0,0 +1,21 @@
|
||||
//@ignore-target-windows: No libc on Windows
|
||||
//@compile-flags: -Zmiri-permissive-provenance
|
||||
|
||||
#[path = "../utils/mod.rs"]
|
||||
mod utils;
|
||||
|
||||
type GetEntropyFn = unsafe extern "C" fn(*mut u8, libc::size_t) -> libc::c_int;
|
||||
|
||||
fn main() {
|
||||
let name = "getentropy\0";
|
||||
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize };
|
||||
// If the GC does not account for the extra_fn_ptr entry that this dlsym just added, this GC
|
||||
// run will delete our entry for the base addr of the function pointer we will transmute to,
|
||||
// and the call through the function pointer will report UB.
|
||||
utils::run_provenance_gc();
|
||||
|
||||
let ptr = addr as *mut libc::c_void;
|
||||
let func: GetEntropyFn = unsafe { std::mem::transmute(ptr) };
|
||||
let dest = &mut [0u8];
|
||||
unsafe { func(dest.as_mut_ptr(), dest.len()) };
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
//@compile-flags: -Zmiri-ignore-leaks -Zmiri-disable-stacked-borrows
|
||||
//@compile-flags: -Zmiri-ignore-leaks -Zmiri-disable-stacked-borrows -Zmiri-provenance-gc=10000
|
||||
// This test's runtime explodes if the GC interval is set to 1 (which we do in CI), so we
|
||||
// override it internally back to the default frequency.
|
||||
|
||||
// The following tests check whether our weak memory emulation produces
|
||||
// any inconsistent execution outcomes
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||
#[path = "../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||
|
||||
// Check that a protector goes back to normal behavior when the function
|
||||
// returns.
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
|
@ -1,4 +1,4 @@
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||
|
||||
#[path = "../../utils/mod.rs"]
|
||||
#[macro_use]
|
||||
|
@ -1,5 +1,5 @@
|
||||
//@revisions: default uniq
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||
//@[uniq]compile-flags: -Zmiri-unique-is-unique
|
||||
|
||||
#![feature(ptr_internals)]
|
||||
|
@ -1,5 +1,5 @@
|
||||
//@revisions: default uniq
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0
|
||||
//@compile-flags: -Zmiri-tree-borrows -Zmiri-provenance-gc=0
|
||||
//@[uniq]compile-flags: -Zmiri-unique-is-unique
|
||||
|
||||
#![feature(vec_into_raw_parts)]
|
||||
|
@ -84,7 +84,7 @@ extern "Rust" {
|
||||
///
|
||||
/// The format of what this emits is unstable and may change at any time. In particular, users should be
|
||||
/// aware that Miri will periodically attempt to garbage collect the contents of all stacks. Callers of
|
||||
/// this function may wish to pass `-Zmiri-tag-gc=0` to disable the GC.
|
||||
/// this function may wish to pass `-Zmiri-provenance-gc=0` to disable the GC.
|
||||
///
|
||||
/// This function is extremely unstable. At any time the format of its output may change, its signature may
|
||||
/// change, or it may be removed entirely.
|
||||
@ -137,4 +137,9 @@ extern "Rust" {
|
||||
out: *mut std::ffi::c_char,
|
||||
out_size: usize,
|
||||
) -> usize;
|
||||
|
||||
/// Run the provenance GC. The GC will run automatically at some cadence,
|
||||
/// but in tests we want to for sure run it at certain points to check
|
||||
/// that it doesn't break anything.
|
||||
pub fn miri_run_provenance_gc();
|
||||
}
|
||||
|
@ -9,3 +9,10 @@ mod miri_extern;
|
||||
|
||||
pub use fs::*;
|
||||
pub use miri_extern::*;
|
||||
|
||||
pub fn run_provenance_gc() {
|
||||
// SAFETY: No preconditions. The GC is fine to run at any time.
|
||||
unsafe {
|
||||
miri_run_provenance_gc()
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +122,31 @@ function checkNeededFields(fullPath, expected, error_text, queryName, position)
|
||||
}
|
||||
|
||||
function valueCheck(fullPath, expected, result, error_text, queryName) {
|
||||
if (Array.isArray(expected)) {
|
||||
if (Array.isArray(expected) && result instanceof Map) {
|
||||
const expected_set = new Set();
|
||||
for (const [key, expected_value] of expected) {
|
||||
expected_set.add(key);
|
||||
checkNeededFields(fullPath, expected_value, error_text, queryName, key);
|
||||
if (result.has(key)) {
|
||||
valueCheck(
|
||||
fullPath + "[" + key + "]",
|
||||
expected_value,
|
||||
result.get(key),
|
||||
error_text,
|
||||
queryName
|
||||
);
|
||||
} else {
|
||||
error_text.push(`${queryName}==> EXPECTED has extra key in map from field ` +
|
||||
`\`${fullPath}\` (key ${key}): \`${JSON.stringify(expected_value)}\``);
|
||||
}
|
||||
}
|
||||
for (const [key, result_value] of result.entries()) {
|
||||
if (!expected_set.has(key)) {
|
||||
error_text.push(`${queryName}==> EXPECTED missing key in map from field ` +
|
||||
`\`${fullPath}\` (key ${key}): \`${JSON.stringify(result_value)}\``);
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(expected)) {
|
||||
let i;
|
||||
for (i = 0; i < expected.length; ++i) {
|
||||
checkNeededFields(fullPath, expected[i], error_text, queryName, i);
|
||||
@ -153,6 +177,9 @@ function valueCheck(fullPath, expected, result, error_text, queryName) {
|
||||
}
|
||||
let result_v = result[key];
|
||||
if (result_v !== null && key === "error") {
|
||||
if (!result_v.forEach) {
|
||||
throw result_v;
|
||||
}
|
||||
result_v.forEach((value, index) => {
|
||||
value = value.split(" ").join(" ");
|
||||
if (index % 2 === 1) {
|
||||
|
@ -161,7 +161,7 @@
|
||||
// [r77] needs-llvm-components: x86
|
||||
// [r78] compile-flags:--target x86_64-linux-android
|
||||
// [r78] needs-llvm-components: x86
|
||||
// [r79] compile-flags:--target x86_64-sun-solaris
|
||||
// [r79] compile-flags:--target x86_64-pc-solaris
|
||||
// [r79] needs-llvm-components: x86
|
||||
// [r80] compile-flags:--target x86_64-unknown-freebsd
|
||||
// [r80] needs-llvm-components: x86
|
||||
|
@ -80,7 +80,7 @@ set-window-size: (851, 600)
|
||||
|
||||
// Check the size and count in tabs
|
||||
assert-text: ("#search-tabs > button:nth-child(1) > .count", " (25) ")
|
||||
assert-text: ("#search-tabs > button:nth-child(2) > .count", " (5) ")
|
||||
assert-text: ("#search-tabs > button:nth-child(2) > .count", " (6) ")
|
||||
assert-text: ("#search-tabs > button:nth-child(3) > .count", " (0) ")
|
||||
store-property: ("#search-tabs > button:nth-child(1)", {"offsetWidth": buttonWidth})
|
||||
assert-property: ("#search-tabs > button:nth-child(2)", {"offsetWidth": |buttonWidth|})
|
||||
|
29
tests/rustdoc-js-std/iterator-type-signatures.js
Normal file
29
tests/rustdoc-js-std/iterator-type-signatures.js
Normal file
@ -0,0 +1,29 @@
|
||||
// ignore-order
|
||||
|
||||
const FILTER_CRATE = "std";
|
||||
|
||||
const EXPECTED = [
|
||||
{
|
||||
'query': 'iterator<t> -> option<t>',
|
||||
'others': [
|
||||
{ 'path': 'std::iter::Iterator', 'name': 'max' },
|
||||
{ 'path': 'std::iter::Iterator', 'name': 'min' },
|
||||
{ 'path': 'std::iter::Iterator', 'name': 'last' },
|
||||
{ 'path': 'std::iter::Iterator', 'name': 'next' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'iterator<t>, usize -> option<t>',
|
||||
'others': [
|
||||
{ 'path': 'std::iter::Iterator', 'name': 'nth' },
|
||||
],
|
||||
},
|
||||
{
|
||||
// Something should be done so that intoiterator is considered a match
|
||||
// for plain iterator.
|
||||
'query': 'iterator<t>, intoiterator<t> -> ordering',
|
||||
'others': [
|
||||
{ 'path': 'std::iter::Iterator', 'name': 'cmp' },
|
||||
],
|
||||
},
|
||||
];
|
245
tests/rustdoc-js-std/parser-bindings.js
Normal file
245
tests/rustdoc-js-std/parser-bindings.js
Normal file
@ -0,0 +1,245 @@
|
||||
const PARSED = [
|
||||
{
|
||||
query: 'A<B=C>',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
'b',
|
||||
[
|
||||
{
|
||||
name: "c",
|
||||
fullPath: ["c"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "c",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
]
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
foundElems: 1,
|
||||
original: 'A<B=C>',
|
||||
returned: [],
|
||||
userQuery: 'a<b=c>',
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: 'A<B = C>',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
'b',
|
||||
[{
|
||||
name: "c",
|
||||
fullPath: ["c"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "c",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
}]
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
foundElems: 1,
|
||||
original: 'A<B = C>',
|
||||
returned: [],
|
||||
userQuery: 'a<b = c>',
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: 'A<B=!>',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
'b',
|
||||
[{
|
||||
name: "never",
|
||||
fullPath: ["never"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "never",
|
||||
generics: [],
|
||||
typeFilter: 15,
|
||||
}]
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
foundElems: 1,
|
||||
original: 'A<B=!>',
|
||||
returned: [],
|
||||
userQuery: 'a<b=!>',
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: 'A<B=[]>',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
'b',
|
||||
[{
|
||||
name: "[]",
|
||||
fullPath: ["[]"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "[]",
|
||||
generics: [],
|
||||
typeFilter: 15,
|
||||
}]
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
foundElems: 1,
|
||||
original: 'A<B=[]>',
|
||||
returned: [],
|
||||
userQuery: 'a<b=[]>',
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: 'A<B=[!]>',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
'b',
|
||||
[{
|
||||
name: "[]",
|
||||
fullPath: ["[]"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "[]",
|
||||
generics: [
|
||||
{
|
||||
name: "never",
|
||||
fullPath: ["never"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "never",
|
||||
generics: [],
|
||||
typeFilter: 15,
|
||||
},
|
||||
],
|
||||
typeFilter: 15,
|
||||
}]
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
foundElems: 1,
|
||||
original: 'A<B=[!]>',
|
||||
returned: [],
|
||||
userQuery: 'a<b=[!]>',
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
query: 'A<B=C=>',
|
||||
elems: [],
|
||||
foundElems: 0,
|
||||
original: 'A<B=C=>',
|
||||
returned: [],
|
||||
userQuery: 'a<b=c=>',
|
||||
error: "Cannot write `=` twice in a binding",
|
||||
},
|
||||
{
|
||||
query: 'A<B=>',
|
||||
elems: [],
|
||||
foundElems: 0,
|
||||
original: 'A<B=>',
|
||||
returned: [],
|
||||
userQuery: 'a<b=>',
|
||||
error: "Unexpected `>` after `=`",
|
||||
},
|
||||
{
|
||||
query: 'B=C',
|
||||
elems: [],
|
||||
foundElems: 0,
|
||||
original: 'B=C',
|
||||
returned: [],
|
||||
userQuery: 'b=c',
|
||||
error: "Type parameter `=` must be within generics list",
|
||||
},
|
||||
{
|
||||
query: '[B=C]',
|
||||
elems: [],
|
||||
foundElems: 0,
|
||||
original: '[B=C]',
|
||||
returned: [],
|
||||
userQuery: '[b=c]',
|
||||
error: "Type parameter `=` cannot be within slice `[]`",
|
||||
},
|
||||
{
|
||||
query: 'A<B<X>=C>',
|
||||
elems: [
|
||||
{
|
||||
name: "a",
|
||||
fullPath: ["a"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "a",
|
||||
generics: [],
|
||||
bindings: [
|
||||
[
|
||||
'b',
|
||||
[
|
||||
{
|
||||
name: "c",
|
||||
fullPath: ["c"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "c",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
{
|
||||
name: "x",
|
||||
fullPath: ["x"],
|
||||
pathWithoutLast: [],
|
||||
pathLast: "x",
|
||||
generics: [],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
typeFilter: -1,
|
||||
},
|
||||
],
|
||||
foundElems: 1,
|
||||
original: 'A<B<X>=C>',
|
||||
returned: [],
|
||||
userQuery: 'a<b<x>=c>',
|
||||
error: null,
|
||||
},
|
||||
];
|
@ -303,7 +303,7 @@ const PARSED = [
|
||||
original: '->a<>b',
|
||||
returned: [],
|
||||
userQuery: '->a<>b',
|
||||
error: 'Expected `,` after `>`, found `b`',
|
||||
error: 'Expected `,` or `=` after `>`, found `b`',
|
||||
},
|
||||
{
|
||||
query: "a<->",
|
||||
|
163
tests/rustdoc-js/assoc-type-backtrack.js
Normal file
163
tests/rustdoc-js/assoc-type-backtrack.js
Normal file
@ -0,0 +1,163 @@
|
||||
// exact-check
|
||||
|
||||
const EXPECTED = [
|
||||
{
|
||||
'query': 'mytrait, mytrait2 -> T',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' },
|
||||
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<U>, mytrait2 -> T',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' },
|
||||
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<Item=U>, mytrait2 -> T',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' },
|
||||
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<T>, mytrait2 -> T',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<Item=T>, mytrait2 -> T',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<T> -> Option<T>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'next' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<Item=T> -> Option<T>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'next' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<U> -> Option<T>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'mytrait<Item=U> -> Option<T>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' },
|
||||
],
|
||||
},
|
||||
// The first two define the base case.
|
||||
{
|
||||
'query': 'myintofuture<fut=myfuture<t>> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
// Unboxings of the one-argument case.
|
||||
{
|
||||
'query': 'myfuture<t> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<myfuture<t>> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
// Invalid unboxing of the one-argument case.
|
||||
// If you unbox one of the myfutures, you need to unbox both of them.
|
||||
{
|
||||
'query': 'myintofuture<fut=t> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
// Unboxings of the two-argument case.
|
||||
{
|
||||
'query': 'myintofuture<fut=t>, myintofuture<fut=t> -> t',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<fut=myfuture>, myintofuture<fut=myfuture> -> myfuture',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<myfuture>, myintofuture<myfuture> -> myfuture',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'myfuture<t>, myfuture<t> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
|
||||
],
|
||||
},
|
||||
// Invalid unboxings of the two-argument case.
|
||||
// If you unbox one of the myfutures, you need to unbox all of them.
|
||||
{
|
||||
'query': 'myintofuture<fut=t>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=t> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=myfuture<t>> -> t',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
// different generics don't match up either
|
||||
{
|
||||
'query': 'myintofuture<fut=myfuture<u>>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
{
|
||||
'query': 'myintofuture<output=t> -> myfuture<tt>',
|
||||
'correction': null,
|
||||
'others': [],
|
||||
},
|
||||
];
|
38
tests/rustdoc-js/assoc-type-backtrack.rs
Normal file
38
tests/rustdoc-js/assoc-type-backtrack.rs
Normal file
@ -0,0 +1,38 @@
|
||||
pub trait MyTrait2<X> {
|
||||
type Output;
|
||||
}
|
||||
|
||||
pub trait MyTrait {
|
||||
type Item;
|
||||
fn next(&mut self) -> Option<Self::Item>;
|
||||
fn fold<B, F>(self, init: B, f: F) -> B where
|
||||
Self: Sized,
|
||||
F: MyTrait2<(B, Self::Item), Output=B>;
|
||||
}
|
||||
|
||||
pub struct Cloned<I>(I);
|
||||
|
||||
impl<'a, T, I> MyTrait for Cloned<I> where
|
||||
T: 'a + Clone,
|
||||
I: MyTrait<Item = &'a T>
|
||||
{
|
||||
type Item = T;
|
||||
fn next(&mut self) -> Option<Self::Item> { loop {} }
|
||||
fn fold<B, F>(self, init: B, f: F) -> B where
|
||||
Self: Sized,
|
||||
F: MyTrait2<(B, Self::Item), Output=B>
|
||||
{
|
||||
loop {}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MyFuture {
|
||||
type Output;
|
||||
}
|
||||
|
||||
pub trait MyIntoFuture {
|
||||
type Output;
|
||||
type Fut: MyFuture<Output=Self::Output>;
|
||||
fn into_future(self) -> Self::Fut;
|
||||
fn into_future_2(self, other: Self) -> Self::Fut;
|
||||
}
|
45
tests/rustdoc-js/assoc-type.js
Normal file
45
tests/rustdoc-js/assoc-type.js
Normal file
@ -0,0 +1,45 @@
|
||||
// exact-check
|
||||
|
||||
const EXPECTED = [
|
||||
// if I just use generics, then the generics version
|
||||
// and the type binding version both show up
|
||||
{
|
||||
'query': 'iterator<something> -> u32',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type', 'name': 'my_fn' },
|
||||
{ 'path': 'assoc_type::my', 'name': 'other_fn' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'iterator<something>',
|
||||
'correction': null,
|
||||
'in_args': [
|
||||
{ 'path': 'assoc_type', 'name': 'my_fn' },
|
||||
{ 'path': 'assoc_type::my', 'name': 'other_fn' },
|
||||
],
|
||||
},
|
||||
// if I write an explicit binding, only it shows up
|
||||
{
|
||||
'query': 'iterator<item=something> -> u32',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type', 'name': 'my_fn' },
|
||||
],
|
||||
},
|
||||
// case insensitivity
|
||||
{
|
||||
'query': 'iterator<ItEm=sOmEtHiNg> -> u32',
|
||||
'correction': null,
|
||||
'others': [
|
||||
{ 'path': 'assoc_type', 'name': 'my_fn' },
|
||||
],
|
||||
},
|
||||
// wrong binding name, no result
|
||||
{
|
||||
'query': 'iterator<something=something> -> u32',
|
||||
'correction': null,
|
||||
'in_args': [],
|
||||
'others': [],
|
||||
},
|
||||
];
|
12
tests/rustdoc-js/assoc-type.rs
Normal file
12
tests/rustdoc-js/assoc-type.rs
Normal file
@ -0,0 +1,12 @@
|
||||
pub fn my_fn<X: Iterator<Item = Something>>(_x: X) -> u32 {
|
||||
3
|
||||
}
|
||||
|
||||
pub struct Something;
|
||||
|
||||
pub mod my {
|
||||
pub trait Iterator<T> {}
|
||||
pub fn other_fn<X: Iterator<crate::Something>>(_: X) -> u32 {
|
||||
3
|
||||
}
|
||||
}
|
57
tests/rustdoc-js/gat.js
Normal file
57
tests/rustdoc-js/gat.js
Normal file
@ -0,0 +1,57 @@
|
||||
// exact-check
|
||||
|
||||
const EXPECTED = [
|
||||
{
|
||||
'query': 'foo<assoc<u8>=u8> -> u32',
|
||||
'correction': null,
|
||||
'in_args': [],
|
||||
'others': [
|
||||
{ 'path': 'gat', 'name': 'sample' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'foo<assoc<u8>=u8> -> !',
|
||||
'correction': null,
|
||||
'in_args': [],
|
||||
'others': [
|
||||
{ 'path': 'gat', 'name': 'synergy' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'foo<assoc<u8>=u8>',
|
||||
'correction': null,
|
||||
'in_args': [
|
||||
{ 'path': 'gat', 'name': 'sample' },
|
||||
{ 'path': 'gat', 'name': 'synergy' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'foo<assoc<u8>=u32>',
|
||||
'correction': null,
|
||||
'in_args': [
|
||||
{ 'path': 'gat', 'name': 'consider' },
|
||||
],
|
||||
},
|
||||
{
|
||||
// This one is arguably a bug, because the way rustdoc
|
||||
// stores GATs in the search index is sloppy, but it's
|
||||
// precise enough to match most of the samples in the
|
||||
// GAT initiative repo
|
||||
'query': 'foo<assoc<u32>=u8>',
|
||||
'correction': null,
|
||||
'in_args': [
|
||||
{ 'path': 'gat', 'name': 'consider' },
|
||||
],
|
||||
},
|
||||
{
|
||||
// This one is arguably a bug, because the way rustdoc
|
||||
// stores GATs in the search index is sloppy, but it's
|
||||
// precise enough to match most of the samples in the
|
||||
// GAT initiative repo
|
||||
'query': 'foo<assoc<T>=T>',
|
||||
'correction': null,
|
||||
'in_args': [
|
||||
{ 'path': 'gat', 'name': 'integrate' },
|
||||
],
|
||||
},
|
||||
];
|
8
tests/rustdoc-js/gat.rs
Normal file
8
tests/rustdoc-js/gat.rs
Normal file
@ -0,0 +1,8 @@
|
||||
pub trait Foo {
|
||||
type Assoc<T>;
|
||||
}
|
||||
|
||||
pub fn sample<X: Foo<Assoc<u8> = u8>>(_: X) -> u32 { loop {} }
|
||||
pub fn synergy(_: impl Foo<Assoc<u8> = u8>) -> ! { loop {} }
|
||||
pub fn consider(_: impl Foo<Assoc<u8> = u32>) -> bool { loop {} }
|
||||
pub fn integrate<T>(_: impl Foo<Assoc<T> = T>) -> T { loop {} }
|
@ -43,4 +43,14 @@ const EXPECTED = [
|
||||
{ 'path': 'never_search', 'name': 'box_uninteresting' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'box<item=!>',
|
||||
'in_args': [],
|
||||
'returned': [],
|
||||
},
|
||||
{
|
||||
'query': 'box<item=never>',
|
||||
'in_args': [],
|
||||
'returned': [],
|
||||
},
|
||||
];
|
||||
|
12
tests/rustdoc-js/trait-methods.js
Normal file
12
tests/rustdoc-js/trait-methods.js
Normal file
@ -0,0 +1,12 @@
|
||||
// exact-check
|
||||
|
||||
const EXPECTED = [
|
||||
{
|
||||
'query': 'mytrait<t> -> option<t>',
|
||||
'correction': null,
|
||||
'in_args': [],
|
||||
'others': [
|
||||
{ 'path': 'trait_methods::MyTrait', 'name': 'next' },
|
||||
],
|
||||
},
|
||||
];
|
4
tests/rustdoc-js/trait-methods.rs
Normal file
4
tests/rustdoc-js/trait-methods.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub trait MyTrait {
|
||||
type Item;
|
||||
fn next(&mut self) -> Option<Self::Item>;
|
||||
}
|
16
tests/ui/borrowck/generic_const_early_param.rs
Normal file
16
tests/ui/borrowck/generic_const_early_param.rs
Normal file
@ -0,0 +1,16 @@
|
||||
#![feature(generic_const_exprs)]
|
||||
//~^ WARN the feature `generic_const_exprs` is incomplete
|
||||
|
||||
struct DataWrapper<'static> {
|
||||
//~^ ERROR invalid lifetime parameter name: `'static`
|
||||
data: &'a [u8; Self::SIZE],
|
||||
//~^ ERROR use of undeclared lifetime name `'a`
|
||||
//~^^ ERROR lifetime may not live long enough
|
||||
}
|
||||
|
||||
impl DataWrapper<'a> {
|
||||
//~^ ERROR undeclared lifetime
|
||||
const SIZE: usize = 14;
|
||||
}
|
||||
|
||||
fn main(){}
|
42
tests/ui/borrowck/generic_const_early_param.stderr
Normal file
42
tests/ui/borrowck/generic_const_early_param.stderr
Normal file
@ -0,0 +1,42 @@
|
||||
error[E0262]: invalid lifetime parameter name: `'static`
|
||||
--> $DIR/generic_const_early_param.rs:4:20
|
||||
|
|
||||
LL | struct DataWrapper<'static> {
|
||||
| ^^^^^^^ 'static is a reserved lifetime name
|
||||
|
||||
error[E0261]: use of undeclared lifetime name `'a`
|
||||
--> $DIR/generic_const_early_param.rs:6:12
|
||||
|
|
||||
LL | struct DataWrapper<'static> {
|
||||
| - help: consider introducing lifetime `'a` here: `'a,`
|
||||
LL |
|
||||
LL | data: &'a [u8; Self::SIZE],
|
||||
| ^^ undeclared lifetime
|
||||
|
||||
error[E0261]: use of undeclared lifetime name `'a`
|
||||
--> $DIR/generic_const_early_param.rs:11:18
|
||||
|
|
||||
LL | impl DataWrapper<'a> {
|
||||
| - ^^ undeclared lifetime
|
||||
| |
|
||||
| help: consider introducing lifetime `'a` here: `<'a>`
|
||||
|
||||
warning: the feature `generic_const_exprs` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/generic_const_early_param.rs:1:12
|
||||
|
|
||||
LL | #![feature(generic_const_exprs)]
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: see issue #76560 <https://github.com/rust-lang/rust/issues/76560> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
error: lifetime may not live long enough
|
||||
--> $DIR/generic_const_early_param.rs:6:20
|
||||
|
|
||||
LL | data: &'a [u8; Self::SIZE],
|
||||
| ^^^^^^^^^^ requires that `'_` must outlive `'static`
|
||||
|
||||
error: aborting due to 4 previous errors; 1 warning emitted
|
||||
|
||||
Some errors have detailed explanations: E0261, E0262.
|
||||
For more information about an error, try `rustc --explain E0261`.
|
@ -1,5 +1,5 @@
|
||||
warning: unexpected `cfg` condition name: `unknown_key`
|
||||
--> $DIR/exhaustive-names-values.rs:12:7
|
||||
--> $DIR/exhaustive-names-values.rs:11:7
|
||||
|
|
||||
LL | #[cfg(unknown_key = "value")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -8,7 +8,7 @@ LL | #[cfg(unknown_key = "value")]
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
||||
warning: unexpected `cfg` condition value: `value`
|
||||
--> $DIR/exhaustive-names-values.rs:16:7
|
||||
--> $DIR/exhaustive-names-values.rs:15:7
|
||||
|
|
||||
LL | #[cfg(test = "value")]
|
||||
| ^^^^----------
|
||||
@ -17,9 +17,5 @@ LL | #[cfg(test = "value")]
|
||||
|
|
||||
= note: no expected value for `test`
|
||||
|
||||
warning: unexpected `empty_cfg` as condition name
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected names
|
||||
|
||||
warning: 3 warnings emitted
|
||||
warning: 2 warnings emitted
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
warning: unexpected `cfg` condition name: `unknown_key`
|
||||
--> $DIR/exhaustive-names-values.rs:12:7
|
||||
--> $DIR/exhaustive-names-values.rs:11:7
|
||||
|
|
||||
LL | #[cfg(unknown_key = "value")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -8,7 +8,7 @@ LL | #[cfg(unknown_key = "value")]
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
||||
warning: unexpected `cfg` condition value: `value`
|
||||
--> $DIR/exhaustive-names-values.rs:16:7
|
||||
--> $DIR/exhaustive-names-values.rs:15:7
|
||||
|
|
||||
LL | #[cfg(test = "value")]
|
||||
| ^^^^----------
|
||||
@ -17,9 +17,5 @@ LL | #[cfg(test = "value")]
|
||||
|
|
||||
= note: no expected value for `test`
|
||||
|
||||
warning: unexpected `empty_names_values` as condition name
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected names
|
||||
|
||||
warning: 3 warnings emitted
|
||||
warning: 2 warnings emitted
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
warning: unexpected `cfg` condition name: `unknown_key`
|
||||
--> $DIR/exhaustive-names-values.rs:12:7
|
||||
--> $DIR/exhaustive-names-values.rs:11:7
|
||||
|
|
||||
LL | #[cfg(unknown_key = "value")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -8,7 +8,7 @@ LL | #[cfg(unknown_key = "value")]
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
||||
warning: unexpected `cfg` condition value: `value`
|
||||
--> $DIR/exhaustive-names-values.rs:16:7
|
||||
--> $DIR/exhaustive-names-values.rs:15:7
|
||||
|
|
||||
LL | #[cfg(test = "value")]
|
||||
| ^^^^----------
|
||||
@ -18,16 +18,12 @@ LL | #[cfg(test = "value")]
|
||||
= note: no expected value for `test`
|
||||
|
||||
warning: unexpected `cfg` condition value: `unk`
|
||||
--> $DIR/exhaustive-names-values.rs:20:7
|
||||
--> $DIR/exhaustive-names-values.rs:19:7
|
||||
|
|
||||
LL | #[cfg(feature = "unk")]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: expected values for `feature` are: `std`
|
||||
|
||||
warning: unexpected condition value `` for condition name `feature`
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected values
|
||||
|
||||
warning: 4 warnings emitted
|
||||
warning: 3 warnings emitted
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
warning: unexpected `cfg` condition name: `unknown_key`
|
||||
--> $DIR/exhaustive-names-values.rs:12:7
|
||||
--> $DIR/exhaustive-names-values.rs:11:7
|
||||
|
|
||||
LL | #[cfg(unknown_key = "value")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -8,7 +8,7 @@ LL | #[cfg(unknown_key = "value")]
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
||||
warning: unexpected `cfg` condition value: `value`
|
||||
--> $DIR/exhaustive-names-values.rs:16:7
|
||||
--> $DIR/exhaustive-names-values.rs:15:7
|
||||
|
|
||||
LL | #[cfg(test = "value")]
|
||||
| ^^^^----------
|
||||
@ -18,16 +18,12 @@ LL | #[cfg(test = "value")]
|
||||
= note: no expected value for `test`
|
||||
|
||||
warning: unexpected `cfg` condition value: `unk`
|
||||
--> $DIR/exhaustive-names-values.rs:20:7
|
||||
--> $DIR/exhaustive-names-values.rs:19:7
|
||||
|
|
||||
LL | #[cfg(feature = "unk")]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: expected values for `feature` are: `std`
|
||||
|
||||
warning: unexpected `full` as condition name
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected names
|
||||
|
||||
warning: 4 warnings emitted
|
||||
warning: 3 warnings emitted
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
// Check warning for unexpected cfg in the code and in the CLI
|
||||
// arguments (here the revision cfg).
|
||||
// Check warning for unexpected cfg in the code.
|
||||
//
|
||||
// check-pass
|
||||
// revisions: empty_names_values empty_cfg feature full
|
||||
|
@ -7,9 +7,5 @@ LL | #[cfg(unknown_key = "value")]
|
||||
= help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows`
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
||||
warning: unexpected `empty_names` as condition name
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected names
|
||||
|
||||
warning: 2 warnings emitted
|
||||
warning: 1 warning emitted
|
||||
|
||||
|
@ -7,9 +7,5 @@ LL | #[cfg(unknown_key = "value")]
|
||||
= help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows`
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
||||
warning: unexpected `exhaustive_names` as condition name
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected names
|
||||
|
||||
warning: 2 warnings emitted
|
||||
warning: 1 warning emitted
|
||||
|
||||
|
@ -9,9 +9,5 @@ LL | #[cfg(test = "value")]
|
||||
= note: no expected value for `test`
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
||||
warning: unexpected `empty_cfg` as condition name
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected names
|
||||
|
||||
warning: 2 warnings emitted
|
||||
warning: 1 warning emitted
|
||||
|
||||
|
@ -38,14 +38,6 @@ LL | #[cfg_attr(uu, test)]
|
||||
|
|
||||
= help: expected names are: `cfg`, `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `names_values`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows`
|
||||
|
||||
warning: unexpected condition value `bar` for condition name `feature`
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected values
|
||||
|
||||
warning: unexpected `unknown_name` as condition name
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected names
|
||||
|
||||
warning: unexpected `cfg` condition name: `widnows`
|
||||
--> $DIR/mix.rs:43:10
|
||||
|
|
||||
@ -188,5 +180,5 @@ LL | cfg!(all(feature = "zebra", feature = "zebra", feature = "zebra"));
|
||||
|
|
||||
= note: expected values for `feature` are: `foo`
|
||||
|
||||
warning: 28 warnings emitted
|
||||
warning: 26 warnings emitted
|
||||
|
||||
|
@ -38,14 +38,6 @@ LL | #[cfg_attr(uu, test)]
|
||||
|
|
||||
= help: expected names are: `cfg`, `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `names_values`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows`
|
||||
|
||||
warning: unexpected condition value `bar` for condition name `feature`
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected values
|
||||
|
||||
warning: unexpected `unknown_name` as condition name
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected names
|
||||
|
||||
warning: unexpected `cfg` condition name: `widnows`
|
||||
--> $DIR/mix.rs:43:10
|
||||
|
|
||||
@ -188,5 +180,5 @@ LL | cfg!(all(feature = "zebra", feature = "zebra", feature = "zebra"));
|
||||
|
|
||||
= note: expected values for `feature` are: `foo`
|
||||
|
||||
warning: 28 warnings emitted
|
||||
warning: 26 warnings emitted
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
warning: unexpected `cfg` condition value: `sedre`
|
||||
--> $DIR/unexpected-cfg-value.rs:11:7
|
||||
--> $DIR/unexpected-cfg-value.rs:9:7
|
||||
|
|
||||
LL | #[cfg(feature = "sedre")]
|
||||
| ^^^^^^^^^^-------
|
||||
@ -10,16 +10,12 @@ LL | #[cfg(feature = "sedre")]
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
||||
warning: unexpected `cfg` condition value: `rand`
|
||||
--> $DIR/unexpected-cfg-value.rs:18:7
|
||||
--> $DIR/unexpected-cfg-value.rs:16:7
|
||||
|
|
||||
LL | #[cfg(feature = "rand")]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: expected values for `feature` are: `full`, `serde`
|
||||
|
||||
warning: unexpected condition value `rand` for condition name `feature`
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected values
|
||||
|
||||
warning: 3 warnings emitted
|
||||
warning: 2 warnings emitted
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
// Check warning for invalid configuration value in the code and
|
||||
// in the cli
|
||||
// Check for unexpected configuration value in the code.
|
||||
//
|
||||
// check-pass
|
||||
// revisions: values cfg
|
||||
// compile-flags: --cfg=feature="rand" -Z unstable-options
|
||||
// compile-flags: --check-cfg=cfg(values,cfg)
|
||||
// compile-flags: -Z unstable-options
|
||||
// [values]compile-flags: --check-cfg=values(feature,"serde","full")
|
||||
// [cfg]compile-flags: --check-cfg=cfg(feature,values("serde","full"))
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
warning: unexpected `cfg` condition value: `sedre`
|
||||
--> $DIR/unexpected-cfg-value.rs:11:7
|
||||
--> $DIR/unexpected-cfg-value.rs:9:7
|
||||
|
|
||||
LL | #[cfg(feature = "sedre")]
|
||||
| ^^^^^^^^^^-------
|
||||
@ -10,16 +10,12 @@ LL | #[cfg(feature = "sedre")]
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
||||
warning: unexpected `cfg` condition value: `rand`
|
||||
--> $DIR/unexpected-cfg-value.rs:18:7
|
||||
--> $DIR/unexpected-cfg-value.rs:16:7
|
||||
|
|
||||
LL | #[cfg(feature = "rand")]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: expected values for `feature` are: `full`, `serde`
|
||||
|
||||
warning: unexpected condition value `rand` for condition name `feature`
|
||||
|
|
||||
= help: was set with `--cfg` but isn't in the `--check-cfg` expected values
|
||||
|
||||
warning: 3 warnings emitted
|
||||
warning: 2 warnings emitted
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user