<commit_before>//! The context or environment in which the language server functions. In our
//! server implementation this is know as the `WorldState`.
//!
//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.

use std::{convert::TryFrom, sync::Arc};

use crossbeam_channel::{unbounded, Receiver, Sender};
use flycheck::{FlycheckConfig, FlycheckHandle};
use lsp_types::Url;
use parking_lot::RwLock;
use ra_db::{CrateId, SourceRoot, VfsPath};
use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId};
use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
use stdx::format_to;
use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf};

use crate::{
    config::{Config, FilesWatcher},
    diagnostics::{CheckFixes, DiagnosticCollection},
    from_proto,
    line_endings::LineEndings,
    main_loop::{ReqQueue, Task},
    request_metrics::{LatestRequests, RequestMetrics},
    show_message,
    thread_pool::TaskPool,
    to_proto::url_from_abs_path,
    Result,
};
use rustc_hash::{FxHashMap, FxHashSet};

fn create_flycheck(
    workspaces: &[ProjectWorkspace],
    config: &FlycheckConfig,
) -> Option<(FlycheckHandle, Receiver<flycheck::Message>)> {
    // FIXME: Figure out the multi-workspace situation
    workspaces.iter().find_map(move |w| match w {
        ProjectWorkspace::Cargo { cargo, .. } => {
            let (sender, receiver) = unbounded();
            let sender = Box::new(move |msg| sender.send(msg).unwrap());
            let cargo_project_root = cargo.workspace_root().to_path_buf();
            let flycheck = FlycheckHandle::spawn(sender, config.clone(), cargo_project_root.into());
            Some((flycheck, receiver))
        }
        ProjectWorkspace::Json { .. } => {
            log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
            None
        }
    })
}

#[derive(Eq, PartialEq)]
pub(crate) enum Status {
    Loading,
    Ready,
}

impl Default for Status {
    fn default() -> Self {
        Status::Loading
    }
}

/// `GlobalState` is the primary mutable state of the language server
///
/// The most interesting components are `vfs`, which stores a consistent
/// snapshot of the file systems, and `analysis_host`, which stores our
/// incremental salsa database.
pub(crate) struct GlobalState {
    sender: Sender<lsp_server::Message>,
    pub(crate) config: Config,
    pub(crate) task_pool: (TaskPool<Task>, Receiver<Task>),
    pub(crate) analysis_host: AnalysisHost,
    pub(crate) loader: Box<dyn vfs::loader::Handle>,
    pub(crate) task_receiver: Receiver<vfs::loader::Message>,
    pub(crate) flycheck: Option<(FlycheckHandle, Receiver<flycheck::Message>)>,
    pub(crate) diagnostics: DiagnosticCollection,
    pub(crate) mem_docs: FxHashSet<VfsPath>,
    pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
    pub(crate) status: Status,
    pub(crate) req_queue: ReqQueue,
    latest_requests: Arc<RwLock<LatestRequests>>,
    source_root_config: SourceRootConfig,
    _proc_macro_client: ProcMacroClient,
    workspaces: Arc<Vec<ProjectWorkspace>>,
}

/// An immutable snapshot of the world's state at a point in time.
pub(crate) struct GlobalStateSnapshot {
    pub(crate) config: Config,
    pub(crate) analysis: Analysis,
    pub(crate) check_fixes: CheckFixes,
    pub(crate) latest_requests: Arc<RwLock<LatestRequests>>,
    vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
    workspaces: Arc<Vec<ProjectWorkspace>>,
}

impl GlobalState {
    pub(crate) fn new(
        sender: Sender<lsp_server::Message>,
        workspaces: Vec<ProjectWorkspace>,
        lru_capacity: Option<usize>,
        config: Config,
        req_queue: ReqQueue,
    ) -> GlobalState {
        let mut change = AnalysisChange::new();

        let project_folders = ProjectFolders::new(&workspaces);

        let (task_sender, task_receiver) = unbounded::<vfs::loader::Message>();
        let mut vfs = vfs::Vfs::default();

        let proc_macro_client = match &config.proc_macro_srv {
            None => ProcMacroClient::dummy(),
            Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) {
                Ok(it) => it,
                Err(err) => {
                    log::error!(
                        "Failed to run ra_proc_macro_srv from path {}, error: {:?}",
                        path.display(),
                        err
                    );
                    ProcMacroClient::dummy()
                }
            },
        };

        let mut loader = {
            let loader = vfs_notify::NotifyHandle::spawn(Box::new(move |msg| {
                task_sender.send(msg).unwrap()
            }));
            Box::new(loader)
        };
        let watch = match config.files.watcher {
            FilesWatcher::Client => vec![],
            FilesWatcher::Notify => project_folders.watch,
        };
        loader.set_config(vfs::loader::Config { load: project_folders.load, watch });

        // Create crate graph from all the workspaces
        let mut crate_graph = CrateGraph::default();
        let mut load = |path: &AbsPath| {
            let contents = loader.load_sync(path);
            let path = vfs::VfsPath::from(path.to_path_buf());
            vfs.set_file_contents(path.clone(), contents);
            vfs.file_id(&path)
        };
        for ws in workspaces.iter() {
            crate_graph.extend(ws.to_crate_graph(
                config.cargo.target.as_deref(),
                &proc_macro_client,
                &mut load,
            ));
        }
        change.set_crate_graph(crate_graph);

        let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c));

        let mut analysis_host = AnalysisHost::new(lru_capacity);
        analysis_host.apply_change(change);

        let task_pool = {
            let (sender, receiver) = unbounded();
            (TaskPool::new(sender), receiver)
        };

        let mut res = GlobalState {
            sender,
            config,
            task_pool,
            analysis_host,
            loader,
            task_receiver,
            flycheck,
            diagnostics: Default::default(),
            mem_docs: FxHashSet::default(),
            vfs: Arc::new(RwLock::new((vfs, FxHashMap::default()))),
            status: Status::default(),
            req_queue,
            latest_requests: Default::default(),
            source_root_config: project_folders.source_root_config,
            _proc_macro_client: proc_macro_client,
            workspaces: Arc::new(workspaces),
        };
        res.process_changes();
        res
    }

    pub(crate) fn update_configuration(&mut self, config: Config) {
        self.analysis_host.update_lru_capacity(config.lru_capacity);
        if config.check != self.config.check {
            self.flycheck =
                config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it));
        }

        self.config = config;
    }

    pub(crate) fn process_changes(&mut self) -> bool {
        let change = {
            let mut change = AnalysisChange::new();
            let (vfs, line_endings_map) = &mut *self.vfs.write();
            let changed_files = vfs.take_changes();
            if changed_files.is_empty() {
                return false;
            }

            let fs_op = changed_files.iter().any(|it| it.is_created_or_deleted());
            if fs_op {
                let roots = self.source_root_config.partition(&vfs);
                change.set_roots(roots)
            }

            for file in changed_files {
                let text = if file.exists() {
                    let bytes = vfs.file_contents(file.file_id).to_vec();
                    match String::from_utf8(bytes).ok() {
                        Some(text) => {
                            let (text, line_endings) = LineEndings::normalize(text);
                            line_endings_map.insert(file.file_id, line_endings);
                            Some(Arc::new(text))
                        }
                        None => None,
                    }
                } else {
                    None
                };
                change.change_file(file.file_id, text);
            }
            change
        };

        self.analysis_host.apply_change(change);
        true
    }

    pub(crate) fn snapshot(&self) -> GlobalStateSnapshot {
        GlobalStateSnapshot {
            config: self.config.clone(),
            workspaces: Arc::clone(&self.workspaces),
            analysis: self.analysis_host.analysis(),
            vfs: Arc::clone(&self.vfs),
            latest_requests: Arc::clone(&self.latest_requests),
            check_fixes: Arc::clone(&self.diagnostics.check_fixes),
        }
    }

    pub(crate) fn maybe_collect_garbage(&mut self) {
        self.analysis_host.maybe_collect_garbage()
    }

    pub(crate) fn collect_garbage(&mut self) {
        self.analysis_host.collect_garbage()
    }

    pub(crate) fn send(&mut self, message: lsp_server::Message) {
        self.sender.send(message).unwrap()
    }
    pub(crate) fn respond(&mut self, response: lsp_server::Response) {
        if let Some((method, start)) = self.req_queue.incoming.complete(response.id.clone()) {
            let duration = start.elapsed();
            log::info!("handled req#{} in {:?}", response.id, duration);
            let metrics =
                RequestMetrics { id: response.id.clone(), method: method.to_string(), duration };
            self.latest_requests.write().record(metrics);
            self.send(response.into());
        }
    }
    pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) {
        show_message(typ, message, &self.sender)
    }
}

impl Drop for GlobalState {
    fn drop(&mut self) {
        self.analysis_host.request_cancellation()
    }
}

impl GlobalStateSnapshot {
    pub(crate) fn url_to_file_id(&self, url: &Url) -> Result<FileId> {
        let path = from_proto::abs_path(url)?;
        let path = path.into();
        let res =
            self.vfs.read().0.file_id(&path).ok_or_else(|| format!("file not found: {}", path))?;
        Ok(res)
    }

    pub(crate) fn file_id_to_url(&self, id: FileId) -> Url {
        file_id_to_url(&self.vfs.read().0, id)
    }

    pub(crate) fn file_line_endings(&self, id: FileId) -> LineEndings {
        self.vfs.read().1[&id]
    }

    pub(crate) fn anchored_path(&self, file_id: FileId, path: &str) -> Url {
        let mut base = self.vfs.read().0.file_path(file_id);
        base.pop();
        let path = base.join(path);
        let path = path.as_path().unwrap();
        url_from_abs_path(&path)
    }

    pub(crate) fn cargo_target_for_crate_root(
        &self,
        crate_id: CrateId,
    ) -> Option<(&CargoWorkspace, Target)> {
        let file_id = self.analysis.crate_root(crate_id).ok()?;
        let path = self.vfs.read().0.file_path(file_id);
        let path = path.as_path()?;
        self.workspaces.iter().find_map(|ws| match ws {
            ProjectWorkspace::Cargo { cargo, .. } => {
                cargo.target_by_root(&path).map(|it| (cargo, it))
            }
            ProjectWorkspace::Json { .. } => None,
        })
    }

    pub(crate) fn status(&self) -> String {
        let mut buf = String::new();
        if self.workspaces.is_empty() {
            buf.push_str("no workspaces\n")
        } else {
            buf.push_str("workspaces:\n");
            for w in self.workspaces.iter() {
                format_to!(buf, "{} packages loaded\n", w.n_packages());
            }
        }
        buf.push_str("\nanalysis:\n");
        buf.push_str(
            &self
                .analysis
                .status()
                .unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()),
        );
        buf
    }
}

pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
    let path = vfs.file_path(id);
    let path = path.as_path().unwrap();
    url_from_abs_path(&path)
}

#[derive(Default)]
pub(crate) struct ProjectFolders {
    pub(crate) load: Vec<vfs::loader::Entry>,
    pub(crate) watch: Vec<usize>,
    pub(crate) source_root_config: SourceRootConfig,
}

