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

More helpful note message when confusing a field with a method #26305

Merged
merged 7 commits into from
Jun 20, 2015
61 changes: 55 additions & 6 deletions src/librustc_typeck/check/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ use CrateCtxt;

use astconv::AstConv;
use check::{self, FnCtxt};
use middle::ty::{self, Ty};
use middle::ty::{self, Ty, ToPolyTraitRef, AsPredicate};
use middle::def;
use middle::lang_items::FnOnceTraitLangItem;
use middle::subst::Substs;
use middle::traits::{Obligation, SelectionContext};
use metadata::{csearch, cstore, decoder};

use syntax::{ast, ast_util};
Expand Down Expand Up @@ -59,12 +62,58 @@ pub fn report_error<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
None);

// If the item has the name of a field, give a help note
if let (&ty::TyStruct(did, _), Some(_)) = (&rcvr_ty.sty, rcvr_expr) {
if let (&ty::TyStruct(did, substs), Some(expr)) = (&rcvr_ty.sty, rcvr_expr) {
let fields = ty::lookup_struct_fields(cx, did);
if fields.iter().any(|f| f.name == item_name) {
cx.sess.span_note(span,
&format!("use `(s.{0})(...)` if you meant to call the \
function stored in the `{0}` field", item_name));

if let Some(field) = fields.iter().find(|f| f.name == item_name) {
let expr_string = match cx.sess.codemap().span_to_snippet(expr.span) {
Ok(expr_string) => expr_string,
_ => "s".into() // Default to a generic placeholder for the
// expression when we can't generate a string
// snippet
};

let span_stored_function = || {
cx.sess.span_note(span,
&format!("use `({0}.{1})(...)` if you meant to call \
the function stored in the `{1}` field",
expr_string, item_name));
};

let span_did_you_mean = || {
cx.sess.span_note(span, &format!("did you mean to write `{0}.{1}`?",
expr_string, item_name));
};

// Determine if the field can be used as a function in some way
let field_ty = ty::lookup_field_type(cx, did, field.id, substs);
if let Ok(fn_once_trait_did) = cx.lang_items.require(FnOnceTraitLangItem) {
let infcx = fcx.infcx();
infcx.probe(|_| {
let fn_once_substs = Substs::new_trait(vec![infcx.next_ty_var()],
Vec::new(),
field_ty);
let trait_ref = ty::TraitRef::new(fn_once_trait_did,
cx.mk_substs(fn_once_substs));
let poly_trait_ref = trait_ref.to_poly_trait_ref();
let obligation = Obligation::misc(span,
fcx.body_id,
poly_trait_ref.as_predicate());
let mut selcx = SelectionContext::new(infcx, fcx);

if selcx.evaluate_obligation(&obligation) {
span_stored_function();
} else {
span_did_you_mean();
}
});
} else {
match field_ty.sty {
// fallback to matching a closure or function pointer
ty::TyClosure(..) | ty::TyBareFn(..) => span_stored_function(),
_ => span_did_you_mean(),
}
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/test/compile-fail/issue-18343.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ struct Obj<F> where F: FnMut() -> u32 {
fn main() {
let o = Obj { closure: || 42 };
o.closure(); //~ ERROR no method named `closure` found
//~^ NOTE use `(s.closure)(...)` if you meant to call the function stored in the `closure` field
//~^ NOTE use `(o.closure)(...)` if you meant to call the function stored in the `closure` field
}
68 changes: 68 additions & 0 deletions src/test/compile-fail/issue-2392.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2014 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.

#![feature(core)]
use std::boxed::FnBox;

struct Obj<F> where F: FnOnce() -> u32 {
closure: F,
not_closure: usize,
}

struct BoxedObj {
boxed_closure: Box<FnBox() -> u32>,
}

struct Wrapper<F> where F: FnMut() -> u32 {
wrap: Obj<F>,
}

fn func() -> u32 {
0
}

fn check_expression() -> Obj<Box<FnBox() -> u32>> {
Obj { closure: Box::new(|| 42_u32) as Box<FnBox() -> u32>, not_closure: 42 }
}

fn main() {
// test variations of function

let o_closure = Obj { closure: || 42, not_closure: 42 };
o_closure.closure(); //~ ERROR no method named `closure` found
//~^ NOTE use `(o_closure.closure)(...)` if you meant to call the function stored

o_closure.not_closure(); //~ ERROR no method named `not_closure` found
//~^ NOTE did you mean to write `o_closure.not_closure`?

let o_func = Obj { closure: func, not_closure: 5 };
o_func.closure(); //~ ERROR no method named `closure` found
//~^ NOTE use `(o_func.closure)(...)` if you meant to call the function stored

let boxed_fn = BoxedObj { boxed_closure: Box::new(func) };
boxed_fn.boxed_closure();//~ ERROR no method named `boxed_closure` found
//~^ NOTE use `(boxed_fn.boxed_closure)(...)` if you meant to call the function stored

let boxed_closure = BoxedObj { boxed_closure: Box::new(|| 42_u32) as Box<FnBox() -> u32> };
boxed_closure.boxed_closure();//~ ERROR no method named `boxed_closure` found
//~^ NOTE use `(boxed_closure.boxed_closure)(...)` if you meant to call the function stored

// test expression writing in the notes

let w = Wrapper { wrap: o_func };
w.wrap.closure();//~ ERROR no method named `closure` found
//~^ NOTE use `(w.wrap.closure)(...)` if you meant to call the function stored

w.wrap.not_closure();//~ ERROR no method named `not_closure` found
//~^ NOTE did you mean to write `w.wrap.not_closure`?

check_expression().closure();//~ ERROR no method named `closure` found
//~^ NOTE use `(check_expression().closure)(...)` if you meant to call the function stored
}