Rollup merge of #41205 - estebank:shorter-mismatched-types-2, r=nikomatsakis

Highlight and simplify mismatched types

Shorten mismatched types errors by replacing subtypes that are not
different with `_`, and highlighting only the subtypes that are
different.

Given a file

```rust
struct X<T1, T2> {
    x: T1,
    y: T2,
}

fn foo() -> X<X<String, String>, String> {
    X { x: X {x: "".to_string(), y: 2}, y: "".to_string()}
}

fn bar() -> Option<String> {
    "".to_string()
}
```

provide the following output

```rust
error[E0308]: mismatched types
  --> file.rs:6:5
   |
 6 |     X { x: X {x: "".to_string(), y: 2}, y: "".to_string()}
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::string::String`, found {integer}
   |
   = note: expected type `X<X<_, std::string::String>, _>`
                                 ^^^^^^^^^^^^^^^^^^^   // < highlighted
              found type `X<X<_, {integer}>, _>`
                                 ^^^^^^^^^             // < highlighted

error[E0308]: mismatched types
  --> file.rs:6:5
   |
10 |     "".to_string()
   |     ^^^^^^^^^^^^^^ expected struct `std::option::Option`, found `std::string::String`
   |
   = note: expected type `Option<std::string::String>`
                          ^^^^^^^                   ^  // < highlighted
              found type `std::string::String`