impl ProjectFolders {
    pub(crate) fn new(workspaces: &[ProjectWorkspace]) -> ProjectFolders {
        let mut res = ProjectFolders::default();
        let mut fsc = FileSetConfig::builder();
        let mut local_filesets = vec![];

        for root in workspaces.iter().flat_map(|it| it.to_roots()) {
            let path = root.path().to_owned();

            let mut file_set_roots: Vec<VfsPath> = vec![];

            let entry = if root.is_member() {
                vfs::loader::Entry::local_cargo_package(path.to_path_buf())
            } else {
                vfs::loader::Entry::cargo_package_dependency(path.to_path_buf())
            };
            res.load.push(entry);
            if root.is_member() {
                res.watch.push(res.load.len() - 1);
            }

            if let Some(out_dir) = root.out_dir() {
                let out_dir = AbsPathBuf::try_from(out_dir.to_path_buf()).unwrap();
                res.load.push(vfs::loader::Entry::rs_files_recursively(out_dir.clone()));
                if root.is_member() {
                    res.watch.push(res.load.len() - 1);
                }
                file_set_roots.push(out_dir.into());
            }
            file_set_roots.push(path.to_path_buf().into());

            if root.is_member() {
                local_filesets.push(fsc.len());
            }
            fsc.add_file_set(file_set_roots)
        }

        let fsc = fsc.build();
        res.source_root_config = SourceRootConfig { fsc, local_filesets };

        res
    }
}

#[derive(Default, Debug)]
pub(crate) struct SourceRootConfig {
    pub(crate) fsc: FileSetConfig,
    pub(crate) local_filesets: Vec<usize>,
}

impl SourceRootConfig {
    pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
        self.fsc
            .partition(vfs)
            .into_iter()
            .enumerate()
            .map(|(idx, file_set)| {
                let is_local = self.local_filesets.contains(&idx);
                if is_local {
                    SourceRoot::new_local(file_set)
                } else {
                    SourceRoot::new_library(file_set)
                }
            })
            .collect()
    }
}
<commit_msg>Separate creation and initialization of global state<commit_after>//! The context or environment in which the language server functions. In our
//! server implementation this is know as the `WorldState`.
//!
//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.

use std::{convert::TryFrom, sync::Arc};

use crossbeam_channel::{unbounded, Receiver, Sender};
use flycheck::{FlycheckConfig, FlycheckHandle};
use lsp_types::Url;
use parking_lot::RwLock;
use ra_db::{CrateId, SourceRoot, VfsPath};
use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId};
use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
use stdx::format_to;
use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf};

use crate::{
    config::{Config, FilesWatcher},
    diagnostics::{CheckFixes, DiagnosticCollection},
    from_proto,
    line_endings::LineEndings,
    main_loop::{ReqQueue, Task},
    request_metrics::{LatestRequests, RequestMetrics},
    show_message,
    thread_pool::TaskPool,
    to_proto::url_from_abs_path,
    Result,
};
use rustc_hash::{FxHashMap, FxHashSet};

fn create_flycheck(
    workspaces: &[ProjectWorkspace],
    config: &FlycheckConfig,
) -> Option<(FlycheckHandle, Receiver<flycheck::Message>)> {
    // FIXME: Figure out the multi-workspace situation
    workspaces.iter().find_map(move |w| match w {
        ProjectWorkspace::Cargo { cargo, .. } => {
            let (sender, receiver) = unbounded();
            let sender = Box::new(move |msg| sender.send(msg).unwrap());
            let cargo_project_root = cargo.workspace_root().to_path_buf();
            let flycheck = FlycheckHandle::spawn(sender, config.clone(), cargo_project_root.into());
            Some((flycheck, receiver))
        }
        ProjectWorkspace::Json { .. } => {
            log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
            None
        }
    })
}

#[derive(Eq, PartialEq)]
pub(crate) enum Status {
    Loading,
    Ready,
}

impl Default for Status {
    fn default() -> Self {
        Status::Loading
    }
}

/// `GlobalState` is the primary mutable state of the language server
///
/// The most interesting components are `vfs`, which stores a consistent
/// snapshot of the file systems, and `analysis_host`, which stores our
/// incremental salsa database.
pub(crate) struct GlobalState {
    sender: Sender<lsp_server::Message>,
    pub(crate) config: Config,
    pub(crate) task_pool: (TaskPool<Task>, Receiver<Task>),
    pub(crate) analysis_host: AnalysisHost,
    pub(crate) loader: Box<dyn vfs::loader::Handle>,
    pub(crate) task_receiver: Receiver<vfs::loader::Message>,
    pub(crate) flycheck: Option<(FlycheckHandle, Receiver<flycheck::Message>)>,
    pub(crate) diagnostics: DiagnosticCollection,
    pub(crate) mem_docs: FxHashSet<VfsPath>,
    pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
    pub(crate) status: Status,
    pub(crate) req_queue: ReqQueue,
    latest_requests: Arc<RwLock<LatestRequests>>,
    source_root_config: SourceRootConfig,
    proc_macro_client: ProcMacroClient,
    workspaces: Arc<Vec<ProjectWorkspace>>,
}

/// An immutable snapshot of the world's state at a point in time.
pub(crate) struct GlobalStateSnapshot {
    pub(crate) config: Config,
    pub(crate) analysis: Analysis,
    pub(crate) check_fixes: CheckFixes,
    pub(crate) latest_requests: Arc<RwLock<LatestRequests>>,
    vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
    workspaces: Arc<Vec<ProjectWorkspace>>,
}

impl GlobalState {
    pub(crate) fn new(
        sender: Sender<lsp_server::Message>,
        workspaces: Vec<ProjectWorkspace>,
        lru_capacity: Option<usize>,
        config: Config,
        req_queue: ReqQueue,
    ) -> GlobalState {
        let (task_sender, task_receiver) = unbounded::<vfs::loader::Message>();

        let loader = {
            let loader = vfs_notify::NotifyHandle::spawn(Box::new(move |msg| {
                task_sender.send(msg).unwrap()
            }));
            Box::new(loader)
        };

        let task_pool = {
            let (sender, receiver) = unbounded();
            (TaskPool::new(sender), receiver)
        };

        let mut res = GlobalState {
            sender,
            config,
            task_pool,
            analysis_host: AnalysisHost::new(lru_capacity),
            loader,
            task_receiver,
            flycheck: None,
            diagnostics: Default::default(),
            mem_docs: FxHashSet::default(),
            vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),
            status: Status::default(),
            req_queue,
            latest_requests: Default::default(),
            source_root_config: SourceRootConfig::default(),
            proc_macro_client: ProcMacroClient::dummy(),
            workspaces: Arc::new(Vec::new()),
        };
        res.reload(workspaces);
        res
    }

    pub(crate) fn reload(&mut self, workspaces: Vec<ProjectWorkspace>) {
        let mut change = AnalysisChange::new();

        let project_folders = ProjectFolders::new(&workspaces);

        self.proc_macro_client = match &self.config.proc_macro_srv {
            None => ProcMacroClient::dummy(),
            Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) {
                Ok(it) => it,
                Err(err) => {
                    log::error!(
                        "Failed to run ra_proc_macro_srv from path {}, error: {:?}",
                        path.display(),
                        err
                    );
                    ProcMacroClient::dummy()
                }
            },
        };
        let watch = match self.config.files.watcher {
            FilesWatcher::Client => vec![],
            FilesWatcher::Notify => project_folders.watch,
        };
        self.loader.set_config(vfs::loader::Config { load: project_folders.load, watch });

        // Create crate graph from all the workspaces
        let crate_graph = {
            let mut crate_graph = CrateGraph::default();
            let vfs = &mut self.vfs.write().0;
            let loader = &mut self.loader;
            let mut load = |path: &AbsPath| {
                let contents = loader.load_sync(path);
                let path = vfs::VfsPath::from(path.to_path_buf());
                vfs.set_file_contents(path.clone(), contents);
                vfs.file_id(&path)
            };
            for ws in workspaces.iter() {
                crate_graph.extend(ws.to_crate_graph(
                    self.config.cargo.target.as_deref(),
                    &self.proc_macro_client,
                    &mut load,
                ));
            }

            crate_graph
        };
        change.set_crate_graph(crate_graph);

        self.flycheck = self.config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c));
        self.source_root_config = project_folders.source_root_config;
        self.workspaces = Arc::new(workspaces);

        self.analysis_host.apply_change(change);
        self.process_changes();
    }

    pub(crate) fn update_configuration(&mut self, config: Config) {
        self.analysis_host.update_lru_capacity(config.lru_capacity);
        if config.check != self.config.check {
            self.flycheck =
                config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it));
        }

        self.config = config;
    }

    pub(crate) fn process_changes(&mut self) -> bool {
        let change = {
            let mut change = AnalysisChange::new();
            let (vfs, line_endings_map) = &mut *self.vfs.write();
            let changed_files = vfs.take_changes();
            if changed_files.is_empty() {
                return false;
            }

            let fs_op = changed_files.iter().any(|it| it.is_created_or_deleted());
            if fs_op {
                let roots = self.source_root_config.partition(&vfs);
                change.set_roots(roots)
            }

            for file in changed_files {
                let text = if file.exists() {
                    let bytes = vfs.file_contents(file.file_id).to_vec();
                    match String::from_utf8(bytes).ok() {
                        Some(text) => {
                            let (text, line_endings) = LineEndings::normalize(text);
                            line_endings_map.insert(file.file_id, line_endings);
                            Some(Arc::new(text))
                        }
                        None => None,
                    }
                } else {
                    None
                };
                change.change_file(file.file_id, text);
            }
            change
        };

        self.analysis_host.apply_change(change);
        true
    }

    pub(crate) fn snapshot(&self) -> GlobalStateSnapshot {
        GlobalStateSnapshot {
            config: self.config.clone(),
            workspaces: Arc::clone(&self.workspaces),
            analysis: self.analysis_host.analysis(),
            vfs: Arc::clone(&self.vfs),
            latest_requests: Arc::clone(&self.latest_requests),
            check_fixes: Arc::clone(&self.diagnostics.check_fixes),
        }
    }

    pub(crate) fn maybe_collect_garbage(&mut self) {
        self.analysis_host.maybe_collect_garbage()
    }

    pub(crate) fn collect_garbage(&mut self) {
        self.analysis_host.collect_garbage()
    }

    pub(crate) fn send(&mut self, message: lsp_server::Message) {
        self.sender.send(message).unwrap()
    }
    pub(crate) fn respond(&mut self, response: lsp_server::Response) {
        if let Some((method, start)) = self.req_queue.incoming.complete(response.id.clone()) {
            let duration = start.elapsed();
            log::info!("handled req#{} in {:?}", response.id, duration);
            let metrics =
                RequestMetrics { id: response.id.clone(), method: method.to_string(), duration };
            self.latest_requests.write().record(metrics);
            self.send(response.into());
        }
    }
    pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) {
        show_message(typ, message, &self.sender)
    }
}

impl Drop for GlobalState {
    fn drop(&mut self) {
        self.analysis_host.request_cancellation()
    }
}

impl GlobalStateSnapshot {
    pub(crate) fn url_to_file_id(&self, url: &Url) -> Result<FileId> {
        let path = from_proto::abs_path(url)?;
        let path = path.into();
        let res =
            self.vfs.read().0.file_id(&path).ok_or_else(|| format!("file not found: {}", path))?;
        Ok(res)
    }

