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

[WIP] Simplify mismatched types by removing same subtypes #39906

Closed
wants to merge 1 commit into from
Closed
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
199 changes: 194 additions & 5 deletions src/librustc/infer/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,155 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
}
}

fn highlight_outer(&self,
Copy link
Contributor

Choose a reason for hiding this comment

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

meta-comment: I've been talking to @cengizio about making this error_reporting section into a proper module, as I think it's starting to have a lot of code. It feels like this type-diffing stuff really wants to be its own module.

Copy link
Contributor

Choose a reason for hiding this comment

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

also, this fn could really use some doc comments explaining its interface.

value: &mut Vec<(String, bool)>,
other_value: &mut Vec<(String, bool)>,
name: String,
sub: &ty::subst::Substs<'tcx>,
pos: usize,
other_ty: &ty::Ty<'tcx>) {
value.push((name, true));
let len = sub.len();
if len > 0 {
value.push(("<".to_string(), true));
}

let sts = sub.regions();
for (i, st) in sts.enumerate() {
value.push((format!("{}", st), false));

if len > 0 && i != len - 1 {
value.push((format!(", "), false));
}
}

let sts = sub.types();
for (i, st) in sts.enumerate() {
if i == pos {
let (v, o_v) = self.cmp(st, other_ty);
value.extend(v);
other_value.extend(o_v);
} else {
value.push((format!("{}", st), true));
}

if len > 0 && i != len - 1 {
value.push((format!(", "), true));
}
}
if len > 0 {
value.push((">".to_string(), true));
}
}


