Skip to content

Commit

Permalink
Auto merge of #45870 - mikeyhew:arbitrary_self_types, r=arielb1
Browse files Browse the repository at this point in the history
Implement arbitrary_self_types

r? @arielb1
cc @nikomatsakis

Partial implementation of #44874.  Supports trait and struct methods with arbitrary self types, as long as the type derefs (transitively) to `Self`. Doesn't support raw-pointer `self` yet.

Methods with non-standard self types (i.e. anything other than `&self, &mut self, and Box<Self>`) are not object safe, because dynamic dispatch hasn't been implemented for them yet.

I believe this is also a (partial) fix for #27941.
  • Loading branch information
bors committed Nov 12, 2017
2 parents c1aacdc + 77cd993 commit b087ded
Show file tree
Hide file tree
Showing 19 changed files with 349 additions and 113 deletions.
8 changes: 6 additions & 2 deletions src/librustc/infer/region_inference/mod.rs
Expand Up @@ -633,11 +633,15 @@ impl<'a, 'gcx, 'tcx> RegionVarBindings<'a, 'gcx, 'tcx> {

debug!("RegionVarBindings: add_constraint({:?})", constraint);

if self.constraints.borrow_mut().insert(constraint, origin).is_none() {
// never overwrite an existing (constraint, origin) - only insert one if it isn't
// present in the map yet. This prevents origins from outside the snapshot being
// replaced with "less informative" origins e.g. during calls to `can_eq`
self.constraints.borrow_mut().entry(constraint).or_insert_with(|| {
if self.in_snapshot() {
self.undo_log.borrow_mut().push(AddConstraint(constraint));
}
}
origin
});
}

fn add_verify(&self, verify: Verify<'tcx>) {
Expand Down
17 changes: 16 additions & 1 deletion src/librustc/traits/object_safety.rs
Expand Up @@ -23,6 +23,7 @@ use hir::def_id::DefId;
use traits;
use ty::{self, Ty, TyCtxt, TypeFoldable};
use ty::subst::Substs;
use ty::util::ExplicitSelf;
use std::borrow::Cow;
use syntax::ast;

Expand Down Expand Up @@ -57,6 +58,10 @@ impl ObjectSafetyViolation {
in its arguments or return type", name).into(),
ObjectSafetyViolation::Method(name, MethodViolationCode::Generic) =>
format!("method `{}` has generic type parameters", name).into(),
ObjectSafetyViolation::Method(name, MethodViolationCode::NonStandardSelfType) =>
format!("method `{}` has a non-standard `self` type. Only `&self`, \
`&mut self`, and `Box<Self>` are currently supported \
for trait objects", name).into(),
ObjectSafetyViolation::AssociatedConst(name) =>
format!("the trait cannot contain associated consts like `{}`", name).into(),
}
Expand All @@ -74,6 +79,9 @@ pub enum MethodViolationCode {

/// e.g., `fn foo<A>()`
Generic,

/// arbitrary `self` type, e.g. `self: Rc<Self>`
NonStandardSelfType,
}

impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
Expand Down Expand Up @@ -260,9 +268,16 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
return Some(MethodViolationCode::StaticMethod);
}

let sig = self.fn_sig(method.def_id);

let self_ty = self.mk_self_type();
let self_arg_ty = sig.skip_binder().inputs()[0];
if let ExplicitSelf::Other = ExplicitSelf::determine(self_arg_ty, |ty| ty == self_ty) {
return Some(MethodViolationCode::NonStandardSelfType);
}

