Skip to content

Commit

Permalink
Structurally normalize in the new solver
Browse files Browse the repository at this point in the history
  • Loading branch information
compiler-errors committed May 22, 2023
1 parent eaf10dc commit 4cfafb2
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 24 deletions.
76 changes: 54 additions & 22 deletions compiler/rustc_hir_analysis/src/autoderef.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::errors::AutoDerefReachedRecursionLimit;
use crate::traits::query::evaluate_obligation::InferCtxtExt;
use crate::traits::NormalizeExt;
use crate::traits::{self, TraitEngine, TraitEngineExt};
use rustc_infer::infer::InferCtxt;
use rustc_middle::ty::TypeVisitableExt;
Expand All @@ -9,6 +8,7 @@ use rustc_session::Limit;
use rustc_span::def_id::LocalDefId;
use rustc_span::def_id::LOCAL_CRATE;
use rustc_span::Span;
use rustc_trait_selection::traits::StructurallyNormalizeExt;

#[derive(Copy, Clone, Debug)]
pub enum AutoderefKind {
Expand Down Expand Up @@ -66,14 +66,27 @@ impl<'a, 'tcx> Iterator for Autoderef<'a, 'tcx> {
}

// Otherwise, deref if type is derefable:
let (kind, new_ty) =
if let Some(mt) = self.state.cur_ty.builtin_deref(self.include_raw_pointers) {
(AutoderefKind::Builtin, mt.ty)
} else if let Some(ty) = self.overloaded_deref_ty(self.state.cur_ty) {
(AutoderefKind::Overloaded, ty)
let (kind, new_ty) = if let Some(ty::TypeAndMut { ty, .. }) =
self.state.cur_ty.builtin_deref(self.include_raw_pointers)
{
debug_assert_eq!(ty, self.infcx.resolve_vars_if_possible(ty));
// NOTE: we may still need to normalize the built-in deref in case
// we have some type like `&<Ty as Trait>::Assoc`, since users of
// autoderef expect this type to have been structurally normalized.
if self.infcx.tcx.trait_solver_next()
&& let ty::Alias(ty::Projection, _) = ty.kind()
{
let (normalized_ty, obligations) = self.structurally_normalize(ty)?;
self.state.obligations.extend(obligations);
(AutoderefKind::Builtin, normalized_ty)
} else {
return None;
};
(AutoderefKind::Builtin, ty)
}
} else if let Some(ty) = self.overloaded_deref_ty(self.state.cur_ty) {
(AutoderefKind::Overloaded, ty)
} else {
return None;
};

if new_ty.references_error() {
return None;
Expand Down Expand Up @@ -119,14 +132,11 @@ impl<'a, 'tcx> Autoderef<'a, 'tcx> {

fn overloaded_deref_ty(&mut self, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
debug!("overloaded_deref_ty({:?})", ty);

let tcx = self.infcx.tcx;

// <ty as Deref>
let trait_ref = ty::TraitRef::new(tcx, tcx.lang_items().deref_trait()?, [ty]);

let cause = traits::ObligationCause::misc(self.span, self.body_id);

let obligation = traits::Obligation::new(
tcx,
cause.clone(),
Expand All @@ -138,26 +148,48 @@ impl<'a, 'tcx> Autoderef<'a, 'tcx> {
return None;
}

let normalized_ty = self
let (normalized_ty, obligations) =
self.structurally_normalize(tcx.mk_projection(tcx.lang_items().deref_target()?, [ty]))?;
debug!("overloaded_deref_ty({:?}) = ({:?}, {:?})", ty, normalized_ty, obligations);
self.state.obligations.extend(obligations);

Some(self.infcx.resolve_vars_if_possible(normalized_ty))
}

#[instrument(level = "debug", skip(self), ret)]
pub fn structurally_normalize(
&self,
ty: Ty<'tcx>,
) -> Option<(Ty<'tcx>, Vec<traits::PredicateObligation<'tcx>>)> {
let tcx = self.infcx.tcx;
let mut fulfill_cx = <dyn TraitEngine<'tcx>>::new_in_snapshot(tcx);

let cause = traits::ObligationCause::misc(self.span, self.body_id);
let normalized_ty = match self
.infcx
.at(&cause, self.param_env)
.normalize(tcx.mk_projection(tcx.lang_items().deref_target()?, trait_ref.substs));
let mut fulfillcx = <dyn TraitEngine<'tcx>>::new_in_snapshot(tcx);
let normalized_ty =
normalized_ty.into_value_registering_obligations(self.infcx, &mut *fulfillcx);
let errors = fulfillcx.select_where_possible(&self.infcx);
.structurally_normalize(ty, &mut *fulfill_cx)
{
Ok(normalized_ty) => normalized_ty,
Err(errors) => {
// This shouldn't happen, except for evaluate/fulfill mismatches,
// but that's not a reason for an ICE (`predicate_may_hold` is conservative
// by design).
debug!(?errors, "encountered errors while fulfilling");
return None;
}
};

let errors = fulfill_cx.select_where_possible(&self.infcx);
if !errors.is_empty() {
// This shouldn't happen, except for evaluate/fulfill mismatches,
// but that's not a reason for an ICE (`predicate_may_hold` is conservative
// by design).
debug!("overloaded_deref_ty: encountered errors {:?} while fulfilling", errors);
debug!(?errors, "encountered errors while fulfilling");
return None;
}
let obligations = fulfillcx.pending_obligations();
debug!("overloaded_deref_ty({:?}) = ({:?}, {:?})", ty, normalized_ty, obligations);
self.state.obligations.extend(obligations);

Some(self.infcx.resolve_vars_if_possible(normalized_ty))
Some((normalized_ty, fulfill_cx.pending_obligations()))
}

/// Returns the final type we ended up with, which may be an inference
Expand Down
29 changes: 27 additions & 2 deletions compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ use rustc_span::symbol::{kw, sym, Ident};
use rustc_span::Span;
use rustc_target::abi::FieldIdx;
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _;
use rustc_trait_selection::traits::{self, NormalizeExt, ObligationCauseCode, ObligationCtxt};
use rustc_trait_selection::traits::{
self, NormalizeExt, ObligationCauseCode, ObligationCtxt, StructurallyNormalizeExt,
};

use std::collections::hash_map::Entry;
use std::slice;
Expand Down Expand Up @@ -1460,10 +1462,33 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}

/// Resolves `typ` by a single level if `typ` is a type variable.
///
/// When the new solver is enabled, this will also attempt to normalize
/// the type if it's a projection (note that it will not deeply normalize
/// projections within the type, just the outermost layer of the type).
///
/// If no resolution is possible, then an error is reported.
/// Numeric inference variables may be left unresolved.
pub fn structurally_resolved_type(&self, sp: Span, ty: Ty<'tcx>) -> Ty<'tcx> {
let ty = self.resolve_vars_with_obligations(ty);
let mut ty = self.resolve_vars_with_obligations(ty);

if self.tcx.trait_solver_next()
&& let ty::Alias(ty::Projection, _) = ty.kind()
{
match self
.at(&self.misc(sp), self.param_env)
.structurally_normalize(ty, &mut **self.fulfillment_cx.borrow_mut())
{
Ok(normalized_ty) => {
ty = normalized_ty;
},
Err(errors) => {
let guar = self.err_ctxt().report_fulfillment_errors(&errors);
return self.tcx.ty_error(guar);
}
}
}

if !ty.is_ty_var() {
ty
} else {
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_trait_selection/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod query;
mod select;
mod specialize;
mod structural_match;
mod structural_normalize;
mod util;
mod vtable;
pub mod wf;
Expand Down Expand Up @@ -62,6 +63,7 @@ pub use self::specialize::{
pub use self::structural_match::{
search_for_adt_const_param_violation, search_for_structural_match_violation,
};
pub use self::structural_normalize::StructurallyNormalizeExt;
pub use self::util::elaborate;
pub use self::util::{expand_trait_aliases, TraitAliasExpander};
pub use self::util::{get_vtable_index_of_object_method, impl_item_is_final, upcast_choices};
Expand Down
55 changes: 55 additions & 0 deletions compiler/rustc_trait_selection/src/traits/structural_normalize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use rustc_infer::infer::at::At;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::traits::{FulfillmentError, TraitEngine};
use rustc_middle::ty::{self, Ty};

use crate::traits::{query::evaluate_obligation::InferCtxtExt, NormalizeExt, Obligation};

pub trait StructurallyNormalizeExt<'tcx> {
fn structurally_normalize(
&self,
ty: Ty<'tcx>,
fulfill_cx: &mut dyn TraitEngine<'tcx>,
) -> Result<Ty<'tcx>, Vec<FulfillmentError<'tcx>>>;
}

impl<'tcx> StructurallyNormalizeExt<'tcx> for At<'_, 'tcx> {
fn structurally_normalize(
&self,
mut ty: Ty<'tcx>,
fulfill_cx: &mut dyn TraitEngine<'tcx>,
) -> Result<Ty<'tcx>, Vec<FulfillmentError<'tcx>>> {
assert!(!ty.is_ty_var(), "should have resolved vars before calling");

if self.infcx.tcx.trait_solver_next() {
while let ty::Alias(ty::Projection, projection_ty) = *ty.kind() {
let new_infer_ty = self.infcx.next_ty_var(TypeVariableOrigin {
kind: TypeVariableOriginKind::NormalizeProjectionType,
span: self.cause.span,
});
let obligation = Obligation::new(
self.infcx.tcx,
self.cause.clone(),
self.param_env,
ty::Binder::dummy(ty::ProjectionPredicate {
projection_ty,
term: new_infer_ty.into(),
}),
);
if self.infcx.predicate_may_hold(&obligation) {
fulfill_cx.register_predicate_obligation(self.infcx, obligation);
let errors = fulfill_cx.select_where_possible(self.infcx);
if !errors.is_empty() {
return Err(errors);
}
ty = self.infcx.resolve_vars_if_possible(new_infer_ty);
} else {
break;
}
}
Ok(ty)
} else {
Ok(self.normalize(ty).into_value_registering_obligations(self.infcx, fulfill_cx))
}
}
}
25 changes: 25 additions & 0 deletions tests/ui/traits/new-solver/normalize-rcvr-for-inherent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// compile-flags: -Ztrait-solver=next
// check-pass

// Verify that we can assemble inherent impl candidates on a possibly
// unnormalized self type.

trait Foo {
type Assoc;
}
impl Foo for i32 {
type Assoc = Bar;
}

struct Bar;
impl Bar {
fn method(&self) {}
}

fn build<T: Foo>(_: T) -> T::Assoc {
todo!()
}

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

#[derive(Default)]
struct Foo {
x: i32,
}

fn main() {
let mut xs = <[Foo; 1]>::default();
xs[0].x = 1;
(&mut xs[0]).x = 2;
}

0 comments on commit 4cfafb2

Please sign in to comment.