Skip to content

Commit 3046ac2

Browse files
committed
Detect cycles and specialize error reporting for Sized. It is important
to get the `Sized` error usable, since that hits new users frequently. Further work is needed for the error reporting for non-Sized cycle cases; those currently just fallback to the old path. Also adjust tests.
1 parent 4bbe532 commit 3046ac2

40 files changed

+291
-92
lines changed

src/librustc/diagnostics.rs

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

715+
E0072: r##"
716+
When defining a recursive struct or enum, any use of the type being defined
717+
from inside the definition must occur behind a pointer (like `Box` or `&`).
718+
This is because structs and enums must have a well-defined size, and without
719+
the pointer the size of the type would need to be unbounded.
720+
721+
Consider the following erroneous definition of a type for a list of bytes:
722+
723+
```
724+
// error, invalid recursive struct type
725+
struct ListNode {
726+
head: u8,
727+
tail: Option<ListNode>,
728+
}
729+
```
730+
731+
This type cannot have a well-defined size, because it needs to be arbitrarily
732+
large (since we would be able to nest `ListNode`s to any depth). Specifically,
733+
734+
```plain
735+
size of `ListNode` = 1 byte for `head`
736+
+ 1 byte for the discriminant of the `Option`
737+
+ size of `ListNode`
738+
```
739+
740+
One way to fix this is by wrapping `ListNode` in a `Box`, like so:
741+
742+
```
743+
struct ListNode {
744+
head: u8,
745+
tail: Option<Box<ListNode>>,
746+
}
747+
```
748+
749+
This works because `Box` is a pointer, so its size is well-known.
750+
"##,
751+
715752
E0109: r##"
716753
You tried to give a type parameter to a type which doesn't need it. Erroneous
717754
code example:

src/librustc/middle/traits/error_reporting.rs

+140-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,141 @@ 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, _) if adt_def.did.is_local() =>
290+
Some((index, adt_def.did)),
291+
_ =>
292+
None,
293+
})
294+
.next()
295+
.unwrap(); // should always be SOME local type involved!
296+
297+
// Rotate so that the "main" type is at index 0.
298+
let struct_enum_tys: Vec<_> =
299+
struct_enum_tys.iter()
300+
.cloned()
301+
.skip(main_index)
302+
.chain(struct_enum_tys.iter().cloned().take(main_index))
303+
.collect();
304+
305+
let tcx = infcx.tcx;
306+
let mut err = recursive_type_with_infinite_size_error(tcx, main_def_id);
307+
let len = struct_enum_tys.len();
308+
if len > 2 {
309+
let span = tcx.map.span_if_local(main_def_id).unwrap();
310+
err.fileline_note(span,
311+
&format!("type `{}` is embedded within `{}`...",
312+
struct_enum_tys[0],
313+
struct_enum_tys[1]));
314+
for &next_ty in &struct_enum_tys[1..len-1] {
315+
err.fileline_note(span,
316+
&format!("...which in turn is embedded within `{}`...", next_ty));
317+
}
318+
err.fileline_note(span,
319+
&format!("...which in turn is embedded within `{}`, \
320+
completing the cycle.",
321+
struct_enum_tys[len-1]));
322+
}
323+
err.emit();
324+
infcx.tcx.sess.abort_if_errors();
325+
unreachable!();
326+
}
327+
328+
pub fn recursive_type_with_infinite_size_error<'tcx>(tcx: &ty::ctxt<'tcx>,
329+
type_def_id: DefId)
330+
-> DiagnosticBuilder<'tcx>
331+
{
332+
assert!(type_def_id.is_local());
333+
let span = tcx.map.span_if_local(type_def_id).unwrap();
334+
let mut err = struct_span_err!(tcx.sess, span, E0072, "recursive type `{}` has infinite size",
335+
tcx.item_path_str(type_def_id));
336+
err.fileline_help(span, &format!("insert indirection (e.g., a `Box`, `Rc`, or `&`) \
337+
at some point to make `{}` representable",
338+
tcx.item_path_str(type_def_id)));
339+
err
340+
}
341+
204342
pub fn report_selection_error<'a, 'tcx>(infcx: &InferCtxt<'a, 'tcx>,
205343
obligation: &PredicateObligation<'tcx>,
206344
error: &SelectionError<'tcx>)

src/librustc/middle/traits/fulfill.rs

+40-3
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
use middle::infer::InferCtxt;
1212
use middle::ty::{self, Ty, TypeFoldable};
1313
use rustc_data_structures::obligation_forest::{Backtrace, ObligationForest, Error};
14-
14+
use std::iter;
1515
use syntax::ast;
1616
use util::common::ErrorReported;
17-
use util::nodemap::{FnvHashSet, NodeMap};
17+
use util::nodemap::{FnvHashMap, FnvHashSet, NodeMap};
1818

1919
use super::CodeAmbiguity;
2020
use super::CodeProjectionError;
@@ -25,6 +25,7 @@ use super::FulfillmentErrorCode;
2525
use super::ObligationCause;
2626
use super::PredicateObligation;
2727
use super::project;
28+
use super::report_overflow_error_cycle;
2829
use super::select::SelectionContext;
2930
use super::Unimplemented;
3031
use super::util::predicate_for_builtin_bound;
@@ -357,6 +358,17 @@ fn process_predicate1<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
357358
}
358359

