<commit_before>//! cargo_check provides the functionality needed to run `cargo check` or
//! another compatible command (f.x. clippy) in a background thread and provide
//! LSP diagnostics based on the output of the command.
use cargo_metadata::Message;
use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender};
use lsp_types::{
    Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd,
    WorkDoneProgressReport,
};
use parking_lot::RwLock;
use std::{
    collections::HashMap,
    path::PathBuf,
    process::{Command, Stdio},
    sync::Arc,
    thread::JoinHandle,
    time::Instant,
};

mod conv;

use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic, SuggestedFix};

#[derive(Clone, Debug)]
pub struct CheckOptions {
    pub enable: bool,
    pub args: Vec<String>,
    pub command: String,
    pub all_targets: bool,
}

/// CheckWatcher wraps the shared state and communication machinery used for
/// running `cargo check` (or other compatible command) and providing
/// diagnostics based on the output.
/// The spawned thread is shut down when this struct is dropped.
#[derive(Debug)]
pub struct CheckWatcher {
    pub task_recv: Receiver<CheckTask>,
    pub cmd_send: Sender<CheckCommand>,
    pub shared: Arc<RwLock<CheckWatcherSharedState>>,
    handle: Option<JoinHandle<()>>,
}

impl CheckWatcher {
    pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher {
        let options = options.clone();
        let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new()));

        let (task_send, task_recv) = unbounded::<CheckTask>();
        let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
        let shared_ = shared.clone();
        let handle = std::thread::spawn(move || {
            let mut check = CheckWatcherState::new(options, workspace_root, shared_);
            check.run(&task_send, &cmd_recv);
        });
        CheckWatcher { task_recv, cmd_send, handle: Some(handle), shared }
    }

    /// Schedule a re-start of the cargo check worker.
    pub fn update(&self) {
        self.cmd_send.send(CheckCommand::Update).unwrap();
    }
}

impl std::ops::Drop for CheckWatcher {
    fn drop(&mut self) {
        if let Some(handle) = self.handle.take() {
            // Replace our reciever with dummy one, so we can drop and close the
            // one actually communicating with the thread
            let recv = std::mem::replace(&mut self.task_recv, crossbeam_channel::never());

            // Dropping the original reciever finishes the thread loop
            drop(recv);

            // Join the thread, it should finish shortly. We don't really care
            // whether it panicked, so it is safe to ignore the result
            let _ = handle.join();
        }
    }
}

#[derive(Debug)]
pub struct CheckWatcherSharedState {
    diagnostic_collection: HashMap<Url, Vec<Diagnostic>>,
    suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>,
}

impl CheckWatcherSharedState {
    fn new() -> CheckWatcherSharedState {
        CheckWatcherSharedState {
            diagnostic_collection: HashMap::new(),
            suggested_fix_collection: HashMap::new(),
        }
    }

    /// Clear the cached diagnostics, and schedule updating diagnostics by the
    /// server, to clear stale results.
    pub fn clear(&mut self, task_send: &Sender<CheckTask>) {
        let cleared_files: Vec<Url> = self.diagnostic_collection.keys().cloned().collect();

        self.diagnostic_collection.clear();
        self.suggested_fix_collection.clear();

        for uri in cleared_files {
            task_send.send(CheckTask::Update(uri.clone())).unwrap();
        }
    }

    pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> {
        self.diagnostic_collection.get(uri).map(|d| d.as_slice())
    }

    pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> {
        self.suggested_fix_collection.get(uri).map(|d| d.as_slice())
    }

    fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) {
        let diagnostics = self.diagnostic_collection.entry(file_uri).or_default();

        // If we're building multiple targets it's possible we've already seen this diagnostic
        let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic));
        if is_duplicate {
            return;
        }

        diagnostics.push(diagnostic);
    }

    fn add_suggested_fix_for_diagnostic(
        &mut self,
        mut suggested_fix: SuggestedFix,
        diagnostic: &Diagnostic,
    ) {
        let file_uri = suggested_fix.location.uri.clone();
        let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default();

        let existing_suggestion: Option<&mut SuggestedFix> =
            file_suggestions.iter_mut().find(|s| s == &&suggested_fix);
        if let Some(existing_suggestion) = existing_suggestion {
            // The existing suggestion also applies to this new diagnostic
            existing_suggestion.diagnostics.push(diagnostic.clone());
        } else {
            // We haven't seen this suggestion before
            suggested_fix.diagnostics.push(diagnostic.clone());
            file_suggestions.push(suggested_fix);
        }
    }
}

#[derive(Debug)]
pub enum CheckTask {
    /// Request a update of the given files diagnostics
    Update(Url),

    /// Request check progress notification to client
    Status(WorkDoneProgress),
}

pub enum CheckCommand {
    /// Request re-start of check thread
    Update,
}

struct CheckWatcherState {
    options: CheckOptions,
    workspace_root: PathBuf,
    watcher: WatchThread,
    last_update_req: Option<Instant>,
    shared: Arc<RwLock<CheckWatcherSharedState>>,
}

impl CheckWatcherState {
    pub fn new(
        options: CheckOptions,
        workspace_root: PathBuf,
        shared: Arc<RwLock<CheckWatcherSharedState>>,
    ) -> CheckWatcherState {
        let watcher = WatchThread::new(&options, &workspace_root);
        CheckWatcherState {
            options,
            workspace_root,
            watcher,
            last_update_req: None,
            shared,
        }
    }

    pub fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
        loop {
            select! {
                recv(&cmd_recv) -> cmd => match cmd {
                    Ok(cmd) => self.handle_command(cmd),
                    Err(RecvError) => {
                        // Command channel has closed, so shut down
                        break;
                    },
                },
                recv(self.watcher.message_recv) -> msg => match msg {
                    Ok(msg) => self.handle_message(msg, task_send),
                    Err(RecvError) => {
                        // Task channel has closed, so shut down
                        break;
                    },
                }
            };

            if self.should_recheck() {
                self.last_update_req.take();
                self.shared.write().clear(task_send);

                // By replacing the watcher, we drop the previous one which
                // causes it to shut down automatically.
                self.watcher = WatchThread::new(&self.options, &self.workspace_root);
            }
        }
    }

    fn should_recheck(&mut self) -> bool {
        if let Some(_last_update_req) = &self.last_update_req {
            // We currently only request an update on save, as we need up to
            // date source on disk for cargo check to do it's magic, so we
            // don't really need to debounce the requests at this point.
            return true;
        }
        false
    }

    fn handle_command(&mut self, cmd: CheckCommand) {
        match cmd {
            CheckCommand::Update => self.last_update_req = Some(Instant::now()),
        }
    }

    fn handle_message(&mut self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
        match msg {
            CheckEvent::Begin => {
                task_send
                    .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin {
                        title: "Running 'cargo check'".to_string(),
                        cancellable: Some(false),
                        message: None,
                        percentage: None,
                    })))
                    .unwrap();
            }

            CheckEvent::End => {
                task_send
                    .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd {
                        message: None,
                    })))
                    .unwrap();
            }

            CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
                task_send
                    .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport {
                        cancellable: Some(false),
                        message: Some(msg.target.name),
                        percentage: None,
                    })))
                    .unwrap();
            }

            CheckEvent::Msg(Message::CompilerMessage(msg)) => {
                let map_result =
                    match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) {
                        Some(map_result) => map_result,
                        None => return,
                    };

                let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result;
                let file_uri = location.uri.clone();

                if !suggested_fixes.is_empty() {
                    for suggested_fix in suggested_fixes {
                        self.shared
                            .write()
                            .add_suggested_fix_for_diagnostic(suggested_fix, &diagnostic);
                    }
                }
                self.shared.write().add_diagnostic(file_uri, diagnostic);

                task_send.send(CheckTask::Update(location.uri)).unwrap();
            }

            CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
            CheckEvent::Msg(Message::Unknown) => {}
        }
    }
}

/// WatchThread exists to wrap around the communication needed to be able to
/// run `cargo check` without blocking. Currently the Rust standard library
/// doesn't provide a way to read sub-process output without blocking, so we
/// have to wrap sub-processes output handling in a thread and pass messages
/// back over a channel.
/// The correct way to dispose of the thread is to drop it, on which the
/// sub-process will be killed, and the thread will be joined.
struct WatchThread {
    handle: Option<JoinHandle<()>>,
    message_recv: Receiver<CheckEvent>,
}

enum CheckEvent {
    Begin,
    Msg(cargo_metadata::Message),
    End,
}

impl WatchThread {
    fn new(options: &CheckOptions, workspace_root: &PathBuf) -> WatchThread {
        let mut args: Vec<String> = vec![
            options.command.clone(),
            "--message-format=json".to_string(),
            "--manifest-path".to_string(),
            format!("{}/Cargo.toml", workspace_root.to_string_lossy()),
        ];
        if options.all_targets {
            args.push("--all-targets".to_string());
        }
        args.extend(options.args.iter().cloned());

        let (message_send, message_recv) = unbounded();
        let enabled = options.enable;
        let handle = std::thread::spawn(move || {
            if !enabled {
                return;
            }

            let mut command = Command::new("cargo")
                .args(&args)
                .stdout(Stdio::piped())
                .stderr(Stdio::null())
                .spawn()
                .expect("couldn't launch cargo");

            // If we trigger an error here, we will do so in the loop instead,
            // which will break out of the loop, and continue the shutdown
            let _ = message_send.send(CheckEvent::Begin);

            for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) {
                let message = match message {
                    Ok(message) => message,
                    Err(err) => {
                        log::error!("Invalid json from cargo check, ignoring: {}", err);
                        continue;
                    }
                };

                match message_send.send(CheckEvent::Msg(message)) {
                    Ok(()) => {}
                    Err(_err) => {
                        // The send channel was closed, so we want to shutdown
                        break;
                    }
                }
            }

            // We can ignore any error here, as we are already in the progress
            // of shutting down.
            let _ = message_send.send(CheckEvent::End);

            // It is okay to ignore the result, as it only errors if the process is already dead
            let _ = command.kill();

            // Again, we don't care about the exit status so just ignore the result
            let _ = command.wait();
        });
        WatchThread { handle: Some(handle), message_recv }
    }
}

