Skip to content
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

Suggest ?Sized when applicable for ADTs #73261

Merged
merged 4 commits into from
Jun 20, 2020
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
10 changes: 2 additions & 8 deletions src/librustc_hir/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2729,14 +2729,8 @@ impl Node<'_> {
pub fn generics(&self) -> Option<&Generics<'_>> {
match self {
Node::TraitItem(TraitItem { generics, .. })
| Node::ImplItem(ImplItem { generics, .. })
| Node::Item(Item {
kind:
ItemKind::Trait(_, _, generics, ..)
| ItemKind::Impl { generics, .. }
| ItemKind::Fn(_, generics, _),
..
}) => Some(generics),
| Node::ImplItem(ImplItem { generics, .. }) => Some(generics),
Node::Item(item) => item.kind.generics(),
_ => None,
}
}
Expand Down
156 changes: 130 additions & 26 deletions src/librustc_trait_selection/traits/error_reporting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder, ErrorReported};
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_hir::intravisit::Visitor;
use rustc_hir::Node;
use rustc_middle::mir::interpret::ErrorHandled;
use rustc_middle::ty::error::ExpectedFound;
Expand All @@ -25,7 +26,7 @@ use rustc_middle::ty::{
TypeFoldable, WithConstness,
};
use rustc_session::DiagnosticMessageId;
use rustc_span::{ExpnKind, Span, DUMMY_SP};
use rustc_span::{ExpnKind, MultiSpan, Span, DUMMY_SP};
use std::fmt;

