Skip to content

Commit

Permalink
Auto merge of #56384 - scalexm:chalk, r=nikomatsakis
Browse files Browse the repository at this point in the history
Implement the new-style trait solver

Final PR of what I believe to be a minimally working implementation of the new-style trait solver.

The new trait solver can be used by providing the `-Z chalk` command line flag. It is currently used everywhere in `rustc_typeck`, and for everything relying on `rustc::infer::canonical::query_response::enter_canonical_trait_query`.

The trait solver is invoked in rustc by using the `evaluate_goal` canonical query. This is not optimal because each call to `evaluate_goal` creates a new `chalk_engine::Forest`, hence rustc cannot use answers to intermediate goals produced by the root goal. We'll need to change that but I guess that's ok for now.

Some next steps, I think, are:
* handle region constraints: region constraints are computed but are completely ignored for now, I think we may need additional support from `chalk_engine` (as a side effect, types or trait references with outlive requirements cannot be proved well-formed)
* deactivate eager normalization in the presence of `-Z chalk` in order to leverage the lazy normalization strategy of the new-style trait solver
* add the remaining built-in impls (only `Sized` is supported currently)
* transition the compiler to using generic goals instead of predicates that still refer to named type parameters etc

I added a few very simple tests to check that the new solver has the right behavior, they won't be needed anymore once it is mature enough. Additionally it shows off that we get [implied bounds](#44491) for free.

r? @nikomatsakis
  • Loading branch information
