Skip to content

Commit

Permalink
Implement object-safety for arbitrary_self_types: part 2
Browse files Browse the repository at this point in the history
For now, all of the receivers that we care about are just a newtyped
pointer — i.e. `Box<Self>`, `Rc<Self>`, `Pin<Box<Self>>`, `Pin<&mut
Self>`. This is much simpler to implement in codeine than the more
general case, because the ABI is the same as a pointer. So we add some
checks in typeck/coherence/builtin.rs to make sure that implementors of
CoerceSized are just newtyped pointers. In this commit, we also
implement the codegen bits.
  • Loading branch information
mikeyhew committed Nov 1, 2018
1 parent d5c2c4a commit 9f59da2
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 21 deletions.
49 changes: 36 additions & 13 deletions src/librustc_codegen_llvm/abi.rs
Expand Up @@ -19,7 +19,7 @@ use type_::Type;
use type_of::{LayoutLlvmExt, PointerKind};
use value::Value;

use rustc_target::abi::{LayoutOf, Size, TyLayout};
use rustc_target::abi::{LayoutOf, Size, TyLayout, Abi as LayoutAbi};
use rustc::ty::{self, Ty};
use rustc::ty::layout;

Expand Down Expand Up @@ -302,21 +302,44 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
FnType::new_internal(cx, sig, extra_args, |ty, arg_idx| {
let mut layout = cx.layout_of(ty);
// Don't pass the vtable, it's not an argument of the virtual fn.
// Instead, pass just the (thin pointer) first field of `*dyn Trait`.
// Instead, pass just the data pointer, but give it the type `*const/mut dyn Trait`
// or `&/&mut dyn Trait` because this is special-cased elsewhere in codegen
if arg_idx == Some(0) {
// FIXME(eddyb) `layout.field(cx, 0)` is not enough because e.g.
// `Box<dyn Trait>` has a few newtype wrappers around the raw
// pointer, so we'd have to "dig down" to find `*dyn Trait`.
let pointee = if layout.is_unsized() {
layout.ty
let fat_pointer_ty = if layout.is_unsized() {
// unsized `self` is passed as a pointer to `self`
// FIXME (mikeyhew) change this to use &own if it is ever added to the language
cx.tcx.mk_mut_ptr(layout.ty)
} else {
layout.ty.builtin_deref(true)
.unwrap_or_else(|| {
bug!("FnType::new_vtable: non-pointer self {:?}", layout)
}).ty
match layout.abi {
LayoutAbi::ScalarPair(..) => (),
_ => bug!("receiver type has unsupported layout: {:?}", layout)
}

let mut fat_pointer_layout = layout;
'descend_newtypes: while !fat_pointer_layout.ty.is_unsafe_ptr()
&& !fat_pointer_layout.ty.is_region_ptr()
{
'iter_fields: for i in 0..fat_pointer_layout.fields.count() {
let field_layout = fat_pointer_layout.field(cx, i);

if !field_layout.is_zst() {
fat_pointer_layout = field_layout;
continue 'descend_newtypes
}
}

bug!("receiver has no non-zero-sized fields {:?}", fat_pointer_layout);
}

fat_pointer_layout.ty
};
let fat_ptr_ty = cx.tcx.mk_mut_ptr(pointee);
layout = cx.layout_of(fat_ptr_ty).field(cx, 0);

// we now have a type like `*mut RcBox<dyn Trait>`
// change its layout to that of `*mut ()`, a thin pointer, but keep the same type
// this is understood as a special case elsewhere in the compiler
let unit_pointer_ty = cx.tcx.mk_mut_ptr(cx.tcx.mk_unit());
layout = cx.layout_of(unit_pointer_ty);
layout.ty = fat_pointer_ty;
}
ArgType::new(layout)
})
Expand Down
40 changes: 34 additions & 6 deletions src/librustc_codegen_llvm/mir/block.rs
Expand Up @@ -642,14 +642,42 @@ impl FunctionCx<'a, 'll, 'tcx> {
(&args[..], None)
};