359360
let obligation = &pending_obligation.obligation;
361+
362+
// If we exceed the recursion limit, take a moment to look for a
363+
// cycle so we can give a better error report from here, where we
364+
// have more context.
365+
let recursion_limit = selcx.tcx().sess.recursion_limit.get();
366+
if obligation.recursion_depth >= recursion_limit {
367+
if let Some(cycle) = scan_for_cycle(obligation, &backtrace) {
368+
report_overflow_error_cycle(selcx.infcx(), &cycle);
369+
}
370+
}
371+
360372
match obligation.predicate {
361373
ty::Predicate::Trait(ref data) => {
362374
if coinductive_match(selcx, obligation, data, &backtrace) {
@@ -488,11 +500,15 @@ fn coinductive_match<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
488500
-> bool
489501
{
490502
if selcx.tcx().trait_has_default_impl(top_data.def_id()) {
503+
debug!("coinductive_match: top_data={:?}", top_data);
491504
for bt_obligation in backtrace.clone() {
505+
debug!("coinductive_match: bt_obligation={:?}", bt_obligation);
506+
492507
// *Everything* in the backtrace must be a defaulted trait.
493508
match bt_obligation.obligation.predicate {
494509
ty::Predicate::Trait(ref data) => {
495510
if !selcx.tcx().trait_has_default_impl(data.def_id()) {
511+
debug!("coinductive_match: trait does not have default impl");
496512
break;
497513
}
498514
}
@@ -501,7 +517,7 @@ fn coinductive_match<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
501517

502518
// And we must find a recursive match.
503519
if bt_obligation.obligation.predicate == top_obligation.predicate {
504-
debug!("process_predicate: found a match in the backtrace");
520+
debug!("coinductive_match: found a match in the backtrace");
505521
return true;
506522
}
507523
}
@@ -510,6 +526,27 @@ fn coinductive_match<'a,'tcx>(selcx: &mut SelectionContext<'a,'tcx>,
510526
false
511527
}
512528

529+
fn scan_for_cycle<'a,'tcx>(top_obligation: &PredicateObligation<'tcx>,
530+
backtrace: &Backtrace<PendingPredicateObligation<'tcx>>)
531+
-> Option<Vec<PredicateObligation<'tcx>>>
532+
{
533+
let mut map = FnvHashMap();
534+
let all_obligations =
535+
|| iter::once(top_obligation)
536+
.chain(backtrace.clone()
537+
.map(|p| &p.obligation));
538+
for (index, bt_obligation) in all_obligations().enumerate() {
539+
if let Some(&start) = map.get(&bt_obligation.predicate) {
540+
// Found a cycle starting at position `start` and running
541+
// until the current position (`index`).
542+
return Some(all_obligations().skip(start).take(index - start + 1).cloned().collect());
543+
} else {
544+
map.insert(bt_obligation.predicate.clone(), index);
545+
}
546+
}
547+
None
548+
}
549+
513550
fn register_region_obligation<'tcx>(t_a: Ty<'tcx>,
514551
r_b: ty::Region,
515552
cause: ObligationCause<'tcx>,

src/librustc/middle/traits/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ use syntax::ast;
2828
use syntax::codemap::{Span, DUMMY_SP};
2929

3030
pub use self::error_reporting::TraitErrorKey;
31+
pub use self::error_reporting::recursive_type_with_infinite_size_error;
3132
pub use self::error_reporting::report_fulfillment_errors;
3233
pub use self::error_reporting::report_overflow_error;
34+
pub use self::error_reporting::report_overflow_error_cycle;
3335
pub use self::error_reporting::report_selection_error;
3436
pub use self::error_reporting::report_object_safety_error;
3537
pub use self::coherence::orphan_check;

src/librustc/middle/traits/project.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ fn project_type<'cx,'tcx>(
479479
let recursion_limit = selcx.tcx().sess.recursion_limit.get();
480480
if obligation.recursion_depth >= recursion_limit {
481481
debug!("project: overflow!");
482-
report_overflow_error(selcx.infcx(), &obligation);
482+
report_overflow_error(selcx.infcx(), &obligation, true);
483483
}
484484

485485
let obligation_trait_ref =

src/librustc/middle/traits/select.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
711711
// not update) the cache.
712712
let recursion_limit = self.infcx.tcx.sess.recursion_limit.get();
713713
if stack.obligation.recursion_depth >= recursion_limit {
714-
report_overflow_error(self.infcx(), &stack.obligation);
714+
report_overflow_error(self.infcx(), &stack.obligation, true);
715715
}
716716

717717
// Check the cache. Note that we skolemize the trait-ref
@@ -2124,6 +2124,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
21242124
nested: ty::Binder<Vec<Ty<'tcx>>>)
21252125
-> VtableBuiltinData<PredicateObligation<'tcx>>
21262126
{
2127+
debug!("vtable_builtin_data(obligation={:?}, bound={:?}, nested={:?})",
2128+
obligation, bound, nested);
2129+
21272130
let trait_def = match self.tcx().lang_items.from_builtin_kind(bound) {
21282131
Ok(def_id) => def_id,
21292132
Err(_) => {

src/librustc_typeck/check/mod.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -4132,7 +4132,7 @@ fn check_const_with_ty<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
41324132
pub fn check_representable(tcx: &ty::ctxt,
41334133
sp: Span,
41344134
item_id: ast::NodeId,
4135-
designation: &str) -> bool {
4135+
_designation: &str) -> bool {
41364136
let rty = tcx.node_id_to_type(item_id);
41374137

41384138
// Check that it is possible to represent this type. This call identifies
@@ -4142,9 +4142,7 @@ pub fn check_representable(tcx: &ty::ctxt,
41424142
// caught by case 1.
41434143
match rty.is_representable(tcx, sp) {
41444144
Representability::SelfRecursive => {
4145-
struct_span_err!(tcx.sess, sp, E0072, "invalid recursive {} type", designation)
4146-
.fileline_help(sp, "wrap the inner value in a box to make it representable")
4147-
.emit();
4145+
traits::recursive_type_with_infinite_size_error(tcx, tcx.map.local_def_id(item_id)).emit();
41484146
return false
41494147
}
41504148
Representability::Representable | Representability::ContainsRecursive => (),

0 commit comments

Comments
 (0)