impl std::ops::Drop for WatchThread {
    fn drop(&mut self) {
        if let Some(handle) = self.handle.take() {
            // Replace our reciever with dummy one, so we can drop and close the
            // one actually communicating with the thread
            let recv = std::mem::replace(&mut self.message_recv, crossbeam_channel::never());

            // Dropping the original reciever initiates thread sub-process shutdown
            drop(recv);

            // Join the thread, it should finish shortly. We don't really care
            // whether it panicked, so it is safe to ignore the result
            let _ = handle.join();
        }
    }
}

fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
    left.source == right.source
        && left.severity == right.severity
        && left.range == right.range
        && left.message == right.message
}
<commit_msg>Cargo fmt run<commit_after>//! cargo_check provides the functionality needed to run `cargo check` or
//! another compatible command (f.x. clippy) in a background thread and provide
//! LSP diagnostics based on the output of the command.
use cargo_metadata::Message;
use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender};
use lsp_types::{
    Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd,
    WorkDoneProgressReport,
};
use parking_lot::RwLock;
use std::{
    collections::HashMap,
    path::PathBuf,
    process::{Command, Stdio},
    sync::Arc,
    thread::JoinHandle,
    time::Instant,
};

mod conv;

use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic, SuggestedFix};

#[derive(Clone, Debug)]
pub struct CheckOptions {
    pub enable: bool,
    pub args: Vec<String>,
    pub command: String,
    pub all_targets: bool,
}

/// CheckWatcher wraps the shared state and communication machinery used for
/// running `cargo check` (or other compatible command) and providing
/// diagnostics based on the output.
/// The spawned thread is shut down when this struct is dropped.
#[derive(Debug)]
pub struct CheckWatcher {
    pub task_recv: Receiver<CheckTask>,
    pub cmd_send: Sender<CheckCommand>,
    pub shared: Arc<RwLock<CheckWatcherSharedState>>,
    handle: Option<JoinHandle<()>>,
}

impl CheckWatcher {
    pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher {
        let options = options.clone();
        let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new()));

        let (task_send, task_recv) = unbounded::<CheckTask>();
        let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
        let shared_ = shared.clone();
        let handle = std::thread::spawn(move || {
            let mut check = CheckWatcherState::new(options, workspace_root, shared_);
            check.run(&task_send, &cmd_recv);
        });
        CheckWatcher { task_recv, cmd_send, handle: Some(handle), shared }
    }

    /// Schedule a re-start of the cargo check worker.
    pub fn update(&self) {
        self.cmd_send.send(CheckCommand::Update).unwrap();
    }
}

impl std::ops::Drop for CheckWatcher {
    fn drop(&mut self) {
        if let Some(handle) = self.handle.take() {
            // Replace our reciever with dummy one, so we can drop and close the
            // one actually communicating with the thread
            let recv = std::mem::replace(&mut self.task_recv, crossbeam_channel::never());

            // Dropping the original reciever finishes the thread loop
            drop(recv);

            // Join the thread, it should finish shortly. We don't really care
            // whether it panicked, so it is safe to ignore the result
            let _ = handle.join();
        }
    }
}

#[derive(Debug)]
pub struct CheckWatcherSharedState {
    diagnostic_collection: HashMap<Url, Vec<Diagnostic>>,
    suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>,
}

impl CheckWatcherSharedState {
    fn new() -> CheckWatcherSharedState {
        CheckWatcherSharedState {
            diagnostic_collection: HashMap::new(),
            suggested_fix_collection: HashMap::new(),
        }
    }

    /// Clear the cached diagnostics, and schedule updating diagnostics by the
    /// server, to clear stale results.
    pub fn clear(&mut self, task_send: &Sender<CheckTask>) {
        let cleared_files: Vec<Url> = self.diagnostic_collection.keys().cloned().collect();

        self.diagnostic_collection.clear();
        self.suggested_fix_collection.clear();

        for uri in cleared_files {
            task_send.send(CheckTask::Update(uri.clone())).unwrap();
        }
    }

    pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> {
        self.diagnostic_collection.get(uri).map(|d| d.as_slice())
    }

    pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> {
        self.suggested_fix_collection.get(uri).map(|d| d.as_slice())
    }

    fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) {
        let diagnostics = self.diagnostic_collection.entry(file_uri).or_default();

        // If we're building multiple targets it's possible we've already seen this diagnostic
        let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic));
        if is_duplicate {
            return;
        }

        diagnostics.push(diagnostic);
    }

    fn add_suggested_fix_for_diagnostic(
        &mut self,
        mut suggested_fix: SuggestedFix,
        diagnostic: &Diagnostic,
    ) {
        let file_uri = suggested_fix.location.uri.clone();
        let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default();

        let existing_suggestion: Option<&mut SuggestedFix> =
            file_suggestions.iter_mut().find(|s| s == &&suggested_fix);
        if let Some(existing_suggestion) = existing_suggestion {
            // The existing suggestion also applies to this new diagnostic
            existing_suggestion.diagnostics.push(diagnostic.clone());
        } else {
            // We haven't seen this suggestion before
            suggested_fix.diagnostics.push(diagnostic.clone());
            file_suggestions.push(suggested_fix);
        }
    }
}

#[derive(Debug)]
pub enum CheckTask {
    /// Request a update of the given files diagnostics
    Update(Url),

    /// Request check progress notification to client
    Status(WorkDoneProgress),
}

pub enum CheckCommand {
    /// Request re-start of check thread
    Update,
}

struct CheckWatcherState {
    options: CheckOptions,
    workspace_root: PathBuf,
    watcher: WatchThread,
    last_update_req: Option<Instant>,
    shared: Arc<RwLock<CheckWatcherSharedState>>,
}

impl CheckWatcherState {
    pub fn new(
        options: CheckOptions,
        workspace_root: PathBuf,
        shared: Arc<RwLock<CheckWatcherSharedState>>,
    ) -> CheckWatcherState {
        let watcher = WatchThread::new(&options, &workspace_root);
        CheckWatcherState { options, workspace_root, watcher, last_update_req: None, shared }
    }

    pub fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
        loop {
            select! {
                recv(&cmd_recv) -> cmd => match cmd {
                    Ok(cmd) => self.handle_command(cmd),
                    Err(RecvError) => {
                        // Command channel has closed, so shut down
                        break;
                    },
                },
                recv(self.watcher.message_recv) -> msg => match msg {
                    Ok(msg) => self.handle_message(msg, task_send),
                    Err(RecvError) => {
                        // Task channel has closed, so shut down
                        break;
                    },
                }
            };

            if self.should_recheck() {
                self.last_update_req.take();
                self.shared.write().clear(task_send);

                // By replacing the watcher, we drop the previous one which
                // causes it to shut down automatically.
                self.watcher = WatchThread::new(&self.options, &self.workspace_root);
            }
        }
    }

    fn should_recheck(&mut self) -> bool {
        if let Some(_last_update_req) = &self.last_update_req {
            // We currently only request an update on save, as we need up to
            // date source on disk for cargo check to do it's magic, so we
            // don't really need to debounce the requests at this point.
            return true;
        }
        false
    }

    fn handle_command(&mut self, cmd: CheckCommand) {
        match cmd {
            CheckCommand::Update => self.last_update_req = Some(Instant::now()),
        }
    }

    fn handle_message(&mut self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
        match msg {
            CheckEvent::Begin => {
                task_send
                    .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin {
                        title: "Running 'cargo check'".to_string(),
                        cancellable: Some(false),
                        message: None,
                        percentage: None,
                    })))
                    .unwrap();
            }

            CheckEvent::End => {
                task_send
                    .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd {
                        message: None,
                    })))
                    .unwrap();
            }

            CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
                task_send
                    .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport {
                        cancellable: Some(false),
                        message: Some(msg.target.name),
                        percentage: None,
                    })))
                    .unwrap();
            }

            CheckEvent::Msg(Message::CompilerMessage(msg)) => {
                let map_result =
                    match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) {
                        Some(map_result) => map_result,
                        None => return,
                    };

                let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result;
                let file_uri = location.uri.clone();

                if !suggested_fixes.is_empty() {
                    for suggested_fix in suggested_fixes {
                        self.shared
                            .write()
                            .add_suggested_fix_for_diagnostic(suggested_fix, &diagnostic);
                    }
                }
                self.shared.write().add_diagnostic(file_uri, diagnostic);

                task_send.send(CheckTask::Update(location.uri)).unwrap();
            }

            CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
            CheckEvent::Msg(Message::Unknown) => {}
        }
    }
}

/// WatchThread exists to wrap around the communication needed to be able to
/// run `cargo check` without blocking. Currently the Rust standard library
/// doesn't provide a way to read sub-process output without blocking, so we
/// have to wrap sub-processes output handling in a thread and pass messages
/// back over a channel.
/// The correct way to dispose of the thread is to drop it, on which the
/// sub-process will be killed, and the thread will be joined.
struct WatchThread {
    handle: Option<JoinHandle<()>>,
    message_recv: Receiver<CheckEvent>,
}

enum CheckEvent {
    Begin,
    Msg(cargo_metadata::Message),
    End,
}

