Skip to content

Commit c14b615

Browse files
committed
Auto merge of #30533 - nikomatsakis:fulfillment-tree, r=aturon
This PR introduces an `ObligationForest` data structure that the fulfillment context can use to track what's going on, instead of the current flat vector. This enables a number of improvements: 1. transactional support, at least for pushing new obligations 2. remove the "errors will be reported" hack -- instead, we only add types to the global cache once their entire subtree has been proven safe. Before, we never knew when this point was reached because we didn't track the subtree. - this in turn allows us to limit coinductive reasoning to structural traits, which sidesteps #29859 3. keeping the backtrace should allow for an improved error message, where we give the user full context - we can also remove chained obligation causes This PR is not 100% complete. In particular: - [x] Currently, types that embed themselves like `struct Foo { f: Foo }` give an overflow when evaluating whether `Foo: Sized`. This is not a very user-friendly error message, and this is a common beginner error. I plan to special-case this scenario, I think. - [x] I should do some perf. measurements. (Update: 2% regression.) - [x] More tests targeting #29859 - [ ] The transactional support is not fully integrated, though that should be easy enough. - [ ] The error messages are not taking advantage of the backtrace. I'd certainly like to do 1 through 3 before landing, but 4 and 5 could come as separate PRs. r? @aturon // good way to learn more about this part of the trait system f? @arielb1 // already knows this part of the trait system :)
2 parents dda25f2 + 4fbb71f commit c14b615

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1467
-356
lines changed

src/librustc/diagnostics.rs

+37
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,43 @@ There's no easy fix for this, generally code will need to be refactored so that
679679
you no longer need to derive from `Super<Self>`.
680680
"####,
681681

682+
E0072: r##"
683+
When defining a recursive struct or enum, any use of the type being defined
684+
from inside the definition must occur behind a pointer (like `Box` or `&`).
685+
This is because structs and enums must have a well-defined size, and without
686+
the pointer the size of the type would need to be unbounded.
687+
688+
Consider the following erroneous definition of a type for a list of bytes:
689+
690+
```
691+
// error, invalid recursive struct type
692+
struct ListNode {
693+
head: u8,
694+
tail: Option<ListNode>,
695+
}
696+
```
697+
698+
This type cannot have a well-defined size, because it needs to be arbitrarily
699+
large (since we would be able to nest `ListNode`s to any depth). Specifically,
700+
701+
```plain
702+
size of `ListNode` = 1 byte for `head`
703+
+ 1 byte for the discriminant of the `Option`
704+
+ size of `ListNode`
705+
```
706+
707+
One way to fix this is by wrapping `ListNode` in a `Box`, like so:
708+
709+
```
710+
struct ListNode {
711+
head: u8,
712+
tail: Option<Box<ListNode>>,
713+
}
714+
```
715+
716+
This works because `Box` is a pointer, so its size is well-known.
717+
"##,
718+
682719
E0109: r##"
683720
You tried to give a type parameter to a type which doesn't need it. Erroneous
684721
code example:

src/librustc/middle/check_const.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ impl<'a, 'tcx> CheckCrateVisitor<'a, 'tcx> {
125125
None => self.tcx.empty_parameter_environment()
126126
};
127127

128-
let infcx = infer::new_infer_ctxt(self.tcx, &self.tcx.tables, Some(param_env), false);
128+
let infcx = infer::new_infer_ctxt(self.tcx, &self.tcx.tables, Some(param_env));
129129