    pub(crate) fn file_id_to_url(&self, id: FileId) -> Url {
        file_id_to_url(&self.vfs.read().0, id)
    }

    pub(crate) fn file_line_endings(&self, id: FileId) -> LineEndings {
        self.vfs.read().1[&id]
    }

    pub(crate) fn anchored_path(&self, file_id: FileId, path: &str) -> Url {
        let mut base = self.vfs.read().0.file_path(file_id);
        base.pop();
        let path = base.join(path);
        let path = path.as_path().unwrap();
        url_from_abs_path(&path)
    }

    pub(crate) fn cargo_target_for_crate_root(
        &self,
        crate_id: CrateId,
    ) -> Option<(&CargoWorkspace, Target)> {
        let file_id = self.analysis.crate_root(crate_id).ok()?;
        let path = self.vfs.read().0.file_path(file_id);
        let path = path.as_path()?;
        self.workspaces.iter().find_map(|ws| match ws {
            ProjectWorkspace::Cargo { cargo, .. } => {
                cargo.target_by_root(&path).map(|it| (cargo, it))
            }
            ProjectWorkspace::Json { .. } => None,
        })
    }

    pub(crate) fn status(&self) -> String {
        let mut buf = String::new();
        if self.workspaces.is_empty() {
            buf.push_str("no workspaces\n")
        } else {
            buf.push_str("workspaces:\n");
            for w in self.workspaces.iter() {
                format_to!(buf, "{} packages loaded\n", w.n_packages());
            }
        }
        buf.push_str("\nanalysis:\n");
        buf.push_str(
            &self
                .analysis
                .status()
                .unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()),
        );
        buf
    }
}

pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
    let path = vfs.file_path(id);
    let path = path.as_path().unwrap();
    url_from_abs_path(&path)
}

#[derive(Default)]
pub(crate) struct ProjectFolders {
    pub(crate) load: Vec<vfs::loader::Entry>,
    pub(crate) watch: Vec<usize>,
    pub(crate) source_root_config: SourceRootConfig,
}

impl ProjectFolders {
    pub(crate) fn new(workspaces: &[ProjectWorkspace]) -> ProjectFolders {
        let mut res = ProjectFolders::default();
        let mut fsc = FileSetConfig::builder();
        let mut local_filesets = vec![];

        for root in workspaces.iter().flat_map(|it| it.to_roots()) {
            let path = root.path().to_owned();

            let mut file_set_roots: Vec<VfsPath> = vec![];

            let entry = if root.is_member() {
                vfs::loader::Entry::local_cargo_package(path.to_path_buf())
            } else {
                vfs::loader::Entry::cargo_package_dependency(path.to_path_buf())
            };
            res.load.push(entry);
            if root.is_member() {
                res.watch.push(res.load.len() - 1);
            }

            if let Some(out_dir) = root.out_dir() {
                let out_dir = AbsPathBuf::try_from(out_dir.to_path_buf()).unwrap();
                res.load.push(vfs::loader::Entry::rs_files_recursively(out_dir.clone()));
                if root.is_member() {
                    res.watch.push(res.load.len() - 1);
                }
                file_set_roots.push(out_dir.into());
            }
            file_set_roots.push(path.to_path_buf().into());

            if root.is_member() {
                local_filesets.push(fsc.len());
            }
            fsc.add_file_set(file_set_roots)
        }

        let fsc = fsc.build();
        res.source_root_config = SourceRootConfig { fsc, local_filesets };

        res
    }
}

#[derive(Default, Debug)]
pub(crate) struct SourceRootConfig {
    pub(crate) fsc: FileSetConfig,
    pub(crate) local_filesets: Vec<usize>,
}

impl SourceRootConfig {
    pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
        self.fsc
            .partition(vfs)
            .into_iter()
            .enumerate()
            .map(|(idx, file_set)| {
                let is_local = self.local_filesets.contains(&idx);
                if is_local {
                    SourceRoot::new_local(file_set)
                } else {
                    SourceRoot::new_library(file_set)
                }
            })
            .collect()
    }
}
<|endoftext|>


<commit_before>use crate::utils::sugg::Sugg;
use crate::utils::{in_macro, snippet_opt, snippet_with_applicability, span_lint_and_sugg};
use if_chain::if_chain;
use rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::BytePos;

declare_clippy_lint! {
    /// **What it does:** Checks for usage of `*&` and `*&mut` in expressions.
    ///
    /// **Why is this bad?** Immediately dereferencing a reference is no-op and
    /// makes the code less clear.
    ///
    /// **Known problems:** Multiple dereference/addrof pairs are not handled so
    /// the suggested fix for `x = **&&y` is `x = *&y`, which is still incorrect.
    ///
    /// **Example:**
    /// ```rust,ignore
    /// // Bad
    /// let a = f(*&mut b);
    /// let c = *&d;
    ///
    /// // Good
    /// let a = f(b);
    /// let c = d;
    /// ```
    pub DEREF_ADDROF,
    complexity,
    "use of `*&` or `*&mut` in an expression"
}

declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]);

fn without_parens(mut e: &Expr) -> &Expr {
    while let ExprKind::Paren(ref child_e) = e.kind {
        e = child_e;
    }
    e
}

impl EarlyLintPass for DerefAddrOf {
    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
        if_chain! {
            if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind;
            if let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind;
            if !in_macro(addrof_target.span);
            then {
                let mut applicability = Applicability::MachineApplicable;
                let sugg = if e.span.from_expansion() {
                    if let Ok(macro_source) = cx.sess.source_map().span_to_snippet(e.span) {
                        // Remove leading whitespace from the given span
                        // e.g: ` $visitor` turns into `$visitor`
                        let trim_leading_whitespaces = |span| {
                            snippet_opt(cx, span).and_then(|snip| {
                                #[allow(clippy::cast_possible_truncation)]
                                snip.find(|c: char| !c.is_whitespace()).map(|pos| {
                                    span.lo() + BytePos(pos as u32)
                                })
                            }).map_or(span, |start_no_whitespace| e.span.with_lo(start_no_whitespace))
                        };

                        let mut generate_snippet = |pattern: &str| {
                            #[allow(clippy::cast_possible_truncation)]
                            macro_source.rfind(pattern).map(|pattern_pos| {
                                let rpos = pattern_pos + pattern.len();
                                let span_after_ref = e.span.with_lo(BytePos(e.span.lo().0 + rpos as u32));
                                let span = trim_leading_whitespaces(span_after_ref);
                                snippet_with_applicability(cx, span, "_", &mut applicability)
                            })
                        };

                        if *mutability == Mutability::Mut {
                            generate_snippet("mut")
                        } else {
                            generate_snippet("&")
                        }
                    } else {
                        Some(snippet_with_applicability(cx, e.span, "_", &mut applicability))
                    }
                } else {
                    Some(snippet_with_applicability(cx, addrof_target.span, "_", &mut applicability))
                };
                if let Some(sugg) = sugg {
                    span_lint_and_sugg(
                        cx,
                        DEREF_ADDROF,
                        e.span,
                        "immediately dereferencing a reference",
                        "try this",
                        sugg.to_string(),
                        applicability,
                    );
                }
            }
        }
    }
}

declare_clippy_lint! {
    /// **What it does:** Checks for references in expressions that use
    /// auto dereference.
    ///
    /// **Why is this bad?** The reference is a no-op and is automatically
    /// dereferenced by the compiler and makes the code less clear.
    ///
    /// **Example:**
    /// ```rust
    /// struct Point(u32, u32);
    /// let point = Point(30, 20);
    /// let x = (&point).0;
    /// ```
    pub REF_IN_DEREF,
    complexity,
    "Use of reference in auto dereference expression."
}

declare_lint_pass!(RefInDeref => [REF_IN_DEREF]);

impl EarlyLintPass for RefInDeref {
    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
        if_chain! {
            if let ExprKind::Field(ref object, _) = e.kind;
            if let ExprKind::Paren(ref parened) = object.kind;
            if let ExprKind::AddrOf(_, _, ref inner) = parened.kind;
            then {
                let applicability = if inner.span.from_expansion() {
                    Applicability::MaybeIncorrect
                } else {
                    Applicability::MachineApplicable
                };
                let sugg = Sugg::ast(cx, inner, "_").maybe_par();
                span_lint_and_sugg(
                    cx,
                    REF_IN_DEREF,
                    object.span,
                    "creating a reference that is immediately dereferenced",
                    "try this",
                    sugg.to_string(),
                    applicability,
                );
            }
        }
    }
}
<commit_msg>Improve the example in `ref_in_deref`<commit_after>use crate::utils::sugg::Sugg;
use crate::utils::{in_macro, snippet_opt, snippet_with_applicability, span_lint_and_sugg};
use if_chain::if_chain;
use rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::BytePos;

declare_clippy_lint! {
    /// **What it does:** Checks for usage of `*&` and `*&mut` in expressions.
    ///
    /// **Why is this bad?** Immediately dereferencing a reference is no-op and
    /// makes the code less clear.
    ///
    /// **Known problems:** Multiple dereference/addrof pairs are not handled so
    /// the suggested fix for `x = **&&y` is `x = *&y`, which is still incorrect.
    ///
    /// **Example:**
    /// ```rust,ignore
    /// // Bad
    /// let a = f(*&mut b);
    /// let c = *&d;
    ///
    /// // Good
    /// let a = f(b);
    /// let c = d;
    /// ```
    pub DEREF_ADDROF,
    complexity,
    "use of `*&` or `*&mut` in an expression"
}

declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]);

fn without_parens(mut e: &Expr) -> &Expr {
    while let ExprKind::Paren(ref child_e) = e.kind {
        e = child_e;
    }
    e
}

impl EarlyLintPass for DerefAddrOf {
    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
        if_chain! {
            if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind;
            if let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind;
            if !in_macro(addrof_target.span);
            then {
                let mut applicability = Applicability::MachineApplicable;
                let sugg = if e.span.from_expansion() {
                    if let Ok(macro_source) = cx.sess.source_map().span_to_snippet(e.span) {
                        // Remove leading whitespace from the given span
                        // e.g: ` $visitor` turns into `$visitor`
                        let trim_leading_whitespaces = |span| {
                            snippet_opt(cx, span).and_then(|snip| {
                                #[allow(clippy::cast_possible_truncation)]
                                snip.find(|c: char| !c.is_whitespace()).map(|pos| {
                                    span.lo() + BytePos(pos as u32)
                                })
                            }).map_or(span, |start_no_whitespace| e.span.with_lo(start_no_whitespace))
                        };

                        let mut generate_snippet = |pattern: &str| {
                            #[allow(clippy::cast_possible_truncation)]
                            macro_source.rfind(pattern).map(|pattern_pos| {
                                let rpos = pattern_pos + pattern.len();
                                let span_after_ref = e.span.with_lo(BytePos(e.span.lo().0 + rpos as u32));
                                let span = trim_leading_whitespaces(span_after_ref);
                                snippet_with_applicability(cx, span, "_", &mut applicability)
                            })
                        };

                        if *mutability == Mutability::Mut {
                            generate_snippet("mut")
                        } else {
                            generate_snippet("&")
                        }
                    } else {
                        Some(snippet_with_applicability(cx, e.span, "_", &mut applicability))
                    }
                } else {
                    Some(snippet_with_applicability(cx, addrof_target.span, "_", &mut applicability))
                };
                if let Some(sugg) = sugg {
                    span_lint_and_sugg(
                        cx,
                        DEREF_ADDROF,
                        e.span,
                        "immediately dereferencing a reference",
                        "try this",
                        sugg.to_string(),
                        applicability,
                    );
                }
            }
        }
    }
}