impl WatchThread {
    fn new(options: &CheckOptions, workspace_root: &PathBuf) -> WatchThread {
        let mut args: Vec<String> = vec![
            options.command.clone(),
            "--message-format=json".to_string(),
            "--manifest-path".to_string(),
            format!("{}/Cargo.toml", workspace_root.to_string_lossy()),
        ];
        if options.all_targets {
            args.push("--all-targets".to_string());
        }
        args.extend(options.args.iter().cloned());

        let (message_send, message_recv) = unbounded();
        let enabled = options.enable;
        let handle = std::thread::spawn(move || {
            if !enabled {
                return;
            }

            let mut command = Command::new("cargo")
                .args(&args)
                .stdout(Stdio::piped())
                .stderr(Stdio::null())
                .spawn()
                .expect("couldn't launch cargo");

            // If we trigger an error here, we will do so in the loop instead,
            // which will break out of the loop, and continue the shutdown
            let _ = message_send.send(CheckEvent::Begin);

            for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) {
                let message = match message {
                    Ok(message) => message,
                    Err(err) => {
                        log::error!("Invalid json from cargo check, ignoring: {}", err);
                        continue;
                    }
                };

                match message_send.send(CheckEvent::Msg(message)) {
                    Ok(()) => {}
                    Err(_err) => {
                        // The send channel was closed, so we want to shutdown
                        break;
                    }
                }
            }

            // We can ignore any error here, as we are already in the progress
            // of shutting down.
            let _ = message_send.send(CheckEvent::End);

            // It is okay to ignore the result, as it only errors if the process is already dead
            let _ = command.kill();

            // Again, we don't care about the exit status so just ignore the result
            let _ = command.wait();
        });
        WatchThread { handle: Some(handle), message_recv }
    }
}

impl std::ops::Drop for WatchThread {
    fn drop(&mut self) {
        if let Some(handle) = self.handle.take() {
            // Replace our reciever with dummy one, so we can drop and close the
            // one actually communicating with the thread
            let recv = std::mem::replace(&mut self.message_recv, crossbeam_channel::never());

            // Dropping the original reciever initiates thread sub-process shutdown
            drop(recv);

            // Join the thread, it should finish shortly. We don't really care
            // whether it panicked, so it is safe to ignore the result
            let _ = handle.join();
        }
    }
}

fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
    left.source == right.source
        && left.severity == right.severity
        && left.range == right.range
        && left.message == right.message
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

///! Utilities for the implementation of JSAPI proxy handlers.

use dom::bindings::conversions::is_dom_proxy;
use dom::bindings::utils::delete_property_by_id;
use js::jsapi::{JSContext, jsid, JSPropertyDescriptor, JSObject, JSString};
use js::jsapi::{JS_GetPropertyDescriptorById, JS_NewStringCopyN};
use js::jsapi::{JS_DefinePropertyById, JS_NewObjectWithGivenProto};
use js::jsapi::{JS_ReportErrorFlagsAndNumber, JS_StrictPropertyStub};
use js::jsapi::{JSREPORT_WARNING, JSREPORT_STRICT, JSREPORT_STRICT_MODE_ERROR};
use js::jsval::ObjectValue;
use js::glue::GetProxyExtra;
use js::glue::{GetObjectProto, GetObjectParent, SetProxyExtra, GetProxyHandler};
use js::glue::InvokeGetOwnPropertyDescriptor;
use js::glue::RUST_js_GetErrorMessage;
use js::glue::AutoIdVector;
use js::{JSPROP_GETTER, JSPROP_ENUMERATE, JSPROP_READONLY, JSRESOLVE_QUALIFIED};

use libc;
use std::mem;
use std::ptr;

static JSPROXYSLOT_EXPANDO: u32 = 0;

pub unsafe extern fn getPropertyDescriptor(cx: *mut JSContext, proxy: *mut JSObject,
                                           id: jsid, set: bool,
                                           desc: *mut JSPropertyDescriptor)
                                           -> bool {
    let handler = GetProxyHandler(proxy);
    if !InvokeGetOwnPropertyDescriptor(handler, cx, proxy, id, set, desc) {
        return false;
    }
    if !(*desc).obj.is_null() {
        return true;
    }

    //let proto = JS_GetPrototype(proxy);
    let proto = GetObjectProto(proxy);
    if proto.is_null() {
        (*desc).obj = ptr::null_mut();
        return true;
    }

    JS_GetPropertyDescriptorById(cx, proto, id, JSRESOLVE_QUALIFIED, desc) != 0
}

pub unsafe extern fn defineProperty_(cx: *mut JSContext, proxy: *mut JSObject, id: jsid,
                              desc: *mut JSPropertyDescriptor) -> bool {
    static JSMSG_GETTER_ONLY: libc::c_uint = 160;

    //FIXME: Workaround for https://github.com/mozilla/rust/issues/13385
    let setter: *const libc::c_void = mem::transmute((*desc).setter);
    let setter_stub: *const libc::c_void = mem::transmute(JS_StrictPropertyStub);
    if ((*desc).attrs & JSPROP_GETTER) != 0 && setter == setter_stub {
        return JS_ReportErrorFlagsAndNumber(cx,
                                            JSREPORT_WARNING | JSREPORT_STRICT |
                                            JSREPORT_STRICT_MODE_ERROR,
                                            Some(RUST_js_GetErrorMessage), ptr::null_mut(),
                                            JSMSG_GETTER_ONLY) != 0;
    }

    let expando = EnsureExpandoObject(cx, proxy);
    if expando.is_null() {
        return false;
    }

    return JS_DefinePropertyById(cx, expando, id, (*desc).value, (*desc).getter,
                                 (*desc).setter, (*desc).attrs) != 0;
}

pub unsafe extern fn delete_(cx: *mut JSContext, proxy: *mut JSObject, id: jsid,
                             bp: *mut bool) -> bool {
    let expando = GetExpandoObject(proxy);
    if expando.is_null() {
        *bp = true;
        return true;
    }

    return delete_property_by_id(cx, expando, id, &mut *bp);
}

pub fn _obj_toString(cx: *mut JSContext, name: &str) -> *mut JSString {
    unsafe {
        let result = format!("[object {}]", name);

        let chars = result.as_ptr() as *const libc::c_char;
        let length = result.len() as libc::size_t;

        let string = JS_NewStringCopyN(cx, chars, length);
        assert!(!string.is_null());
        return string;
    }
}

pub fn GetExpandoObject(obj: *mut JSObject) -> *mut JSObject {
    unsafe {
        assert!(is_dom_proxy(obj));
        let val = GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
        if val.is_undefined() {
            ptr::null_mut()
        } else {
            val.to_object()
        }
    }
}

pub fn EnsureExpandoObject(cx: *mut JSContext, obj: *mut JSObject) -> *mut JSObject {
    unsafe {
        assert!(is_dom_proxy(obj));
        let mut expando = GetExpandoObject(obj);
        if expando.is_null() {
            expando = JS_NewObjectWithGivenProto(cx, ptr::null_mut(),
                                                 ptr::null_mut(),
                                                 GetObjectParent(obj));
            if expando.is_null() {
                return ptr::null_mut();
            }

            SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(&*expando));
        }
        return expando;
    }
}

pub fn FillPropertyDescriptor(desc: &mut JSPropertyDescriptor, obj: *mut JSObject, readonly: bool) {
    desc.obj = obj;
    desc.attrs = if readonly { JSPROP_READONLY } else { 0 } | JSPROP_ENUMERATE;
    desc.getter = None;
    desc.setter = None;
    desc.shortid = 0;
}

pub unsafe extern fn getOwnPropertyNames_(_cx: *mut JSContext,
                                          _obj: *mut JSObject,
                                          _v: *mut AutoIdVector) -> bool {
    true
}

pub unsafe extern fn enumerate_(_cx: *mut JSContext, _obj: *mut JSObject,
                                _v: *mut AutoIdVector) -> bool {
    true
}
<commit_msg>Panic if JS_NewObjectWithGivenProto fails.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

///! Utilities for the implementation of JSAPI proxy handlers.

use dom::bindings::conversions::is_dom_proxy;
use dom::bindings::utils::delete_property_by_id;
use js::jsapi::{JSContext, jsid, JSPropertyDescriptor, JSObject, JSString};
use js::jsapi::{JS_GetPropertyDescriptorById, JS_NewStringCopyN};
use js::jsapi::{JS_DefinePropertyById, JS_NewObjectWithGivenProto};
use js::jsapi::{JS_ReportErrorFlagsAndNumber, JS_StrictPropertyStub};
use js::jsapi::{JSREPORT_WARNING, JSREPORT_STRICT, JSREPORT_STRICT_MODE_ERROR};
use js::jsval::ObjectValue;
use js::glue::GetProxyExtra;
use js::glue::{GetObjectProto, GetObjectParent, SetProxyExtra, GetProxyHandler};
use js::glue::InvokeGetOwnPropertyDescriptor;
use js::glue::RUST_js_GetErrorMessage;
use js::glue::AutoIdVector;
use js::{JSPROP_GETTER, JSPROP_ENUMERATE, JSPROP_READONLY, JSRESOLVE_QUALIFIED};

use libc;
use std::mem;
use std::ptr;

static JSPROXYSLOT_EXPANDO: u32 = 0;

pub unsafe extern fn getPropertyDescriptor(cx: *mut JSContext, proxy: *mut JSObject,
                                           id: jsid, set: bool,
                                           desc: *mut JSPropertyDescriptor)
                                           -> bool {
    let handler = GetProxyHandler(proxy);
    if !InvokeGetOwnPropertyDescriptor(handler, cx, proxy, id, set, desc) {
        return false;
    }
    if !(*desc).obj.is_null() {
        return true;
    }

    //let proto = JS_GetPrototype(proxy);
    let proto = GetObjectProto(proxy);
    if proto.is_null() {
        (*desc).obj = ptr::null_mut();
        return true;
    }

    JS_GetPropertyDescriptorById(cx, proto, id, JSRESOLVE_QUALIFIED, desc) != 0
}