fn cmp(&self, t1: ty::Ty<'tcx>, t2: ty::Ty<'tcx>)
-> (Vec<(String, bool)>, Vec<(String, bool)>)
{
match (&t1.sty, &t2.sty) {
(&ty::TyAdt(def1, sub1), &ty::TyAdt(def2, sub2)) => {
let mut values: (Vec<(String, bool)>, Vec<(String, bool)>) = (vec![], vec![]);
Copy link
Contributor

Choose a reason for hiding this comment

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

does the bool here mean "highlighted"? seems worth a comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed. I'll be cleaning these up with a new enum with two variants.

let name1 = self.tcx.item_path_str(def1.did.clone());
let name2 = self.tcx.item_path_str(def2.did.clone());
if name1 == name2 {
Copy link
Contributor

Choose a reason for hiding this comment

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

seems strange to compare the string, rather than just comparing def1.did == def2.did

Copy link
Contributor

Choose a reason for hiding this comment

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

I think some comments here would be nice, btw... i.e., // X<...> vs X<...>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, there's a dearth of documentation in this code so far. I believe at the time I was writing this I wasn't sure if dids for the same definition with different subtypes were the same (while intuitively would be) and I already had a need for the name string for presentation, so I did it like that.

Copy link
Contributor

Choose a reason for hiding this comment

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

when you say "subtype", are you referring to the X in Option<X>? If so, subtype isn't quite the right term (that usually is used to mean e.g. the relationship between String and Object in Java). I would say "type argument", I guess, if I were referring to the X in some use of Option, and "type parameter" if I am referring to the definition of Option.

// Easy case, replace same types with `_` to shorten the output
// and highlight only the differing types.
values.0.push((name1.to_string(), false));
values.1.push((name2.to_string(), false));

let len = sub1.len();
if len > 0 {
values.0.push(("<".to_string(), false));
values.1.push(("<".to_string(), false));
}

let sts1 = sub1.regions();
let sts2 = sub2.regions();
let x = sts1.zip(sts2);
for (i, (st1, st2)) in x.enumerate() {
values.0.push((format!("{}", st1), st1 != st2));
values.1.push((format!("{}", st2), st1 != st2));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there any case where type mismatch errors need to point at the lifetimes?

Copy link
Contributor

Choose a reason for hiding this comment

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

I am working with @cengizio (slowly, slowly) on refactoring lifetime mismatch errors, but I thnk ideally they wil not go through this generic path, as they deserve a more focused explanation


if len > 0 && i != len - 1 {
values.0.push((format!(", "), false));
values.1.push((format!(", "), false));
}
}

let sts1 = sub1.types();
let sts2 = sub2.types();
let x = sts1.zip(sts2);
for (i, (st1, st2)) in x.enumerate() {
if st1 == st2 {
values.0.push(("_".to_string(), false));
values.1.push(("_".to_string(), false));
} else {
let (x1, x2) = self.cmp(st1, st2);
values.0.extend(x1);
values.1.extend(x2);
}
if len > 0 && i != len - 1 {
values.0.push((format!(", "), false));
values.1.push((format!(", "), false));
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: could we avoid pushing <, >, and the type name into values, and then use join or something to add these commas automatically?

Copy link
Contributor

Choose a reason for hiding this comment

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

I was thinking that maybe instead of building up strings, it'd be nice to build up a tree, and then have one routine that 'flattens' the tree into (string, bool) pairs at the end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like the suggestion. I'll look into it. Would that tree be used for anything else?

One thing that worries me about my hacky approach so far is that there're now two places where a type's presentation string is created, instead of one. If we create a presentation tree, then we can compare tree against tree easily, and the printing is always performed the same way. That way the only custom code for this would be the setting nodes to highlight. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

I was wondering about the repeated code as well. I love the idea of creating a "presentation tree" for a type, actually. It might also help with things like presenting the error message "local" to a particular module (i.e., I'd like to stop using absolute path strings if we can)

}
}
if len > 0 {
values.0.push((">".to_string(), false));
values.1.push((">".to_string(), false));
}
values
} else {
// Check for simple composition
Copy link
Contributor

Choose a reason for hiding this comment

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

what is "simple composition"? an example would be good

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The comments so far were meant for myself only, sorry for the lack of depth in them.

By simple composition I meant cases like Some(usize)/usize and String/Some(String).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The output for this can be seen on the second and third diagnostic errors of my last comment. I'd appreciate feedback on it to know wether I should aim at a different type of output. The third error is purposely weird, to get the worst possible cases likely to appear in the wild, but I foresee the second one to be more representative.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. The highlighting in those examples makes sense to me, I think it looks pretty good as is. It did take me a second to notice and interpret the second case (i.e., when one type starts out highlighted, seems a bit surprising, but it makes sense, ultimately).

for (i, st) in sub1.types().enumerate() {
if st == t2 {
self.highlight_outer(&mut values.0, &mut values.1, name1, sub1, i, &t2);
Copy link
Contributor

Choose a reason for hiding this comment

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

again, an example of code that takes this branch would be great, I have no idea what this is doing :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These two for loops compare one type against the subtype of the other, to catch cases like usize/Some(usize). It doesn't yet handle the case {integer}/Some(usize).

Copy link
Contributor

Choose a reason for hiding this comment

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

OK. I figured it was something like that. Main thing is that I think that in code like this it's super helpful to walk through various examples of types to show what each path is handling.

return values;
}
if let &ty::TyAdt(def, _) = &st.sty {
let name = self.tcx.item_path_str(def.did.clone());
if name == name2 {
self.highlight_outer(&mut values.0,
&mut values.1,
name1,
sub1,
i,
&t2);
return values;
}
}
}
for (i, st) in sub2.types().enumerate() {
Copy link
Contributor

Choose a reason for hiding this comment

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

these two for loops feel like they are begging to be extracted into a fn...no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, they are. :)

if st == t1 {
self.highlight_outer(&mut values.1, &mut values.0, name2, sub2, i, &t1);
return values;
}
if let &ty::TyAdt(def, _) = &st.sty {
let name = self.tcx.item_path_str(def.did.clone());
if name == name1 {
self.highlight_outer(&mut values.1,
&mut values.0,
name2,
sub2,
i,
&t1);
return values;
}
}
}

(vec![(format!("{}", t1), true)], vec![(format!("{}", t2), true)])
}
}
_ => {
if t1 == t2 {
(vec![("_".to_string(), false)], vec![("_".to_string(), false)])
} else {
(vec![(format!("{}", t1), true)], vec![(format!("{}", t2), true)])
}
}
}
}

