From d1a3518e30febb459afeb25b4850a3222f44db8c Mon Sep 17 00:00:00 2001 From: Trevor Spiteri Date: Thu, 19 Dec 2019 17:57:08 +0100 Subject: [PATCH 1/9] doc: minus (U+2212) instead of dash (U+002D) for negative infinity --- src/libcore/num/f32.rs | 2 +- src/libcore/num/f64.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libcore/num/f32.rs b/src/libcore/num/f32.rs index 9e379e638103e..be1e566fafdfa 100644 --- a/src/libcore/num/f32.rs +++ b/src/libcore/num/f32.rs @@ -62,7 +62,7 @@ pub const NAN: f32 = 0.0_f32 / 0.0_f32; /// Infinity (∞). #[stable(feature = "rust1", since = "1.0.0")] pub const INFINITY: f32 = 1.0_f32 / 0.0_f32; -/// Negative infinity (-∞). +/// Negative infinity (−∞). #[stable(feature = "rust1", since = "1.0.0")] pub const NEG_INFINITY: f32 = -1.0_f32 / 0.0_f32; diff --git a/src/libcore/num/f64.rs b/src/libcore/num/f64.rs index 540c6a529d7c8..feadb43ff98dd 100644 --- a/src/libcore/num/f64.rs +++ b/src/libcore/num/f64.rs @@ -62,7 +62,7 @@ pub const NAN: f64 = 0.0_f64 / 0.0_f64; /// Infinity (∞). #[stable(feature = "rust1", since = "1.0.0")] pub const INFINITY: f64 = 1.0_f64 / 0.0_f64; -/// Negative infinity (-∞). +/// Negative infinity (−∞). #[stable(feature = "rust1", since = "1.0.0")] pub const NEG_INFINITY: f64 = -1.0_f64 / 0.0_f64; From 27a083375e5d86ef773c4240884e38633cb0e13a Mon Sep 17 00:00:00 2001 From: jumbatm Date: Mon, 30 Dec 2019 13:59:23 +1000 Subject: [PATCH 2/9] Add self to mailmap. --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 8b5e9f96b3d8d..1634c2da518cc 100644 --- a/.mailmap +++ b/.mailmap @@ -139,6 +139,7 @@ Jorge Aparicio Joseph Martin Joseph T. Lyons Joseph T. Lyons +jumbatm <30644300+jumbatm@users.noreply.github.com> Junyoung Cho Jyun-Yan You Kang Seonghoon From 751fe7c43d428595d965d22ff21cc3c39dbccd5a Mon Sep 17 00:00:00 2001 From: Donough Liu Date: Mon, 30 Dec 2019 14:41:46 +0800 Subject: [PATCH 3/9] Add error code explanation for E0477 --- src/librustc_error_codes/error_codes.rs | 2 +- src/librustc_error_codes/error_codes/E0477.md | 45 +++++++++++++++++++ src/test/ui/issues/issue-26217.stderr | 1 + src/test/ui/issues/issue-54943.stderr | 1 + .../ui/kindck/kindck-impl-type-params.stderr | 3 +- src/test/ui/kindck/kindck-send-object1.stderr | 3 +- ...s-bounded-by-trait-requiring-static.stderr | 1 + ...ions-bounded-method-type-parameters.stderr | 1 + 8 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 src/librustc_error_codes/error_codes/E0477.md diff --git a/src/librustc_error_codes/error_codes.rs b/src/librustc_error_codes/error_codes.rs index 18d58d9d19e2c..272147e28a419 100644 --- a/src/librustc_error_codes/error_codes.rs +++ b/src/librustc_error_codes/error_codes.rs @@ -238,6 +238,7 @@ E0463: include_str!("./error_codes/E0463.md"), E0466: include_str!("./error_codes/E0466.md"), E0468: include_str!("./error_codes/E0468.md"), E0469: include_str!("./error_codes/E0469.md"), +E0477: include_str!("./error_codes/E0477.md"), E0478: include_str!("./error_codes/E0478.md"), E0491: include_str!("./error_codes/E0491.md"), E0492: include_str!("./error_codes/E0492.md"), @@ -531,7 +532,6 @@ E0745: include_str!("./error_codes/E0745.md"), E0474, // captured variable `..` does not outlive the enclosing closure E0475, // index of slice outside its lifetime E0476, // lifetime of the source pointer does not outlive lifetime bound... - E0477, // the type `..` does not fulfill the required lifetime... E0479, // the type `..` (provided as the value of a type parameter) is... E0480, // lifetime of method receiver does not outlive the method call E0481, // lifetime of function argument does not outlive the function call diff --git a/src/librustc_error_codes/error_codes/E0477.md b/src/librustc_error_codes/error_codes/E0477.md new file mode 100644 index 0000000000000..29ce534334598 --- /dev/null +++ b/src/librustc_error_codes/error_codes/E0477.md @@ -0,0 +1,45 @@ +The type does not fulfill the required lifetime. + +Erroneous code example: + +```compile_fail,E0477 +use std::sync::Mutex; + +struct MyString<'a> { + data: &'a str, +} + +fn i_want_static_closure(a: F) + where F: Fn() + 'static {} + +fn print_string<'a>(s: Mutex>) { + + i_want_static_closure(move || { // error: this closure has lifetime 'a + // rather than 'static + println!("{}", s.lock().unwrap().data); + }); +} +``` + +In this example, the closure doesn't satisfy the `'static` lifetime constraint. +To fix this kind of error, you need to double check lifetime of the type. Here, +we can fix this problem by giving `s` a static lifetime: + +``` +use std::sync::Mutex; + +struct MyString<'a> { + data: &'a str, +} + +fn i_want_static_closure(a: F) + where F: Fn() + 'static {} + +fn print_string(s: Mutex>) { + + i_want_static_closure(move || { // error: this closure has lifetime 'a + // rather than 'static + println!("{}", s.lock().unwrap().data); + }); +} +``` diff --git a/src/test/ui/issues/issue-26217.stderr b/src/test/ui/issues/issue-26217.stderr index 8bcc62ab2e73c..be9da569f8be1 100644 --- a/src/test/ui/issues/issue-26217.stderr +++ b/src/test/ui/issues/issue-26217.stderr @@ -8,3 +8,4 @@ LL | foo::<&'a i32>(); error: aborting due to previous error +For more information about this error, try `rustc --explain E0477`. diff --git a/src/test/ui/issues/issue-54943.stderr b/src/test/ui/issues/issue-54943.stderr index d0f03f90c8330..62aacee811110 100644 --- a/src/test/ui/issues/issue-54943.stderr +++ b/src/test/ui/issues/issue-54943.stderr @@ -8,3 +8,4 @@ LL | let x = foo::<&'a u32>(); error: aborting due to previous error +For more information about this error, try `rustc --explain E0477`. diff --git a/src/test/ui/kindck/kindck-impl-type-params.stderr b/src/test/ui/kindck/kindck-impl-type-params.stderr index 777a553c2a58a..2075fdd311e61 100644 --- a/src/test/ui/kindck/kindck-impl-type-params.stderr +++ b/src/test/ui/kindck/kindck-impl-type-params.stderr @@ -76,4 +76,5 @@ LL | let a: Box> = t; error: aborting due to 7 previous errors -For more information about this error, try `rustc --explain E0277`. +Some errors have detailed explanations: E0277, E0477. +For more information about an error, try `rustc --explain E0277`. diff --git a/src/test/ui/kindck/kindck-send-object1.stderr b/src/test/ui/kindck/kindck-send-object1.stderr index 436b92637aaad..b2e89087e387f 100644 --- a/src/test/ui/kindck/kindck-send-object1.stderr +++ b/src/test/ui/kindck/kindck-send-object1.stderr @@ -33,4 +33,5 @@ LL | assert_send::>(); error: aborting due to 3 previous errors -For more information about this error, try `rustc --explain E0277`. +Some errors have detailed explanations: E0277, E0477. +For more information about an error, try `rustc --explain E0277`. diff --git a/src/test/ui/regions/regions-bounded-by-trait-requiring-static.stderr b/src/test/ui/regions/regions-bounded-by-trait-requiring-static.stderr index fcd7332cf39f9..c72d6483c28f4 100644 --- a/src/test/ui/regions/regions-bounded-by-trait-requiring-static.stderr +++ b/src/test/ui/regions/regions-bounded-by-trait-requiring-static.stderr @@ -48,3 +48,4 @@ LL | assert_send::<*mut &'a isize>(); error: aborting due to 6 previous errors +For more information about this error, try `rustc --explain E0477`. diff --git a/src/test/ui/regions/regions-bounded-method-type-parameters.stderr b/src/test/ui/regions/regions-bounded-method-type-parameters.stderr index f77f97f44f2b7..66b61b1349d2b 100644 --- a/src/test/ui/regions/regions-bounded-method-type-parameters.stderr +++ b/src/test/ui/regions/regions-bounded-method-type-parameters.stderr @@ -8,3 +8,4 @@ LL | Foo.some_method::<&'a isize>(); error: aborting due to previous error +For more information about this error, try `rustc --explain E0477`. From b4e1fbcf8147362ee82c8e82ff270ddc3eb08270 Mon Sep 17 00:00:00 2001 From: Donough Liu Date: Mon, 30 Dec 2019 17:14:08 +0800 Subject: [PATCH 4/9] Gammer fix --- src/librustc_error_codes/error_codes/E0477.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/librustc_error_codes/error_codes/E0477.md b/src/librustc_error_codes/error_codes/E0477.md index 29ce534334598..794456451ef33 100644 --- a/src/librustc_error_codes/error_codes/E0477.md +++ b/src/librustc_error_codes/error_codes/E0477.md @@ -21,9 +21,9 @@ fn print_string<'a>(s: Mutex>) { } ``` -In this example, the closure doesn't satisfy the `'static` lifetime constraint. -To fix this kind of error, you need to double check lifetime of the type. Here, -we can fix this problem by giving `s` a static lifetime: +In this example, the closure does not satisfy the `'static` lifetime constraint. +To fix this error, you need to double check the lifetime of the type. Here, we +can fix this problem by giving `s` a static lifetime: ``` use std::sync::Mutex; From ca5a10f53e0b0b6be42861ac057f03f9f9d27fc4 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Sun, 29 Dec 2019 10:59:10 +0100 Subject: [PATCH 5/9] Move region.rs to librustc_passes. --- src/{librustc/middle => librustc_passes}/region.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{librustc/middle => librustc_passes}/region.rs (100%) diff --git a/src/librustc/middle/region.rs b/src/librustc_passes/region.rs similarity index 100% rename from src/librustc/middle/region.rs rename to src/librustc_passes/region.rs From afcd5c16b7d268ca64ca274366276568d0844cc3 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Sun, 29 Dec 2019 10:59:15 +0100 Subject: [PATCH 6/9] Move region_scope_tree query to librustc_passes. --- src/librustc/middle/region.rs | 670 ++++++++++++++++++++++++++++ src/librustc_interface/passes.rs | 1 - src/librustc_passes/lib.rs | 2 + src/librustc_passes/region.rs | 729 +------------------------------ 4 files changed, 683 insertions(+), 719 deletions(-) create mode 100644 src/librustc/middle/region.rs diff --git a/src/librustc/middle/region.rs b/src/librustc/middle/region.rs new file mode 100644 index 0000000000000..7f9c4aec3e37e --- /dev/null +++ b/src/librustc/middle/region.rs @@ -0,0 +1,670 @@ +//! This file builds up the `ScopeTree`, which describes +//! the parent links in the region hierarchy. +//! +//! For more information about how MIR-based region-checking works, +//! see the [rustc guide]. +//! +//! [rustc guide]: https://rust-lang.github.io/rustc-guide/mir/borrowck.html + +use crate::hir; +use crate::hir::def_id::DefId; +use crate::hir::Node; +use crate::ich::{NodeIdHashingMode, StableHashingContext}; +use crate::ty::{self, DefIdTree, TyCtxt}; +use crate::util::nodemap::FxHashMap; + +use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc_index::vec::Idx; +use rustc_macros::HashStable; +use syntax_pos::{Span, DUMMY_SP}; + +use std::fmt; + +/// Represents a statically-describable scope that can be used to +/// bound the lifetime/region for values. +/// +/// `Node(node_id)`: Any AST node that has any scope at all has the +/// `Node(node_id)` scope. Other variants represent special cases not +/// immediately derivable from the abstract syntax tree structure. +/// +/// `DestructionScope(node_id)` represents the scope of destructors +/// implicitly-attached to `node_id` that run immediately after the +/// expression for `node_id` itself. Not every AST node carries a +/// `DestructionScope`, but those that are `terminating_scopes` do; +/// see discussion with `ScopeTree`. +/// +/// `Remainder { block, statement_index }` represents +/// the scope of user code running immediately after the initializer +/// expression for the indexed statement, until the end of the block. +/// +/// So: the following code can be broken down into the scopes beneath: +/// +/// ```text +/// let a = f().g( 'b: { let x = d(); let y = d(); x.h(y) } ) ; +/// +/// +-+ (D12.) +/// +-+ (D11.) +/// +---------+ (R10.) +/// +-+ (D9.) +/// +----------+ (M8.) +/// +----------------------+ (R7.) +/// +-+ (D6.) +/// +----------+ (M5.) +/// +-----------------------------------+ (M4.) +/// +--------------------------------------------------+ (M3.) +/// +--+ (M2.) +/// +-----------------------------------------------------------+ (M1.) +/// +/// (M1.): Node scope of the whole `let a = ...;` statement. +/// (M2.): Node scope of the `f()` expression. +/// (M3.): Node scope of the `f().g(..)` expression. +/// (M4.): Node scope of the block labeled `'b:`. +/// (M5.): Node scope of the `let x = d();` statement +/// (D6.): DestructionScope for temporaries created during M5. +/// (R7.): Remainder scope for block `'b:`, stmt 0 (let x = ...). +/// (M8.): Node scope of the `let y = d();` statement. +/// (D9.): DestructionScope for temporaries created during M8. +/// (R10.): Remainder scope for block `'b:`, stmt 1 (let y = ...). +/// (D11.): DestructionScope for temporaries and bindings from block `'b:`. +/// (D12.): DestructionScope for temporaries created during M1 (e.g., f()). +/// ``` +/// +/// Note that while the above picture shows the destruction scopes +/// as following their corresponding node scopes, in the internal +/// data structures of the compiler the destruction scopes are +/// represented as enclosing parents. This is sound because we use the +/// enclosing parent relationship just to ensure that referenced +/// values live long enough; phrased another way, the starting point +/// of each range is not really the important thing in the above +/// picture, but rather the ending point. +// +// FIXME(pnkfelix): this currently derives `PartialOrd` and `Ord` to +// placate the same deriving in `ty::FreeRegion`, but we may want to +// actually attach a more meaningful ordering to scopes than the one +// generated via deriving here. +#[derive( + Clone, + PartialEq, + PartialOrd, + Eq, + Ord, + Hash, + Copy, + RustcEncodable, + RustcDecodable, + HashStable +)] +pub struct Scope { + pub id: hir::ItemLocalId, + pub data: ScopeData, +} + +impl fmt::Debug for Scope { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.data { + ScopeData::Node => write!(fmt, "Node({:?})", self.id), + ScopeData::CallSite => write!(fmt, "CallSite({:?})", self.id), + ScopeData::Arguments => write!(fmt, "Arguments({:?})", self.id), + ScopeData::Destruction => write!(fmt, "Destruction({:?})", self.id), + ScopeData::Remainder(fsi) => write!( + fmt, + "Remainder {{ block: {:?}, first_statement_index: {}}}", + self.id, + fsi.as_u32(), + ), + } + } +} + +#[derive( + Clone, + PartialEq, + PartialOrd, + Eq, + Ord, + Hash, + Debug, + Copy, + RustcEncodable, + RustcDecodable, + HashStable +)] +pub enum ScopeData { + Node, + + /// Scope of the call-site for a function or closure + /// (outlives the arguments as well as the body). + CallSite, + + /// Scope of arguments passed to a function or closure + /// (they outlive its body). + Arguments, + + /// Scope of destructors for temporaries of node-id. + Destruction, + + /// Scope following a `let id = expr;` binding in a block. + Remainder(FirstStatementIndex), +} + +rustc_index::newtype_index! { + /// Represents a subscope of `block` for a binding that is introduced + /// by `block.stmts[first_statement_index]`. Such subscopes represent + /// a suffix of the block. Note that each subscope does not include + /// the initializer expression, if any, for the statement indexed by + /// `first_statement_index`. + /// + /// For example, given `{ let (a, b) = EXPR_1; let c = EXPR_2; ... }`: + /// + /// * The subscope with `first_statement_index == 0` is scope of both + /// `a` and `b`; it does not include EXPR_1, but does include + /// everything after that first `let`. (If you want a scope that + /// includes EXPR_1 as well, then do not use `Scope::Remainder`, + /// but instead another `Scope` that encompasses the whole block, + /// e.g., `Scope::Node`. + /// + /// * The subscope with `first_statement_index == 1` is scope of `c`, + /// and thus does not include EXPR_2, but covers the `...`. + pub struct FirstStatementIndex { + derive [HashStable] + } +} + +// compilation error if size of `ScopeData` is not the same as a `u32` +static_assert_size!(ScopeData, 4); + +impl Scope { + /// Returns a item-local ID associated with this scope. + /// + /// N.B., likely to be replaced as API is refined; e.g., pnkfelix + /// anticipates `fn entry_node_id` and `fn each_exit_node_id`. + pub fn item_local_id(&self) -> hir::ItemLocalId { + self.id + } + + pub fn hir_id(&self, scope_tree: &ScopeTree) -> hir::HirId { + match scope_tree.root_body { + Some(hir_id) => hir::HirId { owner: hir_id.owner, local_id: self.item_local_id() }, + None => hir::DUMMY_HIR_ID, + } + } + + /// Returns the span of this `Scope`. Note that in general the + /// returned span may not correspond to the span of any `NodeId` in + /// the AST. + pub fn span(&self, tcx: TyCtxt<'_>, scope_tree: &ScopeTree) -> Span { + let hir_id = self.hir_id(scope_tree); + if hir_id == hir::DUMMY_HIR_ID { + return DUMMY_SP; + } + let span = tcx.hir().span(hir_id); + if let ScopeData::Remainder(first_statement_index) = self.data { + if let Node::Block(ref blk) = tcx.hir().get(hir_id) { + // Want span for scope starting after the + // indexed statement and ending at end of + // `blk`; reuse span of `blk` and shift `lo` + // forward to end of indexed statement. + // + // (This is the special case aluded to in the + // doc-comment for this method) + + let stmt_span = blk.stmts[first_statement_index.index()].span; + + // To avoid issues with macro-generated spans, the span + // of the statement must be nested in that of the block. + if span.lo() <= stmt_span.lo() && stmt_span.lo() <= span.hi() { + return Span::new(stmt_span.lo(), span.hi(), span.ctxt()); + } + } + } + span + } +} + +pub type ScopeDepth = u32; + +/// The region scope tree encodes information about region relationships. +#[derive(Default, Debug)] +pub struct ScopeTree { + /// If not empty, this body is the root of this region hierarchy. + pub root_body: Option, + + /// The parent of the root body owner, if the latter is an + /// an associated const or method, as impls/traits can also + /// have lifetime parameters free in this body. + pub root_parent: Option, + + /// Maps from a scope ID to the enclosing scope id; + /// this is usually corresponding to the lexical nesting, though + /// in the case of closures the parent scope is the innermost + /// conditional expression or repeating block. (Note that the + /// enclosing scope ID for the block associated with a closure is + /// the closure itself.) + pub parent_map: FxHashMap, + + /// Maps from a variable or binding ID to the block in which that + /// variable is declared. + var_map: FxHashMap, + + /// Maps from a `NodeId` to the associated destruction scope (if any). + destruction_scopes: FxHashMap, + + /// `rvalue_scopes` includes entries for those expressions whose + /// cleanup scope is larger than the default. The map goes from the + /// expression ID to the cleanup scope id. For rvalues not present in + /// this table, the appropriate cleanup scope is the innermost + /// enclosing statement, conditional expression, or repeating + /// block (see `terminating_scopes`). + /// In constants, None is used to indicate that certain expressions + /// escape into 'static and should have no local cleanup scope. + rvalue_scopes: FxHashMap>, + + /// Encodes the hierarchy of fn bodies. Every fn body (including + /// closures) forms its own distinct region hierarchy, rooted in + /// the block that is the fn body. This map points from the ID of + /// that root block to the ID of the root block for the enclosing + /// fn, if any. Thus the map structures the fn bodies into a + /// hierarchy based on their lexical mapping. This is used to + /// handle the relationships between regions in a fn and in a + /// closure defined by that fn. See the "Modeling closures" + /// section of the README in infer::region_constraints for + /// more details. + closure_tree: FxHashMap, + + /// If there are any `yield` nested within a scope, this map + /// stores the `Span` of the last one and its index in the + /// postorder of the Visitor traversal on the HIR. + /// + /// HIR Visitor postorder indexes might seem like a peculiar + /// thing to care about. but it turns out that HIR bindings + /// and the temporary results of HIR expressions are never + /// storage-live at the end of HIR nodes with postorder indexes + /// lower than theirs, and therefore don't need to be suspended + /// at yield-points at these indexes. + /// + /// For an example, suppose we have some code such as: + /// ```rust,ignore (example) + /// foo(f(), yield y, bar(g())) + /// ``` + /// + /// With the HIR tree (calls numbered for expository purposes) + /// ``` + /// Call#0(foo, [Call#1(f), Yield(y), Call#2(bar, Call#3(g))]) + /// ``` + /// + /// Obviously, the result of `f()` was created before the yield + /// (and therefore needs to be kept valid over the yield) while + /// the result of `g()` occurs after the yield (and therefore + /// doesn't). If we want to infer that, we can look at the + /// postorder traversal: + /// ```plain,ignore + /// `foo` `f` Call#1 `y` Yield `bar` `g` Call#3 Call#2 Call#0 + /// ``` + /// + /// In which we can easily see that `Call#1` occurs before the yield, + /// and `Call#3` after it. + /// + /// To see that this method works, consider: + /// + /// Let `D` be our binding/temporary and `U` be our other HIR node, with + /// `HIR-postorder(U) < HIR-postorder(D)` (in our example, U would be + /// the yield and D would be one of the calls). Let's show that + /// `D` is storage-dead at `U`. + /// + /// Remember that storage-live/storage-dead refers to the state of + /// the *storage*, and does not consider moves/drop flags. + /// + /// Then: + /// 1. From the ordering guarantee of HIR visitors (see + /// `rustc::hir::intravisit`), `D` does not dominate `U`. + /// 2. Therefore, `D` is *potentially* storage-dead at `U` (because + /// we might visit `U` without ever getting to `D`). + /// 3. However, we guarantee that at each HIR point, each + /// binding/temporary is always either always storage-live + /// or always storage-dead. This is what is being guaranteed + /// by `terminating_scopes` including all blocks where the + /// count of executions is not guaranteed. + /// 4. By `2.` and `3.`, `D` is *statically* storage-dead at `U`, + /// QED. + /// + /// This property ought to not on (3) in an essential way -- it + /// is probably still correct even if we have "unrestricted" terminating + /// scopes. However, why use the complicated proof when a simple one + /// works? + /// + /// A subtle thing: `box` expressions, such as `box (&x, yield 2, &y)`. It + /// might seem that a `box` expression creates a `Box` temporary + /// when it *starts* executing, at `HIR-preorder(BOX-EXPR)`. That might + /// be true in the MIR desugaring, but it is not important in the semantics. + /// + /// The reason is that semantically, until the `box` expression returns, + /// the values are still owned by their containing expressions. So + /// we'll see that `&x`. + pub yield_in_scope: FxHashMap, + + /// The number of visit_expr and visit_pat calls done in the body. + /// Used to sanity check visit_expr/visit_pat call count when + /// calculating generator interiors. + pub body_expr_count: FxHashMap, +} + +#[derive(Debug, Copy, Clone, RustcEncodable, RustcDecodable, HashStable)] +pub struct YieldData { + /// The `Span` of the yield. + pub span: Span, + /// The number of expressions and patterns appearing before the `yield` in the body plus one. + pub expr_and_pat_count: usize, + pub source: hir::YieldSource, +} + +impl<'tcx> ScopeTree { + pub fn record_scope_parent(&mut self, child: Scope, parent: Option<(Scope, ScopeDepth)>) { + debug!("{:?}.parent = {:?}", child, parent); + + if let Some(p) = parent { + let prev = self.parent_map.insert(child, p); + assert!(prev.is_none()); + } + + // Record the destruction scopes for later so we can query them. + if let ScopeData::Destruction = child.data { + self.destruction_scopes.insert(child.item_local_id(), child); + } + } + + pub fn each_encl_scope(&self, mut e: E) + where + E: FnMut(Scope, Scope), + { + for (&child, &parent) in &self.parent_map { + e(child, parent.0) + } + } + + pub fn each_var_scope(&self, mut e: E) + where + E: FnMut(&hir::ItemLocalId, Scope), + { + for (child, &parent) in self.var_map.iter() { + e(child, parent) + } + } + + pub fn opt_destruction_scope(&self, n: hir::ItemLocalId) -> Option { + self.destruction_scopes.get(&n).cloned() + } + + /// Records that `sub_closure` is defined within `sup_closure`. These IDs + /// should be the ID of the block that is the fn body, which is + /// also the root of the region hierarchy for that fn. + pub fn record_closure_parent( + &mut self, + sub_closure: hir::ItemLocalId, + sup_closure: hir::ItemLocalId, + ) { + debug!( + "record_closure_parent(sub_closure={:?}, sup_closure={:?})", + sub_closure, sup_closure + ); + assert!(sub_closure != sup_closure); + let previous = self.closure_tree.insert(sub_closure, sup_closure); + assert!(previous.is_none()); + } + + pub fn record_var_scope(&mut self, var: hir::ItemLocalId, lifetime: Scope) { + debug!("record_var_scope(sub={:?}, sup={:?})", var, lifetime); + assert!(var != lifetime.item_local_id()); + self.var_map.insert(var, lifetime); + } + + pub fn record_rvalue_scope(&mut self, var: hir::ItemLocalId, lifetime: Option) { + debug!("record_rvalue_scope(sub={:?}, sup={:?})", var, lifetime); + if let Some(lifetime) = lifetime { + assert!(var != lifetime.item_local_id()); + } + self.rvalue_scopes.insert(var, lifetime); + } + + /// Returns the narrowest scope that encloses `id`, if any. + pub fn opt_encl_scope(&self, id: Scope) -> Option { + self.parent_map.get(&id).cloned().map(|(p, _)| p) + } + + /// Returns the narrowest scope that encloses `id`, if any. + #[allow(dead_code)] // used in cfg + pub fn encl_scope(&self, id: Scope) -> Scope { + self.opt_encl_scope(id).unwrap() + } + + /// Returns the lifetime of the local variable `var_id` + pub fn var_scope(&self, var_id: hir::ItemLocalId) -> Scope { + self.var_map + .get(&var_id) + .cloned() + .unwrap_or_else(|| bug!("no enclosing scope for id {:?}", var_id)) + } + + /// Returns the scope when the temp created by `expr_id` will be cleaned up. + pub fn temporary_scope(&self, expr_id: hir::ItemLocalId) -> Option { + // Check for a designated rvalue scope. + if let Some(&s) = self.rvalue_scopes.get(&expr_id) { + debug!("temporary_scope({:?}) = {:?} [custom]", expr_id, s); + return s; + } + + // Otherwise, locate the innermost terminating scope + // if there's one. Static items, for instance, won't + // have an enclosing scope, hence no scope will be + // returned. + let mut id = Scope { id: expr_id, data: ScopeData::Node }; + + while let Some(&(p, _)) = self.parent_map.get(&id) { + match p.data { + ScopeData::Destruction => { + debug!("temporary_scope({:?}) = {:?} [enclosing]", expr_id, id); + return Some(id); + } + _ => id = p, + } + } + + debug!("temporary_scope({:?}) = None", expr_id); + return None; + } + + /// Returns the lifetime of the variable `id`. + pub fn var_region(&self, id: hir::ItemLocalId) -> ty::RegionKind { + let scope = ty::ReScope(self.var_scope(id)); + debug!("var_region({:?}) = {:?}", id, scope); + scope + } + + pub fn scopes_intersect(&self, scope1: Scope, scope2: Scope) -> bool { + self.is_subscope_of(scope1, scope2) || self.is_subscope_of(scope2, scope1) + } + + /// Returns `true` if `subscope` is equal to or is lexically nested inside `superscope`, and + /// `false` otherwise. + pub fn is_subscope_of(&self, subscope: Scope, superscope: Scope) -> bool { + let mut s = subscope; + debug!("is_subscope_of({:?}, {:?})", subscope, superscope); + while superscope != s { + match self.opt_encl_scope(s) { + None => { + debug!("is_subscope_of({:?}, {:?}, s={:?})=false", subscope, superscope, s); + return false; + } + Some(scope) => s = scope, + } + } + + debug!("is_subscope_of({:?}, {:?})=true", subscope, superscope); + + return true; + } + + /// Returns the ID of the innermost containing body. + pub fn containing_body(&self, mut scope: Scope) -> Option { + loop { + if let ScopeData::CallSite = scope.data { + return Some(scope.item_local_id()); + } + + scope = self.opt_encl_scope(scope)?; + } + } + + /// Finds the nearest common ancestor of two scopes. That is, finds the + /// smallest scope which is greater than or equal to both `scope_a` and + /// `scope_b`. + pub fn nearest_common_ancestor(&self, scope_a: Scope, scope_b: Scope) -> Scope { + if scope_a == scope_b { + return scope_a; + } + + let mut a = scope_a; + let mut b = scope_b; + + // Get the depth of each scope's parent. If either scope has no parent, + // it must be the root, which means we can stop immediately because the + // root must be the nearest common ancestor. (In practice, this is + // moderately common.) + let (parent_a, parent_a_depth) = match self.parent_map.get(&a) { + Some(pd) => *pd, + None => return a, + }; + let (parent_b, parent_b_depth) = match self.parent_map.get(&b) { + Some(pd) => *pd, + None => return b, + }; + + if parent_a_depth > parent_b_depth { + // `a` is lower than `b`. Move `a` up until it's at the same depth + // as `b`. The first move up is trivial because we already found + // `parent_a` above; the loop does the remaining N-1 moves. + a = parent_a; + for _ in 0..(parent_a_depth - parent_b_depth - 1) { + a = self.parent_map.get(&a).unwrap().0; + } + } else if parent_b_depth > parent_a_depth { + // `b` is lower than `a`. + b = parent_b; + for _ in 0..(parent_b_depth - parent_a_depth - 1) { + b = self.parent_map.get(&b).unwrap().0; + } + } else { + // Both scopes are at the same depth, and we know they're not equal + // because that case was tested for at the top of this function. So + // we can trivially move them both up one level now. + assert!(parent_a_depth != 0); + a = parent_a; + b = parent_b; + } + + // Now both scopes are at the same level. We move upwards in lockstep + // until they match. In practice, this loop is almost always executed + // zero times because `a` is almost always a direct ancestor of `b` or + // vice versa. + while a != b { + a = self.parent_map.get(&a).unwrap().0; + b = self.parent_map.get(&b).unwrap().0; + } + + a + } + + /// Assuming that the provided region was defined within this `ScopeTree`, + /// returns the outermost `Scope` that the region outlives. + pub fn early_free_scope(&self, tcx: TyCtxt<'tcx>, br: &ty::EarlyBoundRegion) -> Scope { + let param_owner = tcx.parent(br.def_id).unwrap(); + + let param_owner_id = tcx.hir().as_local_hir_id(param_owner).unwrap(); + let scope = tcx + .hir() + .maybe_body_owned_by(param_owner_id) + .map(|body_id| tcx.hir().body(body_id).value.hir_id.local_id) + .unwrap_or_else(|| { + // The lifetime was defined on node that doesn't own a body, + // which in practice can only mean a trait or an impl, that + // is the parent of a method, and that is enforced below. + if Some(param_owner_id) != self.root_parent { + tcx.sess.delay_span_bug( + DUMMY_SP, + &format!( + "free_scope: {:?} not recognized by the \ + region scope tree for {:?} / {:?}", + param_owner, + self.root_parent.map(|id| tcx.hir().local_def_id(id)), + self.root_body.map(|hir_id| DefId::local(hir_id.owner)) + ), + ); + } + + // The trait/impl lifetime is in scope for the method's body. + self.root_body.unwrap().local_id + }); + + Scope { id: scope, data: ScopeData::CallSite } + } + + /// Assuming that the provided region was defined within this `ScopeTree`, + /// returns the outermost `Scope` that the region outlives. + pub fn free_scope(&self, tcx: TyCtxt<'tcx>, fr: &ty::FreeRegion) -> Scope { + let param_owner = match fr.bound_region { + ty::BoundRegion::BrNamed(def_id, _) => tcx.parent(def_id).unwrap(), + _ => fr.scope, + }; + + // Ensure that the named late-bound lifetimes were defined + // on the same function that they ended up being freed in. + assert_eq!(param_owner, fr.scope); + + let param_owner_id = tcx.hir().as_local_hir_id(param_owner).unwrap(); + let body_id = tcx.hir().body_owned_by(param_owner_id); + Scope { id: tcx.hir().body(body_id).value.hir_id.local_id, data: ScopeData::CallSite } + } + + /// Checks whether the given scope contains a `yield`. If so, + /// returns `Some((span, expr_count))` with the span of a yield we found and + /// the number of expressions and patterns appearing before the `yield` in the body + 1. + /// If there a are multiple yields in a scope, the one with the highest number is returned. + pub fn yield_in_scope(&self, scope: Scope) -> Option { + self.yield_in_scope.get(&scope).cloned() + } + + /// Gives the number of expressions visited in a body. + /// Used to sanity check visit_expr call count when + /// calculating generator interiors. + pub fn body_expr_count(&self, body_id: hir::BodyId) -> Option { + self.body_expr_count.get(&body_id).map(|r| *r) + } +} + +impl<'a> HashStable> for ScopeTree { + fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) { + let ScopeTree { + root_body, + root_parent, + ref body_expr_count, + ref parent_map, + ref var_map, + ref destruction_scopes, + ref rvalue_scopes, + ref closure_tree, + ref yield_in_scope, + } = *self; + + hcx.with_node_id_hashing_mode(NodeIdHashingMode::HashDefPath, |hcx| { + root_body.hash_stable(hcx, hasher); + root_parent.hash_stable(hcx, hasher); + }); + + body_expr_count.hash_stable(hcx, hasher); + parent_map.hash_stable(hcx, hasher); + var_map.hash_stable(hcx, hasher); + destruction_scopes.hash_stable(hcx, hasher); + rvalue_scopes.hash_stable(hcx, hasher); + closure_tree.hash_stable(hcx, hasher); + yield_in_scope.hash_stable(hcx, hasher); + } +} diff --git a/src/librustc_interface/passes.rs b/src/librustc_interface/passes.rs index b4522f4c66531..1ab285c216cbc 100644 --- a/src/librustc_interface/passes.rs +++ b/src/librustc_interface/passes.rs @@ -686,7 +686,6 @@ pub fn default_provide(providers: &mut ty::query::Providers<'_>) { stability::provide(providers); rustc_passes::provide(providers); rustc_traits::provide(providers); - middle::region::provide(providers); rustc_metadata::provide(providers); lint::provide(providers); rustc_lint::provide(providers); diff --git a/src/librustc_passes/lib.rs b/src/librustc_passes/lib.rs index da781f2bae528..8a10c8fe89d62 100644 --- a/src/librustc_passes/lib.rs +++ b/src/librustc_passes/lib.rs @@ -31,6 +31,7 @@ mod lib_features; mod liveness; pub mod loops; mod reachable; +mod region; pub fn provide(providers: &mut Providers<'_>) { check_const::provide(providers); @@ -41,4 +42,5 @@ pub fn provide(providers: &mut Providers<'_>) { liveness::provide(providers); intrinsicck::provide(providers); reachable::provide(providers); + region::provide(providers); } diff --git a/src/librustc_passes/region.rs b/src/librustc_passes/region.rs index 5126d3f7fdefe..b828e014cfc9a 100644 --- a/src/librustc_passes/region.rs +++ b/src/librustc_passes/region.rs @@ -6,362 +6,22 @@ //! //! [rustc guide]: https://rust-lang.github.io/rustc-guide/mir/borrowck.html -use crate::hir; -use crate::hir::def_id::DefId; -use crate::hir::intravisit::{self, NestedVisitorMap, Visitor}; -use crate::hir::Node; -use crate::hir::{Arm, Block, Expr, Local, Pat, PatKind, Stmt}; -use crate::ich::{NodeIdHashingMode, StableHashingContext}; -use crate::ty::query::Providers; -use crate::ty::{self, DefIdTree, TyCtxt}; -use crate::util::nodemap::{FxHashMap, FxHashSet}; - -use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc::hir; +use rustc::hir::def_id::DefId; +use rustc::hir::intravisit::{self, NestedVisitorMap, Visitor}; +use rustc::hir::Node; +use rustc::hir::{Arm, Block, Expr, Local, Pat, PatKind, Stmt}; +use rustc::middle::region::*; +use rustc::ty::query::Providers; +use rustc::ty::TyCtxt; +use rustc::util::nodemap::FxHashSet; + use rustc_index::vec::Idx; -use rustc_macros::HashStable; use syntax::source_map; -use syntax_pos::{Span, DUMMY_SP}; +use syntax_pos::Span; -use std::fmt; use std::mem; -/// Represents a statically-describable scope that can be used to -/// bound the lifetime/region for values. -/// -/// `Node(node_id)`: Any AST node that has any scope at all has the -/// `Node(node_id)` scope. Other variants represent special cases not -/// immediately derivable from the abstract syntax tree structure. -/// -/// `DestructionScope(node_id)` represents the scope of destructors -/// implicitly-attached to `node_id` that run immediately after the -/// expression for `node_id` itself. Not every AST node carries a -/// `DestructionScope`, but those that are `terminating_scopes` do; -/// see discussion with `ScopeTree`. -/// -/// `Remainder { block, statement_index }` represents -/// the scope of user code running immediately after the initializer -/// expression for the indexed statement, until the end of the block. -/// -/// So: the following code can be broken down into the scopes beneath: -/// -/// ```text -/// let a = f().g( 'b: { let x = d(); let y = d(); x.h(y) } ) ; -/// -/// +-+ (D12.) -/// +-+ (D11.) -/// +---------+ (R10.) -/// +-+ (D9.) -/// +----------+ (M8.) -/// +----------------------+ (R7.) -/// +-+ (D6.) -/// +----------+ (M5.) -/// +-----------------------------------+ (M4.) -/// +--------------------------------------------------+ (M3.) -/// +--+ (M2.) -/// +-----------------------------------------------------------+ (M1.) -/// -/// (M1.): Node scope of the whole `let a = ...;` statement. -/// (M2.): Node scope of the `f()` expression. -/// (M3.): Node scope of the `f().g(..)` expression. -/// (M4.): Node scope of the block labeled `'b:`. -/// (M5.): Node scope of the `let x = d();` statement -/// (D6.): DestructionScope for temporaries created during M5. -/// (R7.): Remainder scope for block `'b:`, stmt 0 (let x = ...). -/// (M8.): Node scope of the `let y = d();` statement. -/// (D9.): DestructionScope for temporaries created during M8. -/// (R10.): Remainder scope for block `'b:`, stmt 1 (let y = ...). -/// (D11.): DestructionScope for temporaries and bindings from block `'b:`. -/// (D12.): DestructionScope for temporaries created during M1 (e.g., f()). -/// ``` -/// -/// Note that while the above picture shows the destruction scopes -/// as following their corresponding node scopes, in the internal -/// data structures of the compiler the destruction scopes are -/// represented as enclosing parents. This is sound because we use the -/// enclosing parent relationship just to ensure that referenced -/// values live long enough; phrased another way, the starting point -/// of each range is not really the important thing in the above -/// picture, but rather the ending point. -// -// FIXME(pnkfelix): this currently derives `PartialOrd` and `Ord` to -// placate the same deriving in `ty::FreeRegion`, but we may want to -// actually attach a more meaningful ordering to scopes than the one -// generated via deriving here. -#[derive( - Clone, - PartialEq, - PartialOrd, - Eq, - Ord, - Hash, - Copy, - RustcEncodable, - RustcDecodable, - HashStable -)] -pub struct Scope { - pub id: hir::ItemLocalId, - pub data: ScopeData, -} - -impl fmt::Debug for Scope { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.data { - ScopeData::Node => write!(fmt, "Node({:?})", self.id), - ScopeData::CallSite => write!(fmt, "CallSite({:?})", self.id), - ScopeData::Arguments => write!(fmt, "Arguments({:?})", self.id), - ScopeData::Destruction => write!(fmt, "Destruction({:?})", self.id), - ScopeData::Remainder(fsi) => write!( - fmt, - "Remainder {{ block: {:?}, first_statement_index: {}}}", - self.id, - fsi.as_u32(), - ), - } - } -} - -#[derive( - Clone, - PartialEq, - PartialOrd, - Eq, - Ord, - Hash, - Debug, - Copy, - RustcEncodable, - RustcDecodable, - HashStable -)] -pub enum ScopeData { - Node, - - /// Scope of the call-site for a function or closure - /// (outlives the arguments as well as the body). - CallSite, - - /// Scope of arguments passed to a function or closure - /// (they outlive its body). - Arguments, - - /// Scope of destructors for temporaries of node-id. - Destruction, - - /// Scope following a `let id = expr;` binding in a block. - Remainder(FirstStatementIndex), -} - -rustc_index::newtype_index! { - /// Represents a subscope of `block` for a binding that is introduced - /// by `block.stmts[first_statement_index]`. Such subscopes represent - /// a suffix of the block. Note that each subscope does not include - /// the initializer expression, if any, for the statement indexed by - /// `first_statement_index`. - /// - /// For example, given `{ let (a, b) = EXPR_1; let c = EXPR_2; ... }`: - /// - /// * The subscope with `first_statement_index == 0` is scope of both - /// `a` and `b`; it does not include EXPR_1, but does include - /// everything after that first `let`. (If you want a scope that - /// includes EXPR_1 as well, then do not use `Scope::Remainder`, - /// but instead another `Scope` that encompasses the whole block, - /// e.g., `Scope::Node`. - /// - /// * The subscope with `first_statement_index == 1` is scope of `c`, - /// and thus does not include EXPR_2, but covers the `...`. - pub struct FirstStatementIndex { - derive [HashStable] - } -} - -// compilation error if size of `ScopeData` is not the same as a `u32` -static_assert_size!(ScopeData, 4); - -impl Scope { - /// Returns a item-local ID associated with this scope. - /// - /// N.B., likely to be replaced as API is refined; e.g., pnkfelix - /// anticipates `fn entry_node_id` and `fn each_exit_node_id`. - pub fn item_local_id(&self) -> hir::ItemLocalId { - self.id - } - - pub fn hir_id(&self, scope_tree: &ScopeTree) -> hir::HirId { - match scope_tree.root_body { - Some(hir_id) => hir::HirId { owner: hir_id.owner, local_id: self.item_local_id() }, - None => hir::DUMMY_HIR_ID, - } - } - - /// Returns the span of this `Scope`. Note that in general the - /// returned span may not correspond to the span of any `NodeId` in - /// the AST. - pub fn span(&self, tcx: TyCtxt<'_>, scope_tree: &ScopeTree) -> Span { - let hir_id = self.hir_id(scope_tree); - if hir_id == hir::DUMMY_HIR_ID { - return DUMMY_SP; - } - let span = tcx.hir().span(hir_id); - if let ScopeData::Remainder(first_statement_index) = self.data { - if let Node::Block(ref blk) = tcx.hir().get(hir_id) { - // Want span for scope starting after the - // indexed statement and ending at end of - // `blk`; reuse span of `blk` and shift `lo` - // forward to end of indexed statement. - // - // (This is the special case aluded to in the - // doc-comment for this method) - - let stmt_span = blk.stmts[first_statement_index.index()].span; - - // To avoid issues with macro-generated spans, the span - // of the statement must be nested in that of the block. - if span.lo() <= stmt_span.lo() && stmt_span.lo() <= span.hi() { - return Span::new(stmt_span.lo(), span.hi(), span.ctxt()); - } - } - } - span - } -} - -pub type ScopeDepth = u32; - -/// The region scope tree encodes information about region relationships. -#[derive(Default, Debug)] -pub struct ScopeTree { - /// If not empty, this body is the root of this region hierarchy. - root_body: Option, - - /// The parent of the root body owner, if the latter is an - /// an associated const or method, as impls/traits can also - /// have lifetime parameters free in this body. - root_parent: Option, - - /// Maps from a scope ID to the enclosing scope id; - /// this is usually corresponding to the lexical nesting, though - /// in the case of closures the parent scope is the innermost - /// conditional expression or repeating block. (Note that the - /// enclosing scope ID for the block associated with a closure is - /// the closure itself.) - parent_map: FxHashMap, - - /// Maps from a variable or binding ID to the block in which that - /// variable is declared. - var_map: FxHashMap, - - /// Maps from a `NodeId` to the associated destruction scope (if any). - destruction_scopes: FxHashMap, - - /// `rvalue_scopes` includes entries for those expressions whose - /// cleanup scope is larger than the default. The map goes from the - /// expression ID to the cleanup scope id. For rvalues not present in - /// this table, the appropriate cleanup scope is the innermost - /// enclosing statement, conditional expression, or repeating - /// block (see `terminating_scopes`). - /// In constants, None is used to indicate that certain expressions - /// escape into 'static and should have no local cleanup scope. - rvalue_scopes: FxHashMap>, - - /// Encodes the hierarchy of fn bodies. Every fn body (including - /// closures) forms its own distinct region hierarchy, rooted in - /// the block that is the fn body. This map points from the ID of - /// that root block to the ID of the root block for the enclosing - /// fn, if any. Thus the map structures the fn bodies into a - /// hierarchy based on their lexical mapping. This is used to - /// handle the relationships between regions in a fn and in a - /// closure defined by that fn. See the "Modeling closures" - /// section of the README in infer::region_constraints for - /// more details. - closure_tree: FxHashMap, - - /// If there are any `yield` nested within a scope, this map - /// stores the `Span` of the last one and its index in the - /// postorder of the Visitor traversal on the HIR. - /// - /// HIR Visitor postorder indexes might seem like a peculiar - /// thing to care about. but it turns out that HIR bindings - /// and the temporary results of HIR expressions are never - /// storage-live at the end of HIR nodes with postorder indexes - /// lower than theirs, and therefore don't need to be suspended - /// at yield-points at these indexes. - /// - /// For an example, suppose we have some code such as: - /// ```rust,ignore (example) - /// foo(f(), yield y, bar(g())) - /// ``` - /// - /// With the HIR tree (calls numbered for expository purposes) - /// ``` - /// Call#0(foo, [Call#1(f), Yield(y), Call#2(bar, Call#3(g))]) - /// ``` - /// - /// Obviously, the result of `f()` was created before the yield - /// (and therefore needs to be kept valid over the yield) while - /// the result of `g()` occurs after the yield (and therefore - /// doesn't). If we want to infer that, we can look at the - /// postorder traversal: - /// ```plain,ignore - /// `foo` `f` Call#1 `y` Yield `bar` `g` Call#3 Call#2 Call#0 - /// ``` - /// - /// In which we can easily see that `Call#1` occurs before the yield, - /// and `Call#3` after it. - /// - /// To see that this method works, consider: - /// - /// Let `D` be our binding/temporary and `U` be our other HIR node, with - /// `HIR-postorder(U) < HIR-postorder(D)` (in our example, U would be - /// the yield and D would be one of the calls). Let's show that - /// `D` is storage-dead at `U`. - /// - /// Remember that storage-live/storage-dead refers to the state of - /// the *storage*, and does not consider moves/drop flags. - /// - /// Then: - /// 1. From the ordering guarantee of HIR visitors (see - /// `rustc::hir::intravisit`), `D` does not dominate `U`. - /// 2. Therefore, `D` is *potentially* storage-dead at `U` (because - /// we might visit `U` without ever getting to `D`). - /// 3. However, we guarantee that at each HIR point, each - /// binding/temporary is always either always storage-live - /// or always storage-dead. This is what is being guaranteed - /// by `terminating_scopes` including all blocks where the - /// count of executions is not guaranteed. - /// 4. By `2.` and `3.`, `D` is *statically* storage-dead at `U`, - /// QED. - /// - /// This property ought to not on (3) in an essential way -- it - /// is probably still correct even if we have "unrestricted" terminating - /// scopes. However, why use the complicated proof when a simple one - /// works? - /// - /// A subtle thing: `box` expressions, such as `box (&x, yield 2, &y)`. It - /// might seem that a `box` expression creates a `Box` temporary - /// when it *starts* executing, at `HIR-preorder(BOX-EXPR)`. That might - /// be true in the MIR desugaring, but it is not important in the semantics. - /// - /// The reason is that semantically, until the `box` expression returns, - /// the values are still owned by their containing expressions. So - /// we'll see that `&x`. - yield_in_scope: FxHashMap, - - /// The number of visit_expr and visit_pat calls done in the body. - /// Used to sanity check visit_expr/visit_pat call count when - /// calculating generator interiors. - body_expr_count: FxHashMap, -} - -#[derive(Debug, Copy, Clone, RustcEncodable, RustcDecodable, HashStable)] -pub struct YieldData { - /// The `Span` of the yield. - pub span: Span, - /// The number of expressions and patterns appearing before the `yield` in the body plus one. - pub expr_and_pat_count: usize, - pub source: hir::YieldSource, -} - #[derive(Debug, Copy, Clone)] pub struct Context { /// The root of the current region tree. This is typically the id @@ -419,344 +79,6 @@ struct RegionResolutionVisitor<'tcx> { terminating_scopes: FxHashSet, } -struct ExprLocatorVisitor { - hir_id: hir::HirId, - result: Option, - expr_and_pat_count: usize, -} - -// This visitor has to have the same `visit_expr` calls as `RegionResolutionVisitor` -// since `expr_count` is compared against the results there. -impl<'tcx> Visitor<'tcx> for ExprLocatorVisitor { - fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> { - NestedVisitorMap::None - } - - fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) { - intravisit::walk_pat(self, pat); - - self.expr_and_pat_count += 1; - - if pat.hir_id == self.hir_id { - self.result = Some(self.expr_and_pat_count); - } - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - debug!("ExprLocatorVisitor - pre-increment {} expr = {:?}", self.expr_and_pat_count, expr); - - intravisit::walk_expr(self, expr); - - self.expr_and_pat_count += 1; - - debug!("ExprLocatorVisitor - post-increment {} expr = {:?}", self.expr_and_pat_count, expr); - - if expr.hir_id == self.hir_id { - self.result = Some(self.expr_and_pat_count); - } - } -} - -impl<'tcx> ScopeTree { - pub fn record_scope_parent(&mut self, child: Scope, parent: Option<(Scope, ScopeDepth)>) { - debug!("{:?}.parent = {:?}", child, parent); - - if let Some(p) = parent { - let prev = self.parent_map.insert(child, p); - assert!(prev.is_none()); - } - - // Record the destruction scopes for later so we can query them. - if let ScopeData::Destruction = child.data { - self.destruction_scopes.insert(child.item_local_id(), child); - } - } - - pub fn each_encl_scope(&self, mut e: E) - where - E: FnMut(Scope, Scope), - { - for (&child, &parent) in &self.parent_map { - e(child, parent.0) - } - } - - pub fn each_var_scope(&self, mut e: E) - where - E: FnMut(&hir::ItemLocalId, Scope), - { - for (child, &parent) in self.var_map.iter() { - e(child, parent) - } - } - - pub fn opt_destruction_scope(&self, n: hir::ItemLocalId) -> Option { - self.destruction_scopes.get(&n).cloned() - } - - /// Records that `sub_closure` is defined within `sup_closure`. These IDs - /// should be the ID of the block that is the fn body, which is - /// also the root of the region hierarchy for that fn. - fn record_closure_parent( - &mut self, - sub_closure: hir::ItemLocalId, - sup_closure: hir::ItemLocalId, - ) { - debug!( - "record_closure_parent(sub_closure={:?}, sup_closure={:?})", - sub_closure, sup_closure - ); - assert!(sub_closure != sup_closure); - let previous = self.closure_tree.insert(sub_closure, sup_closure); - assert!(previous.is_none()); - } - - fn record_var_scope(&mut self, var: hir::ItemLocalId, lifetime: Scope) { - debug!("record_var_scope(sub={:?}, sup={:?})", var, lifetime); - assert!(var != lifetime.item_local_id()); - self.var_map.insert(var, lifetime); - } - - fn record_rvalue_scope(&mut self, var: hir::ItemLocalId, lifetime: Option) { - debug!("record_rvalue_scope(sub={:?}, sup={:?})", var, lifetime); - if let Some(lifetime) = lifetime { - assert!(var != lifetime.item_local_id()); - } - self.rvalue_scopes.insert(var, lifetime); - } - - /// Returns the narrowest scope that encloses `id`, if any. - pub fn opt_encl_scope(&self, id: Scope) -> Option { - self.parent_map.get(&id).cloned().map(|(p, _)| p) - } - - /// Returns the narrowest scope that encloses `id`, if any. - #[allow(dead_code)] // used in cfg - pub fn encl_scope(&self, id: Scope) -> Scope { - self.opt_encl_scope(id).unwrap() - } - - /// Returns the lifetime of the local variable `var_id` - pub fn var_scope(&self, var_id: hir::ItemLocalId) -> Scope { - self.var_map - .get(&var_id) - .cloned() - .unwrap_or_else(|| bug!("no enclosing scope for id {:?}", var_id)) - } - - /// Returns the scope when the temp created by `expr_id` will be cleaned up. - pub fn temporary_scope(&self, expr_id: hir::ItemLocalId) -> Option { - // Check for a designated rvalue scope. - if let Some(&s) = self.rvalue_scopes.get(&expr_id) { - debug!("temporary_scope({:?}) = {:?} [custom]", expr_id, s); - return s; - } - - // Otherwise, locate the innermost terminating scope - // if there's one. Static items, for instance, won't - // have an enclosing scope, hence no scope will be - // returned. - let mut id = Scope { id: expr_id, data: ScopeData::Node }; - - while let Some(&(p, _)) = self.parent_map.get(&id) { - match p.data { - ScopeData::Destruction => { - debug!("temporary_scope({:?}) = {:?} [enclosing]", expr_id, id); - return Some(id); - } - _ => id = p, - } - } - - debug!("temporary_scope({:?}) = None", expr_id); - return None; - } - - /// Returns the lifetime of the variable `id`. - pub fn var_region(&self, id: hir::ItemLocalId) -> ty::RegionKind { - let scope = ty::ReScope(self.var_scope(id)); - debug!("var_region({:?}) = {:?}", id, scope); - scope - } - - pub fn scopes_intersect(&self, scope1: Scope, scope2: Scope) -> bool { - self.is_subscope_of(scope1, scope2) || self.is_subscope_of(scope2, scope1) - } - - /// Returns `true` if `subscope` is equal to or is lexically nested inside `superscope`, and - /// `false` otherwise. - pub fn is_subscope_of(&self, subscope: Scope, superscope: Scope) -> bool { - let mut s = subscope; - debug!("is_subscope_of({:?}, {:?})", subscope, superscope); - while superscope != s { - match self.opt_encl_scope(s) { - None => { - debug!("is_subscope_of({:?}, {:?}, s={:?})=false", subscope, superscope, s); - return false; - } - Some(scope) => s = scope, - } - } - - debug!("is_subscope_of({:?}, {:?})=true", subscope, superscope); - - return true; - } - - /// Returns the ID of the innermost containing body. - pub fn containing_body(&self, mut scope: Scope) -> Option { - loop { - if let ScopeData::CallSite = scope.data { - return Some(scope.item_local_id()); - } - - scope = self.opt_encl_scope(scope)?; - } - } - - /// Finds the nearest common ancestor of two scopes. That is, finds the - /// smallest scope which is greater than or equal to both `scope_a` and - /// `scope_b`. - pub fn nearest_common_ancestor(&self, scope_a: Scope, scope_b: Scope) -> Scope { - if scope_a == scope_b { - return scope_a; - } - - let mut a = scope_a; - let mut b = scope_b; - - // Get the depth of each scope's parent. If either scope has no parent, - // it must be the root, which means we can stop immediately because the - // root must be the nearest common ancestor. (In practice, this is - // moderately common.) - let (parent_a, parent_a_depth) = match self.parent_map.get(&a) { - Some(pd) => *pd, - None => return a, - }; - let (parent_b, parent_b_depth) = match self.parent_map.get(&b) { - Some(pd) => *pd, - None => return b, - }; - - if parent_a_depth > parent_b_depth { - // `a` is lower than `b`. Move `a` up until it's at the same depth - // as `b`. The first move up is trivial because we already found - // `parent_a` above; the loop does the remaining N-1 moves. - a = parent_a; - for _ in 0..(parent_a_depth - parent_b_depth - 1) { - a = self.parent_map.get(&a).unwrap().0; - } - } else if parent_b_depth > parent_a_depth { - // `b` is lower than `a`. - b = parent_b; - for _ in 0..(parent_b_depth - parent_a_depth - 1) { - b = self.parent_map.get(&b).unwrap().0; - } - } else { - // Both scopes are at the same depth, and we know they're not equal - // because that case was tested for at the top of this function. So - // we can trivially move them both up one level now. - assert!(parent_a_depth != 0); - a = parent_a; - b = parent_b; - } - - // Now both scopes are at the same level. We move upwards in lockstep - // until they match. In practice, this loop is almost always executed - // zero times because `a` is almost always a direct ancestor of `b` or - // vice versa. - while a != b { - a = self.parent_map.get(&a).unwrap().0; - b = self.parent_map.get(&b).unwrap().0; - } - - a - } - - /// Assuming that the provided region was defined within this `ScopeTree`, - /// returns the outermost `Scope` that the region outlives. - pub fn early_free_scope(&self, tcx: TyCtxt<'tcx>, br: &ty::EarlyBoundRegion) -> Scope { - let param_owner = tcx.parent(br.def_id).unwrap(); - - let param_owner_id = tcx.hir().as_local_hir_id(param_owner).unwrap(); - let scope = tcx - .hir() - .maybe_body_owned_by(param_owner_id) - .map(|body_id| tcx.hir().body(body_id).value.hir_id.local_id) - .unwrap_or_else(|| { - // The lifetime was defined on node that doesn't own a body, - // which in practice can only mean a trait or an impl, that - // is the parent of a method, and that is enforced below. - if Some(param_owner_id) != self.root_parent { - tcx.sess.delay_span_bug( - DUMMY_SP, - &format!( - "free_scope: {:?} not recognized by the \ - region scope tree for {:?} / {:?}", - param_owner, - self.root_parent.map(|id| tcx.hir().local_def_id(id)), - self.root_body.map(|hir_id| DefId::local(hir_id.owner)) - ), - ); - } - - // The trait/impl lifetime is in scope for the method's body. - self.root_body.unwrap().local_id - }); - - Scope { id: scope, data: ScopeData::CallSite } - } - - /// Assuming that the provided region was defined within this `ScopeTree`, - /// returns the outermost `Scope` that the region outlives. - pub fn free_scope(&self, tcx: TyCtxt<'tcx>, fr: &ty::FreeRegion) -> Scope { - let param_owner = match fr.bound_region { - ty::BoundRegion::BrNamed(def_id, _) => tcx.parent(def_id).unwrap(), - _ => fr.scope, - }; - - // Ensure that the named late-bound lifetimes were defined - // on the same function that they ended up being freed in. - assert_eq!(param_owner, fr.scope); - - let param_owner_id = tcx.hir().as_local_hir_id(param_owner).unwrap(); - let body_id = tcx.hir().body_owned_by(param_owner_id); - Scope { id: tcx.hir().body(body_id).value.hir_id.local_id, data: ScopeData::CallSite } - } - - /// Checks whether the given scope contains a `yield`. If so, - /// returns `Some((span, expr_count))` with the span of a yield we found and - /// the number of expressions and patterns appearing before the `yield` in the body + 1. - /// If there a are multiple yields in a scope, the one with the highest number is returned. - pub fn yield_in_scope(&self, scope: Scope) -> Option { - self.yield_in_scope.get(&scope).cloned() - } - - /// Checks whether the given scope contains a `yield` and if that yield could execute - /// after `expr`. If so, it returns the span of that `yield`. - /// `scope` must be inside the body. - pub fn yield_in_scope_for_expr( - &self, - scope: Scope, - expr_hir_id: hir::HirId, - body: &'tcx hir::Body<'tcx>, - ) -> Option { - self.yield_in_scope(scope).and_then(|YieldData { span, expr_and_pat_count, .. }| { - let mut visitor = - ExprLocatorVisitor { hir_id: expr_hir_id, result: None, expr_and_pat_count: 0 }; - visitor.visit_body(body); - if expr_and_pat_count >= visitor.result.unwrap() { Some(span) } else { None } - }) - } - - /// Gives the number of expressions visited in a body. - /// Used to sanity check visit_expr call count when - /// calculating generator interiors. - pub fn body_expr_count(&self, body_id: hir::BodyId) -> Option { - self.body_expr_count.get(&body_id).map(|r| *r) - } -} - /// Records the lifetime of a local variable as `cx.var_parent` fn record_var_lifetime( visitor: &mut RegionResolutionVisitor<'_>, @@ -1505,32 +827,3 @@ fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree { pub fn provide(providers: &mut Providers<'_>) { *providers = Providers { region_scope_tree, ..*providers }; } - -impl<'a> HashStable> for ScopeTree { - fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) { - let ScopeTree { - root_body, - root_parent, - ref body_expr_count, - ref parent_map, - ref var_map, - ref destruction_scopes, - ref rvalue_scopes, - ref closure_tree, - ref yield_in_scope, - } = *self; - - hcx.with_node_id_hashing_mode(NodeIdHashingMode::HashDefPath, |hcx| { - root_body.hash_stable(hcx, hasher); - root_parent.hash_stable(hcx, hasher); - }); - - body_expr_count.hash_stable(hcx, hasher); - parent_map.hash_stable(hcx, hasher); - var_map.hash_stable(hcx, hasher); - destruction_scopes.hash_stable(hcx, hasher); - rvalue_scopes.hash_stable(hcx, hasher); - closure_tree.hash_stable(hcx, hasher); - yield_in_scope.hash_stable(hcx, hasher); - } -} From 176efc432363d544ce86082fa0e29eb9b3a22e4d Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Sun, 29 Dec 2019 11:16:22 +0100 Subject: [PATCH 7/9] Inert doc comments. --- src/librustc_passes/region.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/librustc_passes/region.rs b/src/librustc_passes/region.rs index b828e014cfc9a..7630e3e8950c1 100644 --- a/src/librustc_passes/region.rs +++ b/src/librustc_passes/region.rs @@ -512,6 +512,7 @@ fn resolve_local<'tcx>( /// Returns `true` if `pat` match the `P&` non-terminal. /// + /// ```text /// P& = ref X /// | StructName { ..., P&, ... } /// | VariantName(..., P&, ...) @@ -519,6 +520,7 @@ fn resolve_local<'tcx>( /// | ( ..., P&, ... ) /// | ... "|" P& "|" ... /// | box P& + /// ``` fn is_binding_pat(pat: &hir::Pat<'_>) -> bool { // Note that the code below looks for *explicit* refs only, that is, it won't // know about *implicit* refs as introduced in #42640. @@ -575,6 +577,7 @@ fn resolve_local<'tcx>( /// If `expr` matches the `E&` grammar, then records an extended rvalue scope as appropriate: /// + /// ```text /// E& = & ET /// | StructName { ..., f: E&, ... } /// | [ ..., E&, ... ] @@ -583,6 +586,7 @@ fn resolve_local<'tcx>( /// | box E& /// | E& as ... /// | ( E& ) + /// ``` fn record_rvalue_scope_if_borrow_expr<'tcx>( visitor: &mut RegionResolutionVisitor<'tcx>, expr: &hir::Expr<'_>, @@ -623,11 +627,13 @@ fn resolve_local<'tcx>( /// More formally, if `expr` matches the grammar `ET`, record the rvalue scope of the matching /// `` as `blk_id`: /// + /// ```text /// ET = *ET /// | ET[...] /// | ET.f /// | (ET) /// | + /// ``` /// /// Note: ET is intended to match "rvalues or places based on rvalues". fn record_rvalue_scope<'tcx>( From 5ed625a86eac34988d7a25e0cc98bf737d65603d Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Sun, 29 Dec 2019 14:41:39 +0100 Subject: [PATCH 8/9] Outdated comment. --- src/librustc/middle/region.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustc/middle/region.rs b/src/librustc/middle/region.rs index 7f9c4aec3e37e..a4c489735a96d 100644 --- a/src/librustc/middle/region.rs +++ b/src/librustc/middle/region.rs @@ -1,4 +1,4 @@ -//! This file builds up the `ScopeTree`, which describes +//! This file declares the `ScopeTree` type, which describes //! the parent links in the region hierarchy. //! //! For more information about how MIR-based region-checking works, From 208c1bff0aae2389c1eb497623da895605d4e2b1 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Mon, 30 Dec 2019 00:23:19 +0300 Subject: [PATCH 9/9] Support `-Z ui-testing=yes/no` --- src/librustc_session/config.rs | 6 ++++++ src/librustc_session/options.rs | 2 +- src/librustc_session/session.rs | 8 ++++---- src/librustdoc/config.rs | 2 +- src/librustdoc/lib.rs | 4 ++-- src/test/ui/ui-testing-optout.rs | 2 +- src/tools/compiletest/src/header.rs | 12 ------------ src/tools/compiletest/src/runtest.rs | 8 ++------ 8 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/librustc_session/config.rs b/src/librustc_session/config.rs index 4fce25cafad88..75bd6babe16ec 100644 --- a/src/librustc_session/config.rs +++ b/src/librustc_session/config.rs @@ -593,6 +593,12 @@ impl Options { } } +impl DebuggingOptions { + pub fn ui_testing(&self) -> bool { + self.ui_testing.unwrap_or(false) + } +} + // The type of entry function, so users can have their own entry functions #[derive(Copy, Clone, PartialEq, Hash, Debug)] pub enum EntryFnType { diff --git a/src/librustc_session/options.rs b/src/librustc_session/options.rs index 38c17bbbde797..3683daf7a87de 100644 --- a/src/librustc_session/options.rs +++ b/src/librustc_session/options.rs @@ -904,7 +904,7 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR)"), run_dsymutil: Option = (None, parse_opt_bool, [TRACKED], "run `dsymutil` and delete intermediate object files"), - ui_testing: bool = (false, parse_bool, [UNTRACKED], + ui_testing: Option = (None, parse_opt_bool, [UNTRACKED], "format compiler diagnostics in a way that's better suitable for UI testing"), embed_bitcode: bool = (false, parse_bool, [TRACKED], "embed LLVM bitcode in object files"), diff --git a/src/librustc_session/session.rs b/src/librustc_session/session.rs index 34149c22d2eed..8e9de69539a86 100644 --- a/src/librustc_session/session.rs +++ b/src/librustc_session/session.rs @@ -869,7 +869,7 @@ fn default_emitter( short, external_macro_backtrace, ); - Box::new(emitter.ui_testing(sopts.debugging_opts.ui_testing)) + Box::new(emitter.ui_testing(sopts.debugging_opts.ui_testing())) } else { let emitter = match dst { None => EmitterWriter::stderr( @@ -890,7 +890,7 @@ fn default_emitter( external_macro_backtrace, ), }; - Box::new(emitter.ui_testing(sopts.debugging_opts.ui_testing)) + Box::new(emitter.ui_testing(sopts.debugging_opts.ui_testing())) } } (config::ErrorOutputType::Json { pretty, json_rendered }, None) => Box::new( @@ -901,7 +901,7 @@ fn default_emitter( json_rendered, external_macro_backtrace, ) - .ui_testing(sopts.debugging_opts.ui_testing), + .ui_testing(sopts.debugging_opts.ui_testing()), ), (config::ErrorOutputType::Json { pretty, json_rendered }, Some(dst)) => Box::new( JsonEmitter::new( @@ -912,7 +912,7 @@ fn default_emitter( json_rendered, external_macro_backtrace, ) - .ui_testing(sopts.debugging_opts.ui_testing), + .ui_testing(sopts.debugging_opts.ui_testing()), ), } } diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 7a3cf88f65e21..25a892062fcbb 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -273,7 +273,7 @@ impl Options { error_format, None, debugging_options.treat_err_as_bug, - debugging_options.ui_testing, + debugging_options.ui_testing(), ); // check for deprecated options diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 0b4abe0a22e3b..d5cb17674f7b2 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -449,7 +449,7 @@ fn main_options(options: config::Options) -> i32 { options.error_format, None, options.debugging_options.treat_err_as_bug, - options.debugging_options.ui_testing, + options.debugging_options.ui_testing(), ); match (options.should_test, options.markdown_input()) { @@ -466,7 +466,7 @@ fn main_options(options: config::Options) -> i32 { let diag_opts = ( options.error_format, options.debugging_options.treat_err_as_bug, - options.debugging_options.ui_testing, + options.debugging_options.ui_testing(), options.edition, ); let show_coverage = options.show_coverage; diff --git a/src/test/ui/ui-testing-optout.rs b/src/test/ui/ui-testing-optout.rs index 041c0b0a85af7..901263c5bf8d2 100644 --- a/src/test/ui/ui-testing-optout.rs +++ b/src/test/ui/ui-testing-optout.rs @@ -1,4 +1,4 @@ -// disable-ui-testing-normalization +// compile-flags: -Z ui-testing=no // Line number < 10 type A = B; //~ ERROR diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 093ee662ce446..691b8d3ccfd39 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -376,8 +376,6 @@ pub struct TestProps { pub fail_mode: Option, // rustdoc will test the output of the `--test` option pub check_test_line_numbers_match: bool, - // Do not pass `-Z ui-testing` to UI tests - pub disable_ui_testing_normalization: bool, // customized normalization rules pub normalize_stdout: Vec<(String, String)>, pub normalize_stderr: Vec<(String, String)>, @@ -422,7 +420,6 @@ impl TestProps { fail_mode: None, ignore_pass: false, check_test_line_numbers_match: false, - disable_ui_testing_normalization: false, normalize_stdout: vec![], normalize_stderr: vec![], failure_status: -1, @@ -569,11 +566,6 @@ impl TestProps { self.ignore_pass = config.parse_ignore_pass(ln); } - if !self.disable_ui_testing_normalization { - self.disable_ui_testing_normalization = - config.parse_disable_ui_testing_normalization(ln); - } - if let Some(rule) = config.parse_custom_normalization(ln, "normalize-stdout") { self.normalize_stdout.push(rule); } @@ -826,10 +818,6 @@ impl Config { } } - fn parse_disable_ui_testing_normalization(&self, line: &str) -> bool { - self.parse_name_directive(line, "disable-ui-testing-normalization") - } - fn parse_check_test_line_numbers_match(&self, line: &str) -> bool { self.parse_name_directive(line, "check-test-line-numbers-match") } diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 02225d0ea0192..226a12c6734b7 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1863,17 +1863,13 @@ impl<'test> TestCx<'test> { if self.props.error_patterns.is_empty() { rustc.args(&["--error-format", "json"]); } - if !self.props.disable_ui_testing_normalization { - rustc.arg("-Zui-testing"); - } + rustc.arg("-Zui-testing"); } Ui => { if !self.props.compile_flags.iter().any(|s| s.starts_with("--error-format")) { rustc.args(&["--error-format", "json"]); } - if !self.props.disable_ui_testing_normalization { - rustc.arg("-Zui-testing"); - } + rustc.arg("-Zui-testing"); } MirOpt => { rustc.args(&[