提交 6eff103a 编写于 作者: B bors

Auto merge of #46461 - zackmdavis:elemental_method_suggestion_jamboree, r=estebank

 type error method suggestions use whitelisted identity-like conversions

![method_jamboree_summit](https://user-images.githubusercontent.com/1076988/33523646-e5c43184-d7c0-11e7-98e5-1bff426ade86.png)

Previously, on a type mismatch (and if this wasn't preëmpted by a
higher-priority suggestion), we would look for argumentless methods
returning the expected type, and list them in a `help` note. This had two
major shortcomings: firstly, a lot of the suggestions didn't really make
sense (if you used a &str where a String was expected,
`.to_ascii_uppercase()` is probably not the solution you were hoping
for). Secondly, we weren't generating suggestions from the most useful
traits! We address the first problem with an internal
`#[rustc_conversion_suggestion]` attribute meant to mark methods that keep
the "same value" in the relevant sense, just converting the type. We
address the second problem by making `FnCtxt.probe_for_return_type` pass
the `ProbeScope::AllTraits` to `probe_op`: this would seem to be safe
because grep reveals no other callers of `probe_for_return_type`.

Also, structured suggestions are pretty and good for RLS and friends.

Unfortunately, the trait probing is still not all one would hope for: at a
minimum, we don't know how to rule out `into()` in cases where it wouldn't
actually work, and we don't know how to rule in `.to_owned()` where it
would. Issues #46459 and #46460 have been filed and are ref'd in a FIXME.

This is hoped to resolve #42929, #44672, and #45777.
...@@ -1624,6 +1624,7 @@ pub fn swap_with_slice(&mut self, other: &mut [T]) { ...@@ -1624,6 +1624,7 @@ pub fn swap_with_slice(&mut self, other: &mut [T]) {
/// let x = s.to_vec(); /// let x = s.to_vec();
/// // Here, `s` and `x` can be modified independently. /// // Here, `s` and `x` can be modified independently.
/// ``` /// ```
#[rustc_conversion_suggestion]
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
#[inline] #[inline]
pub fn to_vec(&self) -> Vec<T> pub fn to_vec(&self) -> Vec<T>
......
...@@ -2034,6 +2034,7 @@ pub trait ToString { ...@@ -2034,6 +2034,7 @@ pub trait ToString {
/// ///
/// assert_eq!(five, i.to_string()); /// assert_eq!(five, i.to_string());
/// ``` /// ```
#[rustc_conversion_suggestion]
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
fn to_string(&self) -> String; fn to_string(&self) -> String;
} }
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
use super::method::MethodCallee; use super::method::MethodCallee;
use rustc::infer::InferOk; use rustc::infer::InferOk;
use rustc::session::DiagnosticMessageId;
use rustc::traits; use rustc::traits;
use rustc::ty::{self, Ty, TraitRef}; use rustc::ty::{self, Ty, TraitRef};
use rustc::ty::{ToPredicate, TypeFoldable}; use rustc::ty::{ToPredicate, TypeFoldable};
...@@ -56,19 +57,25 @@ fn next(&mut self) -> Option<Self::Item> { ...@@ -56,19 +57,25 @@ fn next(&mut self) -> Option<Self::Item> {
return Some((self.cur_ty, 0)); return Some((self.cur_ty, 0));
} }
if self.steps.len() == tcx.sess.recursion_limit.get() { if self.steps.len() >= tcx.sess.recursion_limit.get() {
// We've reached the recursion limit, error gracefully. // We've reached the recursion limit, error gracefully.
let suggested_limit = tcx.sess.recursion_limit.get() * 2; let suggested_limit = tcx.sess.recursion_limit.get() * 2;
struct_span_err!(tcx.sess, let msg = format!("reached the recursion limit while auto-dereferencing {:?}",
self.span, self.cur_ty);
E0055, let error_id = (DiagnosticMessageId::ErrorId(55), Some(self.span), msg.clone());
"reached the recursion limit while auto-dereferencing {:?}", let fresh = tcx.sess.one_time_diagnostics.borrow_mut().insert(error_id);
self.cur_ty) if fresh {
.span_label(self.span, "deref recursion limit reached") struct_span_err!(tcx.sess,
.help(&format!( self.span,
"consider adding a `#[recursion_limit=\"{}\"]` attribute to your crate", E0055,
"reached the recursion limit while auto-dereferencing {:?}",
self.cur_ty)
.span_label(self.span, "deref recursion limit reached")
.help(&format!(
"consider adding a `#![recursion_limit=\"{}\"]` attribute to your crate",
suggested_limit)) suggested_limit))
.emit(); .emit();
}
return None; return None;
} }
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
use std::iter;
use check::FnCtxt; use check::FnCtxt;
use rustc::infer::InferOk; use rustc::infer::InferOk;
...@@ -137,49 +138,45 @@ pub fn demand_coerce_diag(&self, ...@@ -137,49 +138,45 @@ pub fn demand_coerce_diag(&self,
if let Some((msg, suggestion)) = self.check_ref(expr, checked_ty, expected) { if let Some((msg, suggestion)) = self.check_ref(expr, checked_ty, expected) {
err.span_suggestion(expr.span, msg, suggestion); err.span_suggestion(expr.span, msg, suggestion);
} else { } else {
let mode = probe::Mode::MethodCall; let methods = self.get_conversion_methods(expected, checked_ty);
let suggestions = self.probe_for_return_type(syntax_pos::DUMMY_SP, if let Ok(expr_text) = self.tcx.sess.codemap().span_to_snippet(expr.span) {
mode, let suggestions = iter::repeat(expr_text).zip(methods.iter())
expected, .map(|(receiver, method)| format!("{}.{}()", receiver, method.name))
checked_ty, .collect::<Vec<_>>();
ast::DUMMY_NODE_ID); if !suggestions.is_empty() {
if suggestions.len() > 0 { err.span_suggestions(expr.span,
err.help(&format!("here are some functions which \ "try using a conversion method",
might fulfill your needs:\n{}", suggestions);
self.get_best_match(&suggestions).join("\n"))); }
} }
} }
(expected, Some(err)) (expected, Some(err))
} }
fn format_method_suggestion(&self, method: &AssociatedItem) -> String { fn get_conversion_methods(&self, expected: Ty<'tcx>, checked_ty: Ty<'tcx>)
format!("- .{}({})", -> Vec<AssociatedItem> {
method.name, let mut methods = self.probe_for_return_type(syntax_pos::DUMMY_SP,
if self.has_no_input_arg(method) { probe::Mode::MethodCall,
"" expected,
} else { checked_ty,
"..." ast::DUMMY_NODE_ID);
}) methods.retain(|m| {
} self.has_no_input_arg(m) &&
self.tcx.get_attrs(m.def_id).iter()
fn display_suggested_methods(&self, methods: &[AssociatedItem]) -> Vec<String> { // This special internal attribute is used to whitelist
methods.iter() // "identity-like" conversion methods to be suggested here.
.take(5) //
.map(|method| self.format_method_suggestion(&*method)) // FIXME (#46459 and #46460): ideally
.collect::<Vec<String>>() // `std::convert::Into::into` and `std::borrow:ToOwned` would
} // also be `#[rustc_conversion_suggestion]`, if not for
// method-probing false-positives and -negatives (respectively).
//
// FIXME? Other potential candidate methods: `as_ref` and
// `as_mut`?
.find(|a| a.check_name("rustc_conversion_suggestion")).is_some()
});
fn get_best_match(&self, methods: &[AssociatedItem]) -> Vec<String> { methods
let no_argument_methods: Vec<_> =
methods.iter()
.filter(|ref x| self.has_no_input_arg(&*x))
.map(|x| x.clone())
.collect();
if no_argument_methods.len() > 0 {
self.display_suggested_methods(&no_argument_methods)
} else {
self.display_suggested_methods(&methods)
}
} }
// This function checks if the method isn't static and takes other arguments than `self`. // This function checks if the method isn't static and takes other arguments than `self`.
......
...@@ -190,7 +190,7 @@ pub fn probe_for_return_type(&self, ...@@ -190,7 +190,7 @@ pub fn probe_for_return_type(&self,
scope_expr_id); scope_expr_id);
let method_names = let method_names =
self.probe_op(span, mode, None, Some(return_type), IsSuggestion(true), self.probe_op(span, mode, None, Some(return_type), IsSuggestion(true),
self_ty, scope_expr_id, ProbeScope::TraitsInScope, self_ty, scope_expr_id, ProbeScope::AllTraits,
|probe_cx| Ok(probe_cx.candidate_method_names())) |probe_cx| Ok(probe_cx.candidate_method_names()))
.unwrap_or(vec![]); .unwrap_or(vec![]);
method_names method_names
...@@ -199,7 +199,7 @@ pub fn probe_for_return_type(&self, ...@@ -199,7 +199,7 @@ pub fn probe_for_return_type(&self,
self.probe_op( self.probe_op(
span, mode, Some(method_name), Some(return_type), span, mode, Some(method_name), Some(return_type),
IsSuggestion(true), self_ty, scope_expr_id, IsSuggestion(true), self_ty, scope_expr_id,
ProbeScope::TraitsInScope, |probe_cx| probe_cx.pick() ProbeScope::AllTraits, |probe_cx| probe_cx.pick()
).ok().map(|pick| pick.item) ).ok().map(|pick| pick.item)
}) })
.collect() .collect()
......
...@@ -229,7 +229,7 @@ ...@@ -229,7 +229,7 @@
// Turn warnings into errors, but only after stage0, where it can be useful for // Turn warnings into errors, but only after stage0, where it can be useful for
// code to emit warnings during language transitions // code to emit warnings during language transitions
#![deny(warnings)] #![cfg_attr(not(stage0), deny(warnings))]
// std may use features in a platform-specific way // std may use features in a platform-specific way
#![allow(unused_features)] #![allow(unused_features)]
......
...@@ -1709,6 +1709,7 @@ pub fn to_string_lossy(&self) -> Cow<str> { ...@@ -1709,6 +1709,7 @@ pub fn to_string_lossy(&self) -> Cow<str> {
/// let path_buf = Path::new("foo.txt").to_path_buf(); /// let path_buf = Path::new("foo.txt").to_path_buf();
/// assert_eq!(path_buf, std::path::PathBuf::from("foo.txt")); /// assert_eq!(path_buf, std::path::PathBuf::from("foo.txt"));
/// ``` /// ```
#[rustc_conversion_suggestion]
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
pub fn to_path_buf(&self) -> PathBuf { pub fn to_path_buf(&self) -> PathBuf {
PathBuf::from(self.inner.to_os_string()) PathBuf::from(self.inner.to_os_string())
......
...@@ -969,6 +969,13 @@ pub fn is_builtin_attr(attr: &ast::Attribute) -> bool { ...@@ -969,6 +969,13 @@ pub fn is_builtin_attr(attr: &ast::Attribute) -> bool {
never be stable", never be stable",
cfg_fn!(rustc_attrs))), cfg_fn!(rustc_attrs))),
// whitelists "identity-like" conversion methods to suggest on type mismatch
("rustc_conversion_suggestion", Whitelisted, Gated(Stability::Unstable,
"rustc_attrs",
"this is an internal attribute that will \
never be stable",
cfg_fn!(rustc_attrs))),
("wasm_import_memory", Whitelisted, Gated(Stability::Unstable, ("wasm_import_memory", Whitelisted, Gated(Stability::Unstable,
"wasm_import_memory", "wasm_import_memory",
"wasm_import_memory attribute is currently unstable", "wasm_import_memory attribute is currently unstable",
......
...@@ -2,16 +2,13 @@ error[E0308]: mismatched types ...@@ -2,16 +2,13 @@ error[E0308]: mismatched types
--> $DIR/deref-suggestion.rs:18:9 --> $DIR/deref-suggestion.rs:18:9
| |
18 | foo(s); //~ ERROR mismatched types 18 | foo(s); //~ ERROR mismatched types
| ^ expected struct `std::string::String`, found reference | ^
| |
| expected struct `std::string::String`, found reference
| help: try using a conversion method: `s.to_string()`
| |
= note: expected type `std::string::String` = note: expected type `std::string::String`
found type `&std::string::String` found type `&std::string::String`
= help: here are some functions which might fulfill your needs:
- .escape_debug()
- .escape_default()
- .escape_unicode()
- .to_ascii_lowercase()
- .to_ascii_uppercase()
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/deref-suggestion.rs:23:10 --> $DIR/deref-suggestion.rs:23:10
......
...@@ -4,11 +4,11 @@ error[E0055]: reached the recursion limit while auto-dereferencing I ...@@ -4,11 +4,11 @@ error[E0055]: reached the recursion limit while auto-dereferencing I
62 | let x: &Bottom = &t; //~ ERROR mismatched types 62 | let x: &Bottom = &t; //~ ERROR mismatched types
| ^^ deref recursion limit reached | ^^ deref recursion limit reached
| |
= help: consider adding a `#[recursion_limit="20"]` attribute to your crate = help: consider adding a `#![recursion_limit="20"]` attribute to your crate
error[E0055]: reached the recursion limit while auto-dereferencing I error[E0055]: reached the recursion limit while auto-dereferencing I
| |
= help: consider adding a `#[recursion_limit="20"]` attribute to your crate = help: consider adding a `#![recursion_limit="20"]` attribute to your crate
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/recursion_limit_deref.rs:62:22 --> $DIR/recursion_limit_deref.rs:62:22
......
...@@ -6,9 +6,6 @@ error[E0308]: mismatched types ...@@ -6,9 +6,6 @@ error[E0308]: mismatched types
| |
= note: expected type `usize` = note: expected type `usize`
found type `std::string::String` found type `std::string::String`
= help: here are some functions which might fulfill your needs:
- .capacity()
- .len()
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/coerce-suggestions.rs:19:19 --> $DIR/coerce-suggestions.rs:19:19
...@@ -44,7 +41,10 @@ error[E0308]: mismatched types ...@@ -44,7 +41,10 @@ error[E0308]: mismatched types
--> $DIR/coerce-suggestions.rs:27:9 --> $DIR/coerce-suggestions.rs:27:9
| |
27 | f = box f; 27 | f = box f;
| ^^^^^ cyclic type of infinite size | ^^^^^
| |
| cyclic type of infinite size
| help: try using a conversion method: `box f.to_string()`
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/coerce-suggestions.rs:31:9 --> $DIR/coerce-suggestions.rs:31:9
......
...@@ -33,8 +33,6 @@ error[E0308]: mismatched types ...@@ -33,8 +33,6 @@ error[E0308]: mismatched types
| |
= note: expected type `usize` = note: expected type `usize`
found type `&'static str` found type `&'static str`
= help: here are some functions which might fulfill your needs:
- .len()
error[E0061]: this function takes 2 parameters but 3 parameters were supplied error[E0061]: this function takes 2 parameters but 3 parameters were supplied
--> $DIR/issue-34264.rs:20:5 --> $DIR/issue-34264.rs:20:5
......
// 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.
use std::path::{Path, PathBuf};
fn main() {
let _tis_an_instants_play: String = "'Tis a fond Ambush—"; //~ ERROR mismatched types
let _just_to_make_bliss: PathBuf = Path::new("/ern/her/own/surprise");
//~^ ERROR mismatched types
let _but_should_the_play: String = 2; // Perhaps surprisingly, we suggest .to_string() here
//~^ ERROR mismatched types
let _prove_piercing_earnest: Vec<usize> = &[1, 2, 3]; //~ ERROR mismatched types
}
error[E0308]: mismatched types
--> $DIR/conversion-methods.rs:15:41
|
15 | let _tis_an_instants_play: String = "'Tis a fond Ambush—"; //~ ERROR mismatched types
| ^^^^^^^^^^^^^^^^^^^^^
| |
| expected struct `std::string::String`, found reference
| help: try using a conversion method: `"'Tis a fond Ambush—".to_string()`
|
= note: expected type `std::string::String`
found type `&'static str`
error[E0308]: mismatched types
--> $DIR/conversion-methods.rs:16:40
|
16 | let _just_to_make_bliss: PathBuf = Path::new("/ern/her/own/surprise");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| expected struct `std::path::PathBuf`, found reference
| help: try using a conversion method: `Path::new("/ern/her/own/surprise").to_path_buf()`
|
= note: expected type `std::path::PathBuf`
found type `&std::path::Path`
error[E0308]: mismatched types
--> $DIR/conversion-methods.rs:19:40
|
19 | let _but_should_the_play: String = 2; // Perhaps surprisingly, we suggest .to_string() here
| ^
| |
| expected struct `std::string::String`, found integral variable
| help: try using a conversion method: `2.to_string()`
|
= note: expected type `std::string::String`
found type `{integer}`
error[E0308]: mismatched types
--> $DIR/conversion-methods.rs:22:47
|
22 | let _prove_piercing_earnest: Vec<usize> = &[1, 2, 3]; //~ ERROR mismatched types
| ^^^^^^^^^^
| |
| expected struct `std::vec::Vec`, found reference
| help: try using a conversion method: `&[1, 2, 3].to_vec()`
|
= note: expected type `std::vec::Vec<usize>`
found type `&[{integer}; 3]`
error: aborting due to 4 previous errors
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册