pub unsafe extern fn defineProperty_(cx: *mut JSContext, proxy: *mut JSObject, id: jsid,
                              desc: *mut JSPropertyDescriptor) -> bool {
    static JSMSG_GETTER_ONLY: libc::c_uint = 160;

    //FIXME: Workaround for https://github.com/mozilla/rust/issues/13385
    let setter: *const libc::c_void = mem::transmute((*desc).setter);
    let setter_stub: *const libc::c_void = mem::transmute(JS_StrictPropertyStub);
    if ((*desc).attrs & JSPROP_GETTER) != 0 && setter == setter_stub {
        return JS_ReportErrorFlagsAndNumber(cx,
                                            JSREPORT_WARNING | JSREPORT_STRICT |
                                            JSREPORT_STRICT_MODE_ERROR,
                                            Some(RUST_js_GetErrorMessage), ptr::null_mut(),
                                            JSMSG_GETTER_ONLY) != 0;
    }

    let expando = EnsureExpandoObject(cx, proxy);
    return JS_DefinePropertyById(cx, expando, id, (*desc).value, (*desc).getter,
                                 (*desc).setter, (*desc).attrs) != 0;
}

pub unsafe extern fn delete_(cx: *mut JSContext, proxy: *mut JSObject, id: jsid,
                             bp: *mut bool) -> bool {
    let expando = GetExpandoObject(proxy);
    if expando.is_null() {
        *bp = true;
        return true;
    }

    return delete_property_by_id(cx, expando, id, &mut *bp);
}

pub fn _obj_toString(cx: *mut JSContext, name: &str) -> *mut JSString {
    unsafe {
        let result = format!("[object {}]", name);

        let chars = result.as_ptr() as *const libc::c_char;
        let length = result.len() as libc::size_t;

        let string = JS_NewStringCopyN(cx, chars, length);
        assert!(!string.is_null());
        return string;
    }
}

pub fn GetExpandoObject(obj: *mut JSObject) -> *mut JSObject {
    unsafe {
        assert!(is_dom_proxy(obj));
        let val = GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
        if val.is_undefined() {
            ptr::null_mut()
        } else {
            val.to_object()
        }
    }
}

pub fn EnsureExpandoObject(cx: *mut JSContext, obj: *mut JSObject) -> *mut JSObject {
    unsafe {
        assert!(is_dom_proxy(obj));
        let mut expando = GetExpandoObject(obj);
        if expando.is_null() {
            expando = JS_NewObjectWithGivenProto(cx, ptr::null_mut(),
                                                 ptr::null_mut(),
                                                 GetObjectParent(obj));
            assert!(!expando.is_null());

            SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(&*expando));
        }
        return expando;
    }
}

pub fn FillPropertyDescriptor(desc: &mut JSPropertyDescriptor, obj: *mut JSObject, readonly: bool) {
    desc.obj = obj;
    desc.attrs = if readonly { JSPROP_READONLY } else { 0 } | JSPROP_ENUMERATE;
    desc.getter = None;
    desc.setter = None;
    desc.shortid = 0;
}

pub unsafe extern fn getOwnPropertyNames_(_cx: *mut JSContext,
                                          _obj: *mut JSObject,
                                          _v: *mut AutoIdVector) -> bool {
    true
}

pub unsafe extern fn enumerate_(_cx: *mut JSContext, _obj: *mut JSObject,
                                _v: *mut AutoIdVector) -> bool {
    true
}
<|endoftext|>


<commit_before>// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// aux-build:issue_54059.rs
// ignore-stage1
// ignore-wasm32-bare no libc for ffi testing
// revisions: rpass1

extern crate issue_54059;

fn main() {}
<commit_msg>Ignore new test on Windows<commit_after>// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// aux-build:issue_54059.rs
// ignore-stage1
// ignore-wasm32-bare no libc for ffi testing
// ignore-windows - dealing with weird symbols issues on dylibs isn't worth it
// revisions: rpass1

extern crate issue_54059;

fn main() {}
<|endoftext|>


<commit_before>use crate::{Assist, AssistCtx, AssistId};

use ast::{BlockExpr, Expr, ForExpr, IfExpr, LoopBodyOwner, LoopExpr, WhileExpr};
use ra_fmt::unwrap_trivial_block;
use ra_syntax::{ast, AstNode, TextRange, T};

// Assist: unwrap_block
//
// This assist removes if...else, for, while and loop control statements to just keep the body.
//
// ```
// fn foo() {
//     if true {<|>
//         println!("foo");
//     }
// }
// ```
// ->
// ```
// fn foo() {
//     println!("foo");
// }
// ```
pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> {
    let l_curly_token = ctx.find_token_at_offset(T!['{'])?;

    let res = if let Some(if_expr) = l_curly_token.ancestors().find_map(IfExpr::cast) {
        // if expression
        let expr_to_unwrap = if_expr.blocks().find_map(|expr| extract_expr(ctx.frange.range, expr));
        let expr_to_unwrap = expr_to_unwrap?;
        // Find if we are in a else if block
        let ancestor = if_expr.syntax().ancestors().skip(1).find_map(ast::IfExpr::cast);

        if let Some(ancestor) = ancestor {
            Some((ast::Expr::IfExpr(ancestor), expr_to_unwrap))
        } else {
            Some((ast::Expr::IfExpr(if_expr), expr_to_unwrap))
        }
    } else if let Some(for_expr) = l_curly_token.ancestors().find_map(ForExpr::cast) {
        // for expression
        let block_expr = for_expr.loop_body()?;
        extract_expr(ctx.frange.range, block_expr)
            .map(|expr_to_unwrap| (ast::Expr::ForExpr(for_expr), expr_to_unwrap))
    } else if let Some(while_expr) = l_curly_token.ancestors().find_map(WhileExpr::cast) {
        // while expression
        let block_expr = while_expr.loop_body()?;
        extract_expr(ctx.frange.range, block_expr)
            .map(|expr_to_unwrap| (ast::Expr::WhileExpr(while_expr), expr_to_unwrap))
    } else if let Some(loop_expr) = l_curly_token.ancestors().find_map(LoopExpr::cast) {
        // loop expression
        let block_expr = loop_expr.loop_body()?;
        extract_expr(ctx.frange.range, block_expr)
            .map(|expr_to_unwrap| (ast::Expr::LoopExpr(loop_expr), expr_to_unwrap))
    } else {
        None
    };

    let (expr, expr_to_unwrap) = res?;
    ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", |edit| {
        edit.set_cursor(expr.syntax().text_range().start());
        edit.target(expr_to_unwrap.syntax().text_range());

        let pat_start: &[_] = &[' ', '{', '\n'];
        let expr_to_unwrap = expr_to_unwrap.to_string();
        let expr_string = expr_to_unwrap.trim_start_matches(pat_start);
        let mut expr_string_lines: Vec<&str> = expr_string.lines().collect();
        expr_string_lines.pop(); // Delete last line

        let expr_string = expr_string_lines
            .into_iter()
            .map(|line| line.replacen("    ", "", 1)) // Delete indentation
            .collect::<Vec<String>>()
            .join("\n");

        edit.replace(expr.syntax().text_range(), expr_string);
    })
}

fn extract_expr(cursor_range: TextRange, block_expr: BlockExpr) -> Option<Expr> {
    let block = block_expr.block()?;
    let cursor_in_range = block.l_curly_token()?.text_range().contains_range(cursor_range);

    if cursor_in_range {
        Some(unwrap_trivial_block(block_expr))
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use crate::helpers::{check_assist, check_assist_not_applicable};

    use super::*;

    #[test]
    fn simple_if() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                bar();
                if true {<|>
                    foo();

                    //comment
                    bar();
                } else {
                    println!("bar");
                }
            }
            "#,
            r#"
            fn main() {
                bar();
                <|>foo();

                //comment
                bar();
            }
            "#,
        );
    }

    #[test]
    fn simple_if_else() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                bar();
                if true {
                    foo();

                    //comment
                    bar();
                } else {<|>
                    println!("bar");
                }
            }
            "#,
            r#"
            fn main() {
                bar();
                <|>println!("bar");
            }
            "#,
        );
    }

    #[test]
    fn simple_if_else_if() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                //bar();
                if true {
                    println!("true");

                    //comment
                    //bar();
                } else if false {<|>
                    println!("bar");
                } else {
                    println!("foo");
                }
            }
            "#,
            r#"
            fn main() {
                //bar();
                <|>println!("bar");
            }
            "#,
        );
    }

    #[test]
    fn simple_if_bad_cursor_position() {
        check_assist_not_applicable(
            unwrap_block,
            r#"
            fn main() {
                bar();<|>
                if true {
                    foo();

                    //comment
                    bar();
                } else {
                    println!("bar");
                }
            }
            "#,
        );
    }

    #[test]
    fn simple_for() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                for i in 0..5 {<|>
                    if true {
                        foo();

                        //comment
                        bar();
                    } else {
                        println!("bar");
                    }
                }
            }
            "#,
            r#"
            fn main() {
                <|>if true {
                    foo();

                    //comment
                    bar();
                } else {
                    println!("bar");
                }
            }
            "#,
        );
    }

    #[test]
    fn simple_if_in_for() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                for i in 0..5 {
                    if true {<|>
                        foo();

                        //comment
                        bar();
                    } else {
                        println!("bar");
                    }
                }
            }
            "#,
            r#"
            fn main() {
                for i in 0..5 {
                    <|>foo();

                    //comment
                    bar();
                }
            }
            "#,
        );
    }

    #[test]
    fn simple_loop() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                loop {<|>
                    if true {
                        foo();

                        //comment
                        bar();
                    } else {
                        println!("bar");
                    }
                }
            }
            "#,
            r#"
            fn main() {
                <|>if true {
                    foo();

                    //comment
                    bar();
                } else {
                    println!("bar");
                }
            }
            "#,
        );
    }

    #[test]
    fn simple_while() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                while true {<|>
                    if true {
                        foo();

                        //comment
                        bar();
                    } else {
                        println!("bar");
                    }
                }
            }
            "#,
            r#"
            fn main() {
                <|>if true {
                    foo();

                    //comment
                    bar();
                } else {
                    println!("bar");
                }
            }
            "#,
        );
    }

    #[test]
    fn simple_if_in_while_bad_cursor_position() {
        check_assist_not_applicable(
            unwrap_block,
            r#"
            fn main() {
                while true {
                    if true {
                        foo();<|>

                        //comment
                        bar();
                    } else {
                        println!("bar");
                    }
                }
            }
            "#,
        );
    }
}
<commit_msg>Add unwrap block assist #4156<commit_after>use crate::{Assist, AssistCtx, AssistId};