declare_clippy_lint! {
    /// **What it does:** Checks for references in expressions that use
    /// auto dereference.
    ///
    /// **Why is this bad?** The reference is a no-op and is automatically
    /// dereferenced by the compiler and makes the code less clear.
    ///
    /// **Example:**
    /// ```rust
    /// struct Point(u32, u32);
    /// let point = Point(30, 20);
    /// let x = (&point).0;
    /// ```
    /// Use instead:
    /// ```rust
    /// # struct Point(u32, u32);
    /// # let point = Point(30, 20);
    /// let x = point.0;
    /// ```
    pub REF_IN_DEREF,
    complexity,
    "Use of reference in auto dereference expression."
}

declare_lint_pass!(RefInDeref => [REF_IN_DEREF]);

impl EarlyLintPass for RefInDeref {
    fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
        if_chain! {
            if let ExprKind::Field(ref object, _) = e.kind;
            if let ExprKind::Paren(ref parened) = object.kind;
            if let ExprKind::AddrOf(_, _, ref inner) = parened.kind;
            then {
                let applicability = if inner.span.from_expansion() {
                    Applicability::MaybeIncorrect
                } else {
                    Applicability::MachineApplicable
                };
                let sugg = Sugg::ast(cx, inner, "_").maybe_par();
                span_lint_and_sugg(
                    cx,
                    REF_IN_DEREF,
                    object.span,
                    "creating a reference that is immediately dereferenced",
                    "try this",
                    sugg.to_string(),
                    applicability,
                );
            }
        }
    }
}
<|endoftext|>


<commit_before>use rustc::lint::*;
use rustc::ty::{self, Ty};
use rustc::hir::*;
use std::borrow::Cow;
use syntax::ast;
use utils::{last_path_segment, match_def_path, paths, snippet, span_lint, span_lint_and_then};
use utils::{sugg, opt_def_id};

/// **What it does:** Checks for transmutes that can't ever be correct on any
/// architecture.
///
/// **Why is this bad?** It's basically guaranteed to be undefined behaviour.
///
/// **Known problems:** When accessing C, users might want to store pointer
/// sized objects in `extradata` arguments to save an allocation.
///
/// **Example:**
/// ```rust
/// let ptr: *const T = core::intrinsics::transmute('x')`
/// ```
declare_lint! {
    pub WRONG_TRANSMUTE,
    Warn,
    "transmutes that are confusing at best, undefined behaviour at worst and always useless"
}

/// **What it does:** Checks for transmutes to the original type of the object
/// and transmutes that could be a cast.
///
/// **Why is this bad?** Readability. The code tricks people into thinking that
/// something complex is going on.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// core::intrinsics::transmute(t) // where the result type is the same as `t`'s
/// ```
declare_lint! {
    pub USELESS_TRANSMUTE,
    Warn,
    "transmutes that have the same to and from types or could be a cast/coercion"
}

/// **What it does:** Checks for transmutes between a type `T` and `*T`.
///
/// **Why is this bad?** It's easy to mistakenly transmute between a type and a
/// pointer to that type.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// core::intrinsics::transmute(t)` // where the result type is the same as
/// `*t` or `&t`'s
/// ```
declare_lint! {
    pub CROSSPOINTER_TRANSMUTE,
    Warn,
    "transmutes that have to or from types that are a pointer to the other"
}

/// **What it does:** Checks for transmutes from a pointer to a reference.
///
/// **Why is this bad?** This can always be rewritten with `&` and `*`.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let _: &T = std::mem::transmute(p); // where p: *const T
/// // can be written:
/// let _: &T = &*p;
/// ```
declare_lint! {
    pub TRANSMUTE_PTR_TO_REF,
    Warn,
    "transmutes from a pointer to a reference type"
}

/// **What it does:** Checks for transmutes from an integer to a `char`.
///
/// **Why is this bad?** Not every integer is a unicode scalar value.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let _: char = std::mem::transmute(x); // where x: u32
/// // should be:
/// let _ = std::char::from_u32(x).unwrap();
/// ```
declare_lint! {
    pub TRANSMUTE_INT_TO_CHAR,
    Warn,
    "transmutes from an integer to a `char`"
}

/// **What it does:** Checks for transmutes from a `&[u8] to a `&str`.
///
/// **Why is this bad?** Not every byte slice is a valid UTF-8 string.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let _: &str = std::mem::transmute(b); // where b: &[u8]
/// // should be:
/// let _ = std::str::from_utf8(b).unwrap();
/// ```
declare_lint! {
    pub TRANSMUTE_BYTES_TO_STR,
    Warn,
    "transmutes from a `&[u8]` to a `&str`"
}

/// **What it does:** Checks for transmutes from an integer to a `bool`.
///
/// **Why is this bad?** This might result in an invalid in-memory representation of a `bool`.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let _: bool = std::mem::transmute(x); // where x: u8
/// // should be:
/// let _: bool = x != 0;
/// ```
declare_lint! {
    pub TRANSMUTE_INT_TO_BOOL,
    Warn,
    "transmutes from an integer to a `bool`"
}

/// **What it does:** Checks for transmutes from an integer to a float.
///
/// **Why is this bad?** This might result in an invalid in-memory representation of a float.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let _: f32 = std::mem::transmute(x); // where x: u32
/// // should be:
/// let _: f32 = f32::from_bits(x);
/// ```
declare_lint! {
    pub TRANSMUTE_INT_TO_FLOAT,
    Warn,
    "transmutes from an integer to a float"
}

pub struct Transmute;

impl LintPass for Transmute {
    fn get_lints(&self) -> LintArray {
        lint_array!(
            CROSSPOINTER_TRANSMUTE,
            TRANSMUTE_PTR_TO_REF,
            USELESS_TRANSMUTE,
            WRONG_TRANSMUTE,
            TRANSMUTE_INT_TO_CHAR,
            TRANSMUTE_BYTES_TO_STR,
            TRANSMUTE_INT_TO_BOOL,
            TRANSMUTE_INT_TO_FLOAT
        )
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
        if let ExprCall(ref path_expr, ref args) = e.node {
            if let ExprPath(ref qpath) = path_expr.node {
                if let Some(def_id) = opt_def_id(cx.tables.qpath_def(qpath, path_expr.hir_id)) {

                    if match_def_path(cx.tcx, def_id, &paths::TRANSMUTE) {
                        let from_ty = cx.tables.expr_ty(&args[0]);
                        let to_ty = cx.tables.expr_ty(e);

                        match (&from_ty.sty, &to_ty.sty) {
                            _ if from_ty == to_ty => span_lint(
                                cx,
                                USELESS_TRANSMUTE,
                                e.span,
                                &format!("transmute from a type (`{}`) to itself", from_ty),
                            ),
                            (&ty::TyRef(_, rty), &ty::TyRawPtr(ptr_ty)) => span_lint_and_then(
                                cx,
                                USELESS_TRANSMUTE,
                                e.span,
                                "transmute from a reference to a pointer",
                                |db| if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
                                    let sugg = if ptr_ty == rty {
                                        arg.as_ty(to_ty)
                                    } else {
                                        arg.as_ty(cx.tcx.mk_ptr(rty)).as_ty(to_ty)
                                    };

                                    db.span_suggestion(e.span, "try", sugg.to_string());
                                },
                            ),
                            (&ty::TyInt(_), &ty::TyRawPtr(_)) | (&ty::TyUint(_), &ty::TyRawPtr(_)) => span_lint_and_then(
                                cx,
                                USELESS_TRANSMUTE,
                                e.span,
                                "transmute from an integer to a pointer",
                                |db| if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
                                    db.span_suggestion(e.span, "try", arg.as_ty(&to_ty.to_string()).to_string());
                                },
                            ),
                            (&ty::TyFloat(_), &ty::TyRef(..)) |
                            (&ty::TyFloat(_), &ty::TyRawPtr(_)) |
                            (&ty::TyChar, &ty::TyRef(..)) |
                            (&ty::TyChar, &ty::TyRawPtr(_)) => span_lint(
                                cx,
                                WRONG_TRANSMUTE,
                                e.span,
                                &format!("transmute from a `{}` to a pointer", from_ty),
                            ),
                            (&ty::TyRawPtr(from_ptr), _) if from_ptr.ty == to_ty => span_lint(
                                cx,
                                CROSSPOINTER_TRANSMUTE,
                                e.span,
                                &format!(
                                    "transmute from a type (`{}`) to the type that it points to (`{}`)",
                                    from_ty,
                                    to_ty
                                ),
                            ),
                            (_, &ty::TyRawPtr(to_ptr)) if to_ptr.ty == from_ty => span_lint(
                                cx,
                                CROSSPOINTER_TRANSMUTE,
                                e.span,
                                &format!("transmute from a type (`{}`) to a pointer to that type (`{}`)", from_ty, to_ty),
                            ),
                            (&ty::TyRawPtr(from_pty), &ty::TyRef(_, to_ref_ty)) => span_lint_and_then(
                                cx,
                                TRANSMUTE_PTR_TO_REF,
                                e.span,
                                &format!(
                                    "transmute from a pointer type (`{}`) to a reference type \
                                    (`{}`)",
                                    from_ty,
                                    to_ty
                                ),
                                |db| {
                                    let arg = sugg::Sugg::hir(cx, &args[0], "..");
                                    let (deref, cast) = if to_ref_ty.mutbl == Mutability::MutMutable {
                                        ("&mut *", "*mut")
                                    } else {
                                        ("&*", "*const")
                                    };

                                    let arg = if from_pty.ty == to_ref_ty.ty {
                                        arg
                                    } else {
                                        arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, to_ref_ty.ty)))
                                    };

                                    db.span_suggestion(e.span, "try", sugg::make_unop(deref, arg).to_string());
                                },
                            ),
                            (&ty::TyInt(ast::IntTy::I32), &ty::TyChar) |
                            (&ty::TyUint(ast::UintTy::U32), &ty::TyChar) => span_lint_and_then(
                                cx,
                                TRANSMUTE_INT_TO_CHAR,
                                e.span,
                                &format!("transmute from a `{}` to a `char`", from_ty),
                                |db| {
                                    let arg = sugg::Sugg::hir(cx, &args[0], "..");
                                    let arg = if let ty::TyInt(_) = from_ty.sty {
                                        arg.as_ty(ty::TyUint(ast::UintTy::U32))
                                    } else {
                                        arg
                                    };
                                    db.span_suggestion(e.span, "consider using", format!("std::char::from_u32({}).unwrap()", arg.to_string()));
                                }
                            ),
                            (&ty::TyRef(_, ref ref_from), &ty::TyRef(_, ref ref_to)) => {
                                if_chain! {
                                    if let (&ty::TySlice(slice_ty), &ty::TyStr) = (&ref_from.ty.sty, &ref_to.ty.sty);
                                    if let ty::TyUint(ast::UintTy::U8) = slice_ty.sty;
                                    if ref_from.mutbl == ref_to.mutbl;
                                    then {
                                        let postfix = if ref_from.mutbl == Mutability::MutMutable {
                                            "_mut"
                                        } else {
                                            ""
                                        };

                                        span_lint_and_then(
                                            cx,
                                            TRANSMUTE_BYTES_TO_STR,
                                            e.span,
                                            &format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
                                            |db| {
                                                db.span_suggestion(
                                                    e.span,
                                                    "consider using",
                                                    format!(
                                                        "std::str::from_utf8{}({}).unwrap()",
                                                        postfix,
                                                        snippet(cx, args[0].span, ".."),
                                                    ),
                                                );
                                            }
                                        )
                                    }
                                }
                            },
                            (&ty::TyInt(ast::IntTy::I8), &ty::TyBool) |
                            (&ty::TyUint(ast::UintTy::U8), &ty::TyBool) => span_lint_and_then(
                                cx,
                                TRANSMUTE_INT_TO_BOOL,
                                e.span,
                                &format!("transmute from a `{}` to a `bool`", from_ty),
                                |db| {
                                    let arg = sugg::Sugg::hir(cx, &args[0], "..");
                                    let zero = sugg::Sugg::NonParen(Cow::from("0"));
                                    db.span_suggestion(e.span, "consider using", sugg::make_binop(ast::BinOpKind::Ne, &arg, &zero).to_string());
                                }
                            ),
                            (&ty::TyInt(_), &ty::TyFloat(_)) |
                            (&ty::TyUint(_), &ty::TyFloat(_)) => span_lint_and_then(
                                cx,
                                TRANSMUTE_INT_TO_FLOAT,
                                e.span,
                                &format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
                                |db| {
                                    let arg = sugg::Sugg::hir(cx, &args[0], "..");
                                    let arg = if let ty::TyInt(int_ty) = from_ty.sty {
                                        arg.as_ty(format!("u{}", int_ty.bit_width().map_or_else(|| "size".to_string(), |v| v.to_string())))
                                    } else {
                                        arg
                                    };
                                    db.span_suggestion(e.span, "consider using", format!("{}::from_bits({})", to_ty, arg.to_string()));
                                }
                            ),
                            _ => return,
                        };
                    }
                }
            }
        }
    }
}

