From bbcb13da88f7a4b25506ec85f2f170e6f78d5a58 Mon Sep 17 00:00:00 2001 From: Jared Roesch Date: Tue, 7 Jul 2015 15:50:02 -0700 Subject: [PATCH] Implement Default TyParam fallback This patch allows type parameter defaults to influence type inference. This is a possible breaking change since it effects the way type inference works and will have different behavior when mixing defaults and literal fallback. --- src/librustc/middle/infer/mod.rs | 78 ++++++++++- src/librustc/middle/infer/type_variable.rs | 9 ++ src/librustc/middle/ty.rs | 11 ++ src/librustc_data_structures/unify/mod.rs | 8 +- src/librustc_typeck/check/method/confirm.rs | 10 +- src/librustc_typeck/check/method/mod.rs | 5 +- src/librustc_typeck/check/method/probe.rs | 4 +- src/librustc_typeck/check/mod.rs | 128 ++++++++++++++---- .../compile-fail/default_ty_param_conflict.rs | 23 ++++ .../default_ty_param_type_alias.rs | 17 +++ .../default_ty_param_method_call_test.rs | 22 +++ src/test/run-pass/default_ty_param_struct.rs | 21 +++ .../run-pass/default_ty_param_trait_impl.rs | 23 ++++ .../default_ty_param_trait_impl_simple.rs | 26 ++++ 14 files changed, 340 insertions(+), 45 deletions(-) create mode 100644 src/test/compile-fail/default_ty_param_conflict.rs create mode 100644 src/test/compile-fail/default_ty_param_type_alias.rs create mode 100644 src/test/run-pass/default_ty_param_method_call_test.rs create mode 100644 src/test/run-pass/default_ty_param_struct.rs create mode 100644 src/test/run-pass/default_ty_param_trait_impl.rs create mode 100644 src/test/run-pass/default_ty_param_trait_impl_simple.rs diff --git a/src/librustc/middle/infer/mod.rs b/src/librustc/middle/infer/mod.rs index c6df1c395cc..5339464503b 100644 --- a/src/librustc/middle/infer/mod.rs +++ b/src/librustc/middle/infer/mod.rs @@ -95,6 +95,9 @@ pub struct InferCtxt<'a, 'tcx: 'a> { normalize: bool, err_count_on_creation: usize, + + // Default Type Parameter fallbacks + pub defaults: RefCell, Ty<'tcx>>>, } /// A map returned by `skolemize_late_bound_regions()` indicating the skolemized @@ -350,7 +353,8 @@ pub fn new_infer_ctxt<'a, 'tcx>(tcx: &'a ty::ctxt<'tcx>, parameter_environment: param_env.unwrap_or(tcx.empty_parameter_environment()), fulfillment_cx: RefCell::new(traits::FulfillmentContext::new(errors_will_be_reported)), normalize: false, - err_count_on_creation: tcx.sess.err_count() + err_count_on_creation: tcx.sess.err_count(), + defaults: RefCell::new(FnvHashMap()), } } @@ -653,6 +657,30 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { } } + pub fn unsolved_variables(&self) -> Vec> { + let mut variables = Vec::new(); + + let unbound_ty_vars = self.type_variables + .borrow() + .unsolved_variables() + .into_iter().map(|t| self.tcx.mk_var(t)); + + let unbound_int_vars = self.int_unification_table + .borrow_mut() + .unsolved_variables() + .into_iter().map(|v| self.tcx.mk_int_var(v)); + + let unbound_float_vars = self.float_unification_table + .borrow_mut() + .unsolved_variables() + .into_iter().map(|v| self.tcx.mk_float_var(v)); + + variables.extend(unbound_ty_vars); + variables.extend(unbound_int_vars); + variables.extend(unbound_float_vars); + return variables; + } + fn combine_fields(&'a self, a_is_expected: bool, trace: TypeTrace<'tcx>) -> CombineFields<'a, 'tcx> { CombineFields {infcx: self, @@ -996,6 +1024,23 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { .collect() } + pub fn type_vars_for_defs(&self, + defs: &[ty::TypeParameterDef<'tcx>]) + -> Vec> { + let mut vars = Vec::with_capacity(defs.len()); + + for def in defs.iter() { + let ty_var = self.next_ty_var(); + match def.default { + None => {}, + Some(default) => { self.defaults.borrow_mut().insert(ty_var, default); } + } + vars.push(ty_var) + } + + vars + } + /// Given a set of generics defined on a type or impl, returns a substitution mapping each /// type/region parameter to a fresh inference variable. pub fn fresh_substs_for_generics(&self, @@ -1003,9 +1048,12 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { generics: &ty::Generics<'tcx>) -> subst::Substs<'tcx> { - let type_params = - generics.types.map( - |_| self.next_ty_var()); + let mut type_params = subst::VecPerParamSpace::empty(); + + for space in subst::ParamSpace::all().iter() { + type_params.replace(*space, self.type_vars_for_defs(generics.types.get_slice(*space))) + } + let region_params = generics.regions.map( |d| self.next_region_var(EarlyBoundRegion(span, d.name))); @@ -1027,8 +1075,8 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { assert!(generics.regions.len(subst::SelfSpace) == 0); assert!(generics.regions.len(subst::FnSpace) == 0); - let type_parameter_count = generics.types.len(subst::TypeSpace); - let type_parameters = self.next_ty_vars(type_parameter_count); + let type_parameter_defs = generics.types.get_slice(subst::TypeSpace); + let type_parameters = self.type_vars_for_defs(type_parameter_defs); let region_param_defs = generics.regions.get_slice(subst::TypeSpace); let regions = self.region_vars_for_defs(span, region_param_defs); @@ -1268,6 +1316,24 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { self.report_and_explain_type_error(trace, err); } + pub fn report_conflicting_default_types(&self, + span: Span, + expected: Ty<'tcx>, + actual: Ty<'tcx>) { + let trace = TypeTrace { + origin: Misc(span), + values: Types(ty::expected_found { + expected: expected, + found: actual + }) + }; + + self.report_and_explain_type_error(trace, &ty::type_err::terr_ty_param_default_mismatch(ty::expected_found { + expected: expected, + found: actual + })); + } + pub fn replace_late_bound_regions_with_fresh_var( &self, span: Span, diff --git a/src/librustc/middle/infer/type_variable.rs b/src/librustc/middle/infer/type_variable.rs index 6f1de3ee635..ebecb0898b4 100644 --- a/src/librustc/middle/infer/type_variable.rs +++ b/src/librustc/middle/infer/type_variable.rs @@ -195,6 +195,15 @@ impl<'tcx> TypeVariableTable<'tcx> { escaping_types } + + pub fn unsolved_variables(&self) -> Vec { + self.values.iter().enumerate().filter_map(|(i, value)| + match &value.value { + &TypeVariableValue::Known(_) => None, + &TypeVariableValue::Bounded(_) => Some(ty::TyVid { index: i as u32 }) + } + ).collect() + } } impl<'tcx> sv::SnapshotVecDelegate for Delegate<'tcx> { diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs index 84ca7cd437a..a08c5e1f73f 100644 --- a/src/librustc/middle/ty.rs +++ b/src/librustc/middle/ty.rs @@ -2068,6 +2068,7 @@ pub enum TypeError<'tcx> { ConvergenceMismatch(ExpectedFound), ProjectionNameMismatched(ExpectedFound), ProjectionBoundsLength(ExpectedFound), + terr_ty_param_default_mismatch(expected_found>) } /// Bounds suitable for an existentially quantified type parameter @@ -5080,6 +5081,11 @@ impl<'tcx> fmt::Display for TypeError<'tcx> { write!(f, "expected {} associated type bindings, found {}", values.expected, values.found) + }, + terr_ty_param_default_mismatch(ref values) => { + write!(f, "conflicting type parameter defaults {} {}", + values.expected, + values.found) } } } @@ -5437,6 +5443,11 @@ impl<'tcx> ctxt<'tcx> { &format!("consider boxing your closure and/or \ using it as a trait object")); } + }, + terr_ty_param_default_mismatch(expected) => { + self.sess.span_note(sp, + &format!("found conflicting defaults {:?} {:?}", + expected.expected, expected.found)) } _ => {} } diff --git a/src/librustc_data_structures/unify/mod.rs b/src/librustc_data_structures/unify/mod.rs index a899bbacc03..7582b7ff61d 100644 --- a/src/librustc_data_structures/unify/mod.rs +++ b/src/librustc_data_structures/unify/mod.rs @@ -339,5 +339,11 @@ impl<'tcx,K,V> UnificationTable pub fn probe(&mut self, a_id: K) -> Option { self.get(a_id).value.clone() } -} + pub fn unsolved_variables(&mut self) -> Vec { + self.values + .iter() + .filter_map(|vv| if vv.value.is_some() { None } else { Some(vv.key()) }) + .collect() + } +} diff --git a/src/librustc_typeck/check/method/confirm.rs b/src/librustc_typeck/check/method/confirm.rs index a8c56b2660c..e3b9b990cb9 100644 --- a/src/librustc_typeck/check/method/confirm.rs +++ b/src/librustc_typeck/check/method/confirm.rs @@ -309,15 +309,17 @@ impl<'a,'tcx> ConfirmContext<'a,'tcx> { // If they were not explicitly supplied, just construct fresh // variables. let num_supplied_types = supplied_method_types.len(); - let num_method_types = pick.item.as_opt_method().unwrap() - .generics.types.len(subst::FnSpace); + let method = pick.item.as_opt_method().unwrap(); + let method_types = method.generics.types.get_slice(subst::FnSpace); + let num_method_types = method_types.len(); + let method_types = { if num_supplied_types == 0 { - self.fcx.infcx().next_ty_vars(num_method_types) + self.fcx.infcx().type_vars_for_defs(method_types) } else if num_method_types == 0 { span_err!(self.tcx().sess, self.span, E0035, "does not take type parameters"); - self.fcx.infcx().next_ty_vars(num_method_types) + self.fcx.infcx().type_vars_for_defs(method_types) } else if num_supplied_types != num_method_types { span_err!(self.tcx().sess, self.span, E0036, "incorrect number of type parameters given for this method"); diff --git a/src/librustc_typeck/check/method/mod.rs b/src/librustc_typeck/check/method/mod.rs index a74c004389b..2ca5a88fb06 100644 --- a/src/librustc_typeck/check/method/mod.rs +++ b/src/librustc_typeck/check/method/mod.rs @@ -167,7 +167,8 @@ pub fn lookup_in_trait_adjusted<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, let trait_def = fcx.tcx().lookup_trait_def(trait_def_id); - let expected_number_of_input_types = trait_def.generics.types.len(subst::TypeSpace); + let type_parameter_defs = trait_def.generics.types.get_slice(subst::TypeSpace); + let expected_number_of_input_types = type_parameter_defs.len(); let input_types = match opt_input_types { Some(input_types) => { assert_eq!(expected_number_of_input_types, input_types.len()); @@ -175,7 +176,7 @@ pub fn lookup_in_trait_adjusted<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, } None => { - fcx.inh.infcx.next_ty_vars(expected_number_of_input_types) + fcx.inh.infcx.type_vars_for_defs(type_parameter_defs) } }; diff --git a/src/librustc_typeck/check/method/probe.rs b/src/librustc_typeck/check/method/probe.rs index a960123efc6..88bd000cfdd 100644 --- a/src/librustc_typeck/check/method/probe.rs +++ b/src/librustc_typeck/check/method/probe.rs @@ -1207,8 +1207,8 @@ impl<'a,'tcx> ProbeContext<'a,'tcx> { !method.generics.regions.is_empty_in(subst::FnSpace) { let method_types = - self.infcx().next_ty_vars( - method.generics.types.len(subst::FnSpace)); + self.infcx().type_vars_for_defs( + method.generics.types.get_slice(subst::FnSpace)); // In general, during probe we erase regions. See // `impl_self_ty()` for an explanation. diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index 082dafc72bc..e226b0b21a1 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -108,6 +108,7 @@ use util::nodemap::{DefIdMap, FnvHashMap, NodeMap}; use util::lev_distance::lev_distance; use std::cell::{Cell, Ref, RefCell}; +use std::collections::HashSet; use std::mem::replace; use std::slice; use syntax::{self, abi, attr}; @@ -1255,28 +1256,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - /// Apply "fallbacks" to some types - /// ! gets replaced with (), unconstrained ints with i32, and unconstrained floats with f64. - pub fn default_type_parameters(&self) { - use middle::ty::UnconstrainedNumeric::{UnconstrainedInt, UnconstrainedFloat, Neither}; - for (_, &mut ref ty) in &mut self.inh.tables.borrow_mut().node_types { - let resolved = self.infcx().resolve_type_vars_if_possible(ty); - if self.infcx().type_var_diverges(resolved) { - demand::eqtype(self, codemap::DUMMY_SP, *ty, self.tcx().mk_nil()); - } else { - match self.infcx().type_is_unconstrained_numeric(resolved) { - UnconstrainedInt => { - demand::eqtype(self, codemap::DUMMY_SP, *ty, self.tcx().types.i32) - }, - UnconstrainedFloat => { - demand::eqtype(self, codemap::DUMMY_SP, *ty, self.tcx().types.f64) - } - Neither => { } - } - } - } - } - #[inline] pub fn write_ty(&self, node_id: ast::NodeId, ty: Ty<'tcx>) { debug!("write_ty({}, {:?}) in fcx {}", @@ -1711,11 +1690,98 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } fn select_all_obligations_and_apply_defaults(&self) { - debug!("select_all_obligations_and_apply_defaults"); + use middle::ty::UnconstrainedNumeric::{UnconstrainedInt, UnconstrainedFloat, Neither}; - self.select_obligations_where_possible(); - self.default_type_parameters(); - self.select_obligations_where_possible(); + debug!("select_all_obligations_and_apply_defaults: defaults={:?}", self.infcx().defaults); + + for _ in (0..self.tcx().sess.recursion_limit.get()) { + self.select_obligations_where_possible(); + + let unsolved_variables = self.inh.infcx.unsolved_variables(); + let mut unbound_tyvars = HashSet::new(); + + // Gather all unconstrainted integer and float variables + for ty in &unsolved_variables { + let resolved = self.infcx().resolve_type_vars_if_possible(ty); + if self.infcx().type_var_diverges(resolved) { + demand::eqtype(self, codemap::DUMMY_SP, *ty, self.tcx().mk_nil()); + } else { + match self.infcx().type_is_unconstrained_numeric(resolved) { + UnconstrainedInt => { + unbound_tyvars.insert(resolved); + }, + UnconstrainedFloat => { + unbound_tyvars.insert(resolved); + } + Neither => {} + } + } + } + + // Collect the set of variables that need fallback applied + for ty in &unsolved_variables { + if self.inh.infcx.defaults.borrow().contains_key(ty) { + let resolved = self.infcx().resolve_type_vars_if_possible(ty); + + debug!("select_all_obligations_and_apply_defaults: ty: {:?} with default: {:?}", + ty, self.inh.infcx.defaults.borrow().get(ty)); + + match resolved.sty { + ty::TyInfer(ty::TyVar(_)) => { + unbound_tyvars.insert(ty); + } + + ty::TyInfer(ty::IntVar(_)) | ty::TyInfer(ty::FloatVar(_)) => { + unbound_tyvars.insert(ty); + if unbound_tyvars.contains(resolved) { + unbound_tyvars.remove(resolved); + } + } + + _ => {} + } + } + } + + if unbound_tyvars.is_empty() { + break; + } + + // Go through the unbound variables and unify them with the proper fallbacks + for ty in &unbound_tyvars { + // let resolved = self.infcx().resolve_type_vars_if_possible(ty); + if self.infcx().type_var_diverges(ty) { + demand::eqtype(self, codemap::DUMMY_SP, *ty, self.tcx().mk_nil()); + } else { + match self.infcx().type_is_unconstrained_numeric(ty) { + UnconstrainedInt => { + demand::eqtype(self, codemap::DUMMY_SP, *ty, self.tcx().types.i32) + }, + UnconstrainedFloat => { + demand::eqtype(self, codemap::DUMMY_SP, *ty, self.tcx().types.f64) + } + Neither => { + let default_map = self.inh.infcx.defaults.borrow(); + if let Some(default) = default_map.get(ty) { + match infer::mk_eqty(self.infcx(), false, + infer::Misc(codemap::DUMMY_SP), + ty, default) { + Ok(()) => { /* ok */ } + Err(_) => { + self.infcx().report_conflicting_default_types( + codemap::DUMMY_SP, + ty, + default) + } + } + } + } + } + } + } + + self.select_obligations_where_possible(); + } } fn select_all_obligations_or_error(&self) { @@ -2421,13 +2487,15 @@ pub fn impl_self_ty<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, let tcx = fcx.tcx(); let ity = tcx.lookup_item_type(did); - let (n_tps, rps, raw_ty) = - (ity.generics.types.len(subst::TypeSpace), + let (tps, rps, raw_ty) = + (ity.generics.types.get_slice(subst::TypeSpace), ity.generics.regions.get_slice(subst::TypeSpace), ity.ty); + debug!("impl_self_ty: tps={:?} rps={:?} raw_ty={:?}", tps, rps, raw_ty); + let rps = fcx.inh.infcx.region_vars_for_defs(span, rps); - let tps = fcx.inh.infcx.next_ty_vars(n_tps); + let tps = fcx.inh.infcx.type_vars_for_defs(tps); let substs = subst::Substs::new_type(tps, rps); let substd_ty = fcx.instantiate_type_scheme(span, &substs, &raw_ty); @@ -4647,7 +4715,7 @@ pub fn instantiate_path<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, // Nothing specified at all: supply inference variables for // everything. if provided_len == 0 && !(require_type_space && space == subst::TypeSpace) { - substs.types.replace(space, fcx.infcx().next_ty_vars(desired.len())); + substs.types.replace(space, fcx.infcx().type_vars_for_defs(&desired[..])); return; } diff --git a/src/test/compile-fail/default_ty_param_conflict.rs b/src/test/compile-fail/default_ty_param_conflict.rs new file mode 100644 index 00000000000..900945da113 --- /dev/null +++ b/src/test/compile-fail/default_ty_param_conflict.rs @@ -0,0 +1,23 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt::Debug; + +// Example from the RFC +fn foo() -> F { F::default() } +fn bar(b: B) { println!("{:?}", b); } + +fn main() { + // Here, F is instantiated with $0=uint + let x = foo(); + + // Here, B is instantiated with $1=uint, and constraint $0 <: $1 is added. + bar(x); +} diff --git a/src/test/compile-fail/default_ty_param_type_alias.rs b/src/test/compile-fail/default_ty_param_type_alias.rs new file mode 100644 index 00000000000..c3e44e55bee --- /dev/null +++ b/src/test/compile-fail/default_ty_param_type_alias.rs @@ -0,0 +1,17 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::collections::HashMap; + +type IntMap = HashMap; + +fn main() { + let x = IntMap::new(); +} diff --git a/src/test/run-pass/default_ty_param_method_call_test.rs b/src/test/run-pass/default_ty_param_method_call_test.rs new file mode 100644 index 00000000000..35f0fcab001 --- /dev/null +++ b/src/test/run-pass/default_ty_param_method_call_test.rs @@ -0,0 +1,22 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +struct Foo; + +impl Foo { + fn method(&self) -> A { + A::default() + } +} + +fn main() { + let f = Foo.method(); + println!("{}", f); +} diff --git a/src/test/run-pass/default_ty_param_struct.rs b/src/test/run-pass/default_ty_param_struct.rs new file mode 100644 index 00000000000..b94b759fd11 --- /dev/null +++ b/src/test/run-pass/default_ty_param_struct.rs @@ -0,0 +1,21 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +struct Foo(A); + +impl Foo { + fn new() -> Foo { + Foo(A::default()) + } +} + +fn main() { + let foo = Foo::new(); +} diff --git a/src/test/run-pass/default_ty_param_trait_impl.rs b/src/test/run-pass/default_ty_param_trait_impl.rs new file mode 100644 index 00000000000..fbceb60b9a8 --- /dev/null +++ b/src/test/run-pass/default_ty_param_trait_impl.rs @@ -0,0 +1,23 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Another example from the RFC +trait Foo { } +trait Bar { } + +impl Foo for Vec {} // Impl 1 +impl Bar for usize { } // Impl 2 + +fn takes_foo(f: F) { } + +fn main() { + let x = Vec::new(); // x: Vec<$0> + takes_foo(x); // adds oblig Vec<$0> : Foo +} diff --git a/src/test/run-pass/default_ty_param_trait_impl_simple.rs b/src/test/run-pass/default_ty_param_trait_impl_simple.rs new file mode 100644 index 00000000000..00d7ccf43be --- /dev/null +++ b/src/test/run-pass/default_ty_param_trait_impl_simple.rs @@ -0,0 +1,26 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// An example from the RFC +trait Foo { fn takes_foo(&self); } +trait Bar { } + +impl Foo for Vec { + fn takes_foo(&self) {} +} // Impl 1 + +impl Bar for usize { } // Impl 2 + +// fn takes_foo(f: F) { } + +fn main() { + let x = Vec::new(); // x: Vec<$0> + x.takes_foo(); // adds oblig Vec<$0> : Foo +}