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

Add future_prelude_collision lint #85707

Merged
merged 25 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
79388aa
Add future_prelude_collision lint
jam1garner May 26, 2021
01bdb8e
Disable `future_prelude_collision` for 2021 edition
jam1garner May 26, 2021
a9dc234
Add docs for FnCtxt::resolve_ufcs
jam1garner May 27, 2021
1626e19
Add support for associated functions to `future_prelude_collision` lint
jam1garner May 27, 2021
c341d5b
Improve documentation for `future_prelude_collision` lint
jam1garner May 27, 2021
35af383
Add UI tests for `future_prelude_collision` lint
jam1garner May 27, 2021
c41a157
Fix incorrect argument description on FnCtxt::resolve_ufcs
jam1garner May 27, 2021
327697a
Fix autoderef and autoref for `future_prelude_collision` lint
jam1garner May 27, 2021
eb5e0af
Add autoderef and autoref tests for `future_prelude_collision` lint
jam1garner May 27, 2021
93c60f2
Fix missing generic parameters from `future_prelude_collision` lint s…
jam1garner May 27, 2021
cb49992
Fix `future_prelude_collision` lint breaking for pointer mutabilty co…
jam1garner May 28, 2021
4a21a0b
Fix `future_prelude_collision` not maintaining type aliases
jam1garner May 28, 2021
64c61a3
Fix `future_prelude_collision` adding unneeded generic arguments
jam1garner May 28, 2021
32408cf
move test to rust-2021 directory
nikomatsakis Jun 4, 2021
19ba219
add inherent-method-collision test
nikomatsakis Jun 4, 2021
8d42f3d
don't warn for fully qual inherent methods
nikomatsakis Jun 14, 2021
17ab9c0
extract Rust 2021 prelude logic to its own module
nikomatsakis Jun 14, 2021
3efa5b4
Emit additional arguments in `future_prelude_collision` lint
jam1garner Jun 15, 2021
56108f6
Add future_prelude_collision to 2021 compat group
jam1garner Jun 15, 2021
dbc9da7
WIP: Find the imports that were used to reach a method
nikomatsakis Jun 17, 2021
9bee7f0
WIP: identify the case where we need to serialize path
nikomatsakis Jun 18, 2021
b18704d
Fix future_prelude_collision for object calls and use as _
jam1garner Jun 19, 2021
3dc47e2
do not run rustfix for future-prelude-collision-shadow
nikomatsakis Jun 21, 2021
186c09a
add test for `dyn` collisions
nikomatsakis Jun 21, 2021
aa3580b
introduce helper function
nikomatsakis Jun 21, 2021
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
50 changes: 50 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3001,6 +3001,7 @@ declare_lint_pass! {
PROC_MACRO_BACK_COMPAT,
OR_PATTERNS_BACK_COMPAT,
LARGE_ASSIGNMENTS,
FUTURE_PRELUDE_COLLISION,
]
}

Expand Down Expand Up @@ -3240,3 +3241,52 @@ declare_lint! {
Allow,
"detects usage of old versions of or-patterns",
}

