From f5023e4c76e6af7720fe3e75236d519140190d19 Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 2 Nov 2023 17:05:28 +0100 Subject: [PATCH 01/12] Remove --check-cfg checking of --cfg args --- compiler/rustc_lint/messages.ftl | 6 ----- compiler/rustc_lint/src/builtin.rs | 25 ------------------- compiler/rustc_lint/src/lib.rs | 1 - compiler/rustc_lint/src/lints.rs | 15 ----------- compiler/rustc_lint_defs/src/builtin.rs | 1 + .../src/compiler-flags/check-cfg.md | 3 +++ 6 files changed, 4 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 068f1372c0ecd..cb2e373c6f901 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -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` instead, and only call `assume_init` after initialization is done diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index e3dd8faeedb3a..b434659f0d5c7 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -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 */ } - } - } - } -} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 606e18866165c..0db30cd8a3d48 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -179,7 +179,6 @@ early_lint_methods!( IncompleteInternalFeatures: IncompleteInternalFeatures, RedundantSemicolons: RedundantSemicolons, UnusedDocComment: UnusedDocComment, - UnexpectedCfgs: UnexpectedCfgs, ] ] ); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 756899e50a8cd..829ac6903deda 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -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)] diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index bef9f469cc657..a2243817df95f 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -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, diff --git a/src/doc/unstable-book/src/compiler-flags/check-cfg.md b/src/doc/unstable-book/src/compiler-flags/check-cfg.md index 0e15c79076fa4..bcfab79047897 100644 --- a/src/doc/unstable-book/src/compiler-flags/check-cfg.md +++ b/src/doc/unstable-book/src/compiler-flags/check-cfg.md @@ -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 From a5658e6de8fda79616ab95ff6f1e0a7833cc7aa3 Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 2 Nov 2023 17:06:32 +0100 Subject: [PATCH 02/12] Update --check-cfg tests that had old --cfg warning --- .../exhaustive-names-values.empty_cfg.stderr | 10 +++------- ...exhaustive-names-values.empty_names_values.stderr | 10 +++------- .../check-cfg/exhaustive-names-values.feature.stderr | 12 ++++-------- .../ui/check-cfg/exhaustive-names-values.full.stderr | 12 ++++-------- tests/ui/check-cfg/exhaustive-names-values.rs | 3 +-- .../ui/check-cfg/exhaustive-names.empty_names.stderr | 6 +----- .../exhaustive-names.exhaustive_names.stderr | 6 +----- .../ui/check-cfg/exhaustive-values.empty_cfg.stderr | 6 +----- tests/ui/check-cfg/mix.cfg.stderr | 10 +--------- tests/ui/check-cfg/mix.names_values.stderr | 10 +--------- tests/ui/check-cfg/unexpected-cfg-value.cfg.stderr | 10 +++------- tests/ui/check-cfg/unexpected-cfg-value.rs | 6 ++---- .../ui/check-cfg/unexpected-cfg-value.values.stderr | 10 +++------- 13 files changed, 28 insertions(+), 83 deletions(-) diff --git a/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr b/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr index 53ccc0f4d317e..12a055d02a7b4 100644 --- a/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr +++ b/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr @@ -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 diff --git a/tests/ui/check-cfg/exhaustive-names-values.empty_names_values.stderr b/tests/ui/check-cfg/exhaustive-names-values.empty_names_values.stderr index 5e8b74054ceb6..12a055d02a7b4 100644 --- a/tests/ui/check-cfg/exhaustive-names-values.empty_names_values.stderr +++ b/tests/ui/check-cfg/exhaustive-names-values.empty_names_values.stderr @@ -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 diff --git a/tests/ui/check-cfg/exhaustive-names-values.feature.stderr b/tests/ui/check-cfg/exhaustive-names-values.feature.stderr index 7705a665eb77e..d71ca09589488 100644 --- a/tests/ui/check-cfg/exhaustive-names-values.feature.stderr +++ b/tests/ui/check-cfg/exhaustive-names-values.feature.stderr @@ -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 diff --git a/tests/ui/check-cfg/exhaustive-names-values.full.stderr b/tests/ui/check-cfg/exhaustive-names-values.full.stderr index f0224a2e33c75..d71ca09589488 100644 --- a/tests/ui/check-cfg/exhaustive-names-values.full.stderr +++ b/tests/ui/check-cfg/exhaustive-names-values.full.stderr @@ -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 diff --git a/tests/ui/check-cfg/exhaustive-names-values.rs b/tests/ui/check-cfg/exhaustive-names-values.rs index f553d93cae2a3..ef1e458b2498f 100644 --- a/tests/ui/check-cfg/exhaustive-names-values.rs +++ b/tests/ui/check-cfg/exhaustive-names-values.rs @@ -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 diff --git a/tests/ui/check-cfg/exhaustive-names.empty_names.stderr b/tests/ui/check-cfg/exhaustive-names.empty_names.stderr index 6190ff71464c5..6bc7845c8328a 100644 --- a/tests/ui/check-cfg/exhaustive-names.empty_names.stderr +++ b/tests/ui/check-cfg/exhaustive-names.empty_names.stderr @@ -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 diff --git a/tests/ui/check-cfg/exhaustive-names.exhaustive_names.stderr b/tests/ui/check-cfg/exhaustive-names.exhaustive_names.stderr index f338434cd299d..6bc7845c8328a 100644 --- a/tests/ui/check-cfg/exhaustive-names.exhaustive_names.stderr +++ b/tests/ui/check-cfg/exhaustive-names.exhaustive_names.stderr @@ -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 diff --git a/tests/ui/check-cfg/exhaustive-values.empty_cfg.stderr b/tests/ui/check-cfg/exhaustive-values.empty_cfg.stderr index 999b270284979..77ddc35100ae3 100644 --- a/tests/ui/check-cfg/exhaustive-values.empty_cfg.stderr +++ b/tests/ui/check-cfg/exhaustive-values.empty_cfg.stderr @@ -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 diff --git a/tests/ui/check-cfg/mix.cfg.stderr b/tests/ui/check-cfg/mix.cfg.stderr index daa200440cc94..21c0c7da1dd3d 100644 --- a/tests/ui/check-cfg/mix.cfg.stderr +++ b/tests/ui/check-cfg/mix.cfg.stderr @@ -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 diff --git a/tests/ui/check-cfg/mix.names_values.stderr b/tests/ui/check-cfg/mix.names_values.stderr index daa200440cc94..21c0c7da1dd3d 100644 --- a/tests/ui/check-cfg/mix.names_values.stderr +++ b/tests/ui/check-cfg/mix.names_values.stderr @@ -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 diff --git a/tests/ui/check-cfg/unexpected-cfg-value.cfg.stderr b/tests/ui/check-cfg/unexpected-cfg-value.cfg.stderr index 2ed7f9005573f..2855aa75966bc 100644 --- a/tests/ui/check-cfg/unexpected-cfg-value.cfg.stderr +++ b/tests/ui/check-cfg/unexpected-cfg-value.cfg.stderr @@ -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 diff --git a/tests/ui/check-cfg/unexpected-cfg-value.rs b/tests/ui/check-cfg/unexpected-cfg-value.rs index a84458071de84..1b8ead956bef0 100644 --- a/tests/ui/check-cfg/unexpected-cfg-value.rs +++ b/tests/ui/check-cfg/unexpected-cfg-value.rs @@ -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")) diff --git a/tests/ui/check-cfg/unexpected-cfg-value.values.stderr b/tests/ui/check-cfg/unexpected-cfg-value.values.stderr index 2ed7f9005573f..2855aa75966bc 100644 --- a/tests/ui/check-cfg/unexpected-cfg-value.values.stderr +++ b/tests/ui/check-cfg/unexpected-cfg-value.values.stderr @@ -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 From 0d0a41789f86c2fc62382831e5256809b3bbe19e Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Fri, 17 Nov 2023 20:33:44 -0500 Subject: [PATCH 03/12] Expand Miri's BorTag GC to a Provenance GC --- .../src/const_eval/machine.rs | 8 + .../rustc_const_eval/src/interpret/machine.rs | 8 + .../rustc_const_eval/src/interpret/memory.rs | 9 + .../rustc_middle/src/mir/interpret/mod.rs | 7 - src/tools/miri/src/borrow_tracker/mod.rs | 22 +- .../src/borrow_tracker/stacked_borrows/mod.rs | 6 +- .../src/borrow_tracker/tree_borrows/tree.rs | 6 +- src/tools/miri/src/concurrency/data_race.rs | 10 +- src/tools/miri/src/concurrency/init_once.rs | 6 +- src/tools/miri/src/concurrency/sync.rs | 6 +- src/tools/miri/src/concurrency/thread.rs | 38 ++-- src/tools/miri/src/concurrency/weak_memory.rs | 6 +- src/tools/miri/src/intptrcast.rs | 45 +++- src/tools/miri/src/lib.rs | 4 +- src/tools/miri/src/machine.rs | 50 ++--- src/tools/miri/src/mono_hash_map.rs | 8 + src/tools/miri/src/provenance_gc.rs | 209 ++++++++++++++++++ src/tools/miri/src/shims/env.rs | 8 +- src/tools/miri/src/shims/foreign_items.rs | 4 + src/tools/miri/src/shims/panic.rs | 10 +- src/tools/miri/src/shims/time.rs | 4 +- src/tools/miri/src/shims/tls.rs | 8 +- src/tools/miri/src/shims/unix/fs.rs | 10 +- src/tools/miri/src/shims/unix/linux/sync.rs | 6 +- src/tools/miri/src/shims/unix/sync.rs | 6 +- src/tools/miri/src/shims/windows/sync.rs | 18 +- src/tools/miri/src/tag_gc.rs | 169 -------------- 27 files changed, 395 insertions(+), 296 deletions(-) create mode 100644 src/tools/miri/src/provenance_gc.rs delete mode 100644 src/tools/miri/src/tag_gc.rs diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 4b447229c5f61..739dbcc5aba64 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -107,6 +107,14 @@ impl interpret::AllocMap for FxIndexMap { FxIndexMap::contains_key(self, k) } + #[inline(always)] + fn contains_key_ref(&self, k: &Q) -> bool + where + K: Borrow, + { + FxIndexMap::contains_key(self, k) + } + #[inline(always)] fn insert(&mut self, k: K, v: V) -> Option { FxIndexMap::insert(self, k, v) diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index 61fe9151d8b41..6617f6f2ffb68 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -49,6 +49,14 @@ pub trait AllocMap { where K: Borrow; + /// 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(&self, k: &Q) -> bool + where + K: Borrow; + /// Inserts a new entry into the map. fn insert(&mut self, k: K, v: V) -> Option; diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 16905e93bf1f8..d5b165a7415b3 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -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) { diff --git a/compiler/rustc_middle/src/mir/interpret/mod.rs b/compiler/rustc_middle/src/mir/interpret/mod.rs index b87c6885e04c8..e360fb3eaaf3c 100644 --- a/compiler/rustc_middle/src/mir/interpret/mod.rs +++ b/compiler/rustc_middle/src/mir/interpret/mod.rs @@ -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(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! diff --git a/src/tools/miri/src/borrow_tracker/mod.rs b/src/tools/miri/src/borrow_tracker/mod.rs index f9cc3acbd51bc..8fae526922943 100644 --- a/src/tools/miri/src/borrow_tracker/mod.rs +++ b/src/tools/miri/src/borrow_tracker/mod.rs @@ -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), } } } diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs index a74c69d52f236..91d924976f78f 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -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)); } } } diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs index 4232cd396c95e..6801397b2cec6 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs @@ -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)) } } diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index 5d109a7d55cef..80d0402fc8758 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -790,9 +790,9 @@ pub struct VClockAlloc { alloc_ranges: RefCell>, } -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. } } diff --git a/src/tools/miri/src/concurrency/init_once.rs b/src/tools/miri/src/concurrency/init_once.rs index 71582c75eae57..9a848d50341a4 100644 --- a/src/tools/miri/src/concurrency/init_once.rs +++ b/src/tools/miri/src/concurrency/init_once.rs @@ -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); } } } diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs index 62f6d57ef36e1..b288b69e0cef9 100644 --- a/src/tools/miri/src/concurrency/sync.rs +++ b/src/tools/miri/src/concurrency/sync.rs @@ -181,10 +181,10 @@ pub(crate) struct SynchronizationState<'mir, 'tcx> { pub(super) init_onces: IndexVec>, } -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); } } } diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index 6449ed29cf8f7..754cfa4d2a827 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -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>, } -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); } } diff --git a/src/tools/miri/src/concurrency/weak_memory.rs b/src/tools/miri/src/concurrency/weak_memory.rs index 2ff344bb1a35f..39e89ce7faa7a 100644 --- a/src/tools/miri/src/concurrency/weak_memory.rs +++ b/src/tools/miri/src/concurrency/weak_memory.rs @@ -108,15 +108,15 @@ pub struct StoreBufferAlloc { store_buffers: RefCell>, } -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); } } } diff --git a/src/tools/miri/src/intptrcast.rs b/src/tools/miri/src/intptrcast.rs index 9966ee3fd919e..cd4d1bcc4647c 100644 --- a/src/tools/miri/src/intptrcast.rs +++ b/src/tools/miri/src/intptrcast.rs @@ -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(()) } diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index b12aae6d4148c..119ec555b2e01 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -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. diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 2085df7d06f84..66d7dfcf3a108 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -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>>, } -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. diff --git a/src/tools/miri/src/mono_hash_map.rs b/src/tools/miri/src/mono_hash_map.rs index 81b3153517f14..220233f8ff5f0 100644 --- a/src/tools/miri/src/mono_hash_map.rs +++ b/src/tools/miri/src/mono_hash_map.rs @@ -46,6 +46,14 @@ impl AllocMap for MonoHashMap { self.0.get_mut().contains_key(k) } + #[inline(always)] + fn contains_key_ref(&self, k: &Q) -> bool + where + K: Borrow, + { + self.0.borrow().contains_key(k) + } + #[inline(always)] fn insert(&mut self, k: K, v: V) -> Option { self.0.get_mut().insert(k, Box::new(v)).map(|x| *x) diff --git a/src/tools/miri/src/provenance_gc.rs b/src/tools/miri/src/provenance_gc.rs new file mode 100644 index 0000000000000..eac4aad27a0ca --- /dev/null +++ b/src/tools/miri/src/provenance_gc.rs @@ -0,0 +1,209 @@ +use either::Either; + +use rustc_data_structures::fx::FxHashSet; + +use crate::*; + +pub type VisitWith<'a> = dyn FnMut(Option, Option) + 'a; + +pub trait VisitProvenance { + fn visit_provenance(&self, visit: &mut VisitWith<'_>); +} + +impl VisitProvenance for Option { + fn visit_provenance(&self, visit: &mut VisitWith<'_>) { + if let Some(x) = self { + x.visit_provenance(visit); + } + } +} + +impl VisitProvenance for std::cell::RefCell { + 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 { + fn visit_provenance(&self, visit: &mut VisitWith<'_>) { + let (prov, _offset) = self.into_parts(); + prov.visit_provenance(visit); + } +} + +impl VisitProvenance for Pointer> { + fn visit_provenance(&self, visit: &mut VisitWith<'_>) { + let (prov, _offset) = self.into_parts(); + prov.visit_provenance(visit); + } +} + +impl VisitProvenance for Scalar { + fn visit_provenance(&self, visit: &mut VisitWith<'_>) { + match self { + Scalar::Ptr(ptr, _) => ptr.visit_provenance(visit), + Scalar::Int(_) => (), + } + } +} + +impl VisitProvenance for Immediate { + 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 { + 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> { + 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, + 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) { + 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) { + 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); + } + } +} diff --git a/src/tools/miri/src/shims/env.rs b/src/tools/miri/src/shims/env.rs index 154a7f6983343..4243898590731 100644 --- a/src/tools/miri/src/shims/env.rs +++ b/src/tools/miri/src/shims/env.rs @@ -37,13 +37,13 @@ pub struct EnvVars<'tcx> { pub(crate) environ: Option>, } -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); } } } diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 329a30a9faf3b..d7aaa08dbf3fd 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -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)?; diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs index 5c0f828e4e6bb..28652c25c2450 100644 --- a/src/tools/miri/src/shims/panic.rs +++ b/src/tools/miri/src/shims/panic.rs @@ -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); } } diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 4918698c6b277..792122a001676 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -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 { diff --git a/src/tools/miri/src/shims/tls.rs b/src/tools/miri/src/shims/tls.rs index 62bd087e7e82d..b319516c25b9e 100644 --- a/src/tools/miri/src/shims/tls.rs +++ b/src/tools/miri/src/shims/tls.rs @@ -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); } } } diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 062623a7f6a60..471eadb8c356f 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -288,8 +288,8 @@ pub struct FileHandler { pub handles: BTreeMap>, } -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); } } } diff --git a/src/tools/miri/src/shims/unix/linux/sync.rs b/src/tools/miri/src/shims/unix/linux/sync.rs index ff25b8120b1c0..10e06226b3fca 100644 --- a/src/tools/miri/src/shims/unix/linux/sync.rs +++ b/src/tools/miri/src/shims/unix/linux/sync.rs @@ -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); } } diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs index 1a91219e01610..45b47450b7b2c 100644 --- a/src/tools/miri/src/shims/unix/sync.rs +++ b/src/tools/miri/src/shims/unix/sync.rs @@ -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); } } diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs index 2c9603097c857..2b9801fea68e6 100644 --- a/src/tools/miri/src/shims/windows/sync.rs +++ b/src/tools/miri/src/shims/windows/sync.rs @@ -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); } } diff --git a/src/tools/miri/src/tag_gc.rs b/src/tools/miri/src/tag_gc.rs deleted file mode 100644 index 3cccdd3635388..0000000000000 --- a/src/tools/miri/src/tag_gc.rs +++ /dev/null @@ -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 VisitTags for Option { - fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) { - if let Some(x) = self { - x.visit_tags(visit); - } - } -} - -impl VisitTags for std::cell::RefCell { - 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 { - fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) { - let (prov, _offset) = self.into_parts(); - prov.visit_tags(visit); - } -} - -impl VisitTags for Pointer> { - fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) { - let (prov, _offset) = self.into_parts(); - prov.visit_tags(visit); - } -} - -impl VisitTags for Scalar { - fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) { - match self { - Scalar::Ptr(ptr, _) => ptr.visit_tags(visit), - Scalar::Int(_) => (), - } - } -} - -impl VisitTags for Immediate { - 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 { - 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> { - 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) { - 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); - } - } - }); - } -} From 0ec82fa9d3d68dd2f29f721a6db6a3af5332242d Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Fri, 17 Nov 2023 20:47:38 -0500 Subject: [PATCH 04/12] tag-gc -> provenance-gc --- src/tools/miri/.github/workflows/ci.yml | 2 +- src/tools/miri/README.md | 8 ++++---- src/tools/miri/src/bin/miri.rs | 4 ++-- .../miri/src/borrow_tracker/tree_borrows/diagnostics.rs | 2 +- .../fail/tree_borrows/reserved/cell-protected-write.rs | 2 +- .../fail/tree_borrows/reserved/int-protected-write.rs | 2 +- .../miri/tests/pass/tree_borrows/cell-alternate-writes.rs | 2 +- .../miri/tests/pass/tree_borrows/end-of-protector.rs | 2 +- src/tools/miri/tests/pass/tree_borrows/formatting.rs | 2 +- .../miri/tests/pass/tree_borrows/reborrow-is-read.rs | 2 +- src/tools/miri/tests/pass/tree_borrows/reserved.rs | 2 +- src/tools/miri/tests/pass/tree_borrows/unique.rs | 2 +- src/tools/miri/tests/pass/tree_borrows/vec_unique.rs | 2 +- src/tools/miri/tests/utils/miri_extern.rs | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 67b48a3742da0..554a12909fc85 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -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 diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index f90fd1f7fc54e..aa0b791bd05fd 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -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=` 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=` 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=,,...` 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 diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 7f777cd4727ad..4517de6289319 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -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::() { 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=") { diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs index b3001b5b88cdc..43e6616e34a71 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs @@ -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 { let mut idx = self.tag_mapping.get(&tag).unwrap(); for _ in 0..nth_parent { diff --git a/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.rs b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.rs index 465679b72c343..3769575765e95 100644 --- a/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.rs +++ b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.rs @@ -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 diff --git a/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.rs b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.rs index 1e6e2eebd26ce..e2956759d0b36 100644 --- a/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.rs +++ b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.rs @@ -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] diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.rs b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.rs index 398b542ed4c24..e6310f8eda646 100644 --- a/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.rs +++ b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.rs @@ -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; diff --git a/src/tools/miri/tests/pass/tree_borrows/end-of-protector.rs b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.rs index fecc3360434d2..4d941850e5415 100644 --- a/src/tools/miri/tests/pass/tree_borrows/end-of-protector.rs +++ b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.rs @@ -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. diff --git a/src/tools/miri/tests/pass/tree_borrows/formatting.rs b/src/tools/miri/tests/pass/tree_borrows/formatting.rs index f22c408ad2538..c4360f0dbb9a4 100644 --- a/src/tools/miri/tests/pass/tree_borrows/formatting.rs +++ b/src/tools/miri/tests/pass/tree_borrows/formatting.rs @@ -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] diff --git a/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.rs b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.rs index a38cd6d289416..7a5fd395c7fd8 100644 --- a/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.rs +++ b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.rs @@ -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] diff --git a/src/tools/miri/tests/pass/tree_borrows/reserved.rs b/src/tools/miri/tests/pass/tree_borrows/reserved.rs index 8d0beab66f406..ef47d30e2efca 100644 --- a/src/tools/miri/tests/pass/tree_borrows/reserved.rs +++ b/src/tools/miri/tests/pass/tree_borrows/reserved.rs @@ -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] diff --git a/src/tools/miri/tests/pass/tree_borrows/unique.rs b/src/tools/miri/tests/pass/tree_borrows/unique.rs index 44e2e8136252e..6c5ed9efceeaa 100644 --- a/src/tools/miri/tests/pass/tree_borrows/unique.rs +++ b/src/tools/miri/tests/pass/tree_borrows/unique.rs @@ -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)] diff --git a/src/tools/miri/tests/pass/tree_borrows/vec_unique.rs b/src/tools/miri/tests/pass/tree_borrows/vec_unique.rs index e5d0a683a7237..8d0f4bd0fe79c 100644 --- a/src/tools/miri/tests/pass/tree_borrows/vec_unique.rs +++ b/src/tools/miri/tests/pass/tree_borrows/vec_unique.rs @@ -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)] diff --git a/src/tools/miri/tests/utils/miri_extern.rs b/src/tools/miri/tests/utils/miri_extern.rs index c0ef2c506413d..cd734bab7d5c8 100644 --- a/src/tools/miri/tests/utils/miri_extern.rs +++ b/src/tools/miri/tests/utils/miri_extern.rs @@ -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. From 63c50712f40055a60aefc8069661c3847bd27df4 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 22 Sep 2023 17:27:06 -0700 Subject: [PATCH 05/12] rustdoc-search: add support for associated types --- src/librustdoc/clean/types.rs | 44 ++ src/librustdoc/formats/cache.rs | 1 + src/librustdoc/formats/item_type.rs | 2 + src/librustdoc/html/render/mod.rs | 39 +- src/librustdoc/html/render/search_index.rs | 422 +++++++++++++++--- src/librustdoc/html/static/js/externs.js | 5 +- src/librustdoc/html/static/js/search.js | 331 ++++++++++++-- src/tools/rustdoc-js/tester.js | 29 +- tests/rustdoc-gui/search-tab.goml | 2 +- .../iterator-type-signatures.js | 29 ++ tests/rustdoc-js-std/parser-bindings.js | 245 ++++++++++ tests/rustdoc-js-std/parser-errors.js | 2 +- tests/rustdoc-js/assoc-type-backtrack.js | 163 +++++++ tests/rustdoc-js/assoc-type-backtrack.rs | 38 ++ tests/rustdoc-js/assoc-type.js | 45 ++ tests/rustdoc-js/assoc-type.rs | 12 + tests/rustdoc-js/gat.js | 57 +++ tests/rustdoc-js/gat.rs | 8 + tests/rustdoc-js/never-search.js | 10 + tests/rustdoc-js/trait-methods.js | 12 + tests/rustdoc-js/trait-methods.rs | 4 + 21 files changed, 1383 insertions(+), 117 deletions(-) create mode 100644 tests/rustdoc-js-std/iterator-type-signatures.js create mode 100644 tests/rustdoc-js-std/parser-bindings.js create mode 100644 tests/rustdoc-js/assoc-type-backtrack.js create mode 100644 tests/rustdoc-js/assoc-type-backtrack.rs create mode 100644 tests/rustdoc-js/assoc-type.js create mode 100644 tests/rustdoc-js/assoc-type.rs create mode 100644 tests/rustdoc-js/gat.js create mode 100644 tests/rustdoc-js/gat.rs create mode 100644 tests/rustdoc-js/trait-methods.js create mode 100644 tests/rustdoc-js/trait-methods.rs diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 871738cdc07d0..ded256fd75c52 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -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> { 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> { 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 + '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 + '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)] diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index d63bbe5896e8f..9b1b6899751e2 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -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(), diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index def3a90c8e812..22527876b76f9 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -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 { diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 90691411f44a0..8b3cd9ca6fc0c 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -113,6 +113,7 @@ pub(crate) struct IndexItem { pub(crate) struct RenderType { id: Option, generics: Option>, + bindings: Option)>>, } 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(&self, serializer: S) -> Result + 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 { diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 1284f69e5d777..b3ae720fcf609 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -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,83 +85,139 @@ 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( - ty: &mut RenderType, map: &mut FxHashMap, itemid: F, lastpathid: &mut isize, crate_paths: &mut Vec<(ItemType, Vec)>, 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( - ty: &mut RenderType, + fn convert_render_type_id( + id: RenderTypeId, cache: &mut Cache, itemid_to_pathid: &mut FxHashMap, primitives: &mut FxHashMap, + associated_types: &mut FxHashMap, lastpathid: &mut isize, crate_paths: &mut Vec<(ItemType, Vec)>, - ) { - if let Some(generics) = &mut ty.generics { - for item in generics { - convert_render_type( - item, - cache, - itemid_to_pathid, - primitives, - lastpathid, - crate_paths, - ); - } - } + ) -> Option { let Cache { ref paths, ref external_paths, .. } = *cache; - 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, + Some(insert_into_map( itemid_to_pathid, ItemId::DefId(defid), lastpathid, crate_paths, item_type, fqp, - ); + )) } else { - ty.id = None; + None } } RenderTypeId::Primitive(primitive) => { let sym = primitive.as_sym(); - insert_into_map( - ty, + 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, + primitives: &mut FxHashMap, + associated_types: &mut FxHashMap, + lastpathid: &mut isize, + crate_paths: &mut Vec<(ItemType, Vec)>, + ) { + if let Some(generics) = &mut ty.generics { + for item in generics { + convert_render_type( + item, + cache, + itemid_to_pathid, + primitives, + associated_types, + lastpathid, + crate_paths, ); } - RenderTypeId::Index(_) => {} } + 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; + }; + 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, cache: &Cache, ) -> Option { + 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 { +fn get_index_type( + clean_type: &clean::Type, + generics: Vec, + rgen: &mut FxHashMap)>, +) -> 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 { +fn get_index_type_id( + clean_type: &clean::Type, + rgen: &mut FxHashMap)>, +) -> Option { + 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 { } 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`, 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, + ); + } + } + // 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 is technically just Iterator, but we want to pretend it's more like this: + // + // fn next(self: Iterator) -> Option + 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); + 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)>, + rgen: &mut FxHashMap)>, + 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>(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, Vec, Vec>) { let decl = &func.decl; + let mut rgen: FxHashMap)> = 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)> = Default::default(); - let mut arg_types = Vec::new(); for arg in decl.inputs.values.iter() { simplify_fn_type( diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js index c7811b43d1641..2338931a18fd2 100644 --- a/src/librustdoc/html/static/js/externs.js +++ b/src/librustdoc/html/static/js/externs.js @@ -14,6 +14,7 @@ function initSearch(searchIndex){} * pathWithoutLast: Array, * pathLast: string, * generics: Array, + * bindings: Map<(string|integer), Array>, * }} */ 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, + * bindings: Map>, * }} */ let FunctionType; diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index e9dd3c439b3f0..22dcb87014315 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -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|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 + * + * ... 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` were + * the type instead. + * + * @param {FunctionType} fnType + * @param {QueryElement} queryElem + * @param {[FunctionType]} whereClause - Trait bounds for generic items. + * @param {Map} mgensIn - Map functions generics to query generics. + * Never modified. + * @returns {false|{mgens: [Map], 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|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) -> Result` // 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}${name}\ 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=Result> is equivalent to MyType>=T> + // and both are, essentially + // MyType)>, 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}${name}\ ty: TY_GENERIC, path: null, generics, + bindings, }; } if (pathIndex === 0) { @@ -2564,6 +2797,7 @@ ${item.displayPath}${name}\ ty: null, path: null, generics, + bindings, }; } const item = lowercasePaths[pathIndex - 1]; @@ -2572,6 +2806,7 @@ ${item.displayPath}${name}\ ty: item.ty, path: item.path, generics, + bindings, }; } diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js index c7e6dd3615e94..6e630a80454ad 100644 --- a/src/tools/rustdoc-js/tester.js +++ b/src/tools/rustdoc-js/tester.js @@ -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) { diff --git a/tests/rustdoc-gui/search-tab.goml b/tests/rustdoc-gui/search-tab.goml index db1605ff220a1..b52bb0688c1b5 100644 --- a/tests/rustdoc-gui/search-tab.goml +++ b/tests/rustdoc-gui/search-tab.goml @@ -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|}) diff --git a/tests/rustdoc-js-std/iterator-type-signatures.js b/tests/rustdoc-js-std/iterator-type-signatures.js new file mode 100644 index 0000000000000..c18ffc1651c15 --- /dev/null +++ b/tests/rustdoc-js-std/iterator-type-signatures.js @@ -0,0 +1,29 @@ +// ignore-order + +const FILTER_CRATE = "std"; + +const EXPECTED = [ + { + 'query': 'iterator -> option', + '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, usize -> option', + 'others': [ + { 'path': 'std::iter::Iterator', 'name': 'nth' }, + ], + }, + { + // Something should be done so that intoiterator is considered a match + // for plain iterator. + 'query': 'iterator, intoiterator -> ordering', + 'others': [ + { 'path': 'std::iter::Iterator', 'name': 'cmp' }, + ], + }, +]; diff --git a/tests/rustdoc-js-std/parser-bindings.js b/tests/rustdoc-js-std/parser-bindings.js new file mode 100644 index 0000000000000..faf880c8116c4 --- /dev/null +++ b/tests/rustdoc-js-std/parser-bindings.js @@ -0,0 +1,245 @@ +const PARSED = [ + { + query: 'A', + 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', + returned: [], + userQuery: 'a', + error: null, + }, + { + query: 'A', + 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', + returned: [], + userQuery: 'a', + error: null, + }, + { + query: 'A', + 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', + returned: [], + userQuery: 'a', + error: null, + }, + { + query: 'A', + elems: [ + { + name: "a", + fullPath: ["a"], + pathWithoutLast: [], + pathLast: "a", + generics: [], + bindings: [ + [ + 'b', + [{ + name: "[]", + fullPath: ["[]"], + pathWithoutLast: [], + pathLast: "[]", + generics: [], + typeFilter: 15, + }] + ], + ], + typeFilter: -1, + }, + ], + foundElems: 1, + original: 'A', + returned: [], + userQuery: 'a', + error: null, + }, + { + query: 'A', + 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', + returned: [], + userQuery: 'a', + error: null, + }, + { + query: 'A', + elems: [], + foundElems: 0, + original: 'A', + returned: [], + userQuery: 'a', + error: "Cannot write `=` twice in a binding", + }, + { + query: 'A', + elems: [], + foundElems: 0, + original: 'A', + returned: [], + userQuery: 'a', + 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=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=C>', + returned: [], + userQuery: 'a=c>', + error: null, + }, +]; diff --git a/tests/rustdoc-js-std/parser-errors.js b/tests/rustdoc-js-std/parser-errors.js index b32bfea5439aa..ab8d72bf71b13 100644 --- a/tests/rustdoc-js-std/parser-errors.js +++ b/tests/rustdoc-js-std/parser-errors.js @@ -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<->", diff --git a/tests/rustdoc-js/assoc-type-backtrack.js b/tests/rustdoc-js/assoc-type-backtrack.js new file mode 100644 index 0000000000000..493e1a9910d18 --- /dev/null +++ b/tests/rustdoc-js/assoc-type-backtrack.js @@ -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, mytrait2 -> T', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' }, + { 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' }, + ], + }, + { + 'query': 'mytrait, mytrait2 -> T', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' }, + { 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' }, + ], + }, + { + 'query': 'mytrait, mytrait2 -> T', + 'correction': null, + 'others': [], + }, + { + 'query': 'mytrait, mytrait2 -> T', + 'correction': null, + 'others': [], + }, + { + 'query': 'mytrait -> Option', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyTrait', 'name': 'next' }, + ], + }, + { + 'query': 'mytrait -> Option', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyTrait', 'name': 'next' }, + ], + }, + { + 'query': 'mytrait -> Option', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' }, + ], + }, + { + 'query': 'mytrait -> Option', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' }, + ], + }, + // The first two define the base case. + { + 'query': 'myintofuture> -> myfuture', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' }, + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + { + 'query': 'myintofuture>, myintofuture> -> myfuture', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + // Unboxings of the one-argument case. + { + 'query': 'myfuture -> myfuture', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' }, + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + { + 'query': 'myintofuture> -> myfuture', + '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 -> myfuture', + 'correction': null, + 'others': [], + }, + // Unboxings of the two-argument case. + { + 'query': 'myintofuture, myintofuture -> t', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + { + 'query': 'myintofuture, myintofuture -> myfuture', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + { + 'query': 'myintofuture, myintofuture -> myfuture', + 'correction': null, + 'others': [ + { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' }, + ], + }, + { + 'query': 'myfuture, myfuture -> myfuture', + '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, myintofuture> -> myfuture', + 'correction': null, + 'others': [], + }, + { + 'query': 'myintofuture>, myintofuture -> myfuture', + 'correction': null, + 'others': [], + }, + { + 'query': 'myintofuture>, myintofuture> -> t', + 'correction': null, + 'others': [], + }, + // different generics don't match up either + { + 'query': 'myintofuture>, myintofuture> -> myfuture', + 'correction': null, + 'others': [], + }, + { + 'query': 'myintofuture -> myfuture', + 'correction': null, + 'others': [], + }, +]; diff --git a/tests/rustdoc-js/assoc-type-backtrack.rs b/tests/rustdoc-js/assoc-type-backtrack.rs new file mode 100644 index 0000000000000..c3cdd78c6e1c4 --- /dev/null +++ b/tests/rustdoc-js/assoc-type-backtrack.rs @@ -0,0 +1,38 @@ +pub trait MyTrait2 { + type Output; +} + +pub trait MyTrait { + type Item; + fn next(&mut self) -> Option; + fn fold(self, init: B, f: F) -> B where + Self: Sized, + F: MyTrait2<(B, Self::Item), Output=B>; +} + +pub struct Cloned(I); + +impl<'a, T, I> MyTrait for Cloned where + T: 'a + Clone, + I: MyTrait +{ + type Item = T; + fn next(&mut self) -> Option { loop {} } + fn fold(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; + fn into_future(self) -> Self::Fut; + fn into_future_2(self, other: Self) -> Self::Fut; +} diff --git a/tests/rustdoc-js/assoc-type.js b/tests/rustdoc-js/assoc-type.js new file mode 100644 index 0000000000000..cc3afaa17c07d --- /dev/null +++ b/tests/rustdoc-js/assoc-type.js @@ -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 -> u32', + 'correction': null, + 'others': [ + { 'path': 'assoc_type', 'name': 'my_fn' }, + { 'path': 'assoc_type::my', 'name': 'other_fn' }, + ], + }, + { + 'query': 'iterator', + '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 -> u32', + 'correction': null, + 'others': [ + { 'path': 'assoc_type', 'name': 'my_fn' }, + ], + }, + // case insensitivity + { + 'query': 'iterator -> u32', + 'correction': null, + 'others': [ + { 'path': 'assoc_type', 'name': 'my_fn' }, + ], + }, + // wrong binding name, no result + { + 'query': 'iterator -> u32', + 'correction': null, + 'in_args': [], + 'others': [], + }, +]; diff --git a/tests/rustdoc-js/assoc-type.rs b/tests/rustdoc-js/assoc-type.rs new file mode 100644 index 0000000000000..e12e73cb546df --- /dev/null +++ b/tests/rustdoc-js/assoc-type.rs @@ -0,0 +1,12 @@ +pub fn my_fn>(_x: X) -> u32 { + 3 +} + +pub struct Something; + +pub mod my { + pub trait Iterator {} + pub fn other_fn>(_: X) -> u32 { + 3 + } +} diff --git a/tests/rustdoc-js/gat.js b/tests/rustdoc-js/gat.js new file mode 100644 index 0000000000000..7cb6a85d135f9 --- /dev/null +++ b/tests/rustdoc-js/gat.js @@ -0,0 +1,57 @@ +// exact-check + +const EXPECTED = [ + { + 'query': 'foo=u8> -> u32', + 'correction': null, + 'in_args': [], + 'others': [ + { 'path': 'gat', 'name': 'sample' }, + ], + }, + { + 'query': 'foo=u8> -> !', + 'correction': null, + 'in_args': [], + 'others': [ + { 'path': 'gat', 'name': 'synergy' }, + ], + }, + { + 'query': 'foo=u8>', + 'correction': null, + 'in_args': [ + { 'path': 'gat', 'name': 'sample' }, + { 'path': 'gat', 'name': 'synergy' }, + ], + }, + { + 'query': 'foo=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=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=T>', + 'correction': null, + 'in_args': [ + { 'path': 'gat', 'name': 'integrate' }, + ], + }, +]; diff --git a/tests/rustdoc-js/gat.rs b/tests/rustdoc-js/gat.rs new file mode 100644 index 0000000000000..b4861cc683ff3 --- /dev/null +++ b/tests/rustdoc-js/gat.rs @@ -0,0 +1,8 @@ +pub trait Foo { + type Assoc; +} + +pub fn sample = u8>>(_: X) -> u32 { loop {} } +pub fn synergy(_: impl Foo = u8>) -> ! { loop {} } +pub fn consider(_: impl Foo = u32>) -> bool { loop {} } +pub fn integrate(_: impl Foo = T>) -> T { loop {} } diff --git a/tests/rustdoc-js/never-search.js b/tests/rustdoc-js/never-search.js index ed24d693133ba..9f18370c2f511 100644 --- a/tests/rustdoc-js/never-search.js +++ b/tests/rustdoc-js/never-search.js @@ -43,4 +43,14 @@ const EXPECTED = [ { 'path': 'never_search', 'name': 'box_uninteresting' }, ], }, + { + 'query': 'box', + 'in_args': [], + 'returned': [], + }, + { + 'query': 'box', + 'in_args': [], + 'returned': [], + }, ]; diff --git a/tests/rustdoc-js/trait-methods.js b/tests/rustdoc-js/trait-methods.js new file mode 100644 index 0000000000000..dafad5e43784f --- /dev/null +++ b/tests/rustdoc-js/trait-methods.js @@ -0,0 +1,12 @@ +// exact-check + +const EXPECTED = [ + { + 'query': 'mytrait -> option', + 'correction': null, + 'in_args': [], + 'others': [ + { 'path': 'trait_methods::MyTrait', 'name': 'next' }, + ], + }, +]; diff --git a/tests/rustdoc-js/trait-methods.rs b/tests/rustdoc-js/trait-methods.rs new file mode 100644 index 0000000000000..c88f5edfd55b7 --- /dev/null +++ b/tests/rustdoc-js/trait-methods.rs @@ -0,0 +1,4 @@ +pub trait MyTrait { + type Item; + fn next(&mut self) -> Option; +} From acc74c085d4b3d21c537f5301f088996227586ae Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 22 Sep 2023 17:48:15 -0700 Subject: [PATCH 06/12] rustdoc: update book with info on type bindings --- .../rustdoc/src/read-documentation/search.md | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/doc/rustdoc/src/read-documentation/search.md b/src/doc/rustdoc/src/read-documentation/search.md index 56a5016d0cee0..1f45bd6c6b827 100644 --- a/src/doc/rustdoc/src/read-documentation/search.md +++ b/src/doc/rustdoc/src/read-documentation/search.md @@ -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]`][iterasslice] | `IntoIter::as_slice` and `IntoIter::next_chunk` | +| [`iterator, 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%20->%20[T]&filter-crate=std +[iterreduce]: ../../std/index.html?search=iterator%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>(input: I) -> usize` can be matched with the following queries: -* `Iterator -> usize` -* `Iterator -> usize` +* `Iterator -> usize` +* `Iterator -> 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 -> bool +/// MyTrait -> bool +/// MyTrait -> bool +/// MyTrait -> bool +/// +/// The following queries, however, will *not* match it: +/// +/// MyTrait -> bool +/// MyTrait -> bool +pub fn my_fn(x: impl MyTrait) -> 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 where T: Default` cannot be precisely searched for (use `option -> 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` matches `Option`, but never match concrete types in function signatures. A trait named as if it were a type, such as `Option`, 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 From 6b4465db50a39f760a8f32ff646441dba4433b67 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sun, 19 Nov 2023 23:12:50 -0500 Subject: [PATCH 07/12] Remove i686-apple-darwin cross-testing --- src/bootstrap/src/core/build_steps/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 254fbc72a8c7e..fa57533599f8e 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -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)] From b99165842df821a4f067ff20d8119c53fc6d54f6 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Sat, 18 Nov 2023 20:48:42 -0500 Subject: [PATCH 08/12] Test that the GC consults the extra_fn_ptr map --- .../miri/tests/pass-dep/extra_fn_ptr_gc.rs | 21 +++++++++++++++++++ src/tools/miri/tests/utils/miri_extern.rs | 5 +++++ src/tools/miri/tests/utils/mod.rs | 7 +++++++ 3 files changed, 33 insertions(+) create mode 100644 src/tools/miri/tests/pass-dep/extra_fn_ptr_gc.rs diff --git a/src/tools/miri/tests/pass-dep/extra_fn_ptr_gc.rs b/src/tools/miri/tests/pass-dep/extra_fn_ptr_gc.rs new file mode 100644 index 0000000000000..716119a0fc344 --- /dev/null +++ b/src/tools/miri/tests/pass-dep/extra_fn_ptr_gc.rs @@ -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()) }; +} diff --git a/src/tools/miri/tests/utils/miri_extern.rs b/src/tools/miri/tests/utils/miri_extern.rs index cd734bab7d5c8..7363c189840a8 100644 --- a/src/tools/miri/tests/utils/miri_extern.rs +++ b/src/tools/miri/tests/utils/miri_extern.rs @@ -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(); } diff --git a/src/tools/miri/tests/utils/mod.rs b/src/tools/miri/tests/utils/mod.rs index 7b7dc231a5019..6386162e09530 100644 --- a/src/tools/miri/tests/utils/mod.rs +++ b/src/tools/miri/tests/utils/mod.rs @@ -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() + } +} From fecd3e684da6159e6592dac66e0e58428685cb2a Mon Sep 17 00:00:00 2001 From: Petr Sumbera Date: Mon, 20 Nov 2023 15:15:47 +0100 Subject: [PATCH 09/12] Remove now deprecated target x86_64-sun-solaris. --- compiler/rustc_target/src/spec/mod.rs | 1 - src/ci/docker/host-x86_64/dist-various-2/Dockerfile | 6 ------ src/doc/rustc/src/platform-support.md | 5 ++--- src/tools/build-manifest/src/main.rs | 1 - .../stack-protector/stack-protector-target-support.rs | 2 +- 5 files changed, 3 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 771aec4de7b17..9b0fedba0923f 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -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), diff --git a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile index 01b46118b9c1e..3f4e025111ade 100644 --- a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile @@ -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 diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index d7266c0f56e0e..3671fdd3fd2be 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -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 diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index 215ffc771f3da..83c7ccc9edeb7 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -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", diff --git a/tests/assembly/stack-protector/stack-protector-target-support.rs b/tests/assembly/stack-protector/stack-protector-target-support.rs index c6528ac7c8d16..6d87fd1912b09 100644 --- a/tests/assembly/stack-protector/stack-protector-target-support.rs +++ b/tests/assembly/stack-protector/stack-protector-target-support.rs @@ -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 From 9ada6544f64fb85d9f0ffa54f1f5fa4ea3731df9 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Fri, 17 Nov 2023 21:05:11 -0500 Subject: [PATCH 10/12] Test with -Zmiri-provenance-gc=1 on Linux --- .../docker/host-x86_64/x86_64-gnu-tools/checktools.sh | 11 ++++++++++- src/tools/miri/tests/pass/0weak_memory_consistency.rs | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh b/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh index 821a09feb2d53..205ee263217ad 100755 --- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh +++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh @@ -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 diff --git a/src/tools/miri/tests/pass/0weak_memory_consistency.rs b/src/tools/miri/tests/pass/0weak_memory_consistency.rs index abfe3b0adeb4d..1cbccb2eebdd8 100644 --- a/src/tools/miri/tests/pass/0weak_memory_consistency.rs +++ b/src/tools/miri/tests/pass/0weak_memory_consistency.rs @@ -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 From 8a77060657e78653eab0e25fc272c4c3386d714c Mon Sep 17 00:00:00 2001 From: Petr Sumbera Date: Mon, 20 Nov 2023 15:55:13 +0100 Subject: [PATCH 11/12] Remove now unnecessary x86_64_sun_solaris.rs. --- .../src/spec/targets/x86_64_sun_solaris.rs | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 compiler/rustc_target/src/spec/targets/x86_64_sun_solaris.rs diff --git a/compiler/rustc_target/src/spec/targets/x86_64_sun_solaris.rs b/compiler/rustc_target/src/spec/targets/x86_64_sun_solaris.rs deleted file mode 100644 index cca099d3bbf65..0000000000000 --- a/compiler/rustc_target/src/spec/targets/x86_64_sun_solaris.rs +++ /dev/null @@ -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, - } -} From f68c6c9528606e3d8e31dac47af95b568372c644 Mon Sep 17 00:00:00 2001 From: ouz-a Date: Sat, 18 Nov 2023 14:35:13 +0300 Subject: [PATCH 12/12] Fix early param lifetimes in generic_const_exprs --- .../src/diagnostics/region_errors.rs | 11 ++++- .../ui/borrowck/generic_const_early_param.rs | 16 +++++++ .../borrowck/generic_const_early_param.stderr | 42 +++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 tests/ui/borrowck/generic_const_early_param.rs create mode 100644 tests/ui/borrowck/generic_const_early_param.stderr diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index 76c5a06c80db2..1069f544f57af 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -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); diff --git a/tests/ui/borrowck/generic_const_early_param.rs b/tests/ui/borrowck/generic_const_early_param.rs new file mode 100644 index 0000000000000..f601e45d21fee --- /dev/null +++ b/tests/ui/borrowck/generic_const_early_param.rs @@ -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(){} diff --git a/tests/ui/borrowck/generic_const_early_param.stderr b/tests/ui/borrowck/generic_const_early_param.stderr new file mode 100644 index 0000000000000..a71ab09396e73 --- /dev/null +++ b/tests/ui/borrowck/generic_const_early_param.stderr @@ -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 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`.