diff options
| author | Brian Anderson <banderson@mozilla.com> | 2012-11-28 16:20:41 -0800 |
|---|---|---|
| committer | Brian Anderson <banderson@mozilla.com> | 2012-11-28 17:09:26 -0800 |
| commit | 65bd40e300323f1e4806dd84834b8d71bb89c08c (patch) | |
| tree | 53b9245b13ba9991627a0e23b8378a328af42ad4 | |
| parent | 9b95d511316ae7124046de61d19335fdfaa021cc (diff) | |
| download | rust-65bd40e300323f1e4806dd84834b8d71bb89c08c.tar.gz rust-65bd40e300323f1e4806dd84834b8d71bb89c08c.zip | |
Remove uses of #[merge]
| -rw-r--r-- | src/libcore/core.rc | 2 | ||||
| -rw-r--r-- | src/libcore/task.rs | 1301 | ||||
| -rw-r--r-- | src/libcore/task/mod.rs | 1304 | ||||
| -rw-r--r-- | src/librustc/driver.rs | 5 | ||||
| -rw-r--r-- | src/librustc/driver/mod.rs | 8 | ||||
| -rw-r--r-- | src/librustc/metadata.rs | 31 | ||||
| -rw-r--r-- | src/librustc/metadata/mod.rs | 33 | ||||
| -rw-r--r-- | src/librustc/middle/borrowck.rs | 618 | ||||
| -rw-r--r-- | src/librustc/middle/borrowck/mod.rs | 620 | ||||
| -rw-r--r-- | src/librustc/middle/typeck.rs | 376 | ||||
| -rw-r--r-- | src/librustc/middle/typeck/mod.rs | 377 | ||||
| -rw-r--r-- | src/librustc/rustc.rc | 10 | ||||
| -rw-r--r-- | src/libsyntax/ext/pipes.rs | 67 | ||||
| -rw-r--r-- | src/libsyntax/ext/pipes/mod.rs | 70 | ||||
| -rw-r--r-- | src/libsyntax/parse.rs | 196 | ||||
| -rw-r--r-- | src/libsyntax/parse/mod.rs | 198 | ||||
| -rw-r--r-- | src/libsyntax/syntax.rc | 5 |
17 files changed, 2614 insertions, 2607 deletions
diff --git a/src/libcore/core.rc b/src/libcore/core.rc index 4c5a56cc6ff..ccd102e9ca5 100644 --- a/src/libcore/core.rc +++ b/src/libcore/core.rc @@ -144,7 +144,7 @@ pub mod send_map; // Concurrency pub mod comm; -#[merge = "task/mod.rs"] +#[path = "task/mod.rs"] pub mod task; pub mod pipes; diff --git a/src/libcore/task.rs b/src/libcore/task.rs deleted file mode 100644 index a2b5c3229ad..00000000000 --- a/src/libcore/task.rs +++ /dev/null @@ -1,1301 +0,0 @@ -// NB: transitionary, de-mode-ing. -// tjc: Deprecated modes allowed because of function arg issue -// in task::spawn. Re-forbid after snapshot. -#[forbid(deprecated_pattern)]; - -/*! - * Task management. - * - * An executing Rust program consists of a tree of tasks, each with their own - * stack, and sole ownership of their allocated heap data. Tasks communicate - * with each other using ports and channels. - * - * When a task fails, that failure will propagate to its parent (the task - * that spawned it) and the parent will fail as well. The reverse is not - * true: when a parent task fails its children will continue executing. When - * the root (main) task fails, all tasks fail, and then so does the entire - * process. - * - * Tasks may execute in parallel and are scheduled automatically by the - * runtime. - * - * # Example - * - * ~~~ - * do spawn { - * log(error, "Hello, World!"); - * } - * ~~~ - */ - -use cmp::Eq; -use result::Result; -use pipes::{stream, Chan, Port}; -use local_data_priv::{local_get, local_set}; -use util::replace; - -use rt::task_id; -use rt::rust_task; - -/// A handle to a task -pub enum Task { - TaskHandle(task_id) -} - -impl Task : cmp::Eq { - pure fn eq(&self, other: &Task) -> bool { *(*self) == *(*other) } - pure fn ne(&self, other: &Task) -> bool { !(*self).eq(other) } -} - -/** - * Indicates the manner in which a task exited. - * - * A task that completes without failing is considered to exit successfully. - * Supervised ancestors and linked siblings may yet fail after this task - * succeeds. Also note that in such a case, it may be nondeterministic whether - * linked failure or successful exit happen first. - * - * If you wish for this result's delivery to block until all linked and/or - * children tasks complete, recommend using a result future. - */ -pub enum TaskResult { - Success, - Failure, -} - -impl TaskResult : Eq { - pure fn eq(&self, other: &TaskResult) -> bool { - match ((*self), (*other)) { - (Success, Success) | (Failure, Failure) => true, - (Success, _) | (Failure, _) => false - } - } - pure fn ne(&self, other: &TaskResult) -> bool { !(*self).eq(other) } -} - -/// Scheduler modes -pub enum SchedMode { - /// All tasks run in the same OS thread - SingleThreaded, - /// Tasks are distributed among available CPUs - ThreadPerCore, - /// Each task runs in its own OS thread - ThreadPerTask, - /// Tasks are distributed among a fixed number of OS threads - ManualThreads(uint), - /** - * Tasks are scheduled on the main OS thread - * - * The main OS thread is the thread used to launch the runtime which, - * in most cases, is the process's initial thread as created by the OS. - */ - PlatformThread -} - -impl SchedMode : cmp::Eq { - pure fn eq(&self, other: &SchedMode) -> bool { - match (*self) { - SingleThreaded => { - match (*other) { - SingleThreaded => true, - _ => false - } - } - ThreadPerCore => { - match (*other) { - ThreadPerCore => true, - _ => false - } - } - ThreadPerTask => { - match (*other) { - ThreadPerTask => true, - _ => false - } - } - ManualThreads(e0a) => { - match (*other) { - ManualThreads(e0b) => e0a == e0b, - _ => false - } - } - PlatformThread => { - match (*other) { - PlatformThread => true, - _ => false - } - } - } - } - pure fn ne(&self, other: &SchedMode) -> bool { - !(*self).eq(other) - } -} - -/** - * Scheduler configuration options - * - * # Fields - * - * * sched_mode - The operating mode of the scheduler - * - * * foreign_stack_size - The size of the foreign stack, in bytes - * - * Rust code runs on Rust-specific stacks. When Rust code calls foreign - * code (via functions in foreign modules) it switches to a typical, large - * stack appropriate for running code written in languages like C. By - * default these foreign stacks have unspecified size, but with this - * option their size can be precisely specified. - */ -pub type SchedOpts = { - mode: SchedMode, - foreign_stack_size: Option<uint> -}; - -/** - * Task configuration options - * - * # Fields - * - * * linked - Propagate failure bidirectionally between child and parent. - * True by default. If both this and 'supervised' are false, then - * either task's failure will not affect the other ("unlinked"). - * - * * supervised - Propagate failure unidirectionally from parent to child, - * but not from child to parent. False by default. - * - * * notify_chan - Enable lifecycle notifications on the given channel - * - * * sched - Specify the configuration of a new scheduler to create the task - * in - * - * By default, every task is created in the same scheduler as its - * parent, where it is scheduled cooperatively with all other tasks - * in that scheduler. Some specialized applications may want more - * control over their scheduling, in which case they can be spawned - * into a new scheduler with the specific properties required. - * - * This is of particular importance for libraries which want to call - * into foreign code that blocks. Without doing so in a different - * scheduler other tasks will be impeded or even blocked indefinitely. - */ -pub type TaskOpts = { - linked: bool, - supervised: bool, - mut notify_chan: Option<Chan<TaskResult>>, - sched: Option<SchedOpts>, -}; - -/** - * The task builder type. - * - * Provides detailed control over the properties and behavior of new tasks. - */ -// NB: Builders are designed to be single-use because they do stateful -// things that get weird when reusing - e.g. if you create a result future -// it only applies to a single task, so then you have to maintain Some -// potentially tricky state to ensure that everything behaves correctly -// when you try to reuse the builder to spawn a new task. We'll just -// sidestep that whole issue by making builders uncopyable and making -// the run function move them in. - -// FIXME (#3724): Replace the 'consumed' bit with move mode on self -pub enum TaskBuilder = { - opts: TaskOpts, - gen_body: fn@(v: fn~()) -> fn~(), - can_not_copy: Option<util::NonCopyable>, - mut consumed: bool, -}; - -/** - * Generate the base configuration for spawning a task, off of which more - * configuration methods can be chained. - * For example, task().unlinked().spawn is equivalent to spawn_unlinked. - */ -pub fn task() -> TaskBuilder { - TaskBuilder({ - opts: default_task_opts(), - gen_body: |body| move body, // Identity function - can_not_copy: None, - mut consumed: false, - }) -} - -#[doc(hidden)] // FIXME #3538 -priv impl TaskBuilder { - fn consume() -> TaskBuilder { - if self.consumed { - fail ~"Cannot copy a task_builder"; // Fake move mode on self - } - self.consumed = true; - let notify_chan = replace(&mut self.opts.notify_chan, None); - TaskBuilder({ - opts: { - linked: self.opts.linked, - supervised: self.opts.supervised, - mut notify_chan: move notify_chan, - sched: self.opts.sched - }, - gen_body: self.gen_body, - can_not_copy: None, - mut consumed: false - }) - } -} - -impl TaskBuilder { - /** - * Decouple the child task's failure from the parent's. If either fails, - * the other will not be killed. - */ - fn unlinked() -> TaskBuilder { - let notify_chan = replace(&mut self.opts.notify_chan, None); - TaskBuilder({ - opts: { - linked: false, - supervised: self.opts.supervised, - mut notify_chan: move notify_chan, - sched: self.opts.sched - }, - can_not_copy: None, - .. *self.consume() - }) - } - /** - * Unidirectionally link the child task's failure with the parent's. The - * child's failure will not kill the parent, but the parent's will kill - * the child. - */ - fn supervised() -> TaskBuilder { - let notify_chan = replace(&mut self.opts.notify_chan, None); - TaskBuilder({ - opts: { - linked: false, - supervised: true, - mut notify_chan: move notify_chan, - sched: self.opts.sched - }, - can_not_copy: None, - .. *self.consume() - }) - } - /** - * Link the child task's and parent task's failures. If either fails, the - * other will be killed. - */ - fn linked() -> TaskBuilder { - let notify_chan = replace(&mut self.opts.notify_chan, None); - TaskBuilder({ - opts: { - linked: true, - supervised: false, - mut notify_chan: move notify_chan, - sched: self.opts.sched - }, - can_not_copy: None, - .. *self.consume() - }) - } - - /** - * Get a future representing the exit status of the task. - * - * Taking the value of the future will block until the child task - * terminates. The future-receiving callback specified will be called - * *before* the task is spawned; as such, do not invoke .get() within the - * closure; rather, store it in an outer variable/list for later use. - * - * Note that the future returning by this function is only useful for - * obtaining the value of the next task to be spawning with the - * builder. If additional tasks are spawned with the same builder - * then a new result future must be obtained prior to spawning each - * task. - * - * # Failure - * Fails if a future_result was already set for this task. - */ - fn future_result(blk: fn(v: Port<TaskResult>)) -> TaskBuilder { - // FIXME (#3725): Once linked failure and notification are - // handled in the library, I can imagine implementing this by just - // registering an arbitrary number of task::on_exit handlers and - // sending out messages. - - if self.opts.notify_chan.is_some() { - fail ~"Can't set multiple future_results for one task!"; - } - - // Construct the future and give it to the caller. - let (notify_pipe_ch, notify_pipe_po) = stream::<TaskResult>(); - - blk(move notify_pipe_po); - - // Reconfigure self to use a notify channel. - TaskBuilder({ - opts: { - linked: self.opts.linked, - supervised: self.opts.supervised, - mut notify_chan: Some(move notify_pipe_ch), - sched: self.opts.sched - }, - can_not_copy: None, - .. *self.consume() - }) - } - /// Configure a custom scheduler mode for the task. - fn sched_mode(mode: SchedMode) -> TaskBuilder { - let notify_chan = replace(&mut self.opts.notify_chan, None); - TaskBuilder({ - opts: { - linked: self.opts.linked, - supervised: self.opts.supervised, - mut notify_chan: move notify_chan, - sched: Some({ mode: mode, foreign_stack_size: None}) - }, - can_not_copy: None, - .. *self.consume() - }) - } - - /** - * Add a wrapper to the body of the spawned task. - * - * Before the task is spawned it is passed through a 'body generator' - * function that may perform local setup operations as well as wrap - * the task body in remote setup operations. With this the behavior - * of tasks can be extended in simple ways. - * - * This function augments the current body generator with a new body - * generator by applying the task body which results from the - * existing body generator to the new body generator. - */ - fn add_wrapper(wrapper: fn@(v: fn~()) -> fn~()) -> TaskBuilder { - let prev_gen_body = self.gen_body; - let notify_chan = replace(&mut self.opts.notify_chan, None); - TaskBuilder({ - opts: { - linked: self.opts.linked, - supervised: self.opts.supervised, - mut notify_chan: move notify_chan, - sched: self.opts.sched - }, - // tjc: I think this is the line that gets miscompiled - // w/ last-use off, if we leave out the move prev_gen_body? - // that makes no sense, though... - gen_body: |move prev_gen_body, - body| { wrapper(prev_gen_body(move body)) }, - can_not_copy: None, - .. *self.consume() - }) - } - - /** - * Creates and executes a new child task - * - * Sets up a new task with its own call stack and schedules it to run - * the provided unique closure. The task has the properties and behavior - * specified by the task_builder. - * - * # Failure - * - * When spawning into a new scheduler, the number of threads requested - * must be greater than zero. - */ - fn spawn(f: fn~()) { - let notify_chan = replace(&mut self.opts.notify_chan, None); - let x = self.consume(); - let opts = { - linked: x.opts.linked, - supervised: x.opts.supervised, - mut notify_chan: move notify_chan, - sched: x.opts.sched - }; - spawn::spawn_raw(move opts, x.gen_body(move f)); - } - /// Runs a task, while transfering ownership of one argument to the child. - fn spawn_with<A: Send>(arg: A, f: fn~(v: A)) { - let arg = ~mut Some(move arg); - do self.spawn |move arg, move f| { - f(option::swap_unwrap(arg)) - } - } - - /** - * Runs a new task while providing a channel from the parent to the child - * - * Sets up a communication channel from the current task to the new - * child task, passes the port to child's body, and returns a channel - * linked to the port to the parent. - * - * This encapsulates some boilerplate handshaking logic that would - * otherwise be required to establish communication from the parent - * to the child. - */ - fn spawn_listener<A: Send>(f: fn~(comm::Port<A>)) -> comm::Chan<A> { - let setup_po = comm::Port(); - let setup_ch = comm::Chan(&setup_po); - do self.spawn |move f| { - let po = comm::Port(); - let ch = comm::Chan(&po); - comm::send(setup_ch, ch); - f(move po); - } - comm::recv(setup_po) - } - - /** - * Runs a new task, setting up communication in both directions - */ - fn spawn_conversation<A: Send, B: Send> - (f: fn~(comm::Port<A>, comm::Chan<B>)) - -> (comm::Port<B>, comm::Chan<A>) { - let from_child = comm::Port(); - let to_parent = comm::Chan(&from_child); - let to_child = do self.spawn_listener |move f, from_parent| { - f(from_parent, to_parent) - }; - (from_child, to_child) - } - - /** - * Execute a function in another task and return either the return value - * of the function or result::err. - * - * # Return value - * - * If the function executed successfully then try returns result::ok - * containing the value returned by the function. If the function fails - * then try returns result::err containing nil. - * - * # Failure - * Fails if a future_result was already set for this task. - */ - fn try<T: Send>(f: fn~() -> T) -> Result<T,()> { - let po = comm::Port(); - let ch = comm::Chan(&po); - let mut result = None; - - let fr_task_builder = self.future_result(|+r| { - result = Some(move r); - }); - do fr_task_builder.spawn |move f| { - comm::send(ch, f()); - } - match option::unwrap(move result).recv() { - Success => result::Ok(comm::recv(po)), - Failure => result::Err(()) - } - } -} - - -/* Task construction */ - -pub fn default_task_opts() -> TaskOpts { - /*! - * The default task options - * - * By default all tasks are supervised by their parent, are spawned - * into the same scheduler, and do not post lifecycle notifications. - */ - - { - linked: true, - supervised: false, - mut notify_chan: None, - sched: None - } -} - -/* Spawn convenience functions */ - -pub fn spawn(f: fn~()) { - /*! - * Creates and executes a new child task - * - * Sets up a new task with its own call stack and schedules it to run - * the provided unique closure. - * - * This function is equivalent to `task().spawn(f)`. - */ - - task().spawn(move f) -} - -pub fn spawn_unlinked(f: fn~()) { - /*! - * Creates a child task unlinked from the current one. If either this - * task or the child task fails, the other will not be killed. - */ - - task().unlinked().spawn(move f) -} - -pub fn spawn_supervised(f: fn~()) { - /*! - * Creates a child task unlinked from the current one. If either this - * task or the child task fails, the other will not be killed. - */ - - task().supervised().spawn(move f) -} - -pub fn spawn_with<A:Send>(arg: A, f: fn~(v: A)) { - /*! - * Runs a task, while transfering ownership of one argument to the - * child. - * - * This is useful for transfering ownership of noncopyables to - * another task. - * - * This function is equivalent to `task().spawn_with(arg, f)`. - */ - - task().spawn_with(move arg, move f) -} - -pub fn spawn_listener<A:Send>(f: fn~(comm::Port<A>)) -> comm::Chan<A> { - /*! - * Runs a new task while providing a channel from the parent to the child - * - * This function is equivalent to `task().spawn_listener(f)`. - */ - - task().spawn_listener(move f) -} - -pub fn spawn_conversation<A: Send, B: Send> - (f: fn~(comm::Port<A>, comm::Chan<B>)) - -> (comm::Port<B>, comm::Chan<A>) { - /*! - * Runs a new task, setting up communication in both directions - * - * This function is equivalent to `task().spawn_conversation(f)`. - */ - - task().spawn_conversation(move f) -} - -pub fn spawn_sched(mode: SchedMode, f: fn~()) { - /*! - * Creates a new scheduler and executes a task on it - * - * Tasks subsequently spawned by that task will also execute on - * the new scheduler. When there are no more tasks to execute the - * scheduler terminates. - * - * # Failure - * - * In manual threads mode the number of threads requested must be - * greater than zero. - */ - - task().sched_mode(mode).spawn(move f) -} - -pub fn try<T:Send>(f: fn~() -> T) -> Result<T,()> { - /*! - * Execute a function in another task and return either the return value - * of the function or result::err. - * - * This is equivalent to task().supervised().try. - */ - - task().supervised().try(move f) -} - - -/* Lifecycle functions */ - -pub fn yield() { - //! Yield control to the task scheduler - - let task_ = rt::rust_get_task(); - let killed = rt::rust_task_yield(task_); - if killed && !failing() { - fail ~"killed"; - } -} - -pub fn failing() -> bool { - //! True if the running task has failed - - rt::rust_task_is_unwinding(rt::rust_get_task()) -} - -pub fn get_task() -> Task { - //! Get a handle to the running task - - TaskHandle(rt::get_task_id()) -} - -/** - * Temporarily make the task unkillable - * - * # Example - * - * ~~~ - * do task::unkillable { - * // detach / yield / destroy must all be called together - * rustrt::rust_port_detach(po); - * // This must not result in the current task being killed - * task::yield(); - * rustrt::rust_port_destroy(po); - * } - * ~~~ - */ -pub unsafe fn unkillable<U>(f: fn() -> U) -> U { - struct AllowFailure { - t: *rust_task, - drop { rt::rust_task_allow_kill(self.t); } - } - - fn AllowFailure(t: *rust_task) -> AllowFailure{ - AllowFailure { - t: t - } - } - - let t = rt::rust_get_task(); - let _allow_failure = AllowFailure(t); - rt::rust_task_inhibit_kill(t); - f() -} - -/// The inverse of unkillable. Only ever to be used nested in unkillable(). -pub unsafe fn rekillable<U>(f: fn() -> U) -> U { - struct DisallowFailure { - t: *rust_task, - drop { rt::rust_task_inhibit_kill(self.t); } - } - - fn DisallowFailure(t: *rust_task) -> DisallowFailure { - DisallowFailure { - t: t - } - } - - let t = rt::rust_get_task(); - let _allow_failure = DisallowFailure(t); - rt::rust_task_allow_kill(t); - f() -} - -/** - * A stronger version of unkillable that also inhibits scheduling operations. - * For use with exclusive ARCs, which use pthread mutexes directly. - */ -pub unsafe fn atomically<U>(f: fn() -> U) -> U { - struct DeferInterrupts { - t: *rust_task, - drop { - rt::rust_task_allow_yield(self.t); - rt::rust_task_allow_kill(self.t); - } - } - - fn DeferInterrupts(t: *rust_task) -> DeferInterrupts { - DeferInterrupts { - t: t - } - } - - let t = rt::rust_get_task(); - let _interrupts = DeferInterrupts(t); - rt::rust_task_inhibit_kill(t); - rt::rust_task_inhibit_yield(t); - f() -} - -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_cant_dup_task_builder() { - let b = task().unlinked(); - do b.spawn { } - // FIXME(#3724): For now, this is a -runtime- failure, because we haven't - // got move mode on self. When 3724 is fixed, this test should fail to - // compile instead, and should go in tests/compile-fail. - do b.spawn { } // b should have been consumed by the previous call -} - -// The following 8 tests test the following 2^3 combinations: -// {un,}linked {un,}supervised failure propagation {up,down}wards. - -// !!! These tests are dangerous. If Something is buggy, they will hang, !!! -// !!! instead of exiting cleanly. This might wedge the buildbots. !!! - -#[test] #[ignore(cfg(windows))] -fn test_spawn_unlinked_unsup_no_fail_down() { // grandchild sends on a port - let po = comm::Port(); - let ch = comm::Chan(&po); - do spawn_unlinked { - do spawn_unlinked { - // Give middle task a chance to fail-but-not-kill-us. - for iter::repeat(16) { task::yield(); } - comm::send(ch, ()); // If killed first, grandparent hangs. - } - fail; // Shouldn't kill either (grand)parent or (grand)child. - } - comm::recv(po); -} -#[test] #[ignore(cfg(windows))] -fn test_spawn_unlinked_unsup_no_fail_up() { // child unlinked fails - do spawn_unlinked { fail; } -} -#[test] #[ignore(cfg(windows))] -fn test_spawn_unlinked_sup_no_fail_up() { // child unlinked fails - do spawn_supervised { fail; } - // Give child a chance to fail-but-not-kill-us. - for iter::repeat(16) { task::yield(); } -} -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_spawn_unlinked_sup_fail_down() { - do spawn_supervised { loop { task::yield(); } } - fail; // Shouldn't leave a child hanging around. -} - -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_spawn_linked_sup_fail_up() { // child fails; parent fails - let po = comm::Port::<()>(); - let _ch = comm::Chan(&po); - // Unidirectional "parenting" shouldn't override bidirectional linked. - // We have to cheat with opts - the interface doesn't support them because - // they don't make sense (redundant with task().supervised()). - let opts = { - let mut opts = default_task_opts(); - opts.linked = true; - opts.supervised = true; - move opts - }; - - let b0 = task(); - let b1 = TaskBuilder({ - opts: move opts, - can_not_copy: None, - .. *b0 - }); - do b1.spawn { fail; } - comm::recv(po); // We should get punted awake -} -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_spawn_linked_sup_fail_down() { // parent fails; child fails - // We have to cheat with opts - the interface doesn't support them because - // they don't make sense (redundant with task().supervised()). - let opts = { - let mut opts = default_task_opts(); - opts.linked = true; - opts.supervised = true; - move opts - }; - - let b0 = task(); - let b1 = TaskBuilder({ - opts: move opts, - can_not_copy: None, - .. *b0 - }); - do b1.spawn { loop { task::yield(); } } - fail; // *both* mechanisms would be wrong if this didn't kill the child... -} -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_spawn_linked_unsup_fail_up() { // child fails; parent fails - let po = comm::Port::<()>(); - let _ch = comm::Chan(&po); - // Default options are to spawn linked & unsupervised. - do spawn { fail; } - comm::recv(po); // We should get punted awake -} -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_spawn_linked_unsup_fail_down() { // parent fails; child fails - // Default options are to spawn linked & unsupervised. - do spawn { loop { task::yield(); } } - fail; -} -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_spawn_linked_unsup_default_opts() { // parent fails; child fails - // Make sure the above test is the same as this one. - do task().linked().spawn { loop { task::yield(); } } - fail; -} - -// A couple bonus linked failure tests - testing for failure propagation even -// when the middle task exits successfully early before kill signals are sent. - -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_spawn_failure_propagate_grandchild() { - // Middle task exits; does grandparent's failure propagate across the gap? - do spawn_supervised { - do spawn_supervised { - loop { task::yield(); } - } - } - for iter::repeat(16) { task::yield(); } - fail; -} - -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_spawn_failure_propagate_secondborn() { - // First-born child exits; does parent's failure propagate to sibling? - do spawn_supervised { - do spawn { // linked - loop { task::yield(); } - } - } - for iter::repeat(16) { task::yield(); } - fail; -} - -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_spawn_failure_propagate_nephew_or_niece() { - // Our sibling exits; does our failure propagate to sibling's child? - do spawn { // linked - do spawn_supervised { - loop { task::yield(); } - } - } - for iter::repeat(16) { task::yield(); } - fail; -} - -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_spawn_linked_sup_propagate_sibling() { - // Middle sibling exits - does eldest's failure propagate to youngest? - do spawn { // linked - do spawn { // linked - loop { task::yield(); } - } - } - for iter::repeat(16) { task::yield(); } - fail; -} - -#[test] -fn test_run_basic() { - let po = comm::Port(); - let ch = comm::Chan(&po); - do task().spawn { - comm::send(ch, ()); - } - comm::recv(po); -} - -#[test] -fn test_add_wrapper() { - let po = comm::Port(); - let ch = comm::Chan(&po); - let b0 = task(); - let b1 = do b0.add_wrapper |body| { - fn~(move body) { - body(); - comm::send(ch, ()); - } - }; - do b1.spawn { } - comm::recv(po); -} - -#[test] -#[ignore(cfg(windows))] -fn test_future_result() { - let mut result = None; - do task().future_result(|+r| { result = Some(move r); }).spawn { } - assert option::unwrap(move result).recv() == Success; - - result = None; - do task().future_result(|+r| - { result = Some(move r); }).unlinked().spawn { - fail; - } - assert option::unwrap(move result).recv() == Failure; -} - -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_back_to_the_future_result() { - let _ = task().future_result(util::ignore).future_result(util::ignore); -} - -#[test] -fn test_spawn_listiner_bidi() { - let po = comm::Port(); - let ch = comm::Chan(&po); - let ch = do spawn_listener |po| { - // Now the child has a port called 'po' to read from and - // an environment-captured channel called 'ch'. - let res: ~str = comm::recv(po); - assert res == ~"ping"; - comm::send(ch, ~"pong"); - }; - // Likewise, the parent has both a 'po' and 'ch' - comm::send(ch, ~"ping"); - let res: ~str = comm::recv(po); - assert res == ~"pong"; -} - -#[test] -fn test_spawn_conversation() { - let (recv_str, send_int) = do spawn_conversation |recv_int, send_str| { - let input = comm::recv(recv_int); - let output = int::str(input); - comm::send(send_str, move output); - }; - comm::send(send_int, 1); - assert comm::recv(recv_str) == ~"1"; -} - -#[test] -fn test_try_success() { - match do try { - ~"Success!" - } { - result::Ok(~"Success!") => (), - _ => fail - } -} - -#[test] -#[ignore(cfg(windows))] -fn test_try_fail() { - match do try { - fail - } { - result::Err(()) => (), - result::Ok(()) => fail - } -} - -#[test] -#[should_fail] -#[ignore(cfg(windows))] -fn test_spawn_sched_no_threads() { - do spawn_sched(ManualThreads(0u)) { } -} - -#[test] -fn test_spawn_sched() { - let po = comm::Port(); - let ch = comm::Chan(&po); - - fn f(i: int, ch: comm::Chan<()>) { - let parent_sched_id = rt::rust_get_sched_id(); - - do spawn_sched(SingleThreaded) { - let child_sched_id = rt::rust_get_sched_id(); - assert parent_sched_id != child_sched_id; - - if (i == 0) { - comm::send(ch, ()); - } else { - f(i - 1, ch); - } - }; - - } - f(10, ch); - comm::recv(po); -} - -#[test] -fn test_spawn_sched_childs_on_same_sched() { - let po = comm::Port(); - let ch = comm::Chan(&po); - - do spawn_sched(SingleThreaded) { - let parent_sched_id = rt::rust_get_sched_id(); - do spawn { - let child_sched_id = rt::rust_get_sched_id(); - // This should be on the same scheduler - assert parent_sched_id == child_sched_id; - comm::send(ch, ()); - }; - }; - - comm::recv(po); -} - -#[nolink] -#[cfg(test)] -extern mod testrt { - fn rust_dbg_lock_create() -> *libc::c_void; - fn rust_dbg_lock_destroy(lock: *libc::c_void); - fn rust_dbg_lock_lock(lock: *libc::c_void); - fn rust_dbg_lock_unlock(lock: *libc::c_void); - fn rust_dbg_lock_wait(lock: *libc::c_void); - fn rust_dbg_lock_signal(lock: *libc::c_void); -} - -#[test] -fn test_spawn_sched_blocking() { - - // Testing that a task in one scheduler can block in foreign code - // without affecting other schedulers - for iter::repeat(20u) { - - let start_po = comm::Port(); - let start_ch = comm::Chan(&start_po); - let fin_po = comm::Port(); - let fin_ch = comm::Chan(&fin_po); - - let lock = testrt::rust_dbg_lock_create(); - - do spawn_sched(SingleThreaded) { - testrt::rust_dbg_lock_lock(lock); - - comm::send(start_ch, ()); - - // Block the scheduler thread - testrt::rust_dbg_lock_wait(lock); - testrt::rust_dbg_lock_unlock(lock); - - comm::send(fin_ch, ()); - }; - - // Wait until the other task has its lock - comm::recv(start_po); - - fn pingpong(po: comm::Port<int>, ch: comm::Chan<int>) { - let mut val = 20; - while val > 0 { - val = comm::recv(po); - comm::send(ch, val - 1); - } - } - - let setup_po = comm::Port(); - let setup_ch = comm::Chan(&setup_po); - let parent_po = comm::Port(); - let parent_ch = comm::Chan(&parent_po); - do spawn { - let child_po = comm::Port(); - comm::send(setup_ch, comm::Chan(&child_po)); - pingpong(child_po, parent_ch); - }; - - let child_ch = comm::recv(setup_po); - comm::send(child_ch, 20); - pingpong(parent_po, child_ch); - testrt::rust_dbg_lock_lock(lock); - testrt::rust_dbg_lock_signal(lock); - testrt::rust_dbg_lock_unlock(lock); - comm::recv(fin_po); - testrt::rust_dbg_lock_destroy(lock); - } -} - -#[cfg(test)] -fn avoid_copying_the_body(spawnfn: fn(v: fn~())) { - let p = comm::Port::<uint>(); - let ch = comm::Chan(&p); - - let x = ~1; - let x_in_parent = ptr::addr_of(&(*x)) as uint; - - do spawnfn |move x| { - let x_in_child = ptr::addr_of(&(*x)) as uint; - comm::send(ch, x_in_child); - } - - let x_in_child = comm::recv(p); - assert x_in_parent == x_in_child; -} - -#[test] -fn test_avoid_copying_the_body_spawn() { - avoid_copying_the_body(spawn); -} - -#[test] -fn test_avoid_copying_the_body_spawn_listener() { - do avoid_copying_the_body |f| { - spawn_listener(fn~(move f, _po: comm::Port<int>) { - f(); - }); - } -} - -#[test] -fn test_avoid_copying_the_body_task_spawn() { - do avoid_copying_the_body |f| { - do task().spawn |move f| { - f(); - } - } -} - -#[test] -fn test_avoid_copying_the_body_spawn_listener_1() { - do avoid_copying_the_body |f| { - task().spawn_listener(fn~(move f, _po: comm::Port<int>) { - f(); - }); - } -} - -#[test] -fn test_avoid_copying_the_body_try() { - do avoid_copying_the_body |f| { - do try |move f| { - f() - }; - } -} - -#[test] -fn test_avoid_copying_the_body_unlinked() { - do avoid_copying_the_body |f| { - do spawn_unlinked |move f| { - f(); - } - } -} - -#[test] -fn test_platform_thread() { - let po = comm::Port(); - let ch = comm::Chan(&po); - do task().sched_mode(PlatformThread).spawn { - comm::send(ch, ()); - } - comm::recv(po); -} - -#[test] -#[ignore(cfg(windows))] -#[should_fail] -fn test_unkillable() { - let po = comm::Port(); - let ch = po.chan(); - - // We want to do this after failing - do spawn_unlinked { - for iter::repeat(10) { yield() } - ch.send(()); - } - - do spawn { - yield(); - // We want to fail after the unkillable task - // blocks on recv - fail; - } - - unsafe { - do unkillable { - let p = ~0; - let pp: *uint = cast::transmute(move p); - - // If we are killed here then the box will leak - po.recv(); - - let _p: ~int = cast::transmute(move pp); - } - } - - // Now we can be killed - po.recv(); -} - -#[test] -#[ignore(cfg(windows))] -#[should_fail] -fn test_unkillable_nested() { - let (ch, po) = pipes::stream(); - - // We want to do this after failing - do spawn_unlinked |move ch| { - for iter::repeat(10) { yield() } - ch.send(()); - } - - do spawn { - yield(); - // We want to fail after the unkillable task - // blocks on recv - fail; - } - - unsafe { - do unkillable { - do unkillable {} // Here's the difference from the previous test. - let p = ~0; - let pp: *uint = cast::transmute(move p); - - // If we are killed here then the box will leak - po.recv(); - - let _p: ~int = cast::transmute(move pp); - } - } - - // Now we can be killed - po.recv(); -} - -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_atomically() { - unsafe { do atomically { yield(); } } -} - -#[test] -fn test_atomically2() { - unsafe { do atomically { } } yield(); // shouldn't fail -} - -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_atomically_nested() { - unsafe { do atomically { do atomically { } yield(); } } -} - -#[test] -fn test_child_doesnt_ref_parent() { - // If the child refcounts the parent task, this will stack overflow when - // climbing the task tree to dereference each ancestor. (See #1789) - // (well, it would if the constant were 8000+ - I lowered it to be more - // valgrind-friendly. try this at home, instead..!) - const generations: uint = 16; - fn child_no(x: uint) -> fn~() { - return || { - if x < generations { - task::spawn(child_no(x+1)); - } - } - } - task::spawn(child_no(0)); -} - -#[test] -fn test_sched_thread_per_core() { - let (chan, port) = pipes::stream(); - - do spawn_sched(ThreadPerCore) |move chan| { - let cores = rt::rust_num_threads(); - let reported_threads = rt::rust_sched_threads(); - assert(cores as uint == reported_threads as uint); - chan.send(()); - } - - port.recv(); -} - -#[test] -fn test_spawn_thread_on_demand() { - let (chan, port) = pipes::stream(); - - do spawn_sched(ManualThreads(2)) |move chan| { - let max_threads = rt::rust_sched_threads(); - assert(max_threads as int == 2); - let running_threads = rt::rust_sched_current_nonlazy_threads(); - assert(running_threads as int == 1); - - let (chan2, port2) = pipes::stream(); - - do spawn() |move chan2| { - chan2.send(()); - } - - let running_threads2 = rt::rust_sched_current_nonlazy_threads(); - assert(running_threads2 as int == 2); - - port2.recv(); - chan.send(()); - } - - port.recv(); -} diff --git a/src/libcore/task/mod.rs b/src/libcore/task/mod.rs index 887983e8b6c..90781044277 100644 --- a/src/libcore/task/mod.rs +++ b/src/libcore/task/mod.rs @@ -1,8 +1,1306 @@ +// NB: transitionary, de-mode-ing. +// tjc: Deprecated modes allowed because of function arg issue +// in task::spawn. Re-forbid after snapshot. +#[forbid(deprecated_pattern)]; -mod local_data_priv; +/*! + * Task management. + * + * An executing Rust program consists of a tree of tasks, each with their own + * stack, and sole ownership of their allocated heap data. Tasks communicate + * with each other using ports and channels. + * + * When a task fails, that failure will propagate to its parent (the task + * that spawned it) and the parent will fail as well. The reverse is not + * true: when a parent task fails its children will continue executing. When + * the root (main) task fails, all tasks fail, and then so does the entire + * process. + * + * Tasks may execute in parallel and are scheduled automatically by the + * runtime. + * + * # Example + * + * ~~~ + * do spawn { + * log(error, "Hello, World!"); + * } + * ~~~ + */ -pub mod local_data; +use cmp::Eq; +use result::Result; +use pipes::{stream, Chan, Port}; +use local_data_priv::{local_get, local_set}; +use util::replace; -pub mod rt; +use rt::task_id; +use rt::rust_task; +mod local_data_priv; +pub mod local_data; +pub mod rt; pub mod spawn; + +/// A handle to a task +pub enum Task { + TaskHandle(task_id) +} + +impl Task : cmp::Eq { + pure fn eq(&self, other: &Task) -> bool { *(*self) == *(*other) } + pure fn ne(&self, other: &Task) -> bool { !(*self).eq(other) } +} + +/** + * Indicates the manner in which a task exited. + * + * A task that completes without failing is considered to exit successfully. + * Supervised ancestors and linked siblings may yet fail after this task + * succeeds. Also note that in such a case, it may be nondeterministic whether + * linked failure or successful exit happen first. + * + * If you wish for this result's delivery to block until all linked and/or + * children tasks complete, recommend using a result future. + */ +pub enum TaskResult { + Success, + Failure, +} + +impl TaskResult : Eq { + pure fn eq(&self, other: &TaskResult) -> bool { + match ((*self), (*other)) { + (Success, Success) | (Failure, Failure) => true, + (Success, _) | (Failure, _) => false + } + } + pure fn ne(&self, other: &TaskResult) -> bool { !(*self).eq(other) } +} + +/// Scheduler modes +pub enum SchedMode { + /// All tasks run in the same OS thread + SingleThreaded, + /// Tasks are distributed among available CPUs + ThreadPerCore, + /// Each task runs in its own OS thread + ThreadPerTask, + /// Tasks are distributed among a fixed number of OS threads + ManualThreads(uint), + /** + * Tasks are scheduled on the main OS thread + * + * The main OS thread is the thread used to launch the runtime which, + * in most cases, is the process's initial thread as created by the OS. + */ + PlatformThread +} + +impl SchedMode : cmp::Eq { + pure fn eq(&self, other: &SchedMode) -> bool { + match (*self) { + SingleThreaded => { + match (*other) { + SingleThreaded => true, + _ => false + } + } + ThreadPerCore => { + match (*other) { + ThreadPerCore => true, + _ => false + } + } + ThreadPerTask => { + match (*other) { + ThreadPerTask => true, + _ => false + } + } + ManualThreads(e0a) => { + match (*other) { + ManualThreads(e0b) => e0a == e0b, + _ => false + } + } + PlatformThread => { + match (*other) { + PlatformThread => true, + _ => false + } + } + } + } + pure fn ne(&self, other: &SchedMode) -> bool { + !(*self).eq(other) + } +} + +/** + * Scheduler configuration options + * + * # Fields + * + * * sched_mode - The operating mode of the scheduler + * + * * foreign_stack_size - The size of the foreign stack, in bytes + * + * Rust code runs on Rust-specific stacks. When Rust code calls foreign + * code (via functions in foreign modules) it switches to a typical, large + * stack appropriate for running code written in languages like C. By + * default these foreign stacks have unspecified size, but with this + * option their size can be precisely specified. + */ +pub type SchedOpts = { + mode: SchedMode, + foreign_stack_size: Option<uint> +}; + +/** + * Task configuration options + * + * # Fields + * + * * linked - Propagate failure bidirectionally between child and parent. + * True by default. If both this and 'supervised' are false, then + * either task's failure will not affect the other ("unlinked"). + * + * * supervised - Propagate failure unidirectionally from parent to child, + * but not from child to parent. False by default. + * + * * notify_chan - Enable lifecycle notifications on the given channel + * + * * sched - Specify the configuration of a new scheduler to create the task + * in + * + * By default, every task is created in the same scheduler as its + * parent, where it is scheduled cooperatively with all other tasks + * in that scheduler. Some specialized applications may want more + * control over their scheduling, in which case they can be spawned + * into a new scheduler with the specific properties required. + * + * This is of particular importance for libraries which want to call + * into foreign code that blocks. Without doing so in a different + * scheduler other tasks will be impeded or even blocked indefinitely. + */ +pub type TaskOpts = { + linked: bool, + supervised: bool, + mut notify_chan: Option<Chan<TaskResult>>, + sched: Option<SchedOpts>, +}; + +/** + * The task builder type. + * + * Provides detailed control over the properties and behavior of new tasks. + */ +// NB: Builders are designed to be single-use because they do stateful +// things that get weird when reusing - e.g. if you create a result future +// it only applies to a single task, so then you have to maintain Some +// potentially tricky state to ensure that everything behaves correctly +// when you try to reuse the builder to spawn a new task. We'll just +// sidestep that whole issue by making builders uncopyable and making +// the run function move them in. + +// FIXME (#3724): Replace the 'consumed' bit with move mode on self +pub enum TaskBuilder = { + opts: TaskOpts, + gen_body: fn@(v: fn~()) -> fn~(), + can_not_copy: Option<util::NonCopyable>, + mut consumed: bool, +}; + +/** + * Generate the base configuration for spawning a task, off of which more + * configuration methods can be chained. + * For example, task().unlinked().spawn is equivalent to spawn_unlinked. + */ +pub fn task() -> TaskBuilder { + TaskBuilder({ + opts: default_task_opts(), + gen_body: |body| move body, // Identity function + can_not_copy: None, + mut consumed: false, + }) +} + +#[doc(hidden)] // FIXME #3538 +priv impl TaskBuilder { + fn consume() -> TaskBuilder { + if self.consumed { + fail ~"Cannot copy a task_builder"; // Fake move mode on self + } + self.consumed = true; + let notify_chan = replace(&mut self.opts.notify_chan, None); + TaskBuilder({ + opts: { + linked: self.opts.linked, + supervised: self.opts.supervised, + mut notify_chan: move notify_chan, + sched: self.opts.sched + }, + gen_body: self.gen_body, + can_not_copy: None, + mut consumed: false + }) + } +} + +impl TaskBuilder { + /** + * Decouple the child task's failure from the parent's. If either fails, + * the other will not be killed. + */ + fn unlinked() -> TaskBuilder { + let notify_chan = replace(&mut self.opts.notify_chan, None); + TaskBuilder({ + opts: { + linked: false, + supervised: self.opts.supervised, + mut notify_chan: move notify_chan, + sched: self.opts.sched + }, + can_not_copy: None, + .. *self.consume() + }) + } + /** + * Unidirectionally link the child task's failure with the parent's. The + * child's failure will not kill the parent, but the parent's will kill + * the child. + */ + fn supervised() -> TaskBuilder { + let notify_chan = replace(&mut self.opts.notify_chan, None); + TaskBuilder({ + opts: { + linked: false, + supervised: true, + mut notify_chan: move notify_chan, + sched: self.opts.sched + }, + can_not_copy: None, + .. *self.consume() + }) + } + /** + * Link the child task's and parent task's failures. If either fails, the + * other will be killed. + */ + fn linked() -> TaskBuilder { + let notify_chan = replace(&mut self.opts.notify_chan, None); + TaskBuilder({ + opts: { + linked: true, + supervised: false, + mut notify_chan: move notify_chan, + sched: self.opts.sched + }, + can_not_copy: None, + .. *self.consume() + }) + } + + /** + * Get a future representing the exit status of the task. + * + * Taking the value of the future will block until the child task + * terminates. The future-receiving callback specified will be called + * *before* the task is spawned; as such, do not invoke .get() within the + * closure; rather, store it in an outer variable/list for later use. + * + * Note that the future returning by this function is only useful for + * obtaining the value of the next task to be spawning with the + * builder. If additional tasks are spawned with the same builder + * then a new result future must be obtained prior to spawning each + * task. + * + * # Failure + * Fails if a future_result was already set for this task. + */ + fn future_result(blk: fn(v: Port<TaskResult>)) -> TaskBuilder { + // FIXME (#3725): Once linked failure and notification are + // handled in the library, I can imagine implementing this by just + // registering an arbitrary number of task::on_exit handlers and + // sending out messages. + + if self.opts.notify_chan.is_some() { + fail ~"Can't set multiple future_results for one task!"; + } + + // Construct the future and give it to the caller. + let (notify_pipe_ch, notify_pipe_po) = stream::<TaskResult>(); + + blk(move notify_pipe_po); + + // Reconfigure self to use a notify channel. + TaskBuilder({ + opts: { + linked: self.opts.linked, + supervised: self.opts.supervised, + mut notify_chan: Some(move notify_pipe_ch), + sched: self.opts.sched + }, + can_not_copy: None, + .. *self.consume() + }) + } + /// Configure a custom scheduler mode for the task. + fn sched_mode(mode: SchedMode) -> TaskBuilder { + let notify_chan = replace(&mut self.opts.notify_chan, None); + TaskBuilder({ + opts: { + linked: self.opts.linked, + supervised: self.opts.supervised, + mut notify_chan: move notify_chan, + sched: Some({ mode: mode, foreign_stack_size: None}) + }, + can_not_copy: None, + .. *self.consume() + }) + } + + /** + * Add a wrapper to the body of the spawned task. + * + * Before the task is spawned it is passed through a 'body generator' + * function that may perform local setup operations as well as wrap + * the task body in remote setup operations. With this the behavior + * of tasks can be extended in simple ways. + * + * This function augments the current body generator with a new body + * generator by applying the task body which results from the + * existing body generator to the new body generator. + */ + fn add_wrapper(wrapper: fn@(v: fn~()) -> fn~()) -> TaskBuilder { + let prev_gen_body = self.gen_body; + let notify_chan = replace(&mut self.opts.notify_chan, None); + TaskBuilder({ + opts: { + linked: self.opts.linked, + supervised: self.opts.supervised, + mut notify_chan: move notify_chan, + sched: self.opts.sched + }, + // tjc: I think this is the line that gets miscompiled + // w/ last-use off, if we leave out the move prev_gen_body? + // that makes no sense, though... + gen_body: |move prev_gen_body, + body| { wrapper(prev_gen_body(move body)) }, + can_not_copy: None, + .. *self.consume() + }) + } + + /** + * Creates and executes a new child task + * + * Sets up a new task with its own call stack and schedules it to run + * the provided unique closure. The task has the properties and behavior + * specified by the task_builder. + * + * # Failure + * + * When spawning into a new scheduler, the number of threads requested + * must be greater than zero. + */ + fn spawn(f: fn~()) { + let notify_chan = replace(&mut self.opts.notify_chan, None); + let x = self.consume(); + let opts = { + linked: x.opts.linked, + supervised: x.opts.supervised, + mut notify_chan: move notify_chan, + sched: x.opts.sched + }; + spawn::spawn_raw(move opts, x.gen_body(move f)); + } + /// Runs a task, while transfering ownership of one argument to the child. + fn spawn_with<A: Send>(arg: A, f: fn~(v: A)) { + let arg = ~mut Some(move arg); + do self.spawn |move arg, move f| { + f(option::swap_unwrap(arg)) + } + } + + /** + * Runs a new task while providing a channel from the parent to the child + * + * Sets up a communication channel from the current task to the new + * child task, passes the port to child's body, and returns a channel + * linked to the port to the parent. + * + * This encapsulates some boilerplate handshaking logic that would + * otherwise be required to establish communication from the parent + * to the child. + */ + fn spawn_listener<A: Send>(f: fn~(comm::Port<A>)) -> comm::Chan<A> { + let setup_po = comm::Port(); + let setup_ch = comm::Chan(&setup_po); + do self.spawn |move f| { + let po = comm::Port(); + let ch = comm::Chan(&po); + comm::send(setup_ch, ch); + f(move po); + } + comm::recv(setup_po) + } + + /** + * Runs a new task, setting up communication in both directions + */ + fn spawn_conversation<A: Send, B: Send> + (f: fn~(comm::Port<A>, comm::Chan<B>)) + -> (comm::Port<B>, comm::Chan<A>) { + let from_child = comm::Port(); + let to_parent = comm::Chan(&from_child); + let to_child = do self.spawn_listener |move f, from_parent| { + f(from_parent, to_parent) + }; + (from_child, to_child) + } + + /** + * Execute a function in another task and return either the return value + * of the function or result::err. + * + * # Return value + * + * If the function executed successfully then try returns result::ok + * containing the value returned by the function. If the function fails + * then try returns result::err containing nil. + * + * # Failure + * Fails if a future_result was already set for this task. + */ + fn try<T: Send>(f: fn~() -> T) -> Result<T,()> { + let po = comm::Port(); + let ch = comm::Chan(&po); + let mut result = None; + + let fr_task_builder = self.future_result(|+r| { + result = Some(move r); + }); + do fr_task_builder.spawn |move f| { + comm::send(ch, f()); + } + match option::unwrap(move result).recv() { + Success => result::Ok(comm::recv(po)), + Failure => result::Err(()) + } + } +} + + +/* Task construction */ + +pub fn default_task_opts() -> TaskOpts { + /*! + * The default task options + * + * By default all tasks are supervised by their parent, are spawned + * into the same scheduler, and do not post lifecycle notifications. + */ + + { + linked: true, + supervised: false, + mut notify_chan: None, + sched: None + } +} + +/* Spawn convenience functions */ + +pub fn spawn(f: fn~()) { + /*! + * Creates and executes a new child task + * + * Sets up a new task with its own call stack and schedules it to run + * the provided unique closure. + * + * This function is equivalent to `task().spawn(f)`. + */ + + task().spawn(move f) +} + +pub fn spawn_unlinked(f: fn~()) { + /*! + * Creates a child task unlinked from the current one. If either this + * task or the child task fails, the other will not be killed. + */ + + task().unlinked().spawn(move f) +} + +pub fn spawn_supervised(f: fn~()) { + /*! + * Creates a child task unlinked from the current one. If either this + * task or the child task fails, the other will not be killed. + */ + + task().supervised().spawn(move f) +} + +pub fn spawn_with<A:Send>(arg: A, f: fn~(v: A)) { + /*! + * Runs a task, while transfering ownership of one argument to the + * child. + * + * This is useful for transfering ownership of noncopyables to + * another task. + * + * This function is equivalent to `task().spawn_with(arg, f)`. + */ + + task().spawn_with(move arg, move f) +} + +pub fn spawn_listener<A:Send>(f: fn~(comm::Port<A>)) -> comm::Chan<A> { + /*! + * Runs a new task while providing a channel from the parent to the child + * + * This function is equivalent to `task().spawn_listener(f)`. + */ + + task().spawn_listener(move f) +} + +pub fn spawn_conversation<A: Send, B: Send> + (f: fn~(comm::Port<A>, comm::Chan<B>)) + -> (comm::Port<B>, comm::Chan<A>) { + /*! + * Runs a new task, setting up communication in both directions + * + * This function is equivalent to `task().spawn_conversation(f)`. + */ + + task().spawn_conversation(move f) +} + +pub fn spawn_sched(mode: SchedMode, f: fn~()) { + /*! + * Creates a new scheduler and executes a task on it + * + * Tasks subsequently spawned by that task will also execute on + * the new scheduler. When there are no more tasks to execute the + * scheduler terminates. + * + * # Failure + * + * In manual threads mode the number of threads requested must be + * greater than zero. + */ + + task().sched_mode(mode).spawn(move f) +} + +pub fn try<T:Send>(f: fn~() -> T) -> Result<T,()> { + /*! + * Execute a function in another task and return either the return value + * of the function or result::err. + * + * This is equivalent to task().supervised().try. + */ + + task().supervised().try(move f) +} + + +/* Lifecycle functions */ + +pub fn yield() { + //! Yield control to the task scheduler + + let task_ = rt::rust_get_task(); + let killed = rt::rust_task_yield(task_); + if killed && !failing() { + fail ~"killed"; + } +} + +pub fn failing() -> bool { + //! True if the running task has failed + + rt::rust_task_is_unwinding(rt::rust_get_task()) +} + +pub fn get_task() -> Task { + //! Get a handle to the running task + + TaskHandle(rt::get_task_id()) +} + +/** + * Temporarily make the task unkillable + * + * # Example + * + * ~~~ + * do task::unkillable { + * // detach / yield / destroy must all be called together + * rustrt::rust_port_detach(po); + * // This must not result in the current task being killed + * task::yield(); + * rustrt::rust_port_destroy(po); + * } + * ~~~ + */ +pub unsafe fn unkillable<U>(f: fn() -> U) -> U { + struct AllowFailure { + t: *rust_task, + drop { rt::rust_task_allow_kill(self.t); } + } + + fn AllowFailure(t: *rust_task) -> AllowFailure{ + AllowFailure { + t: t + } + } + + let t = rt::rust_get_task(); + let _allow_failure = AllowFailure(t); + rt::rust_task_inhibit_kill(t); + f() +} + +/// The inverse of unkillable. Only ever to be used nested in unkillable(). +pub unsafe fn rekillable<U>(f: fn() -> U) -> U { + struct DisallowFailure { + t: *rust_task, + drop { rt::rust_task_inhibit_kill(self.t); } + } + + fn DisallowFailure(t: *rust_task) -> DisallowFailure { + DisallowFailure { + t: t + } + } + + let t = rt::rust_get_task(); + let _allow_failure = DisallowFailure(t); + rt::rust_task_allow_kill(t); + f() +} + +/** + * A stronger version of unkillable that also inhibits scheduling operations. + * For use with exclusive ARCs, which use pthread mutexes directly. + */ +pub unsafe fn atomically<U>(f: fn() -> U) -> U { + struct DeferInterrupts { + t: *rust_task, + drop { + rt::rust_task_allow_yield(self.t); + rt::rust_task_allow_kill(self.t); + } + } + + fn DeferInterrupts(t: *rust_task) -> DeferInterrupts { + DeferInterrupts { + t: t + } + } + + let t = rt::rust_get_task(); + let _interrupts = DeferInterrupts(t); + rt::rust_task_inhibit_kill(t); + rt::rust_task_inhibit_yield(t); + f() +} + +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_cant_dup_task_builder() { + let b = task().unlinked(); + do b.spawn { } + // FIXME(#3724): For now, this is a -runtime- failure, because we haven't + // got move mode on self. When 3724 is fixed, this test should fail to + // compile instead, and should go in tests/compile-fail. + do b.spawn { } // b should have been consumed by the previous call +} + +// The following 8 tests test the following 2^3 combinations: +// {un,}linked {un,}supervised failure propagation {up,down}wards. + +// !!! These tests are dangerous. If Something is buggy, they will hang, !!! +// !!! instead of exiting cleanly. This might wedge the buildbots. !!! + +#[test] #[ignore(cfg(windows))] +fn test_spawn_unlinked_unsup_no_fail_down() { // grandchild sends on a port + let po = comm::Port(); + let ch = comm::Chan(&po); + do spawn_unlinked { + do spawn_unlinked { + // Give middle task a chance to fail-but-not-kill-us. + for iter::repeat(16) { task::yield(); } + comm::send(ch, ()); // If killed first, grandparent hangs. + } + fail; // Shouldn't kill either (grand)parent or (grand)child. + } + comm::recv(po); +} +#[test] #[ignore(cfg(windows))] +fn test_spawn_unlinked_unsup_no_fail_up() { // child unlinked fails + do spawn_unlinked { fail; } +} +#[test] #[ignore(cfg(windows))] +fn test_spawn_unlinked_sup_no_fail_up() { // child unlinked fails + do spawn_supervised { fail; } + // Give child a chance to fail-but-not-kill-us. + for iter::repeat(16) { task::yield(); } +} +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_spawn_unlinked_sup_fail_down() { + do spawn_supervised { loop { task::yield(); } } + fail; // Shouldn't leave a child hanging around. +} + +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_spawn_linked_sup_fail_up() { // child fails; parent fails + let po = comm::Port::<()>(); + let _ch = comm::Chan(&po); + // Unidirectional "parenting" shouldn't override bidirectional linked. + // We have to cheat with opts - the interface doesn't support them because + // they don't make sense (redundant with task().supervised()). + let opts = { + let mut opts = default_task_opts(); + opts.linked = true; + opts.supervised = true; + move opts + }; + + let b0 = task(); + let b1 = TaskBuilder({ + opts: move opts, + can_not_copy: None, + .. *b0 + }); + do b1.spawn { fail; } + comm::recv(po); // We should get punted awake +} +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_spawn_linked_sup_fail_down() { // parent fails; child fails + // We have to cheat with opts - the interface doesn't support them because + // they don't make sense (redundant with task().supervised()). + let opts = { + let mut opts = default_task_opts(); + opts.linked = true; + opts.supervised = true; + move opts + }; + + let b0 = task(); + let b1 = TaskBuilder({ + opts: move opts, + can_not_copy: None, + .. *b0 + }); + do b1.spawn { loop { task::yield(); } } + fail; // *both* mechanisms would be wrong if this didn't kill the child... +} +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_spawn_linked_unsup_fail_up() { // child fails; parent fails + let po = comm::Port::<()>(); + let _ch = comm::Chan(&po); + // Default options are to spawn linked & unsupervised. + do spawn { fail; } + comm::recv(po); // We should get punted awake +} +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_spawn_linked_unsup_fail_down() { // parent fails; child fails + // Default options are to spawn linked & unsupervised. + do spawn { loop { task::yield(); } } + fail; +} +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_spawn_linked_unsup_default_opts() { // parent fails; child fails + // Make sure the above test is the same as this one. + do task().linked().spawn { loop { task::yield(); } } + fail; +} + +// A couple bonus linked failure tests - testing for failure propagation even +// when the middle task exits successfully early before kill signals are sent. + +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_spawn_failure_propagate_grandchild() { + // Middle task exits; does grandparent's failure propagate across the gap? + do spawn_supervised { + do spawn_supervised { + loop { task::yield(); } + } + } + for iter::repeat(16) { task::yield(); } + fail; +} + +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_spawn_failure_propagate_secondborn() { + // First-born child exits; does parent's failure propagate to sibling? + do spawn_supervised { + do spawn { // linked + loop { task::yield(); } + } + } + for iter::repeat(16) { task::yield(); } + fail; +} + +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_spawn_failure_propagate_nephew_or_niece() { + // Our sibling exits; does our failure propagate to sibling's child? + do spawn { // linked + do spawn_supervised { + loop { task::yield(); } + } + } + for iter::repeat(16) { task::yield(); } + fail; +} + +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_spawn_linked_sup_propagate_sibling() { + // Middle sibling exits - does eldest's failure propagate to youngest? + do spawn { // linked + do spawn { // linked + loop { task::yield(); } + } + } + for iter::repeat(16) { task::yield(); } + fail; +} + +#[test] +fn test_run_basic() { + let po = comm::Port(); + let ch = comm::Chan(&po); + do task().spawn { + comm::send(ch, ()); + } + comm::recv(po); +} + +#[test] +fn test_add_wrapper() { + let po = comm::Port(); + let ch = comm::Chan(&po); + let b0 = task(); + let b1 = do b0.add_wrapper |body| { + fn~(move body) { + body(); + comm::send(ch, ()); + } + }; + do b1.spawn { } + comm::recv(po); +} + +#[test] +#[ignore(cfg(windows))] +fn test_future_result() { + let mut result = None; + do task().future_result(|+r| { result = Some(move r); }).spawn { } + assert option::unwrap(move result).recv() == Success; + + result = None; + do task().future_result(|+r| + { result = Some(move r); }).unlinked().spawn { + fail; + } + assert option::unwrap(move result).recv() == Failure; +} + +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_back_to_the_future_result() { + let _ = task().future_result(util::ignore).future_result(util::ignore); +} + +#[test] +fn test_spawn_listiner_bidi() { + let po = comm::Port(); + let ch = comm::Chan(&po); + let ch = do spawn_listener |po| { + // Now the child has a port called 'po' to read from and + // an environment-captured channel called 'ch'. + let res: ~str = comm::recv(po); + assert res == ~"ping"; + comm::send(ch, ~"pong"); + }; + // Likewise, the parent has both a 'po' and 'ch' + comm::send(ch, ~"ping"); + let res: ~str = comm::recv(po); + assert res == ~"pong"; +} + +#[test] +fn test_spawn_conversation() { + let (recv_str, send_int) = do spawn_conversation |recv_int, send_str| { + let input = comm::recv(recv_int); + let output = int::str(input); + comm::send(send_str, move output); + }; + comm::send(send_int, 1); + assert comm::recv(recv_str) == ~"1"; +} + +#[test] +fn test_try_success() { + match do try { + ~"Success!" + } { + result::Ok(~"Success!") => (), + _ => fail + } +} + +#[test] +#[ignore(cfg(windows))] +fn test_try_fail() { + match do try { + fail + } { + result::Err(()) => (), + result::Ok(()) => fail + } +} + +#[test] +#[should_fail] +#[ignore(cfg(windows))] +fn test_spawn_sched_no_threads() { + do spawn_sched(ManualThreads(0u)) { } +} + +#[test] +fn test_spawn_sched() { + let po = comm::Port(); + let ch = comm::Chan(&po); + + fn f(i: int, ch: comm::Chan<()>) { + let parent_sched_id = rt::rust_get_sched_id(); + + do spawn_sched(SingleThreaded) { + let child_sched_id = rt::rust_get_sched_id(); + assert parent_sched_id != child_sched_id; + + if (i == 0) { + comm::send(ch, ()); + } else { + f(i - 1, ch); + } + }; + + } + f(10, ch); + comm::recv(po); +} + +#[test] +fn test_spawn_sched_childs_on_same_sched() { + let po = comm::Port(); + let ch = comm::Chan(&po); + + do spawn_sched(SingleThreaded) { + let parent_sched_id = rt::rust_get_sched_id(); + do spawn { + let child_sched_id = rt::rust_get_sched_id(); + // This should be on the same scheduler + assert parent_sched_id == child_sched_id; + comm::send(ch, ()); + }; + }; + + comm::recv(po); +} + +#[nolink] +#[cfg(test)] +extern mod testrt { + fn rust_dbg_lock_create() -> *libc::c_void; + fn rust_dbg_lock_destroy(lock: *libc::c_void); + fn rust_dbg_lock_lock(lock: *libc::c_void); + fn rust_dbg_lock_unlock(lock: *libc::c_void); + fn rust_dbg_lock_wait(lock: *libc::c_void); + fn rust_dbg_lock_signal(lock: *libc::c_void); +} + +#[test] +fn test_spawn_sched_blocking() { + + // Testing that a task in one scheduler can block in foreign code + // without affecting other schedulers + for iter::repeat(20u) { + + let start_po = comm::Port(); + let start_ch = comm::Chan(&start_po); + let fin_po = comm::Port(); + let fin_ch = comm::Chan(&fin_po); + + let lock = testrt::rust_dbg_lock_create(); + + do spawn_sched(SingleThreaded) { + testrt::rust_dbg_lock_lock(lock); + + comm::send(start_ch, ()); + + // Block the scheduler thread + testrt::rust_dbg_lock_wait(lock); + testrt::rust_dbg_lock_unlock(lock); + + comm::send(fin_ch, ()); + }; + + // Wait until the other task has its lock + comm::recv(start_po); + + fn pingpong(po: comm::Port<int>, ch: comm::Chan<int>) { + let mut val = 20; + while val > 0 { + val = comm::recv(po); + comm::send(ch, val - 1); + } + } + + let setup_po = comm::Port(); + let setup_ch = comm::Chan(&setup_po); + let parent_po = comm::Port(); + let parent_ch = comm::Chan(&parent_po); + do spawn { + let child_po = comm::Port(); + comm::send(setup_ch, comm::Chan(&child_po)); + pingpong(child_po, parent_ch); + }; + + let child_ch = comm::recv(setup_po); + comm::send(child_ch, 20); + pingpong(parent_po, child_ch); + testrt::rust_dbg_lock_lock(lock); + testrt::rust_dbg_lock_signal(lock); + testrt::rust_dbg_lock_unlock(lock); + comm::recv(fin_po); + testrt::rust_dbg_lock_destroy(lock); + } +} + +#[cfg(test)] +fn avoid_copying_the_body(spawnfn: fn(v: fn~())) { + let p = comm::Port::<uint>(); + let ch = comm::Chan(&p); + + let x = ~1; + let x_in_parent = ptr::addr_of(&(*x)) as uint; + + do spawnfn |move x| { + let x_in_child = ptr::addr_of(&(*x)) as uint; + comm::send(ch, x_in_child); + } + + let x_in_child = comm::recv(p); + assert x_in_parent == x_in_child; +} + +#[test] +fn test_avoid_copying_the_body_spawn() { + avoid_copying_the_body(spawn); +} + +#[test] +fn test_avoid_copying_the_body_spawn_listener() { + do avoid_copying_the_body |f| { + spawn_listener(fn~(move f, _po: comm::Port<int>) { + f(); + }); + } +} + +#[test] +fn test_avoid_copying_the_body_task_spawn() { + do avoid_copying_the_body |f| { + do task().spawn |move f| { + f(); + } + } +} + +#[test] +fn test_avoid_copying_the_body_spawn_listener_1() { + do avoid_copying_the_body |f| { + task().spawn_listener(fn~(move f, _po: comm::Port<int>) { + f(); + }); + } +} + +#[test] +fn test_avoid_copying_the_body_try() { + do avoid_copying_the_body |f| { + do try |move f| { + f() + }; + } +} + +#[test] +fn test_avoid_copying_the_body_unlinked() { + do avoid_copying_the_body |f| { + do spawn_unlinked |move f| { + f(); + } + } +} + +#[test] +fn test_platform_thread() { + let po = comm::Port(); + let ch = comm::Chan(&po); + do task().sched_mode(PlatformThread).spawn { + comm::send(ch, ()); + } + comm::recv(po); +} + +#[test] +#[ignore(cfg(windows))] +#[should_fail] +fn test_unkillable() { + let po = comm::Port(); + let ch = po.chan(); + + // We want to do this after failing + do spawn_unlinked { + for iter::repeat(10) { yield() } + ch.send(()); + } + + do spawn { + yield(); + // We want to fail after the unkillable task + // blocks on recv + fail; + } + + unsafe { + do unkillable { + let p = ~0; + let pp: *uint = cast::transmute(move p); + + // If we are killed here then the box will leak + po.recv(); + + let _p: ~int = cast::transmute(move pp); + } + } + + // Now we can be killed + po.recv(); +} + +#[test] +#[ignore(cfg(windows))] +#[should_fail] +fn test_unkillable_nested() { + let (ch, po) = pipes::stream(); + + // We want to do this after failing + do spawn_unlinked |move ch| { + for iter::repeat(10) { yield() } + ch.send(()); + } + + do spawn { + yield(); + // We want to fail after the unkillable task + // blocks on recv + fail; + } + + unsafe { + do unkillable { + do unkillable {} // Here's the difference from the previous test. + let p = ~0; + let pp: *uint = cast::transmute(move p); + + // If we are killed here then the box will leak + po.recv(); + + let _p: ~int = cast::transmute(move pp); + } + } + + // Now we can be killed + po.recv(); +} + +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_atomically() { + unsafe { do atomically { yield(); } } +} + +#[test] +fn test_atomically2() { + unsafe { do atomically { } } yield(); // shouldn't fail +} + +#[test] #[should_fail] #[ignore(cfg(windows))] +fn test_atomically_nested() { + unsafe { do atomically { do atomically { } yield(); } } +} + +#[test] +fn test_child_doesnt_ref_parent() { + // If the child refcounts the parent task, this will stack overflow when + // climbing the task tree to dereference each ancestor. (See #1789) + // (well, it would if the constant were 8000+ - I lowered it to be more + // valgrind-friendly. try this at home, instead..!) + const generations: uint = 16; + fn child_no(x: uint) -> fn~() { + return || { + if x < generations { + task::spawn(child_no(x+1)); + } + } + } + task::spawn(child_no(0)); +} + +#[test] +fn test_sched_thread_per_core() { + let (chan, port) = pipes::stream(); + + do spawn_sched(ThreadPerCore) |move chan| { + let cores = rt::rust_num_threads(); + let reported_threads = rt::rust_sched_threads(); + assert(cores as uint == reported_threads as uint); + chan.send(()); + } + + port.recv(); +} + +#[test] +fn test_spawn_thread_on_demand() { + let (chan, port) = pipes::stream(); + + do spawn_sched(ManualThreads(2)) |move chan| { + let max_threads = rt::rust_sched_threads(); + assert(max_threads as int == 2); + let running_threads = rt::rust_sched_current_nonlazy_threads(); + assert(running_threads as int == 1); + + let (chan2, port2) = pipes::stream(); + + do spawn() |move chan2| { + chan2.send(()); + } + + let running_threads2 = rt::rust_sched_current_nonlazy_threads(); + assert(running_threads2 as int == 2); + + port2.recv(); + chan.send(()); + } + + port.recv(); +} diff --git a/src/librustc/driver.rs b/src/librustc/driver.rs deleted file mode 100644 index cbe6345a9f1..00000000000 --- a/src/librustc/driver.rs +++ /dev/null @@ -1,5 +0,0 @@ -use syntax::diagnostic; -export diagnostic; - -export driver; -export session; diff --git a/src/librustc/driver/mod.rs b/src/librustc/driver/mod.rs index 7d9a55571d4..a9d5afeb522 100644 --- a/src/librustc/driver/mod.rs +++ b/src/librustc/driver/mod.rs @@ -1,4 +1,12 @@ #[legacy_exports]; + +use syntax::diagnostic; + +export diagnostic; + +export driver; +export session; + #[legacy_exports] mod driver; #[legacy_exports] diff --git a/src/librustc/metadata.rs b/src/librustc/metadata.rs deleted file mode 100644 index 2d2d6a3f79e..00000000000 --- a/src/librustc/metadata.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Define the rustc API's that the metadata module has access to -// Over time we will reduce these dependencies and, once metadata has -// no dependencies on rustc it can move into its own crate. - -mod middle { - #[legacy_exports]; - pub use middle_::ty; - pub use middle_::resolve; -} - -mod front { - #[legacy_exports]; -} - -mod back { - #[legacy_exports]; -} - -mod driver { - #[legacy_exports]; -} - -mod util { - #[legacy_exports]; - pub use util_::ppaux; -} - -mod lib { - #[legacy_exports]; - pub use lib_::llvm; -} diff --git a/src/librustc/metadata/mod.rs b/src/librustc/metadata/mod.rs index a82ad39d412..dfe983cc32b 100644 --- a/src/librustc/metadata/mod.rs +++ b/src/librustc/metadata/mod.rs @@ -30,3 +30,36 @@ mod csearch; mod loader; #[legacy_exports] mod filesearch; + + +// Define the rustc API's that the metadata module has access to +// Over time we will reduce these dependencies and, once metadata has +// no dependencies on rustc it can move into its own crate. + +mod middle { + #[legacy_exports]; + pub use middle_::ty; + pub use middle_::resolve; +} + +mod front { + #[legacy_exports]; +} + +mod back { + #[legacy_exports]; +} + +mod driver { + #[legacy_exports]; +} + +mod util { + #[legacy_exports]; + pub use util_::ppaux; +} + +mod lib { + #[legacy_exports]; + pub use lib_::llvm; +} diff --git a/src/librustc/middle/borrowck.rs b/src/librustc/middle/borrowck.rs deleted file mode 100644 index df573b121f8..00000000000 --- a/src/librustc/middle/borrowck.rs +++ /dev/null @@ -1,618 +0,0 @@ -/*! -# Borrow check - -This pass is in job of enforcing *memory safety* and *purity*. As -memory safety is by far the more complex topic, I'll focus on that in -this description, but purity will be covered later on. In the context -of Rust, memory safety means three basic things: - -- no writes to immutable memory; -- all pointers point to non-freed memory; -- all pointers point to memory of the same type as the pointer. - -The last point might seem confusing: after all, for the most part, -this condition is guaranteed by the type check. However, there are -two cases where the type check effectively delegates to borrow check. - -The first case has to do with enums. If there is a pointer to the -interior of an enum, and the enum is in a mutable location (such as a -local variable or field declared to be mutable), it is possible that -the user will overwrite the enum with a new value of a different -variant, and thus effectively change the type of the memory that the -pointer is pointing at. - -The second case has to do with mutability. Basically, the type -checker has only a limited understanding of mutability. It will allow -(for example) the user to get an immutable pointer with the address of -a mutable local variable. It will also allow a `@mut T` or `~mut T` -pointer to be borrowed as a `&r.T` pointer. These seeming oversights -are in fact intentional; they allow the user to temporarily treat a -mutable value as immutable. It is up to the borrow check to guarantee -that the value in question is not in fact mutated during the lifetime -`r` of the reference. - -# Definition of unstable memory - -The primary danger to safety arises due to *unstable memory*. -Unstable memory is memory whose validity or type may change as a -result of an assignment, move, or a variable going out of scope. -There are two cases in Rust where memory is unstable: the contents of -unique boxes and enums. - -Unique boxes are unstable because when the variable containing the -unique box is re-assigned, moves, or goes out of scope, the unique box -is freed or---in the case of a move---potentially given to another -task. In either case, if there is an extant and usable pointer into -the box, then safety guarantees would be compromised. - -Enum values are unstable because they are reassigned the types of -their contents may change if they are assigned with a different -variant than they had previously. - -# Safety criteria that must be enforced - -Whenever a piece of memory is borrowed for lifetime L, there are two -things which the borrow checker must guarantee. First, it must -guarantee that the memory address will remain allocated (and owned by -the current task) for the entirety of the lifetime L. Second, it must -guarantee that the type of the data will not change for the entirety -of the lifetime L. In exchange, the region-based type system will -guarantee that the pointer is not used outside the lifetime L. These -guarantees are to some extent independent but are also inter-related. - -In some cases, the type of a pointer cannot be invalidated but the -lifetime can. For example, imagine a pointer to the interior of -a shared box like: - - let mut x = @mut {f: 5, g: 6}; - let y = &mut x.f; - -Here, a pointer was created to the interior of a shared box which -contains a record. Even if `*x` were to be mutated like so: - - *x = {f: 6, g: 7}; - -This would cause `*y` to change from 5 to 6, but the pointer pointer -`y` remains valid. It still points at an integer even if that integer -has been overwritten. - -However, if we were to reassign `x` itself, like so: - - x = @{f: 6, g: 7}; - -This could potentially invalidate `y`, because if `x` were the final -reference to the shared box, then that memory would be released and -now `y` points at freed memory. (We will see that to prevent this -scenario we will *root* shared boxes that reside in mutable memory -whose contents are borrowed; rooting means that we create a temporary -to ensure that the box is not collected). - -In other cases, like an enum on the stack, the memory cannot be freed -but its type can change: - - let mut x = Some(5); - match x { - Some(ref y) => { ... } - None => { ... } - } - -Here as before, the pointer `y` would be invalidated if we were to -reassign `x` to `none`. (We will see that this case is prevented -because borrowck tracks data which resides on the stack and prevents -variables from reassigned if there may be pointers to their interior) - -Finally, in some cases, both dangers can arise. For example, something -like the following: - - let mut x = ~some(5); - match x { - ~some(ref y) => { ... } - ~none => { ... } - } - -In this case, if `x` to be reassigned or `*x` were to be mutated, then -the pointer `y` would be invalided. (This case is also prevented by -borrowck tracking data which is owned by the current stack frame) - -# Summary of the safety check - -In order to enforce mutability, the borrow check has a few tricks up -its sleeve: - -- When data is owned by the current stack frame, we can identify every - possible assignment to a local variable and simply prevent - potentially dangerous assignments directly. - -- If data is owned by a shared box, we can root the box to increase - its lifetime. - -- If data is found within a borrowed pointer, we can assume that the - data will remain live for the entirety of the borrowed pointer. - -- We can rely on the fact that pure actions (such as calling pure - functions) do not mutate data which is not owned by the current - stack frame. - -# Possible future directions - -There are numerous ways that the `borrowck` could be strengthened, but -these are the two most likely: - -- flow-sensitivity: we do not currently consider flow at all but only - block-scoping. This means that innocent code like the following is - rejected: - - let mut x: int; - ... - x = 5; - let y: &int = &x; // immutable ptr created - ... - - The reason is that the scope of the pointer `y` is the entire - enclosing block, and the assignment `x = 5` occurs within that - block. The analysis is not smart enough to see that `x = 5` always - happens before the immutable pointer is created. This is relatively - easy to fix and will surely be fixed at some point. - -- finer-grained purity checks: currently, our fallback for - guaranteeing random references into mutable, aliasable memory is to - require *total purity*. This is rather strong. We could use local - type-based alias analysis to distinguish writes that could not - possibly invalid the references which must be guaranteed. This - would only work within the function boundaries; function calls would - still require total purity. This seems less likely to be - implemented in the short term as it would make the code - significantly more complex; there is currently no code to analyze - the types and determine the possible impacts of a write. - -# How the code works - -The borrow check code is divided into several major modules, each of -which is documented in its own file. - -The `gather_loans` and `check_loans` are the two major passes of the -analysis. The `gather_loans` pass runs over the IR once to determine -what memory must remain valid and for how long. Its name is a bit of -a misnomer; it does in fact gather up the set of loans which are -granted, but it also determines when @T pointers must be rooted and -for which scopes purity must be required. - -The `check_loans` pass walks the IR and examines the loans and purity -requirements computed in `gather_loans`. It checks to ensure that (a) -the conditions of all loans are honored; (b) no contradictory loans -were granted (for example, loaning out the same memory as mutable and -immutable simultaneously); and (c) any purity requirements are -honored. - -The remaining modules are helper modules used by `gather_loans` and -`check_loans`: - -- `categorization` has the job of analyzing an expression to determine - what kind of memory is used in evaluating it (for example, where - dereferences occur and what kind of pointer is dereferenced; whether - the memory is mutable; etc) -- `loan` determines when data uniquely tied to the stack frame can be - loaned out. -- `preserve` determines what actions (if any) must be taken to preserve - aliasable data. This is the code which decides when to root - an @T pointer or to require purity. - -# Maps that are created - -Borrowck results in two maps. - -- `root_map`: identifies those expressions or patterns whose result - needs to be rooted. Conceptually the root_map maps from an - expression or pattern node to a `node_id` identifying the scope for - which the expression must be rooted (this `node_id` should identify - a block or call). The actual key to the map is not an expression id, - however, but a `root_map_key`, which combines an expression id with a - deref count and is used to cope with auto-deref. - -- `mutbl_map`: identifies those local variables which are modified or - moved. This is used by trans to guarantee that such variables are - given a memory location and not used as immediates. - */ - -use syntax::ast; -use syntax::ast::{mutability, m_mutbl, m_imm, m_const}; -use syntax::visit; -use syntax::ast_util; -use syntax::ast_map; -use syntax::codemap::span; -use util::ppaux::{ty_to_str, region_to_str, explain_region, - expr_repr, note_and_explain_region}; -use std::map::{HashMap, Set}; -use std::list; -use std::list::{List, Cons, Nil}; -use result::{Result, Ok, Err}; -use syntax::print::pprust; -use util::common::indenter; -use ty::to_str; -use dvec::DVec; -use mem_categorization::*; - -export check_crate, root_map, mutbl_map; - -fn check_crate(tcx: ty::ctxt, - method_map: typeck::method_map, - last_use_map: liveness::last_use_map, - crate: @ast::crate) -> (root_map, mutbl_map) { - - let bccx = borrowck_ctxt_(@{tcx: tcx, - method_map: method_map, - last_use_map: last_use_map, - root_map: root_map(), - mutbl_map: HashMap(), - mut loaned_paths_same: 0, - mut loaned_paths_imm: 0, - mut stable_paths: 0, - mut req_pure_paths: 0, - mut guaranteed_paths: 0}); - - let req_maps = gather_loans::gather_loans(bccx, crate); - check_loans::check_loans(bccx, req_maps, crate); - - if tcx.sess.borrowck_stats() { - io::println(~"--- borrowck stats ---"); - io::println(fmt!("paths requiring guarantees: %u", - bccx.guaranteed_paths)); - io::println(fmt!("paths requiring loans : %s", - make_stat(bccx, bccx.loaned_paths_same))); - io::println(fmt!("paths requiring imm loans : %s", - make_stat(bccx, bccx.loaned_paths_imm))); - io::println(fmt!("stable paths : %s", - make_stat(bccx, bccx.stable_paths))); - io::println(fmt!("paths requiring purity : %s", - make_stat(bccx, bccx.req_pure_paths))); - } - - return (bccx.root_map, bccx.mutbl_map); - - fn make_stat(bccx: borrowck_ctxt, stat: uint) -> ~str { - let stat_f = stat as float; - let total = bccx.guaranteed_paths as float; - fmt!("%u (%.0f%%)", stat , stat_f * 100f / total) - } -} - -// ---------------------------------------------------------------------- -// Type definitions - -type borrowck_ctxt_ = {tcx: ty::ctxt, - method_map: typeck::method_map, - last_use_map: liveness::last_use_map, - root_map: root_map, - mutbl_map: mutbl_map, - - // Statistics: - mut loaned_paths_same: uint, - mut loaned_paths_imm: uint, - mut stable_paths: uint, - mut req_pure_paths: uint, - mut guaranteed_paths: uint}; - -enum borrowck_ctxt { - borrowck_ctxt_(@borrowck_ctxt_) -} - -// a map mapping id's of expressions of gc'd type (@T, @[], etc) where -// the box needs to be kept live to the id of the scope for which they -// must stay live. -type root_map = HashMap<root_map_key, ast::node_id>; - -// the keys to the root map combine the `id` of the expression with -// the number of types that it is autodereferenced. So, for example, -// if you have an expression `x.f` and x has type ~@T, we could add an -// entry {id:x, derefs:0} to refer to `x` itself, `{id:x, derefs:1}` -// to refer to the deref of the unique pointer, and so on. -type root_map_key = {id: ast::node_id, derefs: uint}; - -// set of ids of local vars / formal arguments that are modified / moved. -// this is used in trans for optimization purposes. -type mutbl_map = std::map::HashMap<ast::node_id, ()>; - -// Errors that can occur"] -enum bckerr_code { - err_mut_uniq, - err_mut_variant, - err_root_not_permitted, - err_mutbl(ast::mutability), - err_out_of_root_scope(ty::Region, ty::Region), // superscope, subscope - err_out_of_scope(ty::Region, ty::Region) // superscope, subscope -} - -impl bckerr_code : cmp::Eq { - pure fn eq(&self, other: &bckerr_code) -> bool { - match (*self) { - err_mut_uniq => { - match (*other) { - err_mut_uniq => true, - _ => false - } - } - err_mut_variant => { - match (*other) { - err_mut_variant => true, - _ => false - } - } - err_root_not_permitted => { - match (*other) { - err_root_not_permitted => true, - _ => false - } - } - err_mutbl(e0a) => { - match (*other) { - err_mutbl(e0b) => e0a == e0b, - _ => false - } - } - err_out_of_root_scope(e0a, e1a) => { - match (*other) { - err_out_of_root_scope(e0b, e1b) => - e0a == e0b && e1a == e1b, - _ => false - } - } - err_out_of_scope(e0a, e1a) => { - match (*other) { - err_out_of_scope(e0b, e1b) => e0a == e0b && e1a == e1b, - _ => false - } - } - } - } - pure fn ne(&self, other: &bckerr_code) -> bool { !(*self).eq(other) } -} - -// Combination of an error code and the categorization of the expression -// that caused it -type bckerr = {cmt: cmt, code: bckerr_code}; - -impl bckerr : cmp::Eq { - pure fn eq(&self, other: &bckerr) -> bool { - (*self).cmt == (*other).cmt && (*self).code == (*other).code - } - pure fn ne(&self, other: &bckerr) -> bool { !(*self).eq(other) } -} - -// shorthand for something that fails with `bckerr` or succeeds with `T` -type bckres<T> = Result<T, bckerr>; - -/// a complete record of a loan that was granted -struct Loan {lp: @loan_path, cmt: cmt, mutbl: ast::mutability} - -/// maps computed by `gather_loans` that are then used by `check_loans` -/// -/// - `req_loan_map`: map from each block/expr to the required loans needed -/// for the duration of that block/expr -/// - `pure_map`: map from block/expr that must be pure to the error message -/// that should be reported if they are not pure -type req_maps = { - req_loan_map: HashMap<ast::node_id, @DVec<Loan>>, - pure_map: HashMap<ast::node_id, bckerr> -}; - -fn save_and_restore<T:Copy,U>(save_and_restore_t: &mut T, f: fn() -> U) -> U { - let old_save_and_restore_t = *save_and_restore_t; - let u = f(); - *save_and_restore_t = old_save_and_restore_t; - move u -} - -/// Creates and returns a new root_map - -impl root_map_key : cmp::Eq { - pure fn eq(&self, other: &root_map_key) -> bool { - (*self).id == (*other).id && (*self).derefs == (*other).derefs - } - pure fn ne(&self, other: &root_map_key) -> bool { - ! ((*self) == (*other)) - } -} - -#[cfg(stage0)] -impl root_map_key : to_bytes::IterBytes { - pure fn iter_bytes(+lsb0: bool, f: to_bytes::Cb) { - to_bytes::iter_bytes_2(&self.id, &self.derefs, lsb0, f); - } -} -#[cfg(stage1)] -#[cfg(stage2)] -impl root_map_key : to_bytes::IterBytes { - pure fn iter_bytes(&self, +lsb0: bool, f: to_bytes::Cb) { - to_bytes::iter_bytes_2(&self.id, &self.derefs, lsb0, f); - } -} - -fn root_map() -> root_map { - return HashMap(); - - pure fn root_map_key_eq(k1: &root_map_key, k2: &root_map_key) -> bool { - k1.id == k2.id && k1.derefs == k2.derefs - } - - pure fn root_map_key_hash(k: &root_map_key) -> uint { - (k.id << 4) as uint | k.derefs - } -} - -// ___________________________________________________________________________ -// Misc - -impl borrowck_ctxt { - fn is_subregion_of(r_sub: ty::Region, r_sup: ty::Region) -> bool { - region::is_subregion_of(self.tcx.region_map, r_sub, r_sup) - } - - fn cat_expr(expr: @ast::expr) -> cmt { - cat_expr(self.tcx, self.method_map, expr) - } - - fn cat_expr_unadjusted(expr: @ast::expr) -> cmt { - cat_expr_unadjusted(self.tcx, self.method_map, expr) - } - - fn cat_expr_autoderefd(expr: @ast::expr, - adj: @ty::AutoAdjustment) - -> cmt { - cat_expr_autoderefd(self.tcx, self.method_map, expr, adj) - } - - fn cat_def(id: ast::node_id, - span: span, - ty: ty::t, - def: ast::def) -> cmt { - cat_def(self.tcx, self.method_map, id, span, ty, def) - } - - fn cat_variant<N: ast_node>(arg: N, - enum_did: ast::def_id, - cmt: cmt) -> cmt { - cat_variant(self.tcx, self.method_map, arg, enum_did, cmt) - } - - fn cat_discr(cmt: cmt, alt_id: ast::node_id) -> cmt { - return @{cat:cat_discr(cmt, alt_id),.. *cmt}; - } - - fn cat_pattern(cmt: cmt, pat: @ast::pat, op: fn(cmt, @ast::pat)) { - let mc = &mem_categorization_ctxt {tcx: self.tcx, - method_map: self.method_map}; - mc.cat_pattern(cmt, pat, op); - } - - fn report_if_err(bres: bckres<()>) { - match bres { - Ok(()) => (), - Err(e) => self.report(e) - } - } - - fn report(err: bckerr) { - self.span_err( - err.cmt.span, - fmt!("illegal borrow: %s", - self.bckerr_to_str(err))); - self.note_and_explain_bckerr(err); - } - - fn span_err(s: span, m: ~str) { - self.tcx.sess.span_err(s, m); - } - - fn span_note(s: span, m: ~str) { - self.tcx.sess.span_note(s, m); - } - - fn add_to_mutbl_map(cmt: cmt) { - match cmt.cat { - cat_local(id) | cat_arg(id) => { - self.mutbl_map.insert(id, ()); - } - cat_stack_upvar(cmt) => { - self.add_to_mutbl_map(cmt); - } - _ => () - } - } - - fn bckerr_to_str(err: bckerr) -> ~str { - match err.code { - err_mutbl(req) => { - fmt!("creating %s alias to %s", - self.mut_to_str(req), - self.cmt_to_str(err.cmt)) - } - err_mut_uniq => { - ~"unique value in aliasable, mutable location" - } - err_mut_variant => { - ~"enum variant in aliasable, mutable location" - } - err_root_not_permitted => { - // note: I don't expect users to ever see this error - // message, reasons are discussed in attempt_root() in - // preserve.rs. - ~"rooting is not permitted" - } - err_out_of_root_scope(*) => { - ~"cannot root managed value long enough" - } - err_out_of_scope(*) => { - ~"borrowed value does not live long enough" - } - } - } - - fn note_and_explain_bckerr(err: bckerr) { - let code = err.code; - match code { - err_mutbl(*) | err_mut_uniq | err_mut_variant | - err_root_not_permitted => {} - - err_out_of_root_scope(super_scope, sub_scope) => { - note_and_explain_region( - self.tcx, - ~"managed value would have to be rooted for ", - sub_scope, - ~"..."); - note_and_explain_region( - self.tcx, - ~"...but can only be rooted for ", - super_scope, - ~""); - } - - err_out_of_scope(super_scope, sub_scope) => { - note_and_explain_region( - self.tcx, - ~"borrowed pointer must be valid for ", - sub_scope, - ~"..."); - note_and_explain_region( - self.tcx, - ~"...but borrowed value is only valid for ", - super_scope, - ~""); - } - } - } - - - fn cmt_to_str(cmt: cmt) -> ~str { - let mc = &mem_categorization_ctxt {tcx: self.tcx, - method_map: self.method_map}; - mc.cmt_to_str(cmt) - } - - fn cmt_to_repr(cmt: cmt) -> ~str { - let mc = &mem_categorization_ctxt {tcx: self.tcx, - method_map: self.method_map}; - mc.cmt_to_repr(cmt) - } - - fn mut_to_str(mutbl: ast::mutability) -> ~str { - let mc = &mem_categorization_ctxt {tcx: self.tcx, - method_map: self.method_map}; - mc.mut_to_str(mutbl) - } - - fn loan_to_repr(loan: &Loan) -> ~str { - fmt!("Loan(lp=%?, cmt=%s, mutbl=%?)", - loan.lp, self.cmt_to_repr(loan.cmt), loan.mutbl) - } -} - -// The inherent mutability of a component is its default mutability -// assuming it is embedded in an immutable context. In general, the -// mutability can be "overridden" if the component is embedded in a -// mutable structure. -fn inherent_mutability(ck: comp_kind) -> mutability { - match ck { - comp_tuple | comp_anon_field | comp_variant(_) => m_imm, - comp_field(_, m) | comp_index(_, m) => m - } -} diff --git a/src/librustc/middle/borrowck/mod.rs b/src/librustc/middle/borrowck/mod.rs index c4411008404..6ef80321ec2 100644 --- a/src/librustc/middle/borrowck/mod.rs +++ b/src/librustc/middle/borrowck/mod.rs @@ -1,4 +1,239 @@ +/*! +# Borrow check + +This pass is in job of enforcing *memory safety* and *purity*. As +memory safety is by far the more complex topic, I'll focus on that in +this description, but purity will be covered later on. In the context +of Rust, memory safety means three basic things: + +- no writes to immutable memory; +- all pointers point to non-freed memory; +- all pointers point to memory of the same type as the pointer. + +The last point might seem confusing: after all, for the most part, +this condition is guaranteed by the type check. However, there are +two cases where the type check effectively delegates to borrow check. + +The first case has to do with enums. If there is a pointer to the +interior of an enum, and the enum is in a mutable location (such as a +local variable or field declared to be mutable), it is possible that +the user will overwrite the enum with a new value of a different +variant, and thus effectively change the type of the memory that the +pointer is pointing at. + +The second case has to do with mutability. Basically, the type +checker has only a limited understanding of mutability. It will allow +(for example) the user to get an immutable pointer with the address of +a mutable local variable. It will also allow a `@mut T` or `~mut T` +pointer to be borrowed as a `&r.T` pointer. These seeming oversights +are in fact intentional; they allow the user to temporarily treat a +mutable value as immutable. It is up to the borrow check to guarantee +that the value in question is not in fact mutated during the lifetime +`r` of the reference. + +# Definition of unstable memory + +The primary danger to safety arises due to *unstable memory*. +Unstable memory is memory whose validity or type may change as a +result of an assignment, move, or a variable going out of scope. +There are two cases in Rust where memory is unstable: the contents of +unique boxes and enums. + +Unique boxes are unstable because when the variable containing the +unique box is re-assigned, moves, or goes out of scope, the unique box +is freed or---in the case of a move---potentially given to another +task. In either case, if there is an extant and usable pointer into +the box, then safety guarantees would be compromised. + +Enum values are unstable because they are reassigned the types of +their contents may change if they are assigned with a different +variant than they had previously. + +# Safety criteria that must be enforced + +Whenever a piece of memory is borrowed for lifetime L, there are two +things which the borrow checker must guarantee. First, it must +guarantee that the memory address will remain allocated (and owned by +the current task) for the entirety of the lifetime L. Second, it must +guarantee that the type of the data will not change for the entirety +of the lifetime L. In exchange, the region-based type system will +guarantee that the pointer is not used outside the lifetime L. These +guarantees are to some extent independent but are also inter-related. + +In some cases, the type of a pointer cannot be invalidated but the +lifetime can. For example, imagine a pointer to the interior of +a shared box like: + + let mut x = @mut {f: 5, g: 6}; + let y = &mut x.f; + +Here, a pointer was created to the interior of a shared box which +contains a record. Even if `*x` were to be mutated like so: + + *x = {f: 6, g: 7}; + +This would cause `*y` to change from 5 to 6, but the pointer pointer +`y` remains valid. It still points at an integer even if that integer +has been overwritten. + +However, if we were to reassign `x` itself, like so: + + x = @{f: 6, g: 7}; + +This could potentially invalidate `y`, because if `x` were the final +reference to the shared box, then that memory would be released and +now `y` points at freed memory. (We will see that to prevent this +scenario we will *root* shared boxes that reside in mutable memory +whose contents are borrowed; rooting means that we create a temporary +to ensure that the box is not collected). + +In other cases, like an enum on the stack, the memory cannot be freed +but its type can change: + + let mut x = Some(5); + match x { + Some(ref y) => { ... } + None => { ... } + } + +Here as before, the pointer `y` would be invalidated if we were to +reassign `x` to `none`. (We will see that this case is prevented +because borrowck tracks data which resides on the stack and prevents +variables from reassigned if there may be pointers to their interior) + +Finally, in some cases, both dangers can arise. For example, something +like the following: + + let mut x = ~some(5); + match x { + ~some(ref y) => { ... } + ~none => { ... } + } + +In this case, if `x` to be reassigned or `*x` were to be mutated, then +the pointer `y` would be invalided. (This case is also prevented by +borrowck tracking data which is owned by the current stack frame) + +# Summary of the safety check + +In order to enforce mutability, the borrow check has a few tricks up +its sleeve: + +- When data is owned by the current stack frame, we can identify every + possible assignment to a local variable and simply prevent + potentially dangerous assignments directly. + +- If data is owned by a shared box, we can root the box to increase + its lifetime. + +- If data is found within a borrowed pointer, we can assume that the + data will remain live for the entirety of the borrowed pointer. + +- We can rely on the fact that pure actions (such as calling pure + functions) do not mutate data which is not owned by the current + stack frame. + +# Possible future directions + +There are numerous ways that the `borrowck` could be strengthened, but +these are the two most likely: + +- flow-sensitivity: we do not currently consider flow at all but only + block-scoping. This means that innocent code like the following is + rejected: + + let mut x: int; + ... + x = 5; + let y: &int = &x; // immutable ptr created + ... + + The reason is that the scope of the pointer `y` is the entire + enclosing block, and the assignment `x = 5` occurs within that + block. The analysis is not smart enough to see that `x = 5` always + happens before the immutable pointer is created. This is relatively + easy to fix and will surely be fixed at some point. + +- finer-grained purity checks: currently, our fallback for + guaranteeing random references into mutable, aliasable memory is to + require *total purity*. This is rather strong. We could use local + type-based alias analysis to distinguish writes that could not + possibly invalid the references which must be guaranteed. This + would only work within the function boundaries; function calls would + still require total purity. This seems less likely to be + implemented in the short term as it would make the code + significantly more complex; there is currently no code to analyze + the types and determine the possible impacts of a write. + +# How the code works + +The borrow check code is divided into several major modules, each of +which is documented in its own file. + +The `gather_loans` and `check_loans` are the two major passes of the +analysis. The `gather_loans` pass runs over the IR once to determine +what memory must remain valid and for how long. Its name is a bit of +a misnomer; it does in fact gather up the set of loans which are +granted, but it also determines when @T pointers must be rooted and +for which scopes purity must be required. + +The `check_loans` pass walks the IR and examines the loans and purity +requirements computed in `gather_loans`. It checks to ensure that (a) +the conditions of all loans are honored; (b) no contradictory loans +were granted (for example, loaning out the same memory as mutable and +immutable simultaneously); and (c) any purity requirements are +honored. + +The remaining modules are helper modules used by `gather_loans` and +`check_loans`: + +- `categorization` has the job of analyzing an expression to determine + what kind of memory is used in evaluating it (for example, where + dereferences occur and what kind of pointer is dereferenced; whether + the memory is mutable; etc) +- `loan` determines when data uniquely tied to the stack frame can be + loaned out. +- `preserve` determines what actions (if any) must be taken to preserve + aliasable data. This is the code which decides when to root + an @T pointer or to require purity. + +# Maps that are created + +Borrowck results in two maps. + +- `root_map`: identifies those expressions or patterns whose result + needs to be rooted. Conceptually the root_map maps from an + expression or pattern node to a `node_id` identifying the scope for + which the expression must be rooted (this `node_id` should identify + a block or call). The actual key to the map is not an expression id, + however, but a `root_map_key`, which combines an expression id with a + deref count and is used to cope with auto-deref. + +- `mutbl_map`: identifies those local variables which are modified or + moved. This is used by trans to guarantee that such variables are + given a memory location and not used as immediates. + */ + #[legacy_exports]; + +use syntax::ast; +use syntax::ast::{mutability, m_mutbl, m_imm, m_const}; +use syntax::visit; +use syntax::ast_util; +use syntax::ast_map; +use syntax::codemap::span; +use util::ppaux::{ty_to_str, region_to_str, explain_region, + expr_repr, note_and_explain_region}; +use std::map::{HashMap, Set}; +use std::list; +use std::list::{List, Cons, Nil}; +use result::{Result, Ok, Err}; +use syntax::print::pprust; +use util::common::indenter; +use ty::to_str; +use dvec::DVec; +use mem_categorization::*; + #[legacy_exports] mod check_loans; #[legacy_exports] @@ -7,3 +242,388 @@ mod gather_loans; mod loan; #[legacy_exports] mod preserve; + +export check_crate, root_map, mutbl_map; + +fn check_crate(tcx: ty::ctxt, + method_map: typeck::method_map, + last_use_map: liveness::last_use_map, + crate: @ast::crate) -> (root_map, mutbl_map) { + + let bccx = borrowck_ctxt_(@{tcx: tcx, + method_map: method_map, + last_use_map: last_use_map, + root_map: root_map(), + mutbl_map: HashMap(), + mut loaned_paths_same: 0, + mut loaned_paths_imm: 0, + mut stable_paths: 0, + mut req_pure_paths: 0, + mut guaranteed_paths: 0}); + + let req_maps = gather_loans::gather_loans(bccx, crate); + check_loans::check_loans(bccx, req_maps, crate); + + if tcx.sess.borrowck_stats() { + io::println(~"--- borrowck stats ---"); + io::println(fmt!("paths requiring guarantees: %u", + bccx.guaranteed_paths)); + io::println(fmt!("paths requiring loans : %s", + make_stat(bccx, bccx.loaned_paths_same))); + io::println(fmt!("paths requiring imm loans : %s", + make_stat(bccx, bccx.loaned_paths_imm))); + io::println(fmt!("stable paths : %s", + make_stat(bccx, bccx.stable_paths))); + io::println(fmt!("paths requiring purity : %s", + make_stat(bccx, bccx.req_pure_paths))); + } + + return (bccx.root_map, bccx.mutbl_map); + + fn make_stat(bccx: borrowck_ctxt, stat: uint) -> ~str { + let stat_f = stat as float; + let total = bccx.guaranteed_paths as float; + fmt!("%u (%.0f%%)", stat , stat_f * 100f / total) + } +} + +// ---------------------------------------------------------------------- +// Type definitions + +type borrowck_ctxt_ = {tcx: ty::ctxt, + method_map: typeck::method_map, + last_use_map: liveness::last_use_map, + root_map: root_map, + mutbl_map: mutbl_map, + + // Statistics: + mut loaned_paths_same: uint, + mut loaned_paths_imm: uint, + mut stable_paths: uint, + mut req_pure_paths: uint, + mut guaranteed_paths: uint}; + +enum borrowck_ctxt { + borrowck_ctxt_(@borrowck_ctxt_) +} + +// a map mapping id's of expressions of gc'd type (@T, @[], etc) where +// the box needs to be kept live to the id of the scope for which they +// must stay live. +type root_map = HashMap<root_map_key, ast::node_id>; + +// the keys to the root map combine the `id` of the expression with +// the number of types that it is autodereferenced. So, for example, +// if you have an expression `x.f` and x has type ~@T, we could add an +// entry {id:x, derefs:0} to refer to `x` itself, `{id:x, derefs:1}` +// to refer to the deref of the unique pointer, and so on. +type root_map_key = {id: ast::node_id, derefs: uint}; + +// set of ids of local vars / formal arguments that are modified / moved. +// this is used in trans for optimization purposes. +type mutbl_map = std::map::HashMap<ast::node_id, ()>; + +// Errors that can occur"] +enum bckerr_code { + err_mut_uniq, + err_mut_variant, + err_root_not_permitted, + err_mutbl(ast::mutability), + err_out_of_root_scope(ty::Region, ty::Region), // superscope, subscope + err_out_of_scope(ty::Region, ty::Region) // superscope, subscope +} + +impl bckerr_code : cmp::Eq { + pure fn eq(&self, other: &bckerr_code) -> bool { + match (*self) { + err_mut_uniq => { + match (*other) { + err_mut_uniq => true, + _ => false + } + } + err_mut_variant => { + match (*other) { + err_mut_variant => true, + _ => false + } + } + err_root_not_permitted => { + match (*other) { + err_root_not_permitted => true, + _ => false + } + } + err_mutbl(e0a) => { + match (*other) { + err_mutbl(e0b) => e0a == e0b, + _ => false + } + } + err_out_of_root_scope(e0a, e1a) => { + match (*other) { + err_out_of_root_scope(e0b, e1b) => + e0a == e0b && e1a == e1b, + _ => false + } + } + err_out_of_scope(e0a, e1a) => { + match (*other) { + err_out_of_scope(e0b, e1b) => e0a == e0b && e1a == e1b, + _ => false + } + } + } + } + pure fn ne(&self, other: &bckerr_code) -> bool { !(*self).eq(other) } +} + +// Combination of an error code and the categorization of the expression +// that caused it +type bckerr = {cmt: cmt, code: bckerr_code}; + +impl bckerr : cmp::Eq { + pure fn eq(&self, other: &bckerr) -> bool { + (*self).cmt == (*other).cmt && (*self).code == (*other).code + } + pure fn ne(&self, other: &bckerr) -> bool { !(*self).eq(other) } +} + +// shorthand for something that fails with `bckerr` or succeeds with `T` +type bckres<T> = Result<T, bckerr>; + +/// a complete record of a loan that was granted +struct Loan {lp: @loan_path, cmt: cmt, mutbl: ast::mutability} + +/// maps computed by `gather_loans` that are then used by `check_loans` +/// +/// - `req_loan_map`: map from each block/expr to the required loans needed +/// for the duration of that block/expr +/// - `pure_map`: map from block/expr that must be pure to the error message +/// that should be reported if they are not pure +type req_maps = { + req_loan_map: HashMap<ast::node_id, @DVec<Loan>>, + pure_map: HashMap<ast::node_id, bckerr> +}; + +fn save_and_restore<T:Copy,U>(save_and_restore_t: &mut T, f: fn() -> U) -> U { + let old_save_and_restore_t = *save_and_restore_t; + let u = f(); + *save_and_restore_t = old_save_and_restore_t; + move u +} + +/// Creates and returns a new root_map + +impl root_map_key : cmp::Eq { + pure fn eq(&self, other: &root_map_key) -> bool { + (*self).id == (*other).id && (*self).derefs == (*other).derefs + } + pure fn ne(&self, other: &root_map_key) -> bool { + ! ((*self) == (*other)) + } +} + +#[cfg(stage0)] +impl root_map_key : to_bytes::IterBytes { + pure fn iter_bytes(+lsb0: bool, f: to_bytes::Cb) { + to_bytes::iter_bytes_2(&self.id, &self.derefs, lsb0, f); + } +} +#[cfg(stage1)] +#[cfg(stage2)] +impl root_map_key : to_bytes::IterBytes { + pure fn iter_bytes(&self, +lsb0: bool, f: to_bytes::Cb) { + to_bytes::iter_bytes_2(&self.id, &self.derefs, lsb0, f); + } +} + +fn root_map() -> root_map { + return HashMap(); + + pure fn root_map_key_eq(k1: &root_map_key, k2: &root_map_key) -> bool { + k1.id == k2.id && k1.derefs == k2.derefs + } + + pure fn root_map_key_hash(k: &root_map_key) -> uint { + (k.id << 4) as uint | k.derefs + } +} + +// ___________________________________________________________________________ +// Misc + +impl borrowck_ctxt { + fn is_subregion_of(r_sub: ty::Region, r_sup: ty::Region) -> bool { + region::is_subregion_of(self.tcx.region_map, r_sub, r_sup) + } + + fn cat_expr(expr: @ast::expr) -> cmt { + cat_expr(self.tcx, self.method_map, expr) + } + + fn cat_expr_unadjusted(expr: @ast::expr) -> cmt { + cat_expr_unadjusted(self.tcx, self.method_map, expr) + } + + fn cat_expr_autoderefd(expr: @ast::expr, + adj: @ty::AutoAdjustment) + -> cmt { + cat_expr_autoderefd(self.tcx, self.method_map, expr, adj) + } + + fn cat_def(id: ast::node_id, + span: span, + ty: ty::t, + def: ast::def) -> cmt { + cat_def(self.tcx, self.method_map, id, span, ty, def) + } + + fn cat_variant<N: ast_node>(arg: N, + enum_did: ast::def_id, + cmt: cmt) -> cmt { + cat_variant(self.tcx, self.method_map, arg, enum_did, cmt) + } + + fn cat_discr(cmt: cmt, alt_id: ast::node_id) -> cmt { + return @{cat:cat_discr(cmt, alt_id),.. *cmt}; + } + + fn cat_pattern(cmt: cmt, pat: @ast::pat, op: fn(cmt, @ast::pat)) { + let mc = &mem_categorization_ctxt {tcx: self.tcx, + method_map: self.method_map}; + mc.cat_pattern(cmt, pat, op); + } + + fn report_if_err(bres: bckres<()>) { + match bres { + Ok(()) => (), + Err(e) => self.report(e) + } + } + + fn report(err: bckerr) { + self.span_err( + err.cmt.span, + fmt!("illegal borrow: %s", + self.bckerr_to_str(err))); + self.note_and_explain_bckerr(err); + } + + fn span_err(s: span, m: ~str) { + self.tcx.sess.span_err(s, m); + } + + fn span_note(s: span, m: ~str) { + self.tcx.sess.span_note(s, m); + } + + fn add_to_mutbl_map(cmt: cmt) { + match cmt.cat { + cat_local(id) | cat_arg(id) => { + self.mutbl_map.insert(id, ()); + } + cat_stack_upvar(cmt) => { + self.add_to_mutbl_map(cmt); + } + _ => () + } + } + + fn bckerr_to_str(err: bckerr) -> ~str { + match err.code { + err_mutbl(req) => { + fmt!("creating %s alias to %s", + self.mut_to_str(req), + self.cmt_to_str(err.cmt)) + } + err_mut_uniq => { + ~"unique value in aliasable, mutable location" + } + err_mut_variant => { + ~"enum variant in aliasable, mutable location" + } + err_root_not_permitted => { + // note: I don't expect users to ever see this error + // message, reasons are discussed in attempt_root() in + // preserve.rs. + ~"rooting is not permitted" + } + err_out_of_root_scope(*) => { + ~"cannot root managed value long enough" + } + err_out_of_scope(*) => { + ~"borrowed value does not live long enough" + } + } + } + + fn note_and_explain_bckerr(err: bckerr) { + let code = err.code; + match code { + err_mutbl(*) | err_mut_uniq | err_mut_variant | + err_root_not_permitted => {} + + err_out_of_root_scope(super_scope, sub_scope) => { + note_and_explain_region( + self.tcx, + ~"managed value would have to be rooted for ", + sub_scope, + ~"..."); + note_and_explain_region( + self.tcx, + ~"...but can only be rooted for ", + super_scope, + ~""); + } + + err_out_of_scope(super_scope, sub_scope) => { + note_and_explain_region( + self.tcx, + ~"borrowed pointer must be valid for ", + sub_scope, + ~"..."); + note_and_explain_region( + self.tcx, + ~"...but borrowed value is only valid for ", + super_scope, + ~""); + } + } + } + + + fn cmt_to_str(cmt: cmt) -> ~str { + let mc = &mem_categorization_ctxt {tcx: self.tcx, + method_map: self.method_map}; + mc.cmt_to_str(cmt) + } + + fn cmt_to_repr(cmt: cmt) -> ~str { + let mc = &mem_categorization_ctxt {tcx: self.tcx, + method_map: self.method_map}; + mc.cmt_to_repr(cmt) + } + + fn mut_to_str(mutbl: ast::mutability) -> ~str { + let mc = &mem_categorization_ctxt {tcx: self.tcx, + method_map: self.method_map}; + mc.mut_to_str(mutbl) + } + + fn loan_to_repr(loan: &Loan) -> ~str { + fmt!("Loan(lp=%?, cmt=%s, mutbl=%?)", + loan.lp, self.cmt_to_repr(loan.cmt), loan.mutbl) + } +} + +// The inherent mutability of a component is its default mutability +// assuming it is embedded in an immutable context. In general, the +// mutability can be "overridden" if the component is embedded in a +// mutable structure. +fn inherent_mutability(ck: comp_kind) -> mutability { + match ck { + comp_tuple | comp_anon_field | comp_variant(_) => m_imm, + comp_field(_, m) | comp_index(_, m) => m + } +} diff --git a/src/librustc/middle/typeck.rs b/src/librustc/middle/typeck.rs deleted file mode 100644 index 421c68fa9a2..00000000000 --- a/src/librustc/middle/typeck.rs +++ /dev/null @@ -1,376 +0,0 @@ -/* - -typeck.rs, an introduction - -The type checker is responsible for: - -1. Determining the type of each expression -2. Resolving methods and traits -3. Guaranteeing that most type rules are met ("most?", you say, "why most?" - Well, dear reader, read on) - -The main entry point is `check_crate()`. Type checking operates in two major -phases: collect and check. The collect phase passes over all items and -determines their type, without examining their "innards". The check phase -then checks function bodies and so forth. - -Within the check phase, we check each function body one at a time (bodies of -function expressions are checked as part of the containing function). -Inference is used to supply types wherever they are unknown. The actual -checking of a function itself has several phases (check, regionck, writeback), -as discussed in the documentation for the `check` module. - -The type checker is defined into various submodules which are documented -independently: - -- astconv: converts the AST representation of types - into the `ty` representation - -- collect: computes the types of each top-level item and enters them into - the `cx.tcache` table for later use - -- check: walks over function bodies and type checks them, inferring types for - local variables, type parameters, etc as necessary. - -- infer: finds the types to use for each type variable such that - all subtyping and assignment constraints are met. In essence, the check - module specifies the constraints, and the infer module solves them. - -*/ - -use result::Result; -use syntax::{ast, ast_util, ast_map}; -use ast::spanned; -use ast::{required, provided}; -use syntax::ast_map::node_id_to_str; -use syntax::ast_util::{local_def, respan, split_trait_methods}; -use syntax::visit; -use metadata::csearch; -use util::common::{block_query, loop_query}; -use syntax::codemap::span; -use pat_util::{pat_id_map, PatIdMap}; -use middle::ty; -use middle::ty::{arg, field, node_type_table, mk_nil, ty_param_bounds_and_ty}; -use middle::ty::{ty_param_substs_and_ty, vstore_uniq}; -use std::smallintmap; -use std::map; -use std::map::HashMap; -use syntax::print::pprust::*; -use util::ppaux::{ty_to_str, tys_to_str, region_to_str, - bound_region_to_str, vstore_to_str, expr_repr}; -use util::common::{indent, indenter}; -use std::list; -use list::{List, Nil, Cons}; -use dvec::DVec; - -export check; -export check_crate; -export infer; -export method_map; -export method_origin; -export method_map_entry; -export vtable_map; -export vtable_res; -export vtable_origin; -export method_static, method_param, method_trait, method_self; -export vtable_static, vtable_param, vtable_trait; -export provided_methods_map; - -#[auto_serialize] -#[auto_deserialize] -enum method_origin { - // fully statically resolved method - method_static(ast::def_id), - - // method invoked on a type parameter with a bounded trait - method_param(method_param), - - // method invoked on a trait instance - method_trait(ast::def_id, uint, ty::vstore), - - // method invoked on "self" inside a default method - method_self(ast::def_id, uint), -} - -// details for a method invoked with a receiver whose type is a type parameter -// with a bounded trait. -#[auto_serialize] -#[auto_deserialize] -type method_param = { - // the trait containing the method to be invoked - trait_id: ast::def_id, - - // index of the method to be invoked amongst the trait's methods - method_num: uint, - - // index of the type parameter (from those that are in scope) that is - // the type of the receiver - param_num: uint, - - // index of the bound for this type parameter which specifies the trait - bound_num: uint -}; - -type method_map_entry = { - // the type and mode of the self parameter, which is not reflected - // in the fn type (FIXME #3446) - self_arg: ty::arg, - - // method details being invoked - origin: method_origin -}; - -// maps from an expression id that corresponds to a method call to the details -// of the method to be invoked -type method_map = HashMap<ast::node_id, method_map_entry>; - -// Resolutions for bounds of all parameters, left to right, for a given path. -type vtable_res = @~[vtable_origin]; - -enum vtable_origin { - /* - Statically known vtable. def_id gives the class or impl item - from whence comes the vtable, and tys are the type substs. - vtable_res is the vtable itself - */ - vtable_static(ast::def_id, ~[ty::t], vtable_res), - /* - Dynamic vtable, comes from a parameter that has a bound on it: - fn foo<T: quux, baz, bar>(a: T) -- a's vtable would have a - vtable_param origin - - The first uint is the param number (identifying T in the example), - and the second is the bound number (identifying baz) - */ - vtable_param(uint, uint), - /* - Dynamic vtable, comes from something known to have a trait - type. def_id refers to the trait item, tys are the substs - */ - vtable_trait(ast::def_id, ~[ty::t]), -} - -impl vtable_origin { - fn to_str(tcx: ty::ctxt) -> ~str { - match self { - vtable_static(def_id, ref tys, ref vtable_res) => { - fmt!("vtable_static(%?:%s, %?, %?)", - def_id, ty::item_path_str(tcx, def_id), - tys, - vtable_res.map(|o| o.to_str(tcx))) - } - - vtable_param(x, y) => { - fmt!("vtable_param(%?, %?)", x, y) - } - - vtable_trait(def_id, ref tys) => { - fmt!("vtable_trait(%?:%s, %?)", - def_id, ty::item_path_str(tcx, def_id), - tys.map(|t| ty_to_str(tcx, *t))) - } - } - } -} - -type vtable_map = HashMap<ast::node_id, vtable_res>; - -type crate_ctxt_ = {// A mapping from method call sites to traits that have - // that method. - trait_map: resolve::TraitMap, - method_map: method_map, - vtable_map: vtable_map, - coherence_info: @coherence::CoherenceInfo, - tcx: ty::ctxt}; - -enum crate_ctxt { - crate_ctxt_(crate_ctxt_) -} - -// Functions that write types into the node type table -fn write_ty_to_tcx(tcx: ty::ctxt, node_id: ast::node_id, ty: ty::t) { - debug!("write_ty_to_tcx(%d, %s)", node_id, ty_to_str(tcx, ty)); - smallintmap::insert(*tcx.node_types, node_id as uint, ty); -} -fn write_substs_to_tcx(tcx: ty::ctxt, - node_id: ast::node_id, - +substs: ~[ty::t]) { - if substs.len() > 0u { - debug!("write_substs_to_tcx(%d, %?)", node_id, - substs.map(|t| ty_to_str(tcx, *t))); - tcx.node_type_substs.insert(node_id, substs); - } -} - -fn lookup_def_tcx(tcx: ty::ctxt, sp: span, id: ast::node_id) -> ast::def { - match tcx.def_map.find(id) { - Some(x) => x, - _ => { - tcx.sess.span_fatal(sp, ~"internal error looking up a definition") - } - } -} - -fn lookup_def_ccx(ccx: @crate_ctxt, sp: span, id: ast::node_id) -> ast::def { - lookup_def_tcx(ccx.tcx, sp, id) -} - -fn no_params(t: ty::t) -> ty::ty_param_bounds_and_ty { - {bounds: @~[], region_param: None, ty: t} -} - -fn require_same_types( - tcx: ty::ctxt, - maybe_infcx: Option<infer::infer_ctxt>, - t1_is_expected: bool, - span: span, - t1: ty::t, - t2: ty::t, - msg: fn() -> ~str) -> bool { - - let l_tcx, l_infcx; - match maybe_infcx { - None => { - l_tcx = tcx; - l_infcx = infer::new_infer_ctxt(tcx); - } - Some(i) => { - l_tcx = i.tcx; - l_infcx = i; - } - } - - match infer::mk_eqty(l_infcx, t1_is_expected, span, t1, t2) { - result::Ok(()) => true, - result::Err(ref terr) => { - l_tcx.sess.span_err(span, msg() + ~": " + - ty::type_err_to_str(l_tcx, terr)); - ty::note_and_explain_type_err(l_tcx, terr); - false - } - } -} - -// a list of mapping from in-scope-region-names ("isr") to the -// corresponding ty::Region -type isr_alist = @List<(ty::bound_region, ty::Region)>; - -trait get_and_find_region { - fn get(br: ty::bound_region) -> ty::Region; - fn find(br: ty::bound_region) -> Option<ty::Region>; -} - -impl isr_alist: get_and_find_region { - fn get(br: ty::bound_region) -> ty::Region { - self.find(br).get() - } - - fn find(br: ty::bound_region) -> Option<ty::Region> { - for list::each(self) |isr| { - let (isr_br, isr_r) = *isr; - if isr_br == br { return Some(isr_r); } - } - return None; - } -} - -fn arg_is_argv_ty(tcx: ty::ctxt, a: ty::arg) -> bool { - match ty::resolved_mode(tcx, a.mode) { - ast::by_val => { /*ok*/ } - _ => { - return false; - } - } - - match ty::get(a.ty).sty { - ty::ty_evec(mt, vstore_uniq) => { - if mt.mutbl != ast::m_imm { return false; } - match ty::get(mt.ty).sty { - ty::ty_estr(vstore_uniq) => return true, - _ => return false - } - } - _ => return false - } -} - -fn check_main_fn_ty(ccx: @crate_ctxt, - main_id: ast::node_id, - main_span: span) { - - let tcx = ccx.tcx; - let main_t = ty::node_id_to_type(tcx, main_id); - match ty::get(main_t).sty { - ty::ty_fn(fn_ty) => { - match tcx.items.find(main_id) { - Some(ast_map::node_item(it,_)) => { - match it.node { - ast::item_fn(_,_,ps,_) if vec::is_not_empty(ps) => { - tcx.sess.span_err( - main_span, - ~"main function is not allowed \ - to have type parameters"); - return; - } - _ => () - } - } - _ => () - } - let mut ok = ty::type_is_nil(fn_ty.sig.output); - let num_args = vec::len(fn_ty.sig.inputs); - ok &= num_args == 0u; - if !ok { - tcx.sess.span_err( - main_span, - fmt!("Wrong type in main function: found `%s`, \ - expected `fn() -> ()`", - ty_to_str(tcx, main_t))); - } - } - _ => { - tcx.sess.span_bug(main_span, - ~"main has a non-function type: found `" + - ty_to_str(tcx, main_t) + ~"`"); - } - } -} - -fn check_for_main_fn(ccx: @crate_ctxt) { - let tcx = ccx.tcx; - if !tcx.sess.building_library { - match copy tcx.sess.main_fn { - Some((id, sp)) => check_main_fn_ty(ccx, id, sp), - None => tcx.sess.err(~"main function not found") - } - } -} - -fn check_crate(tcx: ty::ctxt, - trait_map: resolve::TraitMap, - crate: @ast::crate) - -> (method_map, vtable_map) { - - let ccx = @crate_ctxt_({trait_map: trait_map, - method_map: std::map::HashMap(), - vtable_map: std::map::HashMap(), - coherence_info: @coherence::CoherenceInfo(), - tcx: tcx}); - collect::collect_item_types(ccx, crate); - coherence::check_coherence(ccx, crate); - deriving::check_deriving(ccx, crate); - - check::check_item_types(ccx, crate); - check_for_main_fn(ccx); - tcx.sess.abort_if_errors(); - (ccx.method_map, ccx.vtable_map) -} -// -// Local Variables: -// mode: rust -// fill-column: 78; -// indent-tabs-mode: nil -// c-basic-offset: 4 -// buffer-file-coding-system: utf-8-unix -// End: -// diff --git a/src/librustc/middle/typeck/mod.rs b/src/librustc/middle/typeck/mod.rs index 11077081d91..6241cac2f5d 100644 --- a/src/librustc/middle/typeck/mod.rs +++ b/src/librustc/middle/typeck/mod.rs @@ -1,5 +1,83 @@ +/* + +typeck.rs, an introduction + +The type checker is responsible for: + +1. Determining the type of each expression +2. Resolving methods and traits +3. Guaranteeing that most type rules are met ("most?", you say, "why most?" + Well, dear reader, read on) + +The main entry point is `check_crate()`. Type checking operates in two major +phases: collect and check. The collect phase passes over all items and +determines their type, without examining their "innards". The check phase +then checks function bodies and so forth. + +Within the check phase, we check each function body one at a time (bodies of +function expressions are checked as part of the containing function). +Inference is used to supply types wherever they are unknown. The actual +checking of a function itself has several phases (check, regionck, writeback), +as discussed in the documentation for the `check` module. + +The type checker is defined into various submodules which are documented +independently: + +- astconv: converts the AST representation of types + into the `ty` representation + +- collect: computes the types of each top-level item and enters them into + the `cx.tcache` table for later use + +- check: walks over function bodies and type checks them, inferring types for + local variables, type parameters, etc as necessary. + +- infer: finds the types to use for each type variable such that + all subtyping and assignment constraints are met. In essence, the check + module specifies the constraints, and the infer module solves them. + +*/ + #[legacy_exports]; +use result::Result; +use syntax::{ast, ast_util, ast_map}; +use ast::spanned; +use ast::{required, provided}; +use syntax::ast_map::node_id_to_str; +use syntax::ast_util::{local_def, respan, split_trait_methods}; +use syntax::visit; +use metadata::csearch; +use util::common::{block_query, loop_query}; +use syntax::codemap::span; +use pat_util::{pat_id_map, PatIdMap}; +use middle::ty; +use middle::ty::{arg, field, node_type_table, mk_nil, ty_param_bounds_and_ty}; +use middle::ty::{ty_param_substs_and_ty, vstore_uniq}; +use std::smallintmap; +use std::map; +use std::map::HashMap; +use syntax::print::pprust::*; +use util::ppaux::{ty_to_str, tys_to_str, region_to_str, + bound_region_to_str, vstore_to_str, expr_repr}; +use util::common::{indent, indenter}; +use std::list; +use list::{List, Nil, Cons}; +use dvec::DVec; + +export check; +export check_crate; +export infer; +export method_map; +export method_origin; +export method_map_entry; +export vtable_map; +export vtable_res; +export vtable_origin; +export method_static, method_param, method_trait, method_self; +export vtable_static, vtable_param, vtable_trait; +export provided_methods_map; + #[legacy_exports] #[merge = "check/mod.rs"] pub mod check; @@ -14,3 +92,302 @@ mod collect; #[legacy_exports] mod coherence; mod deriving; + +#[auto_serialize] +#[auto_deserialize] +enum method_origin { + // fully statically resolved method + method_static(ast::def_id), + + // method invoked on a type parameter with a bounded trait + method_param(method_param), + + // method invoked on a trait instance + method_trait(ast::def_id, uint, ty::vstore), + + // method invoked on "self" inside a default method + method_self(ast::def_id, uint), +} + +// details for a method invoked with a receiver whose type is a type parameter +// with a bounded trait. +#[auto_serialize] +#[auto_deserialize] +type method_param = { + // the trait containing the method to be invoked + trait_id: ast::def_id, + + // index of the method to be invoked amongst the trait's methods + method_num: uint, + + // index of the type parameter (from those that are in scope) that is + // the type of the receiver + param_num: uint, + + // index of the bound for this type parameter which specifies the trait + bound_num: uint +}; + +type method_map_entry = { + // the type and mode of the self parameter, which is not reflected + // in the fn type (FIXME #3446) + self_arg: ty::arg, + + // method details being invoked + origin: method_origin +}; + +// maps from an expression id that corresponds to a method call to the details +// of the method to be invoked +type method_map = HashMap<ast::node_id, method_map_entry>; + +// Resolutions for bounds of all parameters, left to right, for a given path. +type vtable_res = @~[vtable_origin]; + +enum vtable_origin { + /* + Statically known vtable. def_id gives the class or impl item + from whence comes the vtable, and tys are the type substs. + vtable_res is the vtable itself + */ + vtable_static(ast::def_id, ~[ty::t], vtable_res), + /* + Dynamic vtable, comes from a parameter that has a bound on it: + fn foo<T: quux, baz, bar>(a: T) -- a's vtable would have a + vtable_param origin + + The first uint is the param number (identifying T in the example), + and the second is the bound number (identifying baz) + */ + vtable_param(uint, uint), + /* + Dynamic vtable, comes from something known to have a trait + type. def_id refers to the trait item, tys are the substs + */ + vtable_trait(ast::def_id, ~[ty::t]), +} + +impl vtable_origin { + fn to_str(tcx: ty::ctxt) -> ~str { + match self { + vtable_static(def_id, ref tys, ref vtable_res) => { + fmt!("vtable_static(%?:%s, %?, %?)", + def_id, ty::item_path_str(tcx, def_id), + tys, + vtable_res.map(|o| o.to_str(tcx))) + } + + vtable_param(x, y) => { + fmt!("vtable_param(%?, %?)", x, y) + } + + vtable_trait(def_id, ref tys) => { + fmt!("vtable_trait(%?:%s, %?)", + def_id, ty::item_path_str(tcx, def_id), + tys.map(|t| ty_to_str(tcx, *t))) + } + } + } +} + +type vtable_map = HashMap<ast::node_id, vtable_res>; + +type crate_ctxt_ = {// A mapping from method call sites to traits that have + // that method. + trait_map: resolve::TraitMap, + method_map: method_map, + vtable_map: vtable_map, + coherence_info: @coherence::CoherenceInfo, + tcx: ty::ctxt}; + +enum crate_ctxt { + crate_ctxt_(crate_ctxt_) +} + +// Functions that write types into the node type table +fn write_ty_to_tcx(tcx: ty::ctxt, node_id: ast::node_id, ty: ty::t) { + debug!("write_ty_to_tcx(%d, %s)", node_id, ty_to_str(tcx, ty)); + smallintmap::insert(*tcx.node_types, node_id as uint, ty); +} +fn write_substs_to_tcx(tcx: ty::ctxt, + node_id: ast::node_id, + +substs: ~[ty::t]) { + if substs.len() > 0u { + debug!("write_substs_to_tcx(%d, %?)", node_id, + substs.map(|t| ty_to_str(tcx, *t))); + tcx.node_type_substs.insert(node_id, substs); + } +} + +fn lookup_def_tcx(tcx: ty::ctxt, sp: span, id: ast::node_id) -> ast::def { + match tcx.def_map.find(id) { + Some(x) => x, + _ => { + tcx.sess.span_fatal(sp, ~"internal error looking up a definition") + } + } +} + +fn lookup_def_ccx(ccx: @crate_ctxt, sp: span, id: ast::node_id) -> ast::def { + lookup_def_tcx(ccx.tcx, sp, id) +} + +fn no_params(t: ty::t) -> ty::ty_param_bounds_and_ty { + {bounds: @~[], region_param: None, ty: t} +} + +fn require_same_types( + tcx: ty::ctxt, + maybe_infcx: Option<infer::infer_ctxt>, + t1_is_expected: bool, + span: span, + t1: ty::t, + t2: ty::t, + msg: fn() -> ~str) -> bool { + + let l_tcx, l_infcx; + match maybe_infcx { + None => { + l_tcx = tcx; + l_infcx = infer::new_infer_ctxt(tcx); + } + Some(i) => { + l_tcx = i.tcx; + l_infcx = i; + } + } + + match infer::mk_eqty(l_infcx, t1_is_expected, span, t1, t2) { + result::Ok(()) => true, + result::Err(ref terr) => { + l_tcx.sess.span_err(span, msg() + ~": " + + ty::type_err_to_str(l_tcx, terr)); + ty::note_and_explain_type_err(l_tcx, terr); + false + } + } +} + +// a list of mapping from in-scope-region-names ("isr") to the +// corresponding ty::Region +type isr_alist = @List<(ty::bound_region, ty::Region)>; + +trait get_and_find_region { + fn get(br: ty::bound_region) -> ty::Region; + fn find(br: ty::bound_region) -> Option<ty::Region>; +} + +impl isr_alist: get_and_find_region { + fn get(br: ty::bound_region) -> ty::Region { + self.find(br).get() + } + + fn find(br: ty::bound_region) -> Option<ty::Region> { + for list::each(self) |isr| { + let (isr_br, isr_r) = *isr; + if isr_br == br { return Some(isr_r); } + } + return None; + } +} + +fn arg_is_argv_ty(tcx: ty::ctxt, a: ty::arg) -> bool { + match ty::resolved_mode(tcx, a.mode) { + ast::by_val => { /*ok*/ } + _ => { + return false; + } + } + + match ty::get(a.ty).sty { + ty::ty_evec(mt, vstore_uniq) => { + if mt.mutbl != ast::m_imm { return false; } + match ty::get(mt.ty).sty { + ty::ty_estr(vstore_uniq) => return true, + _ => return false + } + } + _ => return false + } +} + +fn check_main_fn_ty(ccx: @crate_ctxt, + main_id: ast::node_id, + main_span: span) { + + let tcx = ccx.tcx; + let main_t = ty::node_id_to_type(tcx, main_id); + match ty::get(main_t).sty { + ty::ty_fn(fn_ty) => { + match tcx.items.find(main_id) { + Some(ast_map::node_item(it,_)) => { + match it.node { + ast::item_fn(_,_,ps,_) if vec::is_not_empty(ps) => { + tcx.sess.span_err( + main_span, + ~"main function is not allowed \ + to have type parameters"); + return; + } + _ => () + } + } + _ => () + } + let mut ok = ty::type_is_nil(fn_ty.sig.output); + let num_args = vec::len(fn_ty.sig.inputs); + ok &= num_args == 0u; + if !ok { + tcx.sess.span_err( + main_span, + fmt!("Wrong type in main function: found `%s`, \ + expected `fn() -> ()`", + ty_to_str(tcx, main_t))); + } + } + _ => { + tcx.sess.span_bug(main_span, + ~"main has a non-function type: found `" + + ty_to_str(tcx, main_t) + ~"`"); + } + } +} + +fn check_for_main_fn(ccx: @crate_ctxt) { + let tcx = ccx.tcx; + if !tcx.sess.building_library { + match copy tcx.sess.main_fn { + Some((id, sp)) => check_main_fn_ty(ccx, id, sp), + None => tcx.sess.err(~"main function not found") + } + } +} + +fn check_crate(tcx: ty::ctxt, + trait_map: resolve::TraitMap, + crate: @ast::crate) + -> (method_map, vtable_map) { + + let ccx = @crate_ctxt_({trait_map: trait_map, + method_map: std::map::HashMap(), + vtable_map: std::map::HashMap(), + coherence_info: @coherence::CoherenceInfo(), + tcx: tcx}); + collect::collect_item_types(ccx, crate); + coherence::check_coherence(ccx, crate); + deriving::check_deriving(ccx, crate); + + check::check_item_types(ccx, crate); + check_for_main_fn(ccx); + tcx.sess.abort_if_errors(); + (ccx.method_map, ccx.vtable_map) +} +// +// Local Variables: +// mode: rust +// fill-column: 78; +// indent-tabs-mode: nil +// c-basic-offset: 4 +// buffer-file-coding-system: utf-8-unix +// End: +// diff --git a/src/librustc/rustc.rc b/src/librustc/rustc.rc index 80592fea500..14af4bc443e 100644 --- a/src/librustc/rustc.rc +++ b/src/librustc/rustc.rc @@ -122,8 +122,7 @@ mod middle { #[legacy_exports] #[path = "middle/resolve.rs"] mod resolve; - #[path = "middle/typeck.rs"] - #[merge = "middle/typeck/mod.rs"] + #[path = "middle/typeck/mod.rs"] pub mod typeck; #[legacy_exports] #[path = "middle/check_loop.rs"] @@ -137,8 +136,7 @@ mod middle { #[legacy_exports] #[path = "middle/lint.rs"] mod lint; - #[path = "middle/borrowck.rs"] - #[merge = "middle/borrowck/mod.rs"] + #[path = "middle/borrowck/mod.rs"] mod borrowck; #[legacy_exports] #[path = "middle/mem_categorization.rs"] @@ -216,10 +214,10 @@ mod back { mod target_strs; } -#[merge = "metadata/mod.rs"] +#[path = "metadata/mod.rs"] mod metadata; -#[merge = "driver/mod.rs"] +#[path = "driver/mod.rs"] mod driver; mod util { diff --git a/src/libsyntax/ext/pipes.rs b/src/libsyntax/ext/pipes.rs deleted file mode 100644 index b4c49b12d59..00000000000 --- a/src/libsyntax/ext/pipes.rs +++ /dev/null @@ -1,67 +0,0 @@ -/*! Implementation of proto! extension. - -This is frequently called the pipe compiler. It handles code such as... - -~~~ -proto! pingpong ( - ping: send { - ping -> pong - } - pong: recv { - pong -> ping - } -) -~~~ - -There are several components: - - * The parser (libsyntax/ext/pipes/parse_proto.rs) - * Responsible for building an AST from a protocol specification. - - * The checker (libsyntax/ext/pipes/check.rs) - * Basic correctness checking for protocols (i.e. no undefined states, etc.) - - * The analyzer (libsyntax/ext/pipes/liveness.rs) - * Determines whether the protocol is bounded or unbounded. - - * The compiler (libsynatx/ext/pipes/pipec.rs) - * Generates a Rust AST from the protocol AST and the results of analysis. - -There is more documentation in each of the files referenced above. - -FIXME (#3072) - This is still incomplete. - -*/ - -use codemap::span; -use ext::base::ext_ctxt; -use ast::tt_delim; -use parse::lexer::{new_tt_reader, reader}; -use parse::parser::Parser; -use parse::common::parser_common; - -use pipes::parse_proto::proto_parser; - -use pipes::proto::{visit, protocol}; - -fn expand_proto(cx: ext_ctxt, _sp: span, id: ast::ident, - tt: ~[ast::token_tree]) -> base::mac_result -{ - let sess = cx.parse_sess(); - let cfg = cx.cfg(); - let tt_rdr = new_tt_reader(cx.parse_sess().span_diagnostic, - cx.parse_sess().interner, None, tt); - let rdr = tt_rdr as reader; - let rust_parser = Parser(sess, cfg, rdr.dup()); - - let proto = rust_parser.parse_proto(cx.str_of(id)); - - // check for errors - visit(proto, cx); - - // do analysis - liveness::analyze(proto, cx); - - // compile - base::mr_item(proto.compile(cx)) -} diff --git a/src/libsyntax/ext/pipes/mod.rs b/src/libsyntax/ext/pipes/mod.rs index 638ccad0143..b064f39eb3a 100644 --- a/src/libsyntax/ext/pipes/mod.rs +++ b/src/libsyntax/ext/pipes/mod.rs @@ -1,3 +1,49 @@ +/*! Implementation of proto! extension. + +This is frequently called the pipe compiler. It handles code such as... + +~~~ +proto! pingpong ( + ping: send { + ping -> pong + } + pong: recv { + pong -> ping + } +) +~~~ + +There are several components: + + * The parser (libsyntax/ext/pipes/parse_proto.rs) + * Responsible for building an AST from a protocol specification. + + * The checker (libsyntax/ext/pipes/check.rs) + * Basic correctness checking for protocols (i.e. no undefined states, etc.) + + * The analyzer (libsyntax/ext/pipes/liveness.rs) + * Determines whether the protocol is bounded or unbounded. + + * The compiler (libsynatx/ext/pipes/pipec.rs) + * Generates a Rust AST from the protocol AST and the results of analysis. + +There is more documentation in each of the files referenced above. + +FIXME (#3072) - This is still incomplete. + +*/ + +use codemap::span; +use ext::base::ext_ctxt; +use ast::tt_delim; +use parse::lexer::{new_tt_reader, reader}; +use parse::parser::Parser; +use parse::common::parser_common; + +use pipes::parse_proto::proto_parser; + +use pipes::proto::{visit, protocol}; + #[legacy_exports] mod ast_builder; #[legacy_exports] @@ -10,3 +56,27 @@ mod proto; mod check; #[legacy_exports] mod liveness; + + +fn expand_proto(cx: ext_ctxt, _sp: span, id: ast::ident, + tt: ~[ast::token_tree]) -> base::mac_result +{ + let sess = cx.parse_sess(); + let cfg = cx.cfg(); + let tt_rdr = new_tt_reader(cx.parse_sess().span_diagnostic, + cx.parse_sess().interner, None, tt); + let rdr = tt_rdr as reader; + let rust_parser = Parser(sess, cfg, rdr.dup()); + + let proto = rust_parser.parse_proto(cx.str_of(id)); + + // check for errors + visit(proto, cx); + + // do analysis + liveness::analyze(proto, cx); + + // compile + base::mr_item(proto.compile(cx)) +} + diff --git a/src/libsyntax/parse.rs b/src/libsyntax/parse.rs deleted file mode 100644 index 9d243556c03..00000000000 --- a/src/libsyntax/parse.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! The main parser interface - -#[legacy_exports]; - -export parser; -export common; -export lexer; -export token; -export comments; -export prec; -export classify; -export attr; - -export parse_sess; -export new_parse_sess, new_parse_sess_special_handler; -export next_node_id; -export new_parser_from_file, new_parser_etc_from_file; -export new_parser_from_source_str; -export new_parser_from_tts; -export new_sub_parser_from_file; -export parse_crate_from_file, parse_crate_from_crate_file; -export parse_crate_from_source_str; -export parse_expr_from_source_str, parse_item_from_source_str; -export parse_stmt_from_source_str; -export parse_tts_from_source_str; -export parse_from_source_str; - -use parser::Parser; -use attr::parser_attr; -use common::parser_common; -use ast::node_id; -use util::interner; -use diagnostic::{span_handler, mk_span_handler, mk_handler, emitter}; -use lexer::{reader, string_reader}; -use parse::token::{ident_interner, mk_ident_interner}; -use codemap::{span, CodeMap, FileMap, CharPos, BytePos}; - -type parse_sess = @{ - cm: @codemap::CodeMap, - mut next_id: node_id, - span_diagnostic: span_handler, - interner: @ident_interner, -}; - -fn new_parse_sess(demitter: Option<emitter>) -> parse_sess { - let cm = @CodeMap::new(); - return @{cm: cm, - mut next_id: 1, - span_diagnostic: mk_span_handler(mk_handler(demitter), cm), - interner: mk_ident_interner(), - }; -} - -fn new_parse_sess_special_handler(sh: span_handler, cm: @codemap::CodeMap) - -> parse_sess { - return @{cm: cm, - mut next_id: 1, - span_diagnostic: sh, - interner: mk_ident_interner(), - }; -} - -fn parse_crate_from_file(input: &Path, cfg: ast::crate_cfg, - sess: parse_sess) -> @ast::crate { - let p = new_crate_parser_from_file(sess, cfg, input); - let r = p.parse_crate_mod(cfg); - return r; -} - -fn parse_crate_from_source_str(name: ~str, source: @~str, cfg: ast::crate_cfg, - sess: parse_sess) -> @ast::crate { - let p = new_parser_from_source_str(sess, cfg, name, - codemap::FssNone, source); - let r = p.parse_crate_mod(cfg); - p.abort_if_errors(); - return r; -} - -fn parse_expr_from_source_str(name: ~str, source: @~str, cfg: ast::crate_cfg, - sess: parse_sess) -> @ast::expr { - let p = new_parser_from_source_str(sess, cfg, name, - codemap::FssNone, source); - let r = p.parse_expr(); - p.abort_if_errors(); - return r; -} - -fn parse_item_from_source_str(name: ~str, source: @~str, cfg: ast::crate_cfg, - +attrs: ~[ast::attribute], - sess: parse_sess) -> Option<@ast::item> { - let p = new_parser_from_source_str(sess, cfg, name, - codemap::FssNone, source); - let r = p.parse_item(attrs); - p.abort_if_errors(); - return r; -} - -fn parse_stmt_from_source_str(name: ~str, source: @~str, cfg: ast::crate_cfg, - +attrs: ~[ast::attribute], - sess: parse_sess) -> @ast::stmt { - let p = new_parser_from_source_str(sess, cfg, name, - codemap::FssNone, source); - let r = p.parse_stmt(attrs); - p.abort_if_errors(); - return r; -} - -fn parse_tts_from_source_str(name: ~str, source: @~str, cfg: ast::crate_cfg, - sess: parse_sess) -> ~[ast::token_tree] { - let p = new_parser_from_source_str(sess, cfg, name, - codemap::FssNone, source); - p.quote_depth += 1u; - let r = p.parse_all_token_trees(); - p.abort_if_errors(); - return r; -} - -fn parse_from_source_str<T>(f: fn (p: Parser) -> T, - name: ~str, ss: codemap::FileSubstr, - source: @~str, cfg: ast::crate_cfg, - sess: parse_sess) - -> T -{ - let p = new_parser_from_source_str(sess, cfg, name, ss, - source); - let r = f(p); - if !p.reader.is_eof() { - p.reader.fatal(~"expected end-of-string"); - } - p.abort_if_errors(); - move r -} - -fn next_node_id(sess: parse_sess) -> node_id { - let rv = sess.next_id; - sess.next_id += 1; - // ID 0 is reserved for the crate and doesn't actually exist in the AST - assert rv != 0; - return rv; -} - -fn new_parser_from_source_str(sess: parse_sess, cfg: ast::crate_cfg, - +name: ~str, +ss: codemap::FileSubstr, - source: @~str) -> Parser { - let filemap = sess.cm.new_filemap_w_substr(name, ss, source); - let srdr = lexer::new_string_reader(sess.span_diagnostic, filemap, - sess.interner); - return Parser(sess, cfg, srdr as reader); -} - -fn new_parser_from_file(sess: parse_sess, cfg: ast::crate_cfg, - path: &Path) -> Result<Parser, ~str> { - match io::read_whole_file_str(path) { - result::Ok(move src) => { - - let filemap = sess.cm.new_filemap(path.to_str(), @move src); - let srdr = lexer::new_string_reader(sess.span_diagnostic, filemap, - sess.interner); - - Ok(Parser(sess, cfg, srdr as reader)) - - } - result::Err(move e) => Err(move e) - } -} - -/// Create a new parser for an entire crate, handling errors as appropriate -/// if the file doesn't exist -fn new_crate_parser_from_file(sess: parse_sess, cfg: ast::crate_cfg, - path: &Path) -> Parser { - match new_parser_from_file(sess, cfg, path) { - Ok(move parser) => move parser, - Err(move e) => { - sess.span_diagnostic.handler().fatal(e) - } - } -} - -/// Create a new parser based on a span from an existing parser. Handles -/// error messages correctly when the file does not exist. -fn new_sub_parser_from_file(sess: parse_sess, cfg: ast::crate_cfg, - path: &Path, sp: span) -> Parser { - match new_parser_from_file(sess, cfg, path) { - Ok(move parser) => move parser, - Err(move e) => { - sess.span_diagnostic.span_fatal(sp, e) - } - } -} - -fn new_parser_from_tts(sess: parse_sess, cfg: ast::crate_cfg, - tts: ~[ast::token_tree]) -> Parser { - let trdr = lexer::new_tt_reader(sess.span_diagnostic, sess.interner, - None, tts); - return Parser(sess, cfg, trdr as reader) -} diff --git a/src/libsyntax/parse/mod.rs b/src/libsyntax/parse/mod.rs index a7c5f20fedf..c290e7cf307 100644 --- a/src/libsyntax/parse/mod.rs +++ b/src/libsyntax/parse/mod.rs @@ -1,3 +1,40 @@ +//! The main parser interface + +#[legacy_exports]; + +export parser; +export common; +export lexer; +export token; +export comments; +export prec; +export classify; +export attr; + +export parse_sess; +export new_parse_sess, new_parse_sess_special_handler; +export next_node_id; +export new_parser_from_file, new_parser_etc_from_file; +export new_parser_from_source_str; +export new_parser_from_tts; +export new_sub_parser_from_file; +export parse_crate_from_file, parse_crate_from_crate_file; +export parse_crate_from_source_str; +export parse_expr_from_source_str, parse_item_from_source_str; +export parse_stmt_from_source_str; +export parse_tts_from_source_str; +export parse_from_source_str; + +use parser::Parser; +use attr::parser_attr; +use common::parser_common; +use ast::node_id; +use util::interner; +use diagnostic::{span_handler, mk_span_handler, mk_handler, emitter}; +use lexer::{reader, string_reader}; +use parse::token::{ident_interner, mk_ident_interner}; +use codemap::{span, CodeMap, FileMap, CharPos, BytePos}; + #[legacy_exports] mod lexer; @@ -26,3 +63,164 @@ mod classify; /// Reporting obsolete syntax #[legacy_exports] mod obsolete; + + +type parse_sess = @{ + cm: @codemap::CodeMap, + mut next_id: node_id, + span_diagnostic: span_handler, + interner: @ident_interner, +}; + +fn new_parse_sess(demitter: Option<emitter>) -> parse_sess { + let cm = @CodeMap::new(); + return @{cm: cm, + mut next_id: 1, + span_diagnostic: mk_span_handler(mk_handler(demitter), cm), + interner: mk_ident_interner(), + }; +} + +fn new_parse_sess_special_handler(sh: span_handler, cm: @codemap::CodeMap) + -> parse_sess { + return @{cm: cm, + mut next_id: 1, + span_diagnostic: sh, + interner: mk_ident_interner(), + }; +} + +fn parse_crate_from_file(input: &Path, cfg: ast::crate_cfg, + sess: parse_sess) -> @ast::crate { + let p = new_crate_parser_from_file(sess, cfg, input); + let r = p.parse_crate_mod(cfg); + return r; +} + +fn parse_crate_from_source_str(name: ~str, source: @~str, cfg: ast::crate_cfg, + sess: parse_sess) -> @ast::crate { + let p = new_parser_from_source_str(sess, cfg, name, + codemap::FssNone, source); + let r = p.parse_crate_mod(cfg); + p.abort_if_errors(); + return r; +} + +fn parse_expr_from_source_str(name: ~str, source: @~str, cfg: ast::crate_cfg, + sess: parse_sess) -> @ast::expr { + let p = new_parser_from_source_str(sess, cfg, name, + codemap::FssNone, source); + let r = p.parse_expr(); + p.abort_if_errors(); + return r; +} + +fn parse_item_from_source_str(name: ~str, source: @~str, cfg: ast::crate_cfg, + +attrs: ~[ast::attribute], + sess: parse_sess) -> Option<@ast::item> { + let p = new_parser_from_source_str(sess, cfg, name, + codemap::FssNone, source); + let r = p.parse_item(attrs); + p.abort_if_errors(); + return r; +} + +fn parse_stmt_from_source_str(name: ~str, source: @~str, cfg: ast::crate_cfg, + +attrs: ~[ast::attribute], + sess: parse_sess) -> @ast::stmt { + let p = new_parser_from_source_str(sess, cfg, name, + codemap::FssNone, source); + let r = p.parse_stmt(attrs); + p.abort_if_errors(); + return r; +} + +fn parse_tts_from_source_str(name: ~str, source: @~str, cfg: ast::crate_cfg, + sess: parse_sess) -> ~[ast::token_tree] { + let p = new_parser_from_source_str(sess, cfg, name, + codemap::FssNone, source); + p.quote_depth += 1u; + let r = p.parse_all_token_trees(); + p.abort_if_errors(); + return r; +} + +fn parse_from_source_str<T>(f: fn (p: Parser) -> T, + name: ~str, ss: codemap::FileSubstr, + source: @~str, cfg: ast::crate_cfg, + sess: parse_sess) + -> T +{ + let p = new_parser_from_source_str(sess, cfg, name, ss, + source); + let r = f(p); + if !p.reader.is_eof() { + p.reader.fatal(~"expected end-of-string"); + } + p.abort_if_errors(); + move r +} + +fn next_node_id(sess: parse_sess) -> node_id { + let rv = sess.next_id; + sess.next_id += 1; + // ID 0 is reserved for the crate and doesn't actually exist in the AST + assert rv != 0; + return rv; +} + +fn new_parser_from_source_str(sess: parse_sess, cfg: ast::crate_cfg, + +name: ~str, +ss: codemap::FileSubstr, + source: @~str) -> Parser { + let filemap = sess.cm.new_filemap_w_substr(name, ss, source); + let srdr = lexer::new_string_reader(sess.span_diagnostic, filemap, + sess.interner); + return Parser(sess, cfg, srdr as reader); +} + +fn new_parser_from_file(sess: parse_sess, cfg: ast::crate_cfg, + path: &Path) -> Result<Parser, ~str> { + match io::read_whole_file_str(path) { + result::Ok(move src) => { + + let filemap = sess.cm.new_filemap(path.to_str(), @move src); + let srdr = lexer::new_string_reader(sess.span_diagnostic, filemap, + sess.interner); + + Ok(Parser(sess, cfg, srdr as reader)) + + } + result::Err(move e) => Err(move e) + } +} + +/// Create a new parser for an entire crate, handling errors as appropriate +/// if the file doesn't exist +fn new_crate_parser_from_file(sess: parse_sess, cfg: ast::crate_cfg, + path: &Path) -> Parser { + match new_parser_from_file(sess, cfg, path) { + Ok(move parser) => move parser, + Err(move e) => { + sess.span_diagnostic.handler().fatal(e) + } + } +} + +/// Create a new parser based on a span from an existing parser. Handles +/// error messages correctly when the file does not exist. +fn new_sub_parser_from_file(sess: parse_sess, cfg: ast::crate_cfg, + path: &Path, sp: span) -> Parser { + match new_parser_from_file(sess, cfg, path) { + Ok(move parser) => move parser, + Err(move e) => { + sess.span_diagnostic.span_fatal(sp, e) + } + } +} + +fn new_parser_from_tts(sess: parse_sess, cfg: ast::crate_cfg, + tts: ~[ast::token_tree]) -> Parser { + let trdr = lexer::new_tt_reader(sess.span_diagnostic, sess.interner, + None, tts); + return Parser(sess, cfg, trdr as reader) +} diff --git a/src/libsyntax/syntax.rc b/src/libsyntax/syntax.rc index d5a28a716ec..60036fe7737 100644 --- a/src/libsyntax/syntax.rc +++ b/src/libsyntax/syntax.rc @@ -44,7 +44,7 @@ mod util { mod interner; } -#[merge = "parse/mod.rs"] +#[path = "parse/mod.rs"] mod parse; mod print { @@ -118,8 +118,7 @@ mod ext { mod source_util; #[legacy_exports] - #[path = "ext/pipes.rs"] - #[merge = "ext/pipes/mod.rs"] + #[path = "ext/pipes/mod.rs"] mod pipes; #[legacy_exports] |