/// Get the snippet of `Bar` in `…::transmute<Foo, &Bar>`. If that snippet is
/// not available , use
/// the type's `ToString` implementation. In weird cases it could lead to types
/// with invalid `'_`
/// lifetime, but it should be rare.
fn get_type_snippet(cx: &LateContext, path: &QPath, to_ref_ty: Ty) -> String {
    let seg = last_path_segment(path);
    if_chain! {
        if let Some(ref params) = seg.parameters;
        if !params.parenthesized;
        if let Some(to_ty) = params.types.get(1);
        if let TyRptr(_, ref to_ty) = to_ty.node;
        then {
            return snippet(cx, to_ty.ty.span, &to_ref_ty.to_string()).to_string();
        }
    }

    to_ref_ty.to_string()
}
<commit_msg>Known problems<commit_after>use rustc::lint::*;
use rustc::ty::{self, Ty};
use rustc::hir::*;
use std::borrow::Cow;
use syntax::ast;
use utils::{last_path_segment, match_def_path, paths, snippet, span_lint, span_lint_and_then};
use utils::{sugg, opt_def_id};

/// **What it does:** Checks for transmutes that can't ever be correct on any
/// architecture.
///
/// **Why is this bad?** It's basically guaranteed to be undefined behaviour.
///
/// **Known problems:** When accessing C, users might want to store pointer
/// sized objects in `extradata` arguments to save an allocation.
///
/// **Example:**
/// ```rust
/// let ptr: *const T = core::intrinsics::transmute('x')`
/// ```
declare_lint! {
    pub WRONG_TRANSMUTE,
    Warn,
    "transmutes that are confusing at best, undefined behaviour at worst and always useless"
}

/// **What it does:** Checks for transmutes to the original type of the object
/// and transmutes that could be a cast.
///
/// **Why is this bad?** Readability. The code tricks people into thinking that
/// something complex is going on.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// core::intrinsics::transmute(t) // where the result type is the same as `t`'s
/// ```
declare_lint! {
    pub USELESS_TRANSMUTE,
    Warn,
    "transmutes that have the same to and from types or could be a cast/coercion"
}

/// **What it does:** Checks for transmutes between a type `T` and `*T`.
///
/// **Why is this bad?** It's easy to mistakenly transmute between a type and a
/// pointer to that type.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// core::intrinsics::transmute(t)` // where the result type is the same as
/// `*t` or `&t`'s
/// ```
declare_lint! {
    pub CROSSPOINTER_TRANSMUTE,
    Warn,
    "transmutes that have to or from types that are a pointer to the other"
}

/// **What it does:** Checks for transmutes from a pointer to a reference.
///
/// **Why is this bad?** This can always be rewritten with `&` and `*`.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let _: &T = std::mem::transmute(p); // where p: *const T
/// // can be written:
/// let _: &T = &*p;
/// ```
declare_lint! {
    pub TRANSMUTE_PTR_TO_REF,
    Warn,
    "transmutes from a pointer to a reference type"
}

/// **What it does:** Checks for transmutes from an integer to a `char`.
///
/// **Why is this bad?** Not every integer is a unicode scalar value.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let _: char = std::mem::transmute(x); // where x: u32
/// // should be:
/// let _ = std::char::from_u32(x).unwrap();
/// ```
declare_lint! {
    pub TRANSMUTE_INT_TO_CHAR,
    Warn,
    "transmutes from an integer to a `char`"
}

/// **What it does:** Checks for transmutes from a `&[u8]` to a `&str`.
///
/// **Why is this bad?** Not every byte slice is a valid UTF-8 string.
///
/// **Known problems:**
/// - [`from_utf8`] which this lint suggests using is slower than `transmute`
/// as it needs to validate the input.
/// If you are certain that the input is always a valid UTF-8,
/// use [`from_utf8_unchecked`] which is as fast as `transmute`
/// but has a semantically meaningful name.
/// - You might want to handle errors returned from [`from_utf8`] instead of calling `unwrap`.
///
/// [`from_utf8`]: https://doc.rust-lang.org/std/str/fn.from_utf8.html
/// [`from_utf8_unchecked`]: https://doc.rust-lang.org/std/str/fn.from_utf8_unchecked.html
///
/// **Example:**
/// ```rust
/// let _: &str = std::mem::transmute(b); // where b: &[u8]
/// // should be:
/// let _ = std::str::from_utf8(b).unwrap();
/// ```
declare_lint! {
    pub TRANSMUTE_BYTES_TO_STR,
    Warn,
    "transmutes from a `&[u8]` to a `&str`"
}

/// **What it does:** Checks for transmutes from an integer to a `bool`.
///
/// **Why is this bad?** This might result in an invalid in-memory representation of a `bool`.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let _: bool = std::mem::transmute(x); // where x: u8
/// // should be:
/// let _: bool = x != 0;
/// ```
declare_lint! {
    pub TRANSMUTE_INT_TO_BOOL,
    Warn,
    "transmutes from an integer to a `bool`"
}

/// **What it does:** Checks for transmutes from an integer to a float.
///
/// **Why is this bad?** This might result in an invalid in-memory representation of a float.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let _: f32 = std::mem::transmute(x); // where x: u32
/// // should be:
/// let _: f32 = f32::from_bits(x);
/// ```
declare_lint! {
    pub TRANSMUTE_INT_TO_FLOAT,
    Warn,
    "transmutes from an integer to a float"
}

pub struct Transmute;