// The `Self` type is erased, so it should not appear in list of
// arguments or return type apart from the receiver.
let ref sig = self.fn_sig(method.def_id);
for input_ty in &sig.skip_binder().inputs()[1..] {
if self.contains_illegal_self_type_reference(trait_def_id, input_ty) {
return Some(MethodViolationCode::ReferencesSelf);
Expand Down
53 changes: 53 additions & 0 deletions src/librustc/ty/util.rs
Expand Up @@ -12,6 +12,7 @@

use hir::def_id::{DefId, LOCAL_CRATE};
use hir::map::DefPathData;
use hir;
use ich::NodeIdHashingMode;
use middle::const_val::ConstVal;
use traits::{self, Reveal};
Expand Down Expand Up @@ -1178,6 +1179,58 @@ fn layout_raw<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
layout
}

pub enum ExplicitSelf<'tcx> {
ByValue,
ByReference(ty::Region<'tcx>, hir::Mutability),
ByBox,
Other
}

impl<'tcx> ExplicitSelf<'tcx> {
/// Categorizes an explicit self declaration like `self: SomeType`
/// into either `self`, `&self`, `&mut self`, `Box<self>`, or
/// `Other`.
/// This is mainly used to require the arbitrary_self_types feature
/// in the case of `Other`, to improve error messages in the common cases,
/// and to make `Other` non-object-safe.
///
/// Examples:
///
/// ```
/// impl<'a> Foo for &'a T {
/// // Legal declarations:
/// fn method1(self: &&'a T); // ExplicitSelf::ByReference
/// fn method2(self: &'a T); // ExplicitSelf::ByValue
/// fn method3(self: Box<&'a T>); // ExplicitSelf::ByBox
/// fn method4(self: Rc<&'a T>); // ExplicitSelf::Other
///
/// // Invalid cases will be caught by `check_method_receiver`:
/// fn method_err1(self: &'a mut T); // ExplicitSelf::Other
/// fn method_err2(self: &'static T) // ExplicitSelf::ByValue
/// fn method_err3(self: &&T) // ExplicitSelf::ByReference
/// }
/// ```
///
pub fn determine<P>(
self_arg_ty: Ty<'tcx>,
is_self_ty: P
) -> ExplicitSelf<'tcx>
where
P: Fn(Ty<'tcx>) -> bool
{
use self::ExplicitSelf::*;

match self_arg_ty.sty {
_ if is_self_ty(self_arg_ty) => ByValue,
ty::TyRef(region, ty::TypeAndMut { ty, mutbl}) if is_self_ty(ty) => {
ByReference(region, mutbl)
}
ty::TyAdt(def, _) if def.is_box() && is_self_ty(self_arg_ty.boxed_ty()) => ByBox,
_ => Other
}
}
}

pub fn provide(providers: &mut ty::maps::Providers) {
*providers = ty::maps::Providers {
is_copy_raw,
Expand Down
61 changes: 0 additions & 61 deletions src/librustc_typeck/astconv.rs
Expand Up @@ -1386,64 +1386,3 @@ impl<'a, 'gcx, 'tcx> Bounds<'tcx> {
vec
}
}

pub enum ExplicitSelf<'tcx> {
ByValue,
ByReference(ty::Region<'tcx>, hir::Mutability),
ByBox
}

impl<'tcx> ExplicitSelf<'tcx> {
/// We wish to (for now) categorize an explicit self
/// declaration like `self: SomeType` into either `self`,
/// `&self`, `&mut self`, or `Box<self>`. We do this here
/// by some simple pattern matching. A more precise check
/// is done later in `check_method_receiver()`.
///
/// Examples:
///
/// ```
/// impl Foo for &T {
/// // Legal declarations:
/// fn method1(self: &&T); // ExplicitSelf::ByReference
/// fn method2(self: &T); // ExplicitSelf::ByValue
/// fn method3(self: Box<&T>); // ExplicitSelf::ByBox
///
/// // Invalid cases will be caught later by `check_method_receiver`:
/// fn method_err1(self: &mut T); // ExplicitSelf::ByReference
/// }
/// ```
///
/// To do the check we just count the number of "modifiers"
/// on each type and compare them. If they are the same or
/// the impl has more, we call it "by value". Otherwise, we
/// look at the outermost modifier on the method decl and
/// call it by-ref, by-box as appropriate. For method1, for
/// example, the impl type has one modifier, but the method
/// type has two, so we end up with
/// ExplicitSelf::ByReference.
pub fn determine(untransformed_self_ty: Ty<'tcx>,
self_arg_ty: Ty<'tcx>)
-> ExplicitSelf<'tcx> {
fn count_modifiers(ty: Ty) -> usize {
match ty.sty {
ty::TyRef(_, mt) => count_modifiers(mt.ty) + 1,
ty::TyAdt(def, _) if def.is_box() => count_modifiers(ty.boxed_ty()) + 1,
_ => 0,
}
}

let impl_modifiers = count_modifiers(untransformed_self_ty);
let method_modifiers = count_modifiers(self_arg_ty);

if impl_modifiers >= method_modifiers {
ExplicitSelf::ByValue
} else {
match self_arg_ty.sty {
ty::TyRef(r, mt) => ExplicitSelf::ByReference(r, mt.mutbl),
ty::TyAdt(def, _) if def.is_box() => ExplicitSelf::ByBox,
_ => ExplicitSelf::ByValue,
}
}
}
}
19 changes: 12 additions & 7 deletions src/librustc_typeck/check/compare_method.rs
Expand Up @@ -13,6 +13,7 @@ use rustc::infer::{self, InferOk};
use rustc::middle::free_region::FreeRegionMap;
use rustc::middle::region;
use rustc::ty::{self, TyCtxt};
use rustc::ty::util::ExplicitSelf;
use rustc::traits::{self, ObligationCause, ObligationCauseCode, Reveal};
use rustc::ty::error::{ExpectedFound, TypeError};
use rustc::ty::subst::{Subst, Substs};
Expand All @@ -21,7 +22,6 @@ use rustc::util::common::ErrorReported;
use syntax_pos::Span;

use super::{Inherited, FnCtxt};
use astconv::ExplicitSelf;

/// Checks that a method from an impl conforms to the signature of
/// the same method as declared in the trait.
Expand Down Expand Up @@ -503,12 +503,17 @@ fn compare_self_type<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
ty::TraitContainer(_) => tcx.mk_self_type()
};
let self_arg_ty = *tcx.fn_sig(method.def_id).input(0).skip_binder();
match ExplicitSelf::determine(untransformed_self_ty, self_arg_ty) {
ExplicitSelf::ByValue => "self".to_string(),
ExplicitSelf::ByReference(_, hir::MutImmutable) => "&self".to_string(),
ExplicitSelf::ByReference(_, hir::MutMutable) => "&mut self".to_string(),
_ => format!("self: {}", self_arg_ty)
}
let param_env = ty::ParamEnv::empty(Reveal::All);

tcx.infer_ctxt().enter(|infcx| {
let can_eq_self = |ty| infcx.can_eq(param_env, untransformed_self_ty, ty).is_ok();
match ExplicitSelf::determine(self_arg_ty, can_eq_self) {
ExplicitSelf::ByValue => "self".to_string(),
ExplicitSelf::ByReference(_, hir::MutImmutable) => "&self".to_string(),
ExplicitSelf::ByReference(_, hir::MutMutable) => "&mut self".to_string(),
_ => format!("self: {}", self_arg_ty)
}
})
};

match (trait_m.method_has_self_argument, impl_m.method_has_self_argument) {
Expand Down
71 changes: 51 additions & 20 deletions src/librustc_typeck/check/wfcheck.rs
Expand Up @@ -8,19 +8,20 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use astconv::ExplicitSelf;
use check::{Inherited, FnCtxt};
use constrained_type_params::{identify_constrained_type_params, Parameter};

use hir::def_id::DefId;
use rustc::traits::{self, ObligationCauseCode};
use rustc::ty::{self, Ty, TyCtxt};
use rustc::ty::util::ExplicitSelf;
use rustc::util::nodemap::{FxHashSet, FxHashMap};
use rustc::middle::lang_items;

use syntax::ast;
use syntax::feature_gate::{self, GateIssue};
use syntax_pos::Span;
use errors::DiagnosticBuilder;
use errors::{DiagnosticBuilder, DiagnosticId};

use rustc::hir::intravisit::{self, Visitor, NestedVisitorMap};
use rustc::hir;
Expand Down Expand Up @@ -451,8 +452,7 @@ impl<'a, 'gcx> CheckTypeWellFormedVisitor<'a, 'gcx> {
method: &ty::AssociatedItem,
self_ty: Ty<'tcx>)
{
// check that the type of the method's receiver matches the
// method's first parameter.
// check that the method has a valid receiver type, given the type `Self`
debug!("check_method_receiver({:?}, self_ty={:?})",
method, self_ty);

Expand All @@ -468,26 +468,57 @@ impl<'a, 'gcx> CheckTypeWellFormedVisitor<'a, 'gcx> {

debug!("check_method_receiver: sig={:?}", sig);

let self_ty = fcx.normalize_associated_types_in(span, &self_ty);
let self_ty = fcx.liberate_late_bound_regions(
method.def_id,
&ty::Binder(self_ty)
);

let self_arg_ty = sig.inputs()[0];
let rcvr_ty = match ExplicitSelf::determine(self_ty, self_arg_ty) {
ExplicitSelf::ByValue => self_ty,
ExplicitSelf::ByReference(region, mutbl) => {
fcx.tcx.mk_ref(region, ty::TypeAndMut {
ty: self_ty,
mutbl,
})

let cause = fcx.cause(span, ObligationCauseCode::MethodReceiver);
let self_arg_ty = fcx.normalize_associated_types_in(span, &self_arg_ty);
let self_arg_ty = fcx.liberate_late_bound_regions(
method.def_id,
&ty::Binder(self_arg_ty)
);

let mut autoderef = fcx.autoderef(span, self_arg_ty);

loop {
if let Some((potential_self_ty, _)) = autoderef.next() {
debug!("check_method_receiver: potential self type `{:?}` to match `{:?}`",
potential_self_ty, self_ty);

if fcx.infcx.can_eq(fcx.param_env, self_ty, potential_self_ty).is_ok() {
autoderef.finalize();
if let Some(mut err) = fcx.demand_eqtype_with_origin(
&cause, self_ty, potential_self_ty) {
err.emit();
}
break
}
} else {
fcx.tcx.sess.diagnostic().mut_span_err(
span, &format!("invalid `self` type: {:?}", self_arg_ty))
.note(&format!("type must be `{:?}` or a type that dereferences to it`", self_ty))
.help("consider changing to `self`, `&self`, `&mut self`, or `self: Box<Self>`")
.code(DiagnosticId::Error("E0307".into()))
.emit();
return
}
ExplicitSelf::ByBox => fcx.tcx.mk_box(self_ty)
};
let rcvr_ty = fcx.normalize_associated_types_in(span, &rcvr_ty);
let rcvr_ty = fcx.liberate_late_bound_regions(method.def_id,
&ty::Binder(rcvr_ty));
}

debug!("check_method_receiver: receiver ty = {:?}", rcvr_ty);
let is_self_ty = |ty| fcx.infcx.can_eq(fcx.param_env, self_ty, ty).is_ok();
let self_kind = ExplicitSelf::determine(self_arg_ty, is_self_ty);

let cause = fcx.cause(span, ObligationCauseCode::MethodReceiver);
if let Some(mut err) = fcx.demand_eqtype_with_origin(&cause, rcvr_ty, self_arg_ty) {
err.emit();
if let ExplicitSelf::Other = self_kind {
if !fcx.tcx.sess.features.borrow().arbitrary_self_types {
feature_gate::feature_err(&fcx.tcx.sess.parse_sess, "arbitrary_self_types", span,
GateIssue::Language, "arbitrary `self` types are unstable")
.help("consider changing to `self`, `&self`, `&mut self`, or `self: Box<Self>`")
.emit();
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/librustc_typeck/diagnostics.rs
Expand Up @@ -4724,6 +4724,7 @@ register_diagnostics! {
// E0247,
// E0248, // value used as a type, now reported earlier during resolution as E0412
// E0249,
E0307, // invalid method `self` type
// E0319, // trait impls for defaulted traits allowed just for structs/enums
// E0372, // coherence not object safe
E0377, // the trait `CoerceUnsized` may only be implemented for a coercion
Expand Down
3 changes: 3 additions & 0 deletions src/libsyntax/feature_gate.rs
Expand Up @@ -409,6 +409,9 @@ declare_features! (

// extern types
(active, extern_types, "1.23.0", Some(43467)),

// Allow trait methods with arbitrary self types
(active, arbitrary_self_types, "1.23.0", Some(44874)),
);

declare_features! (
Expand Down

0 comments on commit b087ded

Please sign in to comment.