Skip to content

Refactored check::autoderef in librustc_typeck to allow additional behaviour upon reaching the recursion limit #28931

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

Closed
wants to merge 11 commits into from
17 changes: 10 additions & 7 deletions src/librustc_typeck/check/method/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,16 @@ fn create_steps<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
-> Option<Vec<CandidateStep<'tcx>>> {
let mut steps = Vec::new();

let (final_ty, dereferences, _) = check::autoderef(fcx,
span,
self_ty,
None,
UnresolvedTypeAction::Error,
NoPreference,
|t, d| {
let (final_ty, dereferences, _) =
check::autoderef_with_recursion_option(fcx,
span,
self_ty,
None,
UnresolvedTypeAction::Error,
NoPreference,
check::AutoderefRecursionOption
::ReturnLastResolvedType,
|t, d| {
steps.push(CandidateStep {
self_ty: t,
autoderefs: d,
Expand Down
65 changes: 57 additions & 8 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2099,6 +2099,17 @@ pub enum UnresolvedTypeAction {
Ignore
}

/// Desired behaviour upon reaching the recursion limit. Used in
Copy link
Member

Choose a reason for hiding this comment

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

yay docs!

/// `autoderef_with_recursion_option`. Default is `ErrorGracefully`
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AutoderefRecursionOption {
/// Returns the last resolved type as it if were the valid end result
ReturnLastResolvedType,
/// Error gracefully upon reaching the limit.. This is the default behaviour
ErrorGracefully,
}


/// Executes an autoderef loop for the type `t`. At each step, invokes `should_stop` to decide
/// whether to terminate the loop. Returns the final type and number of derefs that it performed.
///
Expand All @@ -2109,18 +2120,45 @@ pub fn autoderef<'a, 'tcx, T, F>(fcx: &FnCtxt<'a, 'tcx>,
base_ty: Ty<'tcx>,
opt_expr: Option<&hir::Expr>,
unresolved_type_action: UnresolvedTypeAction,
mut lvalue_pref: LvaluePreference,
mut should_stop: F)
lvalue_pref: LvaluePreference,
should_stop: F)
-> (Ty<'tcx>, usize, Option<T>)
where F: FnMut(Ty<'tcx>, usize) -> Option<T>,
{
// call the implementation with the default behaviour.
autoderef_with_recursion_option(fcx,
sp,
base_ty,
opt_expr,
unresolved_type_action,
lvalue_pref,
AutoderefRecursionOption::ErrorGracefully,
should_stop)
}

/// Actual implementation of of `autoderef`. `autoderef` calls this method with
/// `AutoderefRecursionOption::ErrorGracefully` as default behaviour. Unless
/// specific behaviour is needed in case of reaching the recursion limit,
/// `autoderef` should be used instead.
pub fn autoderef_with_recursion_option<'a, 'tcx, T, F>(fcx: &FnCtxt<'a, 'tcx>,
sp: Span,
base_ty: Ty<'tcx>,
opt_expr: Option<&hir::Expr>,
unresolved_type_action: UnresolvedTypeAction,
mut lvalue_pref: LvaluePreference,
recursion_option: AutoderefRecursionOption,
mut should_stop: F)
-> (Ty<'tcx>, usize, Option<T>)
where F: FnMut(Ty<'tcx>, usize) -> Option<T>,
{
debug!("autoderef(base_ty={:?}, opt_expr={:?}, lvalue_pref={:?})",
base_ty,
opt_expr,
lvalue_pref);

let mut t = base_ty;
for autoderefs in 0..fcx.tcx().sess.recursion_limit.get() {
let recursion_limit = fcx.tcx().sess.recursion_limit.get();
for autoderefs in 0..recursion_limit {
let resolved_t = match unresolved_type_action {
UnresolvedTypeAction::Error => {
structurally_resolved_type(fcx, sp, t)
Expand Down Expand Up @@ -2163,6 +2201,7 @@ pub fn autoderef<'a, 'tcx, T, F>(fcx: &FnCtxt<'a, 'tcx>,
try_overloaded_deref(fcx, sp, method_call, None, resolved_t, lvalue_pref)
}
};

match mt {
Some(mt) => {
t = mt.ty;
Expand All @@ -2174,11 +2213,21 @@ pub fn autoderef<'a, 'tcx, T, F>(fcx: &FnCtxt<'a, 'tcx>,
}
}

// We've reached the recursion limit, error gracefully.
span_err!(fcx.tcx().sess, sp, E0055,
"reached the recursion limit while auto-dereferencing {:?}",
base_ty);
(fcx.tcx().types.err, 0, None)
// Respect the desired behaviour regarding the recursion limit.
match recursion_option {
AutoderefRecursionOption::ReturnLastResolvedType => {
// Treat `t`, the last resolved type, as endresult
return (t, recursion_limit, None);
},
AutoderefRecursionOption::ErrorGracefully => {
// We've reached the recursion limit, error gracefully.
// This is the default behaviour.
span_err!(fcx.tcx().sess, sp, E0055,
"reached the recursion limit while auto-dereferencing {:?}",
base_ty);
(fcx.tcx().types.err, 0, None)
}
}
}

fn try_overloaded_deref<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
Expand Down
50 changes: 50 additions & 0 deletions src/test/run-pass/issue-19509.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::ops::Deref;

struct Foo {
inner: Bar,
}

struct Bar;

impl Foo {
pub fn foo_method(&self) {
}
}

impl Bar {
pub fn bar_method(&self) {
}
}

impl Deref for Foo {
type Target = Bar;

fn deref<'a>(&'a self) -> &'a Self::Target {
&self.inner
}
}

impl Deref for Bar {
type Target = Foo;

fn deref<'a>(&'a self) -> &'a Self::Target {
panic!()
}
}

fn main() {
let foo = Foo { inner: Bar, };
let bar = Bar;

foo.bar_method(); // should compile and execute
}