use ast::{BlockExpr, Expr, ForExpr, IfExpr, LoopBodyOwner, LoopExpr, WhileExpr};
use ra_fmt::unwrap_trivial_block;
use ra_syntax::{ast, AstNode, TextRange, T};

// Assist: unwrap_block
//
// This assist removes if...else, for, while and loop control statements to just keep the body.
//
// ```
// fn foo() {
//     if true {<|>
//         println!("foo");
//     }
// }
// ```
// ->
// ```
// fn foo() {
//     println!("foo");
// }
// ```
pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> {
    let l_curly_token = ctx.find_token_at_offset(T!['{'])?;

    let res = if let Some(if_expr) = l_curly_token.ancestors().find_map(IfExpr::cast) {
        // if expression
        let expr_to_unwrap = if_expr.blocks().find_map(|expr| extract_expr(ctx.frange.range, expr));
        let expr_to_unwrap = expr_to_unwrap?;
        // Find if we are in a else if block
        let ancestor = if_expr.syntax().ancestors().skip(1).find_map(ast::IfExpr::cast);

        if let Some(ancestor) = ancestor {
            Some((ast::Expr::IfExpr(ancestor), expr_to_unwrap))
        } else {
            Some((ast::Expr::IfExpr(if_expr), expr_to_unwrap))
        }
    } else if let Some(for_expr) = l_curly_token.ancestors().find_map(ForExpr::cast) {
        // for expression
        let block_expr = for_expr.loop_body()?;
        extract_expr(ctx.frange.range, block_expr)
            .map(|expr_to_unwrap| (ast::Expr::ForExpr(for_expr), expr_to_unwrap))
    } else if let Some(while_expr) = l_curly_token.ancestors().find_map(WhileExpr::cast) {
        // while expression
        let block_expr = while_expr.loop_body()?;
        extract_expr(ctx.frange.range, block_expr)
            .map(|expr_to_unwrap| (ast::Expr::WhileExpr(while_expr), expr_to_unwrap))
    } else if let Some(loop_expr) = l_curly_token.ancestors().find_map(LoopExpr::cast) {
        // loop expression
        let block_expr = loop_expr.loop_body()?;
        extract_expr(ctx.frange.range, block_expr)
            .map(|expr_to_unwrap| (ast::Expr::LoopExpr(loop_expr), expr_to_unwrap))
    } else {
        None
    };

    let (expr, expr_to_unwrap) = res?;
    ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", |edit| {
        edit.set_cursor(expr.syntax().text_range().start());
        edit.target(expr_to_unwrap.syntax().text_range());

        let pat_start: &[_] = &[' ', '{', '\n'];
        let expr_to_unwrap = expr_to_unwrap.to_string();
        let expr_string = expr_to_unwrap.trim_start_matches(pat_start);
        let mut expr_string_lines: Vec<&str> = expr_string.lines().collect();
        expr_string_lines.pop(); // Delete last line

        let expr_string = expr_string_lines
            .into_iter()
            .map(|line| line.replacen("    ", "", 1)) // Delete indentation
            .collect::<Vec<String>>()
            .join("\n");

        edit.replace(expr.syntax().text_range(), expr_string);
    })
}

fn extract_expr(cursor_range: TextRange, block: BlockExpr) -> Option<Expr> {
    let cursor_in_range = block.l_curly_token()?.text_range().contains_range(cursor_range);

    if cursor_in_range {
        Some(unwrap_trivial_block(block))
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use crate::helpers::{check_assist, check_assist_not_applicable};

    use super::*;

    #[test]
    fn simple_if() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                bar();
                if true {<|>
                    foo();

                    //comment
                    bar();
                } else {
                    println!("bar");
                }
            }
            "#,
            r#"
            fn main() {
                bar();
                <|>foo();

                //comment
                bar();
            }
            "#,
        );
    }

    #[test]
    fn simple_if_else() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                bar();
                if true {
                    foo();

                    //comment
                    bar();
                } else {<|>
                    println!("bar");
                }
            }
            "#,
            r#"
            fn main() {
                bar();
                <|>println!("bar");
            }
            "#,
        );
    }

    #[test]
    fn simple_if_else_if() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                //bar();
                if true {
                    println!("true");

                    //comment
                    //bar();
                } else if false {<|>
                    println!("bar");
                } else {
                    println!("foo");
                }
            }
            "#,
            r#"
            fn main() {
                //bar();
                <|>println!("bar");
            }
            "#,
        );
    }

    #[test]
    fn simple_if_bad_cursor_position() {
        check_assist_not_applicable(
            unwrap_block,
            r#"
            fn main() {
                bar();<|>
                if true {
                    foo();

                    //comment
                    bar();
                } else {
                    println!("bar");
                }
            }
            "#,
        );
    }

    #[test]
    fn simple_for() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                for i in 0..5 {<|>
                    if true {
                        foo();

                        //comment
                        bar();
                    } else {
                        println!("bar");
                    }
                }
            }
            "#,
            r#"
            fn main() {
                <|>if true {
                    foo();

                    //comment
                    bar();
                } else {
                    println!("bar");
                }
            }
            "#,
        );
    }

    #[test]
    fn simple_if_in_for() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                for i in 0..5 {
                    if true {<|>
                        foo();

                        //comment
                        bar();
                    } else {
                        println!("bar");
                    }
                }
            }
            "#,
            r#"
            fn main() {
                for i in 0..5 {
                    <|>foo();

                    //comment
                    bar();
                }
            }
            "#,
        );
    }

    #[test]
    fn simple_loop() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                loop {<|>
                    if true {
                        foo();

                        //comment
                        bar();
                    } else {
                        println!("bar");
                    }
                }
            }
            "#,
            r#"
            fn main() {
                <|>if true {
                    foo();

                    //comment
                    bar();
                } else {
                    println!("bar");
                }
            }
            "#,
        );
    }

    #[test]
    fn simple_while() {
        check_assist(
            unwrap_block,
            r#"
            fn main() {
                while true {<|>
                    if true {
                        foo();

                        //comment
                        bar();
                    } else {
                        println!("bar");
                    }
                }
            }
            "#,
            r#"
            fn main() {
                <|>if true {
                    foo();

                    //comment
                    bar();
                } else {
                    println!("bar");
                }
            }
            "#,
        );
    }

    #[test]
    fn simple_if_in_while_bad_cursor_position() {
        check_assist_not_applicable(
            unwrap_block,
            r#"
            fn main() {
                while true {
                    if true {
                        foo();<|>

                        //comment
                        bar();
                    } else {
                        println!("bar");
                    }
                }
            }
            "#,
        );
    }
}
<|endoftext|>


<commit_before>//! FIXME: write short doc here

use std::mem;

use ra_parser::{ParseError, TreeSink};

use crate::{
    parsing::Token,
    syntax_node::GreenNode,
    SmolStr, SyntaxError,
    SyntaxKind::{self, *},
    SyntaxTreeBuilder, TextRange, TextSize,
};

/// Bridges the parser with our specific syntax tree representation.
///
/// `TextTreeSink` also handles attachment of trivia (whitespace) to nodes.
pub(crate) struct TextTreeSink<'a> {
    text: &'a str,
    tokens: &'a [Token],
    text_pos: TextSize,
    token_pos: usize,
    state: State,
    inner: SyntaxTreeBuilder,
}

enum State {
    PendingStart,
    Normal,
    PendingFinish,
}

impl<'a> TreeSink for TextTreeSink<'a> {
    fn token(&mut self, kind: SyntaxKind, n_tokens: u8) {
        match mem::replace(&mut self.state, State::Normal) {
            State::PendingStart => unreachable!(),
            State::PendingFinish => self.inner.finish_node(),
            State::Normal => (),
        }
        self.eat_trivias();
        let n_tokens = n_tokens as usize;
        let len = self.tokens[self.token_pos..self.token_pos + n_tokens]
            .iter()
            .map(|it| it.len)
            .sum::<TextSize>();
        self.do_token(kind, len, n_tokens);
    }

