|
| 1 | +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT |
| 2 | +// file at the top-level directory of this distribution and at |
| 3 | +// http://rust-lang.org/COPYRIGHT. |
| 4 | +// |
| 5 | +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 6 | +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 7 | +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| 8 | +// option. This file may not be copied, modified, or distributed |
| 9 | +// except according to those terms. |
| 10 | + |
| 11 | +//! "Object safety" refers to the ability for a trait to be converted |
| 12 | +//! to an object. In general, traits may only be converted to an |
| 13 | +//! object if all of their methods meet certain criteria. In particular, |
| 14 | +//! they must: |
| 15 | +//! |
| 16 | +//! - have a suitable receiver from which we can extract a vtable; |
| 17 | +//! - not reference the erased type `Self` except for in this receiver; |
| 18 | +//! - not have generic type parameters |
| 19 | +
|
| 20 | +use super::supertraits; |
| 21 | +use super::elaborate_predicates; |
| 22 | + |
| 23 | +use middle::subst::{mod, SelfSpace}; |
| 24 | +use middle::traits; |
| 25 | +use middle::ty::{mod, Ty}; |
| 26 | +use std::rc::Rc; |
| 27 | +use syntax::ast; |
| 28 | +use util::ppaux::Repr; |
| 29 | + |
| 30 | +pub enum ObjectSafetyViolation<'tcx> { |
| 31 | + /// Self : Sized declared on the trait |
| 32 | + SizedSelf, |
| 33 | + |
| 34 | + /// Method has someting illegal |
| 35 | + Method(Rc<ty::Method<'tcx>>, MethodViolationCode), |
| 36 | +} |
| 37 | + |
| 38 | +/// Reasons a method might not be object-safe. |
| 39 | +#[deriving(Copy,Clone,Show)] |
| 40 | +pub enum MethodViolationCode { |
| 41 | + /// fn(self), |
| 42 | + ByValueSelf, |
| 43 | + |
| 44 | + // fn foo() |
| 45 | + StaticMethod, |
| 46 | + |
| 47 | + // fn foo(&self, x: Self) |
| 48 | + // fn foo(&self) -> Self |
| 49 | + ReferencesSelf, |
| 50 | + |
| 51 | + // fn foo<A>(), |
| 52 | + Generic, |
| 53 | +} |
| 54 | + |
| 55 | +pub fn is_object_safe<'tcx>(tcx: &ty::ctxt<'tcx>, |
| 56 | + trait_ref: ty::PolyTraitRef<'tcx>) |
| 57 | + -> bool |
| 58 | +{ |
| 59 | + // Because we query yes/no results frequently, we keep a cache: |
| 60 | + let cached_result = |
| 61 | + tcx.object_safety_cache.borrow().get(&trait_ref.def_id()).map(|&r| r); |
| 62 | + |
| 63 | + let result = |
| 64 | + cached_result.unwrap_or_else(|| { |
| 65 | + let result = object_safety_violations(tcx, trait_ref.clone()).is_empty(); |
| 66 | + |
| 67 | + // Record just a yes/no result in the cache; this is what is |
| 68 | + // queried most frequently. Note that this may overwrite a |
| 69 | + // previous result, but always with the same thing. |
| 70 | + tcx.object_safety_cache.borrow_mut().insert(trait_ref.def_id(), result); |
| 71 | + |
| 72 | + result |
| 73 | + }); |
| 74 | + |
| 75 | + debug!("is_object_safe({}) = {}", trait_ref.repr(tcx), result); |
| 76 | + |
| 77 | + result |
| 78 | +} |
| 79 | + |
| 80 | +pub fn object_safety_violations<'tcx>(tcx: &ty::ctxt<'tcx>, |
| 81 | + sub_trait_ref: ty::PolyTraitRef<'tcx>) |
| 82 | + -> Vec<ObjectSafetyViolation<'tcx>> |
| 83 | +{ |
| 84 | + supertraits(tcx, sub_trait_ref) |
| 85 | + .flat_map(|tr| object_safety_violations_for_trait(tcx, tr.def_id()).into_iter()) |
| 86 | + .collect() |
| 87 | +} |
| 88 | + |
| 89 | +fn object_safety_violations_for_trait<'tcx>(tcx: &ty::ctxt<'tcx>, |
| 90 | + trait_def_id: ast::DefId) |
| 91 | + -> Vec<ObjectSafetyViolation<'tcx>> |
| 92 | +{ |
| 93 | + // Check methods for violations. |
| 94 | + let mut violations: Vec<_> = |
| 95 | + ty::trait_items(tcx, trait_def_id).iter() |
| 96 | + .flat_map(|item| { |
| 97 | + match *item { |
| 98 | + ty::MethodTraitItem(ref m) => { |
| 99 | + object_safety_violations_for_method(tcx, trait_def_id, &**m) |
| 100 | + .map(|code| ObjectSafetyViolation::Method(m.clone(), code)) |
| 101 | + .into_iter() |
| 102 | + } |
| 103 | + ty::TypeTraitItem(_) => { |
| 104 | + None.into_iter() |
| 105 | + } |
| 106 | + } |
| 107 | + }) |
| 108 | + .collect(); |
| 109 | + |
| 110 | + // Check the trait itself. |
| 111 | + if trait_has_sized_self(tcx, trait_def_id) { |
| 112 | + violations.push(ObjectSafetyViolation::SizedSelf); |
| 113 | + } |
| 114 | + |
| 115 | + debug!("object_safety_violations_for_trait(trait_def_id={}) = {}", |
| 116 | + trait_def_id.repr(tcx), |
| 117 | + violations.repr(tcx)); |
| 118 | + |
| 119 | + violations |
| 120 | +} |
| 121 | + |
| 122 | +fn trait_has_sized_self<'tcx>(tcx: &ty::ctxt<'tcx>, |
| 123 | + trait_def_id: ast::DefId) |
| 124 | + -> bool |
| 125 | +{ |
| 126 | + let trait_def = ty::lookup_trait_def(tcx, trait_def_id); |
| 127 | + let param_env = ty::construct_parameter_environment(tcx, |
| 128 | + &trait_def.generics, |
| 129 | + ast::DUMMY_NODE_ID); |
| 130 | + let predicates = param_env.caller_bounds.predicates.as_slice().to_vec(); |
| 131 | + let sized_def_id = match tcx.lang_items.sized_trait() { |
| 132 | + Some(def_id) => def_id, |
| 133 | + None => { return false; /* No Sized trait, can't require it! */ } |
| 134 | + }; |
| 135 | + |
| 136 | + // Search for a predicate like `Self : Sized` amongst the trait bounds. |
| 137 | + elaborate_predicates(tcx, predicates) |
| 138 | + .any(|predicate| { |
| 139 | + match predicate { |
| 140 | + ty::Predicate::Trait(ref trait_pred) if trait_pred.def_id() == sized_def_id => { |
| 141 | + let self_ty = trait_pred.0.self_ty(); |
| 142 | + match self_ty.sty { |
| 143 | + ty::ty_param(ref data) => data.space == subst::SelfSpace, |
| 144 | + _ => false, |
| 145 | + } |
| 146 | + } |
| 147 | + ty::Predicate::Projection(..) | |
| 148 | + ty::Predicate::Trait(..) | |
| 149 | + ty::Predicate::Equate(..) | |
| 150 | + ty::Predicate::RegionOutlives(..) | |
| 151 | + ty::Predicate::TypeOutlives(..) => { |
| 152 | + false |
| 153 | + } |
| 154 | + } |
| 155 | + }) |
| 156 | +} |
| 157 | + |
| 158 | +fn object_safety_violations_for_method<'tcx>(tcx: &ty::ctxt<'tcx>, |
| 159 | + trait_def_id: ast::DefId, |
| 160 | + method: &ty::Method<'tcx>) |
| 161 | + -> Option<MethodViolationCode> |
| 162 | +{ |
| 163 | + // The method's first parameter must be something that derefs to |
| 164 | + // `&self`. For now, we only accept `&self` and `Box<Self>`. |
| 165 | + match method.explicit_self { |
| 166 | + ty::ByValueExplicitSelfCategory => { |
| 167 | + return Some(MethodViolationCode::ByValueSelf); |
| 168 | + } |
| 169 | + |
| 170 | + ty::StaticExplicitSelfCategory => { |
| 171 | + return Some(MethodViolationCode::StaticMethod); |
| 172 | + } |
| 173 | + |
| 174 | + ty::ByReferenceExplicitSelfCategory(..) | |
| 175 | + ty::ByBoxExplicitSelfCategory => { |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + // The `Self` type is erased, so it should not appear in list of |
| 180 | + // arguments or return type apart from the receiver. |
| 181 | + let ref sig = method.fty.sig; |
| 182 | + for &input_ty in sig.0.inputs[1..].iter() { |
| 183 | + if contains_illegal_self_type_reference(tcx, trait_def_id, input_ty) { |
| 184 | + return Some(MethodViolationCode::ReferencesSelf); |
| 185 | + } |
| 186 | + } |
| 187 | + if let ty::FnConverging(result_type) = sig.0.output { |
| 188 | + if contains_illegal_self_type_reference(tcx, trait_def_id, result_type) { |
| 189 | + return Some(MethodViolationCode::ReferencesSelf); |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + // We can't monomorphize things like `fn foo<A>(...)`. |
| 194 | + if !method.generics.types.is_empty_in(subst::FnSpace) { |
| 195 | + return Some(MethodViolationCode::Generic); |
| 196 | + } |
| 197 | + |
| 198 | + None |
| 199 | +} |
| 200 | + |
| 201 | +fn contains_illegal_self_type_reference<'tcx>(tcx: &ty::ctxt<'tcx>, |
| 202 | + trait_def_id: ast::DefId, |
| 203 | + ty: Ty<'tcx>) |
| 204 | + -> bool |
| 205 | +{ |
| 206 | + // This is somewhat subtle. In general, we want to forbid |
| 207 | + // references to `Self` in the argument and return types, |
| 208 | + // since the value of `Self` is erased. However, there is one |
| 209 | + // exception: it is ok to reference `Self` in order to access |
| 210 | + // an associated type of the current trait, since we retain |
| 211 | + // the value of those associated types in the object type |
| 212 | + // itself. |
| 213 | + // |
| 214 | + // ```rust |
| 215 | + // trait SuperTrait { |
| 216 | + // type X; |
| 217 | + // } |
| 218 | + // |
| 219 | + // trait Trait : SuperTrait { |
| 220 | + // type Y; |
| 221 | + // fn foo(&self, x: Self) // bad |
| 222 | + // fn foo(&self) -> Self // bad |
| 223 | + // fn foo(&self) -> Option<Self> // bad |
| 224 | + // fn foo(&self) -> Self::Y // OK, desugars to next example |
| 225 | + // fn foo(&self) -> <Self as Trait>::Y // OK |
| 226 | + // fn foo(&self) -> Self::X // OK, desugars to next example |
| 227 | + // fn foo(&self) -> <Self as SuperTrait>::X // OK |
| 228 | + // } |
| 229 | + // ``` |
| 230 | + // |
| 231 | + // However, it is not as simple as allowing `Self` in a projected |
| 232 | + // type, because there are illegal ways to use `Self` as well: |
| 233 | + // |
| 234 | + // ```rust |
| 235 | + // trait Trait : SuperTrait { |
| 236 | + // ... |
| 237 | + // fn foo(&self) -> <Self as SomeOtherTrait>::X; |
| 238 | + // } |
| 239 | + // ``` |
| 240 | + // |
| 241 | + // Here we will not have the type of `X` recorded in the |
| 242 | + // object type, and we cannot resolve `Self as SomeOtherTrait` |
| 243 | + // without knowing what `Self` is. |
| 244 | + |
| 245 | + let mut supertraits: Option<Vec<ty::PolyTraitRef<'tcx>>> = None; |
| 246 | + let mut error = false; |
| 247 | + ty::maybe_walk_ty(ty, |ty| { |
| 248 | + match ty.sty { |
| 249 | + ty::ty_param(ref param_ty) => { |
| 250 | + if param_ty.space == SelfSpace { |
| 251 | + error = true; |
| 252 | + } |
| 253 | + |
| 254 | + false // no contained types to walk |
| 255 | + } |
| 256 | + |
| 257 | + ty::ty_projection(ref data) => { |
| 258 | + // This is a projected type `<Foo as SomeTrait>::X`. |
| 259 | + |
| 260 | + // Compute supertraits of current trait lazilly. |
| 261 | + if supertraits.is_none() { |
| 262 | + let trait_def = ty::lookup_trait_def(tcx, trait_def_id); |
| 263 | + let trait_ref = ty::Binder(trait_def.trait_ref.clone()); |
| 264 | + supertraits = Some(traits::supertraits(tcx, trait_ref).collect()); |
| 265 | + } |
| 266 | + |
| 267 | + // Determine whether the trait reference `Foo as |
| 268 | + // SomeTrait` is in fact a supertrait of the |
| 269 | + // current trait. In that case, this type is |
| 270 | + // legal, because the type `X` will be specified |
| 271 | + // in the object type. Note that we can just use |
| 272 | + // direct equality here because all of these types |
| 273 | + // are part of the formal parameter listing, and |
| 274 | + // hence there should be no inference variables. |
| 275 | + let projection_trait_ref = ty::Binder(data.trait_ref.clone()); |
| 276 | + let is_supertrait_of_current_trait = |
| 277 | + supertraits.as_ref().unwrap().contains(&projection_trait_ref); |
| 278 | + |
| 279 | + if is_supertrait_of_current_trait { |
| 280 | + false // do not walk contained types, do not report error, do collect $200 |
| 281 | + } else { |
| 282 | + true // DO walk contained types, POSSIBLY reporting an error |
| 283 | + } |
| 284 | + } |
| 285 | + |
| 286 | + _ => true, // walk contained types, if any |
| 287 | + } |
| 288 | + }); |
| 289 | + |
| 290 | + error |
| 291 | +} |
| 292 | + |
| 293 | +impl<'tcx> Repr<'tcx> for ObjectSafetyViolation<'tcx> { |
| 294 | + fn repr(&self, tcx: &ty::ctxt<'tcx>) -> String { |
| 295 | + match *self { |
| 296 | + ObjectSafetyViolation::SizedSelf => |
| 297 | + format!("SizedSelf"), |
| 298 | + ObjectSafetyViolation::Method(ref m, code) => |
| 299 | + format!("Method({},{})", m.repr(tcx), code), |
| 300 | + } |
| 301 | + } |
| 302 | +} |
0 commit comments