130130
f(&mut euv::ExprUseVisitor::new(self, &infcx))
131131
}
@@ -280,7 +280,7 @@ impl<'a, 'tcx> CheckCrateVisitor<'a, 'tcx> {
280280

281281
fn check_static_type(&self, e: &hir::Expr) {
282282
let ty = self.tcx.node_id_to_type(e.id);
283-
let infcx = infer::new_infer_ctxt(self.tcx, &self.tcx.tables, None, false);
283+
let infcx = infer::new_infer_ctxt(self.tcx, &self.tcx.tables, None);
284284
let cause = traits::ObligationCause::new(e.span, e.id, traits::SharedStatic);
285285
let mut fulfill_cx = infcx.fulfillment_cx.borrow_mut();
286286
fulfill_cx.register_builtin_bound(&infcx, ty, ty::BoundSync, cause);

src/librustc/middle/check_match.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1095,8 +1095,7 @@ fn check_legality_of_move_bindings(cx: &MatchCheckCtxt,
10951095
//FIXME: (@jroesch) this code should be floated up as well
10961096
let infcx = infer::new_infer_ctxt(cx.tcx,
10971097
&cx.tcx.tables,
1098-
Some(cx.param_env.clone()),
1099-
false);
1098+
Some(cx.param_env.clone()));
11001099
if infcx.type_moves_by_default(pat_ty, pat.span) {
11011100
check_move(p, sub.as_ref().map(|p| &**p));
11021101
}
@@ -1128,8 +1127,7 @@ fn check_for_mutation_in_guard<'a, 'tcx>(cx: &'a MatchCheckCtxt<'a, 'tcx>,
11281127

11291128
let infcx = infer::new_infer_ctxt(cx.tcx,
11301129
&cx.tcx.tables,
1131-
Some(checker.cx.param_env.clone()),
1132-
false);
1130+
Some(checker.cx.param_env.clone()));
11331131

11341132
let mut visitor = ExprUseVisitor::new(&mut checker, &infcx);
11351133
visitor.walk_expr(guard);

src/librustc/middle/check_rvalues.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ impl<'a, 'tcx, 'v> intravisit::Visitor<'v> for RvalueContext<'a, 'tcx> {
4444
let param_env = ParameterEnvironment::for_item(self.tcx, fn_id);
4545
let infcx = infer::new_infer_ctxt(self.tcx,
4646
&self.tcx.tables,
47-
Some(param_env.clone()),
48-
false);
47+
Some(param_env.clone()));
4948
let mut delegate = RvalueContextDelegate { tcx: self.tcx, param_env: &param_env };
5049
let mut euv = euv::ExprUseVisitor::new(&mut delegate, &infcx);
5150
euv.walk_fn(fd, b);

src/librustc/middle/const_eval.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1262,7 +1262,7 @@ fn resolve_trait_associated_const<'a, 'tcx: 'a>(tcx: &'a ty::ctxt<'tcx>,
12621262
substs: trait_substs });
12631263

12641264
tcx.populate_implementations_for_trait_if_necessary(trait_ref.def_id());
1265-
let infcx = infer::new_infer_ctxt(tcx, &tcx.tables, None, false);
1265+
let infcx = infer::new_infer_ctxt(tcx, &tcx.tables, None);
12661266

12671267
let mut selcx = traits::SelectionContext::new(&infcx);
12681268
let obligation = traits::Obligation::new(traits::ObligationCause::dummy(),

src/librustc/middle/infer/mod.rs

+4-11
Original file line numberDiff line numberDiff line change
@@ -354,16 +354,9 @@ pub fn fixup_err_to_string(f: FixupError) -> String {
354354
}
355355
}
356356

