From cafbd99c3822f24ca53c3cce79ffe2471e05dbc3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 30 Oct 2017 04:51:10 -0400 Subject: [PATCH] extend NLL regions to include free region indices and add outlives --- src/librustc_mir/dataflow/impls/borrows.rs | 6 +- .../transform/nll/constraint_generation.rs | 4 +- .../transform/nll/free_regions.rs | 88 +++++++++ src/librustc_mir/transform/nll/mod.rs | 30 ++- .../transform/nll/region_infer.rs | 171 ++++++++++-------- src/librustc_mir/transform/nll/renumber.rs | 106 ++++++++--- src/test/compile-fail/nll/region-error.rs | 5 + 7 files changed, 297 insertions(+), 113 deletions(-) create mode 100644 src/librustc_mir/transform/nll/free_regions.rs create mode 100644 src/test/compile-fail/nll/region-error.rs diff --git a/src/librustc_mir/dataflow/impls/borrows.rs b/src/librustc_mir/dataflow/impls/borrows.rs index 17aa8c054181..85f05a10cd9d 100644 --- a/src/librustc_mir/dataflow/impls/borrows.rs +++ b/src/librustc_mir/dataflow/impls/borrows.rs @@ -38,7 +38,7 @@ pub struct Borrows<'a, 'gcx: 'tcx, 'tcx: 'a> { location_map: FxHashMap, region_map: FxHashMap, FxHashSet>, region_span_map: FxHashMap, - nonlexical_regioncx: Option<&'a RegionInferenceContext>, + nonlexical_regioncx: Option<&'a RegionInferenceContext<'tcx>>, } // temporarily allow some dead fields: `kind` and `region` will be @@ -69,7 +69,7 @@ impl<'tcx> fmt::Display for BorrowData<'tcx> { impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> { pub fn new(tcx: TyCtxt<'a, 'gcx, 'tcx>, mir: &'a Mir<'tcx>, - nonlexical_regioncx: Option<&'a RegionInferenceContext>) + nonlexical_regioncx: Option<&'a RegionInferenceContext<'tcx>>) -> Self { let mut visitor = GatherBorrows { idx_vec: IndexVec::new(), location_map: FxHashMap(), @@ -140,7 +140,7 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> { if let Some(regioncx) = self.nonlexical_regioncx { for (borrow_index, borrow_data) in self.borrows.iter_enumerated() { let borrow_region = regioncx.region_value(borrow_data.region.to_region_index()); - if !borrow_region.may_contain(location) && location != borrow_data.location { + if !borrow_region.may_contain_point(location) && location != borrow_data.location { debug!("kill_loans_out_of_scope_at_location: kill{:?} \ location={:?} borrow_data={:?}", borrow_index, location, borrow_data); sets.kill(&borrow_index); diff --git a/src/librustc_mir/transform/nll/constraint_generation.rs b/src/librustc_mir/transform/nll/constraint_generation.rs index a7570c610d8d..f077dd4d1240 100644 --- a/src/librustc_mir/transform/nll/constraint_generation.rs +++ b/src/librustc_mir/transform/nll/constraint_generation.rs @@ -29,7 +29,7 @@ use super::region_infer::RegionInferenceContext; pub(super) fn generate_constraints<'a, 'gcx, 'tcx>( infcx: &InferCtxt<'a, 'gcx, 'tcx>, - regioncx: &mut RegionInferenceContext, + regioncx: &mut RegionInferenceContext<'tcx>, mir: &Mir<'tcx>, mir_source: MirSource, liveness: &LivenessResults, @@ -45,7 +45,7 @@ pub(super) fn generate_constraints<'a, 'gcx, 'tcx>( struct ConstraintGeneration<'cx, 'gcx: 'tcx, 'tcx: 'cx> { infcx: &'cx InferCtxt<'cx, 'gcx, 'tcx>, - regioncx: &'cx mut RegionInferenceContext, + regioncx: &'cx mut RegionInferenceContext<'tcx>, mir: &'cx Mir<'tcx>, liveness: &'cx LivenessResults, mir_source: MirSource, diff --git a/src/librustc_mir/transform/nll/free_regions.rs b/src/librustc_mir/transform/nll/free_regions.rs new file mode 100644 index 000000000000..006a2f9047ad --- /dev/null +++ b/src/librustc_mir/transform/nll/free_regions.rs @@ -0,0 +1,88 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Code to extract the free regions declared on a function and the +//! relationships between them. For example: +//! +//! ``` +//! fn foo<'a, 'b, 'c: 'b>() { } +//! ``` +//! +//! here we would be returning a map assigning each of `{'a, 'b, 'c}` +//! to an index, as well as the `FreeRegionMap` which can compute +//! relationships between them. +//! +//! The code in this file doesn't *do anything* with those results; it +//! just returns them for other code to use. + +use rustc::infer::InferCtxt; +use rustc::middle::free_region::FreeRegionMap; +use rustc::mir::transform::MirSource; +use rustc::ty; +use rustc::ty::subst::Substs; +use rustc::util::nodemap::FxHashMap; + +#[derive(Debug)] +pub struct FreeRegions<'tcx> { + /// Given a free region defined on this function (either early- or + /// late-bound), this maps it to its internal region index. The + /// corresponding variable will be "capped" so that it cannot + /// grow. + pub indices: FxHashMap, usize>, + + /// The map from the typeck tables telling us how to relate free regions. + pub free_region_map: &'tcx FreeRegionMap<'tcx>, +} + +pub fn free_regions<'a, 'gcx, 'tcx>( + infcx: &InferCtxt<'a, 'gcx, 'tcx>, + source: MirSource, +) -> FreeRegions<'tcx> { + debug!("free_regions(source={:?})", source); + + let item_id = source.item_id(); + let item_def_id = infcx.tcx.hir.local_def_id(item_id); + + let mut indices = FxHashMap(); + + // Extract the early regions. + let item_substs = Substs::identity_for_item(infcx.tcx, item_def_id); + for item_subst in item_substs { + if let Some(region) = item_subst.as_region() { + insert_free_region(&mut indices, region); + } + } + + // Extract the late-bound regions. Use the liberated fn sigs, + // where the late-bound regions will have been converted into free + // regions, and add them to the map. + let fn_hir_id = infcx.tcx.hir.node_to_hir_id(item_id); + let tables = infcx.tcx.typeck_tables_of(item_def_id); + let fn_sig = tables.liberated_fn_sigs()[fn_hir_id].clone(); + infcx + .tcx + .for_each_free_region(&fn_sig.inputs_and_output, |region| { + if let ty::ReFree(_) = *region { + insert_free_region(&mut indices, region); + } + }); + + debug!("free_regions: indices={:#?}", indices); + + FreeRegions { indices, free_region_map: &tables.free_region_map } +} + +fn insert_free_region<'tcx>( + free_regions: &mut FxHashMap, usize>, + region: ty::Region<'tcx>, +) { + let len = free_regions.len(); + free_regions.entry(region).or_insert(len); +} diff --git a/src/librustc_mir/transform/nll/mod.rs b/src/librustc_mir/transform/nll/mod.rs index d4938dc40bf2..2dd9b85c2946 100644 --- a/src/librustc_mir/transform/nll/mod.rs +++ b/src/librustc_mir/transform/nll/mod.rs @@ -22,6 +22,7 @@ use util as mir_util; use self::mir_util::PassWhere; mod constraint_generation; +mod free_regions; mod subtype; pub(crate) mod region_infer; @@ -36,9 +37,12 @@ pub fn compute_regions<'a, 'gcx, 'tcx>( infcx: &InferCtxt<'a, 'gcx, 'tcx>, source: MirSource, mir: &mut Mir<'tcx>, -) -> RegionInferenceContext { +) -> RegionInferenceContext<'tcx> { + // Compute named region information. + let free_regions = &free_regions::free_regions(infcx, source); + // Replace all regions with fresh inference variables. - let num_region_variables = renumber::renumber_mir(infcx, mir); + let num_region_variables = renumber::renumber_mir(infcx, free_regions, mir); // Compute what is live where. let liveness = &LivenessResults { @@ -61,11 +65,9 @@ pub fn compute_regions<'a, 'gcx, 'tcx>( // Create the region inference context, generate the constraints, // and then solve them. - let mut regioncx = RegionInferenceContext::new(num_region_variables); + let mut regioncx = RegionInferenceContext::new(free_regions, num_region_variables, mir); constraint_generation::generate_constraints(infcx, &mut regioncx, &mir, source, liveness); - let errors = regioncx.solve(infcx, &mir); - - assert!(errors.is_empty(), "FIXME: report region inference failures"); + regioncx.solve(infcx, &mir); // Dump MIR results into a file, if that is enabled. This let us // write unit-tests. @@ -152,11 +154,15 @@ fn dump_mir_results<'a, 'gcx, 'tcx>( #[derive(Clone, Default, PartialEq, Eq)] pub struct Region { points: BTreeSet, + free_regions: BTreeSet, } impl fmt::Debug for Region { fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(formatter, "{:?}", self.points) + formatter.debug_set() + .entries(&self.points) + .entries(&self.free_regions) + .finish() } } @@ -165,9 +171,17 @@ impl Region { self.points.insert(point) } - pub fn may_contain(&self, point: Location) -> bool { + pub fn add_free_region(&mut self, region: RegionIndex) -> bool { + self.free_regions.insert(region) + } + + pub fn may_contain_point(&self, point: Location) -> bool { self.points.contains(&point) } + + pub fn may_contain_free_region(&self, region: RegionIndex) -> bool { + self.free_regions.contains(®ion) + } } newtype_index!(RegionIndex { diff --git a/src/librustc_mir/transform/nll/region_infer.rs b/src/librustc_mir/transform/nll/region_infer.rs index b825528b1bc8..1027cf850901 100644 --- a/src/librustc_mir/transform/nll/region_infer.rs +++ b/src/librustc_mir/transform/nll/region_infer.rs @@ -9,37 +9,49 @@ // except according to those terms. use super::{Region, RegionIndex}; -use std::mem; +use super::free_regions::FreeRegions; use rustc::infer::InferCtxt; use rustc::mir::{Location, Mir}; -use rustc_data_structures::indexed_vec::IndexVec; +use rustc::ty; +use rustc_data_structures::indexed_vec::{Idx, IndexVec}; use rustc_data_structures::fx::FxHashSet; -pub struct RegionInferenceContext { +pub struct RegionInferenceContext<'tcx> { /// Contains the definition for every region variable. Region /// variables are identified by their index (`RegionIndex`). The /// definition contains information about where the region came /// from as well as its final inferred value. - definitions: IndexVec, + definitions: IndexVec>, + + /// The indices of all "free regions" in scope. These are the + /// lifetime parameters (anonymous and named) declared in the + /// function signature: + /// + /// fn foo<'a, 'b>(x: &Foo<'a, 'b>) + /// ^^ ^^ ^ + /// + /// These indices will be from 0..N, as it happens, but we collect + /// them into a vector for convenience. + free_regions: Vec, /// The constraints we have accumulated and used during solving. constraints: Vec, - - /// List of errors we have accumulated as we add constraints. - /// After solving is done, this is replaced with an empty vector. - errors: Vec, -} - -pub struct InferenceError { - pub constraint_point: Location, - pub name: (), // FIXME(nashenas88) RegionName } #[derive(Default)] -struct RegionDefinition { - name: (), // FIXME(nashenas88) RegionName +struct RegionDefinition<'tcx> { + /// If this is a free-region, then this is `Some(X)` where `X` is + /// the name of the region. + name: Option>, + + /// If true, this is a constant region which cannot grow larger. + /// This is used for named regions as well as `'static`. + constant: bool, + + /// The current value of this inference variable. This starts out + /// empty, but grows as we add constraints. The final value is + /// determined when `solve()` is executed. value: Region, - capped: bool, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -49,14 +61,73 @@ pub struct Constraint { point: Location, } -impl RegionInferenceContext { - pub fn new(num_region_variables: usize) -> Self { - Self { +impl<'tcx> RegionInferenceContext<'tcx> { + /// Creates a new region inference context with a total of + /// `num_region_variables` valid inference variables; the first N + /// of those will be constant regions representing the free + /// regions defined in `free_regions`. + pub fn new(free_regions: &FreeRegions<'tcx>, + num_region_variables: usize, + mir: &Mir<'tcx>) + -> Self { + let mut result = Self { definitions: (0..num_region_variables) .map(|_| RegionDefinition::default()) .collect(), constraints: Vec::new(), - errors: Vec::new(), + free_regions: Vec::new(), + }; + + result.init_free_regions(free_regions, mir); + + result + } + + fn init_free_regions(&mut self, + free_regions: &FreeRegions<'tcx>, + mir: &Mir<'tcx>) + { + let &FreeRegions { ref indices, ref free_region_map } = free_regions; + + // For each free region variable X, it should contain: + // + // (a) the entire CFG + // (b) `end(Y)` for all regions Y such that X: Y (or Y <= X) + // + // we add however the regions for clause (b) somewhat in + // reverse, because of how the data structure in + // `free_regions` is organized. + for (free_region, index) in indices { + let variable = RegionIndex::new(*index); + + self.free_regions.push(variable); + + self.definitions[variable].name = Some(free_region); + self.definitions[variable].constant = true; + + // Add all nodes in the CFG to `definition.value`. + for (block, block_data) in mir.basic_blocks().iter_enumerated() { + let definition = &mut self.definitions[variable]; + for statement_index in 0 .. block_data.statements.len() + 1 { + let location = Location { block, statement_index }; + definition.value.add_point(location); + } + } + + // Add `end(X)` into the set for X. + self.definitions[variable].value.add_free_region(variable); + + // Go through each region Y that outlives X (i.e., where + // Y: X is true). Add `end(X)` into the set for `Y`. + for superregion in free_region_map.regions_that_outlive(&free_region) { + let superregion_index = RegionIndex::new(indices[superregion]); + self.definitions[superregion_index].value.add_free_region(variable); + } + + debug!("init_free_regions: region variable for `{:?}` is `{:?}` with value `{:?}`", + free_region, + variable, + self.definitions[variable].value); } } @@ -72,42 +143,25 @@ impl RegionInferenceContext { &self.definitions[r].value } - /// Flags a region as being "capped" -- this means that if its - /// value is required to grow as a result of some constraint - /// (e.g., `add_live_point` or `add_outlives`), that indicates an - /// error. This is used for the regions representing named - /// lifetime parameters on a function: they get initialized to - /// their complete value, and then "capped" so that they can no - /// longer grow. - #[allow(dead_code)] - pub(super) fn cap_var(&mut self, v: RegionIndex) { - self.definitions[v].capped = true; - } - + /// Indicates that the region variable `v` is live at the point `point`. pub(super) fn add_live_point(&mut self, v: RegionIndex, point: Location) { debug!("add_live_point({:?}, {:?})", v, point); let definition = &mut self.definitions[v]; - if definition.value.add_point(point) { - if definition.capped { - self.errors.push(InferenceError { - constraint_point: point, - name: definition.name, - }); - } - } + definition.value.add_point(point); } + /// Indicates that the region variable `sup` must outlive `sub` is live at the point `point`. pub(super) fn add_outlives(&mut self, sup: RegionIndex, sub: RegionIndex, point: Location) { debug!("add_outlives({:?}: {:?} @ {:?}", sup, sub, point); self.constraints.push(Constraint { sup, sub, point }); } /// Perform region inference. - pub(super) fn solve<'a, 'gcx, 'tcx>( + pub(super) fn solve<'a, 'gcx>( &mut self, infcx: &'a InferCtxt<'a, 'gcx, 'tcx>, mir: &'a Mir<'tcx>, - ) -> Vec + ) where 'gcx: 'tcx + 'a, 'tcx: 'a, @@ -125,23 +179,6 @@ impl RegionInferenceContext { if dfs.copy(sub, &mut sup_def.value, constraint.point) { changed = true; - if sup_def.capped { - // This is kind of a hack, but when we add a - // constraint, the "point" is always the point - // AFTER the action that induced the - // constraint. So report the error on the - // action BEFORE that. - assert!(constraint.point.statement_index > 0); - let p = Location { - block: constraint.point.block, - statement_index: constraint.point.statement_index - 1, - }; - - self.errors.push(InferenceError { - constraint_point: p, - name: sup_def.name, - }); - } } debug!(" sup (after) : {:?}", sup_def.value); @@ -149,8 +186,6 @@ impl RegionInferenceContext { } debug!("\n"); } - - mem::replace(&mut self.errors, Vec::new()) } } @@ -179,7 +214,7 @@ impl<'a, 'gcx: 'tcx, 'tcx: 'a> Dfs<'a, 'gcx, 'tcx> { while let Some(p) = stack.pop() { debug!(" dfs: p={:?}", p); - if !from_region.may_contain(p) { + if !from_region.may_contain_point(p) { debug!(" not in from-region"); continue; } @@ -214,19 +249,11 @@ impl<'a, 'gcx: 'tcx, 'tcx: 'a> Dfs<'a, 'gcx, 'tcx> { }; if successor_points.is_empty() { - // FIXME handle free regions // If we reach the END point in the graph, then copy // over any skolemized end points in the `from_region` // and make sure they are included in the `to_region`. - // for region_decl in self.infcx.tcx.tables.borrow().free_region_map() { - // // FIXME(nashenas88) figure out skolemized_end points - // let block = self.env.graph.skolemized_end(region_decl.name); - // let skolemized_end_point = Location { - // block, - // statement_index: 0, - // }; - // changed |= to_region.add_point(skolemized_end_point); - // } + + to_region.free_regions.extend(&from_region.free_regions); } else { stack.extend(successor_points); } diff --git a/src/librustc_mir/transform/nll/renumber.rs b/src/librustc_mir/transform/nll/renumber.rs index 40ce44bae9df..a3ff7a041ca0 100644 --- a/src/librustc_mir/transform/nll/renumber.rs +++ b/src/librustc_mir/transform/nll/renumber.rs @@ -8,22 +8,40 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use rustc::ty::TypeFoldable; +use rustc_data_structures::indexed_vec::Idx; use rustc::ty::subst::{Kind, Substs}; -use rustc::ty::{Ty, ClosureSubsts, RegionVid, RegionKind}; -use rustc::mir::{Mir, Location, Rvalue, BasicBlock, Statement, StatementKind}; +use rustc::ty::{self, ClosureSubsts, RegionKind, RegionVid, Ty, TypeFoldable}; +use rustc::mir::{BasicBlock, Local, Location, Mir, Rvalue, Statement, StatementKind}; use rustc::mir::visit::{MutVisitor, TyContext}; use rustc::infer::{self as rustc_infer, InferCtxt}; use syntax_pos::DUMMY_SP; use std::collections::HashMap; +use super::free_regions::FreeRegions; + /// Replaces all free regions appearing in the MIR with fresh /// inference variables, returning the number of variables created. -pub fn renumber_mir<'a, 'gcx, 'tcx>(infcx: &InferCtxt<'a, 'gcx, 'tcx>, - mir: &mut Mir<'tcx>) - -> usize -{ - let mut visitor = NLLVisitor::new(infcx); +pub fn renumber_mir<'a, 'gcx, 'tcx>( + infcx: &InferCtxt<'a, 'gcx, 'tcx>, + free_regions: &FreeRegions<'tcx>, + mir: &mut Mir<'tcx>, +) -> usize { + // Create inference variables for each of the free regions + // declared on the function signature. + let free_region_inference_vars = (0..free_regions.indices.len()) + .map(|_| { + infcx.next_region_var(rustc_infer::MiscVariable(DUMMY_SP)) + }) + .collect(); + + let mut visitor = NLLVisitor { + infcx, + lookup_map: HashMap::new(), + num_region_variables: free_regions.indices.len(), + free_regions, + free_region_inference_vars, + arg_count: mir.arg_count, + }; visitor.visit_mir(mir); visitor.num_region_variables } @@ -32,22 +50,40 @@ struct NLLVisitor<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> { lookup_map: HashMap, num_region_variables: usize, infcx: &'a InferCtxt<'a, 'gcx, 'tcx>, + free_regions: &'a FreeRegions<'tcx>, + free_region_inference_vars: Vec>, + arg_count: usize, } impl<'a, 'gcx, 'tcx> NLLVisitor<'a, 'gcx, 'tcx> { - pub fn new(infcx: &'a InferCtxt<'a, 'gcx, 'tcx>) -> Self { - NLLVisitor { - infcx, - lookup_map: HashMap::new(), - num_region_variables: 0 - } + /// Replaces all regions appearing in `value` with fresh inference + /// variables. This is what we do for almost the entire MIR, with + /// the exception of the declared types of our arguments. + fn renumber_regions(&mut self, value: &T) -> T + where + T: TypeFoldable<'tcx>, + { + self.infcx + .tcx + .fold_regions(value, &mut false, |_region, _depth| { + self.num_region_variables += 1; + self.infcx + .next_region_var(rustc_infer::MiscVariable(DUMMY_SP)) + }) } - fn renumber_regions(&mut self, value: &T) -> T where T: TypeFoldable<'tcx> { - self.infcx.tcx.fold_regions(value, &mut false, |_region, _depth| { - self.num_region_variables += 1; - self.infcx.next_region_var(rustc_infer::MiscVariable(DUMMY_SP)) - }) + /// Renumbers the regions appearing in `value`, but those regions + /// are expected to be free regions from the function signature. + fn renumber_free_regions(&mut self, value: &T) -> T + where + T: TypeFoldable<'tcx>, + { + self.infcx + .tcx + .fold_regions(value, &mut false, |region, _depth| { + let index = self.free_regions.indices[®ion]; + self.free_region_inference_vars[index] + }) } fn store_region(&mut self, region: &RegionKind, lookup: TyContext) { @@ -69,17 +105,31 @@ impl<'a, 'gcx, 'tcx> NLLVisitor<'a, 'gcx, 'tcx> { self.store_region(region, ty_context); } } + + fn is_argument_or_return_slot(&self, local: Local) -> bool { + // The first argument is return slot, next N are arguments. + local.index() <= self.arg_count + } } impl<'a, 'gcx, 'tcx> MutVisitor<'tcx> for NLLVisitor<'a, 'gcx, 'tcx> { fn visit_ty(&mut self, ty: &mut Ty<'tcx>, ty_context: TyContext) { + let is_arg = match ty_context { + TyContext::LocalDecl { local, .. } => self.is_argument_or_return_slot(local), + _ => false, + }; + let old_ty = *ty; - *ty = self.renumber_regions(&old_ty); + *ty = if is_arg { + self.renumber_free_regions(&old_ty) + } else { + self.renumber_regions(&old_ty) + }; self.store_ty_regions(ty, ty_context); } fn visit_substs(&mut self, substs: &mut &'tcx Substs<'tcx>, location: Location) { - *substs = self.renumber_regions(&{*substs}); + *substs = self.renumber_regions(&{ *substs }); let ty_context = TyContext::Location(location); for kind in *substs { self.store_kind_regions(kind, ty_context); @@ -110,9 +160,7 @@ impl<'a, 'gcx, 'tcx> MutVisitor<'tcx> for NLLVisitor<'a, 'gcx, 'tcx> { self.super_rvalue(rvalue, location); } - fn visit_closure_substs(&mut self, - substs: &mut ClosureSubsts<'tcx>, - location: Location) { + fn visit_closure_substs(&mut self, substs: &mut ClosureSubsts<'tcx>, location: Location) { *substs = self.renumber_regions(substs); let ty_context = TyContext::Location(location); for kind in substs.substs { @@ -120,10 +168,12 @@ impl<'a, 'gcx, 'tcx> MutVisitor<'tcx> for NLLVisitor<'a, 'gcx, 'tcx> { } } - fn visit_statement(&mut self, - block: BasicBlock, - statement: &mut Statement<'tcx>, - location: Location) { + fn visit_statement( + &mut self, + block: BasicBlock, + statement: &mut Statement<'tcx>, + location: Location, + ) { if let StatementKind::EndRegion(_) = statement.kind { statement.kind = StatementKind::Nop; } diff --git a/src/test/compile-fail/nll/region-error.rs b/src/test/compile-fail/nll/region-error.rs new file mode 100644 index 000000000000..4a3e838beb34 --- /dev/null +++ b/src/test/compile-fail/nll/region-error.rs @@ -0,0 +1,5 @@ +fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'b u32 { + &*x +} + +fn main() { }