diff --git a/doc/tutorial.md b/doc/tutorial.md index a818689d7e3..0d542441103 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -751,25 +751,6 @@ match mypoint { } ~~~ -Structs are the only type in Rust that may have user-defined -destructors, defined with `drop` blocks. Inside a `drop`, the name -`self` refers to the struct's value. - -~~~ -struct TimeBomb { - explosivity: uint, - - drop { - for iter::repeat(self.explosivity) { - io::println(fmt!("blam!")); - } - } -} -~~~ - -> ***Note***: This destructor syntax is temporary. Eventually destructors -> will be defined for any type using [traits](#traits). - ## Enums Enums are datatypes that have several alternate representations. For @@ -1909,8 +1890,8 @@ traits are automatically derived and implemented for all applicable types by the compiler, and may not be overridden: * `Copy` - Types that can be copied: either implicitly, or explicitly with the - `copy` operator. All types are copyable unless they are classes - with destructors or otherwise contain classes with destructors. + `copy` operator. All types are copyable unless they have destructors or + contain types with destructors. * `Send` - Sendable (owned) types. All types are sendable unless they contain managed boxes, managed closures, or otherwise managed @@ -1922,6 +1903,28 @@ types by the compiler, and may not be overridden: > ***Note:*** These three traits were referred to as 'kinds' in earlier > iterations of the language, and often still are. +There is also a special trait known as `Drop`. This trait defines one method +called `finalize`, which is automatically called when value of the a type that +implements this trait is destroyed, either because the value went out of scope +or because the garbage collector reclaimed it. + +~~~ +struct TimeBomb { + explosivity: uint, +} + +impl TimeBomb : Drop { + fn finalize() { + for iter::repeat(self.explosivity) { + io::println("blam!"); + } + } +} +~~~ + +It is illegal to call `finalize` directly. Only code inserted by the compiler +may call it. + ## Declaring and implementing traits A trait consists of a set of methods, without bodies, or may be empty, diff --git a/src/libcore/core.rs b/src/libcore/core.rs index 12a426261c4..1be217dac9f 100644 --- a/src/libcore/core.rs +++ b/src/libcore/core.rs @@ -28,6 +28,8 @@ pub use to_str::ToStr; #[cfg(notest)] pub use ops::{Const, Copy, Send, Owned}; #[cfg(notest)] +pub use ops::{Drop}; +#[cfg(notest)] pub use ops::{Add, Sub, Mul, Div, Modulo, Neg, BitAnd, BitOr, BitXor}; #[cfg(notest)] pub use ops::{Shl, Shr, Index}; @@ -38,6 +40,8 @@ extern mod coreops(name = "core", vers = "0.5"); #[cfg(test)] pub use coreops::ops::{Const, Copy, Send, Owned}; #[cfg(test)] +pub use coreops::ops::{Drop}; +#[cfg(test)] pub use coreops::ops::{Add, Sub, Mul, Div, Modulo, Neg, BitAnd, BitOr}; #[cfg(test)] pub use coreops::ops::{BitXor}; diff --git a/src/libcore/ops.rs b/src/libcore/ops.rs index 7c6bcf5bd51..96b96d0f27a 100644 --- a/src/libcore/ops.rs +++ b/src/libcore/ops.rs @@ -23,6 +23,11 @@ pub trait Owned { // Empty. } +#[lang="drop"] +pub trait Drop { + fn finalize(); // XXX: Rename to "drop"? --pcwalton +} + #[lang="add"] pub trait Add { pure fn add(rhs: &RHS) -> Result; diff --git a/src/rustc/middle/lang_items.rs b/src/rustc/middle/lang_items.rs index 383fe2db323..b1d68bee259 100644 --- a/src/rustc/middle/lang_items.rs +++ b/src/rustc/middle/lang_items.rs @@ -28,6 +28,8 @@ struct LanguageItems { mut send_trait: Option, mut owned_trait: Option, + mut drop_trait: Option, + mut add_trait: Option, mut sub_trait: Option, mut mul_trait: Option, @@ -59,6 +61,8 @@ mod language_items { send_trait: None, owned_trait: None, + drop_trait: None, + add_trait: None, sub_trait: None, mul_trait: None, @@ -94,6 +98,8 @@ fn LanguageItemCollector(crate: @crate, session: Session, item_refs.insert(~"send", &mut items.send_trait); item_refs.insert(~"owned", &mut items.owned_trait); + item_refs.insert(~"drop", &mut items.drop_trait); + item_refs.insert(~"add", &mut items.add_trait); item_refs.insert(~"sub", &mut items.sub_trait); item_refs.insert(~"mul", &mut items.mul_trait); diff --git a/src/rustc/middle/ty.rs b/src/rustc/middle/ty.rs index 14c5ed5e71e..5e1bccb6c9d 100644 --- a/src/rustc/middle/ty.rs +++ b/src/rustc/middle/ty.rs @@ -404,7 +404,16 @@ type ctxt = // A mapping from the def ID of an impl to the IDs of the derived // methods within it. automatically_derived_methods_for_impl: - HashMap + HashMap, + + // A mapping from the def ID of an enum or struct type to the def ID + // of the method that implements its destructor. If the type is not + // present in this map, it does not have a destructor. This map is + // populated during the coherence phase of typechecking. + destructor_for_type: HashMap, + + // A method will be in this list if and only if it is a destructor. + destructors: HashMap }; enum tbox_flag { @@ -921,7 +930,9 @@ fn mk_ctxt(s: session::Session, deriving_struct_methods: HashMap(), deriving_enum_methods: HashMap(), automatically_derived_methods: HashMap(), - automatically_derived_methods_for_impl: HashMap()} + automatically_derived_methods_for_impl: HashMap(), + destructor_for_type: HashMap(), + destructors: HashMap()} } @@ -3580,6 +3591,11 @@ fn item_path_str(cx: ctxt, id: ast::def_id) -> ~str { /* If class_id names a class with a dtor, return Some(the dtor's id). Otherwise return none. */ fn ty_dtor(cx: ctxt, class_id: def_id) -> Option { + match cx.destructor_for_type.find(class_id) { + Some(method_def_id) => return Some(method_def_id), + None => {} // Continue. + } + if is_local(class_id) { match cx.items.find(class_id.node) { Some(ast_map::node_item(@{ diff --git a/src/rustc/middle/typeck/check/method.rs b/src/rustc/middle/typeck/check/method.rs index dd3f4069a9c..8afd28a5914 100644 --- a/src/rustc/middle/typeck/check/method.rs +++ b/src/rustc/middle/typeck/check/method.rs @@ -774,6 +774,7 @@ impl LookupContext { let fty = self.fn_ty_from_origin(&candidate.origin); self.enforce_trait_instance_limitations(fty, candidate); + self.enforce_drop_trait_limitations(candidate); // before we only checked whether self_ty could be a subtype // of rcvr_ty; now we actually make it so (this may cause @@ -858,6 +859,25 @@ impl LookupContext { } } + fn enforce_drop_trait_limitations(&self, candidate: &Candidate) { + // No code can call the finalize method explicitly. + let bad; + match candidate.origin { + method_static(method_id) | method_self(method_id, _) => { + bad = self.tcx().destructors.contains_key(method_id); + } + method_param({trait_id: trait_id, _}) | + method_trait(trait_id, _, _) => { + bad = self.tcx().destructor_for_type.contains_key(trait_id); + } + } + + if bad { + self.tcx().sess.span_err(self.expr.span, + ~"explicit call to destructor"); + } + } + fn is_relevant(&self, self_ty: ty::t, candidate: &Candidate) -> bool { debug!("is_relevant(self_ty=%s, candidate=%s)", self.ty_to_str(self_ty), self.cand_to_str(candidate)); diff --git a/src/rustc/middle/typeck/coherence.rs b/src/rustc/middle/typeck/coherence.rs index d0ec2b3cfc5..26c559fd135 100644 --- a/src/rustc/middle/typeck/coherence.rs +++ b/src/rustc/middle/typeck/coherence.rs @@ -228,6 +228,11 @@ impl CoherenceChecker { // coherence checks, because we ensure by construction that no errors // can happen at link time. self.add_external_crates(); + + // Populate the table of destructors. It might seem a bit strange to + // do this here, but it's actually the most convenient place, since + // the coherence tables contain the trait -> type mappings. + self.populate_destructor_table(); } fn check_implementation(item: @item, associated_traits: ~[@trait_ref]) { @@ -913,6 +918,58 @@ impl CoherenceChecker { } } } + + // + // Destructors + // + + fn populate_destructor_table() { + let coherence_info = &self.crate_context.coherence_info; + let tcx = self.crate_context.tcx; + let drop_trait = tcx.lang_items.drop_trait.get(); + let impls_opt = coherence_info.extension_methods.find(drop_trait); + + let impls; + match impls_opt { + None => return, // No types with (new-style) destructors present. + Some(found_impls) => impls = found_impls + } + + for impls.each |impl_info| { + if impl_info.methods.len() < 1 { + // We'll error out later. For now, just don't ICE. + loop; + } + let method_def_id = impl_info.methods[0].did; + + let self_type = self.get_self_type_for_implementation(*impl_info); + match ty::get(self_type.ty).sty { + ty::ty_class(type_def_id, _) => { + tcx.destructor_for_type.insert(type_def_id, method_def_id); + tcx.destructors.insert(method_def_id, ()); + } + _ => { + // Destructors only work on nominal types. + if impl_info.did.crate == ast::local_crate { + match tcx.items.find(impl_info.did.node) { + Some(ast_map::node_item(@item, _)) => { + tcx.sess.span_err(item.span, + ~"the Drop trait may only \ + be implemented on \ + structures"); + } + _ => { + tcx.sess.bug(~"didn't find impl in ast map"); + } + } + } else { + tcx.sess.bug(~"found external impl of Drop trait on \ + something other than a struct"); + } + } + } + } + } } fn check_coherence(crate_context: @crate_ctxt, crate: @crate) { diff --git a/src/test/compile-fail/drop-on-non-struct.rs b/src/test/compile-fail/drop-on-non-struct.rs new file mode 100644 index 00000000000..8ee72d9d2fd --- /dev/null +++ b/src/test/compile-fail/drop-on-non-struct.rs @@ -0,0 +1,12 @@ +type Foo = @[u8]; + +impl Foo : Drop { //~ ERROR the Drop trait may only be implemented + fn finalize() { + io::println("kaboom"); + } +} + +fn main() { +} + + diff --git a/src/test/compile-fail/explicit-call-to-dtor.rs b/src/test/compile-fail/explicit-call-to-dtor.rs new file mode 100644 index 00000000000..56af671852b --- /dev/null +++ b/src/test/compile-fail/explicit-call-to-dtor.rs @@ -0,0 +1,15 @@ +struct Foo { + x: int +} + +impl Foo : Drop { + fn finalize() { + io::println("kaboom"); + } +} + +fn main() { + let x = Foo { x: 3 }; + x.finalize(); //~ ERROR explicit call to destructor +} + diff --git a/src/test/compile-fail/explicit-call-to-supertrait-dtor.rs b/src/test/compile-fail/explicit-call-to-supertrait-dtor.rs new file mode 100644 index 00000000000..c7c2748235a --- /dev/null +++ b/src/test/compile-fail/explicit-call-to-supertrait-dtor.rs @@ -0,0 +1,25 @@ +struct Foo { + x: int +} + +trait Bar : Drop { + fn blah(); +} + +impl Foo : Drop { + fn finalize() { + io::println("kaboom"); + } +} + +impl Foo : Bar { + fn blah() { + self.finalize(); //~ ERROR explicit call to destructor + } +} + +fn main() { + let x = Foo { x: 3 }; +} + + diff --git a/src/test/run-pass/drop-trait.rs b/src/test/run-pass/drop-trait.rs new file mode 100644 index 00000000000..3a379a4c9dc --- /dev/null +++ b/src/test/run-pass/drop-trait.rs @@ -0,0 +1,14 @@ +struct Foo { + x: int +} + +impl Foo : Drop { + fn finalize() { + io::println("bye"); + } +} + +fn main() { + let x: Foo = Foo { x: 3 }; +} +