    fn start_node(&mut self, kind: SyntaxKind) {
        match mem::replace(&mut self.state, State::Normal) {
            State::PendingStart => {
                self.inner.start_node(kind);
                // No need to attach trivias to previous node: there is no
                // previous node.
                return;
            }
            State::PendingFinish => self.inner.finish_node(),
            State::Normal => (),
        }

        let n_trivias =
            self.tokens[self.token_pos..].iter().take_while(|it| it.kind.is_trivia()).count();
        let leading_trivias = &self.tokens[self.token_pos..self.token_pos + n_trivias];
        let mut trivia_end =
            self.text_pos + leading_trivias.iter().map(|it| it.len).sum::<TextSize>();

        let n_attached_trivias = {
            let leading_trivias = leading_trivias.iter().rev().map(|it| {
                let next_end = trivia_end - it.len;
                let range = TextRange::new(next_end, trivia_end);
                trivia_end = next_end;
                (it.kind, &self.text[range])
            });
            n_attached_trivias(kind, leading_trivias)
        };
        self.eat_n_trivias(n_trivias - n_attached_trivias);
        self.inner.start_node(kind);
        self.eat_n_trivias(n_attached_trivias);
    }

    fn finish_node(&mut self) {
        match mem::replace(&mut self.state, State::PendingFinish) {
            State::PendingStart => unreachable!(),
            State::PendingFinish => self.inner.finish_node(),
            State::Normal => (),
        }
    }

    fn error(&mut self, error: ParseError) {
        self.inner.error(error, self.text_pos)
    }
}

impl<'a> TextTreeSink<'a> {
    pub(super) fn new(text: &'a str, tokens: &'a [Token]) -> Self {
        Self {
            text,
            tokens,
            text_pos: 0.into(),
            token_pos: 0,
            state: State::PendingStart,
            inner: SyntaxTreeBuilder::default(),
        }
    }

    pub(super) fn finish(mut self) -> (GreenNode, Vec<SyntaxError>) {
        match mem::replace(&mut self.state, State::Normal) {
            State::PendingFinish => {
                self.eat_trivias();
                self.inner.finish_node()
            }
            State::PendingStart | State::Normal => unreachable!(),
        }

        self.inner.finish_raw()
    }

    fn eat_trivias(&mut self) {
        while let Some(&token) = self.tokens.get(self.token_pos) {
            if !token.kind.is_trivia() {
                break;
            }
            self.do_token(token.kind, token.len, 1);
        }
    }

    fn eat_n_trivias(&mut self, n: usize) {
        for _ in 0..n {
            let token = self.tokens[self.token_pos];
            assert!(token.kind.is_trivia());
            self.do_token(token.kind, token.len, 1);
        }
    }

    fn do_token(&mut self, kind: SyntaxKind, len: TextSize, n_tokens: usize) {
        let range = TextRange::at(self.text_pos, len);
        let text: SmolStr = self.text[range].into();
        self.text_pos += len;
        self.token_pos += n_tokens;
        self.inner.token(kind, text);
    }
}

fn n_attached_trivias<'a>(
    kind: SyntaxKind,
    trivias: impl Iterator<Item = (SyntaxKind, &'a str)>,
) -> usize {
    match kind {
        MACRO_CALL | CONST_DEF | TYPE_ALIAS_DEF | STRUCT_DEF | ENUM_DEF | ENUM_VARIANT | FN_DEF
        | TRAIT_DEF | MODULE | RECORD_FIELD_DEF | STATIC_DEF => {
            let mut res = 0;
            let mut trivias = trivias.enumerate().peekable();

            while let Some((i, (kind, text))) = trivias.next() {
                match kind {
                    WHITESPACE => {
                        if text.contains("\n\n") {
                            // we check whether the next token is a doc-comment
                            // and skip the whitespace in this case
                            if let Some((peek_kind, peek_text)) =
                                trivias.peek().map(|(_, pair)| pair)
                            {
                                if *peek_kind == COMMENT && peek_text.starts_with("///") {
                                    continue;
                                }
                            }
                            break;
                        }
                    }
                    COMMENT => {
                        res = i + 1;
                    }
                    _ => (),
                }
            }
            res
        }
        _ => 0,
    }
}
<commit_msg>Comment decorations<commit_after>//! FIXME: write short doc here

use std::mem;

use ra_parser::{ParseError, TreeSink};

use crate::{
    parsing::Token,
    syntax_node::GreenNode,
    SmolStr, SyntaxError,
    SyntaxKind::{self, *},
    SyntaxTreeBuilder, TextRange, TextSize,
};

/// Bridges the parser with our specific syntax tree representation.
///
/// `TextTreeSink` also handles attachment of trivia (whitespace) to nodes.
pub(crate) struct TextTreeSink<'a> {
    text: &'a str,
    tokens: &'a [Token],
    text_pos: TextSize,
    token_pos: usize,
    state: State,
    inner: SyntaxTreeBuilder,
}

enum State {
    PendingStart,
    Normal,
    PendingFinish,
}

impl<'a> TreeSink for TextTreeSink<'a> {
    fn token(&mut self, kind: SyntaxKind, n_tokens: u8) {
        match mem::replace(&mut self.state, State::Normal) {
            State::PendingStart => unreachable!(),
            State::PendingFinish => self.inner.finish_node(),
            State::Normal => (),
        }
        self.eat_trivias();
        let n_tokens = n_tokens as usize;
        let len = self.tokens[self.token_pos..self.token_pos + n_tokens]
            .iter()
            .map(|it| it.len)
            .sum::<TextSize>();
        self.do_token(kind, len, n_tokens);
    }

    fn start_node(&mut self, kind: SyntaxKind) {
        match mem::replace(&mut self.state, State::Normal) {
            State::PendingStart => {
                self.inner.start_node(kind);
                // No need to attach trivias to previous node: there is no
                // previous node.
                return;
            }
            State::PendingFinish => self.inner.finish_node(),
            State::Normal => (),
        }

        let n_trivias =
            self.tokens[self.token_pos..].iter().take_while(|it| it.kind.is_trivia()).count();
        let leading_trivias = &self.tokens[self.token_pos..self.token_pos + n_trivias];
        let mut trivia_end =
            self.text_pos + leading_trivias.iter().map(|it| it.len).sum::<TextSize>();

        let n_attached_trivias = {
            let leading_trivias = leading_trivias.iter().rev().map(|it| {
                let next_end = trivia_end - it.len;
                let range = TextRange::new(next_end, trivia_end);
                trivia_end = next_end;
                (it.kind, &self.text[range])
            });
            n_attached_trivias(kind, leading_trivias)
        };
        self.eat_n_trivias(n_trivias - n_attached_trivias);
        self.inner.start_node(kind);
        self.eat_n_trivias(n_attached_trivias);
    }

    fn finish_node(&mut self) {
        match mem::replace(&mut self.state, State::PendingFinish) {
            State::PendingStart => unreachable!(),
            State::PendingFinish => self.inner.finish_node(),
            State::Normal => (),
        }
    }

    fn error(&mut self, error: ParseError) {
        self.inner.error(error, self.text_pos)
    }
}

impl<'a> TextTreeSink<'a> {
    pub(super) fn new(text: &'a str, tokens: &'a [Token]) -> Self {
        Self {
            text,
            tokens,
            text_pos: 0.into(),
            token_pos: 0,
            state: State::PendingStart,
            inner: SyntaxTreeBuilder::default(),
        }
    }

    pub(super) fn finish(mut self) -> (GreenNode, Vec<SyntaxError>) {
        match mem::replace(&mut self.state, State::Normal) {
            State::PendingFinish => {
                self.eat_trivias();
                self.inner.finish_node()
            }
            State::PendingStart | State::Normal => unreachable!(),
        }

        self.inner.finish_raw()
    }

    fn eat_trivias(&mut self) {
        while let Some(&token) = self.tokens.get(self.token_pos) {
            if !token.kind.is_trivia() {
                break;
            }
            self.do_token(token.kind, token.len, 1);
        }
    }

    fn eat_n_trivias(&mut self, n: usize) {
        for _ in 0..n {
            let token = self.tokens[self.token_pos];
            assert!(token.kind.is_trivia());
            self.do_token(token.kind, token.len, 1);
        }
    }

    fn do_token(&mut self, kind: SyntaxKind, len: TextSize, n_tokens: usize) {
        let range = TextRange::at(self.text_pos, len);
        let text: SmolStr = self.text[range].into();
        self.text_pos += len;
        self.token_pos += n_tokens;
        self.inner.token(kind, text);
    }
}

fn n_attached_trivias<'a>(
    kind: SyntaxKind,
    trivias: impl Iterator<Item = (SyntaxKind, &'a str)>,
) -> usize {
    match kind {
        MACRO_CALL | CONST_DEF | TYPE_ALIAS_DEF | STRUCT_DEF | ENUM_DEF | ENUM_VARIANT | FN_DEF
        | TRAIT_DEF | MODULE | RECORD_FIELD_DEF | STATIC_DEF => {
            let mut res = 0;
            let mut trivias = trivias.enumerate().peekable();

            while let Some((i, (kind, text))) = trivias.next() {
                match kind {
                    WHITESPACE => {
                        if text.contains("\n\n") {
                            // we check whether the next token is a doc-comment
                            // and skip the whitespace in this case
                            if let Some((peek_kind, peek_text)) =
                                trivias.peek().map(|(_, pair)| pair)
                            {
                                if *peek_kind == COMMENT
                                    && peek_text.starts_with("///")
                                    && !peek_text.starts_with("////")
                                {
                                    continue;
                                }
                            }
                            break;
                        }
                    }
                    COMMENT => {
                        res = i + 1;
                    }
                    _ => (),
                }
            }
            res
        }
        _ => 0,
    }
}
<|endoftext|>


<commit_before>//! Fully type-check project and print various stats, like the number of type
//! errors.

use std::{
    path::Path,
    time::{SystemTime, UNIX_EPOCH},
};

use hir::{
    db::{AstDatabase, DefDatabase, HirDatabase},
    original_range, AssocItem, Crate, HasSource, HirDisplay, ModuleDef,
};
use hir_def::FunctionId;
use hir_ty::{Ty, TypeWalk};
use itertools::Itertools;
use oorandom::Rand32;
use ra_db::{
    salsa::{self, ParallelDatabase},
    SourceDatabaseExt,
};
use ra_syntax::AstNode;
use rayon::prelude::*;
use rustc_hash::FxHashSet;
use stdx::format_to;