for (i, arg) in first_args.iter().enumerate() {
'make_args: for (i, arg) in first_args.iter().enumerate() {
let mut op = self.codegen_operand(&bx, arg);

if let (0, Some(ty::InstanceDef::Virtual(_, idx))) = (i, def) {
if let Pair(data_ptr, meta) = op.val {
llfn = Some(meth::VirtualIndex::from_index(idx)
.get_fn(&bx, meta, &fn_ty));
llargs.push(data_ptr);
continue;
if let Pair(..) = op.val {
// descend through newtype wrappers until `op` is a builtin pointer to
// `dyn Trait`, e.g. `*const dyn Trait`, `&mut dyn Trait`
'descend_newtypes: while !op.layout.ty.is_unsafe_ptr()
&& !op.layout.ty.is_region_ptr()
{
'iter_fields: for i in 0..op.layout.fields.count() {
let field = op.extract_field(&bx, i);
if !field.layout.is_zst() {
// we found the one non-zero-sized field that is allowed
// now find *its* non-zero-sized field, or stop if it's a
// pointer
op = field;
continue 'descend_newtypes
}
}

span_bug!(span, "receiver has no non-zero-sized fields {:?}", op);
}

// now that we have `*dyn Trait` or `&dyn Trait`, split it up into its
// data pointer and vtable. Look up the method in the vtable, and pass
// the data pointer as the first argument
match op.val {
Pair(data_ptr, meta) => {
llfn = Some(meth::VirtualIndex::from_index(idx)
.get_fn(&bx, meta, &fn_ty));
llargs.push(data_ptr);
continue 'make_args
}
other => bug!("expected a Pair, got {:?}", other)
}
} else if let Ref(data_ptr, Some(meta), _) = op.val {
// by-value dynamic dispatch
llfn = Some(meth::VirtualIndex::from_index(idx)
Expand Down
162 changes: 160 additions & 2 deletions src/librustc_typeck/coherence/builtin.rs
Expand Up @@ -31,8 +31,8 @@ pub fn check_trait<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, trait_def_id: DefId) {
Checker { tcx, trait_def_id }
.check(tcx.lang_items().drop_trait(), visit_implementation_of_drop)
.check(tcx.lang_items().copy_trait(), visit_implementation_of_copy)
.check(tcx.lang_items().coerce_unsized_trait(),
visit_implementation_of_coerce_unsized);
.check(tcx.lang_items().coerce_unsized_trait(), visit_implementation_of_coerce_unsized)
.check(tcx.lang_items().coerce_sized_trait(), visit_implementation_of_coerce_sized);
}

struct Checker<'a, 'tcx: 'a> {
Expand Down Expand Up @@ -162,6 +162,164 @@ fn visit_implementation_of_coerce_unsized<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
}
}

fn visit_implementation_of_coerce_sized<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, impl_did: DefId) {
debug!("visit_implementation_of_coerce_sized: impl_did={:?}",
impl_did);
if impl_did.is_local() {
let coerce_sized_trait = tcx.lang_items().coerce_sized_trait().unwrap();

let impl_node_id = tcx.hir.as_local_node_id(impl_did).unwrap();
let span = tcx.hir.span(impl_node_id);

let source = tcx.type_of(impl_did);
assert!(!source.has_escaping_regions());
let target = {
let trait_ref = tcx.impl_trait_ref(impl_did).unwrap();
assert_eq!(trait_ref.def_id, coerce_sized_trait);

trait_ref.substs.type_at(1)
};

debug!("visit_implementation_of_coerce_sized: {:?} -> {:?}",
source,
target);

let param_env = tcx.param_env(impl_did);

let create_err = |msg: &str| {
struct_span_err!(tcx.sess, span, E0378, "{}", msg)
};

tcx.infer_ctxt().enter(|infcx| {
let cause = ObligationCause::misc(span, impl_node_id);

use ty::TyKind::*;
match (&source.sty, &target.sty) {
(&Ref(r_a, _, mutbl_a), Ref(r_b, _, mutbl_b))
if infcx.at(&cause, param_env).eq(r_a, r_b).is_ok()
&& mutbl_a == *mutbl_b => (),
(&RawPtr(tm_a), &RawPtr(tm_b))
if tm_a.mutbl == tm_b.mutbl => (),
(&Adt(def_a, substs_a), &Adt(def_b, substs_b))
if def_a.is_struct() && def_b.is_struct() =>
{
if def_a != def_b {
let source_path = tcx.item_path_str(def_a.did);
let target_path = tcx.item_path_str(def_b.did);

create_err(
&format!(
"the trait `CoerceSized` may only be implemented \
for a coercion between structures with the same \
definition; expected {}, found {}",
source_path, target_path,
)
).emit();

return
}

let fields = &def_a.non_enum_variant().fields;

let coerced_fields = fields.iter().filter_map(|field| {
if tcx.type_of(field.did).is_phantom_data() {
// ignore PhantomData fields
return None
}

let ty_a = field.ty(tcx, substs_a);
let ty_b = field.ty(tcx, substs_b);
if let Ok(ok) = infcx.at(&cause, param_env).eq(ty_a, ty_b) {
if ok.obligations.is_empty() {
create_err(
"the trait `CoerceSized` may only be implemented for structs \
containing the field being coerced, `PhantomData` fields, \
and nothing else"
).note(
&format!(
"extra field `{}` of type `{}` is not allowed",
field.ident, ty_a,
)
).emit();

return None;
}
}

Some(field)
}).collect::<Vec<_>>();

if coerced_fields.is_empty() {
create_err(
"the trait `CoerceSized` may only be implemented \
for a coercion between structures with a single field \
being coerced, none found"
).emit();
} else if coerced_fields.len() > 1 {
create_err(
"implementing the `CoerceSized` trait requires multiple coercions",
).note(
"the trait `CoerceSized` may only be implemented \
for a coercion between structures with a single field \
being coerced"
).note(
&format!(
"currently, {} fields need coercions: {}",
coerced_fields.len(),
coerced_fields.iter().map(|field| {
format!("{} ({} to {})",
field.ident,
field.ty(tcx, substs_a),
field.ty(tcx, substs_b),
)
}).collect::<Vec<_>>()
.join(", ")
)
).emit();
} else {
let mut fulfill_cx = TraitEngine::new(infcx.tcx);

for field in coerced_fields {

let predicate = tcx.predicate_for_trait_def(
param_env,
cause.clone(),
coerce_sized_trait,
0,
field.ty(tcx, substs_a),
&[field.ty(tcx, substs_b).into()]
);

fulfill_cx.register_predicate_obligation(&infcx, predicate);
}

// Check that all transitive obligations are satisfied.
if let Err(errors) = fulfill_cx.select_all_or_error(&infcx) {
infcx.report_fulfillment_errors(&errors, None, false);
}

// Finally, resolve all regions.
let region_scope_tree = region::ScopeTree::default();
let outlives_env = OutlivesEnvironment::new(param_env);
infcx.resolve_regions_and_report_errors(
impl_did,
&region_scope_tree,
&outlives_env,
SuppressRegionErrors::default(),
);
}
}
_ => {
create_err(
"the trait `CoerceSsized` may only be implemented \
for a coercion between structures"
).emit();
}
}
})
}
}

