Skip to content

Commit b36917b

Browse files
committedJun 18, 2018
Auto merge of #51460 - nikomatsakis:nll-perf-examination-refactor-1, r=pnkfelix
Improve memoization and refactor NLL type check I have a big branch that is refactoring NLL type check with the goal of introducing canonicalization-based memoization for all of the operations it does. This PR contains an initial prefix of that branch which, I believe, stands alone. It does introduce a few smaller optimizations of its own: - Skip operations that are trivially a no-op - Cache the results of the dropck-outlives computations done by liveness - Skip resetting unifications if nothing changed r? @pnkfelix
2 parents 862703e + 2e25bed commit b36917b

File tree

14 files changed

+685
-481
lines changed

14 files changed

+685
-481
lines changed
 

‎src/librustc/infer/region_constraints/mod.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ pub struct RegionConstraintCollector<'tcx> {
6969
/// would wind up with a fresh stream of region variables that
7070
/// have been equated but appear distinct.
7171
unification_table: ut::UnificationTable<ut::InPlace<ty::RegionVid>>,
72+
73+
/// a flag set to true when we perform any unifications; this is used
74+
/// to micro-optimize `take_and_reset_data`
75+
any_unifications: bool,
7276
}
7377

7478
pub type VarInfos = IndexVec<RegionVid, RegionVariableInfo>;
@@ -234,6 +238,7 @@ pub struct RegionVariableInfo {
234238
pub struct RegionSnapshot {
235239
length: usize,
236240
region_snapshot: ut::Snapshot<ut::InPlace<ty::RegionVid>>,
241+
any_unifications: bool,
237242
}
238243

239244
/// When working with skolemized regions, we often wish to find all of
@@ -280,6 +285,7 @@ impl<'tcx> RegionConstraintCollector<'tcx> {
280285
bound_count: 0,
281286
undo_log: Vec::new(),
282287
unification_table: ut::UnificationTable::new(),
288+
any_unifications: false,
283289
}
284290
}
285291

@@ -325,6 +331,7 @@ impl<'tcx> RegionConstraintCollector<'tcx> {
325331
bound_count: _,
326332
undo_log: _,
327333
unification_table,
334+
any_unifications,
328335
} = self;
329336

330337
// Clear the tables of (lubs, glbs), so that we will create
@@ -338,7 +345,10 @@ impl<'tcx> RegionConstraintCollector<'tcx> {
338345
// un-unified" state. Note that when we unify `a` and `b`, we
339346
// also insert `a <= b` and a `b <= a` edges, so the
340347
// `RegionConstraintData` contains the relationship here.
341-
unification_table.reset_unifications(|vid| unify_key::RegionVidKey { min_vid: vid });
348+
if *any_unifications {
349+
unification_table.reset_unifications(|vid| unify_key::RegionVidKey { min_vid: vid });
350+
*any_unifications = false;
351+
}
342352

343353
mem::replace(data, RegionConstraintData::default())
344354
}
@@ -358,6 +368,7 @@ impl<'tcx> RegionConstraintCollector<'tcx> {
358368
RegionSnapshot {
359369
length,
360370
region_snapshot: self.unification_table.snapshot(),
371+
any_unifications: self.any_unifications,
361372
}
362373
}
363374

@@ -385,6 +396,7 @@ impl<'tcx> RegionConstraintCollector<'tcx> {
385396
let c = self.undo_log.pop().unwrap();
386397
assert!(c == OpenSnapshot);
387398
self.unification_table.rollback_to(snapshot.region_snapshot);
399+
self.any_unifications = snapshot.any_unifications;
388400
}
389401

390402
fn rollback_undo_entry(&mut self, undo_entry: UndoLogEntry<'tcx>) {
@@ -623,6 +635,7 @@ impl<'tcx> RegionConstraintCollector<'tcx> {
623635

624636
if let (ty::ReVar(sub), ty::ReVar(sup)) = (*sub, *sup) {
625637
self.unification_table.union(sub, sup);
638+
self.any_unifications = true;
626639
}
627640
}
628641
}

‎src/librustc_data_structures/indexed_vec.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,11 @@ impl<I: Idx, T> IndexVec<I, T> {
367367
IndexVec { raw: Vec::new(), _marker: PhantomData }
368368
}
369369

370+
#[inline]
371+
pub fn from_raw(raw: Vec<T>) -> Self {
372+
IndexVec { raw, _marker: PhantomData }
373+
}
374+
370375
#[inline]
371376
pub fn with_capacity(capacity: usize) -> Self {
372377
IndexVec { raw: Vec::with_capacity(capacity), _marker: PhantomData }

‎src/librustc_mir/borrow_check/nll/constraint_generation.rs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
use borrow_check::borrow_set::BorrowSet;
1212
use borrow_check::location::LocationTable;
1313
use borrow_check::nll::facts::AllFacts;
14+
use borrow_check::nll::region_infer::{Cause, RegionInferenceContext};
15+
use borrow_check::nll::ToRegionVid;
1416
use rustc::hir;
1517
use rustc::infer::InferCtxt;
1618
use rustc::mir::visit::TyContext;
@@ -21,9 +23,7 @@ use rustc::mir::{Local, PlaceProjection, ProjectionElem, Statement, Terminator};
2123
use rustc::ty::fold::TypeFoldable;
2224
use rustc::ty::subst::Substs;
2325
use rustc::ty::{self, CanonicalTy, ClosureSubsts, GeneratorSubsts};
24-
25-
use super::region_infer::{Cause, RegionInferenceContext};
26-
use super::ToRegionVid;
26+
use std::iter;
2727

2828
pub(super) fn generate_constraints<'cx, 'gcx, 'tcx>(
2929
infcx: &InferCtxt<'cx, 'gcx, 'tcx>,
@@ -32,6 +32,7 @@ pub(super) fn generate_constraints<'cx, 'gcx, 'tcx>(
3232
location_table: &LocationTable,
3333
mir: &Mir<'tcx>,
3434
borrow_set: &BorrowSet<'tcx>,
35+
liveness_set_from_typeck: &[(ty::Region<'tcx>, Location, Cause)],
3536
) {
3637
let mut cg = ConstraintGeneration {
3738
borrow_set,
@@ -42,6 +43,8 @@ pub(super) fn generate_constraints<'cx, 'gcx, 'tcx>(
4243
mir,
4344
};
4445

46+
cg.add_region_liveness_constraints_from_type_check(liveness_set_from_typeck);
47+
4548
for (bb, data) in mir.basic_blocks().iter_enumerated() {
4649
cg.visit_basic_block_data(bb, data);
4750
}
@@ -209,7 +212,7 @@ impl<'cg, 'cx, 'gcx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'cx, 'gcx
209212
self.add_reborrow_constraint(location, region, borrowed_place);
210213
}
211214

212-
_ => { }
215+
_ => {}
213216
}
214217

215218
self.super_rvalue(rvalue, location);
@@ -225,6 +228,42 @@ impl<'cg, 'cx, 'gcx, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'cx, 'gcx
225228
}
226229