impl LintPass for Transmute {
    fn get_lints(&self) -> LintArray {
        lint_array!(
            CROSSPOINTER_TRANSMUTE,
            TRANSMUTE_PTR_TO_REF,
            USELESS_TRANSMUTE,
            WRONG_TRANSMUTE,
            TRANSMUTE_INT_TO_CHAR,
            TRANSMUTE_BYTES_TO_STR,
            TRANSMUTE_INT_TO_BOOL,
            TRANSMUTE_INT_TO_FLOAT
        )
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
        if let ExprCall(ref path_expr, ref args) = e.node {
            if let ExprPath(ref qpath) = path_expr.node {
                if let Some(def_id) = opt_def_id(cx.tables.qpath_def(qpath, path_expr.hir_id)) {

                    if match_def_path(cx.tcx, def_id, &paths::TRANSMUTE) {
                        let from_ty = cx.tables.expr_ty(&args[0]);
                        let to_ty = cx.tables.expr_ty(e);

                        match (&from_ty.sty, &to_ty.sty) {
                            _ if from_ty == to_ty => span_lint(
                                cx,
                                USELESS_TRANSMUTE,
                                e.span,
                                &format!("transmute from a type (`{}`) to itself", from_ty),
                            ),
                            (&ty::TyRef(_, rty), &ty::TyRawPtr(ptr_ty)) => span_lint_and_then(
                                cx,
                                USELESS_TRANSMUTE,
                                e.span,
                                "transmute from a reference to a pointer",
                                |db| if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
                                    let sugg = if ptr_ty == rty {
                                        arg.as_ty(to_ty)
                                    } else {
                                        arg.as_ty(cx.tcx.mk_ptr(rty)).as_ty(to_ty)
                                    };

                                    db.span_suggestion(e.span, "try", sugg.to_string());
                                },
                            ),
                            (&ty::TyInt(_), &ty::TyRawPtr(_)) | (&ty::TyUint(_), &ty::TyRawPtr(_)) => span_lint_and_then(
                                cx,
                                USELESS_TRANSMUTE,
                                e.span,
                                "transmute from an integer to a pointer",
                                |db| if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
                                    db.span_suggestion(e.span, "try", arg.as_ty(&to_ty.to_string()).to_string());
                                },
                            ),
                            (&ty::TyFloat(_), &ty::TyRef(..)) |
                            (&ty::TyFloat(_), &ty::TyRawPtr(_)) |
                            (&ty::TyChar, &ty::TyRef(..)) |
                            (&ty::TyChar, &ty::TyRawPtr(_)) => span_lint(
                                cx,
                                WRONG_TRANSMUTE,
                                e.span,
                                &format!("transmute from a `{}` to a pointer", from_ty),
                            ),
                            (&ty::TyRawPtr(from_ptr), _) if from_ptr.ty == to_ty => span_lint(
                                cx,
                                CROSSPOINTER_TRANSMUTE,
                                e.span,
                                &format!(
                                    "transmute from a type (`{}`) to the type that it points to (`{}`)",
                                    from_ty,
                                    to_ty
                                ),
                            ),
                            (_, &ty::TyRawPtr(to_ptr)) if to_ptr.ty == from_ty => span_lint(
                                cx,
                                CROSSPOINTER_TRANSMUTE,
                                e.span,
                                &format!("transmute from a type (`{}`) to a pointer to that type (`{}`)", from_ty, to_ty),
                            ),
                            (&ty::TyRawPtr(from_pty), &ty::TyRef(_, to_ref_ty)) => span_lint_and_then(
                                cx,
                                TRANSMUTE_PTR_TO_REF,
                                e.span,
                                &format!(
                                    "transmute from a pointer type (`{}`) to a reference type \
                                    (`{}`)",
                                    from_ty,
                                    to_ty
                                ),
                                |db| {
                                    let arg = sugg::Sugg::hir(cx, &args[0], "..");
                                    let (deref, cast) = if to_ref_ty.mutbl == Mutability::MutMutable {
                                        ("&mut *", "*mut")
                                    } else {
                                        ("&*", "*const")
                                    };

                                    let arg = if from_pty.ty == to_ref_ty.ty {
                                        arg
                                    } else {
                                        arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, to_ref_ty.ty)))
                                    };

                                    db.span_suggestion(e.span, "try", sugg::make_unop(deref, arg).to_string());
                                },
                            ),
                            (&ty::TyInt(ast::IntTy::I32), &ty::TyChar) |
                            (&ty::TyUint(ast::UintTy::U32), &ty::TyChar) => span_lint_and_then(
                                cx,
                                TRANSMUTE_INT_TO_CHAR,
                                e.span,
                                &format!("transmute from a `{}` to a `char`", from_ty),
                                |db| {
                                    let arg = sugg::Sugg::hir(cx, &args[0], "..");
                                    let arg = if let ty::TyInt(_) = from_ty.sty {
                                        arg.as_ty(ty::TyUint(ast::UintTy::U32))
                                    } else {
                                        arg
                                    };
                                    db.span_suggestion(e.span, "consider using", format!("std::char::from_u32({}).unwrap()", arg.to_string()));
                                }
                            ),
                            (&ty::TyRef(_, ref ref_from), &ty::TyRef(_, ref ref_to)) => {
                                if_chain! {
                                    if let (&ty::TySlice(slice_ty), &ty::TyStr) = (&ref_from.ty.sty, &ref_to.ty.sty);
                                    if let ty::TyUint(ast::UintTy::U8) = slice_ty.sty;
                                    if ref_from.mutbl == ref_to.mutbl;
                                    then {
                                        let postfix = if ref_from.mutbl == Mutability::MutMutable {
                                            "_mut"
                                        } else {
                                            ""
                                        };

                                        span_lint_and_then(
                                            cx,
                                            TRANSMUTE_BYTES_TO_STR,
                                            e.span,
                                            &format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
                                            |db| {
                                                db.span_suggestion(
                                                    e.span,
                                                    "consider using",
                                                    format!(
                                                        "std::str::from_utf8{}({}).unwrap()",
                                                        postfix,
                                                        snippet(cx, args[0].span, ".."),
                                                    ),
                                                );
                                            }
                                        )
                                    }
                                }
                            },
                            (&ty::TyInt(ast::IntTy::I8), &ty::TyBool) |
                            (&ty::TyUint(ast::UintTy::U8), &ty::TyBool) => span_lint_and_then(
                                cx,
                                TRANSMUTE_INT_TO_BOOL,
                                e.span,
                                &format!("transmute from a `{}` to a `bool`", from_ty),
                                |db| {
                                    let arg = sugg::Sugg::hir(cx, &args[0], "..");
                                    let zero = sugg::Sugg::NonParen(Cow::from("0"));
                                    db.span_suggestion(e.span, "consider using", sugg::make_binop(ast::BinOpKind::Ne, &arg, &zero).to_string());
                                }
                            ),
                            (&ty::TyInt(_), &ty::TyFloat(_)) |
                            (&ty::TyUint(_), &ty::TyFloat(_)) => span_lint_and_then(
                                cx,
                                TRANSMUTE_INT_TO_FLOAT,
                                e.span,
                                &format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
                                |db| {
                                    let arg = sugg::Sugg::hir(cx, &args[0], "..");
                                    let arg = if let ty::TyInt(int_ty) = from_ty.sty {
                                        arg.as_ty(format!("u{}", int_ty.bit_width().map_or_else(|| "size".to_string(), |v| v.to_string())))
                                    } else {
                                        arg
                                    };
                                    db.span_suggestion(e.span, "consider using", format!("{}::from_bits({})", to_ty, arg.to_string()));
                                }
                            ),
                            _ => return,
                        };
                    }
                }
            }
        }
    }
}

/// Get the snippet of `Bar` in `…::transmute<Foo, &Bar>`. If that snippet is
/// not available , use
/// the type's `ToString` implementation. In weird cases it could lead to types
/// with invalid `'_`
/// lifetime, but it should be rare.
fn get_type_snippet(cx: &LateContext, path: &QPath, to_ref_ty: Ty) -> String {
    let seg = last_path_segment(path);
    if_chain! {
        if let Some(ref params) = seg.parameters;
        if !params.parenthesized;
        if let Some(to_ty) = params.types.get(1);
        if let TyRptr(_, ref to_ty) = to_ty.node;
        then {
            return snippet(cx, to_ty.ty.span, &to_ref_ty.to_string()).to_string();
        }
    }

    to_ref_ty.to_string()
}
<|endoftext|>


<commit_before>use funcs::*;
use env::*;
use atom::*;
use errors::*;
use errors::Error::*;

fn eval_procedure(p: &Procedure, args: &[Atom], env: &mut Env) ->  AtomResult {
    try!(env.apply(&p.params, &args[1..]));

    let res = p.body.iter()
        .map(|node| eval(&node, env))
        .last().unwrap_or(Err(InvalidArguments));

    env.pop();

    res
}

fn eval_macro(p: &Procedure, args: &[Atom], env: &mut Env) -> AtomResult {
    //println!("about to eval: {:?}\n with args {:?}", p, args);
    try!(env.apply(&p.params, args));
    let res = p.body.iter()
        .map(|node| {
            //println!("res: {}", node);
            eval(&node, env)
        })
        .last().unwrap_or(Err(InvalidArguments));

    //println!("macro eval res: {:?}", res);
    env.pop();

    res
}

fn define(args: &[Atom], env: &mut Env) -> AtomResult {
    if args.len() != 2 {
        return Err(Error::InvalidArguments);
    }

    if let Atom::Symbol(ref key) = args[0] {
        let value = try!(eval(&args[1], env));
        env.set(key.clone(), value);
        Ok(Atom::Nil)
    } else {
        Err(Error::InvalidArguments)
    }
}

fn quote(list: &[Atom]) -> AtomResult {
    list.first().map(|atom| atom.clone()).ok_or(InvalidArguments)
}

fn collect_body(l: &[Atom]) -> Vec<Atom> {
    l.iter().skip(2).map(|x| x.clone()).collect()
}

fn macroexpand(list: &[Atom], env: &Env) -> AtomResult {
    Err(NotImplemented)
}

fn do_form(list: &[Atom], env: &mut Env) -> AtomResult {
    list.iter()
        .map(|node| eval(&node, env))
        .last().unwrap_or(Err(InvalidArguments))
}

fn make_proc(list: &[Atom]) -> AtomResult {
    if let Atom::List(ref p) = list[1] {
        Ok(Atom::Function(Function::Proc(Procedure{ params: p.clone(),
                                                    body: collect_body(list)})))
    } else {
        Err(InvalidArguments)
    }
}

fn macro_form(params: &Atom, list: Vec<Atom>) -> AtomResult {
    if let Atom::List(ref p) = *params {
        // println!("args: {:?}", p);
        // println!("body: {:?}", list);

        Ok(Atom::Function(Function::Macro(Procedure{ params: p.clone(),
                                                     body: list})))
    } else {
        Err(InvalidArguments)
    }
}

fn if_form(list: &[Atom], env: &mut Env) -> AtomResult {
    if list.len() < 2 {
        Err(InvalidArguments)
    } else {
        eval(&list[0], env)
            .and_then(|p| {
                let truthy = match p {
                    Atom::Boolean(b) => b,
                    Atom::Nil => false,
                    _ => true
                };
                if truthy {
                    eval(&list[1], env)
                } else if list.len() > 2 {
                    eval(&list[2], env)
                } else {
                    Ok(Atom::Boolean(false))
                }
            })
    }
}

fn eval_special_forms(f: Form, list: &[Atom], env: &mut Env) -> AtomResult {
    use atom::Form::*;
    match f {
        Def => {
            define(&list[1..], env)
        },
        Do => {
            do_form(&list[1..], env)
        },
        Macro => {
            panic!("ds");
            //macro_form(list)
        }
        Fn => {
            make_proc(&list)
        },
        Quote => {
            quote(&list[1..])
        },
        If => {
            if_form(&list[1..], env)
        },
        MacroExpand => {
            macroexpand(&list[1..], env)
        },
        _ => Err(InvalidArguments)
    }
}

fn eval_node(atom: &Atom, list: &[Atom], env: &mut Env) -> AtomResult {
    match *atom {
        Atom::Form(Form::QuasiQuote) => Ok(Atom::List(list.iter().map(|n| n.clone()).collect())),
        Atom::Symbol(_) =>  {
            let args: Vec<Atom> = try!(list.iter().map(|n| eval(n, env)).collect());
            eval_node(&args[0], &args, env)
        },
        Atom::Form(f) => eval_special_forms(f, list, env),
        Atom::Function(ref func) => {
            match *func {
                Function::Proc(ref p) => {
                    eval_procedure(p, list, env)
                }
                Function::Native(native) => {
                    eval_native(native, &list[1..], env)
                },
                Function::Macro(ref m) => {
                    eval_macro(m, &list[1..], env)
                        .and_then(|n| eval(&n, env))
                }
            }
        }
        _ => {
            Err(Error::NotAFunction)
        }
    }
}

pub fn eval(node: &Atom, env: &mut Env) -> Result<Atom, Error> {
    //println!("eval: {}", node);
    match *node {
        Atom::List(ref list) if !list.is_empty() => {
            eval(&list[0], env).and_then(|first| eval_node(&first, list, env))
        },
        Atom::Symbol(ref s) => {
            Ok(env.get(s).unwrap_or(Atom::Nil))
        }
        _ => Ok(node.clone())
    }
}

fn expand_quasiquote(atom: &Atom) -> AtomResult {
    //println!("expand_quasiquote: {}", atom);
    if !atom.is_pair() { // ()
        return Ok(Atom::List(vec![Atom::Form(Form::Quote), atom.clone()]))
    }

    if let Atom::List(ref list) = *atom {
        if let Atom::Form(Form::Unquote) = list[0] {
            // ~x
            return Ok(list[1].clone())
        }

        if let Atom::List(ref sublist) = list[0] {
            // (())
            println!("WARNING I HOPE WE DONT GET HERE");
            match sublist[0] {
                Atom::Form(Form::Splice) => {
                    // ((splice x) y)
                    let mut v = vec![];
                    v.push(sublist[1].clone());
                    for i in list.iter().skip(1) {
                        v.push(i.clone())
                    }
                    return Ok(Atom::List(v))
                }
                _ => ()
            }
        }
        let v = Atom::List(try!(list.iter().map(|n| expand_quasiquote(&n)).collect()));
        //println!("expanded list: {}", v);
        return Ok(v)
    } else {
        println!("arg");
        Err(Error::Parser)
    }
}

pub fn expand(node: &Atom, env: &mut Env, depth: i32) -> AtomResult {
    match *node {
        Atom::List(ref list) => {
            list.first().ok_or(Error::Parser)
                .and_then(|n| expand(n, env, depth + 1))
                .and_then(|n| expand_node(&n, &list[0..], env, depth + 1))
        },
        Atom::Symbol(ref sym) => {
            Ok(env.get(sym).unwrap_or(Atom::Nil))
        },
        _ => Ok(node.clone())
    }
}

