Skip to content

Specialize infinite-type "insert some indirection" suggestion for Option #91416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 48 additions & 11 deletions compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2285,10 +2285,10 @@ impl<'v> Visitor<'v> for FindTypeParam {
}
}

pub fn recursive_type_with_infinite_size_error(
tcx: TyCtxt<'_>,
pub fn recursive_type_with_infinite_size_error<'tcx>(
tcx: TyCtxt<'tcx>,
type_def_id: DefId,
spans: Vec<Span>,
spans: Vec<(Span, Option<hir::HirId>)>,
) {
assert!(type_def_id.is_local());
let span = tcx.hir().span_if_local(type_def_id).unwrap();
Expand All @@ -2297,24 +2297,33 @@ pub fn recursive_type_with_infinite_size_error(
let mut err =
struct_span_err!(tcx.sess, span, E0072, "recursive type `{}` has infinite size", path);
err.span_label(span, "recursive type has infinite size");
for &span in &spans {
for &(span, _) in &spans {
err.span_label(span, "recursive without indirection");
}
let msg = format!(
"insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `{}` representable",
path,
);
if spans.len() <= 4 {
// FIXME(compiler-errors): This suggestion might be erroneous if Box is shadowed
err.multipart_suggestion(
&msg,
spans
.iter()
.flat_map(|&span| {
[
(span.shrink_to_lo(), "Box<".to_string()),
(span.shrink_to_hi(), ">".to_string()),
]
.into_iter()
.into_iter()
.flat_map(|(span, field_id)| {
if let Some(generic_span) = get_option_generic_from_field_id(tcx, field_id) {
// If we match an `Option` and can grab the span of the Option's generic, then
// suggest boxing the generic arg for a non-null niche optimization.
vec![
(generic_span.shrink_to_lo(), "Box<".to_string()),
(generic_span.shrink_to_hi(), ">".to_string()),
]
} else {
vec![
(span.shrink_to_lo(), "Box<".to_string()),
(span.shrink_to_hi(), ">".to_string()),
]
}
})
.collect(),
Applicability::HasPlaceholders,
Expand All @@ -2325,6 +2334,34 @@ pub fn recursive_type_with_infinite_size_error(
err.emit();
}

/// Extract the span for the generic type `T` of `Option<T>` in a field definition
fn get_option_generic_from_field_id(tcx: TyCtxt<'_>, field_id: Option<hir::HirId>) -> Option<Span> {
let node = tcx.hir().find(field_id?);

// Expect a field from our field_id
let Some(hir::Node::Field(field_def)) = node
else { bug!("Expected HirId corresponding to FieldDef, found: {:?}", node) };

// Match a type that is a simple QPath with no Self
let hir::TyKind::Path(hir::QPath::Resolved(None, path)) = &field_def.ty.kind
else { return None };

// Check if the path we're checking resolves to Option
let hir::def::Res::Def(_, did) = path.res
else { return None };

// Bail if this path doesn't describe `::core::option::Option`
if !tcx.is_diagnostic_item(sym::Option, did) {
return None;
}

// Match a single generic arg in the 0th path segment
let generic_arg = path.segments.last()?.args?.args.get(0)?;

// Take the span out of the type, if it's a type
if let hir::GenericArg::Type(generic_ty) = generic_arg { Some(generic_ty.span) } else { None }
}

/// Summarizes information
#[derive(Clone)]
pub enum ArgKind {
Expand Down
70 changes: 47 additions & 23 deletions compiler/rustc_ty_utils/src/representability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,20 @@ use std::cmp;
pub enum Representability {
Representable,
ContainsRecursive,
SelfRecursive(Vec<Span>),
/// Return a list of types that are included in themselves:
/// the spans where they are self-included, and (if found)
/// the HirId of the FieldDef that defines the self-inclusion.
SelfRecursive(Vec<(Span, Option<hir::HirId>)>),
}

/// Check whether a type is representable. This means it cannot contain unboxed
/// structural recursion. This check is needed for structs and enums.
pub fn ty_is_representable<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, sp: Span) -> Representability {
pub fn ty_is_representable<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
sp: Span,
field_id: Option<hir::HirId>,
) -> Representability {
debug!("is_type_representable: {:?}", ty);
// To avoid a stack overflow when checking an enum variant or struct that
// contains a different, structurally recursive type, maintain a stack of
Expand All @@ -38,11 +46,12 @@ pub fn ty_is_representable<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, sp: Span) -> R
let mut force_result = false;
let r = is_type_structurally_recursive(
tcx,
sp,
&mut seen,
&mut shadow_seen,
&mut representable_cache,
ty,
sp,
field_id,
&mut force_result,
);
debug!("is_type_representable: {:?} is {:?}", ty, r);
Expand All @@ -61,11 +70,12 @@ fn fold_repr<It: Iterator<Item = Representability>>(iter: It) -> Representabilit

fn are_inner_types_recursive<'tcx>(
tcx: TyCtxt<'tcx>,
sp: Span,
seen: &mut Vec<Ty<'tcx>>,
shadow_seen: &mut Vec<ty::AdtDef<'tcx>>,
representable_cache: &mut FxHashMap<Ty<'tcx>, Representability>,
ty: Ty<'tcx>,
sp: Span,
field_id: Option<hir::HirId>,
force_result: &mut bool,
) -> Representability {
debug!("are_inner_types_recursive({:?}, {:?}, {:?})", ty, seen, shadow_seen);
Expand All @@ -75,11 +85,12 @@ fn are_inner_types_recursive<'tcx>(
fold_repr(fields.iter().map(|ty| {
is_type_structurally_recursive(
tcx,
sp,
seen,
shadow_seen,
representable_cache,
ty,
sp,
field_id,
force_result,
)
}))
Expand All @@ -88,20 +99,26 @@ fn are_inner_types_recursive<'tcx>(
// FIXME(#11924) Behavior undecided for zero-length vectors.
ty::Array(ty, _) => is_type_structurally_recursive(
tcx,
sp,
seen,
shadow_seen,
representable_cache,
*ty,
sp,
field_id,
force_result,
),
ty::Adt(def, substs) => {
// Find non representable fields with their spans
fold_repr(def.all_fields().map(|field| {
let ty = field.ty(tcx, substs);
let span = match field.did.as_local().and_then(|id| tcx.hir().find_by_def_id(id)) {
Some(hir::Node::Field(field)) => field.ty.span,
_ => sp,
let (sp, field_id) = match field
.did
.as_local()
.map(|id| tcx.hir().local_def_id_to_hir_id(id))
.and_then(|id| tcx.hir().find(id))
{
Some(hir::Node::Field(field)) => (field.ty.span, Some(field.hir_id)),
_ => (sp, field_id),
};

let mut result = None;
Expand Down Expand Up @@ -130,7 +147,7 @@ fn are_inner_types_recursive<'tcx>(
// result without adjusting).
if shadow_seen.len() > seen.len() && shadow_seen.first() == Some(def) {
*force_result = true;
result = Some(Representability::SelfRecursive(vec![span]));
result = Some(Representability::SelfRecursive(vec![(sp, field_id)]));
}

if result == None {
Expand Down Expand Up @@ -161,16 +178,17 @@ fn are_inner_types_recursive<'tcx>(
result = Some(
match is_type_structurally_recursive(
tcx,
span,
&mut nested_seen,
shadow_seen,
representable_cache,
raw_adt_ty,
sp,
field_id,
force_result,
) {
Representability::SelfRecursive(_) => {
if *force_result {
Representability::SelfRecursive(vec![span])
Representability::SelfRecursive(vec![(sp, field_id)])
} else {
Representability::ContainsRecursive
}
Expand Down Expand Up @@ -208,15 +226,16 @@ fn are_inner_types_recursive<'tcx>(
result = Some(
match is_type_structurally_recursive(
tcx,
span,
seen,
shadow_seen,
representable_cache,
ty,
sp,
field_id,
force_result,
) {
Representability::SelfRecursive(_) => {
Representability::SelfRecursive(vec![span])
Representability::SelfRecursive(vec![(sp, field_id)])
}
x => x,
},
Expand Down Expand Up @@ -247,29 +266,31 @@ fn same_adt<'tcx>(ty: Ty<'tcx>, def: ty::AdtDef<'tcx>) -> bool {
// contain any types on stack `seen`?
fn is_type_structurally_recursive<'tcx>(
tcx: TyCtxt<'tcx>,
sp: Span,
seen: &mut Vec<Ty<'tcx>>,
shadow_seen: &mut Vec<ty::AdtDef<'tcx>>,
representable_cache: &mut FxHashMap<Ty<'tcx>, Representability>,
ty: Ty<'tcx>,
sp: Span,
field_id: Option<hir::HirId>,
force_result: &mut bool,
) -> Representability {
debug!("is_type_structurally_recursive: {:?} {:?}", ty, sp);
debug!("is_type_structurally_recursive: {:?} {:?} {:?}", ty, sp, field_id);
if let Some(representability) = representable_cache.get(&ty) {
debug!(
"is_type_structurally_recursive: {:?} {:?} - (cached) {:?}",
ty, sp, representability
"is_type_structurally_recursive: {:?} {:?} {:?} - (cached) {:?}",
ty, sp, field_id, representability
);
return representability.clone();
}

let representability = is_type_structurally_recursive_inner(
tcx,
sp,
seen,
shadow_seen,
representable_cache,
ty,
sp,
field_id,
force_result,
);

Expand All @@ -279,11 +300,12 @@ fn is_type_structurally_recursive<'tcx>(

fn is_type_structurally_recursive_inner<'tcx>(
tcx: TyCtxt<'tcx>,
sp: Span,
seen: &mut Vec<Ty<'tcx>>,
shadow_seen: &mut Vec<ty::AdtDef<'tcx>>,
representable_cache: &mut FxHashMap<Ty<'tcx>, Representability>,
ty: Ty<'tcx>,
sp: Span,
field_id: Option<hir::HirId>,
force_result: &mut bool,
) -> Representability {
match ty.kind() {
Expand All @@ -305,7 +327,7 @@ fn is_type_structurally_recursive_inner<'tcx>(
if let Some(&seen_adt) = iter.next() {
if same_adt(seen_adt, *def) {
debug!("SelfRecursive: {:?} contains {:?}", seen_adt, ty);
return Representability::SelfRecursive(vec![sp]);
return Representability::SelfRecursive(vec![(sp, field_id)]);
}
}

Expand Down Expand Up @@ -335,11 +357,12 @@ fn is_type_structurally_recursive_inner<'tcx>(
shadow_seen.push(*def);
let out = are_inner_types_recursive(
tcx,
sp,
seen,
shadow_seen,
representable_cache,
ty,
sp,
field_id,
force_result,
);
shadow_seen.pop();
Expand All @@ -350,11 +373,12 @@ fn is_type_structurally_recursive_inner<'tcx>(
// No need to push in other cases.
are_inner_types_recursive(
tcx,
sp,
seen,
shadow_seen,
representable_cache,
ty,
sp,
field_id,
force_result,
)
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_typeck/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,7 @@ pub(super) fn check_representable(tcx: TyCtxt<'_>, sp: Span, item_def_id: LocalD
// recursive type. It is only necessary to throw an error on those that
// contain themselves. For case 2, there must be an inner type that will be
// caught by case 1.
match representability::ty_is_representable(tcx, rty, sp) {
match representability::ty_is_representable(tcx, rty, sp, None) {
Representability::SelfRecursive(spans) => {
recursive_type_with_infinite_size_error(tcx, item_def_id.to_def_id(), spans);
return false;
Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/issues/issue-17431-1.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ LL | struct Foo { foo: Option<Option<Foo>> }
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Foo` representable
|
LL | struct Foo { foo: Box<Option<Option<Foo>>> }
| ++++ +
LL | struct Foo { foo: Option<Box<Option<Foo>>> }
| ++++ +

error: aborting due to previous error

Expand Down
8 changes: 4 additions & 4 deletions src/test/ui/issues/issue-17431-2.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ LL | struct Baz { q: Option<Foo> }
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Baz` representable
|
LL | struct Baz { q: Box<Option<Foo>> }
| ++++ +
LL | struct Baz { q: Option<Box<Foo>> }
| ++++ +

error[E0072]: recursive type `Foo` has infinite size
--> $DIR/issue-17431-2.rs:4:1
Expand All @@ -21,8 +21,8 @@ LL | struct Foo { q: Option<Baz> }
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Foo` representable
|
LL | struct Foo { q: Box<Option<Baz>> }
| ++++ +
LL | struct Foo { q: Option<Box<Baz>> }
| ++++ +

error: aborting due to 2 previous errors

Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/issues/issue-17431-4.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ LL | struct Foo<T> { foo: Option<Option<Foo<T>>>, marker: marker::PhantomData<T>
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Foo` representable
|
LL | struct Foo<T> { foo: Box<Option<Option<Foo<T>>>>, marker: marker::PhantomData<T> }
| ++++ +
LL | struct Foo<T> { foo: Option<Box<Option<Foo<T>>>>, marker: marker::PhantomData<T> }
| ++++ +

error: aborting due to previous error

Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/issues/issue-17431-7.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ LL | enum Foo { Voo(Option<Option<Foo>>) }
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Foo` representable
|
LL | enum Foo { Voo(Box<Option<Option<Foo>>>) }
| ++++ +
LL | enum Foo { Voo(Option<Box<Option<Foo>>>) }
| ++++ +

error: aborting due to previous error

Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/issues/issue-3779.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ LL | element: Option<S>
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `S` representable
|
LL | element: Box<Option<S>>
| ++++ +
LL | element: Option<Box<S>>
| ++++ +

error: aborting due to previous error

Expand Down
Loading