use super::elaborate_predicates;
use crate::infer::TyCtxtInferExt;
use crate::traits::const_evaluatable::{self, AbstractConst};
use crate::traits::query::evaluate_obligation::InferCtxtExt;
use crate::traits::{self, Obligation, ObligationCause};
use rustc_errors::FatalError;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::subst::{GenericArg, InternalSubsts, Subst};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeVisitor, WithConstness};
use rustc_middle::ty::{Predicate, ToPredicate};
use rustc_session::lint::builtin::WHERE_CLAUSES_OBJECT_SAFETY;
use rustc_span::symbol::Symbol;
use rustc_span::{MultiSpan, Span};
use smallvec::SmallVec;
use std::array;
use std::iter;
use std::ops::ControlFlow;
pub use crate::traits::{MethodViolationCode, ObjectSafetyViolation};
pub fn astconv_object_safety_violations(
tcx: TyCtxt<'_>,
trait_def_id: DefId,
) -> Vec<ObjectSafetyViolation> {
debug_assert!(tcx.generics_of(trait_def_id).has_self);
let violations = traits::supertrait_def_ids(tcx, trait_def_id)
.map(|def_id| predicates_reference_self(tcx, def_id, true))
.filter(|spans| !spans.is_empty())
.map(ObjectSafetyViolation::SupertraitSelf)
.collect();
debug!("astconv_object_safety_violations(trait_def_id={:?}) = {:?}", trait_def_id, violations);
violations
}
fn object_safety_violations(
tcx: TyCtxt<'tcx>,
trait_def_id: DefId,
) -> &'tcx [ObjectSafetyViolation] {
debug_assert!(tcx.generics_of(trait_def_id).has_self);
debug!("object_safety_violations: {:?}", trait_def_id);
tcx.arena.alloc_from_iter(
traits::supertrait_def_ids(tcx, trait_def_id)
.flat_map(|def_id| object_safety_violations_for_trait(tcx, def_id)),
)
}
pub fn is_vtable_safe_method(tcx: TyCtxt<'_>, trait_def_id: DefId, method: &ty::AssocItem) -> bool {
debug_assert!(tcx.generics_of(trait_def_id).has_self);
debug!("is_vtable_safe_method({:?}, {:?})", trait_def_id, method);
if generics_require_sized_self(tcx, method.def_id) {
return false;
}
match virtual_call_violation_for_method(tcx, trait_def_id, method) {
None | Some(MethodViolationCode::WhereClauseReferencesSelf) => true,
Some(_) => false,
}
}
fn object_safety_violations_for_trait(
tcx: TyCtxt<'_>,
trait_def_id: DefId,
) -> Vec<ObjectSafetyViolation> {
let mut violations: Vec<_> = tcx
.associated_items(trait_def_id)
.in_definition_order()
.filter(|item| item.kind == ty::AssocKind::Fn)
.filter_map(|item| {
object_safety_violation_for_method(tcx, trait_def_id, &item)
.map(|(code, span)| ObjectSafetyViolation::Method(item.ident.name, code, span))
})
.filter(|violation| {
if let ObjectSafetyViolation::Method(
_,
MethodViolationCode::WhereClauseReferencesSelf,
span,
) = violation
{
lint_object_unsafe_trait(tcx, *span, trait_def_id, violation);
false
} else {
true
}
})
.collect();
if trait_has_sized_self(tcx, trait_def_id) {
let spans = get_sized_bounds(tcx, trait_def_id);
violations.push(ObjectSafetyViolation::SizedSelf(spans));
}
let spans = predicates_reference_self(tcx, trait_def_id, false);
if !spans.is_empty() {
violations.push(ObjectSafetyViolation::SupertraitSelf(spans));
}
let spans = bounds_reference_self(tcx, trait_def_id);
if !spans.is_empty() {
violations.push(ObjectSafetyViolation::SupertraitSelf(spans));
}
violations.extend(
tcx.associated_items(trait_def_id)
.in_definition_order()
.filter(|item| item.kind == ty::AssocKind::Const)
.map(|item| ObjectSafetyViolation::AssocConst(item.ident.name, item.ident.span)),
);
debug!(
"object_safety_violations_for_trait(trait_def_id={:?}) = {:?}",
trait_def_id, violations
);
violations
}
fn lint_object_unsafe_trait(
tcx: TyCtxt<'_>,
span: Span,
trait_def_id: DefId,
violation: &ObjectSafetyViolation,
) {
tcx.struct_span_lint_hir(WHERE_CLAUSES_OBJECT_SAFETY, hir::CRATE_HIR_ID, span, |lint| {
let mut err = lint.build(&format!(
"the trait `{}` cannot be made into an object",
tcx.def_path_str(trait_def_id)
));
let node = tcx.hir().get_if_local(trait_def_id);
let mut spans = MultiSpan::from_span(span);
if let Some(hir::Node::Item(item)) = node {
spans.push_span_label(
item.ident.span,
"this trait cannot be made into an object...".into(),
);
spans.push_span_label(span, format!("...because {}", violation.error_msg()));
} else {
spans.push_span_label(
span,
format!(
"the trait cannot be made into an object because {}",
violation.error_msg()
),
);
};
err.span_note(
spans,
"for a trait to be \"object safe\" it needs to allow building a vtable to allow the \
call to be resolvable dynamically; for more information visit \
<https://doc.rust-lang.org/reference/items/traits.html#object-safety>",
);
if node.is_some() {
violation.solution(&mut err);
}
err.emit();
});
}
fn sized_trait_bound_spans<'tcx>(
tcx: TyCtxt<'tcx>,
bounds: hir::GenericBounds<'tcx>,
) -> impl 'tcx + Iterator<Item = Span> {
bounds.iter().filter_map(move |b| match b {
hir::GenericBound::Trait(trait_ref, hir::TraitBoundModifier::None)
if trait_has_sized_self(
tcx,
trait_ref.trait_ref.trait_def_id().unwrap_or_else(|| FatalError.raise()),
) =>
{
Some(trait_ref.span)
}
_ => None,
})
}
fn get_sized_bounds(tcx: TyCtxt<'_>, trait_def_id: DefId) -> SmallVec<[Span; 1]> {
tcx.hir()
.get_if_local(trait_def_id)
.and_then(|node| match node {
hir::Node::Item(hir::Item {
kind: hir::ItemKind::Trait(.., generics, bounds, _),
..
}) => Some(
generics
.where_clause
.predicates
.iter()
.filter_map(|pred| {
match pred {
hir::WherePredicate::BoundPredicate(pred)
if pred.bounded_ty.hir_id.owner.to_def_id() == trait_def_id =>
{
Some(sized_trait_bound_spans(tcx, pred.bounds))
}
_ => None,
}
})
.flatten()
.chain(sized_trait_bound_spans(tcx, bounds))
.collect::<SmallVec<[Span; 1]>>(),
),
_ => None,
})
.unwrap_or_else(SmallVec::new)
}
fn predicates_reference_self(
tcx: TyCtxt<'_>,
trait_def_id: DefId,
supertraits_only: bool,
) -> SmallVec<[Span; 1]> {
let trait_ref = ty::Binder::dummy(ty::TraitRef::identity(tcx, trait_def_id));
let predicates = if supertraits_only {
tcx.super_predicates_of(trait_def_id)
} else {
tcx.predicates_of(trait_def_id)
};
predicates
.predicates
.iter()
.map(|&(predicate, sp)| (predicate.subst_supertrait(tcx, &trait_ref), sp))
.filter_map(|predicate| predicate_references_self(tcx, predicate))
.collect()
}
fn bounds_reference_self(tcx: TyCtxt<'_>, trait_def_id: DefId) -> SmallVec<[Span; 1]> {
let trait_ref = ty::Binder::dummy(ty::TraitRef::identity(tcx, trait_def_id));
tcx.associated_items(trait_def_id)
.in_definition_order()
.filter(|item| item.kind == ty::AssocKind::Type)
.flat_map(|item| tcx.explicit_item_bounds(item.def_id))
.map(|&(predicate, sp)| (predicate.subst_supertrait(tcx, &trait_ref), sp))
.filter_map(|predicate| predicate_references_self(tcx, predicate))
.collect()
}
fn predicate_references_self(
tcx: TyCtxt<'tcx>,
(predicate, sp): (ty::Predicate<'tcx>, Span),
) -> Option<Span> {
let self_ty = tcx.types.self_param;
let has_self_ty = |arg: &GenericArg<'_>| arg.walk().any(|arg| arg == self_ty.into());
match predicate.skip_binders() {
ty::PredicateAtom::Trait(ref data, _) => {
if data.trait_ref.substs[1..].iter().any(has_self_ty) { Some(sp) } else { None }
}
ty::PredicateAtom::Projection(ref data) => {
if data.projection_ty.trait_ref(tcx).substs[1..].iter().any(has_self_ty) {
Some(sp)
} else {
None
}
}
ty::PredicateAtom::WellFormed(..)
| ty::PredicateAtom::ObjectSafe(..)
| ty::PredicateAtom::TypeOutlives(..)
| ty::PredicateAtom::RegionOutlives(..)
| ty::PredicateAtom::ClosureKind(..)
| ty::PredicateAtom::Subtype(..)
| ty::PredicateAtom::ConstEvaluatable(..)
| ty::PredicateAtom::ConstEquate(..)
| ty::PredicateAtom::TypeWellFormedFromEnv(..) => None,
}
}
fn trait_has_sized_self(tcx: TyCtxt<'_>, trait_def_id: DefId) -> bool {
generics_require_sized_self(tcx, trait_def_id)
}
fn generics_require_sized_self(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
let sized_def_id = match tcx.lang_items().sized_trait() {
Some(def_id) => def_id,
None => {
return false;
}
};
let predicates = tcx.predicates_of(def_id);
let predicates = predicates.instantiate_identity(tcx).predicates;
elaborate_predicates(tcx, predicates.into_iter()).any(|obligation| {
match obligation.predicate.skip_binders() {
ty::PredicateAtom::Trait(ref trait_pred, _) => {
trait_pred.def_id() == sized_def_id && trait_pred.self_ty().is_param(0)
}
ty::PredicateAtom::Projection(..)
| ty::PredicateAtom::Subtype(..)
| ty::PredicateAtom::RegionOutlives(..)
| ty::PredicateAtom::WellFormed(..)
| ty::PredicateAtom::ObjectSafe(..)
| ty::PredicateAtom::ClosureKind(..)
| ty::PredicateAtom::TypeOutlives(..)
| ty::PredicateAtom::ConstEvaluatable(..)
| ty::PredicateAtom::ConstEquate(..)
| ty::PredicateAtom::TypeWellFormedFromEnv(..) => false,
}
})
}
fn object_safety_violation_for_method(
tcx: TyCtxt<'_>,
trait_def_id: DefId,
method: &ty::AssocItem,
) -> Option<(MethodViolationCode, Span)> {
debug!("object_safety_violation_for_method({:?}, {:?})", trait_def_id, method);
if generics_require_sized_self(tcx, method.def_id) {
return None;
}
let violation = virtual_call_violation_for_method(tcx, trait_def_id, method);
violation.map(|v| {
let node = tcx.hir().get_if_local(method.def_id);
let span = match (v, node) {
(MethodViolationCode::ReferencesSelfInput(arg), Some(node)) => node
.fn_decl()
.and_then(|decl| decl.inputs.get(arg + 1))
.map_or(method.ident.span, |arg| arg.span),
(MethodViolationCode::UndispatchableReceiver, Some(node)) => node
.fn_decl()
.and_then(|decl| decl.inputs.get(0))
.map_or(method.ident.span, |arg| arg.span),
(MethodViolationCode::ReferencesSelfOutput, Some(node)) => {
node.fn_decl().map_or(method.ident.span, |decl| decl.output.span())
}
_ => method.ident.span,
};
(v, span)
})
}
fn virtual_call_violation_for_method<'tcx>(
tcx: TyCtxt<'tcx>,
trait_def_id: DefId,
method: &ty::AssocItem,
) -> Option<MethodViolationCode> {
let sig = tcx.fn_sig(method.def_id);
if !method.fn_has_self_parameter {
let sugg =
tcx.hir().get_if_local(method.def_id).as_ref().and_then(|node| node.generics()).map(
|generics| match generics.where_clause.predicates {
[] => (" where Self: Sized", generics.where_clause.span),
[.., pred] => (", Self: Sized", pred.span().shrink_to_hi()),
},
);
let sm = tcx.sess.source_map();
let self_span = method.ident.span.to(tcx
.hir()
.span_if_local(method.def_id)
.unwrap_or_else(|| sm.next_point(method.ident.span))
.shrink_to_hi());
let self_span = sm.span_through_char(self_span, '(').shrink_to_hi();
return Some(MethodViolationCode::StaticMethod(
sugg,
self_span,
!sig.inputs().skip_binder().is_empty(),
));
}
for (i, &input_ty) in sig.skip_binder().inputs()[1..].iter().enumerate() {
if contains_illegal_self_type_reference(tcx, trait_def_id, input_ty) {
return Some(MethodViolationCode::ReferencesSelfInput(i));
}
}
if contains_illegal_self_type_reference(tcx, trait_def_id, sig.output().skip_binder()) {
return Some(MethodViolationCode::ReferencesSelfOutput);
}
let own_counts = tcx.generics_of(method.def_id).own_counts();
if own_counts.types + own_counts.consts != 0 {
return Some(MethodViolationCode::Generic);
}
if tcx
.predicates_of(method.def_id)
.predicates
.iter()
.cloned()
.filter(|(p, _)| p.to_opt_type_outlives().is_none())
.any(|pred| contains_illegal_self_type_reference(tcx, trait_def_id, pred))
{
return Some(MethodViolationCode::WhereClauseReferencesSelf);
}
let receiver_ty =
tcx.liberate_late_bound_regions(method.def_id, sig.map_bound(|sig| sig.inputs()[0]));
if receiver_ty != tcx.types.self_param {
if !receiver_is_dispatchable(tcx, method, receiver_ty) {
return Some(MethodViolationCode::UndispatchableReceiver);
} else {
use rustc_target::abi::Abi;
let param_env = tcx.param_env(method.def_id);
let abi_of_ty = |ty: Ty<'tcx>| -> Option<&Abi> {
match tcx.layout_of(param_env.and(ty)) {
Ok(layout) => Some(&layout.abi),
Err(err) => {
tcx.sess.delay_span_bug(
tcx.def_span(method.def_id),
&format!("error: {}\n while computing layout for type {:?}", err, ty),
);
None
}
}
};
let unit_receiver_ty =
receiver_for_self_ty(tcx, receiver_ty, tcx.mk_unit(), method.def_id);
match abi_of_ty(unit_receiver_ty) {
Some(Abi::Scalar(..)) => (),
abi => {
tcx.sess.delay_span_bug(
tcx.def_span(method.def_id),
&format!(
"receiver when `Self = ()` should have a Scalar ABI; found {:?}",
abi
),
);
}
}
let trait_object_ty =
object_ty_for_trait(tcx, trait_def_id, tcx.mk_region(ty::ReStatic));
let trait_object_receiver =
receiver_for_self_ty(tcx, receiver_ty, trait_object_ty, method.def_id);
match abi_of_ty(trait_object_receiver) {
Some(Abi::ScalarPair(..)) => (),
abi => {
tcx.sess.delay_span_bug(
tcx.def_span(method.def_id),
&format!(
"receiver when `Self = {}` should have a ScalarPair ABI; found {:?}",
trait_object_ty, abi
),
);
}
}
}
}
None
}
fn receiver_for_self_ty<'tcx>(
tcx: TyCtxt<'tcx>,
receiver_ty: Ty<'tcx>,
self_ty: Ty<'tcx>,
method_def_id: DefId,
) -> Ty<'tcx> {
debug!("receiver_for_self_ty({:?}, {:?}, {:?})", receiver_ty, self_ty, method_def_id);
let substs = InternalSubsts::for_item(tcx, method_def_id, |param, _| {
if param.index == 0 { self_ty.into() } else { tcx.mk_param_from_def(param) }
});
let result = receiver_ty.subst(tcx, substs);
debug!(
"receiver_for_self_ty({:?}, {:?}, {:?}) = {:?}",
receiver_ty, self_ty, method_def_id, result
);
result
}
fn object_ty_for_trait<'tcx>(
tcx: TyCtxt<'tcx>,
trait_def_id: DefId,
lifetime: ty::Region<'tcx>,
) -> Ty<'tcx> {
debug!("object_ty_for_trait: trait_def_id={:?}", trait_def_id);
let trait_ref = ty::TraitRef::identity(tcx, trait_def_id);
let trait_predicate =
ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref));
let mut associated_types = traits::supertraits(tcx, ty::Binder::dummy(trait_ref))
.flat_map(|super_trait_ref| {
tcx.associated_items(super_trait_ref.def_id())
.in_definition_order()
.map(move |item| (super_trait_ref, item))
})
.filter(|(_, item)| item.kind == ty::AssocKind::Type)
.collect::<Vec<_>>();
associated_types.sort_by_cached_key(|(_, item)| tcx.def_path_hash(item.def_id));
let projection_predicates = associated_types.into_iter().map(|(super_trait_ref, item)| {
let super_trait_ref = super_trait_ref.skip_binder();
ty::ExistentialPredicate::Projection(ty::ExistentialProjection {
ty: tcx.mk_projection(item.def_id, super_trait_ref.substs),
item_def_id: item.def_id,
substs: super_trait_ref.substs,
})
});
let existential_predicates =
tcx.mk_existential_predicates(iter::once(trait_predicate).chain(projection_predicates));
let object_ty = tcx.mk_dynamic(
ty::Binder::bind(existential_predicates),
lifetime,
);
debug!("object_ty_for_trait: object_ty=`{}`", object_ty);
object_ty
}
#[allow(dead_code)]
fn receiver_is_dispatchable<'tcx>(
tcx: TyCtxt<'tcx>,
method: &ty::AssocItem,
receiver_ty: Ty<'tcx>,
) -> bool {
debug!("receiver_is_dispatchable: method = {:?}, receiver_ty = {:?}", method, receiver_ty);
let traits = (tcx.lang_items().unsize_trait(), tcx.lang_items().dispatch_from_dyn_trait());
let (unsize_did, dispatch_from_dyn_did) = if let (Some(u), Some(cu)) = traits {
(u, cu)
} else {
debug!("receiver_is_dispatchable: Missing Unsize or DispatchFromDyn traits");
return false;
};
let unsized_self_ty: Ty<'tcx> =
tcx.mk_ty_param(u32::MAX, Symbol::intern("RustaceansAreAwesome"));
let unsized_receiver_ty =
receiver_for_self_ty(tcx, receiver_ty, unsized_self_ty, method.def_id);
let param_env = {
let param_env = tcx.param_env(method.def_id);
let unsize_predicate = ty::TraitRef {
def_id: unsize_did,
substs: tcx.mk_substs_trait(tcx.types.self_param, &[unsized_self_ty.into()]),
}
.without_const()
.to_predicate(tcx);
let trait_predicate = {
let substs =
InternalSubsts::for_item(tcx, method.container.assert_trait(), |param, _| {
if param.index == 0 {
unsized_self_ty.into()
} else {
tcx.mk_param_from_def(param)
}
});
ty::TraitRef { def_id: unsize_did, substs }.without_const().to_predicate(tcx)
};
let caller_bounds: Vec<Predicate<'tcx>> = param_env
.caller_bounds()
.iter()
.chain(array::IntoIter::new([unsize_predicate, trait_predicate]))
.collect();
ty::ParamEnv::new(tcx.intern_predicates(&caller_bounds), param_env.reveal())
};
let obligation = {
let predicate = ty::TraitRef {
def_id: dispatch_from_dyn_did,
substs: tcx.mk_substs_trait(receiver_ty, &[unsized_receiver_ty.into()]),
}
.without_const()
.to_predicate(tcx);
Obligation::new(ObligationCause::dummy(), param_env, predicate)
};
tcx.infer_ctxt().enter(|ref infcx| {
infcx.predicate_must_hold_modulo_regions(&obligation)
})
}
fn contains_illegal_self_type_reference<'tcx, T: TypeFoldable<'tcx>>(
tcx: TyCtxt<'tcx>,
trait_def_id: DefId,
value: T,
) -> bool {
struct IllegalSelfTypeVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
trait_def_id: DefId,
supertraits: Option<Vec<ty::PolyTraitRef<'tcx>>>,
}
impl<'tcx> TypeVisitor<'tcx> for IllegalSelfTypeVisitor<'tcx> {
type BreakTy = ();
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
match t.kind() {
ty::Param(_) => {
if t == self.tcx.types.self_param {
ControlFlow::BREAK
} else {
ControlFlow::CONTINUE
}
}
ty::Projection(ref data) => {
if self.supertraits.is_none() {
let trait_ref =
ty::Binder::bind(ty::TraitRef::identity(self.tcx, self.trait_def_id));
self.supertraits = Some(traits::supertraits(self.tcx, trait_ref).collect());
}
let projection_trait_ref = ty::Binder::bind(data.trait_ref(self.tcx));
let is_supertrait_of_current_trait =
self.supertraits.as_ref().unwrap().contains(&projection_trait_ref);
if is_supertrait_of_current_trait {
ControlFlow::CONTINUE
} else {
t.super_visit_with(self)
}
}
_ => t.super_visit_with(self),
}
}
fn visit_const(&mut self, ct: &ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
self.visit_ty(ct.ty)?;
use rustc_middle::mir::abstract_const::Node;
if let Ok(Some(ct)) = AbstractConst::from_const(self.tcx, ct) {
const_evaluatable::walk_abstract_const(self.tcx, ct, |node| match node {
Node::Leaf(leaf) => {
let leaf = leaf.subst(self.tcx, ct.substs);
self.visit_const(leaf)
}
Node::Binop(..) | Node::UnaryOp(..) | Node::FunctionCall(_, _) => {
ControlFlow::CONTINUE
}
})
} else {
ControlFlow::CONTINUE
}
}
fn visit_predicate(&mut self, pred: ty::Predicate<'tcx>) -> ControlFlow<Self::BreakTy> {
if let ty::PredicateAtom::ConstEvaluatable(def, substs) = pred.skip_binders() {
use rustc_middle::mir::abstract_const::Node;
if let Ok(Some(ct)) = AbstractConst::new(self.tcx, def, substs) {
const_evaluatable::walk_abstract_const(self.tcx, ct, |node| match node {
Node::Leaf(leaf) => {
let leaf = leaf.subst(self.tcx, ct.substs);
self.visit_const(leaf)
}
Node::Binop(..) | Node::UnaryOp(..) | Node::FunctionCall(_, _) => {
ControlFlow::CONTINUE
}
})
} else {
ControlFlow::CONTINUE
}
} else {
pred.super_visit_with(self)
}
}
}
value
.visit_with(&mut IllegalSelfTypeVisitor { tcx, trait_def_id, supertraits: None })
.is_break()
}
pub fn provide(providers: &mut ty::query::Providers) {
*providers = ty::query::Providers { object_safety_violations, ..*providers };
}