Skip to content

Commit

Permalink
rustc_lint: handle more method calls in unconditional_recursion.
Browse files Browse the repository at this point in the history
  • Loading branch information
eddyb committed Aug 3, 2015
1 parent a34b0a4 commit 585f0e9
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 15 deletions.
68 changes: 53 additions & 15 deletions src/librustc_lint/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@

use metadata::{csearch, decoder};
use middle::{cfg, def, infer, pat_util, stability, traits};
use middle::def::*;
use middle::subst::Substs;
use middle::ty::{self, Ty};
use middle::const_eval::{eval_const_expr_partial, ConstVal};
Expand Down Expand Up @@ -2251,34 +2250,73 @@ impl LintPass for UnconditionalRecursion {
}
}

// Check if the method call `id` refers to method `method`.
// Check if the expression `id` performs a call to `method`.
fn expr_refers_to_this_method(tcx: &ty::ctxt,
method: &ty::Method,
id: ast::NodeId) -> bool {
let method_call = ty::MethodCall::expr(id);
let callee = match tcx.tables.borrow().method_map.get(&method_call) {
Some(&m) => m,
None => return false
};
let callee_item = tcx.impl_or_trait_item(callee.def_id);
let tables = tcx.tables.borrow();

// Check for method calls and overloaded operators.
if let Some(m) = tables.method_map.get(&ty::MethodCall::expr(id)) {
if method_call_refers_to_method(tcx, method, m.def_id, m.substs, id) {
return true;
}
}

// Check for overloaded autoderef method calls.
if let Some(&ty::AdjustDerefRef(ref adj)) = tables.adjustments.get(&id) {
for i in 0..adj.autoderefs {
let method_call = ty::MethodCall::autoderef(id, i as u32);
if let Some(m) = tables.method_map.get(&method_call) {
if method_call_refers_to_method(tcx, method, m.def_id, m.substs, id) {
return true;
}
}
}
}

// Check for calls to methods via explicit paths (e.g. `T::method()`).
match tcx.map.get(id) {
ast_map::NodeExpr(&ast::Expr { node: ast::ExprCall(ref callee, _), .. }) => {
match tcx.def_map.borrow().get(&callee.id).map(|d| d.full_def()) {
Some(def::DefMethod(def_id)) => {
let no_substs = &ty::ItemSubsts::empty();
let ts = tables.item_substs.get(&callee.id).unwrap_or(no_substs);
method_call_refers_to_method(tcx, method, def_id, &ts.substs, id)
}
_ => false
}
}
_ => false
}
}

// Check if the method call to the method with the ID `callee_id`
// and instantiated with `callee_substs` refers to method `method`.
fn method_call_refers_to_method<'tcx>(tcx: &ty::ctxt<'tcx>,
method: &ty::Method,
callee_id: ast::DefId,
callee_substs: &Substs<'tcx>,
expr_id: ast::NodeId) -> bool {
let callee_item = tcx.impl_or_trait_item(callee_id);

match callee_item.container() {
// This is an inherent method, so the `def_id` refers
// directly to the method definition.
ty::ImplContainer(_) => {
callee.def_id == method.def_id
callee_id == method.def_id
}

// A trait method, from any number of possible sources.
// Attempt to select a concrete impl before checking.
ty::TraitContainer(trait_def_id) => {
let trait_substs = callee.substs.clone().method_to_trait();
let trait_substs = callee_substs.clone().method_to_trait();
let trait_substs = tcx.mk_substs(trait_substs);
let trait_ref = ty::TraitRef::new(trait_def_id, trait_substs);
let trait_ref = ty::Binder(trait_ref);
let span = tcx.map.span(id);
let span = tcx.map.span(expr_id);
let obligation =
traits::Obligation::new(traits::ObligationCause::misc(span, id),
traits::Obligation::new(traits::ObligationCause::misc(span, expr_id),
trait_ref.to_poly_trait_predicate());

let param_env = ty::ParameterEnvironment::for_item(tcx, method.def_id.node);
Expand All @@ -2289,12 +2327,12 @@ impl LintPass for UnconditionalRecursion {
// If `T` is `Self`, then this call is inside
// a default method definition.
Ok(Some(traits::VtableParam(_))) => {
let self_ty = callee.substs.self_ty();
let self_ty = callee_substs.self_ty();
let on_self = self_ty.map_or(false, |t| t.is_self());
// We can only be recurring in a default
// method if we're being called literally
// on the `Self` type.
on_self && callee.def_id == method.def_id
on_self && callee_id == method.def_id
}

// The `impl` is known, so we check that with a
Expand Down Expand Up @@ -2454,7 +2492,7 @@ impl LintPass for MutableTransmutes {
ast::ExprPath(..) => (),
_ => return None
}
if let DefFn(did, _) = cx.tcx.resolve_expr(expr) {
if let def::DefFn(did, _) = cx.tcx.resolve_expr(expr) {
if !def_id_is_transmute(cx, did) {
return None;
}
Expand Down
66 changes: 66 additions & 0 deletions src/test/compile-fail/lint-unconditional-recursion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ fn quz() -> bool { //~ ERROR function cannot return without recurring
}
}

// Trait method calls.
trait Foo {
fn bar(&self) { //~ ERROR function cannot return without recurring
self.bar() //~ NOTE recursive call site
Expand All @@ -53,14 +54,79 @@ impl Foo for Box<Foo+'static> {
self.bar() //~ NOTE recursive call site
}
}
}

// Trait method call with integer fallback after method resolution.
impl Foo for i32 {
fn bar(&self) { //~ ERROR function cannot return without recurring
0.bar() //~ NOTE recursive call site
}
}

impl Foo for u32 {
fn bar(&self) {
0.bar()
}
}

// Trait method calls via paths.
trait Foo2 {
fn bar(&self) { //~ ERROR function cannot return without recurring
Foo2::bar(self) //~ NOTE recursive call site
}
}

impl Foo2 for Box<Foo2+'static> {
fn bar(&self) { //~ ERROR function cannot return without recurring
loop {
Foo2::bar(self) //~ NOTE recursive call site
}
}
}

struct Baz;
impl Baz {
// Inherent method call.
fn qux(&self) { //~ ERROR function cannot return without recurring
self.qux(); //~ NOTE recursive call site
}

// Inherent method call via path.
fn as_ref(&self) -> &Self { //~ ERROR function cannot return without recurring
Baz::as_ref(self) //~ NOTE recursive call site
}
}

// Trait method calls to impls via paths.
impl Default for Baz {
fn default() -> Baz { //~ ERROR function cannot return without recurring
let x = Default::default(); //~ NOTE recursive call site
x
}
}

// Overloaded operators.
impl std::ops::Deref for Baz {
type Target = ();
fn deref(&self) -> &() { //~ ERROR function cannot return without recurring
&**self //~ NOTE recursive call site
}
}

impl std::ops::Index<usize> for Baz {
type Output = Baz;
fn index(&self, x: usize) -> &Baz { //~ ERROR function cannot return without recurring
&self[x] //~ NOTE recursive call site
}
}

// Overloaded autoderef.
struct Quux;
impl std::ops::Deref for Quux {
type Target = Baz;
fn deref(&self) -> &Baz { //~ ERROR function cannot return without recurring
self.as_ref() //~ NOTE recursive call site
}
}

fn all_fine() {
Expand Down

0 comments on commit 585f0e9

Please sign in to comment.