Skip to content

Commit

Permalink
Auto merge of rust-lang#85707 - jam1garner:future_prelude_collision_l…
Browse files Browse the repository at this point in the history
…int, r=nikomatsakis

Add `future_prelude_collision` lint

Implements rust-lang#84594. (RFC rust-lang/rfcs#3114 ([rendered](https://github.com/rust-lang/rfcs/blob/master/text/3114-prelude-2021.md))) Not entirely complete but wanted to have my progress decently available while I finish off the last little bits.

Things left to implement:

* [x] UI tests for lints
* [x] Only emit lint for 2015 and 2018 editions
* [ ] Lint name/message bikeshedding
* [x] Implement for `FromIterator` (from best I can tell, the current approach as mentioned from [this comment](rust-lang#84594 (comment)) won't work due to `FromIterator` instances not using dot-call syntax, but if I'm correct about this then that would also need to be fixed for `TryFrom`/`TryInto`)*
* [x] Add to `rust-2021-migration` group? (See rust-lang#85512) (added to `rust-2021-compatibility` group)
* [ ] Link to edition guide in lint docs

*edit: looked into it, `lookup_method` will also not be hit for `TryFrom`/`TryInto` for non-dotcall syntax. If anyone who is more familiar with typecheck knows the equivalent for looking up associated functions, feel free to chime in.
  • Loading branch information
bors committed Jun 22, 2021
2 parents c38111c + aa3580b commit 44f4a87
Show file tree
Hide file tree
Showing 22 changed files with 1,144 additions and 31 deletions.
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 @@ -3244,3 +3245,52 @@ declare_lint! {
edition: Some(Edition::Edition2021),
};
}

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
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 @@ -586,6 +586,7 @@ symbols! {
from,
from_desugaring,
from_generator,
from_iter,
from_method,
from_output,
from_residual,
Expand Down Expand Up @@ -1238,7 +1239,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

0 comments on commit 44f4a87

Please sign in to comment.