pub fn note_type_err(&self,
diag: &mut DiagnosticBuilder<'tcx>,
cause: &ObligationCause<'tcx>,
Expand Down Expand Up @@ -665,26 +814,64 @@ 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)> {
/// Returns two `Vec`s representing portions of a type with a flag on wether it should
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: s/wether/whether/

/// be highlighted in the output.
///
/// For given types `X<String, usize>` and `X<usize, usize>`, this method would return
///
/// ```nocode
/// Some((vec![
/// ("X", false),
/// ("<", false),
/// ("String", true),
/// (",", false),
/// ("_", false),
/// (">", false)
/// ], vec![
/// ("X", false),
/// ("<", false),
/// ("usize", true),
/// (",", false),
/// ("_", false),
/// (">", false)
/// ]))
/// ```
fn values_str(&self, values: &ValuePairs<'tcx>)
-> Option<(Vec<(String, bool)>, Vec<(String, bool)>)>
{
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<(Vec<(String, bool)>, Vec<(String, bool)>)>
{
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))
}


fn expected_found_str<T: fmt::Display + TypeFoldable<'tcx>>(
&self,
exp_found: &ty::error::ExpectedFound<T>)
-> Option<(String, String)>
-> Option<(Vec<(String, bool)>, Vec<(String, bool)>)>
{
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((vec![(format!("{}", exp_found.expected), true)],
vec![(format!("{}", exp_found.found), true)]))
}

fn report_generic_bound_failure(&self,
Expand Down Expand Up @@ -1140,6 +1327,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.iter().map(|x| x.0.to_owned()).collect::<String>();
let found = found.iter().map(|x| x.0.to_owned()).collect::<String>();
// FIXME: do we want a "the" here?
err.span_note(
trace.cause.span,
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2259,7 +2259,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)
Expand Down
26 changes: 14 additions & 12 deletions src/librustc_errors/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,30 +81,32 @@ impl Diagnostic {

pub fn note_expected_found(&mut self,
label: &fmt::Display,
expected: &fmt::Display,
found: &fmt::Display)
expected: &[(String, bool)],
found: &[(String, bool)])
-> &mut Self
{
self.note_expected_found_extra(label, expected, found, &"", &"")
}

pub fn note_expected_found_extra(&mut self,
label: &fmt::Display,
expected: &fmt::Display,
found: &fmt::Display,
expected: &[(String, bool)],
found: &[(String, bool)],
expected_extra: &fmt::Display,
found_extra: &fmt::Display)
-> &mut Self
{
let mut msg: Vec<_> = vec![(format!("expected {} `", label), Style::NoStyle)];
msg.extend(expected.iter()
.map(|x| (x.0.to_owned(), if x.1 { Style::Highlight } else { Style::NoStyle })));
msg.push((format!("`{}\n", expected_extra), Style::NoStyle));
msg.push((format!(" found {} `", label), Style::NoStyle));
msg.extend(found.iter()
.map(|x| (x.0.to_owned(), if x.1 { Style::Highlight } else { Style::NoStyle })));
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
}

Expand Down
8 changes: 4 additions & 4 deletions src/librustc_errors/diagnostic_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,14 @@ impl<'a> DiagnosticBuilder<'a> {

forward!(pub fn note_expected_found(&mut self,
label: &fmt::Display,
expected: &fmt::Display,
found: &fmt::Display)
expected: &[(String, bool)],
found: &[(String, bool)])
-> &mut Self);

forward!(pub fn note_expected_found_extra(&mut self,
label: &fmt::Display,
expected: &fmt::Display,
found: &fmt::Display,
expected: &[(String, bool)],
found: &[(String, bool)],
expected_extra: &fmt::Display,
found_extra: &fmt::Display)
-> &mut Self);
Expand Down
4 changes: 3 additions & 1 deletion src/librustc_typeck/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1037,7 +1037,9 @@ fn evaluate_disr_expr(ccx: &CrateCtxt, repr_ty: attr::IntType, body: hir::BodyId
let ty_hint = repr_ty.to_ty(ccx.tcx);
let print_err = |cv: ConstVal| {
struct_span_err!(ccx.tcx.sess, e.span, E0079, "mismatched types")
.note_expected_found(&"type", &ty_hint, &format!("{}", cv.description()))
.note_expected_found(&"type",
&[(format!("{}", ty_hint), true)],
&[(format!("{}", cv.description()), true)])
.span_label(e.span, &format!("expected '{}' type", ty_hint))
.emit();
};
Expand Down
57 changes: 57 additions & 0 deletions src/test/ui/mismatched_types/abridged.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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 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() {}
Loading