```

Fix #21025. Re: #40186. Follow up to #39906.

I'm looking to change how this output is accomplished so that it doesn't create list of strings to pass around, but rather add an elided `Ty` placeholder, and use the same string formatting for normal types. I'll be doing that soonish.

r? @nikomatsakis
This commit is contained in:
Tim Neumann 2017-04-12 14:45:45 +02:00 committed by GitHub
commit afb300d831
11 changed files with 485 additions and 32 deletions

View File

@ -70,7 +70,7 @@ use ty::{self, TyCtxt, TypeFoldable};
use ty::{Region, Issue32330};
use ty::error::TypeError;
use syntax_pos::{Pos, Span};
use errors::DiagnosticBuilder;
use errors::{DiagnosticBuilder, DiagnosticStyledString};
mod note;
@ -365,6 +365,262 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
}
}
/// Given that `other_ty` is the same as a type argument for `name` in `sub`, populate `value`
/// highlighting `name` and every type argument that isn't at `pos` (which is `other_ty`), and
/// populate `other_value` with `other_ty`.
///
/// ```text
/// Foo<Bar<Qux>>
/// ^^^^--------^ this is highlighted
/// | |
/// | this type argument is exactly the same as the other type, not highlighted
/// this is highlighted
/// Bar<Qux>
/// -------- this type is the same as a type argument in the other type, not highlighted
/// ```
fn highlight_outer(&self,
mut value: &mut DiagnosticStyledString,
mut other_value: &mut DiagnosticStyledString,
name: String,
sub: &ty::subst::Substs<'tcx>,
pos: usize,
other_ty: &ty::Ty<'tcx>) {
// `value` and `other_value` hold two incomplete type representation for display.
// `name` is the path of both types being compared. `sub`
value.push_highlighted(name);
let len = sub.len();
if len > 0 {
value.push_highlighted("<");
}
// Output the lifetimes fot the first type
let lifetimes = sub.regions().map(|lifetime| {
let s = format!("{}", lifetime);
if s.is_empty() {
"'_".to_string()
} else {
s
}
}).collect::<Vec<_>>().join(", ");
if !lifetimes.is_empty() {
if sub.regions().count() < len {
value.push_normal(lifetimes + &", ");
} else {
value.push_normal(lifetimes);
}
}
// Highlight all the type arguments that aren't at `pos` and compare the type argument at
// `pos` and `other_ty`.
for (i, type_arg) in sub.types().enumerate() {
if i == pos {
let values = self.cmp(type_arg, other_ty);
value.0.extend((values.0).0);
other_value.0.extend((values.1).0);
} else {
value.push_highlighted(format!("{}", type_arg));
}
if len > 0 && i != len - 1 {
value.push_normal(", ");
}
//self.push_comma(&mut value, &mut other_value, len, i);
}
if len > 0 {
value.push_highlighted(">");
}
}
/// If `other_ty` is the same as a type argument present in `sub`, highlight `path` in `t1_out`,
/// as that is the difference to the other type.
///
/// For the following code:
///
/// ```norun
/// let x: Foo<Bar<Qux>> = foo::<Bar<Qux>>();
/// ```
///
/// The type error output will behave in the following way:
///
/// ```text
/// Foo<Bar<Qux>>
/// ^^^^--------^ this is highlighted
/// | |
/// | this type argument is exactly the same as the other type, not highlighted
/// this is highlighted
/// Bar<Qux>
/// -------- this type is the same as a type argument in the other type, not highlighted
/// ```
fn cmp_type_arg(&self,
mut t1_out: &mut DiagnosticStyledString,
mut t2_out: &mut DiagnosticStyledString,
path: String,
sub: &ty::subst::Substs<'tcx>,
other_path: String,
other_ty: &ty::Ty<'tcx>) -> Option<()> {
for (i, ta) in sub.types().enumerate() {
if &ta == other_ty {
self.highlight_outer(&mut t1_out, &mut t2_out, path, sub, i, &other_ty);
return Some(());
}
if let &ty::TyAdt(def, _) = &ta.sty {
let path_ = self.tcx.item_path_str(def.did.clone());
if path_ == other_path {
self.highlight_outer(&mut t1_out, &mut t2_out, path, sub, i, &other_ty);
return Some(());
}
}
}
None
}
/// Add a `,` to the type representation only if it is appropriate.
fn push_comma(&self,
value: &mut DiagnosticStyledString,
other_value: &mut DiagnosticStyledString,
len: usize,
pos: usize) {
if len > 0 && pos != len - 1 {
value.push_normal(", ");
other_value.push_normal(", ");
}
}
/// Compare two given types, eliding parts that are the same between them and highlighting
/// relevant differences, and return two representation of those types for highlighted printing.
fn cmp(&self, t1: ty::Ty<'tcx>, t2: ty::Ty<'tcx>)
-> (DiagnosticStyledString, DiagnosticStyledString)
{
match (&t1.sty, &t2.sty) {
(&ty::TyAdt(def1, sub1), &ty::TyAdt(def2, sub2)) => {
let mut values = (DiagnosticStyledString::new(), DiagnosticStyledString::new());
let path1 = self.tcx.item_path_str(def1.did.clone());
let path2 = self.tcx.item_path_str(def2.did.clone());
if def1.did == def2.did {
// Easy case. Replace same types with `_` to shorten the output and highlight
// the differing ones.
// let x: Foo<Bar, Qux> = y::<Foo<Quz, Qux>>();
// Foo<Bar, _>
// Foo<Quz, _>
// --- ^ type argument elided
// |
// highlighted in output
values.0.push_normal(path1);
values.1.push_normal(path2);
// Only draw `<...>` if there're lifetime/type arguments.
let len = sub1.len();
if len > 0 {
values.0.push_normal("<");
values.1.push_normal("<");
}
fn lifetime_display(lifetime: &Region) -> String {
let s = format!("{}", lifetime);
if s.is_empty() {
"'_".to_string()
} else {
s
}
}
// At one point we'd like to elide all lifetimes here, they are irrelevant for
// all diagnostics that use this output
//
// Foo<'x, '_, Bar>
// Foo<'y, '_, Qux>
// ^^ ^^ --- type arguments are not elided
// | |
// | elided as they were the same
// not elided, they were different, but irrelevant
let lifetimes = sub1.regions().zip(sub2.regions());
for (i, lifetimes) in lifetimes.enumerate() {
let l1 = lifetime_display(lifetimes.0);
let l2 = lifetime_display(lifetimes.1);
if l1 == l2 {
values.0.push_normal("'_");
values.1.push_normal("'_");
} else {
values.0.push_highlighted(l1);
values.1.push_highlighted(l2);
}
self.push_comma(&mut values.0, &mut values.1, len, i);
}
// We're comparing two types with the same path, so we compare the type
// arguments for both. If they are the same, do not highlight and elide from the
// output.
// Foo<_, Bar>
// Foo<_, Qux>
// ^ elided type as this type argument was the same in both sides
let type_arguments = sub1.types().zip(sub2.types());
let regions_len = sub1.regions().collect::<Vec<_>>().len();
for (i, (ta1, ta2)) in type_arguments.enumerate() {
let i = i + regions_len;
if ta1 == ta2 {
values.0.push_normal("_");
values.1.push_normal("_");
} else {
let (x1, x2) = self.cmp(ta1, ta2);
(values.0).0.extend(x1.0);
(values.1).0.extend(x2.0);
}
self.push_comma(&mut values.0, &mut values.1, len, i);
}
// Close the type argument bracket.
// Only draw `<...>` if there're lifetime/type arguments.
if len > 0 {
values.0.push_normal(">");
values.1.push_normal(">");
}
values
} else {
// Check for case:
// let x: Foo<Bar<Qux> = foo::<Bar<Qux>>();
// Foo<Bar<Qux>
// ------- this type argument is exactly the same as the other type
// Bar<Qux>
if self.cmp_type_arg(&mut values.0,
&mut values.1,
path1.clone(),
sub1,
path2.clone(),
&t2).is_some() {
return values;
}
// Check for case:
// let x: Bar<Qux> = y:<Foo<Bar<Qux>>>();
// Bar<Qux>
// Foo<Bar<Qux>>
// ------- this type argument is exactly the same as the other type
if self.cmp_type_arg(&mut values.1,
&mut values.0,
path2,
sub2,
path1,
&t1).is_some() {
return values;
}
// We couldn't find anything in common, highlight everything.
// let x: Bar<Qux> = y::<Foo<Zar>>();
(DiagnosticStyledString::highlighted(format!("{}", t1)),
DiagnosticStyledString::highlighted(format!("{}", t2)))
}
}
_ => {
if t1 == t2 {
// The two types are the same, elide and don't highlight.
(DiagnosticStyledString::normal("_"), DiagnosticStyledString::normal("_"))
} else {
// We couldn't find anything in common, highlight everything.
(DiagnosticStyledString::highlighted(format!("{}", t1)),
DiagnosticStyledString::highlighted(format!("{}", t2)))
}
}
}
}
pub fn note_type_err(&self,
diag: &mut DiagnosticBuilder<'tcx>,
cause: &ObligationCause<'tcx>,
@ -397,14 +653,14 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
if let Some((expected, found)) = expected_found {
match (terr, is_simple_error, expected == found) {
(&TypeError::Sorts(ref values), false, true) => {
(&TypeError::Sorts(ref values), false, true) => {
diag.note_expected_found_extra(
&"type", &expected, &found,
&"type", expected, found,
&format!(" ({})", values.expected.sort_string(self.tcx)),
&format!(" ({})", values.found.sort_string(self.tcx)));
}
(_, false, _) => {
diag.note_expected_found(&"type", &expected, &found);
diag.note_expected_found(&"type", expected, found);
}
_ => (),
}
@ -472,26 +728,40 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
diag
}
/// Returns a string of the form "expected `{}`, found `{}`".
fn values_str(&self, values: &ValuePairs<'tcx>) -> Option<(String, String)> {
fn values_str(&self, values: &ValuePairs<'tcx>)
-> Option<(DiagnosticStyledString, DiagnosticStyledString)>
{
match *values {
infer::Types(ref exp_found) => self.expected_found_str(exp_found),
infer::Types(ref exp_found) => self.expected_found_str_ty(exp_found),
infer::TraitRefs(ref exp_found) => self.expected_found_str(exp_found),
infer::PolyTraitRefs(ref exp_found) => self.expected_found_str(exp_found),
}
}
fn expected_found_str_ty(&self,
exp_found: &ty::error::ExpectedFound<ty::Ty<'tcx>>)
-> Option<(DiagnosticStyledString, DiagnosticStyledString)> {
let exp_found = self.resolve_type_vars_if_possible(exp_found);
if exp_found.references_error() {
return None;
}
Some(self.cmp(exp_found.expected, exp_found.found))
}
/// Returns a string of the form "expected `{}`, found `{}`".
fn expected_found_str<T: fmt::Display + TypeFoldable<'tcx>>(
&self,
exp_found: &ty::error::ExpectedFound<T>)
-> Option<(String, String)>
-> Option<(DiagnosticStyledString, DiagnosticStyledString)>
{
let exp_found = self.resolve_type_vars_if_possible(exp_found);
if exp_found.references_error() {
return None;
}
Some((format!("{}", exp_found.expected), format!("{}", exp_found.found)))
Some((DiagnosticStyledString::highlighted(format!("{}", exp_found.expected)),
DiagnosticStyledString::highlighted(format!("{}", exp_found.found))))
}
fn report_generic_bound_failure(&self,

View File

@ -20,6 +20,8 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
match *origin {
infer::Subtype(ref trace) => {
if let Some((expected, found)) = self.values_str(&trace.values) {
let expected = expected.content();
let found = found.content();
// FIXME: do we want a "the" here?
err.span_note(trace.cause.span,
&format!("...so that {} (expected {}, found {})",

View File

@ -2241,7 +2241,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
/// `DefId` is really just an interned def-path).
///
/// Note that if `id` is not local to this crate, the result will
// be a non-local `DefPath`.
/// be a non-local `DefPath`.
pub fn def_path(self, id: DefId) -> hir_map::DefPath {
if id.is_local() {
self.hir.def_path(id)

View File

@ -35,6 +35,46 @@ pub struct SubDiagnostic {
pub render_span: Option<RenderSpan>,
}
#[derive(PartialEq, Eq)]
pub struct DiagnosticStyledString(pub Vec<StringPart>);
impl DiagnosticStyledString {
pub fn new() -> DiagnosticStyledString {
DiagnosticStyledString(vec![])
}
pub fn push_normal<S: Into<String>>(&mut self, t: S) {
self.0.push(StringPart::Normal(t.into()));
}
pub fn push_highlighted<S: Into<String>>(&mut self, t: S) {
self.0.push(StringPart::Highlighted(t.into()));
}
pub fn normal<S: Into<String>>(t: S) -> DiagnosticStyledString {
DiagnosticStyledString(vec![StringPart::Normal(t.into())])
}
pub fn highlighted<S: Into<String>>(t: S) -> DiagnosticStyledString {
DiagnosticStyledString(vec![StringPart::Highlighted(t.into())])
}
pub fn content(&self) -> String {
self.0.iter().map(|x| x.content()).collect::<String>()
}
}
#[derive(PartialEq, Eq)]
pub enum StringPart {
Normal(String),
Highlighted(String),
}
impl StringPart {
pub fn content(&self) -> String {
match self {
&StringPart::Normal(ref s) | & StringPart::Highlighted(ref s) => s.to_owned()
}
}
}
impl Diagnostic {
pub fn new(level: Level, message: &str) -> Self {
Diagnostic::new_with_code(level, None, message)
@ -81,8 +121,8 @@ impl Diagnostic {
pub fn note_expected_found(&mut self,
label: &fmt::Display,
expected: &fmt::Display,
found: &fmt::Display)
expected: DiagnosticStyledString,
found: DiagnosticStyledString)
-> &mut Self
{
self.note_expected_found_extra(label, expected, found, &"", &"")
@ -90,21 +130,29 @@ impl Diagnostic {
pub fn note_expected_found_extra(&mut self,
label: &fmt::Display,
expected: &fmt::Display,
found: &fmt::Display,
expected: DiagnosticStyledString,
found: DiagnosticStyledString,
expected_extra: &fmt::Display,
found_extra: &fmt::Display)
-> &mut Self
{
let mut msg: Vec<_> = vec![(format!("expected {} `", label), Style::NoStyle)];
msg.extend(expected.0.iter()
.map(|x| match *x {
StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle),
StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight),
}));
msg.push((format!("`{}\n", expected_extra), Style::NoStyle));
msg.push((format!(" found {} `", label), Style::NoStyle));
msg.extend(found.0.iter()
.map(|x| match *x {
StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle),
StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight),
}));
msg.push((format!("`{}", found_extra), Style::NoStyle));
// For now, just attach these as notes
self.highlighted_note(vec![
(format!("expected {} `", label), Style::NoStyle),
(format!("{}", expected), Style::Highlight),
(format!("`{}\n", expected_extra), Style::NoStyle),
(format!(" found {} `", label), Style::NoStyle),
(format!("{}", found), Style::Highlight),
(format!("`{}", found_extra), Style::NoStyle),
]);
self.highlighted_note(msg);
self
}

View File

@ -9,6 +9,8 @@
// except according to those terms.
use Diagnostic;
use DiagnosticStyledString;
use Level;
use Handler;
use std::fmt::{self, Debug};
@ -115,14 +117,14 @@ impl<'a> DiagnosticBuilder<'a> {
forward!(pub fn note_expected_found(&mut self,
label: &fmt::Display,
expected: &fmt::Display,
found: &fmt::Display)
expected: DiagnosticStyledString,
found: DiagnosticStyledString)
-> &mut Self);
forward!(pub fn note_expected_found_extra(&mut self,
label: &fmt::Display,
expected: &fmt::Display,
found: &fmt::Display,
expected: DiagnosticStyledString,
found: DiagnosticStyledString,
expected_extra: &fmt::Display,
found_extra: &fmt::Display)
-> &mut Self);

View File

@ -203,7 +203,7 @@ impl error::Error for ExplicitBug {
}
}
pub use diagnostic::{Diagnostic, SubDiagnostic};
pub use diagnostic::{Diagnostic, SubDiagnostic, DiagnosticStyledString, StringPart};
pub use diagnostic_builder::DiagnosticBuilder;
/// A handler deals with errors; certain errors

View File

@ -4,8 +4,8 @@ error[E0308]: mismatched types
16 | x.push(y);
| ^ lifetime mismatch
|
= note: expected type `Ref<'a, i32>`
found type `Ref<'_, i32>`
= note: expected type `Ref<'a, _>`
found type `Ref<'_, _>`
note: the anonymous lifetime #2 defined on the body at 15:51...
--> $DIR/ex2a-push-one-existing-name.rs:15:52
|

View File

@ -4,8 +4,8 @@ error[E0308]: mismatched types
16 | x.push(y);
| ^ lifetime mismatch
|
= note: expected type `Ref<'_, i32>`
found type `Ref<'_, i32>`
= note: expected type `Ref<'_, _>`
found type `Ref<'_, _>`
note: the anonymous lifetime #3 defined on the body at 15:43...
--> $DIR/ex2b-push-no-existing-names.rs:15:44
|

View File

@ -27,7 +27,7 @@ note: but, the lifetime must be valid for the lifetime 'b as defined on the body
17 | | x.push(z);
18 | | }
| |_^ ...ending here
note: ...so that expression is assignable (expected Ref<'b, i32>, found Ref<'_, i32>)
note: ...so that expression is assignable (expected Ref<'b, _>, found Ref<'_, _>)
--> $DIR/ex2c-push-inference-variable.rs:17:12
|
17 | x.push(z);

View File

@ -0,0 +1,61 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
enum Bar {
Qux,
Zar,
}
struct Foo {
bar: usize,
}
struct X<T1, T2> {
x: T1,
y: T2,
}
fn a() -> Foo {
Some(Foo { bar: 1 })
}
fn a2() -> Foo {
Ok(Foo { bar: 1})
}
fn b() -> Option<Foo> {
Foo { bar: 1 }
}
fn c() -> Result<Foo, Bar> {
Foo { bar: 1 }
}
fn d() -> X<X<String, String>, String> {
X {
x: X {
x: "".to_string(),
y: 2,
},
y: 3,
}
}
fn e() -> X<X<String, String>, String> {
X {
x: X {
x: "".to_string(),
y: 2,
},
y: "".to_string(),
}
}
fn main() {}

View File

@ -0,0 +1,70 @@
error[E0308]: mismatched types
--> $DIR/abridged.rs:26:5
|
26 | Some(Foo { bar: 1 })
| ^^^^^^^^^^^^^^^^^^^^ expected struct `Foo`, found enum `std::option::Option`
|
= note: expected type `Foo`
found type `std::option::Option<Foo>`
error[E0308]: mismatched types
--> $DIR/abridged.rs:30:5
|
30 | Ok(Foo { bar: 1})
| ^^^^^^^^^^^^^^^^^ expected struct `Foo`, found enum `std::result::Result`
|
= note: expected type `Foo`
found type `std::result::Result<Foo, _>`
error[E0308]: mismatched types
--> $DIR/abridged.rs:34:5
|
34 | Foo { bar: 1 }
| ^^^^^^^^^^^^^^ expected enum `std::option::Option`, found struct `Foo`
|
= note: expected type `std::option::Option<Foo>`
found type `Foo`
error[E0308]: mismatched types
--> $DIR/abridged.rs:38:5
|
38 | Foo { bar: 1 }
| ^^^^^^^^^^^^^^ expected enum `std::result::Result`, found struct `Foo`
|
= note: expected type `std::result::Result<Foo, Bar>`
found type `Foo`
error[E0308]: mismatched types
--> $DIR/abridged.rs:42:5
|
42 | X {
| _____^ starting here...
43 | | x: X {
44 | | x: "".to_string(),
45 | | y: 2,
46 | | },
47 | | y: 3,
48 | | }
| |_____^ ...ending here: expected struct `std::string::String`, found integral variable
|
= note: expected type `X<X<_, std::string::String>, std::string::String>`
found type `X<X<_, {integer}>, {integer}>`
error[E0308]: mismatched types
--> $DIR/abridged.rs:52:5
|
52 | X {
| _____^ starting here...
53 | | x: X {
54 | | x: "".to_string(),
55 | | y: 2,
56 | | },
57 | | y: "".to_string(),
58 | | }
| |_____^ ...ending here: expected struct `std::string::String`, found integral variable
|
= note: expected type `X<X<_, std::string::String>, _>`
found type `X<X<_, {integer}>, _>`
error: aborting due to 6 previous errors