pub fn coerce_unsized_info<'a, 'gcx>(gcx: TyCtxt<'a, 'gcx, 'gcx>,
impl_did: DefId)
-> CoerceUnsizedInfo {
Expand Down
74 changes: 74 additions & 0 deletions src/librustc_typeck/diagnostics.rs
Expand Up @@ -3084,6 +3084,80 @@ containing the unsized type is the last and only unsized type field in the
struct.
"##,

E0378: r##"
The `CoerceSized` trait currently can only be implemented for builtin pointer
types and structs that are newtype wrappers around them — that is, the struct
must have only one field (except for`PhantomData`), and that field must itself
implement `CoerceSized`.
Examples:
```
#![feature(coerce_sized, unsize)]
use std::{
marker::Unsize,
ops::CoerceSized,
};
struct Ptr<T: ?Sized>(*const T);
impl<T: ?Sized, U: ?Sized> CoerceUnsized<Ptr<U>> for Ptr<T>
where
T: Unsize<U>,
{}
impl<T: ?Sized, U: ?Sized> CoerceSized<Ptr<T>> for Ptr<U>
where
T: Unsize<U>,
{}
```
```
#![feature(coerce_unsized, coerce_sized)]
use std::ops::{CoerceUnsized, CoerceSized};
struct Wrapper<T> {
ptr: T,
_phantom: PhantomData<()>,
}
impl<T, U> CoerceUnsized<Wrapper<U>> for Wrapper<T>
where
T: CoerceUnsized<U>,
{}
impl<T, U> CoerceSized<Wrapper<T>> for Wrapper<U>
where
T: CoerceUnsized<U>,
U: CoerceSized<T>,
{}
```
Example of illegal CoerceSized implementation
(illegal because of extra field)
```compile-fail,E0378
#![feature(coerce_unsized, coerce_sized)]
use std::ops::{CoerceUnsized, CoerceSized};
struct WrapperWithExtraField<T> {
ptr: T,
extra_stuff: i32,
}
impl<T, U> CoerceUnsized<WrapperWithExtraField<U>> for WrapperWithExtraField<T>
where
T: CoerceUnsized<U>,
{}
impl<T, U> CoerceSized<WrapperWithExtraField<T>> for WrapperWithExtraField<U>
where
T: CoerceUnsized<U>,
U: CoerceSized<T>,
{}
```
"##,

E0390: r##"
You tried to implement methods for a primitive type. Erroneous code example:
Expand Down

0 comments on commit 9f59da2

Please sign in to comment.