about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Hewson <michael@michaelhewson.ca>2018-09-20 03:16:10 -0400
committerMichael Hewson <michael@michaelhewson.ca>2018-11-01 18:15:20 -0400
commit9f59da28648c57d7c4fcac371f9f86adddeb20a3 (patch)
treef5e99d63d7c3499f09b01054df0a0284bd69c25c
parentd5c2c4a4339b2a6c64d16282d85bfd27bf01d361 (diff)
downloadrust-9f59da28648c57d7c4fcac371f9f86adddeb20a3.tar.gz
rust-9f59da28648c57d7c4fcac371f9f86adddeb20a3.zip
Implement object-safety for arbitrary_self_types: part 2
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.
-rw-r--r--src/librustc_codegen_llvm/abi.rs49
-rw-r--r--src/librustc_codegen_llvm/mir/block.rs40
-rw-r--r--src/librustc_typeck/coherence/builtin.rs162
-rw-r--r--src/librustc_typeck/diagnostics.rs74
4 files changed, 304 insertions, 21 deletions
diff --git a/src/librustc_codegen_llvm/abi.rs b/src/librustc_codegen_llvm/abi.rs
index 7c7662a88de..1662b57b8b3 100644
--- a/src/librustc_codegen_llvm/abi.rs
+++ b/src/librustc_codegen_llvm/abi.rs
@@ -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;
 
@@ -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)
         })
diff --git a/src/librustc_codegen_llvm/mir/block.rs b/src/librustc_codegen_llvm/mir/block.rs
index d98b7869ae9..0cb4963f97f 100644
--- a/src/librustc_codegen_llvm/mir/block.rs
+++ b/src/librustc_codegen_llvm/mir/block.rs
@@ -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)
diff --git a/src/librustc_typeck/coherence/builtin.rs b/src/librustc_typeck/coherence/builtin.rs
index 05a83dd307c..eba73b1da9a 100644
--- a/src/librustc_typeck/coherence/builtin.rs
+++ b/src/librustc_typeck/coherence/builtin.rs
@@ -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> {
@@ -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 {
diff --git a/src/librustc_typeck/diagnostics.rs b/src/librustc_typeck/diagnostics.rs
index f57d050fa2d..2cfca345c27 100644
--- a/src/librustc_typeck/diagnostics.rs
+++ b/src/librustc_typeck/diagnostics.rs
@@ -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: