Skip to content

Commit

Permalink
Rollup merge of rust-lang#60045 - estebank:suggest-std, r=petrochenkov
Browse files Browse the repository at this point in the history
Suggest appropriate path when calling associated item on bare types

When looking at the documentation for `std::f32` or `std::str`, for
example, it is easy to get confused and assume `std::f32` and `f32`
are the same thing. Because of this, it is not uncommon to attempt
writing `f32::consts::PI` instead of the correct
`std::f32::consts::PI`. When encountering the former, which results
in an access error due to it being an inexistent path, try to access
the same path under `std`. If this succeeds, this information is
stored for later tweaking of the final E0599 to provide an
appropriate suggestion.

Fix rust-lang#26760, fix rust-lang#46660.
  • Loading branch information
Centril authored Apr 19, 2019
2 parents 5e5bad3 + 94e5ec1 commit e343271
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 40 deletions.
5 changes: 5 additions & 0 deletions src/librustc/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ pub struct Session {

/// `Span`s of trait methods that weren't found to avoid emitting object safety errors
pub trait_methods_not_found: Lock<FxHashSet<Span>>,

/// Mapping from ident span to path span for paths that don't exist as written, but that
/// exist under `std`. For example, wrote `str::from_utf8` instead of `std::str::from_utf8`.
pub confused_type_with_std_module: Lock<FxHashMap<Span, Span>>,
}

pub struct PerfStats {
Expand Down Expand Up @@ -1248,6 +1252,7 @@ fn build_session_(
has_panic_handler: Once::new(),
driver_lint_caps,
trait_methods_not_found: Lock::new(Default::default()),
confused_type_with_std_module: Lock::new(Default::default()),
};

validate_commandline_args_with_session_available(&sess);
Expand Down
59 changes: 40 additions & 19 deletions src/librustc_resolve/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3273,6 +3273,25 @@ impl<'a> Resolver<'a> {
let traits = self.get_traits_containing_item(item_name, ns);
self.trait_map.insert(id, traits);
}

let mut std_path = vec![Segment::from_ident(Ident::from_str("std"))];
std_path.extend(path);
if self.primitive_type_table.primitive_types.contains_key(&path[0].ident.name) {
let cl = CrateLint::No;
let ns = Some(ns);
if let PathResult::Module(_) | PathResult::NonModule(_) =
self.resolve_path_without_parent_scope(&std_path, ns, false, span, cl)
{
// check if we wrote `str::from_utf8` instead of `std::str::from_utf8`
let item_span = path.iter().last().map(|segment| segment.ident.span)
.unwrap_or(span);
debug!("accessed item from `std` submodule as a bare type {:?}", std_path);
let mut hm = self.session.confused_type_with_std_module.borrow_mut();
hm.insert(item_span, span);
// In some places (E0223) we only have access to the full path
hm.insert(span, span);
}
}
resolution
}
_ => report_errors(self, None)
Expand Down Expand Up @@ -3387,16 +3406,17 @@ impl<'a> Resolver<'a> {
}