227230
impl<'cx, 'cg, 'gcx, 'tcx> ConstraintGeneration<'cx, 'cg, 'gcx, 'tcx> {
231+
/// The MIR type checker generates region liveness constraints
232+
/// that we also have to respect.
233+
fn add_region_liveness_constraints_from_type_check(
234+
&mut self,
235+
liveness_set: &[(ty::Region<'tcx>, Location, Cause)],
236+
) {
237+
debug!(
238+
"add_region_liveness_constraints_from_type_check(liveness_set={} items)",
239+
liveness_set.len(),
240+
);
241+
242+
let ConstraintGeneration {
243+
regioncx,
244+
location_table,
245+
all_facts,
246+
..
247+
} = self;
248+
249+
for (region, location, cause) in liveness_set {
250+
debug!("generate: {:#?} is live at {:#?}", region, location);
251+
let region_vid = regioncx.to_region_vid(region);
252+
regioncx.add_live_point(region_vid, *location, &cause);
253+
}
254+
255+
if let Some(all_facts) = all_facts {
256+
all_facts
257+
.region_live_at
258+
.extend(liveness_set.into_iter().flat_map(|(region, location, _)| {
259+
let r = regioncx.to_region_vid(region);
260+
let p1 = location_table.start_index(*location);
261+
let p2 = location_table.mid_index(*location);
262+
iter::once((r, p1)).chain(iter::once((r, p2)))
263+
}));
264+
}
265+
}
266+
228267
/// Some variable with type `live_ty` is "regular live" at
229268
/// `location` -- i.e., it may be used later. This means that all
230269
/// regions appearing in the type `live_ty` must be live at

‎src/librustc_mir/borrow_check/nll/mod.rs

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use borrow_check::borrow_set::BorrowSet;
1212
use borrow_check::location::{LocationIndex, LocationTable};
1313
use borrow_check::nll::facts::AllFactsExt;
14+
use borrow_check::nll::type_check::MirTypeckRegionConstraints;
1415
use dataflow::indexes::BorrowIndex;
1516
use dataflow::move_paths::MoveData;
1617
use dataflow::FlowAtLocation;
@@ -41,7 +42,6 @@ mod facts;
4142
mod invalidation;
4243
crate mod region_infer;
4344
mod renumber;
44-
mod subtype_constraint_generation;
4545
crate mod type_check;
4646
mod universal_regions;
4747

@@ -91,53 +91,61 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
9191
Option<Rc<Output<RegionVid, BorrowIndex, LocationIndex>>>,
9292
Option<ClosureRegionRequirements<'gcx>>,
9393
) {
94+
let mut all_facts = if infcx.tcx.sess.opts.debugging_opts.nll_facts
95+
|| infcx.tcx.sess.opts.debugging_opts.polonius
96+
{
97+
Some(AllFacts::default())
98+
} else {
99+
None
100+
};
101+
94102
// Run the MIR type-checker.
95103
let liveness = &LivenessResults::compute(mir);
96-
let constraint_sets = &type_check::type_check(
104+
let constraint_sets = type_check::type_check(
97105
infcx,
98106
param_env,
99107
mir,
100108
def_id,
101109
&universal_regions,
110+
location_table,
102111
&liveness,
112+
&mut all_facts,
103113
flow_inits,
104114
move_data,
105115
);
106116

107-
let mut all_facts = if infcx.tcx.sess.opts.debugging_opts.nll_facts
108-
|| infcx.tcx.sess.opts.debugging_opts.polonius
109-
{
110-
Some(AllFacts::default())
111-
} else {
112-
None
113-
};
114-
115117
if let Some(all_facts) = &mut all_facts {
116118
all_facts
117119
.universal_region
118120
.extend(universal_regions.universal_regions());
119121
}
120122

121-
// Create the region inference context, taking ownership of the region inference
122-
// data that was contained in `infcx`.
123+
// Create the region inference context, taking ownership of the
124+
// region inference data that was contained in `infcx`, and the
125+
// base constraints generated by the type-check.
123126
let var_origins = infcx.take_region_var_origins();
124-
let mut regioncx = RegionInferenceContext::new(var_origins, universal_regions, mir);
125-
126-
// Generate various constraints.
127-
subtype_constraint_generation::generate(
128-
&mut regioncx,
129-
&mut all_facts,
130-
location_table,
127+
let MirTypeckRegionConstraints {
128+
liveness_set,
129+
outlives_constraints,
130+
type_tests,
131+
} = constraint_sets;
132+
let mut regioncx = RegionInferenceContext::new(
133+
var_origins,
134+
universal_regions,
131135
mir,
132-
constraint_sets,
136+
outlives_constraints,
137+
type_tests,
133138
);
139+
140+
// Generate various additional constraints.
134141
constraint_generation::generate_constraints(
135142
infcx,
136143
&mut regioncx,
137144
&mut all_facts,
138145
location_table,
139146
&mir,
140147
borrow_set,
148+
&liveness_set,
141149
);
142150
invalidation::generate_invalidates(
143151
infcx,

‎src/librustc_mir/borrow_check/nll/region_infer/dump_mir.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
//! context internal state.
1515
1616
use std::io::{self, Write};
17-
use super::{Constraint, RegionInferenceContext};
17+
use super::{OutlivesConstraint, RegionInferenceContext};
1818

1919
// Room for "'_#NNNNr" before things get misaligned.
2020
// Easy enough to fix if this ever doesn't seem like
@@ -79,7 +79,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
7979
let mut constraints: Vec<_> = self.constraints.iter().collect();
8080
constraints.sort();
8181
for constraint in &constraints {
82-
let Constraint {
82+
let OutlivesConstraint {
8383
sup,
8484
sub,
8585
point,

‎src/librustc_mir/borrow_check/nll/region_infer/graphviz.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
2727

2828
impl<'this, 'tcx> dot::Labeller<'this> for RegionInferenceContext<'tcx> {
2929
type Node = RegionVid;
30-
type Edge = Constraint;
30+
type Edge = OutlivesConstraint;
3131

3232
fn graph_id(&'this self) -> dot::Id<'this> {
3333
dot::Id::new(format!("RegionInferenceContext")).unwrap()
@@ -41,31 +41,31 @@ impl<'this, 'tcx> dot::Labeller<'this> for RegionInferenceContext<'tcx> {
4141
fn node_label(&'this self, n: &RegionVid) -> dot::LabelText<'this> {
4242
dot::LabelText::LabelStr(format!("{:?}", n).into_cow())
4343
}
44-
fn edge_label(&'this self, e: &Constraint) -> dot::LabelText<'this> {
44+
fn edge_label(&'this self, e: &OutlivesConstraint) -> dot::LabelText<'this> {
4545
dot::LabelText::LabelStr(format!("{:?}", e.point).into_cow())
4646
}
4747
}
4848

4949
impl<'this, 'tcx> dot::GraphWalk<'this> for RegionInferenceContext<'tcx> {
5050
type Node = RegionVid;
51-
type Edge = Constraint;
51+
type Edge = OutlivesConstraint;
5252

5353
fn nodes(&'this self) -> dot::Nodes<'this, RegionVid> {
5454
let vids: Vec<RegionVid> = self.definitions.indices().collect();
5555
vids.into_cow()
5656
}
57-
fn edges(&'this self) -> dot::Edges<'this, Constraint> {
57+
fn edges(&'this self) -> dot::Edges<'this, OutlivesConstraint> {
5858
(&self.constraints.raw[..]).into_cow()
5959
}
6060

6161
// Render `a: b` as `a <- b`, indicating the flow
6262
// of data during inference.
6363

64-
fn source(&'this self, edge: &Constraint) -> RegionVid {
64+
fn source(&'this self, edge: &OutlivesConstraint) -> RegionVid {
6565
edge.sub
6666
}
6767

68-
fn target(&'this self, edge: &Constraint) -> RegionVid {
68+
fn target(&'this self, edge: &OutlivesConstraint) -> RegionVid {
6969
edge.sup
7070
}
7171
}

‎src/librustc_mir/borrow_check/nll/region_infer/mod.rs

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub struct RegionInferenceContext<'tcx> {
6868
dependency_map: Option<IndexVec<RegionVid, Option<ConstraintIndex>>>,
6969

7070
/// The constraints we have accumulated and used during solving.
71-
constraints: IndexVec<ConstraintIndex, Constraint>,
71+
constraints: IndexVec<ConstraintIndex, OutlivesConstraint>,
7272

7373
/// Type constraints that we check after solving.
7474
type_tests: Vec<TypeTest<'tcx>>,
@@ -118,19 +118,19 @@ pub(crate) enum Cause {
118118
}
119119

120120
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
121-
pub struct Constraint {
121+
pub struct OutlivesConstraint {
122122
// NB. The ordering here is not significant for correctness, but
123123
// it is for convenience. Before we dump the constraints in the
124124
// debugging logs, we sort them, and we'd like the "super region"
125125
// to be first, etc. (In particular, span should remain last.)
126126
/// The region SUP must outlive SUB...
127-
sup: RegionVid,
127+
pub sup: RegionVid,
128128

129129
/// Region that must be outlived.
130-
sub: RegionVid,
130+
pub sub: RegionVid,
131131

132132
/// At this location.
133-
point: Location,
133+
pub point: Location,
134134

135135
/// Later on, we thread the constraints onto a linked list
136136
/// grouped by their `sub` field. So if you had:
@@ -140,10 +140,10 @@ pub struct Constraint {
140140
/// 0 | `'a: 'b` | Some(2)
141141
/// 1 | `'b: 'c` | None
142142
/// 2 | `'c: 'b` | None
143-
next: Option<ConstraintIndex>,
143+
pub next: Option<ConstraintIndex>,
144144

145145
/// Where did this constraint arise?
146-
span: Span,
146+
pub span: Span,
147147
}
148148

149149
newtype_index!(ConstraintIndex { DEBUG_FORMAT = "ConstraintIndex({})" });
@@ -239,11 +239,19 @@ impl<'tcx> RegionInferenceContext<'tcx> {
239239
/// `num_region_variables` valid inference variables; the first N
240240
/// of those will be constant regions representing the free
241241
/// regions defined in `universal_regions`.
242+
///
243+
/// The `outlives_constraints` and `type_tests` are an initial set
244+
/// of constraints produced by the MIR type check.
242245
pub(crate) fn new(
243246
var_infos: VarInfos,
244247
universal_regions: UniversalRegions<'tcx>,
245248
mir: &Mir<'tcx>,
249+
outlives_constraints: Vec<OutlivesConstraint>,
250+
type_tests: Vec<TypeTest<'tcx>>,
246251
) -> Self {
252+
// The `next` field should not yet have been initialized:
253+
debug_assert!(outlives_constraints.iter().all(|c| c.next.is_none()));
254+
247255
let num_region_variables = var_infos.len();
248256
let num_universal_regions = universal_regions.len();
249257

@@ -261,8 +269,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
261269
liveness_constraints: RegionValues::new(elements, num_region_variables),
262270
inferred_values: None,
263271
dependency_map: None,
264-
constraints: IndexVec::new(),
265-
type_tests: Vec::new(),
272+
constraints: IndexVec::from_raw(outlives_constraints),
273+
type_tests,
266274
universal_regions,
267275
};
268276

@@ -345,15 +353,17 @@ impl<'tcx> RegionInferenceContext<'tcx> {
345353
where
346354
R: ToRegionVid,
347355
{
348-
let inferred_values = self.inferred_values
356+
let inferred_values = self
357+
.inferred_values
349358
.as_ref()
350359
.expect("region values not yet inferred");
351360
inferred_values.contains(r.to_region_vid(), p)
352361
}
353362

354363
/// Returns access to the value of `r` for debugging purposes.
355364
crate fn region_value_str(&self, r: RegionVid) -> String {
356-
let inferred_values = self.inferred_values
365+
let inferred_values = self
366+
.inferred_values
357367
.as_ref()
358368
.expect("region values not yet inferred");
359369

@@ -387,7 +397,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
387397
) {
388398
debug!("add_outlives({:?}: {:?} @ {:?}", sup, sub, point);
389399
assert!(self.inferred_values.is_none(), "values already inferred");
390-
self.constraints.push(Constraint {
400+
self.constraints.push(OutlivesConstraint {
391401
span,
392402
sup,
393403
sub,
@@ -396,11 +406,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
396406
});
397407
}
398408

399-
/// Add a "type test" that must be satisfied.
400-
pub(super) fn add_type_test(&mut self, type_test: TypeTest<'tcx>) {
401-
self.type_tests.push(type_test);
402-
}
403-
404409
/// Perform region inference and report errors if we see any
405410
/// unsatisfiable constraints. If this is a closure, returns the
406411
/// region requirements to propagate to our creator, if any.
@@ -465,7 +470,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
465470
self.inferred_values = Some(inferred_values);
466471
}
467472

468-
#[inline(never)] // ensure dfs is identifiable in profiles
469473
fn compute_region_values(&self, _mir: &Mir<'tcx>) -> RegionValues {
470474
debug!("compute_region_values()");
471475
debug!("compute_region_values: constraints={:#?}", {
@@ -516,7 +520,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
516520
/// indices of constraints that need to be re-evaluated when X changes.
517521
/// These are constraints like Y: X @ P -- so if X changed, we may
518522
/// need to grow Y.
519-
#[inline(never)] // ensure dfs is identifiable in profiles
520523
fn build_dependency_map(&mut self) -> IndexVec<RegionVid, Option<ConstraintIndex>> {
521524
let mut map = IndexVec::from_elem(None, &self.definitions);
522525

@@ -595,7 +598,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
595598
if self.universal_regions.is_universal_region(r) {
596599
return self.definitions[r].external_name;
597600
} else {
598-
let inferred_values = self.inferred_values
601+
let inferred_values = self
602+
.inferred_values
599603
.as_ref()
600604
.expect("region values not yet inferred");
601605
let upper_bound = self.universal_upper_bound(r);
@@ -634,8 +638,11 @@ impl<'tcx> RegionInferenceContext<'tcx> {
634638
// region, which ensures it can be encoded in a `ClosureOutlivesRequirement`.
635639
let lower_bound_plus = self.non_local_universal_upper_bound(*lower_bound);
636640
assert!(self.universal_regions.is_universal_region(lower_bound_plus));
637-
assert!(!self.universal_regions
638-
.is_local_free_region(lower_bound_plus));
641+
assert!(
642+
!self
643+
.universal_regions
644+
.is_local_free_region(lower_bound_plus)
645+
);
639646

640647
propagated_outlives_requirements.push(ClosureOutlivesRequirement {
641648
subject,
@@ -663,7 +670,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
663670
) -> Option<ClosureOutlivesSubject<'gcx>> {
664671
let tcx = infcx.tcx;
665672
let gcx = tcx.global_tcx();
666-
let inferred_values = self.inferred_values
673+
let inferred_values = self
674+
.inferred_values
667675
.as_ref()
668676
.expect("region values not yet inferred");
669677

@@ -844,7 +852,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
844852
sup_region, sub_region, point
845853
);
846854

847-
let inferred_values = self.inferred_values
855+
let inferred_values = self
856+
.inferred_values
848857
.as_ref()
849858
.expect("values for regions not yet inferred");
850859

@@ -911,7 +920,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
911920
) {
912921
// The universal regions are always found in a prefix of the
913922
// full list.
914-
let universal_definitions = self.definitions
923+
let universal_definitions = self
924+
.definitions
915925
.iter_enumerated()
916926
.take_while(|(_, fr_definition)| fr_definition.is_universal);
917927

@@ -1139,7 +1149,7 @@ impl<'tcx> RegionDefinition<'tcx> {
11391149
}
11401150
}
11411151

1142-
impl fmt::Debug for Constraint {
1152+
impl fmt::Debug for OutlivesConstraint {
11431153
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
11441154
write!(
11451155
formatter,

‎src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs

Lines changed: 0 additions & 199 deletions
This file was deleted.
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use borrow_check::location::LocationTable;
12+
use borrow_check::nll::facts::AllFacts;
13+
use borrow_check::nll::region_infer::{OutlivesConstraint, RegionTest, TypeTest};
14+
use borrow_check::nll::type_check::Locations;
15+
use borrow_check::nll::universal_regions::UniversalRegions;
16+
use rustc::infer::region_constraints::Constraint;
17+
use rustc::infer::region_constraints::RegionConstraintData;
18+
use rustc::infer::region_constraints::{Verify, VerifyBound};
19+
use rustc::mir::{Location, Mir};
20+
use rustc::ty;
21+
use syntax::codemap::Span;
22+
23+
crate struct ConstraintConversion<'a, 'tcx: 'a> {
24+
mir: &'a Mir<'tcx>,
25+
universal_regions: &'a UniversalRegions<'tcx>,
26+
location_table: &'a LocationTable,
27+
outlives_constraints: &'a mut Vec<OutlivesConstraint>,
28+
type_tests: &'a mut Vec<TypeTest<'tcx>>,
29+
all_facts: &'a mut Option<AllFacts>,
30+
31+
}
32+
33+
impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
34+
crate fn new(
35+
mir: &'a Mir<'tcx>,
36+
universal_regions: &'a UniversalRegions<'tcx>,
37+
location_table: &'a LocationTable,
38+
outlives_constraints: &'a mut Vec<OutlivesConstraint>,
39+
type_tests: &'a mut Vec<TypeTest<'tcx>>,
40+
all_facts: &'a mut Option<AllFacts>,
41+
) -> Self {
42+
Self {
43+
mir,
44+
universal_regions,
45+
location_table,
46+
outlives_constraints,
47+
type_tests,
48+
all_facts,
49+
}
50+
}
51+
52+
crate fn convert(
53+
&mut self,
54+
locations: Locations,
55+
data: &RegionConstraintData<'tcx>,
56+
) {
57+
debug!("generate: constraints at: {:#?}", locations);
58+
let RegionConstraintData {
59+
constraints,
60+
verifys,
61+
givens,
62+
} = data;
63+
64+
let span = self
65+
.mir
66+
.source_info(locations.from_location().unwrap_or(Location::START))
67+
.span;
68+
69+
let at_location = locations.at_location().unwrap_or(Location::START);
70+
71+
for constraint in constraints.keys() {
72+
debug!("generate: constraint: {:?}", constraint);
73+
let (a_vid, b_vid) = match constraint {
74+
Constraint::VarSubVar(a_vid, b_vid) => (*a_vid, *b_vid),
75+
Constraint::RegSubVar(a_r, b_vid) => (self.to_region_vid(a_r), *b_vid),
76+
Constraint::VarSubReg(a_vid, b_r) => (*a_vid, self.to_region_vid(b_r)),
77+
Constraint::RegSubReg(a_r, b_r) => {
78+
(self.to_region_vid(a_r), self.to_region_vid(b_r))
79+
}
80+
};
81+
82+
// We have the constraint that `a_vid <= b_vid`. Add
83+
// `b_vid: a_vid` to our region checker. Note that we
84+
// reverse direction, because `regioncx` talks about
85+
// "outlives" (`>=`) whereas the region constraints
86+
// talk about `<=`.
87+
self.add_outlives(span, b_vid, a_vid, at_location);
88+
89+
// In the new analysis, all outlives relations etc
90+
// "take effect" at the mid point of the statement
91+
// that requires them, so ignore the `at_location`.
92+
if let Some(all_facts) = &mut self.all_facts {
93+
if let Some(from_location) = locations.from_location() {
94+
all_facts.outlives.push((
95+
b_vid,
96+
a_vid,
97+
self.location_table.mid_index(from_location),
98+
));
99+
} else {
100+
for location in self.location_table.all_points() {
101+
all_facts.outlives.push((b_vid, a_vid, location));
102+
}
103+
}
104+
}
105+
}
106+
107+
for verify in verifys {
108+
let type_test = self.verify_to_type_test(verify, span, locations);
109+
self.add_type_test(type_test);
110+
}
111+
112+
assert!(
113+
givens.is_empty(),
114+
"MIR type-checker does not use givens (thank goodness)"
115+
);
116+
}
117+
118+
fn verify_to_type_test(
119+
&self,
120+
verify: &Verify<'tcx>,
121+
span: Span,
122+
locations: Locations,
123+
) -> TypeTest<'tcx> {
124+
let generic_kind = verify.kind;
125+
126+
let lower_bound = self.to_region_vid(verify.region);
127+
128+
let point = locations.at_location().unwrap_or(Location::START);
129+
130+
let test = self.verify_bound_to_region_test(&verify.bound);
131+
132+
TypeTest {
133+
generic_kind,
134+
lower_bound,
135+
point,
136+
span,
137+
test,
138+
}
139+
}
140+
141+
fn verify_bound_to_region_test(&self, verify_bound: &VerifyBound<'tcx>) -> RegionTest {
142+
match verify_bound {
143+
VerifyBound::AnyRegion(regions) => RegionTest::IsOutlivedByAnyRegionIn(
144+
regions.iter().map(|r| self.to_region_vid(r)).collect(),
145+
),
146+
147+
VerifyBound::AllRegions(regions) => RegionTest::IsOutlivedByAllRegionsIn(
148+
regions.iter().map(|r| self.to_region_vid(r)).collect(),
149+
),
150+
151+
VerifyBound::AnyBound(bounds) => RegionTest::Any(
152+
bounds
153+
.iter()
154+
.map(|b| self.verify_bound_to_region_test(b))
155+
.collect(),
156+
),
157+
158+
VerifyBound::AllBounds(bounds) => RegionTest::All(
159+
bounds
160+
.iter()
161+
.map(|b| self.verify_bound_to_region_test(b))
162+
.collect(),
163+
),
164+
}
165+
}
166+
167+
fn to_region_vid(&self, r: ty::Region<'tcx>) -> ty::RegionVid {
168+
self.universal_regions.to_region_vid(r)
169+
}
170+
171+
fn add_outlives(
172+
&mut self,
173+
span: Span,
174+
sup: ty::RegionVid,
175+
sub: ty::RegionVid,
176+
point: Location,
177+
) {
178+
self.outlives_constraints.push(OutlivesConstraint {
179+
span,
180+
sub,
181+
sup,
182+
point,
183+
next: None,
184+
});
185+
}
186+
187+
fn add_type_test(&mut self, type_test: TypeTest<'tcx>) {
188+
self.type_tests.push(type_test);
189+
}
190+
}

‎src/librustc_mir/borrow_check/nll/type_check/input_output.rs

Lines changed: 78 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ use borrow_check::nll::renumber;
2121
use borrow_check::nll::universal_regions::UniversalRegions;
2222
use rustc::hir::def_id::DefId;
2323
use rustc::infer::InferOk;
24-
use rustc::ty::Ty;
25-
use rustc::ty::subst::Subst;
26-
use rustc::mir::*;
2724
use rustc::mir::visit::TyContext;
28-
use rustc::traits::PredicateObligations;
25+
use rustc::mir::*;
26+
use rustc::traits::{ObligationCause, PredicateObligations};
27+
use rustc::ty::subst::Subst;
28+
use rustc::ty::Ty;
2929

3030
use rustc_data_structures::indexed_vec::Idx;
3131

@@ -56,8 +56,8 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
5656
}
5757

5858
assert!(
59-
mir.yield_ty.is_some() && universal_regions.yield_ty.is_some() ||
60-
mir.yield_ty.is_none() && universal_regions.yield_ty.is_none()
59+
mir.yield_ty.is_some() && universal_regions.yield_ty.is_some()
60+
|| mir.yield_ty.is_none() && universal_regions.yield_ty.is_none()
6161
);
6262
if let Some(mir_yield_ty) = mir.yield_ty {
6363
let ur_yield_ty = universal_regions.yield_ty.unwrap();
@@ -76,57 +76,67 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
7676
output_ty
7777
);
7878
let mir_output_ty = mir.local_decls[RETURN_PLACE].ty;
79-
let anon_type_map = self.fully_perform_op(Locations::All, |cx| {
80-
let mut obligations = ObligationAccumulator::default();
81-
82-
let (output_ty, anon_type_map) = obligations.add(infcx.instantiate_anon_types(
83-
mir_def_id,
84-
cx.body_id,
85-
cx.param_env,
86-
&output_ty,
87-
));
88-
debug!(
89-
"equate_inputs_and_outputs: instantiated output_ty={:?}",
90-
output_ty
91-
);
92-
debug!(
93-
"equate_inputs_and_outputs: anon_type_map={:#?}",
94-
anon_type_map
95-
);
96-
97-
debug!(
98-
"equate_inputs_and_outputs: mir_output_ty={:?}",
99-
mir_output_ty
100-
);
101-
obligations.add(infcx
102-
.at(&cx.misc(cx.last_span), cx.param_env)
103-
.eq(output_ty, mir_output_ty)?);
104-
105-
for (&anon_def_id, anon_decl) in &anon_type_map {
106-
let anon_defn_ty = tcx.type_of(anon_def_id);
107-
let anon_defn_ty = anon_defn_ty.subst(tcx, anon_decl.substs);
108-
let anon_defn_ty = renumber::renumber_regions(
109-
cx.infcx,
110-
TyContext::Location(Location::START),
111-
&anon_defn_ty,
112-
);
113-
debug!(
114-
"equate_inputs_and_outputs: concrete_ty={:?}",
115-
anon_decl.concrete_ty
116-
);
117-
debug!("equate_inputs_and_outputs: anon_defn_ty={:?}", anon_defn_ty);
118-
obligations.add(infcx
119-
.at(&cx.misc(cx.last_span), cx.param_env)
120-
.eq(anon_decl.concrete_ty, anon_defn_ty)?);
121-
}
122-
123-
debug!("equate_inputs_and_outputs: equated");
124-
125-
Ok(InferOk {
126-
value: Some(anon_type_map),
127-
obligations: obligations.into_vec(),
128-
})
129-
}).unwrap_or_else(|terr| {
79+
let anon_type_map =
80+
self.fully_perform_op(
81+
Locations::All,
82+
|| format!("input_output"),
83+
|cx| {
84+
let mut obligations = ObligationAccumulator::default();
85+
86+
let dummy_body_id = ObligationCause::dummy().body_id;
87+
let (output_ty, anon_type_map) = obligations.add(infcx.instantiate_anon_types(
88+
mir_def_id,
89+
dummy_body_id,
90+
cx.param_env,
91+
&output_ty,
92+
));
93+
debug!(
94+
"equate_inputs_and_outputs: instantiated output_ty={:?}",
95+
output_ty
96+
);
97+
debug!(
98+
"equate_inputs_and_outputs: anon_type_map={:#?}",
99+
anon_type_map
100+
);
101+
102+
debug!(
103+
"equate_inputs_and_outputs: mir_output_ty={:?}",
104+
mir_output_ty
105+
);
106+
obligations.add(
107+
infcx
108+
.at(&ObligationCause::dummy(), cx.param_env)
109+
.eq(output_ty, mir_output_ty)?,
110+
);
111+
112+
for (&anon_def_id, anon_decl) in &anon_type_map {
113+
let anon_defn_ty = tcx.type_of(anon_def_id);
114+
let anon_defn_ty = anon_defn_ty.subst(tcx, anon_decl.substs);
115+
let anon_defn_ty = renumber::renumber_regions(
116+
cx.infcx,
117+
TyContext::Location(Location::START),
118+
&anon_defn_ty,
119+
);
120+
debug!(
121+
"equate_inputs_and_outputs: concrete_ty={:?}",
122+
anon_decl.concrete_ty
123+
);
124+
debug!("equate_inputs_and_outputs: anon_defn_ty={:?}", anon_defn_ty);
125+
obligations.add(
126+
infcx
127+
.at(&ObligationCause::dummy(), cx.param_env)
128+
.eq(anon_decl.concrete_ty, anon_defn_ty)?,
129+
);
130+
}
131+
132+
debug!("equate_inputs_and_outputs: equated");
133+
134+
Ok(InferOk {
135+
value: Some(anon_type_map),
136+
obligations: obligations.into_vec(),
137+
})
138+
},
139+
).unwrap_or_else(|terr| {
130140
span_mirbug!(
131141
self,
132142
Location::START,
@@ -143,13 +153,17 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
143153
// prove that `T: Iterator` where `T` is the type we
144154
// instantiated it with).
145155
if let Some(anon_type_map) = anon_type_map {
146-
self.fully_perform_op(Locations::All, |_cx| {
147-
infcx.constrain_anon_types(&anon_type_map, universal_regions);
148-
Ok(InferOk {
149-
value: (),
150-
obligations: vec![],
151-
})
152-
}).unwrap();
156+
self.fully_perform_op(
157+
Locations::All,
158+
|| format!("anon_type_map"),
159+
|_cx| {
160+
infcx.constrain_anon_types(&anon_type_map, universal_regions);
161+
Ok(InferOk {
162+
value: (),
163+
obligations: vec![],
164+
})
165+
},
166+
).unwrap();
153167
}
154168
}
155169

‎src/librustc_mir/borrow_check/nll/type_check/liveness.rs

Lines changed: 61 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,19 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use dataflow::{FlowAtLocation, FlowsAtLocation};
1211
use borrow_check::nll::region_infer::Cause;
13-
use dataflow::MaybeInitializedPlaces;
12+
use borrow_check::nll::type_check::AtLocation;
1413
use dataflow::move_paths::{HasMoveData, MoveData};
15-
use rustc::mir::{BasicBlock, Location, Mir};
14+
use dataflow::MaybeInitializedPlaces;
15+
use dataflow::{FlowAtLocation, FlowsAtLocation};
16+
use rustc::infer::region_constraints::RegionConstraintData;
1617
use rustc::mir::Local;
17-
use rustc::ty::{Ty, TyCtxt, TypeFoldable};
18-
use rustc::infer::InferOk;
19-
use borrow_check::nll::type_check::AtLocation;
18+
use rustc::mir::{BasicBlock, Location, Mir};
19+
use rustc::traits::ObligationCause;
20+
use rustc::ty::subst::Kind;
21+
use rustc::ty::{Ty, TypeFoldable};
22+
use rustc_data_structures::fx::FxHashMap;
23+
use std::rc::Rc;
2024
use util::liveness::LivenessResults;
2125

2226
use super::TypeChecker;
@@ -36,14 +40,13 @@ pub(super) fn generate<'gcx, 'tcx>(
3640
flow_inits: &mut FlowAtLocation<MaybeInitializedPlaces<'_, 'gcx, 'tcx>>,
3741
move_data: &MoveData<'tcx>,
3842
) {
39-
let tcx = cx.tcx();
4043
let mut generator = TypeLivenessGenerator {
4144
cx,
42-
tcx,
4345
mir,
4446
liveness,
4547
flow_inits,
4648
move_data,
49+
drop_data: FxHashMap(),
4750
};
4851

4952
for bb in mir.basic_blocks().indices() {
@@ -59,11 +62,16 @@ where
5962
'gcx: 'tcx,
6063
{
6164
cx: &'gen mut TypeChecker<'typeck, 'gcx, 'tcx>,
62-
tcx: TyCtxt<'typeck, 'gcx, 'tcx>,
6365
mir: &'gen Mir<'tcx>,
6466
liveness: &'gen LivenessResults,
6567
flow_inits: &'gen mut FlowAtLocation<MaybeInitializedPlaces<'flow, 'gcx, 'tcx>>,
6668
move_data: &'gen MoveData<'tcx>,
69+
drop_data: FxHashMap<Ty<'tcx>, DropData<'tcx>>,
70+
}
71+
72+
struct DropData<'tcx> {
73+
dropped_kinds: Vec<Kind<'tcx>>,
74+
region_constraint_data: Option<Rc<RegionConstraintData<'tcx>>>,
6775
}
6876

6977
impl<'gen, 'typeck, 'flow, 'gcx, 'tcx> TypeLivenessGenerator<'gen, 'typeck, 'flow, 'gcx, 'tcx> {
@@ -80,7 +88,7 @@ impl<'gen, 'typeck, 'flow, 'gcx, 'tcx> TypeLivenessGenerator<'gen, 'typeck, 'flo
8088
for live_local in live_locals.iter() {
8189
let live_local_ty = self.mir.local_decls[live_local].ty;
8290
let cause = Cause::LiveVar(live_local, location);
83-
self.push_type_live_constraint(live_local_ty, location, cause);
91+
Self::push_type_live_constraint(&mut self.cx, live_local_ty, location, cause);
8492
}
8593
});
8694

@@ -148,17 +156,21 @@ impl<'gen, 'typeck, 'flow, 'gcx, 'tcx> TypeLivenessGenerator<'gen, 'typeck, 'flo
148156
/// `location` -- i.e., it may be used later. This means that all
149157
/// regions appearing in the type `live_ty` must be live at
150158
/// `location`.
151-
fn push_type_live_constraint<T>(&mut self, value: T, location: Location, cause: Cause)
152-
where
159+
fn push_type_live_constraint<T>(
160+
cx: &mut TypeChecker<'_, 'gcx, 'tcx>,
161+
value: T,
162+
location: Location,
163+
cause: Cause,
164+
) where
153165
T: TypeFoldable<'tcx>,
154166
{
155167
debug!(
156168
"push_type_live_constraint(live_ty={:?}, location={:?})",
157169
value, location
158170
);
159171

160-
self.tcx.for_each_free_region(&value, |live_region| {
161-
self.cx
172+
cx.tcx().for_each_free_region(&value, |live_region| {
173+
cx
162174
.constraints
163175
.liveness_set
164176
.push((live_region, location, cause.clone()));
@@ -181,47 +193,44 @@ impl<'gen, 'typeck, 'flow, 'gcx, 'tcx> TypeLivenessGenerator<'gen, 'typeck, 'flo
181193
dropped_local, dropped_ty, location
182194
);
183195

184-
// If we end visiting the same type twice (usually due to a cycle involving
185-
// associated types), we need to ensure that its region types match up with the type
186-
// we added to the 'known' map the first time around. For this reason, we need
187-
// our infcx to hold onto its calculated region constraints after each call
188-
// to dtorck_constraint_for_ty. Otherwise, normalizing the corresponding associated
189-
// type will end up instantiating the type with a new set of inference variables
190-
// Since this new type will never be in 'known', we end up looping forever.
191-
//
192-
// For this reason, we avoid calling TypeChecker.normalize, instead doing all normalization
193-
// ourselves in one large 'fully_perform_op' callback.
194-
let kind_constraints = self.cx
195-
.fully_perform_op(location.at_self(), |cx| {
196-
let span = cx.last_span;
197-
198-
let mut final_obligations = Vec::new();
199-
let mut kind_constraints = Vec::new();
200-
201-
let InferOk {
202-
value: kinds,
203-
obligations,
204-
} = cx.infcx
205-
.at(&cx.misc(span), cx.param_env)
206-
.dropck_outlives(dropped_ty);
207-
for kind in kinds {
208-
// All things in the `outlives` array may be touched by
209-
// the destructor and must be live at this point.
210-
let cause = Cause::DropVar(dropped_local, location);
211-
kind_constraints.push((kind, location, cause));
212-
}
196+
let drop_data = self.drop_data.entry(dropped_ty).or_insert_with({
197+
let cx = &mut self.cx;
198+
move || Self::compute_drop_data(cx, dropped_ty)
199+
});
213200

214-
final_obligations.extend(obligations);
201+
if let Some(data) = &drop_data.region_constraint_data {
202+
self.cx
203+
.push_region_constraints(location.at_self(), data.clone());
204+
}
215205

216-
Ok(InferOk {
217-
value: kind_constraints,
218-
obligations: final_obligations,
219-
})
220-
})
221-
.unwrap();
206+
// All things in the `outlives` array may be touched by
207+
// the destructor and must be live at this point.
208+
let cause = Cause::DropVar(dropped_local, location);
209+
for &kind in &drop_data.dropped_kinds {
210+
Self::push_type_live_constraint(&mut self.cx, kind, location, cause);
211+
}
212+
}
222213

223-
for (kind, location, cause) in kind_constraints {
224-
self.push_type_live_constraint(kind, location, cause);
214+
fn compute_drop_data(
215+
cx: &mut TypeChecker<'_, 'gcx, 'tcx>,
216+
dropped_ty: Ty<'tcx>,
217+
) -> DropData<'tcx> {
218+
debug!("compute_drop_data(dropped_ty={:?})", dropped_ty,);
219+
220+
let (dropped_kinds, region_constraint_data) =
221+
cx.fully_perform_op_and_get_region_constraint_data(
222+
|| format!("compute_drop_data(dropped_ty={:?})", dropped_ty),
223+
|cx| {
224+
Ok(cx
225+
.infcx
226+
.at(&ObligationCause::dummy(), cx.param_env)
227+
.dropck_outlives(dropped_ty))
228+
},
229+
).unwrap();
230+
231+
DropData {
232+
dropped_kinds,
233+
region_constraint_data,
225234
}
226235
}
227236
}

‎src/librustc_mir/borrow_check/nll/type_check/mod.rs

Lines changed: 216 additions & 99 deletions
Large diffs are not rendered by default.

‎src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-no-bound.stderr

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,15 @@ LL | | });
2424
= note: where '_#1r: '_#0r
2525

2626
error: free region `ReFree(DefId(0/0:6 ~ propagate_approximated_shorter_to_static_no_bound[317d]::supply[0]), BrNamed(crate0:DefIndex(1:16), 'a))` does not outlive free region `ReStatic`
27-
--> $DIR/propagate-approximated-shorter-to-static-no-bound.rs:45:47
27+
--> $DIR/propagate-approximated-shorter-to-static-no-bound.rs:45:5
2828
|
29-
LL | establish_relationships(&cell_a, &cell_b, |_outlives, x, y| {
30-
| _______________________________________________^
29+
LL | / establish_relationships(&cell_a, &cell_b, |_outlives, x, y| {
3130
LL | | //~^ ERROR does not outlive free region
3231
LL | |
3332
LL | | // Only works if 'x: 'y:
3433
LL | | demand_y(x, y, x.get()) //~ WARNING not reporting region error due to nll
3534
LL | | });
36-
| |_____^
35+
| |______^
3736

3837
note: No external requirements
3938
--> $DIR/propagate-approximated-shorter-to-static-no-bound.rs:44:1

‎src/test/ui/nll/closure-requirements/propagate-approximated-shorter-to-static-wrong-bound.stderr

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,15 @@ LL | | });
2424
= note: where '_#1r: '_#0r
2525

2626
error: free region `ReFree(DefId(0/0:6 ~ propagate_approximated_shorter_to_static_wrong_bound[317d]::supply[0]), BrNamed(crate0:DefIndex(1:16), 'a))` does not outlive free region `ReStatic`
27-
--> $DIR/propagate-approximated-shorter-to-static-wrong-bound.rs:48:47
27+
--> $DIR/propagate-approximated-shorter-to-static-wrong-bound.rs:48:5
2828
|
29-
LL | establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y| {
30-
| _______________________________________________^
29+
LL | / establish_relationships(&cell_a, &cell_b, |_outlives1, _outlives2, x, y| {
3130
LL | | //~^ ERROR does not outlive free region
3231
LL | | // Only works if 'x: 'y:
3332
LL | | demand_y(x, y, x.get())
3433
LL | | //~^ WARNING not reporting region error due to nll
3534
LL | | });
36-
| |_____^
35+
| |______^
3736

3837
note: No external requirements
3938
--> $DIR/propagate-approximated-shorter-to-static-wrong-bound.rs:47:1

0 commit comments

Comments
 (0)
Please sign in to comment.