spec. graph: track defining and finalizing impls

This commit is contained in:
Jonas Schievink 2020-03-29 00:57:49 +01:00
parent a1e7495a41
commit 49ba323c8d
8 changed files with 93 additions and 63 deletions

View File

@ -6,6 +6,7 @@ pub mod query;
pub mod select;
pub mod specialization_graph;
mod structural_impls;
pub mod util;
use crate::mir::interpret::ErrorHandled;
use crate::ty::subst::SubstsRef;

View File

@ -154,9 +154,45 @@ impl Iterator for Ancestors<'_> {
}
}
pub struct NodeItem {
pub node: Node,
/// Information about the most specialized definition of an associated item.
pub struct LeafDef {
/// The associated item described by this `LeafDef`.
pub item: ty::AssocItem,
/// The node in the specialization graph containing the definition of `item`.
pub defining_node: Node,
/// The "top-most" (ie. least specialized) specialization graph node that finalized the
/// definition of `item`.
///
/// Example:
///
/// ```
/// trait Tr {
/// fn assoc(&self);
/// }
///
/// impl<T> Tr for T {
/// default fn assoc(&self) {}
/// }
///
/// impl Tr for u8 {}
/// ```
///
/// If we start the leaf definition search at `impl Tr for u8`, that impl will be the
/// `finalizing_node`, while `defining_node` will be the generic impl.
///
/// If the leaf definition search is started at the generic impl, `finalizing_node` will be
/// `None`, since the most specialized impl we found still allows overriding the method
/// (doesn't finalize it).
pub finalizing_node: Option<Node>,
}
impl LeafDef {
/// Returns whether this definition is known to not be further specializable.
pub fn is_final(&self) -> bool {
self.finalizing_node.is_some()
}
}
impl<'tcx> Ancestors<'tcx> {
@ -167,11 +203,27 @@ impl<'tcx> Ancestors<'tcx> {
tcx: TyCtxt<'tcx>,
trait_item_name: Ident,
trait_item_kind: ty::AssocKind,
) -> Option<NodeItem> {
) -> Option<LeafDef> {
let trait_def_id = self.trait_def_id;
let mut finalizing_node = None;
self.find_map(|node| {
node.item(tcx, trait_item_name, trait_item_kind, trait_def_id)
.map(|item| NodeItem { node, item })
if let Some(item) = node.item(tcx, trait_item_name, trait_item_kind, trait_def_id) {
if finalizing_node.is_none() {
let is_specializable = item.defaultness.is_default()
|| super::util::impl_is_default(tcx, node.def_id());
if !is_specializable {
finalizing_node = Some(node);
}
}
Some(LeafDef { item, defining_node: node, finalizing_node })
} else {
// Item not mentioned. This "finalizes" any defaulted item provided by an ancestor.
finalizing_node = Some(node);
None
}
})
}
}

View File

@ -0,0 +1,17 @@
use crate::ty::TyCtxt;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
pub fn impl_is_default(tcx: TyCtxt<'_>, node_item_def_id: DefId) -> bool {
match tcx.hir().as_local_hir_id(node_item_def_id) {
Some(hir_id) => {
let item = tcx.hir().expect_item(hir_id);
if let hir::ItemKind::Impl { defaultness, .. } = item.kind {
defaultness.is_default()
} else {
false
}
}
None => tcx.impl_defaultness(node_item_def_id).is_default(),
}
}

View File

@ -64,8 +64,7 @@ pub use self::structural_match::NonStructuralMatchTy;
pub use self::util::{elaborate_predicates, elaborate_trait_ref, elaborate_trait_refs};
pub use self::util::{expand_trait_aliases, TraitAliasExpander};
pub use self::util::{
get_vtable_index_of_object_method, impl_is_default, impl_item_is_final,
predicate_for_trait_def, upcast_choices,
get_vtable_index_of_object_method, impl_item_is_final, predicate_for_trait_def, upcast_choices,
};
pub use self::util::{
supertrait_def_ids, supertraits, transitive_bounds, SupertraitDefIds, Supertraits,

View File

@ -1015,49 +1015,21 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
assoc_ty_def(selcx, impl_data.impl_def_id, obligation.predicate.item_def_id)
.map_err(|ErrorReported| ())?;
let is_default = if node_item.node.is_from_trait() {
// If true, the impl inherited a `type Foo = Bar`
// given in the trait, which is implicitly default.
// Otherwise, the impl did not specify `type` and
// neither did the trait:
//
// ```rust
// trait Foo { type T; }
// impl Foo for Bar { }
// ```
//
// This is an error, but it will be
// reported in `check_impl_items_against_trait`.
// We accept it here but will flag it as
// an error when we confirm the candidate
// (which will ultimately lead to `normalize_to_error`
// being invoked).
false
} else {
// If we're looking at a trait *impl*, the item is
// specializable if the impl or the item are marked
// `default`.
node_item.item.defaultness.is_default()
|| super::util::impl_is_default(selcx.tcx(), node_item.node.def_id())
};
match is_default {
if node_item.is_final() {
// Non-specializable items are always projectable
false => true,
true
} else {
// Only reveal a specializable default if we're past type-checking
// and the obligation is monomorphic, otherwise passes such as
// transmute checking and polymorphic MIR optimizations could
// get a result which isn't correct for all monomorphizations.
true if obligation.param_env.reveal == Reveal::All => {
if obligation.param_env.reveal == Reveal::All {
// NOTE(eddyb) inference variables can resolve to parameters, so
// assume `poly_trait_ref` isn't monomorphic, if it contains any.
let poly_trait_ref =
selcx.infcx().resolve_vars_if_possible(&poly_trait_ref);
!poly_trait_ref.needs_infer() && !poly_trait_ref.needs_subst()
}
true => {
} else {
debug!(
"assemble_candidates_from_impls: not eligible due to default: \
assoc_ty={} predicate={}",
@ -1422,7 +1394,8 @@ fn confirm_impl_candidate<'cx, 'tcx>(
return Progress { ty: tcx.types.err, obligations: nested };
}
let substs = obligation.predicate.substs.rebase_onto(tcx, trait_def_id, substs);
let substs = translate_substs(selcx.infcx(), param_env, impl_def_id, substs, assoc_ty.node);
let substs =
translate_substs(selcx.infcx(), param_env, impl_def_id, substs, assoc_ty.defining_node);
let ty = if let ty::AssocKind::OpaqueTy = assoc_ty.item.kind {
let item_substs = InternalSubsts::identity_for_item(tcx, assoc_ty.item.def_id);
tcx.mk_opaque(assoc_ty.item.def_id, item_substs)
@ -1447,7 +1420,7 @@ fn assoc_ty_def(
selcx: &SelectionContext<'_, '_>,
impl_def_id: DefId,
assoc_ty_def_id: DefId,
) -> Result<specialization_graph::NodeItem, ErrorReported> {
) -> Result<specialization_graph::LeafDef, ErrorReported> {
let tcx = selcx.tcx();
let assoc_ty_name = tcx.associated_item(assoc_ty_def_id).ident;
let trait_def_id = tcx.impl_trait_ref(impl_def_id).unwrap().def_id;
@ -1464,9 +1437,10 @@ fn assoc_ty_def(
if matches!(item.kind, ty::AssocKind::Type | ty::AssocKind::OpaqueTy)
&& tcx.hygienic_eq(item.ident, assoc_ty_name, trait_def_id)
{
return Ok(specialization_graph::NodeItem {
node: specialization_graph::Node::Impl(impl_def_id),
return Ok(specialization_graph::LeafDef {
item: *item,
defining_node: impl_node,
finalizing_node: if item.defaultness.is_default() { None } else { Some(impl_node) },
});
}
}

View File

@ -141,7 +141,7 @@ pub fn find_associated_item<'tcx>(
param_env,
impl_data.impl_def_id,
substs,
node_item.node,
node_item.defining_node,
);
infcx.tcx.erase_regions(&substs)
});

View File

@ -4,11 +4,11 @@ use smallvec::smallvec;
use smallvec::SmallVec;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::outlives::Component;
use rustc_middle::ty::subst::{GenericArg, Subst, SubstsRef};
use rustc_middle::ty::{self, ToPolyTraitRef, ToPredicate, Ty, TyCtxt, WithConstness};
use rustc_middle::traits::util::impl_is_default;
use super::{Normalized, Obligation, ObligationCause, PredicateObligation, SelectionContext};
@ -651,20 +651,6 @@ pub fn generator_trait_ref_and_outputs(
ty::Binder::bind((trait_ref, sig.skip_binder().yield_ty, sig.skip_binder().return_ty))
}
pub fn impl_is_default(tcx: TyCtxt<'_>, node_item_def_id: DefId) -> bool {
match tcx.hir().as_local_hir_id(node_item_def_id) {
Some(hir_id) => {
let item = tcx.hir().expect_item(hir_id);
if let hir::ItemKind::Impl { defaultness, .. } = item.kind {
defaultness.is_default()
} else {
false
}
}
None => tcx.impl_defaultness(node_item_def_id).is_default(),
}
}
pub fn impl_item_is_final(tcx: TyCtxt<'_>, assoc_item: &ty::AssocItem) -> bool {
assoc_item.defaultness.is_final() && !impl_is_default(tcx, assoc_item.container.id())
}

View File

@ -110,6 +110,7 @@ use rustc_infer::infer::{self, InferCtxt, InferOk, InferResult, TyCtxtInferExt};
use rustc_middle::hir::map::blocks::FnLikeNode;
use rustc_middle::middle::region;
use rustc_middle::mir::interpret::ConstValue;
use rustc_middle::traits::util::impl_is_default;
use rustc_middle::ty::adjustment::{
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCast,
};
@ -1942,7 +1943,7 @@ fn check_specialization_validity<'tcx>(
// grandparent. In that case, if parent is a `default impl`, inherited items use the
// "defaultness" from the grandparent, else they are final.
None => {
if traits::impl_is_default(tcx, parent_impl.def_id()) {
if impl_is_default(tcx, parent_impl.def_id()) {
None
} else {
Some(Err(parent_impl.def_id()))
@ -2114,10 +2115,10 @@ fn check_impl_items_against_trait<'tcx>(
for trait_item in tcx.associated_items(impl_trait_ref.def_id).in_definition_order() {
let is_implemented = ancestors
.leaf_def(tcx, trait_item.ident, trait_item.kind)
.map(|node_item| !node_item.node.is_from_trait())
.map(|node_item| !node_item.defining_node.is_from_trait())
.unwrap_or(false);
if !is_implemented && !traits::impl_is_default(tcx, impl_id) {
if !is_implemented && !impl_is_default(tcx, impl_id) {
if !trait_item.defaultness.has_value() {
missing_items.push(*trait_item);
}