use crate::{
    cli::{
        load_cargo::load_cargo, progress_report::ProgressReport, report_metric, Result, Verbosity,
    },
    print_memory_usage,
};
use ra_prof::StopWatch;

/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
struct Snap<DB>(DB);
impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
    fn clone(&self) -> Snap<salsa::Snapshot<DB>> {
        Snap(self.0.snapshot())
    }
}

pub fn analysis_stats(
    verbosity: Verbosity,
    memory_usage: bool,
    path: &Path,
    only: Option<&str>,
    with_deps: bool,
    randomize: bool,
    parallel: bool,
    load_output_dirs: bool,
    with_proc_macro: bool,
) -> Result<()> {
    let mut rng = {
        let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
        Rand32::new(seed)
    };

    let mut db_load_sw = StopWatch::start().memory(memory_usage);
    let (host, vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?;
    let db = host.raw_database();
    eprintln!("Database loaded {}", db_load_sw.elapsed());

    let mut analysis_sw = StopWatch::start().memory(memory_usage);
    let mut num_crates = 0;
    let mut visited_modules = FxHashSet::default();
    let mut visit_queue = Vec::new();

    let mut krates = Crate::all(db);
    if randomize {
        shuffle(&mut rng, &mut krates);
    }
    for krate in krates {
        let module = krate.root_module(db).expect("crate without root module");
        let file_id = module.definition_source(db).file_id;
        let file_id = file_id.original_file(db);
        let source_root = db.file_source_root(file_id);
        let source_root = db.source_root(source_root);
        if !source_root.is_library || with_deps {
            num_crates += 1;
            visit_queue.push(module);
        }
    }

    if randomize {
        shuffle(&mut rng, &mut visit_queue);
    }

    eprintln!("Crates in this dir: {}", num_crates);
    let mut num_decls = 0;
    let mut funcs = Vec::new();
    while let Some(module) = visit_queue.pop() {
        if visited_modules.insert(module) {
            visit_queue.extend(module.children(db));

            for decl in module.declarations(db) {
                num_decls += 1;
                if let ModuleDef::Function(f) = decl {
                    funcs.push(f);
                }
            }

            for impl_def in module.impl_defs(db) {
                for item in impl_def.items(db) {
                    num_decls += 1;
                    if let AssocItem::Function(f) = item {
                        funcs.push(f);
                    }
                }
            }
        }
    }
    eprintln!("Total modules found: {}", visited_modules.len());
    eprintln!("Total declarations: {}", num_decls);
    eprintln!("Total functions: {}", funcs.len());
    eprintln!("Item Collection: {}", analysis_sw.elapsed());

    if randomize {
        shuffle(&mut rng, &mut funcs);
    }

    let mut bar = match verbosity {
        Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
        _ => ProgressReport::new(funcs.len() as u64),
    };

    if parallel {
        let mut inference_sw = StopWatch::start().memory(memory_usage);
        let snap = Snap(db.snapshot());
        funcs
            .par_iter()
            .map_with(snap, |snap, &f| {
                let f_id = FunctionId::from(f);
                snap.0.body(f_id.into());
                snap.0.infer(f_id.into());
            })
            .count();
        eprintln!("Parallel Inference: {}", inference_sw.elapsed());
    }

    let mut inference_sw = StopWatch::start().memory(memory_usage);
    bar.tick();
    let mut num_exprs = 0;
    let mut num_exprs_unknown = 0;
    let mut num_exprs_partially_unknown = 0;
    let mut num_type_mismatches = 0;
    for f in funcs {
        let name = f.name(db);
        let full_name = f
            .module(db)
            .path_to_root(db)
            .into_iter()
            .rev()
            .filter_map(|it| it.name(db))
            .chain(Some(f.name(db)))
            .join("::");
        if let Some(only_name) = only {
            if name.to_string() != only_name && full_name != only_name {
                continue;
            }
        }
        let mut msg = format!("processing: {}", full_name);
        if verbosity.is_verbose() {
            let src = f.source(db);
            let original_file = src.file_id.original_file(db);
            let path = vfs.file_path(original_file);
            let syntax_range = src.value.syntax().text_range();
            format_to!(msg, " ({} {:?})", path, syntax_range);
        }
        if verbosity.is_spammy() {
            bar.println(msg.to_string());
        }
        bar.set_message(&msg);
        let f_id = FunctionId::from(f);
        let body = db.body(f_id.into());
        let inference_result = db.infer(f_id.into());
        let (previous_exprs, previous_unknown, previous_partially_unknown) =
            (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
        for (expr_id, _) in body.exprs.iter() {
            let ty = &inference_result[expr_id];
            num_exprs += 1;
            if let Ty::Unknown = ty {
                num_exprs_unknown += 1;
            } else {
                let mut is_partially_unknown = false;
                ty.walk(&mut |ty| {
                    if let Ty::Unknown = ty {
                        is_partially_unknown = true;
                    }
                });
                if is_partially_unknown {
                    num_exprs_partially_unknown += 1;
                }
            }
            if only.is_some() && verbosity.is_spammy() {
                // in super-verbose mode for just one function, we print every single expression
                let (_, sm) = db.body_with_source_map(f_id.into());
                let src = sm.expr_syntax(expr_id);
                if let Ok(src) = src {
                    let node = {
                        let root = db.parse_or_expand(src.file_id).unwrap();
                        src.value.to_node(&root)
                    };
                    let original_file = src.file_id.original_file(db);
                    let line_index = host.analysis().file_line_index(original_file).unwrap();
                    let text_range = node.syntax().text_range();
                    let (start, end) = (
                        line_index.line_col(text_range.start()),
                        line_index.line_col(text_range.end()),
                    );
                    bar.println(format!(
                        "{}:{}-{}:{}: {}",
                        start.line + 1,
                        start.col_utf16,
                        end.line + 1,
                        end.col_utf16,
                        ty.display(db)
                    ));
                } else {
                    bar.println(format!("unknown location: {}", ty.display(db)));
                }
            }
            if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
                num_type_mismatches += 1;
                if verbosity.is_verbose() {
                    let (_, sm) = db.body_with_source_map(f_id.into());
                    let src = sm.expr_syntax(expr_id);
                    if let Ok(src) = src {
                        // FIXME: it might be nice to have a function (on Analysis?) that goes from Source<T> -> (LineCol, LineCol) directly
                        // But also, we should just turn the type mismatches into diagnostics and provide these
                        let root = db.parse_or_expand(src.file_id).unwrap();
                        let node = src.map(|e| e.to_node(&root).syntax().clone());
                        let original_range = original_range(db, node.as_ref());
                        let path = vfs.file_path(original_range.file_id);
                        let line_index =
                            host.analysis().file_line_index(original_range.file_id).unwrap();
                        let text_range = original_range.range;
                        let (start, end) = (
                            line_index.line_col(text_range.start()),
                            line_index.line_col(text_range.end()),
                        );
                        bar.println(format!(
                            "{} {}:{}-{}:{}: Expected {}, got {}",
                            path,
                            start.line + 1,
                            start.col_utf16,
                            end.line + 1,
                            end.col_utf16,
                            mismatch.expected.display(db),
                            mismatch.actual.display(db)
                        ));
                    } else {
                        bar.println(format!(
                            "{}: Expected {}, got {}",
                            name,
                            mismatch.expected.display(db),
                            mismatch.actual.display(db)
                        ));
                    }
                }
            }
        }
        if verbosity.is_spammy() {
            bar.println(format!(
                "In {}: {} exprs, {} unknown, {} partial",
                full_name,
                num_exprs - previous_exprs,
                num_exprs_unknown - previous_unknown,
                num_exprs_partially_unknown - previous_partially_unknown
            ));
        }
        bar.inc(1);
    }
    bar.finish_and_clear();
    eprintln!("Total expressions: {}", num_exprs);
    eprintln!(
        "Expressions of unknown type: {} ({}%)",
        num_exprs_unknown,
        if num_exprs > 0 { num_exprs_unknown * 100 / num_exprs } else { 100 }
    );
    report_metric("unknown type", num_exprs_unknown, "#");

    eprintln!(
        "Expressions of partially unknown type: {} ({}%)",
        num_exprs_partially_unknown,
        if num_exprs > 0 { num_exprs_partially_unknown * 100 / num_exprs } else { 100 }
    );

    eprintln!("Type mismatches: {}", num_type_mismatches);
    report_metric("type mismatches", num_type_mismatches, "#");

    eprintln!("Inference: {}", inference_sw.elapsed());

    let total_span = analysis_sw.elapsed();
    eprintln!("Total: {}", total_span);
    report_metric("total time", total_span.time.as_millis() as u64, "ms");
    if let Some(instructions) = total_span.instructions {
        report_metric("total time", instructions, "#instr");
    }
    if let Some(memory) = total_span.memory {
        report_metric("total memory", memory.allocated.megabytes() as u64, "MB");
    }

    if memory_usage {
        print_memory_usage(host, vfs);
    }

    Ok(())
}

fn shuffle<T>(rng: &mut Rand32, slice: &mut [T]) {
    for i in (1..slice.len()).rev() {
        let idx = rng.rand_range(0..i as u32) as usize;
        slice.swap(idx, i)
    }
}
<commit_msg>Do not show progress with parallel analysis<commit_after>//! Fully type-check project and print various stats, like the number of type
//! errors.

use std::{
    path::Path,
    time::{SystemTime, UNIX_EPOCH},
};

use hir::{
    db::{AstDatabase, DefDatabase, HirDatabase},
    original_range, AssocItem, Crate, HasSource, HirDisplay, ModuleDef,
};
use hir_def::FunctionId;
use hir_ty::{Ty, TypeWalk};
use itertools::Itertools;
use oorandom::Rand32;
use ra_db::{
    salsa::{self, ParallelDatabase},
    SourceDatabaseExt,
};
use ra_syntax::AstNode;
use rayon::prelude::*;
use rustc_hash::FxHashSet;
use stdx::format_to;

use crate::{
    cli::{
        load_cargo::load_cargo, progress_report::ProgressReport, report_metric, Result, Verbosity,
    },
    print_memory_usage,
};
use ra_prof::StopWatch;

/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
struct Snap<DB>(DB);
impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
    fn clone(&self) -> Snap<salsa::Snapshot<DB>> {
        Snap(self.0.snapshot())
    }
}

