diff --git a/src/librustc_codegen_llvm/abi.rs b/src/librustc_codegen_llvm/abi.rs index 7c7662a88de53..1662b57b8b319 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` 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` + // 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 d98b7869ae98e..0cb4963f97fae 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 05a83dd307c38..eba73b1da9aab 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::>(); + + 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::>() + .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, + ®ion_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 f57d050fa2d77..2cfca345c27f8 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(*const T); + +impl CoerceUnsized> for Ptr +where + T: Unsize, +{} + +impl CoerceSized> for Ptr +where + T: Unsize, +{} +``` + +``` +#![feature(coerce_unsized, coerce_sized)] +use std::ops::{CoerceUnsized, CoerceSized}; + +struct Wrapper { + ptr: T, + _phantom: PhantomData<()>, +} + +impl CoerceUnsized> for Wrapper +where + T: CoerceUnsized, +{} + +impl CoerceSized> for Wrapper +where + T: CoerceUnsized, + U: CoerceSized, +{} +``` + +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 { + ptr: T, + extra_stuff: i32, +} + +impl CoerceUnsized> for WrapperWithExtraField +where + T: CoerceUnsized, +{} + +impl CoerceSized> for WrapperWithExtraField +where + T: CoerceUnsized, + U: CoerceSized, +{} +``` +"##, + E0390: r##" You tried to implement methods for a primitive type. Erroneous code example: