Skip to content

Commit

Permalink
Suggest appropriate path when calling associated item on bare types
Browse files Browse the repository at this point in the history
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.

This suggestion applies to both E0233 and E0599 and is only checked
when the first ident of a path corresponds to a primitive type.
  • Loading branch information
estebank committed Apr 18, 2019
1 parent d6f513e commit 94e5ec1
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 94e5ec1

Please sign in to comment.