declare_lint! {
/// The `future_prelude_collision` lint detects the usage of trait methods which are ambiguous
/// with traits added to the prelude in future editions.
///
/// ### Example
///
/// ```rust,compile_fail
/// #![deny(future_prelude_collision)]
///
/// trait Foo {
/// fn try_into(self) -> Result<String, !>;
/// }
///
/// impl Foo for &str {
/// fn try_into(self) -> Result<String, !> {
/// Ok(String::from(self))
/// }
/// }
///
/// fn main() {
/// let x: String = "3".try_into().unwrap();
/// // ^^^^^^^^
/// // This call to try_into matches both Foo:try_into and TryInto::try_into as
/// // `TryInto` has been added to the Rust prelude in 2021 edition.
/// println!("{}", x);
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// In Rust 2021, one of the important introductions is the [prelude changes], which add
/// `TryFrom`, `TryInto`, and `FromIterator` into the standard library's prelude. Since this
/// results in an amiguity as to which method/function to call when an existing `try_into`
/// method is called via dot-call syntax or a `try_from`/`from_iter` associated function
/// is called directly on a type.
///
/// [prelude changes]: https://blog.rust-lang.org/inside-rust/2021/03/04/planning-rust-2021.html#prelude-changes
Copy link
Member

Choose a reason for hiding this comment

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

Note: We should link to the edition guide once the 2021 stuff is online.

pub FUTURE_PRELUDE_COLLISION,
Allow,
"detects the usage of trait methods which are ambiguous with traits added to the \
prelude in future editions",
@future_incompatible = FutureIncompatibleInfo {
reference: "issue #85684 <https://github.com/rust-lang/rust/issues/85684>",
edition: Some(Edition::Edition2021),
};
}
3 changes: 3 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,7 @@ symbols! {
from,
from_desugaring,
from_generator,
from_iter,
from_method,
from_output,
from_residual,
Expand Down Expand Up @@ -1236,7 +1237,9 @@ symbols! {
truncf32,
truncf64,
try_blocks,
try_from,
try_from_trait,
try_into,
try_into_trait,
try_trait_v2,
tt,
Expand Down
5 changes: 3 additions & 2 deletions compiler/rustc_typeck/src/check/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr: &'tcx hir::Expr<'tcx>,
) -> Ty<'tcx> {
let tcx = self.tcx;
let (res, opt_ty, segs) = self.resolve_ty_and_res_ufcs(qpath, expr.hir_id, expr.span);
let (res, opt_ty, segs) =
self.resolve_ty_and_res_fully_qualified_call(qpath, expr.hir_id, expr.span);
let ty = match res {
Res::Err => {
self.set_tainted_by_errors();
Expand Down Expand Up @@ -940,7 +941,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// no need to check for bot/err -- callee does that
let rcvr_t = self.structurally_resolved_type(args[0].span, rcvr_t);

let method = match self.lookup_method(rcvr_t, segment, span, expr, rcvr) {
let method = match self.lookup_method(rcvr_t, segment, span, expr, rcvr, args) {
Ok(method) => {
// We could add a "consider `foo::<params>`" suggestion here, but I wasn't able to
// trigger this codepath causing `structuraly_resolved_type` to emit an error.
Expand Down
49 changes: 28 additions & 21 deletions compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -906,13 +906,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

/// Resolves an associated value path into a base type and associated constant, or method
/// resolution. The newly resolved definition is written into `type_dependent_defs`.
pub fn resolve_ty_and_res_ufcs(
pub fn resolve_ty_and_res_fully_qualified_call(
&self,
qpath: &'tcx QPath<'tcx>,
hir_id: hir::HirId,
span: Span,
) -> (Res, Option<Ty<'tcx>>, &'tcx [hir::PathSegment<'tcx>]) {
debug!("resolve_ty_and_res_ufcs: qpath={:?} hir_id={:?} span={:?}", qpath, hir_id, span);
debug!(
"resolve_ty_and_res_fully_qualified_call: qpath={:?} hir_id={:?} span={:?}",
qpath, hir_id, span
);
let (ty, qself, item_segment) = match *qpath {
QPath::Resolved(ref opt_qself, ref path) => {
return (
Expand All @@ -922,7 +925,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);
}
QPath::TypeRelative(ref qself, ref segment) => (self.to_ty(qself), qself, segment),
QPath::LangItem(..) => bug!("`resolve_ty_and_res_ufcs` called on `LangItem`"),
QPath::LangItem(..) => {
bug!("`resolve_ty_and_res_fully_qualified_call` called on `LangItem`")
}
};
if let Some(&cached_result) = self.typeck_results.borrow().type_dependent_defs().get(hir_id)
{
Expand All @@ -932,25 +937,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return (def, Some(ty), slice::from_ref(&**item_segment));
}
let item_name = item_segment.ident;
let result = self.resolve_ufcs(span, item_name, ty, hir_id).or_else(|error| {
let result = match error {
method::MethodError::PrivateMatch(kind, def_id, _) => Ok((kind, def_id)),
_ => Err(ErrorReported),
};
if item_name.name != kw::Empty {
if let Some(mut e) = self.report_method_error(
span,
ty,
item_name,
SelfSource::QPath(qself),
error,
None,
) {
e.emit();
let result = self
.resolve_fully_qualified_call(span, item_name, ty, qself.span, hir_id)
.or_else(|error| {
let result = match error {
method::MethodError::PrivateMatch(kind, def_id, _) => Ok((kind, def_id)),
_ => Err(ErrorReported),
};
if item_name.name != kw::Empty {
if let Some(mut e) = self.report_method_error(
span,
ty,
item_name,
SelfSource::QPath(qself),
error,
None,
) {
e.emit();
}
}
}
result
});
result
});

if result.is_ok() {
self.maybe_lint_bare_trait(qpath, hir_id);
Expand Down
47 changes: 41 additions & 6 deletions compiler/rustc_typeck/src/check/method/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/method-lookup.html

mod confirm;
mod prelude2021;
pub mod probe;
mod suggest;

Expand Down Expand Up @@ -173,14 +174,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
///
/// # Arguments
///
/// Given a method call like `foo.bar::<T1,...Tn>(...)`:
/// Given a method call like `foo.bar::<T1,...Tn>(a, b + 1, ...)`:
///
/// * `self`: the surrounding `FnCtxt` (!)
/// * `self_ty`: the (unadjusted) type of the self expression (`foo`)
/// * `segment`: the name and generic arguments of the method (`bar::<T1, ...Tn>`)
/// * `span`: the span for the method call
/// * `call_expr`: the complete method call: (`foo.bar::<T1,...Tn>(...)`)
/// * `self_expr`: the self expression (`foo`)
/// * `args`: the expressions of the arguments (`a, b + 1, ...`)
#[instrument(level = "debug", skip(self, call_expr, self_expr))]
pub fn lookup_method(
&self,
Expand All @@ -189,6 +191,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
span: Span,
call_expr: &'tcx hir::Expr<'tcx>,
self_expr: &'tcx hir::Expr<'tcx>,
args: &'tcx [hir::Expr<'tcx>],
) -> Result<MethodCallee<'tcx>, MethodError<'tcx>> {
debug!(
"lookup(method_name={}, self_ty={:?}, call_expr={:?}, self_expr={:?})",
Expand All @@ -198,6 +201,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let pick =
self.lookup_probe(span, segment.ident, self_ty, call_expr, ProbeScope::TraitsInScope)?;

self.lint_dot_call_from_2018(self_ty, segment, span, call_expr, self_expr, &pick, args);

for import_id in &pick.import_ids {
debug!("used_trait_import: {:?}", import_id);
Lrc::get_mut(&mut self.typeck_results.borrow_mut().used_trait_imports)
Expand Down Expand Up @@ -417,16 +422,33 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Some(InferOk { obligations, value: callee })
}

/// Performs a [full-qualified function call] (formerly "universal function call") lookup. If
/// lookup is successful, it will return the type of definition and the [`DefId`] of the found
/// function definition.
///
/// [full-qualified function call]: https://doc.rust-lang.org/reference/expressions/call-expr.html#disambiguating-function-calls
///
/// # Arguments
///
/// Given a function call like `Foo::bar::<T1,...Tn>(...)`:
///
/// * `self`: the surrounding `FnCtxt` (!)
/// * `span`: the span of the call, excluding arguments (`Foo::bar::<T1, ...Tn>`)
/// * `method_name`: the identifier of the function within the container type (`bar`)
/// * `self_ty`: the type to search within (`Foo`)
/// * `self_ty_span` the span for the type being searched within (span of `Foo`)
/// * `expr_id`: the [`hir::HirId`] of the expression composing the entire call
#[instrument(level = "debug", skip(self))]
pub fn resolve_ufcs(
pub fn resolve_fully_qualified_call(
&self,
span: Span,
method_name: Ident,
self_ty: Ty<'tcx>,
self_ty_span: Span,
expr_id: hir::HirId,
) -> Result<(DefKind, DefId), MethodError<'tcx>> {
debug!(
"resolve_ufcs: method_name={:?} self_ty={:?} expr_id={:?}",
"resolve_fully_qualified_call: method_name={:?} self_ty={:?} expr_id={:?}",
method_name, self_ty, expr_id,
);

Expand Down Expand Up @@ -463,18 +485,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr_id,
ProbeScope::TraitsInScope,
)?;
debug!("resolve_ufcs: pick={:?}", pick);

self.lint_fully_qualified_call_from_2018(
span,
method_name,
self_ty,
self_ty_span,
expr_id,
&pick,
);

debug!("resolve_fully_qualified_call: pick={:?}", pick);
{
let mut typeck_results = self.typeck_results.borrow_mut();
let used_trait_imports = Lrc::get_mut(&mut typeck_results.used_trait_imports).unwrap();
for import_id in pick.import_ids {
debug!("resolve_ufcs: used_trait_import: {:?}", import_id);
debug!("resolve_fully_qualified_call: used_trait_import: {:?}", import_id);
used_trait_imports.insert(import_id);
}
}

let def_kind = pick.item.kind.as_def_kind();
debug!("resolve_ufcs: def_kind={:?}, def_id={:?}", def_kind, pick.item.def_id);
debug!(
"resolve_fully_qualified_call: def_kind={:?}, def_id={:?}",
def_kind, pick.item.def_id
);
tcx.check_stability(pick.item.def_id, Some(expr_id), span, Some(method_name.span));
Ok((def_kind, pick.item.def_id))
}
Expand Down
Loading