Skip to content

Commit

Permalink
Rollup merge of #107061 - compiler-errors:new-solver-new-candidates-3…
Browse files Browse the repository at this point in the history
…, r=lcnr

Implement some more new solver candidates and fix some bugs

First, fix some bugs:

1. `IndexVec::drain_enumerated(a..b)` does not give us an iterator of index keys + items enumerated from `a..b`, but from `0..(b-a)`... That caused a bug. See first commit for the fix.
2. Implement the `_: Trait` ambiguity hack. I put it in assemble, let me know if it should live elsewhere. This is important, since we otherwise consider `_: Sized` to have no solutions, and nothing passes!
3. Swap `Ambiguity` and `Unimplemented` cases for the new solver. Sorry for accidentally swapping them 😄
4. Check GATs' own predicates during projection confirmation.

Then implement a few builtin traits:

5. Implement `PointerSized`. Pretty independent.
6. Implement `Fn` family of traits for fnptr, fndef, and closures. Closures are currently broken because `FulfillCtxt::relationships` is intentionally left unimplemented. See comment in the test.

r? ```@lcnr```
  • Loading branch information
compiler-errors authored Jan 21, 2023
2 parents d26c88c + ed6aebb commit 3452104
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 21 deletions.
7 changes: 6 additions & 1 deletion compiler/rustc_index/src/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,12 @@ impl<I: Idx, T> IndexVec<I, T> {
&'a mut self,
range: R,
) -> impl Iterator<Item = (I, T)> + 'a {
self.raw.drain(range).enumerate().map(|(n, t)| (I::new(n), t))
let begin = match range.start_bound() {
std::ops::Bound::Included(i) => *i,
std::ops::Bound::Excluded(i) => i.checked_add(1).unwrap(),
std::ops::Bound::Unbounded => 0,
};
self.raw.drain(range).enumerate().map(move |(n, t)| (I::new(begin + n), t))
}

#[inline]
Expand Down
41 changes: 39 additions & 2 deletions compiler/rustc_trait_selection/src/solve/assembly.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Code shared by trait and projection goals for candidate assembly.

use super::infcx_ext::InferCtxtExt;
use super::{CanonicalResponse, EvalCtxt, Goal, QueryResult};
use super::{CanonicalResponse, Certainty, EvalCtxt, Goal, MaybeCause, QueryResult};
use rustc_hir::def_id::DefId;
use rustc_infer::traits::query::NoSolution;
use rustc_infer::traits::util::elaborate_predicates;
Expand Down Expand Up @@ -79,7 +79,7 @@ pub(super) enum CandidateSource {
AliasBound(usize),
}

pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy {
pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy + Eq {
fn self_ty(self) -> Ty<'tcx>;

fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self;
Expand Down Expand Up @@ -117,13 +117,43 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy {
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx>;

fn consider_builtin_pointer_sized_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx>;

fn consider_builtin_fn_trait_candidates(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
kind: ty::ClosureKind,
) -> QueryResult<'tcx>;

fn consider_builtin_tuple_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx>;
}

impl<'tcx> EvalCtxt<'_, 'tcx> {
pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<'tcx>>(
&mut self,
goal: Goal<'tcx, G>,
) -> Vec<Candidate<'tcx>> {
debug_assert_eq!(goal, self.infcx.resolve_vars_if_possible(goal));

// HACK: `_: Trait` is ambiguous, because it may be satisfied via a builtin rule,
// object bound, alias bound, etc. We are unable to determine this until we can at
// least structually resolve the type one layer.
if goal.predicate.self_ty().is_ty_var() {
return vec![Candidate {
source: CandidateSource::BuiltinImpl,
result: self
.make_canonical_response(Certainty::Maybe(MaybeCause::Ambiguity))
.unwrap(),
}];
}

let mut candidates = Vec::new();

self.assemble_candidates_after_normalizing_self_ty(goal, &mut candidates);
Expand Down Expand Up @@ -169,6 +199,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
Ok((_, certainty)) => certainty,
Err(NoSolution) => return,
};
let normalized_ty = self.infcx.resolve_vars_if_possible(normalized_ty);

