rustc_typeck: unify expected return types with formal return types to propagate coercions through calls of generic functions.

This commit is contained in:
Eduard Burtescu 2015-01-08 18:25:52 +02:00
parent 21ec0c85d9
commit e73fbc69cd
5 changed files with 180 additions and 23 deletions

View File

@ -613,6 +613,39 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
self.commit_unconditionally(move || self.try(move |_| f()))
}
/// Execute `f` and commit only the region bindings if successful.
/// The function f must be very careful not to leak any non-region
/// variables that get created.
pub fn commit_regions_if_ok<T, E, F>(&self, f: F) -> Result<T, E> where
F: FnOnce() -> Result<T, E>
{
debug!("commit_regions_if_ok()");
let CombinedSnapshot { type_snapshot,
int_snapshot,
float_snapshot,
region_vars_snapshot } = self.start_snapshot();
let r = self.try(move |_| f());
// Roll back any non-region bindings - they should be resolved
// inside `f`, with, e.g. `resolve_type_vars_if_possible`.
self.type_variables
.borrow_mut()
.rollback_to(type_snapshot);
self.int_unification_table
.borrow_mut()
.rollback_to(int_snapshot);
self.float_unification_table
.borrow_mut()
.rollback_to(float_snapshot);
// Commit region vars that may escape through resolved types.
self.region_vars
.commit(region_vars_snapshot);
r
}
/// Execute `f`, unroll bindings on panic
pub fn try<T, E, F>(&self, f: F) -> Result<T, E> where
F: FnOnce(&CombinedSnapshot) -> Result<T, E>

View File