357-
/// errors_will_be_reported is required to proxy to the fulfillment context
358-
/// FIXME -- a better option would be to hold back on modifying
359-
/// the global cache until we know that all dependent obligations
360-
/// are also satisfied. In that case, we could actually remove
361-
/// this boolean flag, and we'd also avoid the problem of squelching
362-
/// duplicate errors that occur across fns.
363357
pub fn new_infer_ctxt<'a, 'tcx>(tcx: &'a ty::ctxt<'tcx>,
364358
tables: &'a RefCell<ty::Tables<'tcx>>,
365-
param_env: Option<ty::ParameterEnvironment<'a, 'tcx>>,
366-
errors_will_be_reported: bool)
359+
param_env: Option<ty::ParameterEnvironment<'a, 'tcx>>)
367360
-> InferCtxt<'a, 'tcx> {
368361
InferCtxt {
369362
tcx: tcx,
@@ -373,7 +366,7 @@ pub fn new_infer_ctxt<'a, 'tcx>(tcx: &'a ty::ctxt<'tcx>,
373366
float_unification_table: RefCell::new(UnificationTable::new()),
374367
region_vars: RegionVarBindings::new(tcx),
375368
parameter_environment: param_env.unwrap_or(tcx.empty_parameter_environment()),
376-
fulfillment_cx: RefCell::new(traits::FulfillmentContext::new(errors_will_be_reported)),
369+
fulfillment_cx: RefCell::new(traits::FulfillmentContext::new()),
377370
reported_trait_errors: RefCell::new(FnvHashSet()),
378371
normalize: false,
379372
err_count_on_creation: tcx.sess.err_count()
@@ -383,7 +376,7 @@ pub fn new_infer_ctxt<'a, 'tcx>(tcx: &'a ty::ctxt<'tcx>,
383376
pub fn normalizing_infer_ctxt<'a, 'tcx>(tcx: &'a ty::ctxt<'tcx>,
384377
tables: &'a RefCell<ty::Tables<'tcx>>)
385378
-> InferCtxt<'a, 'tcx> {
386-
let mut infcx = new_infer_ctxt(tcx, tables, None, false);
379+
let mut infcx = new_infer_ctxt(tcx, tables, None);
387380
infcx.normalize = true;
388381
infcx
389382
}
@@ -522,7 +515,7 @@ pub fn normalize_associated_type<'tcx,T>(tcx: &ty::ctxt<'tcx>, value: &T) -> T
522515
return value;
523516
}
524517

525-
let infcx = new_infer_ctxt(tcx, &tcx.tables, None, true);
518+
let infcx = new_infer_ctxt(tcx, &tcx.tables, None);
526519
let mut selcx = traits::SelectionContext::new(&infcx);
527520
let cause = traits::ObligationCause::dummy();
528521
let traits::Normalized { value: result, obligations } =

src/librustc/middle/traits/error_reporting.rs

+141-2
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,8 @@ fn report_on_unimplemented<'a, 'tcx>(infcx: &InferCtxt<'a, 'tcx>,
182182
/// if the program type checks or not -- and they are unusual
183183
/// occurrences in any case.
184184
pub fn report_overflow_error<'a, 'tcx, T>(infcx: &InferCtxt<'a, 'tcx>,
185-
obligation: &Obligation<'tcx, T>)
185+
obligation: &Obligation<'tcx, T>,
186+
suggest_increasing_limit: bool)
186187
-> !
187188
where T: fmt::Display + TypeFoldable<'tcx>
188189
{
@@ -192,7 +193,9 @@ pub fn report_overflow_error<'a, 'tcx, T>(infcx: &InferCtxt<'a, 'tcx>,
192193
"overflow evaluating the requirement `{}`",
193194
predicate);
194195

195-
suggest_new_overflow_limit(infcx.tcx, &mut err, obligation.cause.span);
196+
if suggest_increasing_limit {
197+
suggest_new_overflow_limit(infcx.tcx, &mut err, obligation.cause.span);
198+
}
196199

197200
note_obligation_cause(infcx, &mut err, obligation);
198201

@@ -201,6 +204,142 @@ pub fn report_overflow_error<'a, 'tcx, T>(infcx: &InferCtxt<'a, 'tcx>,
201204
unreachable!();
202205
}
203206

207+
/// Reports that a cycle was detected which led to overflow and halts
208+
/// compilation. This is equivalent to `report_overflow_error` except
209+
/// that we can give a more helpful error message (and, in particular,
210+
/// we do not suggest increasing the overflow limit, which is not
211+
/// going to help).
212+
pub fn report_overflow_error_cycle<'a, 'tcx>(infcx: &InferCtxt<'a, 'tcx>,
213+
cycle: &Vec<PredicateObligation<'tcx>>)
214+
-> !
215+
{
216+
assert!(cycle.len() > 1);
217+
218+
debug!("report_overflow_error_cycle(cycle length = {})", cycle.len());
219+
220+
let cycle = infcx.resolve_type_vars_if_possible(cycle);
221+
222+
debug!("report_overflow_error_cycle: cycle={:?}", cycle);
223+
224+
assert_eq!(&cycle[0].predicate, &cycle.last().unwrap().predicate);
225+
226+
try_report_overflow_error_type_of_infinite_size(infcx, &cycle);
227+
report_overflow_error(infcx, &cycle[0], false);
228+
}
229+
230+
/// If a cycle results from evaluated whether something is Sized, that
231+
/// is a particular special case that always results from a struct or
232+
/// enum definition that lacks indirection (e.g., `struct Foo { x: Foo
233+
/// }`). We wish to report a targeted error for this case.
234+
pub fn try_report_overflow_error_type_of_infinite_size<'a, 'tcx>(
235+
infcx: &InferCtxt<'a, 'tcx>,
236+
cycle: &[PredicateObligation<'tcx>])
237+
{
238+
let sized_trait = match infcx.tcx.lang_items.sized_trait() {
239+
Some(v) => v,
240+
None => return,
241+
};
242+
let top_is_sized = {
243+
match cycle[0].predicate {
244+
ty::Predicate::Trait(ref data) => data.def_id() == sized_trait,
245+
_ => false,
246+
}
247+
};
248+
if !top_is_sized {
249+
return;
250+
}
251+
252+
// The only way to have a type of infinite size is to have,
253+
// somewhere, a struct/enum type involved. Identify all such types
254+
// and report the cycle to the user.
255+
256+
let struct_enum_tys: Vec<_> =
257+
cycle.iter()
258+
.flat_map(|obligation| match obligation.predicate {
259+
ty::Predicate::Trait(ref data) => {
260+
assert_eq!(data.def_id(), sized_trait);
261+
let self_ty = data.skip_binder().trait_ref.self_ty(); // (*)
262+
// (*) ok to skip binder because this is just
263+
// error reporting and regions don't really
264+
// matter
265+
match self_ty.sty {
266+
ty::TyEnum(..) | ty::TyStruct(..) => Some(self_ty),
267+
_ => None,
268+
}
269+
}
270+
_ => {
271+
infcx.tcx.sess.span_bug(obligation.cause.span,
272+
&format!("Sized cycle involving non-trait-ref: {:?}",
273+
obligation.predicate));
274+
}
275+
})
276+
.collect();
277+
278+
assert!(!struct_enum_tys.is_empty());
279+
280+
// This is a bit tricky. We want to pick a "main type" in the
281+
// listing that is local to the current crate, so we can give a
282+
// good span to the user. But it might not be the first one in our
283+
// cycle list. So find the first one that is local and then
284+
// rotate.
285+
let (main_index, main_def_id) =
286+
struct_enum_tys.iter()
287+
.enumerate()
288+
.filter_map(|(index, ty)| match ty.sty {
289+
ty::TyEnum(adt_def, _) | ty::TyStruct(adt_def, _)
290+
if adt_def.did.is_local() =>
291+
Some((index, adt_def.did)),
292+
_ =>
293+
None,
294+
})
295+
.next()
296+
.unwrap(); // should always be SOME local type involved!
297+
298+
// Rotate so that the "main" type is at index 0.
299+
let struct_enum_tys: Vec<_> =
300+
struct_enum_tys.iter()
301+
.cloned()
302+
.skip(main_index)
303+
.chain(struct_enum_tys.iter().cloned().take(main_index))
304+
.collect();
305+
306+
let tcx = infcx.tcx;
307+
let mut err = recursive_type_with_infinite_size_error(tcx, main_def_id);
308+
let len = struct_enum_tys.len();
309+
if len > 2 {
310+
let span = tcx.map.span_if_local(main_def_id).unwrap();
311+
err.fileline_note(span,
312+
&format!("type `{}` is embedded within `{}`...",
313+
struct_enum_tys[0],
314+
struct_enum_tys[1]));
315+
for &next_ty in &struct_enum_tys[1..len-1] {
316+
err.fileline_note(span,
317+
&format!("...which in turn is embedded within `{}`...", next_ty));
318+
}
319+
err.fileline_note(span,
320+
&format!("...which in turn is embedded within `{}`, \
321+
completing the cycle.",
322+
struct_enum_tys[len-1]));
323+
}
324+
err.emit();
325+
infcx.tcx.sess.abort_if_errors();
326+
unreachable!();
327+
}
328+
329+
pub fn recursive_type_with_infinite_size_error<'tcx>(tcx: &ty::ctxt<'tcx>,
330+
type_def_id: DefId)
331+
-> DiagnosticBuilder<'tcx>
332+
{
333+
assert!(type_def_id.is_local());
334+
let span = tcx.map.span_if_local(type_def_id).unwrap();
335+
let mut err = struct_span_err!(tcx.sess, span, E0072, "recursive type `{}` has infinite size",
336+
tcx.item_path_str(type_def_id));
337+
err.fileline_help(span, &format!("insert indirection (e.g., a `Box`, `Rc`, or `&`) \
338+
at some point to make `{}` representable",
339+
tcx.item_path_str(type_def_id)));
340+
err
341+
}
342+
204343
pub fn report_selection_error<'a, 'tcx>(infcx: &InferCtxt<'a, 'tcx>,
205344
obligation: &PredicateObligation<'tcx>,
206345
error: &SelectionError<'tcx>)

0 commit comments

Comments
 (0)