pub fn expand_node(node: &Atom, list: &[Atom], env: &mut Env, depth: i32) -> AtomResult {
    match *node {
        Atom::Form(Form::Quote) => {
            Ok(Atom::List(list.iter().map(|n| n.clone()).collect()))
        }
        Atom::Form(Form::Unquote) => {
            //println!("unquote: {}", node);
            Ok(node.clone())
        },
        Atom::Form(Form::Macro) => {
            let exps: Vec<Atom> = try!(list.iter().skip(3).map(|n| expand(&n, env, depth + 1)).collect());
            let m = try!(macro_form(&list[2], exps));

            if let Atom::Symbol(ref k) = list[1] {
                env.set(k.clone(), m);
                Ok(Atom::Nil)
            } else {
                Err(Error::InvalidArguments)
            }
        },
        Atom::Function(ref func) => {
            match *func {
                Function::Macro(ref m) => {
                    // println!("eval_macro");
                    eval_macro(m, &list[1..], env).and_then(|a| {
                        // println!("and_then expand: {}", a);
                        expand(&a, env, depth + 1)})
                },
                _ => {
                    Ok(Atom::List(try!(list.iter().map(|n| expand(n, env, depth + 1)).collect())))
                }
            }
        },
        Atom::Form(Form::QuasiQuote) => {
            expand_quasiquote(&list[1])
        }
        _ => {
            Ok(Atom::List(try!(list.iter().map(|n| expand(n, env, depth + 1)).collect())))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use parser::*;
    use atom::*;
    use env::Env;

    #[test]
    fn if_special_form() {
        let x = eval(&tokenize("(if (= 1 1) true false)").unwrap(), &mut Env::new()).unwrap();

        assert_eq!(Atom::from(true), x);
    }

    #[test]
    fn if_special_form_false() {
        let x = eval(&tokenize("(if (= 1 2) true false)").unwrap(), &mut Env::new()).unwrap();

        assert_eq!(Atom::Boolean(false), x);
    }

    #[test]
    fn if_no_else() {
        let x = teval("(if (= 1 1) true)");

        assert_eq!(Atom::Boolean(true), x);
    }

    fn tassert(v: &str) {
        assert_eq!(Atom::Boolean(true), teval(v));
    }

    fn trefute(v: &str) {
        assert_eq!(Atom::Boolean(false), teval(v));
    }

    #[test]
    fn comparisons() {
        tassert("(= 1 1 1 1)");
        trefute("(= 1 0 1 1)");
        tassert("(< 1 5 10)");
        trefute("(< 5 1 20)");
        tassert("(<= 1 1 1 5)");
        trefute("(<= 5 2 1 5)");
        tassert("(> 5 3 2 1)");
        trefute("(> 5 3 2 10)");
        tassert("(>= 5 5 5 3)");
        trefute("(>= 5 5 5 10)");
    }
    use number::Number;

    fn num(i: i64) -> Atom {
        Atom::Number(Number::Integer(i))
    }

    #[test]
    fn adds() {
        assert_eq!(num(5), teval("(+ 2 3)"));
        assert_eq!(num(25), teval("(+ 5 5 5 5 5)"));
    }

    #[test]
    fn subs() {
        assert_eq!(num(0), teval("(- 5 5)"));
        assert_eq!(num(5), teval("(- 20 10 5)"));
    }

    #[test]
    fn muls() {
        assert_eq!(num(0), teval("(* 5 5 5 0)"));
        assert_eq!(num(5), teval("(* 1 5)"));
        assert_eq!(num(-5), teval("(* -1 5)"));
    }

    fn teval(s: &str) -> Atom {
        let mut env = Env::new();
        tokenize(s).and_then(|n| expand(&n, &mut env, 0))
            .and_then(|n| eval(&n, &mut env)).unwrap()
    }

    fn teval_env(s: &str, env: &mut Env) -> AtomResult {
        tokenize(s).and_then(|n| expand(&n, env, 0))
            .and_then(|n| eval(&n, env))
        //eval(&tokenize(s).unwrap(), env)
    }

    #[test]
    fn symbol_resolving() {
        let mut env = Env::new();

        teval_env("(def foo 5)", &mut env).unwrap();

        assert_eq!(num(0), teval_env("(- foo 5)", &mut env).unwrap());
    }

    #[test]
    fn no_def_func() {
        assert_eq!(teval("3"), teval("((fn (a b) (+ a b)) 1 2)"));
    }

    // #[test]
    // fn first_and_rest() {
    //     assert_eq!(num(0), teval("(first (list 0 1 2))"));
    //     assert_eq!(teval("(list 1 2)"), teval("(rest (list 0 1 2))"));
    // }

    #[test]
    fn simple_func() {
        let mut env = Env::new();
        let _ = eval(&tokenize("(def f (fn (r b) (+ r b)))").unwrap(), &mut env).unwrap();
        let res = eval(&tokenize("(f 2 3)").unwrap(), &mut env).unwrap();

        assert_eq!(num(5), res);
    }

    #[test]
    fn quote_func() {
        assert_eq!(teval("'(1 2)"), teval("(quote (1 2))"));
        assert_eq!(teval("'a"), teval("(quote a)"));
    }

    #[test]
    fn do_func() {
        assert_eq!(teval("4"), teval("(do (+ 1 2) (+ 2 2))"));
    }

    // #[test]
    //  fn simple_macro() {
    //      let mut env = Env::new();
    //      teval_env("(defmacro m () '(+ 3 5))", &mut env).unwrap();
    //      assert_eq!(teval("'(+ 3 5)"), teval_env("(macroexpand '(m))", &mut env).unwrap());

    //      teval_env("(def mm (macro () (+ 3 5)))", &mut env).unwrap();
    //      assert_eq!(teval("8"), teval_env("(macroexpand '(mm))", &mut env).unwrap());

    //      assert_eq!(teval("8"), teval_env("(m)", &mut env).unwrap());
    //      assert_eq!(teval("8"), teval_env("(mm)", &mut env).unwrap());
    //  }

    #[test]
    fn more_complex_macro() {
        let mut env = Env::new();
        teval_env("(defmacro unless (p a b) `(if (not ~p) ~a ~b))", &mut env).unwrap();
        assert_eq!(teval("3"), teval_env("(unless false 3 5)", &mut env).unwrap());
    }
}
<commit_msg>tests passing<commit_after>use funcs::*;
use env::*;
use atom::*;
use errors::*;
use errors::Error::*;

fn eval_procedure(p: &Procedure, args: &[Atom], env: &mut Env) ->  AtomResult {
    try!(env.apply(&p.params, &args[1..]));

    let res = p.body.iter()
        .map(|node| eval(&node, env))
        .last().unwrap_or(Err(InvalidArguments));

    env.pop();

    res
}

fn eval_macro(p: &Procedure, args: &[Atom], env: &mut Env) -> AtomResult {
    try!(env.apply(&p.params, args));
    let res = p.body.iter()
        .map(|node| eval(&node, env))
        .last().unwrap_or(Err(InvalidArguments));

    env.pop();

    res
}

fn define(args: &[Atom], env: &mut Env) -> AtomResult {
    if args.len() != 2 {
        return Err(Error::InvalidArguments);
    }

    if let Atom::Symbol(ref key) = args[0] {
        let value = try!(eval(&args[1], env));
        env.set(key.clone(), value);
        Ok(Atom::Nil)
    } else {
        Err(Error::InvalidArguments)
    }
}

fn quote(list: &[Atom]) -> AtomResult {
    list.first().map(|atom| atom.clone()).ok_or(InvalidArguments)
}

fn collect_body(l: &[Atom]) -> Vec<Atom> {
    l.iter().skip(2).map(|x| x.clone()).collect()
}

fn macroexpand(list: &[Atom], env: &Env) -> AtomResult {
    Err(NotImplemented)
}

fn do_form(list: &[Atom], env: &mut Env) -> AtomResult {
    list.iter()
        .map(|node| eval(&node, env))
        .last().unwrap_or(Err(InvalidArguments))
}

fn make_proc(list: &[Atom]) -> AtomResult {
    if let Atom::List(ref p) = list[1] {
        Ok(Atom::Function(Function::Proc(Procedure{ params: p.clone(),
                                                    body: collect_body(list)})))
    } else {
        Err(InvalidArguments)
    }
}

fn macro_form(params: &Atom, list: Vec<Atom>) -> AtomResult {
    if let Atom::List(ref p) = *params {
        Ok(Atom::Function(Function::Macro(Procedure{ params: p.clone(),
                                                     body: list})))
    } else {
        Err(InvalidArguments)
    }
}

fn if_form(list: &[Atom], env: &mut Env) -> AtomResult {
    if list.len() < 2 {
        Err(InvalidArguments)
    } else {
        eval(&list[0], env)
            .and_then(|p| {
                let truthy = match p {
                    Atom::Boolean(b) => b,
                    Atom::Nil => false,
                    _ => true
                };
                if truthy {
                    eval(&list[1], env)
                } else if list.len() > 2 {
                    eval(&list[2], env)
                } else {
                    Ok(Atom::Boolean(false))
                }
            })
    }
}

fn eval_special_forms(f: Form, list: &[Atom], env: &mut Env) -> AtomResult {
    use atom::Form::*;
    match f {
        Def => {
            define(&list[1..], env)
        },
        Do => {
            do_form(&list[1..], env)
        },
        Macro => {
            unreachable!();
        }
        Fn => {
            make_proc(&list)
        },
        Quote => {
            quote(&list[1..])
        },
        If => {
            if_form(&list[1..], env)
        },
        MacroExpand => {
            macroexpand(&list[1..], env)
        },
        _ => Err(InvalidArguments)
    }
}

fn eval_node(atom: &Atom, list: &[Atom], env: &mut Env) -> AtomResult {
    match *atom {
        Atom::Form(Form::QuasiQuote) => Ok(Atom::List(list.iter().map(|n| n.clone()).collect())),
        Atom::Symbol(_) =>  {
            let args: Vec<Atom> = try!(list.iter().map(|n| eval(n, env)).collect());
            eval_node(&args[0], &args, env)
        },
        Atom::Form(f) => eval_special_forms(f, list, env),
        Atom::Function(ref func) => {
            match *func {
                Function::Proc(ref p) => {
                    eval_procedure(p, list, env)
                }
                Function::Native(native) => {
                    eval_native(native, &list[1..], env)
                },
                Function::Macro(ref m) => {
                    eval_macro(m, &list[1..], env)
                        .and_then(|n| eval(&n, env))
                }
            }
        }
        _ => {
            Err(Error::NotAFunction)
        }
    }
}

pub fn eval(node: &Atom, env: &mut Env) -> Result<Atom, Error> {
    match *node {
        Atom::List(ref list) if !list.is_empty() => {
            eval(&list[0], env).and_then(|first| eval_node(&first, list, env))
        },
        Atom::Symbol(ref s) => {
            Ok(env.get(s).unwrap_or(Atom::Nil))
        }
        _ => Ok(node.clone())
    }
}

fn expand_quasiquote(atom: &Atom) -> AtomResult {
    if !atom.is_pair() { // ()
        return Ok(Atom::List(vec![Atom::Form(Form::Quote), atom.clone()]))
    }

    if let Atom::List(ref list) = *atom {
        if let Atom::Form(Form::Unquote) = list[0] {
            // ~x
            return Ok(list[1].clone())
        }

        if let Atom::List(ref sublist) = list[0] {
            // (())
            println!("WARNING I HOPE WE DONT GET HERE");
            match sublist[0] {
                Atom::Form(Form::Splice) => {
                    // ((splice x) y)
                    let mut v = vec![];
                    v.push(sublist[1].clone());
                    for i in list.iter().skip(1) {
                        v.push(i.clone())
                    }
                    return Ok(Atom::List(v))
                }
                _ => ()
            }
        }
        let v = Atom::List(try!(list.iter().map(|n| expand_quasiquote(&n)).collect()));
        return Ok(v)
    } else {
        Err(Error::Parser)
    }
}

pub fn expand(node: &Atom, env: &mut Env, depth: i32) -> AtomResult {
    match *node {
        Atom::List(ref list) => {
            list.first().ok_or(Error::Parser)
                .and_then(|n| expand(n, env, depth + 1))
                .and_then(|n| expand_node(&n, &list[0..], env, depth + 1))
        },
        Atom::Symbol(ref sym) => {
            Ok(env.get(sym).unwrap_or(node.clone()))
        },
        _ => Ok(node.clone())
    }
}

pub fn expand_node(node: &Atom, list: &[Atom], env: &mut Env, depth: i32) -> AtomResult {
    match *node {
        Atom::Form(Form::Quote) => {
            Ok(Atom::List(list.iter().map(|n| n.clone()).collect()))
        }
        Atom::Form(Form::Unquote) => {
            Ok(node.clone())
        },
        Atom::Form(Form::Macro) => {
            let exps: Vec<Atom> = try!(list.iter().skip(3).map(|n| expand(&n, env, depth + 1)).collect());
            let m = try!(macro_form(&list[2], exps));

            if let Atom::Symbol(ref k) = list[1] {
                env.set(k.clone(), m);
                Ok(Atom::Nil)
            } else {
                Err(Error::InvalidArguments)
            }
        },
        Atom::Function(ref func) => {
            match *func {
                Function::Macro(ref m) => {
                    eval_macro(m, &list[1..], env).and_then(|a| expand(&a, env, depth + 1))
                },
                _ => {
                    Ok(Atom::List(try!(list.iter().map(|n| expand(n, env, depth + 1)).collect())))
                }
            }
        },
        Atom::Form(Form::QuasiQuote) => {
            expand_quasiquote(&list[1])
        }
        _ => {
            Ok(Atom::List(try!(list.iter().map(|n| expand(n, env, depth + 1)).collect())))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use parser::*;
    use atom::*;
    use env::Env;

    #[test]
    fn if_special_form() {
        let x = eval(&tokenize("(if (= 1 1) true false)").unwrap(), &mut Env::new()).unwrap();

        assert_eq!(Atom::from(true), x);
    }

    #[test]
    fn if_special_form_false() {
        let x = eval(&tokenize("(if (= 1 2) true false)").unwrap(), &mut Env::new()).unwrap();

        assert_eq!(Atom::Boolean(false), x);
    }

    #[test]
    fn if_no_else() {
        let x = teval("(if (= 1 1) true)");

        assert_eq!(Atom::Boolean(true), x);
    }

    fn tassert(v: &str) {
        assert_eq!(Atom::Boolean(true), teval(v));
    }

    fn trefute(v: &str) {
        assert_eq!(Atom::Boolean(false), teval(v));
    }

    #[test]
    fn comparisons() {
        tassert("(= 1 1 1 1)");
        trefute("(= 1 0 1 1)");
        tassert("(< 1 5 10)");
        trefute("(< 5 1 20)");
        tassert("(<= 1 1 1 5)");
        trefute("(<= 5 2 1 5)");
        tassert("(> 5 3 2 1)");
        trefute("(> 5 3 2 10)");
        tassert("(>= 5 5 5 3)");
        trefute("(>= 5 5 5 10)");
    }
    use number::Number;

    fn num(i: i64) -> Atom {
        Atom::Number(Number::Integer(i))
    }

    #[test]
    fn adds() {
        assert_eq!(num(5), teval("(+ 2 3)"));
        assert_eq!(num(25), teval("(+ 5 5 5 5 5)"));
    }

    #[test]
    fn subs() {
        assert_eq!(num(0), teval("(- 5 5)"));
        assert_eq!(num(5), teval("(- 20 10 5)"));
    }

    #[test]
    fn muls() {
        assert_eq!(num(0), teval("(* 5 5 5 0)"));
        assert_eq!(num(5), teval("(* 1 5)"));
        assert_eq!(num(-5), teval("(* -1 5)"));
    }

    fn teval(s: &str) -> Atom {
        let mut env = Env::new();
        tokenize(s).and_then(|n| expand(&n, &mut env, 0))
            .and_then(|n| eval(&n, &mut env)).unwrap()
    }

    fn teval_env(s: &str, env: &mut Env) -> AtomResult {
        tokenize(s).and_then(|n| expand(&n, env, 0))
            .and_then(|n| eval(&n, env))
        //eval(&tokenize(s).unwrap(), env)
    }

    #[test]
    fn symbol_resolving() {
        let mut env = Env::new();

        teval_env("(def foo 5)", &mut env).unwrap();

        assert_eq!(num(0), teval_env("(- foo 5)", &mut env).unwrap());
    }

    #[test]
    fn no_def_func() {
        assert_eq!(teval("3"), teval("((fn (a b) (+ a b)) 1 2)"));
    }

    // #[test]
    // fn first_and_rest() {
    //     assert_eq!(num(0), teval("(first (list 0 1 2))"));
    //     assert_eq!(teval("(list 1 2)"), teval("(rest (list 0 1 2))"));
    // }

    #[test]
    fn simple_func() {
        let mut env = Env::new();
        let _ = eval(&tokenize("(def f (fn (r b) (+ r b)))").unwrap(), &mut env).unwrap();
        let res = eval(&tokenize("(f 2 3)").unwrap(), &mut env).unwrap();

        assert_eq!(num(5), res);
    }

    #[test]
    fn quote_func() {
        assert_eq!(teval("'(1 2)"), teval("(quote (1 2))"));
        assert_eq!(teval("'a"), teval("(quote a)"));
    }

    #[test]
    fn do_func() {
        assert_eq!(teval("4"), teval("(do (+ 1 2) (+ 2 2))"));
    }

    // #[test]
    //  fn simple_macro() {
    //      let mut env = Env::new();
    //      teval_env("(defmacro m () '(+ 3 5))", &mut env).unwrap();
    //      assert_eq!(teval("'(+ 3 5)"), teval_env("(macroexpand '(m))", &mut env).unwrap());

    //      teval_env("(def mm (macro () (+ 3 5)))", &mut env).unwrap();
    //      assert_eq!(teval("8"), teval_env("(macroexpand '(mm))", &mut env).unwrap());

    //      assert_eq!(teval("8"), teval_env("(m)", &mut env).unwrap());
    //      assert_eq!(teval("8"), teval_env("(mm)", &mut env).unwrap());
    //  }

    #[test]
    fn more_complex_macro() {
        let mut env = Env::new();
        teval_env("(defmacro unless (p a b) `(if (not ~p) ~a ~b))", &mut env).unwrap();
        assert_eq!(teval("3"), teval_env("(unless false 3 5)", &mut env).unwrap());
    }
}
<|endoftext|>


<commit_before>/// from: https://raw.githubusercontent.com/chriskrycho/newrustacean.com/master/src/e002.rs

/// This struct is simple but useful to see how borrowing and moving work.
#[derive(Debug)]
pub struct Circle {
    /// X position of the circle's origin.
    pub x: f64,
    /// Y position of the circle's origin
    pub y: f64,
    /// Radius of the circle
    pub r: f64,

    pub w: Boxx,
}

#[derive(Debug)]
pub struct Boxx {
    pub x: f64,
    pub y: f64,
}

//const nil:Circle= Circle{x:1,y:2,r:3,w:nul};
//const nul:Circle= Circle{x:1,y:2,r:3,w:nil};

impl Circle {
    /// Creates a `Circle` instance centered on the "origin" (x = 0, y = 0).
    fn origin() -> Circle {
        Circle {
            x: 1.2,
            y: 3.4,
            r: 5.6,
            w: Boxx{x:1.0, y:2.0},
        }
    }

    pub fn by_take(self) -> f64 {
        println!("Taking ownership, not just borrowing a reference. INTENSE.");
        self.x
    }
}

fn main() {
    // Constructors can have default behavior, like this one.
    let immutable = Circle::origin();

    let by_take_x = immutable.by_take();//TODO: what happened with y,z? or even x? they got destroyed? so the return is a copy/clone then? since self.x is returned!
    println!("By take: {}", by_take_x);
    //println!("Does the circle still exist? {:?}", immutable);//no
}

<commit_msg>interesting Drop trait prevents move of any fields<commit_after>/// from: https://raw.githubusercontent.com/chriskrycho/newrustacean.com/master/src/e002.rs

/// This struct is simple but useful to see how borrowing and moving work.
#[derive(Debug)]
pub struct Circle {
    /// X position of the circle's origin.
    pub x: f64,
    /// Y position of the circle's origin
    pub y: f64,
    /// Radius of the circle
    pub r: f64,

    pub w: Boxx,
}

#[derive(Debug)]
pub struct Boxx {
    pub x: f64,
    pub y: f64,
}

//const nil:Circle= Circle{x:1,y:2,r:3,w:nul};
//const nul:Circle= Circle{x:1,y:2,r:3,w:nil};

impl Drop for Circle {
    fn drop(&mut self) {
        println!("Dropping! {:?}",self);
    }
}

impl Circle {
    /// Creates a `Circle` instance centered on the "origin" (x = 0, y = 0).
    fn origin() -> Circle {
        Circle {
            x: 1.2,
            y: 3.4,
            r: 5.6,
            w: Boxx{x:1.0, y:2.0},
        }
    }

    pub fn by_borrow_x(&self) -> &f64 {
        println!("Taking ownership, not just borrowing a reference. INTENSE.");
        &self.x
    }

    pub fn by_take(self) -> Boxx {
        println!("Taking ownership, not just borrowing a reference. INTENSE.");
        self.w  //it is moved out... unless defined the trait Drop on the type Circle!
    }

 /*   pub fn by_take2(self) -> f64 {
        println!("Taking ownership, not just borrowing a reference. INTENSE.");
        self.x
    }*/

    pub fn by_borrow(&self) -> &Boxx {
        println!("borrowing box");
        &self.w
    }
}

fn main() {
    // Constructors can have default behavior, like this one.
    let immutable = Circle::origin();

    {
        let b = immutable.by_borrow();
        println!("showing borrowed box: {:?}", b);
    }
    {
        let x = immutable.by_borrow_x();
        println!("showing borrowed x: {:?}", x);
    }
//    let y= immutable.by_take2();
    let by_take_box = immutable.by_take();//TODO: what happened with y,z? or even x? they got destroyed? so the return is a copy/clone then? since self.w is returned! ok, so i'm assuming since the box is moved out, the others are just destroyed; but how is the box moved out? memmove?
    println!("By take: {:?}", by_take_box);
    //println!("Does the circle still exist? {:?}", immutable);//no
}

<|endoftext|>