@ -14,6 +14,8 @@ use super::check_argument_types;
use super::check_expr;
use super::check_method_argument_types;
use super::err_args;
use super::Expectation;
use super::expected_types_for_fn_args;
use super::FnCtxt;
use super::LvaluePreference;
use super::method;
@ -65,7 +67,8 @@ pub fn check_legal_trait_for_method_call(ccx: &CrateCtxt, span: Span, trait_id:
pub fn check_call<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
call_expr: &ast::Expr,
callee_expr: &ast::Expr,
arg_exprs: &[P<ast::Expr>])
arg_exprs: &[P<ast::Expr>],
expected: Expectation<'tcx>)
{
check_expr(fcx, callee_expr);
let original_callee_ty = fcx.expr_ty(callee_expr);
@ -84,15 +87,15 @@ pub fn check_call<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
match result {
None => {
// this will report an error since original_callee_ty is not a fn
confirm_builtin_call(fcx, call_expr, original_callee_ty, arg_exprs);
confirm_builtin_call(fcx, call_expr, original_callee_ty, arg_exprs, expected);
}
Some(CallStep::Builtin) => {
confirm_builtin_call(fcx, call_expr, callee_ty, arg_exprs);
confirm_builtin_call(fcx, call_expr, callee_ty, arg_exprs, expected);
}
Some(CallStep::Overloaded(method_callee)) => {
confirm_overloaded_call(fcx, call_expr, arg_exprs, method_callee);
confirm_overloaded_call(fcx, call_expr, arg_exprs, method_callee, expected);
}
}
}
@ -153,7 +156,8 @@ fn try_overloaded_call_step<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
fn confirm_builtin_call<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
call_expr: &ast::Expr,
callee_ty: Ty<'tcx>,
arg_exprs: &[P<ast::Expr>])
arg_exprs: &[P<ast::Expr>],
expected: Expectation<'tcx>)
{
let error_fn_sig;
@ -192,9 +196,15 @@ fn confirm_builtin_call<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
fcx.normalize_associated_types_in(call_expr.span, &fn_sig);
// Call the generic checker.
let expected_arg_tys = expected_types_for_fn_args(fcx,
call_expr.span,
expected,
fn_sig.output,
fn_sig.inputs.as_slice());
check_argument_types(fcx,
call_expr.span,
fn_sig.inputs.as_slice(),
&expected_arg_tys[],
arg_exprs,
AutorefArgs::No,
fn_sig.variadic,
@ -206,7 +216,8 @@ fn confirm_builtin_call<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
fn confirm_overloaded_call<'a,'tcx>(fcx: &FnCtxt<'a, 'tcx>,
call_expr: &ast::Expr,
arg_exprs: &[P<ast::Expr>],
method_callee: ty::MethodCallee<'tcx>)
method_callee: ty::MethodCallee<'tcx>,
expected: Expectation<'tcx>)
{
let output_type = check_method_argument_types(fcx,
call_expr.span,
@ -214,7 +225,8 @@ fn confirm_overloaded_call<'a,'tcx>(fcx: &FnCtxt<'a, 'tcx>,
call_expr,
arg_exprs,
AutorefArgs::No,
TupleArgumentsFlag::TupleArguments);
TupleArgumentsFlag::TupleArguments,
expected);
let method_call = ty::MethodCall::expr(call_expr.id);
fcx.inh.method_map.borrow_mut().insert(method_call, method_callee);
write_call(fcx, call_expr, output_type);

View File

@ -2559,7 +2559,8 @@ fn lookup_method_for_for_loop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
iterator_expr,
&[],
AutorefArgs::No,
DontTupleArguments);
DontTupleArguments,
NoExpectation);
match method {
Some(method) => {
@ -2601,7 +2602,8 @@ fn check_method_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
callee_expr: &ast::Expr,
args_no_rcvr: &[P<ast::Expr>],
autoref_args: AutorefArgs,
tuple_arguments: TupleArgumentsFlag)
tuple_arguments: TupleArgumentsFlag,
expected: Expectation<'tcx>)
-> ty::FnOutput<'tcx> {
if ty::type_is_error(method_fn_ty) {
let err_inputs = err_args(fcx.tcx(), args_no_rcvr.len());
@ -2614,6 +2616,7 @@ fn check_method_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
check_argument_types(fcx,
sp,
&err_inputs[],
&[],
args_no_rcvr,
autoref_args,
false,
@ -2623,9 +2626,15 @@ fn check_method_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
match method_fn_ty.sty {
ty::ty_bare_fn(_, ref fty) => {
// HACK(eddyb) ignore self in the definition (see above).
let expected_arg_tys = expected_types_for_fn_args(fcx,
sp,
expected,
fty.sig.0.output,
&fty.sig.0.inputs[1..]);
check_argument_types(fcx,
sp,
&fty.sig.0.inputs[1..],
&expected_arg_tys[],
args_no_rcvr,
autoref_args,
fty.sig.0.variadic,
@ -2645,6 +2654,7 @@ fn check_method_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
fn check_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
sp: Span,
fn_inputs: &[Ty<'tcx>],
expected_arg_tys: &[Ty<'tcx>],
args: &[P<ast::Expr>],
autoref_args: AutorefArgs,
variadic: bool,
@ -2659,6 +2669,7 @@ fn check_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
1
};
let mut expected_arg_tys = expected_arg_tys;
let expected_arg_count = fn_inputs.len();
let formal_tys = if tuple_arguments == TupleArguments {
let tuple_type = structurally_resolved_type(fcx, sp, fn_inputs[0]);
@ -2671,8 +2682,16 @@ fn check_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
if arg_types.len() == 1 {""} else {"s"},
args.len(),
if args.len() == 1 {" was"} else {"s were"});
expected_arg_tys = &[][];
err_args(fcx.tcx(), args.len())
} else {
expected_arg_tys = match expected_arg_tys.get(0) {
Some(&ty) => match ty.sty {
ty::ty_tup(ref tys) => &**tys,
_ => &[]
},
None => &[]
};
(*arg_types).clone()
}
}
@ -2680,14 +2699,15 @@ fn check_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
span_err!(tcx.sess, sp, E0059,
"cannot use call notation; the first type parameter \
for the function trait is neither a tuple nor unit");
expected_arg_tys = &[][];
err_args(fcx.tcx(), args.len())
}
}
} else if expected_arg_count == supplied_arg_count {
fn_inputs.iter().map(|a| *a).collect()
fn_inputs.to_vec()
} else if variadic {
if supplied_arg_count >= expected_arg_count {
fn_inputs.iter().map(|a| *a).collect()
fn_inputs.to_vec()
} else {
span_err!(tcx.sess, sp, E0060,
"this function takes at least {} parameter{} \
@ -2696,6 +2716,7 @@ fn check_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
if expected_arg_count == 1 {""} else {"s"},
supplied_arg_count,
if supplied_arg_count == 1 {" was"} else {"s were"});
expected_arg_tys = &[][];
err_args(fcx.tcx(), supplied_arg_count)
}
} else {
@ -2705,6 +2726,7 @@ fn check_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
if expected_arg_count == 1 {""} else {"s"},
supplied_arg_count,
if supplied_arg_count == 1 {" was"} else {"s were"});
expected_arg_tys = &[][];
err_args(fcx.tcx(), supplied_arg_count)
};
@ -2768,7 +2790,25 @@ fn check_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
AutorefArgs::No => {}
}
check_expr_coercable_to_type(fcx, &**arg, formal_ty);
// The special-cased logic below has three functions:
// 1. Provide as good of an expected type as possible.
let expected = expected_arg_tys.get(i).map(|&ty| {
Expectation::rvalue_hint(ty)
});
check_expr_with_unifier(fcx, &**arg,
expected.unwrap_or(ExpectHasType(formal_ty)),
NoPreference, || {
// 2. Coerce to the most detailed type that could be coerced
// to, which is `expected_ty` if `rvalue_hint` returns an
// `ExprHasType(expected_ty)`, or the `formal_ty` otherwise.
let coerce_ty = expected.and_then(|e| e.only_has_type(fcx));
demand::coerce(fcx, arg.span, coerce_ty.unwrap_or(formal_ty), &**arg);
// 3. Relate the expected type and the formal one,
// if the expected type was used for the coercion.
coerce_ty.map(|ty| demand::suptype(fcx, arg.span, formal_ty, ty));
});
}
}
}
@ -3008,6 +3048,45 @@ enum TupleArgumentsFlag {
TupleArguments,
}
/// Unifies the return type with the expected type early, for more coercions
/// and forward type information on the argument expressions.
fn expected_types_for_fn_args<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
call_span: Span,
expected_ret: Expectation<'tcx>,
formal_ret: ty::FnOutput<'tcx>,
formal_args: &[Ty<'tcx>])
-> Vec<Ty<'tcx>> {
let expected_args = expected_ret.only_has_type(fcx).and_then(|ret_ty| {
if let ty::FnConverging(formal_ret_ty) = formal_ret {
fcx.infcx().commit_regions_if_ok(|| {
// Attempt to apply a subtyping relationship between the formal
// return type (likely containing type variables if the function
// is polymorphic) and the expected return type.
// No argument expectations are produced if unification fails.
let origin = infer::Misc(call_span);
let ures = fcx.infcx().sub_types(false, origin, formal_ret_ty, ret_ty);
// FIXME(#15760) can't use try! here, FromError doesn't default
// to identity so the resulting type is not constrained.
if let Err(e) = ures {
return Err(e);
}
// Record all the argument types, with the substitutions
// produced from the above subtyping unification.
Ok(formal_args.iter().map(|ty| {
fcx.infcx().resolve_type_vars_if_possible(ty)
}).collect())
}).ok()
} else {
None
}
}).unwrap_or(vec![]);
debug!("expected_types_for_fn_args(formal={} -> {}, expected={} -> {})",
formal_args.repr(fcx.tcx()), formal_ret.repr(fcx.tcx()),
expected_args.repr(fcx.tcx()), expected_ret.repr(fcx.tcx()));
expected_args
}
/// Invariant:
/// If an expression has any sub-expressions that result in a type error,
/// inspecting that expression's type with `ty::type_is_error` will return
@ -3029,12 +3108,13 @@ fn check_expr_with_unifier<'a, 'tcx, F>(fcx: &FnCtxt<'a, 'tcx>,
expr.repr(fcx.tcx()), expected.repr(fcx.tcx()));
// Checks a method call.
fn check_method_call(fcx: &FnCtxt,
expr: &ast::Expr,
method_name: ast::SpannedIdent,
args: &[P<ast::Expr>],
tps: &[P<ast::Ty>],
lvalue_pref: LvaluePreference) {
fn check_method_call<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
expr: &ast::Expr,
method_name: ast::SpannedIdent,
args: &[P<ast::Expr>],
tps: &[P<ast::Ty>],
expected: Expectation<'tcx>,
lvalue_pref: LvaluePreference) {
let rcvr = &*args[0];
check_expr_with_lvalue_pref(fcx, &*rcvr, lvalue_pref);
@ -3071,7 +3151,8 @@ fn check_expr_with_unifier<'a, 'tcx, F>(fcx: &FnCtxt<'a, 'tcx>,
expr,
&args[1..],
AutorefArgs::No,
DontTupleArguments);
DontTupleArguments,
expected);
write_call(fcx, expr, ret_ty);
}
@ -3182,7 +3263,8 @@ fn check_expr_with_unifier<'a, 'tcx, F>(fcx: &FnCtxt<'a, 'tcx>,
op_ex,
args,
autoref_args,
DontTupleArguments) {
DontTupleArguments,
NoExpectation) {
ty::FnConverging(result_type) => result_type,
ty::FnDiverging => fcx.tcx().types.err
}
@ -3198,7 +3280,8 @@ fn check_expr_with_unifier<'a, 'tcx, F>(fcx: &FnCtxt<'a, 'tcx>,
op_ex,
args,
autoref_args,
DontTupleArguments);
DontTupleArguments,
NoExpectation);
fcx.tcx().types.err
}
}
@ -4045,10 +4128,10 @@ fn check_expr_with_unifier<'a, 'tcx, F>(fcx: &FnCtxt<'a, 'tcx>,
fcx.write_ty(id, fcx.node_ty(b.id));
}
ast::ExprCall(ref callee, ref args) => {
callee::check_call(fcx, expr, &**callee, &args[]);
callee::check_call(fcx, expr, &**callee, &args[], expected);
}
ast::ExprMethodCall(ident, ref tps, ref args) => {
check_method_call(fcx, expr, ident, &args[], &tps[], lvalue_pref);
check_method_call(fcx, expr, ident, &args[], &tps[], expected, lvalue_pref);
let arg_tys = args.iter().map(|a| fcx.expr_ty(&**a));
let args_err = arg_tys.fold(false,
|rest_err, a| {

View File

@ -30,4 +30,7 @@ pub fn main() {
let _: &Fn(int) -> _ = &{ |x| (x as u8) };
let _: &Show = &if true { false } else { true };
let _: &Show = &match true { true => 'a', false => 'b' };
let _: Box<[int]> = Box::new([1, 2, 3]);
let _: Box<Fn(int) -> _> = Box::new(|x| (x as u8));
}

View File

@ -0,0 +1,26 @@
// Copyright 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.
// Check that coercions unify the expected return type of a polymorphic
// function call, instead of leaving the type variables as they were.
struct Foo;
impl Foo {
fn foo<T>(self, x: T) -> Option<T> { Some(x) }
}
pub fn main() {
let _: Option<fn()> = Some(main);
let _: Option<fn()> = Foo.foo(main);
// The same two cases, with implicit type variables made explicit.
let _: Option<fn()> = Some::<_>(main);
let _: Option<fn()> = Foo.foo::<_>(main);
}