// NOTE: Alternatively we could call `evaluate_goal` here and only have a `Normalized` candidate.
// This doesn't work as long as we use `CandidateSource` in winnowing.
Expand Down Expand Up @@ -224,6 +255,12 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|| lang_items.clone_trait() == Some(trait_def_id)
{
G::consider_builtin_copy_clone_candidate(self, goal)
} else if lang_items.pointer_sized() == Some(trait_def_id) {
G::consider_builtin_pointer_sized_candidate(self, goal)
} else if let Some(kind) = self.tcx().fn_trait_kind_from_def_id(trait_def_id) {
G::consider_builtin_fn_trait_candidates(self, goal, kind)
} else if lang_items.tuple_trait() == Some(trait_def_id) {
G::consider_builtin_tuple_candidate(self, goal)
} else {
Err(NoSolution)
};
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_trait_selection/src/solve/fulfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
.drain(..)
.map(|obligation| FulfillmentError {
obligation: obligation.clone(),
code: FulfillmentErrorCode::CodeSelectionError(SelectionError::Unimplemented),
code: FulfillmentErrorCode::CodeAmbiguity,
root_obligation: obligation,
})
.collect()
Expand All @@ -75,7 +75,9 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
Err(NoSolution) => {
errors.push(FulfillmentError {
obligation: obligation.clone(),
code: FulfillmentErrorCode::CodeAmbiguity,
code: FulfillmentErrorCode::CodeSelectionError(
SelectionError::Unimplemented,
),
root_obligation: obligation,
});
continue;
Expand Down
43 changes: 42 additions & 1 deletion compiler/rustc_trait_selection/src/solve/project_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::traits::{specialization_graph, translate_substs};

use super::assembly::{self, Candidate, CandidateSource};
use super::infcx_ext::InferCtxtExt;
use super::trait_goals::structural_traits;
use super::{Certainty, EvalCtxt, Goal, MaybeCause, QueryResult};
use rustc_errors::ErrorGuaranteed;
use rustc_hir::def::DefKind;
Expand All @@ -11,9 +12,9 @@ use rustc_infer::traits::query::NoSolution;
use rustc_infer::traits::specialization_graph::LeafDef;
use rustc_infer::traits::Reveal;
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
use rustc_middle::ty::TypeVisitable;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::ty::{ProjectionPredicate, TypeSuperVisitable, TypeVisitor};
use rustc_middle::ty::{ToPredicate, TypeVisitable};
use rustc_span::DUMMY_SP;
use std::iter;
use std::ops::ControlFlow;
Expand Down Expand Up @@ -351,6 +352,46 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
) -> QueryResult<'tcx> {
bug!("`Copy`/`Clone` does not have an associated type: {:?}", goal);
}

fn consider_builtin_pointer_sized_candidate(
_ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx> {
bug!("`PointerSized` does not have an associated type: {:?}", goal);
}

fn consider_builtin_fn_trait_candidates(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
goal_kind: ty::ClosureKind,
) -> QueryResult<'tcx> {
if let Some(tupled_inputs_and_output) =
structural_traits::extract_tupled_inputs_and_output_from_callable(
ecx.tcx(),
goal.predicate.self_ty(),
goal_kind,
)?
{
let pred = tupled_inputs_and_output
.map_bound(|(inputs, output)| ty::ProjectionPredicate {
projection_ty: ecx
.tcx()
.mk_alias_ty(goal.predicate.def_id(), [goal.predicate.self_ty(), inputs]),
term: output.into(),
})
.to_predicate(ecx.tcx());
Self::consider_assumption(ecx, goal, pred)
} else {
ecx.make_canonical_response(Certainty::Maybe(MaybeCause::Ambiguity))
}
}