bors committed Dec 27, 2018
2 parents bc09637 + 993d213 commit fb86d60
Show file tree
Hide file tree
Showing 48 changed files with 1,311 additions and 293 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "chalk-engine"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"chalk-macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
Expand Down Expand Up @@ -2067,7 +2067,7 @@ dependencies = [
"backtrace 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"chalk-engine 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"chalk-engine 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"fmt_macros 0.0.0",
"graphviz 0.0.0",
Expand Down Expand Up @@ -2640,7 +2640,7 @@ name = "rustc_traits"
version = "0.0.0"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"chalk-engine 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"chalk-engine 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"graphviz 0.0.0",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc 0.0.0",
Expand Down Expand Up @@ -3403,7 +3403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum cargo_metadata 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d8dfe3adeb30f7938e6c1dd5327f29235d8ada3e898aeb08c343005ec2915a2"
"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16"
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
"checksum chalk-engine 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6749eb72e7d4355d944a99f15fbaea701b978c18c5e184a025fcde942b0c9779"
"checksum chalk-engine 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17ec698a6f053a23bfbe646d9f2fde4b02abc19125595270a99e6f44ae0bdd1a"
"checksum chalk-macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "295635afd6853aa9f20baeb7f0204862440c0fe994c5a253d5f479dac41d047e"
"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878"
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ syntax_pos = { path = "../libsyntax_pos" }
backtrace = "0.3.3"
parking_lot = "0.6"
byteorder = { version = "1.1", features = ["i128"]}
chalk-engine = { version = "0.8.0", default-features=false }
chalk-engine = { version = "0.9.0", default-features=false }
rustc_fs_util = { path = "../librustc_fs_util" }
smallvec = { version = "0.6.7", features = ["union", "may_dangle"] }

Expand Down
1 change: 1 addition & 0 deletions src/librustc/dep_graph/dep_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ define_dep_nodes!( <'tcx>
[] ImpliedOutlivesBounds(CanonicalTyGoal<'tcx>),
[] DropckOutlives(CanonicalTyGoal<'tcx>),
[] EvaluateObligation(CanonicalPredicateGoal<'tcx>),
[] EvaluateGoal(traits::ChalkCanonicalGoal<'tcx>),
[] TypeOpAscribeUserType(CanonicalTypeOpAscribeUserTypeGoal<'tcx>),
[] TypeOpEq(CanonicalTypeOpEqGoal<'tcx>),
[] TypeOpSubtype(CanonicalTypeOpSubtypeGoal<'tcx>),
Expand Down
14 changes: 13 additions & 1 deletion src/librustc/ich/impls_ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,8 @@ for ty::steal::Steal<T>

impl_stable_hash_for!(struct ty::ParamEnv<'tcx> {
caller_bounds,
reveal
reveal,
def_id
});

impl_stable_hash_for!(enum traits::Reveal {
Expand Down Expand Up @@ -1194,6 +1195,10 @@ impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for traits::Goal<'tcx> {
quantifier.hash_stable(hcx, hasher);
goal.hash_stable(hcx, hasher);
},
Subtype(a, b) => {
a.hash_stable(hcx, hasher);
b.hash_stable(hcx, hasher);
}
CannotProve => { },
}
}
Expand Down Expand Up @@ -1239,3 +1244,10 @@ impl_stable_hash_for!(
clauses,
}
);

impl_stable_hash_for!(
impl<'tcx, G> for struct traits::InEnvironment<'tcx, G> {
environment,
goal,
}
);
13 changes: 11 additions & 2 deletions src/librustc/infer/canonical/canonicalizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,13 @@ impl<'cx, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for Canonicalizer<'cx, 'gcx, 'tcx>
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
match t.sty {
ty::Infer(ty::TyVar(vid)) => {
debug!("canonical: type var found with vid {:?}", vid);
match self.infcx.unwrap().probe_ty_var(vid) {
// `t` could be a float / int variable: canonicalize that instead
Ok(t) => self.fold_ty(t),
Ok(t) => {
debug!("(resolved to {:?})", t);
self.fold_ty(t)
}

// `TyVar(vid)` is unresolved, track its universe index in the canonicalized
// result
Expand Down Expand Up @@ -448,7 +452,12 @@ impl<'cx, 'gcx, 'tcx> Canonicalizer<'cx, 'gcx, 'tcx> {

// Fast path: nothing that needs to be canonicalized.
if !value.has_type_flags(needs_canonical_flags) {
let out_value = gcx.lift(value).unwrap();
let out_value = gcx.lift(value).unwrap_or_else(|| {
bug!(
"failed to lift `{:?}` (nothing to canonicalize)",
value
)
});
let canon_value = Canonical {
max_universe: ty::UniverseIndex::ROOT,
variables: List::empty(),
Expand Down
26 changes: 25 additions & 1 deletion src/librustc/infer/canonical/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,9 +420,33 @@ BraceStructLiftImpl! {
}

impl<'tcx> CanonicalVarValues<'tcx> {
fn len(&self) -> usize {
pub fn len(&self) -> usize {
self.var_values.len()
}

/// Make an identity substitution from this one: each bound var
/// is matched to the same bound var, preserving the original kinds.
/// For example, if we have:
/// `self.var_values == [Type(u32), Lifetime('a), Type(u64)]`
/// we'll return a substitution `subst` with:
/// `subst.var_values == [Type(^0), Lifetime(^1), Type(^2)]`.
pub fn make_identity<'a>(&self, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> Self {
use ty::subst::UnpackedKind;

CanonicalVarValues {
var_values: self.var_values.iter()
.zip(0..)
.map(|(kind, i)| match kind.unpack() {
UnpackedKind::Type(..) => tcx.mk_ty(
ty::Bound(ty::INNERMOST, ty::BoundVar::from_u32(i).into())
).into(),
UnpackedKind::Lifetime(..) => tcx.mk_region(
ty::ReLateBound(ty::INNERMOST, ty::BoundRegion::BrAnon(i))
).into(),
})
.collect()
}
}
}

impl<'a, 'tcx> IntoIterator for &'a CanonicalVarValues<'tcx> {
Expand Down
18 changes: 11 additions & 7 deletions src/librustc/infer/canonical/query_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use rustc_data_structures::sync::Lrc;
use std::fmt::Debug;
use syntax_pos::DUMMY_SP;
use traits::query::{Fallible, NoSolution};
use traits::{FulfillmentContext, TraitEngine};
use traits::TraitEngine;
use traits::{Obligation, ObligationCause, PredicateObligation};
use ty::fold::TypeFoldable;
use ty::subst::{Kind, UnpackedKind};
Expand All @@ -48,7 +48,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxtBuilder<'cx, 'gcx, 'tcx> {
pub fn enter_canonical_trait_query<K, R>(
&'tcx mut self,
canonical_key: &Canonical<'tcx, K>,
operation: impl FnOnce(&InferCtxt<'_, 'gcx, 'tcx>, &mut FulfillmentContext<'tcx>, K)
operation: impl FnOnce(&InferCtxt<'_, 'gcx, 'tcx>, &mut dyn TraitEngine<'tcx>, K)
-> Fallible<R>,
) -> Fallible<CanonicalizedQueryResponse<'gcx, R>>
where
Expand All @@ -59,9 +59,13 @@ impl<'cx, 'gcx, 'tcx> InferCtxtBuilder<'cx, 'gcx, 'tcx> {
DUMMY_SP,
canonical_key,
|ref infcx, key, canonical_inference_vars| {
let fulfill_cx = &mut FulfillmentContext::new();
let value = operation(infcx, fulfill_cx, key)?;
infcx.make_canonicalized_query_response(canonical_inference_vars, value, fulfill_cx)
let mut fulfill_cx = TraitEngine::new(infcx.tcx);
let value = operation(infcx, &mut *fulfill_cx, key)?;
infcx.make_canonicalized_query_response(
canonical_inference_vars,
value,
&mut *fulfill_cx
)
},
)
}
Expand Down Expand Up @@ -91,7 +95,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
&self,
inference_vars: CanonicalVarValues<'tcx>,
answer: T,
fulfill_cx: &mut FulfillmentContext<'tcx>,
fulfill_cx: &mut dyn TraitEngine<'tcx>,
) -> Fallible<CanonicalizedQueryResponse<'gcx, T>>
where
T: Debug + Lift<'gcx> + TypeFoldable<'tcx>,
Expand Down Expand Up @@ -138,7 +142,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
&self,
inference_vars: CanonicalVarValues<'tcx>,
answer: T,
fulfill_cx: &mut FulfillmentContext<'tcx>,
fulfill_cx: &mut dyn TraitEngine<'tcx>,
) -> Result<QueryResponse<'tcx, T>, NoSolution>
where
T: Debug + TypeFoldable<'tcx> + Lift<'gcx>,
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ impl<'tcx, T> InferOk<'tcx, T> {
pub fn into_value_registering_obligations(
self,
infcx: &InferCtxt<'_, '_, 'tcx>,
fulfill_cx: &mut impl TraitEngine<'tcx>,
fulfill_cx: &mut dyn TraitEngine<'tcx>,
) -> T {
let InferOk { value, obligations } = self;
for obligation in obligations {
Expand Down
7 changes: 6 additions & 1 deletion src/librustc/traits/auto_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,12 +388,17 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
computed_preds.extend(user_computed_preds.iter().cloned());
let normalized_preds =
elaborate_predicates(tcx, computed_preds.clone().into_iter().collect());
new_env = ty::ParamEnv::new(tcx.mk_predicates(normalized_preds), param_env.reveal);
new_env = ty::ParamEnv::new(
tcx.mk_predicates(normalized_preds),
param_env.reveal,
None
);
}

let final_user_env = ty::ParamEnv::new(
tcx.mk_predicates(user_computed_preds.into_iter()),
user_env.reveal,
None
);
debug!(
"evaluate_nested_obligations(ty_did={:?}, trait_did={:?}): succeeded with '{:?}' \
Expand Down
165 changes: 165 additions & 0 deletions src/librustc/traits/chalk_fulfill.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use traits::{
Environment,
InEnvironment,
TraitEngine,
ObligationCause,
PredicateObligation,
FulfillmentError,
FulfillmentErrorCode,
SelectionError,
};
use traits::query::NoSolution;
use infer::InferCtxt;
use infer::canonical::{Canonical, OriginalQueryValues};
use ty::{self, Ty};
use rustc_data_structures::fx::FxHashSet;

pub type CanonicalGoal<'tcx> = Canonical<'tcx, InEnvironment<'tcx, ty::Predicate<'tcx>>>;

pub struct FulfillmentContext<'tcx> {
obligations: FxHashSet<InEnvironment<'tcx, PredicateObligation<'tcx>>>,
}

impl FulfillmentContext<'tcx> {
crate fn new() -> Self {
FulfillmentContext {
obligations: FxHashSet::default(),
}
}
}

fn in_environment(
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
obligation: PredicateObligation<'tcx>
) -> InEnvironment<'tcx, PredicateObligation<'tcx>> {
assert!(!infcx.is_in_snapshot());
let obligation = infcx.resolve_type_vars_if_possible(&obligation);

let environment = match obligation.param_env.def_id {
Some(def_id) => infcx.tcx.environment(def_id),
None if obligation.param_env.caller_bounds.is_empty() => Environment {
clauses: ty::List::empty(),
},
_ => bug!("non-empty `ParamEnv` with no def-id"),
};

InEnvironment {
environment,
goal: obligation,
}
}

impl TraitEngine<'tcx> for FulfillmentContext<'tcx> {
fn normalize_projection_type(
&mut self,
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
_param_env: ty::ParamEnv<'tcx>,
projection_ty: ty::ProjectionTy<'tcx>,
_cause: ObligationCause<'tcx>,
) -> Ty<'tcx> {
infcx.tcx.mk_ty(ty::Projection(projection_ty))
}

fn register_predicate_obligation(
&mut self,
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
obligation: PredicateObligation<'tcx>,
) {
self.obligations.insert(in_environment(infcx, obligation));
}

fn select_all_or_error(
&mut self,
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
) -> Result<(), Vec<FulfillmentError<'tcx>>> {
self.select_where_possible(infcx)?;

if self.obligations.is_empty() {
Ok(())
} else {
let errors = self.obligations.iter()
.map(|obligation| FulfillmentError {
obligation: obligation.goal.clone(),
code: FulfillmentErrorCode::CodeAmbiguity,
})
.collect();
Err(errors)
}
}

fn select_where_possible(
&mut self,
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
) -> Result<(), Vec<FulfillmentError<'tcx>>> {
let mut errors = Vec::new();
let mut next_round = FxHashSet::default();
let mut making_progress;

loop {
making_progress = false;

// We iterate over all obligations, and record if we are able
// to unambiguously prove at least one obligation.
for obligation in self.obligations.drain() {
let mut orig_values = OriginalQueryValues::default();
let canonical_goal = infcx.canonicalize_query(&InEnvironment {
environment: obligation.environment,
goal: obligation.goal.predicate,
}, &mut orig_values);

match infcx.tcx.global_tcx().evaluate_goal(canonical_goal) {
Ok(response) => {
if response.is_proven() {
making_progress = true;

match infcx.instantiate_query_response_and_region_obligations(
&obligation.goal.cause,
obligation.goal.param_env,
&orig_values,
&response
) {
Ok(infer_ok) => next_round.extend(
infer_ok.obligations
.into_iter()
.map(|obligation| in_environment(infcx, obligation))
),

Err(_err) => errors.push(FulfillmentError {
obligation: obligation.goal,
code: FulfillmentErrorCode::CodeSelectionError(
SelectionError::Unimplemented
),
}),
}
} else {
// Ambiguous: retry at next round.
next_round.insert(obligation);
}
}

Err(NoSolution) => errors.push(FulfillmentError {
obligation: obligation.goal,
code: FulfillmentErrorCode::CodeSelectionError(
SelectionError::Unimplemented
),
})
}
}
next_round = std::mem::replace(&mut self.obligations, next_round);

if !making_progress {
break;
}
}

if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}

fn pending_obligations(&self) -> Vec<PredicateObligation<'tcx>> {
self.obligations.iter().map(|obligation| obligation.goal.clone()).collect()
}
}
Loading

0 comments on commit fb86d60

Please sign in to comment.