use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
Expand Down Expand Up @@ -1695,36 +1696,95 @@ impl<'a, 'tcx> InferCtxtPrivExt<'tcx> for InferCtxt<'a, 'tcx> {
err: &mut DiagnosticBuilder<'tcx>,
obligation: &PredicateObligation<'tcx>,
) {
if let (
ty::PredicateKind::Trait(pred, _),
ObligationCauseCode::BindingObligation(item_def_id, span),
) = (obligation.predicate.kind(), &obligation.cause.code)
{
if let (Some(generics), true) = (
self.tcx.hir().get_if_local(*item_def_id).as_ref().and_then(|n| n.generics()),
Some(pred.def_id()) == self.tcx.lang_items().sized_trait(),
) {
for param in generics.params {
if param.span == *span
&& !param.bounds.iter().any(|bound| {
bound.trait_ref().and_then(|trait_ref| trait_ref.trait_def_id())
== self.tcx.lang_items().sized_trait()
})
{
let (span, separator) = match param.bounds {
[] => (span.shrink_to_hi(), ":"),
[.., bound] => (bound.span().shrink_to_hi(), " +"),
};
err.span_suggestion_verbose(
span,
"consider relaxing the implicit `Sized` restriction",
format!("{} ?Sized", separator),
Applicability::MachineApplicable,
let (pred, item_def_id, span) =
match (obligation.predicate.kind(), &obligation.cause.code.peel_derives()) {
(
ty::PredicateKind::Trait(pred, _),
ObligationCauseCode::BindingObligation(item_def_id, span),
) => (pred, item_def_id, span),
_ => return,
};

let node = match (
self.tcx.hir().get_if_local(*item_def_id),
Some(pred.def_id()) == self.tcx.lang_items().sized_trait(),
) {
(Some(node), true) => node,
_ => return,
};
let generics = match node.generics() {
Some(generics) => generics,
None => return,
};
for param in generics.params {
if param.span != *span
|| param.bounds.iter().any(|bound| {
bound.trait_ref().and_then(|trait_ref| trait_ref.trait_def_id())
== self.tcx.lang_items().sized_trait()
})
{
continue;
}
match node {
hir::Node::Item(
item
@
hir::Item {
kind:
hir::ItemKind::Enum(..)
| hir::ItemKind::Struct(..)
| hir::ItemKind::Union(..),
..
},
) => {
// Suggesting `T: ?Sized` is only valid in an ADT if `T` is only used in a
// borrow. `struct S<'a, T: ?Sized>(&'a T);` is valid, `struct S<T: ?Sized>(T);`
// is not.
let mut visitor = FindTypeParam {
param: param.name.ident().name,
invalid_spans: vec![],
nested: false,
};
visitor.visit_item(item);
if !visitor.invalid_spans.is_empty() {
let mut multispan: MultiSpan = param.span.into();
multispan.push_span_label(
param.span,
format!("this could be changed to `{}: ?Sized`...", param.name.ident()),
);
for sp in visitor.invalid_spans {
multispan.push_span_label(
sp,
format!(
"...if indirection was used here: `Box<{}>`",
param.name.ident(),
),
);
}
err.span_help(
multispan,
&format!(
"you could relax the implicit `Sized` bound on `{T}` if it were \
used through indirection like `&{T}` or `Box<{T}>`",
T = param.name.ident(),
),
);
return;
}
}
_ => {}
}
let (span, separator) = match param.bounds {
[] => (span.shrink_to_hi(), ":"),
[.., bound] => (bound.span().shrink_to_hi(), " +"),
};
err.span_suggestion_verbose(
span,
"consider relaxing the implicit `Sized` restriction",
format!("{} ?Sized", separator),
Applicability::MachineApplicable,
);
return;
}
}

Expand All @@ -1744,6 +1804,50 @@ impl<'a, 'tcx> InferCtxtPrivExt<'tcx> for InferCtxt<'a, 'tcx> {
}
}

/// Look for type `param` in an ADT being used only through a reference to confirm that suggesting
/// `param: ?Sized` would be a valid constraint.
struct FindTypeParam {
param: rustc_span::Symbol,
invalid_spans: Vec<Span>,
nested: bool,
}

impl<'v> Visitor<'v> for FindTypeParam {
type Map = rustc_hir::intravisit::ErasedMap<'v>;

fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap<Self::Map> {
hir::intravisit::NestedVisitorMap::None
}

fn visit_ty(&mut self, ty: &hir::Ty<'_>) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this analysis doesn't seem right. For example, consider an example like this:

struct Foo<T> {
    x: Vec<T>, // requires `T: Sized`
    y: Box<T>, // does not require `T: Sized`
}

if I'm reading the code correctly, it is going to highlight both of those uses of T?

Doing this correctly is going to be a touch tricky at least, but we could make it more precise in various ways. For example, we could go over the list of fields and look at their Ty<'tcx> type to see if its WF conditions require that T: Sized, and only then walk over the HIR for the type.

Copy link
Contributor Author

@estebank estebank Jun 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now only point at the uses of T when it is bare, otherwise we assume that they allow ?Sized. That example will suggest T: ?Sized:

error[E0277]: the size for values of type `T` cannot be known at compilation time
 --> file6.rs:6:21
  |
1 | struct Foo<T> {
  |            - required by this bound in `Foo`
...
6 | struct X<T: ?Sized>(Foo<T>);
  |          -          ^^^^^^ doesn't have a size known at compile-time
  |          |
  |          this type parameter needs to be `std::marker::Sized`
  |
  = help: the trait `std::marker::Sized` is not implemented for `T`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
help: consider relaxing the implicit `Sized` restriction
  |
1 | struct Foo<T: ?Sized> {
  |             ^^^^^^^^

When applying it you get

error[E0277]: the size for values of type `T` cannot be known at compilation time
   --> file6.rs:2:5
    |: aborting due to previous error
1   | struct Foo<T: ?Sized> {
    |            - this type parameter needs to be `std::marker::Sized`
2   |     x: Vec<T>,
    |     ^^^^^^^^^ doesn't have a size known at compile-time
    |
    = help: the trait `std::marker::Sized` is not implemented for `T`
    = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>

I am indeed not verifying whether Vec or Box have a T: ?Sized or T: Sized constraint. That would be a good thing to add, but I feel that the current behavior is good enough before adding more code to probe Vec and Box to identify what their bounds are.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK -- can you maybe add some comments into the code explaining that a bit? i.e., what is the goal of this visitor and can you clarify where it makes 'safe assumptions'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment.

// We collect the spans of all uses of the "bare" type param, like in `field: T` or
// `field: (T, T)` where we could make `T: ?Sized` while skipping cases that are known to be
// valid like `field: &'a T` or `field: *mut T` and cases that *might* have further `Sized`
// obligations like `Box<T>` and `Vec<T>`, but we perform no extra analysis for those cases
// and suggest `T: ?Sized` regardless of their obligations. This is fine because the errors
// in that case should make what happened clear enough.
match ty.kind {
hir::TyKind::Ptr(_) | hir::TyKind::Rptr(..) | hir::TyKind::TraitObject(..) => {}
hir::TyKind::Path(hir::QPath::Resolved(None, path))
if path.segments.len() == 1 && path.segments[0].ident.name == self.param =>
{
if !self.nested {
self.invalid_spans.push(ty.span);
}
}
hir::TyKind::Path(_) => {
let prev = self.nested;
self.nested = true;
hir::intravisit::walk_ty(self, ty);
self.nested = prev;
}
_ => {
hir::intravisit::walk_ty(self, ty);
}
}
}
}

pub fn recursive_type_with_infinite_size_error(
tcx: TyCtxt<'tcx>,
type_def_id: DefId,
Expand Down
4 changes: 4 additions & 0 deletions src/test/ui/dst/dst-sized-trait-param.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ LL | impl Foo<[isize]> for usize { }
|
= help: the trait `std::marker::Sized` is not implemented for `[isize]`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
help: consider relaxing the implicit `Sized` restriction
|
LL | trait Foo<T: ?Sized> : Sized { fn take(self, x: &T) { } } // Note: T is sized
| ^^^^^^^^

error[E0277]: the size for values of type `[usize]` cannot be known at compilation time
--> $DIR/dst-sized-trait-param.rs:10:6
Expand Down
12 changes: 12 additions & 0 deletions src/test/ui/extern/extern-types-unsized.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ LL | assert_sized::<Foo>();
= help: within `Foo`, the trait `std::marker::Sized` is not implemented for `A`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= note: required because it appears within the type `Foo`
help: consider relaxing the implicit `Sized` restriction
|
LL | fn assert_sized<T: ?Sized>() { }
| ^^^^^^^^

error[E0277]: the size for values of type `A` cannot be known at compilation time
--> $DIR/extern-types-unsized.rs:28:5
Expand All @@ -39,6 +43,10 @@ LL | assert_sized::<Bar<A>>();
= help: within `Bar<A>`, the trait `std::marker::Sized` is not implemented for `A`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= note: required because it appears within the type `Bar<A>`
help: consider relaxing the implicit `Sized` restriction
|
LL | fn assert_sized<T: ?Sized>() { }
| ^^^^^^^^

error[E0277]: the size for values of type `A` cannot be known at compilation time
--> $DIR/extern-types-unsized.rs:31:5
Expand All @@ -53,6 +61,10 @@ LL | assert_sized::<Bar<Bar<A>>>();
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= note: required because it appears within the type `Bar<A>`
= note: required because it appears within the type `Bar<Bar<A>>`
help: consider relaxing the implicit `Sized` restriction
|
LL | fn assert_sized<T: ?Sized>() { }
| ^^^^^^^^

error: aborting due to 4 previous errors

Expand Down
4 changes: 4 additions & 0 deletions src/test/ui/issues/issue-10412.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ LL | impl<'self> Serializable<str> for &'self str {
|
= help: the trait `std::marker::Sized` is not implemented for `str`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
help: consider relaxing the implicit `Sized` restriction
|
LL | trait Serializable<'self, T: ?Sized> {
| ^^^^^^^^

error: aborting due to 9 previous errors

Expand Down
7 changes: 7 additions & 0 deletions src/test/ui/issues/issue-18919.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ LL | enum Option<T> {
|
= help: the trait `std::marker::Sized` is not implemented for `dyn for<'r> std::ops::Fn(&'r isize) -> isize`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
help: you could relax the implicit `Sized` bound on `T` if it were used through indirection like `&T` or `Box<T>`
--> $DIR/issue-18919.rs:7:13
|
LL | enum Option<T> {
| ^ this could be changed to `T: ?Sized`...
LL | Some(T),
| - ...if indirection was used here: `Box<T>`

error: aborting due to previous error

Expand Down
7 changes: 7 additions & 0 deletions src/test/ui/issues/issue-23281.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ LL | struct Vec<T> {
|
= help: the trait `std::marker::Sized` is not implemented for `(dyn std::ops::Fn() + 'static)`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
help: you could relax the implicit `Sized` bound on `T` if it were used through indirection like `&T` or `Box<T>`
--> $DIR/issue-23281.rs:8:12
|
LL | struct Vec<T> {
| ^ this could be changed to `T: ?Sized`...
LL | t: T,
| - ...if indirection was used here: `Box<T>`

error: aborting due to previous error

Expand Down
28 changes: 28 additions & 0 deletions src/test/ui/suggestions/adt-param-with-implicit-sized-bound.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
trait Trait {
fn func1() -> Struct1<Self>; //~ ERROR E0277
fn func2<'a>() -> Struct2<'a, Self>; //~ ERROR E0277
fn func3() -> Struct3<Self>; //~ ERROR E0277
fn func4() -> Struct4<Self>; //~ ERROR E0277
}

struct Struct1<T>{
_t: std::marker::PhantomData<*const T>,
}
struct Struct2<'a, T>{
_t: &'a T,
}
struct Struct3<T>{
_t: T,
}

struct X<T>(T);

struct Struct4<T>{
_t: X<T>,
}

struct Struct5<T: ?Sized>{
_t: X<T>, //~ ERROR E0277
}

fn main() {}
Loading