fn consider_builtin_tuple_candidate(
_ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx> {
bug!("`Tuple` does not have an associated type: {:?}", goal);
}
}

/// This behavior is also implemented in `rustc_ty_utils` and in the old `project` code.
Expand Down
66 changes: 62 additions & 4 deletions compiler/rustc_trait_selection/src/solve/trait_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ use std::iter;

use super::assembly::{self, Candidate, CandidateSource};
use super::infcx_ext::InferCtxtExt;
use super::{EvalCtxt, Goal, QueryResult};
use super::{Certainty, EvalCtxt, Goal, MaybeCause, QueryResult};
use rustc_hir::def_id::DefId;
use rustc_infer::infer::InferCtxt;
use rustc_infer::traits::query::NoSolution;
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
use rustc_middle::ty::TraitPredicate;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt};
use rustc_middle::ty::{TraitPredicate, TypeVisitable};
use rustc_span::DUMMY_SP;

mod structural_traits;
pub mod structural_traits;

impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
fn self_ty(self) -> Ty<'tcx> {
Expand Down Expand Up @@ -127,6 +127,64 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
structural_traits::instantiate_constituent_tys_for_copy_clone_trait,
)
}

fn consider_builtin_pointer_sized_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx> {
if goal.predicate.self_ty().has_non_region_infer() {
return ecx.make_canonical_response(Certainty::Maybe(MaybeCause::Ambiguity));
}

let tcx = ecx.tcx();
let self_ty = tcx.erase_regions(goal.predicate.self_ty());

if let Ok(layout) = tcx.layout_of(goal.param_env.and(self_ty))
&& let usize_layout = tcx.layout_of(ty::ParamEnv::empty().and(tcx.types.usize)).unwrap().layout
&& layout.layout.size() == usize_layout.size()
&& layout.layout.align().abi == usize_layout.align().abi
{
// FIXME: We could make this faster by making a no-constraints response
ecx.make_canonical_response(Certainty::Yes)
} else {
Err(NoSolution)
}
}

fn consider_builtin_fn_trait_candidates(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
goal_kind: ty::ClosureKind,
) -> QueryResult<'tcx> {
if let Some(tupled_inputs_and_output) =
structural_traits::extract_tupled_inputs_and_output_from_callable(
ecx.tcx(),
goal.predicate.self_ty(),
goal_kind,
)?
{
let pred = tupled_inputs_and_output
.map_bound(|(inputs, _)| {
ecx.tcx()
.mk_trait_ref(goal.predicate.def_id(), [goal.predicate.self_ty(), inputs])
})
.to_predicate(ecx.tcx());
Self::consider_assumption(ecx, goal, pred)
} else {
ecx.make_canonical_response(Certainty::Maybe(MaybeCause::Ambiguity))
}
}

fn consider_builtin_tuple_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx> {
if let ty::Tuple(..) = goal.predicate.self_ty().kind() {
ecx.make_canonical_response(Certainty::Yes)
} else {
Err(NoSolution)
}
}
}

impl<'tcx> EvalCtxt<'_, 'tcx> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use rustc_hir::{Movability, Mutability};
use rustc_infer::{infer::InferCtxt, traits::query::NoSolution};
use rustc_middle::ty::{self, Ty};
use rustc_middle::ty::{self, Ty, TyCtxt};

// Calculates the constituent types of a type for `auto trait` purposes.
//
Expand Down Expand Up @@ -30,10 +30,7 @@ pub(super) fn instantiate_constituent_tys_for_auto_trait<'tcx>(
| ty::Foreign(..)
| ty::Alias(ty::Projection, ..)
| ty::Bound(..)
| ty::Infer(ty::TyVar(_)) => {
// FIXME: Do we need to mark anything as ambiguous here? Yeah?
Err(NoSolution)
}
| ty::Infer(ty::TyVar(_)) => Err(NoSolution),

ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => bug!(),

