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 diff --git a/src/libcore/num/f32.rs b/src/libcore/num/f32.rs index f1f1bb13f0f24..505484c2a49dc 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 5f9dc541b7d91..8f3af42d25d80 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; diff --git a/src/librustc/middle/region.rs b/src/librustc/middle/region.rs index 5126d3f7fdefe..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, @@ -8,22 +8,17 @@ 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 crate::util::nodemap::FxHashMap; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_index::vec::Idx; use rustc_macros::HashStable; -use syntax::source_map; use syntax_pos::{Span, DUMMY_SP}; use std::fmt; -use std::mem; /// Represents a statically-describable scope that can be used to /// bound the lifetime/region for values. @@ -232,12 +227,12 @@ pub type ScopeDepth = u32; #[derive(Default, Debug)] pub struct ScopeTree { /// If not empty, this body is the root of this region hierarchy. - root_body: Option, + 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. - root_parent: Option, + pub root_parent: Option, /// Maps from a scope ID to the enclosing scope id; /// this is usually corresponding to the lexical nesting, though @@ -245,7 +240,7 @@ pub struct ScopeTree { /// 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, + pub parent_map: FxHashMap, /// Maps from a variable or binding ID to the block in which that /// variable is declared. @@ -345,12 +340,12 @@ pub struct ScopeTree { /// 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, + 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. - body_expr_count: FxHashMap, + pub body_expr_count: FxHashMap, } #[derive(Debug, Copy, Clone, RustcEncodable, RustcDecodable, HashStable)] @@ -362,101 +357,6 @@ pub struct YieldData { pub source: hir::YieldSource, } -#[derive(Debug, Copy, Clone)] -pub struct Context { - /// The root of the current region tree. This is typically the id - /// of the innermost fn body. Each fn forms its own disjoint tree - /// in the region hierarchy. These fn bodies are themselves - /// arranged into a tree. See the "Modeling closures" section of - /// the README in `infer::region_constraints` for more - /// details. - root_id: Option, - - /// The scope that contains any new variables declared, plus its depth in - /// the scope tree. - var_parent: Option<(Scope, ScopeDepth)>, - - /// Region parent of expressions, etc., plus its depth in the scope tree. - parent: Option<(Scope, ScopeDepth)>, -} - -struct RegionResolutionVisitor<'tcx> { - tcx: TyCtxt<'tcx>, - - // The number of expressions and patterns visited in the current body. - expr_and_pat_count: usize, - // When this is `true`, we record the `Scopes` we encounter - // when processing a Yield expression. This allows us to fix - // up their indices. - pessimistic_yield: bool, - // Stores scopes when `pessimistic_yield` is `true`. - fixup_scopes: Vec, - // The generated scope tree. - scope_tree: ScopeTree, - - cx: Context, - - /// `terminating_scopes` is a set containing the ids of each - /// statement, or conditional/repeating expression. These scopes - /// are calling "terminating scopes" because, when attempting to - /// find the scope of a temporary, by default we search up the - /// enclosing scopes until we encounter the terminating scope. A - /// conditional/repeating expression is one which is not - /// guaranteed to execute exactly once upon entering the parent - /// scope. This could be because the expression only executes - /// conditionally, such as the expression `b` in `a && b`, or - /// because the expression may execute many times, such as a loop - /// body. The reason that we distinguish such expressions is that, - /// upon exiting the parent scope, we cannot statically know how - /// many times the expression executed, and thus if the expression - /// creates temporaries we cannot know statically how many such - /// temporaries we would have to cleanup. Therefore, we ensure that - /// the temporaries never outlast the conditional/repeating - /// expression, preventing the need for dynamic checks and/or - /// arbitrary amounts of stack space. Terminating scopes end - /// up being contained in a DestructionScope that contains the - /// destructor's execution. - 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); @@ -497,7 +397,7 @@ impl<'tcx> ScopeTree { /// 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( + pub fn record_closure_parent( &mut self, sub_closure: hir::ItemLocalId, sup_closure: hir::ItemLocalId, @@ -511,13 +411,13 @@ impl<'tcx> ScopeTree { assert!(previous.is_none()); } - fn record_var_scope(&mut self, var: hir::ItemLocalId, lifetime: Scope) { + 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); } - fn record_rvalue_scope(&mut self, var: hir::ItemLocalId, lifetime: Option) { + 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()); @@ -732,23 +632,6 @@ impl<'tcx> ScopeTree { 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. @@ -757,755 +640,6 @@ impl<'tcx> ScopeTree { } } -/// Records the lifetime of a local variable as `cx.var_parent` -fn record_var_lifetime( - visitor: &mut RegionResolutionVisitor<'_>, - var_id: hir::ItemLocalId, - _sp: Span, -) { - match visitor.cx.var_parent { - None => { - // this can happen in extern fn declarations like - // - // extern fn isalnum(c: c_int) -> c_int - } - Some((parent_scope, _)) => visitor.scope_tree.record_var_scope(var_id, parent_scope), - } -} - -fn resolve_block<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, blk: &'tcx hir::Block<'tcx>) { - debug!("resolve_block(blk.hir_id={:?})", blk.hir_id); - - let prev_cx = visitor.cx; - - // We treat the tail expression in the block (if any) somewhat - // differently from the statements. The issue has to do with - // temporary lifetimes. Consider the following: - // - // quux({ - // let inner = ... (&bar()) ...; - // - // (... (&foo()) ...) // (the tail expression) - // }, other_argument()); - // - // Each of the statements within the block is a terminating - // scope, and thus a temporary (e.g., the result of calling - // `bar()` in the initializer expression for `let inner = ...;`) - // will be cleaned up immediately after its corresponding - // statement (i.e., `let inner = ...;`) executes. - // - // On the other hand, temporaries associated with evaluating the - // tail expression for the block are assigned lifetimes so that - // they will be cleaned up as part of the terminating scope - // *surrounding* the block expression. Here, the terminating - // scope for the block expression is the `quux(..)` call; so - // those temporaries will only be cleaned up *after* both - // `other_argument()` has run and also the call to `quux(..)` - // itself has returned. - - visitor.enter_node_scope_with_dtor(blk.hir_id.local_id); - visitor.cx.var_parent = visitor.cx.parent; - - { - // This block should be kept approximately in sync with - // `intravisit::walk_block`. (We manually walk the block, rather - // than call `walk_block`, in order to maintain precise - // index information.) - - for (i, statement) in blk.stmts.iter().enumerate() { - match statement.kind { - hir::StmtKind::Local(..) | hir::StmtKind::Item(..) => { - // Each declaration introduces a subscope for bindings - // introduced by the declaration; this subscope covers a - // suffix of the block. Each subscope in a block has the - // previous subscope in the block as a parent, except for - // the first such subscope, which has the block itself as a - // parent. - visitor.enter_scope(Scope { - id: blk.hir_id.local_id, - data: ScopeData::Remainder(FirstStatementIndex::new(i)), - }); - visitor.cx.var_parent = visitor.cx.parent; - } - hir::StmtKind::Expr(..) | hir::StmtKind::Semi(..) => {} - } - visitor.visit_stmt(statement) - } - walk_list!(visitor, visit_expr, &blk.expr); - } - - visitor.cx = prev_cx; -} - -fn resolve_arm<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, arm: &'tcx hir::Arm<'tcx>) { - let prev_cx = visitor.cx; - - visitor.enter_scope(Scope { id: arm.hir_id.local_id, data: ScopeData::Node }); - visitor.cx.var_parent = visitor.cx.parent; - - visitor.terminating_scopes.insert(arm.body.hir_id.local_id); - - if let Some(hir::Guard::If(ref expr)) = arm.guard { - visitor.terminating_scopes.insert(expr.hir_id.local_id); - } - - intravisit::walk_arm(visitor, arm); - - visitor.cx = prev_cx; -} - -fn resolve_pat<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, pat: &'tcx hir::Pat<'tcx>) { - visitor.record_child_scope(Scope { id: pat.hir_id.local_id, data: ScopeData::Node }); - - // If this is a binding then record the lifetime of that binding. - if let PatKind::Binding(..) = pat.kind { - record_var_lifetime(visitor, pat.hir_id.local_id, pat.span); - } - - debug!("resolve_pat - pre-increment {} pat = {:?}", visitor.expr_and_pat_count, pat); - - intravisit::walk_pat(visitor, pat); - - visitor.expr_and_pat_count += 1; - - debug!("resolve_pat - post-increment {} pat = {:?}", visitor.expr_and_pat_count, pat); -} - -fn resolve_stmt<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, stmt: &'tcx hir::Stmt<'tcx>) { - let stmt_id = stmt.hir_id.local_id; - debug!("resolve_stmt(stmt.id={:?})", stmt_id); - - // Every statement will clean up the temporaries created during - // execution of that statement. Therefore each statement has an - // associated destruction scope that represents the scope of the - // statement plus its destructors, and thus the scope for which - // regions referenced by the destructors need to survive. - visitor.terminating_scopes.insert(stmt_id); - - let prev_parent = visitor.cx.parent; - visitor.enter_node_scope_with_dtor(stmt_id); - - intravisit::walk_stmt(visitor, stmt); - - visitor.cx.parent = prev_parent; -} - -fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx hir::Expr<'tcx>) { - debug!("resolve_expr - pre-increment {} expr = {:?}", visitor.expr_and_pat_count, expr); - - let prev_cx = visitor.cx; - visitor.enter_node_scope_with_dtor(expr.hir_id.local_id); - - { - let terminating_scopes = &mut visitor.terminating_scopes; - let mut terminating = |id: hir::ItemLocalId| { - terminating_scopes.insert(id); - }; - match expr.kind { - // Conditional or repeating scopes are always terminating - // scopes, meaning that temporaries cannot outlive them. - // This ensures fixed size stacks. - hir::ExprKind::Binary( - source_map::Spanned { node: hir::BinOpKind::And, .. }, - _, - ref r, - ) - | hir::ExprKind::Binary( - source_map::Spanned { node: hir::BinOpKind::Or, .. }, - _, - ref r, - ) => { - // For shortcircuiting operators, mark the RHS as a terminating - // scope since it only executes conditionally. - terminating(r.hir_id.local_id); - } - - hir::ExprKind::Loop(ref body, _, _) => { - terminating(body.hir_id.local_id); - } - - hir::ExprKind::DropTemps(ref expr) => { - // `DropTemps(expr)` does not denote a conditional scope. - // Rather, we want to achieve the same behavior as `{ let _t = expr; _t }`. - terminating(expr.hir_id.local_id); - } - - hir::ExprKind::AssignOp(..) - | hir::ExprKind::Index(..) - | hir::ExprKind::Unary(..) - | hir::ExprKind::Call(..) - | hir::ExprKind::MethodCall(..) => { - // FIXME(https://github.com/rust-lang/rfcs/issues/811) Nested method calls - // - // The lifetimes for a call or method call look as follows: - // - // call.id - // - arg0.id - // - ... - // - argN.id - // - call.callee_id - // - // The idea is that call.callee_id represents *the time when - // the invoked function is actually running* and call.id - // represents *the time to prepare the arguments and make the - // call*. See the section "Borrows in Calls" borrowck/README.md - // for an extended explanation of why this distinction is - // important. - // - // record_superlifetime(new_cx, expr.callee_id); - } - - _ => {} - } - } - - let prev_pessimistic = visitor.pessimistic_yield; - - // Ordinarily, we can rely on the visit order of HIR intravisit - // to correspond to the actual execution order of statements. - // However, there's a weird corner case with compund assignment - // operators (e.g. `a += b`). The evaluation order depends on whether - // or not the operator is overloaded (e.g. whether or not a trait - // like AddAssign is implemented). - - // For primitive types (which, despite having a trait impl, don't actually - // end up calling it), the evluation order is right-to-left. For example, - // the following code snippet: - // - // let y = &mut 0; - // *{println!("LHS!"); y} += {println!("RHS!"); 1}; - // - // will print: - // - // RHS! - // LHS! - // - // However, if the operator is used on a non-primitive type, - // the evaluation order will be left-to-right, since the operator - // actually get desugared to a method call. For example, this - // nearly identical code snippet: - // - // let y = &mut String::new(); - // *{println!("LHS String"); y} += {println!("RHS String"); "hi"}; - // - // will print: - // LHS String - // RHS String - // - // To determine the actual execution order, we need to perform - // trait resolution. Unfortunately, we need to be able to compute - // yield_in_scope before type checking is even done, as it gets - // used by AST borrowcheck. - // - // Fortunately, we don't need to know the actual execution order. - // It suffices to know the 'worst case' order with respect to yields. - // Specifically, we need to know the highest 'expr_and_pat_count' - // that we could assign to the yield expression. To do this, - // we pick the greater of the two values from the left-hand - // and right-hand expressions. This makes us overly conservative - // about what types could possibly live across yield points, - // but we will never fail to detect that a type does actually - // live across a yield point. The latter part is critical - - // we're already overly conservative about what types will live - // across yield points, as the generated MIR will determine - // when things are actually live. However, for typecheck to work - // properly, we can't miss any types. - - match expr.kind { - // Manually recurse over closures, because they are the only - // case of nested bodies that share the parent environment. - hir::ExprKind::Closure(.., body, _, _) => { - let body = visitor.tcx.hir().body(body); - visitor.visit_body(body); - } - hir::ExprKind::AssignOp(_, ref left_expr, ref right_expr) => { - debug!( - "resolve_expr - enabling pessimistic_yield, was previously {}", - prev_pessimistic - ); - - let start_point = visitor.fixup_scopes.len(); - visitor.pessimistic_yield = true; - - // If the actual execution order turns out to be right-to-left, - // then we're fine. However, if the actual execution order is left-to-right, - // then we'll assign too low a count to any `yield` expressions - // we encounter in 'right_expression' - they should really occur after all of the - // expressions in 'left_expression'. - visitor.visit_expr(&right_expr); - visitor.pessimistic_yield = prev_pessimistic; - - debug!("resolve_expr - restoring pessimistic_yield to {}", prev_pessimistic); - visitor.visit_expr(&left_expr); - debug!("resolve_expr - fixing up counts to {}", visitor.expr_and_pat_count); - - // Remove and process any scopes pushed by the visitor - let target_scopes = visitor.fixup_scopes.drain(start_point..); - - for scope in target_scopes { - let mut yield_data = visitor.scope_tree.yield_in_scope.get_mut(&scope).unwrap(); - let count = yield_data.expr_and_pat_count; - let span = yield_data.span; - - // expr_and_pat_count never decreases. Since we recorded counts in yield_in_scope - // before walking the left-hand side, it should be impossible for the recorded - // count to be greater than the left-hand side count. - if count > visitor.expr_and_pat_count { - bug!( - "Encountered greater count {} at span {:?} - expected no greater than {}", - count, - span, - visitor.expr_and_pat_count - ); - } - let new_count = visitor.expr_and_pat_count; - debug!( - "resolve_expr - increasing count for scope {:?} from {} to {} at span {:?}", - scope, count, new_count, span - ); - - yield_data.expr_and_pat_count = new_count; - } - } - - _ => intravisit::walk_expr(visitor, expr), - } - - visitor.expr_and_pat_count += 1; - - debug!("resolve_expr post-increment {}, expr = {:?}", visitor.expr_and_pat_count, expr); - - if let hir::ExprKind::Yield(_, source) = &expr.kind { - // Mark this expr's scope and all parent scopes as containing `yield`. - let mut scope = Scope { id: expr.hir_id.local_id, data: ScopeData::Node }; - loop { - let data = YieldData { - span: expr.span, - expr_and_pat_count: visitor.expr_and_pat_count, - source: *source, - }; - visitor.scope_tree.yield_in_scope.insert(scope, data); - if visitor.pessimistic_yield { - debug!("resolve_expr in pessimistic_yield - marking scope {:?} for fixup", scope); - visitor.fixup_scopes.push(scope); - } - - // Keep traversing up while we can. - match visitor.scope_tree.parent_map.get(&scope) { - // Don't cross from closure bodies to their parent. - Some(&(superscope, _)) => match superscope.data { - ScopeData::CallSite => break, - _ => scope = superscope, - }, - None => break, - } - } - } - - visitor.cx = prev_cx; -} - -fn resolve_local<'tcx>( - visitor: &mut RegionResolutionVisitor<'tcx>, - pat: Option<&'tcx hir::Pat<'tcx>>, - init: Option<&'tcx hir::Expr<'tcx>>, -) { - debug!("resolve_local(pat={:?}, init={:?})", pat, init); - - let blk_scope = visitor.cx.var_parent.map(|(p, _)| p); - - // As an exception to the normal rules governing temporary - // lifetimes, initializers in a let have a temporary lifetime - // of the enclosing block. This means that e.g., a program - // like the following is legal: - // - // let ref x = HashMap::new(); - // - // Because the hash map will be freed in the enclosing block. - // - // We express the rules more formally based on 3 grammars (defined - // fully in the helpers below that implement them): - // - // 1. `E&`, which matches expressions like `&` that - // own a pointer into the stack. - // - // 2. `P&`, which matches patterns like `ref x` or `(ref x, ref - // y)` that produce ref bindings into the value they are - // matched against or something (at least partially) owned by - // the value they are matched against. (By partially owned, - // I mean that creating a binding into a ref-counted or managed value - // would still count.) - // - // 3. `ET`, which matches both rvalues like `foo()` as well as places - // based on rvalues like `foo().x[2].y`. - // - // A subexpression `` that appears in a let initializer - // `let pat [: ty] = expr` has an extended temporary lifetime if - // any of the following conditions are met: - // - // A. `pat` matches `P&` and `expr` matches `ET` - // (covers cases where `pat` creates ref bindings into an rvalue - // produced by `expr`) - // B. `ty` is a borrowed pointer and `expr` matches `ET` - // (covers cases where coercion creates a borrow) - // C. `expr` matches `E&` - // (covers cases `expr` borrows an rvalue that is then assigned - // to memory (at least partially) owned by the binding) - // - // Here are some examples hopefully giving an intuition where each - // rule comes into play and why: - // - // Rule A. `let (ref x, ref y) = (foo().x, 44)`. The rvalue `(22, 44)` - // would have an extended lifetime, but not `foo()`. - // - // Rule B. `let x = &foo().x`. The rvalue `foo()` would have extended - // lifetime. - // - // In some cases, multiple rules may apply (though not to the same - // rvalue). For example: - // - // let ref x = [&a(), &b()]; - // - // Here, the expression `[...]` has an extended lifetime due to rule - // A, but the inner rvalues `a()` and `b()` have an extended lifetime - // due to rule C. - - if let Some(expr) = init { - record_rvalue_scope_if_borrow_expr(visitor, &expr, blk_scope); - - if let Some(pat) = pat { - if is_binding_pat(pat) { - record_rvalue_scope(visitor, &expr, blk_scope); - } - } - } - - // Make sure we visit the initializer first, so expr_and_pat_count remains correct - if let Some(expr) = init { - visitor.visit_expr(expr); - } - if let Some(pat) = pat { - visitor.visit_pat(pat); - } - - /// Returns `true` if `pat` match the `P&` non-terminal. - /// - /// P& = ref X - /// | StructName { ..., P&, ... } - /// | VariantName(..., P&, ...) - /// | [ ..., P&, ... ] - /// | ( ..., 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. - // - // This is not a problem. For example, consider - // - // let (ref x, ref y) = (Foo { .. }, Bar { .. }); - // - // Due to the explicit refs on the left hand side, the below code would signal - // that the temporary value on the right hand side should live until the end of - // the enclosing block (as opposed to being dropped after the let is complete). - // - // To create an implicit ref, however, you must have a borrowed value on the RHS - // already, as in this example (which won't compile before #42640): - // - // let Foo { x, .. } = &Foo { x: ..., ... }; - // - // in place of - // - // let Foo { ref x, .. } = Foo { ... }; - // - // In the former case (the implicit ref version), the temporary is created by the - // & expression, and its lifetime would be extended to the end of the block (due - // to a different rule, not the below code). - match pat.kind { - PatKind::Binding(hir::BindingAnnotation::Ref, ..) - | PatKind::Binding(hir::BindingAnnotation::RefMut, ..) => true, - - PatKind::Struct(_, ref field_pats, _) => { - field_pats.iter().any(|fp| is_binding_pat(&fp.pat)) - } - - PatKind::Slice(ref pats1, ref pats2, ref pats3) => { - pats1.iter().any(|p| is_binding_pat(&p)) - || pats2.iter().any(|p| is_binding_pat(&p)) - || pats3.iter().any(|p| is_binding_pat(&p)) - } - - PatKind::Or(ref subpats) - | PatKind::TupleStruct(_, ref subpats, _) - | PatKind::Tuple(ref subpats, _) => subpats.iter().any(|p| is_binding_pat(&p)), - - PatKind::Box(ref subpat) => is_binding_pat(&subpat), - - PatKind::Ref(_, _) - | PatKind::Binding(hir::BindingAnnotation::Unannotated, ..) - | PatKind::Binding(hir::BindingAnnotation::Mutable, ..) - | PatKind::Wild - | PatKind::Path(_) - | PatKind::Lit(_) - | PatKind::Range(_, _, _) => false, - } - } - - /// If `expr` matches the `E&` grammar, then records an extended rvalue scope as appropriate: - /// - /// E& = & ET - /// | StructName { ..., f: E&, ... } - /// | [ ..., E&, ... ] - /// | ( ..., E&, ... ) - /// | {...; E&} - /// | box E& - /// | E& as ... - /// | ( E& ) - fn record_rvalue_scope_if_borrow_expr<'tcx>( - visitor: &mut RegionResolutionVisitor<'tcx>, - expr: &hir::Expr<'_>, - blk_id: Option, - ) { - match expr.kind { - hir::ExprKind::AddrOf(_, _, ref subexpr) => { - record_rvalue_scope_if_borrow_expr(visitor, &subexpr, blk_id); - record_rvalue_scope(visitor, &subexpr, blk_id); - } - hir::ExprKind::Struct(_, fields, _) => { - for field in fields { - record_rvalue_scope_if_borrow_expr(visitor, &field.expr, blk_id); - } - } - hir::ExprKind::Array(subexprs) | hir::ExprKind::Tup(subexprs) => { - for subexpr in subexprs { - record_rvalue_scope_if_borrow_expr(visitor, &subexpr, blk_id); - } - } - hir::ExprKind::Cast(ref subexpr, _) => { - record_rvalue_scope_if_borrow_expr(visitor, &subexpr, blk_id) - } - hir::ExprKind::Block(ref block, _) => { - if let Some(ref subexpr) = block.expr { - record_rvalue_scope_if_borrow_expr(visitor, &subexpr, blk_id); - } - } - _ => {} - } - } - - /// Applied to an expression `expr` if `expr` -- or something owned or partially owned by - /// `expr` -- is going to be indirectly referenced by a variable in a let statement. In that - /// case, the "temporary lifetime" or `expr` is extended to be the block enclosing the `let` - /// statement. - /// - /// More formally, if `expr` matches the grammar `ET`, record the rvalue scope of the matching - /// `` as `blk_id`: - /// - /// ET = *ET - /// | ET[...] - /// | ET.f - /// | (ET) - /// | - /// - /// Note: ET is intended to match "rvalues or places based on rvalues". - fn record_rvalue_scope<'tcx>( - visitor: &mut RegionResolutionVisitor<'tcx>, - expr: &hir::Expr<'_>, - blk_scope: Option, - ) { - let mut expr = expr; - loop { - // Note: give all the expressions matching `ET` with the - // extended temporary lifetime, not just the innermost rvalue, - // because in codegen if we must compile e.g., `*rvalue()` - // into a temporary, we request the temporary scope of the - // outer expression. - visitor.scope_tree.record_rvalue_scope(expr.hir_id.local_id, blk_scope); - - match expr.kind { - hir::ExprKind::AddrOf(_, _, ref subexpr) - | hir::ExprKind::Unary(hir::UnDeref, ref subexpr) - | hir::ExprKind::Field(ref subexpr, _) - | hir::ExprKind::Index(ref subexpr, _) => { - expr = &subexpr; - } - _ => { - return; - } - } - } - } -} - -impl<'tcx> RegionResolutionVisitor<'tcx> { - /// Records the current parent (if any) as the parent of `child_scope`. - /// Returns the depth of `child_scope`. - fn record_child_scope(&mut self, child_scope: Scope) -> ScopeDepth { - let parent = self.cx.parent; - self.scope_tree.record_scope_parent(child_scope, parent); - // If `child_scope` has no parent, it must be the root node, and so has - // a depth of 1. Otherwise, its depth is one more than its parent's. - parent.map_or(1, |(_p, d)| d + 1) - } - - /// Records the current parent (if any) as the parent of `child_scope`, - /// and sets `child_scope` as the new current parent. - fn enter_scope(&mut self, child_scope: Scope) { - let child_depth = self.record_child_scope(child_scope); - self.cx.parent = Some((child_scope, child_depth)); - } - - fn enter_node_scope_with_dtor(&mut self, id: hir::ItemLocalId) { - // If node was previously marked as a terminating scope during the - // recursive visit of its parent node in the AST, then we need to - // account for the destruction scope representing the scope of - // the destructors that run immediately after it completes. - if self.terminating_scopes.contains(&id) { - self.enter_scope(Scope { id, data: ScopeData::Destruction }); - } - self.enter_scope(Scope { id, data: ScopeData::Node }); - } -} - -impl<'tcx> Visitor<'tcx> for RegionResolutionVisitor<'tcx> { - fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> { - NestedVisitorMap::None - } - - fn visit_block(&mut self, b: &'tcx Block<'tcx>) { - resolve_block(self, b); - } - - fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) { - let body_id = body.id(); - let owner_id = self.tcx.hir().body_owner(body_id); - - debug!( - "visit_body(id={:?}, span={:?}, body.id={:?}, cx.parent={:?})", - owner_id, - self.tcx.sess.source_map().span_to_string(body.value.span), - body_id, - self.cx.parent - ); - - let outer_ec = mem::replace(&mut self.expr_and_pat_count, 0); - let outer_cx = self.cx; - let outer_ts = mem::take(&mut self.terminating_scopes); - self.terminating_scopes.insert(body.value.hir_id.local_id); - - if let Some(root_id) = self.cx.root_id { - self.scope_tree.record_closure_parent(body.value.hir_id.local_id, root_id); - } - self.cx.root_id = Some(body.value.hir_id.local_id); - - self.enter_scope(Scope { id: body.value.hir_id.local_id, data: ScopeData::CallSite }); - self.enter_scope(Scope { id: body.value.hir_id.local_id, data: ScopeData::Arguments }); - - // The arguments and `self` are parented to the fn. - self.cx.var_parent = self.cx.parent.take(); - for param in body.params { - self.visit_pat(¶m.pat); - } - - // The body of the every fn is a root scope. - self.cx.parent = self.cx.var_parent; - if self.tcx.hir().body_owner_kind(owner_id).is_fn_or_closure() { - self.visit_expr(&body.value) - } else { - // Only functions have an outer terminating (drop) scope, while - // temporaries in constant initializers may be 'static, but only - // according to rvalue lifetime semantics, using the same - // syntactical rules used for let initializers. - // - // e.g., in `let x = &f();`, the temporary holding the result from - // the `f()` call lives for the entirety of the surrounding block. - // - // Similarly, `const X: ... = &f();` would have the result of `f()` - // live for `'static`, implying (if Drop restrictions on constants - // ever get lifted) that the value *could* have a destructor, but - // it'd get leaked instead of the destructor running during the - // evaluation of `X` (if at all allowed by CTFE). - // - // However, `const Y: ... = g(&f());`, like `let y = g(&f());`, - // would *not* let the `f()` temporary escape into an outer scope - // (i.e., `'static`), which means that after `g` returns, it drops, - // and all the associated destruction scope rules apply. - self.cx.var_parent = None; - resolve_local(self, None, Some(&body.value)); - } - - if body.generator_kind.is_some() { - self.scope_tree.body_expr_count.insert(body_id, self.expr_and_pat_count); - } - - // Restore context we had at the start. - self.expr_and_pat_count = outer_ec; - self.cx = outer_cx; - self.terminating_scopes = outer_ts; - } - - fn visit_arm(&mut self, a: &'tcx Arm<'tcx>) { - resolve_arm(self, a); - } - fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) { - resolve_pat(self, p); - } - fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) { - resolve_stmt(self, s); - } - fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { - resolve_expr(self, ex); - } - fn visit_local(&mut self, l: &'tcx Local<'tcx>) { - resolve_local(self, Some(&l.pat), l.init.as_ref().map(|e| &**e)); - } -} - -fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree { - let closure_base_def_id = tcx.closure_base_def_id(def_id); - if closure_base_def_id != def_id { - return tcx.region_scope_tree(closure_base_def_id); - } - - let id = tcx.hir().as_local_hir_id(def_id).unwrap(); - let scope_tree = if let Some(body_id) = tcx.hir().maybe_body_owned_by(id) { - let mut visitor = RegionResolutionVisitor { - tcx, - scope_tree: ScopeTree::default(), - expr_and_pat_count: 0, - cx: Context { root_id: None, parent: None, var_parent: None }, - terminating_scopes: Default::default(), - pessimistic_yield: false, - fixup_scopes: vec![], - }; - - let body = tcx.hir().body(body_id); - visitor.scope_tree.root_body = Some(body.value.hir_id); - - // If the item is an associated const or a method, - // record its impl/trait parent, as it can also have - // lifetime parameters free in this body. - match tcx.hir().get(id) { - Node::ImplItem(_) | Node::TraitItem(_) => { - visitor.scope_tree.root_parent = Some(tcx.hir().get_parent_item(id)); - } - _ => {} - } - - visitor.visit_body(body); - - visitor.scope_tree - } else { - ScopeTree::default() - }; - - tcx.arena.alloc(scope_tree) -} - -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 { 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..794456451ef33 --- /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 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; + +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/librustc_interface/passes.rs b/src/librustc_interface/passes.rs index 57fd2fb6d2783..c30f3e68110e8 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 new file mode 100644 index 0000000000000..7630e3e8950c1 --- /dev/null +++ b/src/librustc_passes/region.rs @@ -0,0 +1,835 @@ +//! 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 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 syntax::source_map; +use syntax_pos::Span; + +use std::mem; + +#[derive(Debug, Copy, Clone)] +pub struct Context { + /// The root of the current region tree. This is typically the id + /// of the innermost fn body. Each fn forms its own disjoint tree + /// in the region hierarchy. These fn bodies are themselves + /// arranged into a tree. See the "Modeling closures" section of + /// the README in `infer::region_constraints` for more + /// details. + root_id: Option, + + /// The scope that contains any new variables declared, plus its depth in + /// the scope tree. + var_parent: Option<(Scope, ScopeDepth)>, + + /// Region parent of expressions, etc., plus its depth in the scope tree. + parent: Option<(Scope, ScopeDepth)>, +} + +struct RegionResolutionVisitor<'tcx> { + tcx: TyCtxt<'tcx>, + + // The number of expressions and patterns visited in the current body. + expr_and_pat_count: usize, + // When this is `true`, we record the `Scopes` we encounter + // when processing a Yield expression. This allows us to fix + // up their indices. + pessimistic_yield: bool, + // Stores scopes when `pessimistic_yield` is `true`. + fixup_scopes: Vec, + // The generated scope tree. + scope_tree: ScopeTree, + + cx: Context, + + /// `terminating_scopes` is a set containing the ids of each + /// statement, or conditional/repeating expression. These scopes + /// are calling "terminating scopes" because, when attempting to + /// find the scope of a temporary, by default we search up the + /// enclosing scopes until we encounter the terminating scope. A + /// conditional/repeating expression is one which is not + /// guaranteed to execute exactly once upon entering the parent + /// scope. This could be because the expression only executes + /// conditionally, such as the expression `b` in `a && b`, or + /// because the expression may execute many times, such as a loop + /// body. The reason that we distinguish such expressions is that, + /// upon exiting the parent scope, we cannot statically know how + /// many times the expression executed, and thus if the expression + /// creates temporaries we cannot know statically how many such + /// temporaries we would have to cleanup. Therefore, we ensure that + /// the temporaries never outlast the conditional/repeating + /// expression, preventing the need for dynamic checks and/or + /// arbitrary amounts of stack space. Terminating scopes end + /// up being contained in a DestructionScope that contains the + /// destructor's execution. + terminating_scopes: FxHashSet, +} + +/// Records the lifetime of a local variable as `cx.var_parent` +fn record_var_lifetime( + visitor: &mut RegionResolutionVisitor<'_>, + var_id: hir::ItemLocalId, + _sp: Span, +) { + match visitor.cx.var_parent { + None => { + // this can happen in extern fn declarations like + // + // extern fn isalnum(c: c_int) -> c_int + } + Some((parent_scope, _)) => visitor.scope_tree.record_var_scope(var_id, parent_scope), + } +} + +fn resolve_block<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, blk: &'tcx hir::Block<'tcx>) { + debug!("resolve_block(blk.hir_id={:?})", blk.hir_id); + + let prev_cx = visitor.cx; + + // We treat the tail expression in the block (if any) somewhat + // differently from the statements. The issue has to do with + // temporary lifetimes. Consider the following: + // + // quux({ + // let inner = ... (&bar()) ...; + // + // (... (&foo()) ...) // (the tail expression) + // }, other_argument()); + // + // Each of the statements within the block is a terminating + // scope, and thus a temporary (e.g., the result of calling + // `bar()` in the initializer expression for `let inner = ...;`) + // will be cleaned up immediately after its corresponding + // statement (i.e., `let inner = ...;`) executes. + // + // On the other hand, temporaries associated with evaluating the + // tail expression for the block are assigned lifetimes so that + // they will be cleaned up as part of the terminating scope + // *surrounding* the block expression. Here, the terminating + // scope for the block expression is the `quux(..)` call; so + // those temporaries will only be cleaned up *after* both + // `other_argument()` has run and also the call to `quux(..)` + // itself has returned. + + visitor.enter_node_scope_with_dtor(blk.hir_id.local_id); + visitor.cx.var_parent = visitor.cx.parent; + + { + // This block should be kept approximately in sync with + // `intravisit::walk_block`. (We manually walk the block, rather + // than call `walk_block`, in order to maintain precise + // index information.) + + for (i, statement) in blk.stmts.iter().enumerate() { + match statement.kind { + hir::StmtKind::Local(..) | hir::StmtKind::Item(..) => { + // Each declaration introduces a subscope for bindings + // introduced by the declaration; this subscope covers a + // suffix of the block. Each subscope in a block has the + // previous subscope in the block as a parent, except for + // the first such subscope, which has the block itself as a + // parent. + visitor.enter_scope(Scope { + id: blk.hir_id.local_id, + data: ScopeData::Remainder(FirstStatementIndex::new(i)), + }); + visitor.cx.var_parent = visitor.cx.parent; + } + hir::StmtKind::Expr(..) | hir::StmtKind::Semi(..) => {} + } + visitor.visit_stmt(statement) + } + walk_list!(visitor, visit_expr, &blk.expr); + } + + visitor.cx = prev_cx; +} + +fn resolve_arm<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, arm: &'tcx hir::Arm<'tcx>) { + let prev_cx = visitor.cx; + + visitor.enter_scope(Scope { id: arm.hir_id.local_id, data: ScopeData::Node }); + visitor.cx.var_parent = visitor.cx.parent; + + visitor.terminating_scopes.insert(arm.body.hir_id.local_id); + + if let Some(hir::Guard::If(ref expr)) = arm.guard { + visitor.terminating_scopes.insert(expr.hir_id.local_id); + } + + intravisit::walk_arm(visitor, arm); + + visitor.cx = prev_cx; +} + +fn resolve_pat<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, pat: &'tcx hir::Pat<'tcx>) { + visitor.record_child_scope(Scope { id: pat.hir_id.local_id, data: ScopeData::Node }); + + // If this is a binding then record the lifetime of that binding. + if let PatKind::Binding(..) = pat.kind { + record_var_lifetime(visitor, pat.hir_id.local_id, pat.span); + } + + debug!("resolve_pat - pre-increment {} pat = {:?}", visitor.expr_and_pat_count, pat); + + intravisit::walk_pat(visitor, pat); + + visitor.expr_and_pat_count += 1; + + debug!("resolve_pat - post-increment {} pat = {:?}", visitor.expr_and_pat_count, pat); +} + +fn resolve_stmt<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, stmt: &'tcx hir::Stmt<'tcx>) { + let stmt_id = stmt.hir_id.local_id; + debug!("resolve_stmt(stmt.id={:?})", stmt_id); + + // Every statement will clean up the temporaries created during + // execution of that statement. Therefore each statement has an + // associated destruction scope that represents the scope of the + // statement plus its destructors, and thus the scope for which + // regions referenced by the destructors need to survive. + visitor.terminating_scopes.insert(stmt_id); + + let prev_parent = visitor.cx.parent; + visitor.enter_node_scope_with_dtor(stmt_id); + + intravisit::walk_stmt(visitor, stmt); + + visitor.cx.parent = prev_parent; +} + +fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + debug!("resolve_expr - pre-increment {} expr = {:?}", visitor.expr_and_pat_count, expr); + + let prev_cx = visitor.cx; + visitor.enter_node_scope_with_dtor(expr.hir_id.local_id); + + { + let terminating_scopes = &mut visitor.terminating_scopes; + let mut terminating = |id: hir::ItemLocalId| { + terminating_scopes.insert(id); + }; + match expr.kind { + // Conditional or repeating scopes are always terminating + // scopes, meaning that temporaries cannot outlive them. + // This ensures fixed size stacks. + hir::ExprKind::Binary( + source_map::Spanned { node: hir::BinOpKind::And, .. }, + _, + ref r, + ) + | hir::ExprKind::Binary( + source_map::Spanned { node: hir::BinOpKind::Or, .. }, + _, + ref r, + ) => { + // For shortcircuiting operators, mark the RHS as a terminating + // scope since it only executes conditionally. + terminating(r.hir_id.local_id); + } + + hir::ExprKind::Loop(ref body, _, _) => { + terminating(body.hir_id.local_id); + } + + hir::ExprKind::DropTemps(ref expr) => { + // `DropTemps(expr)` does not denote a conditional scope. + // Rather, we want to achieve the same behavior as `{ let _t = expr; _t }`. + terminating(expr.hir_id.local_id); + } + + hir::ExprKind::AssignOp(..) + | hir::ExprKind::Index(..) + | hir::ExprKind::Unary(..) + | hir::ExprKind::Call(..) + | hir::ExprKind::MethodCall(..) => { + // FIXME(https://github.com/rust-lang/rfcs/issues/811) Nested method calls + // + // The lifetimes for a call or method call look as follows: + // + // call.id + // - arg0.id + // - ... + // - argN.id + // - call.callee_id + // + // The idea is that call.callee_id represents *the time when + // the invoked function is actually running* and call.id + // represents *the time to prepare the arguments and make the + // call*. See the section "Borrows in Calls" borrowck/README.md + // for an extended explanation of why this distinction is + // important. + // + // record_superlifetime(new_cx, expr.callee_id); + } + + _ => {} + } + } + + let prev_pessimistic = visitor.pessimistic_yield; + + // Ordinarily, we can rely on the visit order of HIR intravisit + // to correspond to the actual execution order of statements. + // However, there's a weird corner case with compund assignment + // operators (e.g. `a += b`). The evaluation order depends on whether + // or not the operator is overloaded (e.g. whether or not a trait + // like AddAssign is implemented). + + // For primitive types (which, despite having a trait impl, don't actually + // end up calling it), the evluation order is right-to-left. For example, + // the following code snippet: + // + // let y = &mut 0; + // *{println!("LHS!"); y} += {println!("RHS!"); 1}; + // + // will print: + // + // RHS! + // LHS! + // + // However, if the operator is used on a non-primitive type, + // the evaluation order will be left-to-right, since the operator + // actually get desugared to a method call. For example, this + // nearly identical code snippet: + // + // let y = &mut String::new(); + // *{println!("LHS String"); y} += {println!("RHS String"); "hi"}; + // + // will print: + // LHS String + // RHS String + // + // To determine the actual execution order, we need to perform + // trait resolution. Unfortunately, we need to be able to compute + // yield_in_scope before type checking is even done, as it gets + // used by AST borrowcheck. + // + // Fortunately, we don't need to know the actual execution order. + // It suffices to know the 'worst case' order with respect to yields. + // Specifically, we need to know the highest 'expr_and_pat_count' + // that we could assign to the yield expression. To do this, + // we pick the greater of the two values from the left-hand + // and right-hand expressions. This makes us overly conservative + // about what types could possibly live across yield points, + // but we will never fail to detect that a type does actually + // live across a yield point. The latter part is critical - + // we're already overly conservative about what types will live + // across yield points, as the generated MIR will determine + // when things are actually live. However, for typecheck to work + // properly, we can't miss any types. + + match expr.kind { + // Manually recurse over closures, because they are the only + // case of nested bodies that share the parent environment. + hir::ExprKind::Closure(.., body, _, _) => { + let body = visitor.tcx.hir().body(body); + visitor.visit_body(body); + } + hir::ExprKind::AssignOp(_, ref left_expr, ref right_expr) => { + debug!( + "resolve_expr - enabling pessimistic_yield, was previously {}", + prev_pessimistic + ); + + let start_point = visitor.fixup_scopes.len(); + visitor.pessimistic_yield = true; + + // If the actual execution order turns out to be right-to-left, + // then we're fine. However, if the actual execution order is left-to-right, + // then we'll assign too low a count to any `yield` expressions + // we encounter in 'right_expression' - they should really occur after all of the + // expressions in 'left_expression'. + visitor.visit_expr(&right_expr); + visitor.pessimistic_yield = prev_pessimistic; + + debug!("resolve_expr - restoring pessimistic_yield to {}", prev_pessimistic); + visitor.visit_expr(&left_expr); + debug!("resolve_expr - fixing up counts to {}", visitor.expr_and_pat_count); + + // Remove and process any scopes pushed by the visitor + let target_scopes = visitor.fixup_scopes.drain(start_point..); + + for scope in target_scopes { + let mut yield_data = visitor.scope_tree.yield_in_scope.get_mut(&scope).unwrap(); + let count = yield_data.expr_and_pat_count; + let span = yield_data.span; + + // expr_and_pat_count never decreases. Since we recorded counts in yield_in_scope + // before walking the left-hand side, it should be impossible for the recorded + // count to be greater than the left-hand side count. + if count > visitor.expr_and_pat_count { + bug!( + "Encountered greater count {} at span {:?} - expected no greater than {}", + count, + span, + visitor.expr_and_pat_count + ); + } + let new_count = visitor.expr_and_pat_count; + debug!( + "resolve_expr - increasing count for scope {:?} from {} to {} at span {:?}", + scope, count, new_count, span + ); + + yield_data.expr_and_pat_count = new_count; + } + } + + _ => intravisit::walk_expr(visitor, expr), + } + + visitor.expr_and_pat_count += 1; + + debug!("resolve_expr post-increment {}, expr = {:?}", visitor.expr_and_pat_count, expr); + + if let hir::ExprKind::Yield(_, source) = &expr.kind { + // Mark this expr's scope and all parent scopes as containing `yield`. + let mut scope = Scope { id: expr.hir_id.local_id, data: ScopeData::Node }; + loop { + let data = YieldData { + span: expr.span, + expr_and_pat_count: visitor.expr_and_pat_count, + source: *source, + }; + visitor.scope_tree.yield_in_scope.insert(scope, data); + if visitor.pessimistic_yield { + debug!("resolve_expr in pessimistic_yield - marking scope {:?} for fixup", scope); + visitor.fixup_scopes.push(scope); + } + + // Keep traversing up while we can. + match visitor.scope_tree.parent_map.get(&scope) { + // Don't cross from closure bodies to their parent. + Some(&(superscope, _)) => match superscope.data { + ScopeData::CallSite => break, + _ => scope = superscope, + }, + None => break, + } + } + } + + visitor.cx = prev_cx; +} + +fn resolve_local<'tcx>( + visitor: &mut RegionResolutionVisitor<'tcx>, + pat: Option<&'tcx hir::Pat<'tcx>>, + init: Option<&'tcx hir::Expr<'tcx>>, +) { + debug!("resolve_local(pat={:?}, init={:?})", pat, init); + + let blk_scope = visitor.cx.var_parent.map(|(p, _)| p); + + // As an exception to the normal rules governing temporary + // lifetimes, initializers in a let have a temporary lifetime + // of the enclosing block. This means that e.g., a program + // like the following is legal: + // + // let ref x = HashMap::new(); + // + // Because the hash map will be freed in the enclosing block. + // + // We express the rules more formally based on 3 grammars (defined + // fully in the helpers below that implement them): + // + // 1. `E&`, which matches expressions like `&` that + // own a pointer into the stack. + // + // 2. `P&`, which matches patterns like `ref x` or `(ref x, ref + // y)` that produce ref bindings into the value they are + // matched against or something (at least partially) owned by + // the value they are matched against. (By partially owned, + // I mean that creating a binding into a ref-counted or managed value + // would still count.) + // + // 3. `ET`, which matches both rvalues like `foo()` as well as places + // based on rvalues like `foo().x[2].y`. + // + // A subexpression `` that appears in a let initializer + // `let pat [: ty] = expr` has an extended temporary lifetime if + // any of the following conditions are met: + // + // A. `pat` matches `P&` and `expr` matches `ET` + // (covers cases where `pat` creates ref bindings into an rvalue + // produced by `expr`) + // B. `ty` is a borrowed pointer and `expr` matches `ET` + // (covers cases where coercion creates a borrow) + // C. `expr` matches `E&` + // (covers cases `expr` borrows an rvalue that is then assigned + // to memory (at least partially) owned by the binding) + // + // Here are some examples hopefully giving an intuition where each + // rule comes into play and why: + // + // Rule A. `let (ref x, ref y) = (foo().x, 44)`. The rvalue `(22, 44)` + // would have an extended lifetime, but not `foo()`. + // + // Rule B. `let x = &foo().x`. The rvalue `foo()` would have extended + // lifetime. + // + // In some cases, multiple rules may apply (though not to the same + // rvalue). For example: + // + // let ref x = [&a(), &b()]; + // + // Here, the expression `[...]` has an extended lifetime due to rule + // A, but the inner rvalues `a()` and `b()` have an extended lifetime + // due to rule C. + + if let Some(expr) = init { + record_rvalue_scope_if_borrow_expr(visitor, &expr, blk_scope); + + if let Some(pat) = pat { + if is_binding_pat(pat) { + record_rvalue_scope(visitor, &expr, blk_scope); + } + } + } + + // Make sure we visit the initializer first, so expr_and_pat_count remains correct + if let Some(expr) = init { + visitor.visit_expr(expr); + } + if let Some(pat) = pat { + visitor.visit_pat(pat); + } + + /// Returns `true` if `pat` match the `P&` non-terminal. + /// + /// ```text + /// P& = ref X + /// | StructName { ..., P&, ... } + /// | VariantName(..., P&, ...) + /// | [ ..., P&, ... ] + /// | ( ..., 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. + // + // This is not a problem. For example, consider + // + // let (ref x, ref y) = (Foo { .. }, Bar { .. }); + // + // Due to the explicit refs on the left hand side, the below code would signal + // that the temporary value on the right hand side should live until the end of + // the enclosing block (as opposed to being dropped after the let is complete). + // + // To create an implicit ref, however, you must have a borrowed value on the RHS + // already, as in this example (which won't compile before #42640): + // + // let Foo { x, .. } = &Foo { x: ..., ... }; + // + // in place of + // + // let Foo { ref x, .. } = Foo { ... }; + // + // In the former case (the implicit ref version), the temporary is created by the + // & expression, and its lifetime would be extended to the end of the block (due + // to a different rule, not the below code). + match pat.kind { + PatKind::Binding(hir::BindingAnnotation::Ref, ..) + | PatKind::Binding(hir::BindingAnnotation::RefMut, ..) => true, + + PatKind::Struct(_, ref field_pats, _) => { + field_pats.iter().any(|fp| is_binding_pat(&fp.pat)) + } + + PatKind::Slice(ref pats1, ref pats2, ref pats3) => { + pats1.iter().any(|p| is_binding_pat(&p)) + || pats2.iter().any(|p| is_binding_pat(&p)) + || pats3.iter().any(|p| is_binding_pat(&p)) + } + + PatKind::Or(ref subpats) + | PatKind::TupleStruct(_, ref subpats, _) + | PatKind::Tuple(ref subpats, _) => subpats.iter().any(|p| is_binding_pat(&p)), + + PatKind::Box(ref subpat) => is_binding_pat(&subpat), + + PatKind::Ref(_, _) + | PatKind::Binding(hir::BindingAnnotation::Unannotated, ..) + | PatKind::Binding(hir::BindingAnnotation::Mutable, ..) + | PatKind::Wild + | PatKind::Path(_) + | PatKind::Lit(_) + | PatKind::Range(_, _, _) => false, + } + } + + /// If `expr` matches the `E&` grammar, then records an extended rvalue scope as appropriate: + /// + /// ```text + /// E& = & ET + /// | StructName { ..., f: E&, ... } + /// | [ ..., E&, ... ] + /// | ( ..., E&, ... ) + /// | {...; E&} + /// | box E& + /// | E& as ... + /// | ( E& ) + /// ``` + fn record_rvalue_scope_if_borrow_expr<'tcx>( + visitor: &mut RegionResolutionVisitor<'tcx>, + expr: &hir::Expr<'_>, + blk_id: Option, + ) { + match expr.kind { + hir::ExprKind::AddrOf(_, _, ref subexpr) => { + record_rvalue_scope_if_borrow_expr(visitor, &subexpr, blk_id); + record_rvalue_scope(visitor, &subexpr, blk_id); + } + hir::ExprKind::Struct(_, fields, _) => { + for field in fields { + record_rvalue_scope_if_borrow_expr(visitor, &field.expr, blk_id); + } + } + hir::ExprKind::Array(subexprs) | hir::ExprKind::Tup(subexprs) => { + for subexpr in subexprs { + record_rvalue_scope_if_borrow_expr(visitor, &subexpr, blk_id); + } + } + hir::ExprKind::Cast(ref subexpr, _) => { + record_rvalue_scope_if_borrow_expr(visitor, &subexpr, blk_id) + } + hir::ExprKind::Block(ref block, _) => { + if let Some(ref subexpr) = block.expr { + record_rvalue_scope_if_borrow_expr(visitor, &subexpr, blk_id); + } + } + _ => {} + } + } + + /// Applied to an expression `expr` if `expr` -- or something owned or partially owned by + /// `expr` -- is going to be indirectly referenced by a variable in a let statement. In that + /// case, the "temporary lifetime" or `expr` is extended to be the block enclosing the `let` + /// statement. + /// + /// 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>( + visitor: &mut RegionResolutionVisitor<'tcx>, + expr: &hir::Expr<'_>, + blk_scope: Option, + ) { + let mut expr = expr; + loop { + // Note: give all the expressions matching `ET` with the + // extended temporary lifetime, not just the innermost rvalue, + // because in codegen if we must compile e.g., `*rvalue()` + // into a temporary, we request the temporary scope of the + // outer expression. + visitor.scope_tree.record_rvalue_scope(expr.hir_id.local_id, blk_scope); + + match expr.kind { + hir::ExprKind::AddrOf(_, _, ref subexpr) + | hir::ExprKind::Unary(hir::UnDeref, ref subexpr) + | hir::ExprKind::Field(ref subexpr, _) + | hir::ExprKind::Index(ref subexpr, _) => { + expr = &subexpr; + } + _ => { + return; + } + } + } + } +} + +impl<'tcx> RegionResolutionVisitor<'tcx> { + /// Records the current parent (if any) as the parent of `child_scope`. + /// Returns the depth of `child_scope`. + fn record_child_scope(&mut self, child_scope: Scope) -> ScopeDepth { + let parent = self.cx.parent; + self.scope_tree.record_scope_parent(child_scope, parent); + // If `child_scope` has no parent, it must be the root node, and so has + // a depth of 1. Otherwise, its depth is one more than its parent's. + parent.map_or(1, |(_p, d)| d + 1) + } + + /// Records the current parent (if any) as the parent of `child_scope`, + /// and sets `child_scope` as the new current parent. + fn enter_scope(&mut self, child_scope: Scope) { + let child_depth = self.record_child_scope(child_scope); + self.cx.parent = Some((child_scope, child_depth)); + } + + fn enter_node_scope_with_dtor(&mut self, id: hir::ItemLocalId) { + // If node was previously marked as a terminating scope during the + // recursive visit of its parent node in the AST, then we need to + // account for the destruction scope representing the scope of + // the destructors that run immediately after it completes. + if self.terminating_scopes.contains(&id) { + self.enter_scope(Scope { id, data: ScopeData::Destruction }); + } + self.enter_scope(Scope { id, data: ScopeData::Node }); + } +} + +impl<'tcx> Visitor<'tcx> for RegionResolutionVisitor<'tcx> { + fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> { + NestedVisitorMap::None + } + + fn visit_block(&mut self, b: &'tcx Block<'tcx>) { + resolve_block(self, b); + } + + fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) { + let body_id = body.id(); + let owner_id = self.tcx.hir().body_owner(body_id); + + debug!( + "visit_body(id={:?}, span={:?}, body.id={:?}, cx.parent={:?})", + owner_id, + self.tcx.sess.source_map().span_to_string(body.value.span), + body_id, + self.cx.parent + ); + + let outer_ec = mem::replace(&mut self.expr_and_pat_count, 0); + let outer_cx = self.cx; + let outer_ts = mem::take(&mut self.terminating_scopes); + self.terminating_scopes.insert(body.value.hir_id.local_id); + + if let Some(root_id) = self.cx.root_id { + self.scope_tree.record_closure_parent(body.value.hir_id.local_id, root_id); + } + self.cx.root_id = Some(body.value.hir_id.local_id); + + self.enter_scope(Scope { id: body.value.hir_id.local_id, data: ScopeData::CallSite }); + self.enter_scope(Scope { id: body.value.hir_id.local_id, data: ScopeData::Arguments }); + + // The arguments and `self` are parented to the fn. + self.cx.var_parent = self.cx.parent.take(); + for param in body.params { + self.visit_pat(¶m.pat); + } + + // The body of the every fn is a root scope. + self.cx.parent = self.cx.var_parent; + if self.tcx.hir().body_owner_kind(owner_id).is_fn_or_closure() { + self.visit_expr(&body.value) + } else { + // Only functions have an outer terminating (drop) scope, while + // temporaries in constant initializers may be 'static, but only + // according to rvalue lifetime semantics, using the same + // syntactical rules used for let initializers. + // + // e.g., in `let x = &f();`, the temporary holding the result from + // the `f()` call lives for the entirety of the surrounding block. + // + // Similarly, `const X: ... = &f();` would have the result of `f()` + // live for `'static`, implying (if Drop restrictions on constants + // ever get lifted) that the value *could* have a destructor, but + // it'd get leaked instead of the destructor running during the + // evaluation of `X` (if at all allowed by CTFE). + // + // However, `const Y: ... = g(&f());`, like `let y = g(&f());`, + // would *not* let the `f()` temporary escape into an outer scope + // (i.e., `'static`), which means that after `g` returns, it drops, + // and all the associated destruction scope rules apply. + self.cx.var_parent = None; + resolve_local(self, None, Some(&body.value)); + } + + if body.generator_kind.is_some() { + self.scope_tree.body_expr_count.insert(body_id, self.expr_and_pat_count); + } + + // Restore context we had at the start. + self.expr_and_pat_count = outer_ec; + self.cx = outer_cx; + self.terminating_scopes = outer_ts; + } + + fn visit_arm(&mut self, a: &'tcx Arm<'tcx>) { + resolve_arm(self, a); + } + fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) { + resolve_pat(self, p); + } + fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) { + resolve_stmt(self, s); + } + fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { + resolve_expr(self, ex); + } + fn visit_local(&mut self, l: &'tcx Local<'tcx>) { + resolve_local(self, Some(&l.pat), l.init.as_ref().map(|e| &**e)); + } +} + +fn region_scope_tree(tcx: TyCtxt<'_>, def_id: DefId) -> &ScopeTree { + let closure_base_def_id = tcx.closure_base_def_id(def_id); + if closure_base_def_id != def_id { + return tcx.region_scope_tree(closure_base_def_id); + } + + let id = tcx.hir().as_local_hir_id(def_id).unwrap(); + let scope_tree = if let Some(body_id) = tcx.hir().maybe_body_owned_by(id) { + let mut visitor = RegionResolutionVisitor { + tcx, + scope_tree: ScopeTree::default(), + expr_and_pat_count: 0, + cx: Context { root_id: None, parent: None, var_parent: None }, + terminating_scopes: Default::default(), + pessimistic_yield: false, + fixup_scopes: vec![], + }; + + let body = tcx.hir().body(body_id); + visitor.scope_tree.root_body = Some(body.value.hir_id); + + // If the item is an associated const or a method, + // record its impl/trait parent, as it can also have + // lifetime parameters free in this body. + match tcx.hir().get(id) { + Node::ImplItem(_) | Node::TraitItem(_) => { + visitor.scope_tree.root_parent = Some(tcx.hir().get_parent_item(id)); + } + _ => {} + } + + visitor.visit_body(body); + + visitor.scope_tree + } else { + ScopeTree::default() + }; + + tcx.arena.alloc(scope_tree) +} + +pub fn provide(providers: &mut Providers<'_>) { + *providers = Providers { region_scope_tree, ..*providers }; +} 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 fb289f8ab8a27..b0496dabc7213 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/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`. 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(&[