pub fn analysis_stats(
    verbosity: Verbosity,
    memory_usage: bool,
    path: &Path,
    only: Option<&str>,
    with_deps: bool,
    randomize: bool,
    parallel: bool,
    load_output_dirs: bool,
    with_proc_macro: bool,
) -> Result<()> {
    let mut rng = {
        let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
        Rand32::new(seed)
    };

    let mut db_load_sw = StopWatch::start().memory(memory_usage);
    let (host, vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?;
    let db = host.raw_database();
    eprintln!("Database loaded {}", db_load_sw.elapsed());

    let mut analysis_sw = StopWatch::start().memory(memory_usage);
    let mut num_crates = 0;
    let mut visited_modules = FxHashSet::default();
    let mut visit_queue = Vec::new();

    let mut krates = Crate::all(db);
    if randomize {
        shuffle(&mut rng, &mut krates);
    }
    for krate in krates {
        let module = krate.root_module(db).expect("crate without root module");
        let file_id = module.definition_source(db).file_id;
        let file_id = file_id.original_file(db);
        let source_root = db.file_source_root(file_id);
        let source_root = db.source_root(source_root);
        if !source_root.is_library || with_deps {
            num_crates += 1;
            visit_queue.push(module);
        }
    }

    if randomize {
        shuffle(&mut rng, &mut visit_queue);
    }

    eprintln!("Crates in this dir: {}", num_crates);
    let mut num_decls = 0;
    let mut funcs = Vec::new();
    while let Some(module) = visit_queue.pop() {
        if visited_modules.insert(module) {
            visit_queue.extend(module.children(db));

            for decl in module.declarations(db) {
                num_decls += 1;
                if let ModuleDef::Function(f) = decl {
                    funcs.push(f);
                }
            }

            for impl_def in module.impl_defs(db) {
                for item in impl_def.items(db) {
                    num_decls += 1;
                    if let AssocItem::Function(f) = item {
                        funcs.push(f);
                    }
                }
            }
        }
    }
    eprintln!("Total modules found: {}", visited_modules.len());
    eprintln!("Total declarations: {}", num_decls);
    eprintln!("Total functions: {}", funcs.len());
    eprintln!("Item Collection: {}", analysis_sw.elapsed());

    if randomize {
        shuffle(&mut rng, &mut funcs);
    }

    let mut bar = match verbosity {
        Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
        _ if parallel => ProgressReport::hidden(),
        _ => ProgressReport::new(funcs.len() as u64),
    };

    if parallel {
        let mut inference_sw = StopWatch::start().memory(memory_usage);
        let snap = Snap(db.snapshot());
        funcs
            .par_iter()
            .map_with(snap, |snap, &f| {
                let f_id = FunctionId::from(f);
                snap.0.body(f_id.into());
                snap.0.infer(f_id.into());
            })
            .count();
        eprintln!("Parallel Inference: {}", inference_sw.elapsed());
    }

    let mut inference_sw = StopWatch::start().memory(memory_usage);
    bar.tick();
    let mut num_exprs = 0;
    let mut num_exprs_unknown = 0;
    let mut num_exprs_partially_unknown = 0;
    let mut num_type_mismatches = 0;
    for f in funcs {
        let name = f.name(db);
        let full_name = f
            .module(db)
            .path_to_root(db)
            .into_iter()
            .rev()
            .filter_map(|it| it.name(db))
            .chain(Some(f.name(db)))
            .join("::");
        if let Some(only_name) = only {
            if name.to_string() != only_name && full_name != only_name {
                continue;
            }
        }
        let mut msg = format!("processing: {}", full_name);
        if verbosity.is_verbose() {
            let src = f.source(db);
            let original_file = src.file_id.original_file(db);
            let path = vfs.file_path(original_file);
            let syntax_range = src.value.syntax().text_range();
            format_to!(msg, " ({} {:?})", path, syntax_range);
        }
        if verbosity.is_spammy() {
            bar.println(msg.to_string());
        }
        bar.set_message(&msg);
        let f_id = FunctionId::from(f);
        let body = db.body(f_id.into());
        let inference_result = db.infer(f_id.into());
        let (previous_exprs, previous_unknown, previous_partially_unknown) =
            (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
        for (expr_id, _) in body.exprs.iter() {
            let ty = &inference_result[expr_id];
            num_exprs += 1;
            if let Ty::Unknown = ty {
                num_exprs_unknown += 1;
            } else {
                let mut is_partially_unknown = false;
                ty.walk(&mut |ty| {
                    if let Ty::Unknown = ty {
                        is_partially_unknown = true;
                    }
                });
                if is_partially_unknown {
                    num_exprs_partially_unknown += 1;
                }
            }
            if only.is_some() && verbosity.is_spammy() {
                // in super-verbose mode for just one function, we print every single expression
                let (_, sm) = db.body_with_source_map(f_id.into());
                let src = sm.expr_syntax(expr_id);
                if let Ok(src) = src {
                    let node = {
                        let root = db.parse_or_expand(src.file_id).unwrap();
                        src.value.to_node(&root)
                    };
                    let original_file = src.file_id.original_file(db);
                    let line_index = host.analysis().file_line_index(original_file).unwrap();
                    let text_range = node.syntax().text_range();
                    let (start, end) = (
                        line_index.line_col(text_range.start()),
                        line_index.line_col(text_range.end()),
                    );
                    bar.println(format!(
                        "{}:{}-{}:{}: {}",
                        start.line + 1,
                        start.col_utf16,
                        end.line + 1,
                        end.col_utf16,
                        ty.display(db)
                    ));
                } else {
                    bar.println(format!("unknown location: {}", ty.display(db)));
                }
            }
            if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
                num_type_mismatches += 1;
                if verbosity.is_verbose() {
                    let (_, sm) = db.body_with_source_map(f_id.into());
                    let src = sm.expr_syntax(expr_id);
                    if let Ok(src) = src {
                        // FIXME: it might be nice to have a function (on Analysis?) that goes from Source<T> -> (LineCol, LineCol) directly
                        // But also, we should just turn the type mismatches into diagnostics and provide these
                        let root = db.parse_or_expand(src.file_id).unwrap();
                        let node = src.map(|e| e.to_node(&root).syntax().clone());
                        let original_range = original_range(db, node.as_ref());
                        let path = vfs.file_path(original_range.file_id);
                        let line_index =
                            host.analysis().file_line_index(original_range.file_id).unwrap();
                        let text_range = original_range.range;
                        let (start, end) = (
                            line_index.line_col(text_range.start()),
                            line_index.line_col(text_range.end()),
                        );
                        bar.println(format!(
                            "{} {}:{}-{}:{}: Expected {}, got {}",
                            path,
                            start.line + 1,
                            start.col_utf16,
                            end.line + 1,
                            end.col_utf16,
                            mismatch.expected.display(db),
                            mismatch.actual.display(db)
                        ));
                    } else {
                        bar.println(format!(
                            "{}: Expected {}, got {}",
                            name,
                            mismatch.expected.display(db),
                            mismatch.actual.display(db)
                        ));
                    }
                }
            }
        }
        if verbosity.is_spammy() {
            bar.println(format!(
                "In {}: {} exprs, {} unknown, {} partial",
                full_name,
                num_exprs - previous_exprs,
                num_exprs_unknown - previous_unknown,
                num_exprs_partially_unknown - previous_partially_unknown
            ));
        }
        bar.inc(1);
    }
    bar.finish_and_clear();
    eprintln!("Total expressions: {}", num_exprs);
    eprintln!(
        "Expressions of unknown type: {} ({}%)",
        num_exprs_unknown,
        if num_exprs > 0 { num_exprs_unknown * 100 / num_exprs } else { 100 }
    );
    report_metric("unknown type", num_exprs_unknown, "#");

    eprintln!(
        "Expressions of partially unknown type: {} ({}%)",
        num_exprs_partially_unknown,
        if num_exprs > 0 { num_exprs_partially_unknown * 100 / num_exprs } else { 100 }
    );

    eprintln!("Type mismatches: {}", num_type_mismatches);
    report_metric("type mismatches", num_type_mismatches, "#");

    eprintln!("Inference: {}", inference_sw.elapsed());

    let total_span = analysis_sw.elapsed();
    eprintln!("Total: {}", total_span);
    report_metric("total time", total_span.time.as_millis() as u64, "ms");
    if let Some(instructions) = total_span.instructions {
        report_metric("total time", instructions, "#instr");
    }
    if let Some(memory) = total_span.memory {
        report_metric("total memory", memory.allocated.megabytes() as u64, "MB");
    }

    if memory_usage {
        print_memory_usage(host, vfs);
    }

    Ok(())
}

fn shuffle<T>(rng: &mut Rand32, slice: &mut [T]) {
    for i in (1..slice.len()).rev() {
        let idx = rng.rand_range(0..i as u32) as usize;
        slice.swap(idx, i)
    }
}
<|endoftext|>