Expand Down Expand Up @@ -101,9 +98,8 @@ pub(super) fn instantiate_constituent_tys_for_sized_trait<'tcx>(
| ty::Dynamic(..)
| ty::Foreign(..)
| ty::Alias(..)
| ty::Param(_) => Err(NoSolution),

ty::Infer(ty::TyVar(_)) => bug!("FIXME: ambiguous"),
| ty::Param(_)
| ty::Infer(ty::TyVar(_)) => Err(NoSolution),

ty::Placeholder(..)
| ty::Bound(..)
Expand Down Expand Up @@ -151,9 +147,8 @@ pub(super) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>(
| ty::Ref(_, _, Mutability::Mut)
| ty::Adt(_, _)
| ty::Alias(_, _)
| ty::Param(_) => Err(NoSolution),

ty::Infer(ty::TyVar(_)) => bug!("FIXME: ambiguous"),
| ty::Param(_)
| ty::Infer(ty::TyVar(_)) => Err(NoSolution),

ty::Placeholder(..)
| ty::Bound(..)
Expand All @@ -177,3 +172,52 @@ pub(super) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>(
}
}
}

pub(crate) fn extract_tupled_inputs_and_output_from_callable<'tcx>(
tcx: TyCtxt<'tcx>,
self_ty: Ty<'tcx>,
goal_kind: ty::ClosureKind,
) -> Result<Option<ty::Binder<'tcx, (Ty<'tcx>, Ty<'tcx>)>>, NoSolution> {
match *self_ty.kind() {
ty::FnDef(def_id, substs) => Ok(Some(
tcx.bound_fn_sig(def_id)
.subst(tcx, substs)
.map_bound(|sig| (tcx.mk_tup(sig.inputs().iter()), sig.output())),
)),
ty::FnPtr(sig) => {
Ok(Some(sig.map_bound(|sig| (tcx.mk_tup(sig.inputs().iter()), sig.output()))))
}
ty::Closure(_, substs) => {
let closure_substs = substs.as_closure();
match closure_substs.kind_ty().to_opt_closure_kind() {
Some(closure_kind) if closure_kind.extends(goal_kind) => {}
None => return Ok(None),
_ => return Err(NoSolution),
}
Ok(Some(closure_substs.sig().map_bound(|sig| (sig.inputs()[0], sig.output()))))
}
ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Adt(_, _)
| ty::Foreign(_)
| ty::Str
| ty::Array(_, _)
| ty::Slice(_)
| ty::RawPtr(_)
| ty::Ref(_, _, _)
| ty::Dynamic(_, _, _)
| ty::Generator(_, _, _)
| ty::GeneratorWitness(_)
| ty::Never
| ty::Tuple(_)
| ty::Alias(_, _)
| ty::Param(_)
| ty::Placeholder(_)
| ty::Bound(_, _)
| ty::Infer(_)
| ty::Error(_) => Err(NoSolution),
}
}
15 changes: 15 additions & 0 deletions tests/ui/traits/new-solver/fn-trait-closure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// compile-flags: -Ztrait-solver=next
// known-bug: unknown
// failure-status: 101
// dont-check-compiler-stderr

// This test will fail until we fix `FulfillmentCtxt::relationships`. That's
// because we create a type variable for closure upvar types, which is not
// constrained until after we try to do fallback on diverging type variables.
// Thus, we will call that function, which is unimplemented.

fn require_fn(_: impl Fn() -> i32) {}

fn main() {
require_fn(|| -> i32 { 1i32 });
}
13 changes: 13 additions & 0 deletions tests/ui/traits/new-solver/fn-trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// compile-flags: -Ztrait-solver=next
// check-pass

fn require_fn(_: impl Fn() -> i32) {}

fn f() -> i32 {
1i32
}

fn main() {
require_fn(f);
require_fn(f as fn() -> i32);
}
Loading

0 comments on commit 3452104

Please sign in to comment.