Skip to content

Commit 19dcecb

Browse files
committed
Refactor object-safety into its own (cached) module so that we can
check it more easily; also extend object safety to cover sized types as well as static methods. This makes it sufficient so that we can always ensure that `Foo : Foo` holds for any trait `Foo`.
1 parent 2c1d7a7 commit 19dcecb

File tree

14 files changed

+600
-271
lines changed

14 files changed

+600
-271
lines changed

src/librustc/middle/traits/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ pub use self::fulfill::{FulfillmentContext, RegionObligation};
2929
pub use self::project::MismatchedProjectionTypes;
3030
pub use self::project::normalize;
3131
pub use self::project::Normalized;
32+
pub use self::object_safety::is_object_safe;
33+
pub use self::object_safety::object_safety_violations;
34+
pub use self::object_safety::ObjectSafetyViolation;
35+
pub use self::object_safety::MethodViolationCode;
3236
pub use self::select::SelectionContext;
3337
pub use self::select::SelectionCache;
3438
pub use self::select::{MethodMatchResult, MethodMatched, MethodAmbiguous, MethodDidNotMatch};
@@ -45,6 +49,7 @@ mod coherence;
4549
mod error_reporting;
4650
mod fulfill;
4751
mod project;
52+
mod object_safety;
4853
mod select;
4954
mod util;
5055

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
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+
}

src/librustc/middle/ty.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,9 @@ pub struct ctxt<'tcx> {
827827
/// parameters are never placed into this cache, because their
828828
/// results are dependent on the parameter environment.
829829
pub type_impls_sized_cache: RefCell<HashMap<Ty<'tcx>,bool>>,
830+
831+
/// Caches whether traits are object safe
832+
pub object_safety_cache: RefCell<DefIdMap<bool>>,
830833
}
831834

832835
// Flags that we track on types. These flags are propagated upwards
@@ -2384,6 +2387,7 @@ pub fn mk_ctxt<'tcx>(s: Session,
23842387
repr_hint_cache: RefCell::new(DefIdMap::new()),
23852388
type_impls_copy_cache: RefCell::new(HashMap::new()),
23862389
type_impls_sized_cache: RefCell::new(HashMap::new()),
2390+
object_safety_cache: RefCell::new(DefIdMap::new()),
23872391
}
23882392
}
23892393

src/librustc_driver/pretty.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ impl PpSourceMode {
141141
}
142142
}
143143

144-
trait PrinterSupport<'ast>: pprust::PpAnn + Sized {
144+
trait PrinterSupport<'ast>: pprust::PpAnn {
145145
/// Provides a uniform interface for re-extracting a reference to a
146146
/// `Session` from a value that now owns it.
147147
fn sess<'a>(&'a self) -> &'a Session;
@@ -154,7 +154,7 @@ trait PrinterSupport<'ast>: pprust::PpAnn + Sized {
154154
///
155155
/// (Rust does not yet support upcasting from a trait object to
156156
/// an object for one of its super-traits.)
157-
fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn { self as &pprust::PpAnn }
157+
fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn;
158158
}
159159

160160
struct NoAnn<'ast> {
@@ -168,6 +168,8 @@ impl<'ast> PrinterSupport<'ast> for NoAnn<'ast> {
168168
fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map<'ast>> {
169169
self.ast_map.as_ref()
170170
}
171+
172+
fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn { self }
171173
}
172174

173175
impl<'ast> pprust::PpAnn for NoAnn<'ast> {}
@@ -183,6 +185,8 @@ impl<'ast> PrinterSupport<'ast> for IdentifiedAnnotation<'ast> {
183185
fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map<'ast>> {
184186
self.ast_map.as_ref()
185187
}
188+
189+
fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn { self }
186190
}
187191

188192
impl<'ast> pprust::PpAnn for IdentifiedAnnotation<'ast> {
@@ -232,6 +236,8 @@ impl<'ast> PrinterSupport<'ast> for HygieneAnnotation<'ast> {
232236
fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map<'ast>> {
233237
self.ast_map.as_ref()
234238
}
239+
240+
fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn { self }
235241
}
236242

237243
impl<'ast> pprust::PpAnn for HygieneAnnotation<'ast> {
@@ -265,6 +271,8 @@ impl<'tcx> PrinterSupport<'tcx> for TypedAnnotation<'tcx> {
265271
fn ast_map<'a>(&'a self) -> Option<&'a ast_map::Map<'tcx>> {
266272
Some(&self.analysis.ty_cx.map)
267273
}
274+
275+
fn pp_ann<'a>(&'a self) -> &'a pprust::PpAnn { self }
268276
}
269277

270278
impl<'tcx> pprust::PpAnn for TypedAnnotation<'tcx> {

0 commit comments

Comments
 (0)