// Resolve in alternative namespaces if resolution in the primary namespace fails.
fn resolve_qpath_anywhere(&mut self,
id: NodeId,
qself: Option<&QSelf>,
path: &[Segment],
primary_ns: Namespace,
span: Span,
defer_to_typeck: bool,
global_by_default: bool,
crate_lint: CrateLint)
-> Option<PathResolution> {
fn resolve_qpath_anywhere(
&mut self,
id: NodeId,
qself: Option<&QSelf>,
path: &[Segment],
primary_ns: Namespace,
span: Span,
defer_to_typeck: bool,
global_by_default: bool,
crate_lint: CrateLint,
) -> Option<PathResolution> {
let mut fin_res = None;
// FIXME: can't resolve paths in macro namespace yet, macros are
// processed by the little special hack below.
Expand Down Expand Up @@ -3426,15 +3446,16 @@ impl<'a> Resolver<'a> {
}

/// Handles paths that may refer to associated items.
fn resolve_qpath(&mut self,
id: NodeId,
qself: Option<&QSelf>,
path: &[Segment],
ns: Namespace,
span: Span,
global_by_default: bool,
crate_lint: CrateLint)
-> Option<PathResolution> {
fn resolve_qpath(
&mut self,
id: NodeId,
qself: Option<&QSelf>,
path: &[Segment],
ns: Namespace,
span: Span,
global_by_default: bool,
crate_lint: CrateLint,
) -> Option<PathResolution> {
debug!(
"resolve_qpath(id={:?}, qself={:?}, path={:?}, \
ns={:?}, span={:?}, global_by_default={:?})",
Expand Down
57 changes: 38 additions & 19 deletions src/librustc_typeck/astconv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1187,18 +1187,33 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
ty
}

fn report_ambiguous_associated_type(&self,
span: Span,
type_str: &str,
trait_str: &str,
name: &str) {
struct_span_err!(self.tcx().sess, span, E0223, "ambiguous associated type")
.span_suggestion(
fn report_ambiguous_associated_type(
&self,
span: Span,
type_str: &str,
trait_str: &str,
name: &str,
) {
let mut err = struct_span_err!(self.tcx().sess, span, E0223, "ambiguous associated type");
if let (Some(_), Ok(snippet)) = (
self.tcx().sess.confused_type_with_std_module.borrow().get(&span),
self.tcx().sess.source_map().span_to_snippet(span),
) {
err.span_suggestion(
span,
"use fully-qualified syntax",
format!("<{} as {}>::{}", type_str, trait_str, name),
Applicability::HasPlaceholders
).emit();
"you are looking for the module in `std`, not the primitive type",
format!("std::{}", snippet),
Applicability::MachineApplicable,
);
} else {
err.span_suggestion(
span,
"use fully-qualified syntax",
format!("<{} as {}>::{}", type_str, trait_str, name),
Applicability::HasPlaceholders
);
}
err.emit();
}

// Search for a bound on a type parameter which includes the associated item
Expand Down Expand Up @@ -1391,10 +1406,12 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
err.emit();
} else if !qself_ty.references_error() {
// Don't print `TyErr` to the user.
self.report_ambiguous_associated_type(span,
&qself_ty.to_string(),
"Trait",
&assoc_ident.as_str());
self.report_ambiguous_associated_type(
span,
&qself_ty.to_string(),
"Trait",
&assoc_ident.as_str(),
);
}
return (tcx.types.err, Def::Err);
}
Expand Down Expand Up @@ -1461,10 +1478,12 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
ty
} else {
let path_str = tcx.def_path_str(trait_def_id);
self.report_ambiguous_associated_type(span,
"Type",
&path_str,
&item_segment.ident.as_str());
self.report_ambiguous_associated_type(
span,
"Type",
&path_str,
&item_segment.ident.as_str(),
);
return tcx.types.err;
};

Expand Down
18 changes: 16 additions & 2 deletions src/librustc_typeck/check/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,15 +292,29 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
return;
} else {
span = item_name.span;
struct_span_err!(
let mut err = struct_span_err!(
tcx.sess,
span,
E0599,
"no {} named `{}` found for type `{}` in the current scope",
item_kind,
item_name,
ty_str
)
);
if let Some(span) = tcx.sess.confused_type_with_std_module.borrow()
.get(&span)
{
if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(*span) {
err.span_suggestion(
*span,
"you are looking for the module in `std`, \
not the primitive type",
format!("std::{}", snippet),
Applicability::MachineApplicable,
);
}
}
err
}
} else {
tcx.sess.diagnostic().struct_dummy()
Expand Down
4 changes: 4 additions & 0 deletions src/test/ui/issues/issue-22933-3.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ error[E0599]: no associated item named `MIN` found for type `u8` in the current
|
LL | const FOO: [u32; u8::MIN as usize] = [];
| ^^^ associated item not found in `u8`
help: you are looking for the module in `std`, not the primitive type
|
LL | const FOO: [u32; std::u8::MIN as usize] = [];
| ^^^^^^^^^^^^

error: aborting due to previous error

Expand Down
7 changes: 7 additions & 0 deletions src/test/ui/suggestions/suggest-std-when-using-type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() {
let pi = f32::consts::PI; //~ ERROR ambiguous associated type
let bytes = "hello world".as_bytes();
let string = unsafe {
str::from_utf8(bytes) //~ ERROR no function or associated item named `from_utf8` found
};
}
24 changes: 24 additions & 0 deletions src/test/ui/suggestions/suggest-std-when-using-type.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
error[E0223]: ambiguous associated type
--> $DIR/suggest-std-when-using-type.rs:2:14
|
LL | let pi = f32::consts::PI;
| ^^^^^^^^^^^^^^^
help: you are looking for the module in `std`, not the primitive type
|
LL | let pi = std::f32::consts::PI;
| ^^^^^^^^^^^^^^^^^^^^

error[E0599]: no function or associated item named `from_utf8` found for type `str` in the current scope
--> $DIR/suggest-std-when-using-type.rs:5:14
|
LL | str::from_utf8(bytes)
| ^^^^^^^^^ function or associated item not found in `str`
help: you are looking for the module in `std`, not the primitive type
|
LL | std::str::from_utf8(bytes)
| ^^^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

Some errors occurred: E0223, E0599.
For more information about an error, try `rustc --explain E0223`.

0 comments on commit e343271

Please sign in to comment.