<commit_before>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::HashMap;
use std::io;
use std::mem;

use core::os::process::handle_from_pid;
use core::os::process::windows_child::{Child, ExitStatus, Handle};
use protocol::{self, ShutdownMethod};
use time::{Duration, SteadyTime};
use winapi::shared::minwindef::{DWORD, LPDWORD, MAX_PATH};
use winapi::shared::winerror::{ERROR_FILE_NOT_FOUND, WAIT_TIMEOUT};
use winapi::um::handleapi::{self, INVALID_HANDLE_VALUE};
use winapi::um::processthreadsapi;
use winapi::um::synchapi;
use winapi::um::tlhelp32::{self, LPPROCESSENTRY32W, PROCESSENTRY32W, TH32CS_SNAPPROCESS};
use winapi::um::winbase::{INFINITE, WAIT_OBJECT_0};
use winapi::um::wincon;

use error::{Error, Result};
use service::Service;

const PROCESS_ACTIVE: u32 = 259;
type ProcessTable = HashMap<DWORD, Vec<DWORD>>;

pub struct Process {
    handle: Handle,
    last_status: Option<ExitStatus>,
}

impl Process {
    fn new(handle: Handle) -> Self {
        Process {
            handle: handle,
            last_status: None,
        }
    }

    pub fn id(&self) -> u32 {
        unsafe { processthreadsapi::GetProcessId(self.handle.raw()) as u32 }
    }

    /// Attempt to gracefully terminate a process and then forcefully kill it after
    /// 8 seconds if it has not terminated.
    pub fn kill(&mut self) -> ShutdownMethod {
        if self.status().is_some() {
            return ShutdownMethod::AlreadyExited;
        }
        let ret = unsafe { wincon::GenerateConsoleCtrlEvent(1, self.id()) };
        if ret == 0 {
            debug!(
                "Failed to send ctrl-break to pid {}: {}",
                self.id(),
                io::Error::last_os_error()
            );
        }

        let stop_time = SteadyTime::now() + Duration::seconds(8);
        loop {
            if ret == 0 || SteadyTime::now() > stop_time {
                let proc_table = build_proc_table();
                terminate_process_descendants(&proc_table, self.id());
                return ShutdownMethod::Killed;
            }

            if self.status().is_some() {
                return ShutdownMethod::GracefulTermination;
            }
        }
    }

    pub fn wait(&mut self) -> Result<ExitStatus> {
        unsafe {
            let res = synchapi::WaitForSingleObject(self.handle.raw(), INFINITE);
            if res != WAIT_OBJECT_0 {
                return Err(Error::ExecWait(io::Error::last_os_error()));
            }
            let mut status = 0;
            cvt(processthreadsapi::GetExitCodeProcess(
                self.handle.raw(),
                &mut status,
            )).map_err(Error::ExecWait)?;
            Ok(ExitStatus::from(status))
        }
    }

    pub fn try_wait(&mut self) -> Result<Option<ExitStatus>> {
        unsafe {
            match synchapi::WaitForSingleObject(self.handle.raw(), 0) {
                WAIT_OBJECT_0 => {}
                WAIT_TIMEOUT => return Ok(None),
                _ => return Err(Error::ExecWait(io::Error::last_os_error())),
            }
            let mut status = 0;
            cvt(processthreadsapi::GetExitCodeProcess(
                self.handle.raw(),
                &mut status,
            )).map_err(Error::ExecWait)?;
            Ok(Some(ExitStatus::from(status)))
        }
    }

    fn status(&mut self) -> Option<ExitStatus> {
        if self.last_status.is_some() {
            return self.last_status;
        }
        match exit_code(&self.handle) {
            Some(PROCESS_ACTIVE) => None,
            Some(code) => {
                self.last_status = Some(ExitStatus::from(code));
                self.last_status
            }
            None => None,
        }
    }
}

pub fn run(msg: protocol::Spawn) -> Result<Service> {
    // Supervisors prior to version 0.53.0 pulled in beta versions of
    // powershell. The official 6.0.0 version of powershell changed
    // the name of the powershell binary to pwsh.exe. Here we will
    // first attempt the latest binary name and fall back to the
    // former name.
    match spawn_pwsh("pwsh.exe", msg.clone()) {
        Ok(service) => Ok(service),
        Err(err) => {
            if err.raw_os_error() == Some(ERROR_FILE_NOT_FOUND as i32) {
                spawn_pwsh("powershell.exe", msg).map_err(Error::Spawn)
            } else {
                Err(Error::Spawn(err))
            }
        }
    }
}

fn spawn_pwsh(ps_binary_name: &str, mut msg: protocol::Spawn) -> io::Result<Service> {
    debug!("launcher is spawning {}", msg.get_binary());
    let ps_cmd = format!("iex $(gc {} | out-string)", msg.get_binary());
    let password = if msg.get_svc_password().is_empty() {
        None
    } else {
        Some(msg.take_svc_password())
    };
    match Child::spawn(
        ps_binary_name,
        vec!["-NonInteractive", "-command", ps_cmd.as_str()],
        msg.get_env(),
        msg.get_svc_user(),
        password,
    ) {
        Ok(child) => {
            let process = Process::new(child.handle);
            Ok(Service::new(msg, process, child.stdout, child.stderr))
        }
        Err(_) => Err(io::Error::last_os_error()),
    }
}

fn build_proc_table() -> ProcessTable {
    let processes_snap_handle =
        unsafe { tlhelp32::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };

    if processes_snap_handle == INVALID_HANDLE_VALUE {
        error!(
            "Failed to call CreateToolhelp32Snapshot: {}",
            io::Error::last_os_error()
        );
        return ProcessTable::new();
    }
    let mut table = ProcessTable::new();
    let mut process_entry = PROCESSENTRY32W {
        dwSize: mem::size_of::<PROCESSENTRY32W>() as u32,
        cntUsage: 0,
        th32ProcessID: 0,
        th32DefaultHeapID: 0,
        th32ModuleID: 0,
        cntThreads: 0,
        th32ParentProcessID: 0,
        pcPriClassBase: 0,
        dwFlags: 0,
        szExeFile: [0; MAX_PATH],
    };
    // Get the first process from the snapshot.
    match unsafe {
        tlhelp32::Process32FirstW(
            processes_snap_handle,
            &mut process_entry as LPPROCESSENTRY32W,
        )
    } {
        1 => {
            // First process worked, loop to find the process with the correct name.
            let mut process_success: i32 = 1;
            // Loop through all processes until we find one where `szExeFile` == `name`.
            while process_success == 1 {
                let children = table
                    .entry(process_entry.th32ParentProcessID)
                    .or_insert(Vec::new());
                (*children).push(process_entry.th32ProcessID);
                process_success =
                    unsafe { tlhelp32::Process32NextW(processes_snap_handle, &mut process_entry) };
            }
            unsafe { handleapi::CloseHandle(processes_snap_handle) };
        }
        0 | _ => unsafe {
            handleapi::CloseHandle(processes_snap_handle);
        },
    }
    table
}

fn cvt(i: i32) -> io::Result<i32> {
    if i == 0 {
        Err(io::Error::last_os_error())
    } else {
        Ok(i)
    }
}

fn exit_code(handle: &Handle) -> Option<u32> {
    let mut exit_code: u32 = 0;
    unsafe {
        let ret = processthreadsapi::GetExitCodeProcess(handle.raw(), &mut exit_code as LPDWORD);
        if ret == 0 {
            error!(
                "Failed to retrieve Exit Code: {}",
                io::Error::last_os_error()
            );
            return None;
        }
    }
    Some(exit_code)
}

fn terminate_process_descendants(table: &ProcessTable, pid: DWORD) {
    if let Some(children) = table.get(&pid) {
        for child in children {
            terminate_process_descendants(table, child.clone());
        }
    }
    unsafe {
        match handle_from_pid(pid) {
            Some(h) => {
                if processthreadsapi::TerminateProcess(h, 1) == 0 {
                    error!(
                        "Failed to call TerminateProcess on pid {}: {}",
                        pid,
                        io::Error::last_os_error()
                    );
                }
            }
            None => {}
        }
    }
}
<commit_msg>Fix return type for wait/try_wait on Windows<commit_after>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::HashMap;
use std::io;
use std::mem;

use core::os::process::handle_from_pid;
use core::os::process::windows_child::{Child, ExitStatus, Handle};
use protocol::{self, ShutdownMethod};
use time::{Duration, SteadyTime};
use winapi::shared::minwindef::{DWORD, LPDWORD, MAX_PATH};
use winapi::shared::winerror::{ERROR_FILE_NOT_FOUND, WAIT_TIMEOUT};
use winapi::um::handleapi::{self, INVALID_HANDLE_VALUE};
use winapi::um::processthreadsapi;
use winapi::um::synchapi;
use winapi::um::tlhelp32::{self, LPPROCESSENTRY32W, PROCESSENTRY32W, TH32CS_SNAPPROCESS};
use winapi::um::winbase::{INFINITE, WAIT_OBJECT_0};
use winapi::um::wincon;

use error::{Error, Result};
use service::Service;

const PROCESS_ACTIVE: u32 = 259;
type ProcessTable = HashMap<DWORD, Vec<DWORD>>;

pub struct Process {
    handle: Handle,
    last_status: Option<ExitStatus>,
}

impl Process {
    fn new(handle: Handle) -> Self {
        Process {
            handle: handle,
            last_status: None,
        }
    }

    pub fn id(&self) -> u32 {
        unsafe { processthreadsapi::GetProcessId(self.handle.raw()) as u32 }
    }

    /// Attempt to gracefully terminate a process and then forcefully kill it after
    /// 8 seconds if it has not terminated.
    pub fn kill(&mut self) -> ShutdownMethod {
        if self.status().is_some() {
            return ShutdownMethod::AlreadyExited;
        }
        let ret = unsafe { wincon::GenerateConsoleCtrlEvent(1, self.id()) };
        if ret == 0 {
            debug!(
                "Failed to send ctrl-break to pid {}: {}",
                self.id(),
                io::Error::last_os_error()
            );
        }

        let stop_time = SteadyTime::now() + Duration::seconds(8);
        loop {
            if ret == 0 || SteadyTime::now() > stop_time {
                let proc_table = build_proc_table();
                terminate_process_descendants(&proc_table, self.id());
                return ShutdownMethod::Killed;
            }

            if self.status().is_some() {
                return ShutdownMethod::GracefulTermination;
            }
        }
    }

    pub fn wait(&mut self) -> io::Result<ExitStatus> {
        unsafe {
            let res = synchapi::WaitForSingleObject(self.handle.raw(), INFINITE);
            if res != WAIT_OBJECT_0 {
                return Err(Error::ExecWait(io::Error::last_os_error()));
            }
            let mut status = 0;
            cvt(processthreadsapi::GetExitCodeProcess(
                self.handle.raw(),
                &mut status,
            )).map_err(Error::ExecWait)?;
            Ok(ExitStatus::from(status))
        }
    }

    pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
        unsafe {
            match synchapi::WaitForSingleObject(self.handle.raw(), 0) {
                WAIT_OBJECT_0 => {}
                WAIT_TIMEOUT => return Ok(None),
                _ => return Err(Error::ExecWait(io::Error::last_os_error())),
            }
            let mut status = 0;
            cvt(processthreadsapi::GetExitCodeProcess(
                self.handle.raw(),
                &mut status,
            )).map_err(Error::ExecWait)?;
            Ok(Some(ExitStatus::from(status)))
        }
    }

    fn status(&mut self) -> Option<ExitStatus> {
        if self.last_status.is_some() {
            return self.last_status;
        }
        match exit_code(&self.handle) {
            Some(PROCESS_ACTIVE) => None,
            Some(code) => {
                self.last_status = Some(ExitStatus::from(code));
                self.last_status
            }
            None => None,
        }
    }
}

pub fn run(msg: protocol::Spawn) -> Result<Service> {
    // Supervisors prior to version 0.53.0 pulled in beta versions of
    // powershell. The official 6.0.0 version of powershell changed
    // the name of the powershell binary to pwsh.exe. Here we will
    // first attempt the latest binary name and fall back to the
    // former name.
    match spawn_pwsh("pwsh.exe", msg.clone()) {
        Ok(service) => Ok(service),
        Err(err) => {
            if err.raw_os_error() == Some(ERROR_FILE_NOT_FOUND as i32) {
                spawn_pwsh("powershell.exe", msg).map_err(Error::Spawn)
            } else {
                Err(Error::Spawn(err))
            }
        }
    }
}

fn spawn_pwsh(ps_binary_name: &str, mut msg: protocol::Spawn) -> io::Result<Service> {
    debug!("launcher is spawning {}", msg.get_binary());
    let ps_cmd = format!("iex $(gc {} | out-string)", msg.get_binary());
    let password = if msg.get_svc_password().is_empty() {
        None
    } else {
        Some(msg.take_svc_password())
    };
    match Child::spawn(
        ps_binary_name,
        vec!["-NonInteractive", "-command", ps_cmd.as_str()],
        msg.get_env(),
        msg.get_svc_user(),
        password,
    ) {
        Ok(child) => {
            let process = Process::new(child.handle);
            Ok(Service::new(msg, process, child.stdout, child.stderr))
        }
        Err(_) => Err(io::Error::last_os_error()),
    }
}

fn build_proc_table() -> ProcessTable {
    let processes_snap_handle =
        unsafe { tlhelp32::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };

    if processes_snap_handle == INVALID_HANDLE_VALUE {
        error!(
            "Failed to call CreateToolhelp32Snapshot: {}",
            io::Error::last_os_error()
        );
        return ProcessTable::new();
    }
    let mut table = ProcessTable::new();
    let mut process_entry = PROCESSENTRY32W {
        dwSize: mem::size_of::<PROCESSENTRY32W>() as u32,
        cntUsage: 0,
        th32ProcessID: 0,
        th32DefaultHeapID: 0,
        th32ModuleID: 0,
        cntThreads: 0,
        th32ParentProcessID: 0,
        pcPriClassBase: 0,
        dwFlags: 0,
        szExeFile: [0; MAX_PATH],
    };
    // Get the first process from the snapshot.
    match unsafe {
        tlhelp32::Process32FirstW(
            processes_snap_handle,
            &mut process_entry as LPPROCESSENTRY32W,
        )
    } {
        1 => {
            // First process worked, loop to find the process with the correct name.
            let mut process_success: i32 = 1;
            // Loop through all processes until we find one where `szExeFile` == `name`.
            while process_success == 1 {
                let children = table
                    .entry(process_entry.th32ParentProcessID)
                    .or_insert(Vec::new());
                (*children).push(process_entry.th32ProcessID);
                process_success =
                    unsafe { tlhelp32::Process32NextW(processes_snap_handle, &mut process_entry) };
            }
            unsafe { handleapi::CloseHandle(processes_snap_handle) };
        }
        0 | _ => unsafe {
            handleapi::CloseHandle(processes_snap_handle);
        },
    }
    table
}

fn cvt(i: i32) -> io::Result<i32> {
    if i == 0 {
        Err(io::Error::last_os_error())
    } else {
        Ok(i)
    }
}

fn exit_code(handle: &Handle) -> Option<u32> {
    let mut exit_code: u32 = 0;
    unsafe {
        let ret = processthreadsapi::GetExitCodeProcess(handle.raw(), &mut exit_code as LPDWORD);
        if ret == 0 {
            error!(
                "Failed to retrieve Exit Code: {}",
                io::Error::last_os_error()
            );
            return None;
        }
    }
    Some(exit_code)
}

fn terminate_process_descendants(table: &ProcessTable, pid: DWORD) {
    if let Some(children) = table.get(&pid) {
        for child in children {
            terminate_process_descendants(table, child.clone());
        }
    }
    unsafe {
        match handle_from_pid(pid) {
            Some(h) => {
                if processthreadsapi::TerminateProcess(h, 1) == 0 {
                    error!(
                        "Failed to call TerminateProcess on pid {}: {}",
                        pid,
                        io::Error::last_os_error()
                    );
                }
            }
            None => {}
        }
    }
}
<|endoftext|>


<commit_before>import std::generic_os::setenv;
import std::generic_os::getenv;
import std::option;

#[test]
fn test_setenv() {
    setenv("NAME", "VALUE");
    assert getenv("NAME") == option::some("VALUE");
}

#[test]
fn test_setenv_overwrite() {
    setenv("NAME", "1");
    setenv("NAME", "2");
    assert getenv("NAME") == option::some("2");
}

// Windows GetEnvironmentVariable requires some extra work to make sure
// the buffer the variable is copied into is the right size
#[test]
fn test_getenv_big() {
    auto s = "";
    auto i = 0;
    while (i < 100) {
        s += "aaaaaaaaaa";
        i += 1;
    }
    setenv("NAME", s);
    assert getenv("NAME") == option::some(s);
}

// Local Variables:
// mode: rust;
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C .. 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
<commit_msg>Make the setenv tests threadsafe(ish)<commit_after>import std::generic_os::setenv;
import std::generic_os::getenv;
import std::option;

#[test]
fn test_setenv() {
    // NB: Each test of setenv needs to use different variable names or the
    // tests will not be threadsafe
    setenv("NAME1", "VALUE");
    assert getenv("NAME1") == option::some("VALUE");
}

#[test]
fn test_setenv_overwrite() {
    setenv("NAME2", "1");
    setenv("NAME2", "2");
    assert getenv("NAME2") == option::some("2");
}

// Windows GetEnvironmentVariable requires some extra work to make sure
// the buffer the variable is copied into is the right size
#[test]
fn test_getenv_big() {
    auto s = "";
    auto i = 0;
    while (i < 100) {
        s += "aaaaaaaaaa";
        i += 1;
    }
    setenv("NAME3", s);
    assert getenv("NAME3") == option::some(s);
}

// Local Variables:
// mode: rust;
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C .. 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
<|endoftext|>


<commit_before>//! Compute eigenvalues and eigenvectors of general, non-symmetric matrices.
use lapack::c::{sgeev, dgeev, cgeev, zgeev};
use impl_prelude::*;
use super::types::{EigenError, Solution};

/// Scalar trait for computing eigenvalues.
///
/// In order to extract eigenvalues or eigenvectors from a matrix,
/// that matrix with must have  entries implementing the `Eigen` trait.
pub trait Eigen: Sized + Clone {
    /// The type of the eigenvalues.
    type EigenValue;

    /// The solution structure of the eigenvalue computation.
    type Solution;

    /// Return the eigenvalues and, optionally, the left and/or right
    /// eigenvectors of a general matrix.
    ///
    /// The entries in the input matrix `mat` are modified when
    /// calculating the eigenvalues.
    fn compute_into<D>(mut mat: ArrayBase<D, Ix2>,
                       compute_left: bool,
                       compute_right: bool)
                       -> Result<Self::Solution, EigenError>
        where D: DataOwned<Elem = Self> + DataMut<Elem = Self>;

    /// Return the eigenvvalues and, optionally, the eigenvectors of a general matrix.
    fn compute<D>(mat: &ArrayBase<D, Ix2>,
                  compute_left: bool,
                  compute_right: bool)
                  -> Result<Self::Solution, EigenError>
        where D: Data<Elem = Self>
    {
        let vec: Vec<Self> = mat.iter().cloned().collect();
        let new_mat = Array::from_shape_vec(mat.dim(), vec).unwrap();
        Self::compute_into(new_mat, compute_left, compute_right)
    }
}

/// Macro for implementing the Eigen trait on real-valued matrices.
macro_rules! impl_eigen_real {
    ($impl_type:ident, $eigv_type:ident, $func:ident)  => (
        impl Eigen for $impl_type {

            type EigenValue = $eigv_type;
            type Solution = Solution<$impl_type, $eigv_type>;

            fn compute_into<D>(mut mat: ArrayBase<D, Ix2>,
                               compute_left: bool, compute_right: bool) ->
                Result<Self::Solution, EigenError>
                where D: DataMut<Elem=Self> + DataOwned<Elem=Self> {

                let dim = mat.dim();
                if dim.0 != dim.1 {
                    return Err(EigenError::NotSquare);
                }
                let n = mat.dim().0 as i32;

                let (data_slice, layout, ld) = match slice_and_layout_mut(&mut mat) {
                    Some(s) => s,
                    None => return Err(EigenError::BadLayout)
                };

                let mut vl = matrix_with_layout(if compute_left { dim } else { (0, 0) }, layout);
                let mut vr = matrix_with_layout(if compute_right { dim } else { (0, 0) }, layout);

                let mut values_real_imag = vec![0.0; 2 * n as usize];
                let (mut values_real, mut values_imag) = values_real_imag.split_at_mut(n as usize);

                let vl_opt = (if compute_left {'V'} else {'N'}) as u8;
                let vr_opt = (if compute_right {'V'} else {'N'}) as u8;

                let info = $func(layout, vl_opt, vr_opt, n, data_slice,
                                 ld as i32, &mut values_real, &mut values_imag,
                                 vl.as_slice_mut().unwrap(), n,
                                 vr.as_slice_mut().unwrap(), n);

                if info == 0 {
                    let vals: Vec<_> = values_real.iter().zip(values_imag.iter())
                        .map(|(x, y)| Self::EigenValue::new(*x, *y)).collect();
                    Ok(Solution {
                        values: ArrayBase::from_vec(vals),
                        left_vectors: if compute_left { Some(vl) } else { None },
                        right_vectors: if compute_right { Some(vr) } else { None }})
                } else if info < 0 {
                    Err(EigenError::IllegalParameter(-info))
                } else {
                    Err(EigenError::Failed)
                }
            }
        }
    )
}

impl_eigen_real!(f32, c32, sgeev);
impl_eigen_real!(f64, c64, dgeev);

macro_rules! impl_eigen_complex {
    ($impl_type:ident, $func:ident)  => (
        impl Eigen for $impl_type {

            type EigenValue = $impl_type;
            type Solution = Solution<$impl_type, $impl_type>;

            fn compute_into<D>(mut mat: ArrayBase<D, Ix2>,
                               compute_left: bool, compute_right: bool)
                               -> Result<Self::Solution, EigenError>
                where D: DataMut<Elem=Self> + DataOwned<Elem=Self> {

                let dim = mat.dim();
                if dim.0 != dim.1 {
                    return Err(EigenError::NotSquare);
                }

                let n = dim.0 as i32;

                let (data_slice, layout, ld) = match slice_and_layout_mut(&mut mat) {
                    Some(s) => s,
                    None => return Err(EigenError::BadLayout)
                };

                let mut vl = matrix_with_layout(if compute_left { dim } else { (0, 0) }, layout);
                let mut vr = matrix_with_layout(if compute_right { dim } else { (0, 0) }, layout);

                let mut values = Array::default(n as Ix);

                let vl_opt = (if compute_left {'V'} else {'N'}) as u8;
                let vr_opt = (if compute_right {'V'} else {'N'}) as u8;

                let info = $func(layout, vl_opt, vr_opt, n,
                                 data_slice, ld as i32,
                                 values.as_slice_mut().unwrap(),
                                 vl.as_slice_mut().unwrap(), n,
                                 vr.as_slice_mut().unwrap(), n);

                if info  == 0 {
                    Ok(Solution {
                        values: values,
                        left_vectors: if compute_left { Some(vl) } else { None },
                        right_vectors: if compute_right { Some(vr) } else { None }})
                } else if info < 0 {
                    Err(EigenError::IllegalParameter(-info))
                } else {
                    Err(EigenError::Failed)
                }
            }
        }
    )
}

impl_eigen_complex!(c32, cgeev);
impl_eigen_complex!(c64, zgeev);

#[cfg(test)]
mod tests {}
<commit_msg>fix warning about pattern in declaration<commit_after>//! Compute eigenvalues and eigenvectors of general, non-symmetric matrices.
use lapack::c::{sgeev, dgeev, cgeev, zgeev};
use impl_prelude::*;
use super::types::{EigenError, Solution};

/// Scalar trait for computing eigenvalues.
///
/// In order to extract eigenvalues or eigenvectors from a matrix,
/// that matrix with must have  entries implementing the `Eigen` trait.
pub trait Eigen: Sized + Clone {
    /// The type of the eigenvalues.
    type EigenValue;

    /// The solution structure of the eigenvalue computation.
    type Solution;

    /// Return the eigenvalues and, optionally, the left and/or right
    /// eigenvectors of a general matrix.
    ///
    /// The entries in the input matrix `mat` are modified when
    /// calculating the eigenvalues.
    fn compute_into<D>(mat: ArrayBase<D, Ix2>,
                       compute_left: bool,
                       compute_right: bool)
                       -> Result<Self::Solution, EigenError>
        where D: DataOwned<Elem = Self> + DataMut<Elem = Self>;

    /// Return the eigenvvalues and, optionally, the eigenvectors of a general matrix.
    fn compute<D>(mat: &ArrayBase<D, Ix2>,
                  compute_left: bool,
                  compute_right: bool)
                  -> Result<Self::Solution, EigenError>
        where D: Data<Elem = Self>
    {
        let vec: Vec<Self> = mat.iter().cloned().collect();
        let new_mat = Array::from_shape_vec(mat.dim(), vec).unwrap();
        Self::compute_into(new_mat, compute_left, compute_right)
    }
}

/// Macro for implementing the Eigen trait on real-valued matrices.
macro_rules! impl_eigen_real {
    ($impl_type:ident, $eigv_type:ident, $func:ident)  => (
        impl Eigen for $impl_type {

            type EigenValue = $eigv_type;
            type Solution = Solution<$impl_type, $eigv_type>;

            fn compute_into<D>(mut mat: ArrayBase<D, Ix2>,
                               compute_left: bool, compute_right: bool) ->
                Result<Self::Solution, EigenError>
                where D: DataMut<Elem=Self> + DataOwned<Elem=Self> {

                let dim = mat.dim();
                if dim.0 != dim.1 {
                    return Err(EigenError::NotSquare);
                }
                let n = mat.dim().0 as i32;

                let (data_slice, layout, ld) = match slice_and_layout_mut(&mut mat) {
                    Some(s) => s,
                    None => return Err(EigenError::BadLayout)
                };

                let mut vl = matrix_with_layout(if compute_left { dim } else { (0, 0) }, layout);
                let mut vr = matrix_with_layout(if compute_right { dim } else { (0, 0) }, layout);

                let mut values_real_imag = vec![0.0; 2 * n as usize];
                let (mut values_real, mut values_imag) = values_real_imag.split_at_mut(n as usize);

                let vl_opt = (if compute_left {'V'} else {'N'}) as u8;
                let vr_opt = (if compute_right {'V'} else {'N'}) as u8;

                let info = $func(layout, vl_opt, vr_opt, n, data_slice,
                                 ld as i32, &mut values_real, &mut values_imag,
                                 vl.as_slice_mut().unwrap(), n,
                                 vr.as_slice_mut().unwrap(), n);

                if info == 0 {
                    let vals: Vec<_> = values_real.iter().zip(values_imag.iter())
                        .map(|(x, y)| Self::EigenValue::new(*x, *y)).collect();
                    Ok(Solution {
                        values: ArrayBase::from_vec(vals),
                        left_vectors: if compute_left { Some(vl) } else { None },
                        right_vectors: if compute_right { Some(vr) } else { None }})
                } else if info < 0 {
                    Err(EigenError::IllegalParameter(-info))
                } else {
                    Err(EigenError::Failed)
                }
            }
        }
    )
}

impl_eigen_real!(f32, c32, sgeev);
impl_eigen_real!(f64, c64, dgeev);

macro_rules! impl_eigen_complex {
    ($impl_type:ident, $func:ident)  => (
        impl Eigen for $impl_type {

            type EigenValue = $impl_type;
            type Solution = Solution<$impl_type, $impl_type>;

            fn compute_into<D>(mut mat: ArrayBase<D, Ix2>,
                               compute_left: bool, compute_right: bool)
                               -> Result<Self::Solution, EigenError>
                where D: DataMut<Elem=Self> + DataOwned<Elem=Self> {

                let dim = mat.dim();
                if dim.0 != dim.1 {
                    return Err(EigenError::NotSquare);
                }

                let n = dim.0 as i32;

                let (data_slice, layout, ld) = match slice_and_layout_mut(&mut mat) {
                    Some(s) => s,
                    None => return Err(EigenError::BadLayout)
                };

                let mut vl = matrix_with_layout(if compute_left { dim } else { (0, 0) }, layout);
                let mut vr = matrix_with_layout(if compute_right { dim } else { (0, 0) }, layout);

                let mut values = Array::default(n as Ix);

                let vl_opt = (if compute_left {'V'} else {'N'}) as u8;
                let vr_opt = (if compute_right {'V'} else {'N'}) as u8;

                let info = $func(layout, vl_opt, vr_opt, n,
                                 data_slice, ld as i32,
                                 values.as_slice_mut().unwrap(),
                                 vl.as_slice_mut().unwrap(), n,
                                 vr.as_slice_mut().unwrap(), n);

                if info  == 0 {
                    Ok(Solution {
                        values: values,
                        left_vectors: if compute_left { Some(vl) } else { None },
                        right_vectors: if compute_right { Some(vr) } else { None }})
                } else if info < 0 {
                    Err(EigenError::IllegalParameter(-info))
                } else {
                    Err(EigenError::Failed)
                }
            }
        }
    )
}

impl_eigen_complex!(c32, cgeev);
impl_eigen_complex!(c64, zgeev);

#[cfg(test)]
mod tests {}
<|endoftext|>


<commit_before>use ast::{Expr, ExprItem, Term};
use math::rich_number::RichNumber;
use eval::fixity::Fixity;
use eval::context_item::ContextItem;
use eval::eval_context::{EvalContext, EvalError};

#[derive(Debug, Clone)]
pub struct EvalTreeNode {
    item: ContextItem,
    children: Vec<EvalTreeNode>,
}

impl EvalTreeNode {
    pub fn new(item: ContextItem) -> EvalTreeNode {
        EvalTreeNode {
            item: item,
            children: Vec::new(),
        }
    }

    pub fn from_expr(context: &EvalContext, expr: Expr) -> Result<EvalTreeNode, EvalError> {
        let mut nodes = try!(EvalTreeNode::convert_to_nodes(context, expr));

        while nodes.len() != 1 {
            let node_index = EvalTreeNode::left_highest_precedence(&nodes);
            if node_index.is_none() {
                return Err(EvalError::UnexpectedSymbols);
            }
            let node_index = node_index.unwrap();

            let params = try!(EvalTreeNode::drain_params(&mut nodes, node_index));

            let mut node = &mut nodes[node_index];
            node.children = params;
        }

        debug_assert!(nodes.len() == 1);
        Ok(nodes.pop().unwrap())
    }

    fn convert_to_nodes(context: &EvalContext, expr: Expr) -> Result<Vec<EvalTreeNode>, EvalError> {
        let mut nodes: Vec<EvalTreeNode> = Vec::new();

        for item in expr.into_iter() {
            nodes.push(match item {
                ExprItem::AnonymousFunction(function) => {
                    EvalTreeNode::new(ContextItem::function_defaults(function))
                },
                ExprItem::SingleTerm(term) => match term {
                    Term::Symbol(sym) => {
                        let item = context.get(&sym);
                        if item.is_none() {
                            return Err(EvalError::UndefinedSymbol(sym));
                        }
                        EvalTreeNode::new(item.unwrap())
                    },
                    Term::Number(num) => {
                        EvalTreeNode::new(
                            ContextItem::Number(
                                RichNumber::from(num)
                            )
                        )
                    },
                    Term::StringLiteral(value) => {
                        EvalTreeNode::new(
                            ContextItem::Constant(value)
                        )
                    },
                },
                ExprItem::Group(expr) => try!(EvalTreeNode::from_expr(context, expr)),
            });
        }

        Ok(nodes)
    }

    /// Returns the index of the leftmost node with the highest precedence
    /// Only returns ContextItems with precedence
    fn left_highest_precedence(nodes: &Vec<EvalTreeNode>) -> Option<usize> {
        // (index, precedence)
        let mut highest: Option<(usize, u8)> = None;

        for (i, node) in nodes.iter().enumerate() {
            // This node has already been taken care of
            if !node.children.is_empty() {
                continue;
            }

            let precedence = node.item.resolve_precedence();
            if precedence.is_none() {
                continue;
            }
            let precedence = *precedence.unwrap();

            // This MUST be less than, NOT less than or equal to to get the
            // leftmost item
            if highest.is_none() || highest.unwrap().1 < precedence {
                highest = Some((i, precedence));
            }
        }

        highest.map(|x| x.0)
    }

    // Removes the nodes adjacent to the node_index (but not including the
    // node_index) that correspond to the number of parameters expected
    // by that node in the direction specified by that node's fixity
    fn drain_params(nodes: &mut Vec<EvalTreeNode>, node_index: usize) -> Result<Vec<EvalTreeNode>, EvalError> {
        let fixity;
        let params;
        {
            let node = &nodes[node_index];

            // unwrap() is safe here because node item should be a definition or built in
            fixity = node.item.resolve_fixity().unwrap();
            params = node.item.resolve_params().unwrap();
            debug_assert!(params != 0);
        }

        Ok(match fixity {
            Fixity::Infix => {
                // Infix functions MUST have 2 parameters
                if params != 2 {
                    return Err(EvalError::InvalidSymbolDefinition {
                        expected_params: 2,
                        actual_params: params,
                    });
                }

                // It is vital that the second remove happen before the
                // first because this operation shifts all indexes to the left
                let second = nodes.remove(node_index + 1);
                let first = nodes.remove(node_index - 1);

                vec![first, second]
            },
            Fixity::Prefix => {
                let start;
                let end = node_index;

                // params > node_index would result in a negative sum
                if params > node_index || end >= nodes.len() {
                    return Err(EvalError::ExpectedParams(params));
                }
                // need to declare this here to avoid overflow
                start = node_index - params;

                nodes.drain(start..end).collect()
            },
            Fixity::Postfix => {
                // No need to worry about node_index being less than zero because we
                // are adding 1 here
                let start = node_index + 1;
                // The +1 in the end of the range is because ranges stop
                // at the index 1 before the end
                let end = node_index + params + 1;
                if end >= nodes.len() {
                    return Err(EvalError::ExpectedParams(params));
                }
                nodes.drain(start..end).collect()
            },
        })
    }
}

<commit_msg>Added test for eval_tree_node<commit_after>use ast::{Expr, ExprItem, Term};
use math::rich_number::RichNumber;
use eval::fixity::Fixity;
use eval::context_item::ContextItem;
use eval::eval_context::{EvalContext, EvalError};

#[derive(Debug, Clone)]
pub struct EvalTreeNode {
    item: ContextItem,
    children: Vec<EvalTreeNode>,
}

impl EvalTreeNode {
    pub fn new(item: ContextItem) -> EvalTreeNode {
        EvalTreeNode {
            item: item,
            children: Vec::new(),
        }
    }

    pub fn with_children(item: ContextItem, children: Vec<EvalTreeNode>) -> EvalTreeNode {
        let mut node = EvalTreeNode::new(item);
        node.children = children;

        node
    }

    pub fn from_expr(context: &EvalContext, expr: Expr) -> Result<EvalTreeNode, EvalError> {
        let mut nodes = try!(EvalTreeNode::convert_to_nodes(context, expr));

        while nodes.len() != 1 {
            let node_index = EvalTreeNode::left_highest_precedence(&nodes);
            if node_index.is_none() {
                return Err(EvalError::UnexpectedSymbols);
            }
            let node_index = node_index.unwrap();

            let params = try!(EvalTreeNode::drain_params(&mut nodes, node_index));

            let mut node = &mut nodes[node_index];
            node.children = params;
        }

        debug_assert!(nodes.len() == 1);
        Ok(nodes.pop().unwrap())
    }

    fn convert_to_nodes(context: &EvalContext, expr: Expr) -> Result<Vec<EvalTreeNode>, EvalError> {
        let mut nodes: Vec<EvalTreeNode> = Vec::new();

        for item in expr.into_iter() {
            nodes.push(match item {
                ExprItem::AnonymousFunction(function) => {
                    EvalTreeNode::new(ContextItem::function_defaults(function))
                },
                ExprItem::SingleTerm(term) => match term {
                    Term::Symbol(sym) => {
                        let item = context.get(&sym);
                        if item.is_none() {
                            return Err(EvalError::UndefinedSymbol(sym));
                        }
                        EvalTreeNode::new(item.unwrap())
                    },
                    Term::Number(num) => {
                        EvalTreeNode::new(
                            ContextItem::Number(
                                RichNumber::from(num)
                            )
                        )
                    },
                    Term::StringLiteral(value) => {
                        EvalTreeNode::new(
                            ContextItem::Constant(value)
                        )
                    },
                },
                ExprItem::Group(expr) => try!(EvalTreeNode::from_expr(context, expr)),
            });
        }

        Ok(nodes)
    }

    /// Returns the index of the leftmost node with the highest precedence
    /// Only returns ContextItems with precedence
    fn left_highest_precedence(nodes: &Vec<EvalTreeNode>) -> Option<usize> {
        // (index, precedence)
        let mut highest: Option<(usize, u8)> = None;

        for (i, node) in nodes.iter().enumerate() {
            // This node has already been taken care of
            if !node.children.is_empty() {
                continue;
            }

            let precedence = node.item.resolve_precedence();
            if precedence.is_none() {
                continue;
            }
            let precedence = *precedence.unwrap();

            // This MUST be less than, NOT less than or equal to to get the
            // leftmost item
            if highest.is_none() || highest.unwrap().1 < precedence {
                highest = Some((i, precedence));
            }
        }

        highest.map(|x| x.0)
    }

    // Removes the nodes adjacent to the node_index (but not including the
    // node_index) that correspond to the number of parameters expected
    // by that node in the direction specified by that node's fixity
    fn drain_params(nodes: &mut Vec<EvalTreeNode>, node_index: usize) -> Result<Vec<EvalTreeNode>, EvalError> {
        let fixity;
        let params;
        {
            let node = &nodes[node_index];

            // unwrap() is safe here because node item should be a definition or built in
            fixity = node.item.resolve_fixity().unwrap();
            params = node.item.resolve_params().unwrap();
            debug_assert!(params != 0);
        }

        Ok(match fixity {
            Fixity::Infix => {
                // Infix functions MUST have 2 parameters
                if params != 2 {
                    return Err(EvalError::InvalidSymbolDefinition {
                        expected_params: 2,
                        actual_params: params,
                    });
                }

                // It is vital that the second remove happen before the
                // first because this operation shifts all indexes to the left
                let second = nodes.remove(node_index + 1);
                let first = nodes.remove(node_index - 1);

                vec![first, second]
            },
            Fixity::Prefix => {
                let start;
                let end = node_index;

                // params > node_index would result in a negative sum
                if params > node_index || end >= nodes.len() {
                    return Err(EvalError::ExpectedParams(params));
                }
                // need to declare this here to avoid overflow
                start = node_index - params;

                nodes.drain(start..end).collect()
            },
            Fixity::Postfix => {
                // No need to worry about node_index being less than zero because we
                // are adding 1 here
                let start = node_index + 1;
                // The +1 in the end of the range is because ranges stop
                // at the index 1 before the end
                let end = node_index + params + 1;
                if end >= nodes.len() {
                    return Err(EvalError::ExpectedParams(params));
                }
                nodes.drain(start..end).collect()
            },
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ast::*;

    use eval::fixity::Fixity;
    use eval::context_item::ContextItem;
    use eval::eval_context::EvalContext;
    use math::rich_number::RichNumber;

    #[test]
    fn creates_tree_with_correct_precedence() {
        // f 3 + (4 * 8 + 99) - ((67)) / (x - g 4) + 32
        let expr: Expr = vec![
            ExprItem::SingleTerm(Term::Symbol("f".to_owned())),
            ExprItem::SingleTerm(Term::Number(3f64)),
            ExprItem::SingleTerm(Term::Symbol("+".to_owned())),
            ExprItem::Group(vec![
                ExprItem::SingleTerm(Term::Number(4f64)),
                ExprItem::SingleTerm(Term::Symbol("*".to_owned())),
                ExprItem::SingleTerm(Term::Number(8f64)),
                ExprItem::SingleTerm(Term::Symbol("+".to_owned())),
                ExprItem::SingleTerm(Term::Number(99f64)),
            ]),
            ExprItem::SingleTerm(Term::Symbol("-".to_owned())),
            ExprItem::Group(vec![
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(Term::Number(67f64)),
                ])
            ]),
            ExprItem::SingleTerm(Term::Symbol("/".to_owned())),
            ExprItem::Group(vec![
                ExprItem::SingleTerm(Term::Symbol("x".to_owned())),
                ExprItem::SingleTerm(Term::Symbol("-".to_owned())),
                ExprItem::SingleTerm(Term::Symbol("g".to_owned())),
                ExprItem::SingleTerm(Term::Number(4f64)),
            ]),
            ExprItem::SingleTerm(Term::Symbol("+".to_owned())),
            ExprItem::SingleTerm(Term::Number(32f64)),
        ];

        let mut context = basic_context();

        let nleaf = |n| EvalTreeNode::new(ContextItem::Number(RichNumber::from(n)));
        let lookup = |name| context.get(name).unwrap();

        let expected_tree = EvalTreeNode::with_children(lookup("-"), vec![
            EvalTreeNode::with_children(lookup("+"), vec![
                EvalTreeNode::with_children(lookup("f"), vec![
                    nleaf(3),
                ]),
                EvalTreeNode::with_children(lookup("+"), vec![
                    EvalTreeNode::with_children(lookup("*"), vec![
                        nleaf(4),
                        nleaf(8),
                    ]),
                    nleaf(99),
                ]),
            ]),
            EvalTreeNode::with_children(lookup("+"), vec![
                EvalTreeNode::with_children(lookup("/"), vec![
                    nleaf(67),
                    EvalTreeNode::with_children(lookup("-"), vec![
                        EvalTreeNode::new(lookup("x")),
                        EvalTreeNode::with_children(lookup("g"), vec![
                            nleaf(4),
                        ]),
                    ]),
                ]),
                nleaf(32),
            ]),
        ]);
    }

    /// Defines a basic context with the operators: + - / * %
    /// with their normal, mathematical precedence
    /// as well as two functions f and g as well as a number x
    /// None of these definitions have a real, evaluatable function
    fn basic_context() -> EvalContext {
        let mut context = EvalContext::new();

        let fake_unary: Function = Function {
            params: vec!["a".to_owned()],
            body: Expr::new(),
        };

        let fake_binary: Function = Function {
            params: vec!["a".to_owned(), "b".to_owned()],
            body: Expr::new(),
        };

        context.set_number("x", RichNumber::from(3875f64));

        context.define_defaults("f", fake_unary.clone());
        context.define_defaults("g", fake_unary.clone());

        context.define("+", Fixity::Infix, 6, fake_binary.clone());
        context.define("-", Fixity::Infix, 6, fake_binary.clone());

        context.define("*", Fixity::Infix, 7, fake_binary.clone());
        context.define("/", Fixity::Infix, 7, fake_binary.clone());
        context.define("%", Fixity::Infix, 7, fake_binary.clone());

        context
    }
}

<|endoftext|>


<commit_before>use super::*;

use std::sync::Arc;

use ra_db::SourceDatabase;

fn check_def_map_is_not_recomputed(initial: &str, file_change: &str) {
    let (mut db, pos) = MockDatabase::with_position(initial);
    let crate_id = db.crate_graph().iter().next().unwrap();
    let krate = Crate { crate_id };
    {
        let events = db.log_executed(|| {
            db.crate_def_map(krate);
        });
        assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
    db.set_file_text(pos.file_id, Arc::new(file_change.to_string()));

    {
        let events = db.log_executed(|| {
            db.crate_def_map(krate);
        });
        assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
}

#[test]
fn typing_inside_a_function_should_not_invalidate_def_map() {
    check_def_map_is_not_recomputed(
        "
        //- /lib.rs
        mod foo;<|>

        use crate::foo::bar::Baz;

        fn foo() -> i32 {
            1 + 1
        }
        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        ",
        "
        mod foo;

        use crate::foo::bar::Baz;

        fn foo() -> i32 { 92 }
        ",
    );
}

#[test]
fn adding_inner_items_should_not_invalidate_def_map() {
    check_def_map_is_not_recomputed(
        "
        //- /lib.rs
        struct S { a: i32}
        enum E { A }
        trait T {
            fn a() {}
        }
        mod foo;<|>
        impl S {
            fn a() {}
        }
        use crate::foo::bar::Baz;
        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        ",
        "
        struct S { a: i32, b: () }
        enum E { A, B }
        trait T {
            fn a() {}
            fn b() {}
        }
        mod foo;<|>
        impl S {
            fn a() {}
            fn b() {}
        }
        use crate::foo::bar::Baz;
        ",
    );
}

#[test]
fn typing_inside_a_macro_should_not_invalidate_def_map() {
    check_def_map_is_not_recomputed(
        "
        //- /lib.rs
        macro_rules! m {
            ($ident:ident) => {
                struct Foo;
            }
        }
        mod foo;

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        <|>
        m!(X);
        ",
        "
        m!(Y);
        ",
    );
}
<commit_msg>more realistic test for incrementality<commit_after>use super::*;

use std::sync::Arc;

use ra_db::SourceDatabase;

fn check_def_map_is_not_recomputed(initial: &str, file_change: &str) {
    let (mut db, pos) = MockDatabase::with_position(initial);
    let crate_id = db.crate_graph().iter().next().unwrap();
    let krate = Crate { crate_id };
    {
        let events = db.log_executed(|| {
            db.crate_def_map(krate);
        });
        assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
    db.set_file_text(pos.file_id, Arc::new(file_change.to_string()));

    {
        let events = db.log_executed(|| {
            db.crate_def_map(krate);
        });
        assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
}

#[test]
fn typing_inside_a_function_should_not_invalidate_def_map() {
    check_def_map_is_not_recomputed(
        "
        //- /lib.rs
        mod foo;<|>

        use crate::foo::bar::Baz;

        fn foo() -> i32 {
            1 + 1
        }
        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        ",
        "
        mod foo;

        use crate::foo::bar::Baz;

        fn foo() -> i32 { 92 }
        ",
    );
}

#[test]
fn adding_inner_items_should_not_invalidate_def_map() {
    check_def_map_is_not_recomputed(
        "
        //- /lib.rs
        struct S { a: i32}
        enum E { A }
        trait T {
            fn a() {}
        }
        mod foo;<|>
        impl S {
            fn a() {}
        }
        use crate::foo::bar::Baz;
        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        pub struct Baz;
        ",
        "
        struct S { a: i32, b: () }
        enum E { A, B }
        trait T {
            fn a() {}
            fn b() {}
        }
        mod foo;<|>
        impl S {
            fn a() {}
            fn b() {}
        }
        use crate::foo::bar::Baz;
        ",
    );
}

#[test]
fn typing_inside_a_macro_should_not_invalidate_def_map() {
    let (mut db, pos) = MockDatabase::with_position(
        "
        //- /lib.rs
        macro_rules! m {
            ($ident:ident) => {
                fn f() {
                    $ident + $ident;
                };
            }
        }
        mod foo;

        //- /foo/mod.rs
        pub mod bar;

        //- /foo/bar.rs
        <|>
        m!(X);
        ",
    );
    {
        let events = db.log_executed(|| {
            let module = crate::source_binder::module_from_file_id(&db, pos.file_id).unwrap();
            let decls = module.declarations(&db);
            assert_eq!(decls.len(), 1);
        });
        assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
    db.set_file_text(pos.file_id, Arc::new("m!(Y);".to_string()));

    {
        let events = db.log_executed(|| {
            let module = crate::source_binder::module_from_file_id(&db, pos.file_id).unwrap();
            let decls = module.declarations(&db);
            assert_eq!(decls.len(), 1);
        });
        assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
    }
}
<|endoftext|>


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

use std::{
    ffi::OsStr,
    ops,
    path::{Path, PathBuf},
};

use anyhow::{Context, Result};
use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId};
use ra_arena::{Arena, Idx};
use ra_cargo_watch::run_cargo;
use ra_db::Edition;
use rustc_hash::FxHashMap;
use serde::Deserialize;

/// `CargoWorkspace` represents the logical structure of, well, a Cargo
/// workspace. It pretty closely mirrors `cargo metadata` output.
///
/// Note that internally, rust analyzer uses a different structure:
/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates,
/// while this knows about `Packages` & `Targets`: purely cargo-related
/// concepts.
#[derive(Debug, Clone)]
pub struct CargoWorkspace {
    packages: Arena<PackageData>,
    targets: Arena<TargetData>,
    workspace_root: PathBuf,
}

impl ops::Index<Package> for CargoWorkspace {
    type Output = PackageData;
    fn index(&self, index: Package) -> &PackageData {
        &self.packages[index]
    }
}

impl ops::Index<Target> for CargoWorkspace {
    type Output = TargetData;
    fn index(&self, index: Target) -> &TargetData {
        &self.targets[index]
    }
}

#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase", default)]
pub struct CargoFeatures {
    /// Do not activate the `default` feature.
    pub no_default_features: bool,

    /// Activate all available features
    pub all_features: bool,

    /// List of features to activate.
    /// This will be ignored if `cargo_all_features` is true.
    pub features: Vec<String>,

    /// Runs cargo check on launch to figure out the correct values of OUT_DIR
    pub load_out_dirs_from_check: bool,
}

impl Default for CargoFeatures {
    fn default() -> Self {
        CargoFeatures {
            no_default_features: false,
            all_features: true,
            features: Vec::new(),
            load_out_dirs_from_check: false,
        }
    }
}

pub type Package = Idx<PackageData>;

pub type Target = Idx<TargetData>;

#[derive(Debug, Clone)]
pub struct PackageData {
    pub id: String,
    pub name: String,
    pub manifest: PathBuf,
    pub targets: Vec<Target>,
    pub is_member: bool,
    pub dependencies: Vec<PackageDependency>,
    pub edition: Edition,
    pub features: Vec<String>,
    pub out_dir: Option<PathBuf>,
    pub proc_macro_dylib_path: Option<PathBuf>,
}

#[derive(Debug, Clone)]
pub struct PackageDependency {
    pub pkg: Package,
    pub name: String,
}

#[derive(Debug, Clone)]
pub struct TargetData {
    pub package: Package,
    pub name: String,
    pub root: PathBuf,
    pub kind: TargetKind,
    pub is_proc_macro: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetKind {
    Bin,
    /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
    Lib,
    Example,
    Test,
    Bench,
    Other,
}

impl TargetKind {
    fn new(kinds: &[String]) -> TargetKind {
        for kind in kinds {
            return match kind.as_str() {
                "bin" => TargetKind::Bin,
                "test" => TargetKind::Test,
                "bench" => TargetKind::Bench,
                "example" => TargetKind::Example,
                "proc-macro" => TargetKind::Lib,
                _ if kind.contains("lib") => TargetKind::Lib,
                _ => continue,
            };
        }
        TargetKind::Other
    }
}

impl PackageData {
    pub fn root(&self) -> &Path {
        self.manifest.parent().unwrap()
    }
}

impl CargoWorkspace {
    pub fn from_cargo_metadata(
        cargo_toml: &Path,
        cargo_features: &CargoFeatures,
    ) -> Result<CargoWorkspace> {
        let mut meta = MetadataCommand::new();
        meta.manifest_path(cargo_toml);
        if cargo_features.all_features {
            meta.features(CargoOpt::AllFeatures);
        } else if cargo_features.no_default_features {
            // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
            // https://github.com/oli-obk/cargo_metadata/issues/79
            meta.features(CargoOpt::NoDefaultFeatures);
        } else if !cargo_features.features.is_empty() {
            meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone()));
        }
        if let Some(parent) = cargo_toml.parent() {
            meta.current_dir(parent);
        }
        let meta = meta.exec().with_context(|| {
            format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
        })?;

        let mut out_dir_by_id = FxHashMap::default();
        let mut proc_macro_dylib_paths = FxHashMap::default();
        if cargo_features.load_out_dirs_from_check {
            let resources = load_extern_resources(cargo_toml, cargo_features);
            out_dir_by_id = resources.out_dirs;
            proc_macro_dylib_paths = resources.proc_dylib_paths;
        }

        let mut pkg_by_id = FxHashMap::default();
        let mut packages = Arena::default();
        let mut targets = Arena::default();

        let ws_members = &meta.workspace_members;

        for meta_pkg in meta.packages {
            let cargo_metadata::Package { id, edition, name, manifest_path, .. } = meta_pkg;
            let is_member = ws_members.contains(&id);
            let edition = edition
                .parse::<Edition>()
                .with_context(|| format!("Failed to parse edition {}", edition))?;
            let pkg = packages.alloc(PackageData {
                name,
                id: id.to_string(),
                manifest: manifest_path,
                targets: Vec::new(),
                is_member,
                edition,
                dependencies: Vec::new(),
                features: Vec::new(),
                out_dir: out_dir_by_id.get(&id).cloned(),
                proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(),
            });
            let pkg_data = &mut packages[pkg];
            pkg_by_id.insert(id, pkg);
            for meta_tgt in meta_pkg.targets {
                let is_proc_macro = meta_tgt.kind.as_slice() == ["proc-macro"];
                let tgt = targets.alloc(TargetData {
                    package: pkg,
                    name: meta_tgt.name,
                    root: meta_tgt.src_path.clone(),
                    kind: TargetKind::new(meta_tgt.kind.as_slice()),
                    is_proc_macro,
                });
                pkg_data.targets.push(tgt);
            }
        }
        let resolve = meta.resolve.expect("metadata executed with deps");
        for node in resolve.nodes {
            let source = match pkg_by_id.get(&node.id) {
                Some(&src) => src,
                // FIXME: replace this and a similar branch below with `.unwrap`, once
                // https://github.com/rust-lang/cargo/issues/7841
                // is fixed and hits stable (around 1.43-is probably?).
                None => {
                    log::error!("Node id do not match in cargo metadata, ignoring {}", node.id);
                    continue;
                }
            };
            for dep_node in node.deps {
                let pkg = match pkg_by_id.get(&dep_node.pkg) {
                    Some(&pkg) => pkg,
                    None => {
                        log::error!(
                            "Dep node id do not match in cargo metadata, ignoring {}",
                            dep_node.pkg
                        );
                        continue;
                    }
                };
                let dep = PackageDependency { name: dep_node.name, pkg };
                packages[source].dependencies.push(dep);
            }
            packages[source].features.extend(node.features);
        }

        Ok(CargoWorkspace { packages, targets, workspace_root: meta.workspace_root })
    }

    pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + ExactSizeIterator + 'a {
        self.packages.iter().map(|(id, _pkg)| id)
    }

    pub fn target_by_root(&self, root: &Path) -> Option<Target> {
        self.packages()
            .filter_map(|pkg| self[pkg].targets.iter().find(|&&it| self[it].root == root))
            .next()
            .copied()
    }

    pub fn workspace_root(&self) -> &Path {
        &self.workspace_root
    }

    pub fn package_flag(&self, package: &PackageData) -> String {
        if self.is_unique(&*package.name) {
            package.name.clone()
        } else {
            package.id.clone()
        }
    }

    fn is_unique(&self, name: &str) -> bool {
        self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
    }
}

#[derive(Debug, Clone, Default)]
pub struct ExternResources {
    out_dirs: FxHashMap<PackageId, PathBuf>,
    proc_dylib_paths: FxHashMap<PackageId, PathBuf>,
}

pub fn load_extern_resources(cargo_toml: &Path, cargo_features: &CargoFeatures) -> ExternResources {
    let mut args: Vec<String> = vec![
        "check".to_string(),
        "--message-format=json".to_string(),
        "--manifest-path".to_string(),
        cargo_toml.display().to_string(),
    ];

    if cargo_features.all_features {
        args.push("--all-features".to_string());
    } else if cargo_features.no_default_features {
        // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
        // https://github.com/oli-obk/cargo_metadata/issues/79
        args.push("--no-default-features".to_string());
    } else {
        args.extend(cargo_features.features.iter().cloned());
    }

    let mut acc = ExternResources::default();
    let res = run_cargo(&args, cargo_toml.parent(), &mut |message| {
        match message {
            Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => {
                acc.out_dirs.insert(package_id, out_dir);
            }

            Message::CompilerArtifact(message) => {
                if message.target.kind.contains(&"proc-macro".to_string()) {
                    let package_id = message.package_id;
                    // Skip rmeta file
                    if let Some(filename) =
                        message.filenames.iter().filter(|name| is_dylib(name)).next()
                    {
                        acc.proc_dylib_paths.insert(package_id, filename.clone());
                    }
                }
            }
            Message::CompilerMessage(_) => (),
            Message::Unknown => (),
        }
        true
    });

    if let Err(err) = res {
        log::error!("Failed to load outdirs: {:?}", err);
    }

    acc
}

// FIXME: File a better way to know if it is a dylib
fn is_dylib(path: &Path) -> bool {
    let ext = match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase())
    {
        None => return false,
        Some(ext) => ext,
    };

    match ext.as_str() {
        "dll" => true,
        "dylib" => true,
        "so" => true,
        _ => false,
    }
}
<commit_msg>Use matches in is_dylib<commit_after>//! FIXME: write short doc here

use std::{
    ffi::OsStr,
    ops,
    path::{Path, PathBuf},
};

use anyhow::{Context, Result};
use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId};
use ra_arena::{Arena, Idx};
use ra_cargo_watch::run_cargo;
use ra_db::Edition;
use rustc_hash::FxHashMap;
use serde::Deserialize;

/// `CargoWorkspace` represents the logical structure of, well, a Cargo
/// workspace. It pretty closely mirrors `cargo metadata` output.
///
/// Note that internally, rust analyzer uses a different structure:
/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates,
/// while this knows about `Packages` & `Targets`: purely cargo-related
/// concepts.
#[derive(Debug, Clone)]
pub struct CargoWorkspace {
    packages: Arena<PackageData>,
    targets: Arena<TargetData>,
    workspace_root: PathBuf,
}

impl ops::Index<Package> for CargoWorkspace {
    type Output = PackageData;
    fn index(&self, index: Package) -> &PackageData {
        &self.packages[index]
    }
}

impl ops::Index<Target> for CargoWorkspace {
    type Output = TargetData;
    fn index(&self, index: Target) -> &TargetData {
        &self.targets[index]
    }
}

#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase", default)]
pub struct CargoFeatures {
    /// Do not activate the `default` feature.
    pub no_default_features: bool,

    /// Activate all available features
    pub all_features: bool,

    /// List of features to activate.
    /// This will be ignored if `cargo_all_features` is true.
    pub features: Vec<String>,

    /// Runs cargo check on launch to figure out the correct values of OUT_DIR
    pub load_out_dirs_from_check: bool,
}

impl Default for CargoFeatures {
    fn default() -> Self {
        CargoFeatures {
            no_default_features: false,
            all_features: true,
            features: Vec::new(),
            load_out_dirs_from_check: false,
        }
    }
}

pub type Package = Idx<PackageData>;

pub type Target = Idx<TargetData>;

#[derive(Debug, Clone)]
pub struct PackageData {
    pub id: String,
    pub name: String,
    pub manifest: PathBuf,
    pub targets: Vec<Target>,
    pub is_member: bool,
    pub dependencies: Vec<PackageDependency>,
    pub edition: Edition,
    pub features: Vec<String>,
    pub out_dir: Option<PathBuf>,
    pub proc_macro_dylib_path: Option<PathBuf>,
}

#[derive(Debug, Clone)]
pub struct PackageDependency {
    pub pkg: Package,
    pub name: String,
}

#[derive(Debug, Clone)]
pub struct TargetData {
    pub package: Package,
    pub name: String,
    pub root: PathBuf,
    pub kind: TargetKind,
    pub is_proc_macro: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetKind {
    Bin,
    /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
    Lib,
    Example,
    Test,
    Bench,
    Other,
}

impl TargetKind {
    fn new(kinds: &[String]) -> TargetKind {
        for kind in kinds {
            return match kind.as_str() {
                "bin" => TargetKind::Bin,
                "test" => TargetKind::Test,
                "bench" => TargetKind::Bench,
                "example" => TargetKind::Example,
                "proc-macro" => TargetKind::Lib,
                _ if kind.contains("lib") => TargetKind::Lib,
                _ => continue,
            };
        }
        TargetKind::Other
    }
}

impl PackageData {
    pub fn root(&self) -> &Path {
        self.manifest.parent().unwrap()
    }
}

impl CargoWorkspace {
    pub fn from_cargo_metadata(
        cargo_toml: &Path,
        cargo_features: &CargoFeatures,
    ) -> Result<CargoWorkspace> {
        let mut meta = MetadataCommand::new();
        meta.manifest_path(cargo_toml);
        if cargo_features.all_features {
            meta.features(CargoOpt::AllFeatures);
        } else if cargo_features.no_default_features {
            // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
            // https://github.com/oli-obk/cargo_metadata/issues/79
            meta.features(CargoOpt::NoDefaultFeatures);
        } else if !cargo_features.features.is_empty() {
            meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone()));
        }
        if let Some(parent) = cargo_toml.parent() {
            meta.current_dir(parent);
        }
        let meta = meta.exec().with_context(|| {
            format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
        })?;

        let mut out_dir_by_id = FxHashMap::default();
        let mut proc_macro_dylib_paths = FxHashMap::default();
        if cargo_features.load_out_dirs_from_check {
            let resources = load_extern_resources(cargo_toml, cargo_features);
            out_dir_by_id = resources.out_dirs;
            proc_macro_dylib_paths = resources.proc_dylib_paths;
        }

        let mut pkg_by_id = FxHashMap::default();
        let mut packages = Arena::default();
        let mut targets = Arena::default();

        let ws_members = &meta.workspace_members;

        for meta_pkg in meta.packages {
            let cargo_metadata::Package { id, edition, name, manifest_path, .. } = meta_pkg;
            let is_member = ws_members.contains(&id);
            let edition = edition
                .parse::<Edition>()
                .with_context(|| format!("Failed to parse edition {}", edition))?;
            let pkg = packages.alloc(PackageData {
                name,
                id: id.to_string(),
                manifest: manifest_path,
                targets: Vec::new(),
                is_member,
                edition,
                dependencies: Vec::new(),
                features: Vec::new(),
                out_dir: out_dir_by_id.get(&id).cloned(),
                proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(),
            });
            let pkg_data = &mut packages[pkg];
            pkg_by_id.insert(id, pkg);
            for meta_tgt in meta_pkg.targets {
                let is_proc_macro = meta_tgt.kind.as_slice() == ["proc-macro"];
                let tgt = targets.alloc(TargetData {
                    package: pkg,
                    name: meta_tgt.name,
                    root: meta_tgt.src_path.clone(),
                    kind: TargetKind::new(meta_tgt.kind.as_slice()),
                    is_proc_macro,
                });
                pkg_data.targets.push(tgt);
            }
        }
        let resolve = meta.resolve.expect("metadata executed with deps");
        for node in resolve.nodes {
            let source = match pkg_by_id.get(&node.id) {
                Some(&src) => src,
                // FIXME: replace this and a similar branch below with `.unwrap`, once
                // https://github.com/rust-lang/cargo/issues/7841
                // is fixed and hits stable (around 1.43-is probably?).
                None => {
                    log::error!("Node id do not match in cargo metadata, ignoring {}", node.id);
                    continue;
                }
            };
            for dep_node in node.deps {
                let pkg = match pkg_by_id.get(&dep_node.pkg) {
                    Some(&pkg) => pkg,
                    None => {
                        log::error!(
                            "Dep node id do not match in cargo metadata, ignoring {}",
                            dep_node.pkg
                        );
                        continue;
                    }
                };
                let dep = PackageDependency { name: dep_node.name, pkg };
                packages[source].dependencies.push(dep);
            }
            packages[source].features.extend(node.features);
        }

        Ok(CargoWorkspace { packages, targets, workspace_root: meta.workspace_root })
    }

    pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + ExactSizeIterator + 'a {
        self.packages.iter().map(|(id, _pkg)| id)
    }

    pub fn target_by_root(&self, root: &Path) -> Option<Target> {
        self.packages()
            .filter_map(|pkg| self[pkg].targets.iter().find(|&&it| self[it].root == root))
            .next()
            .copied()
    }

    pub fn workspace_root(&self) -> &Path {
        &self.workspace_root
    }

    pub fn package_flag(&self, package: &PackageData) -> String {
        if self.is_unique(&*package.name) {
            package.name.clone()
        } else {
            package.id.clone()
        }
    }

    fn is_unique(&self, name: &str) -> bool {
        self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
    }
}

#[derive(Debug, Clone, Default)]
pub struct ExternResources {
    out_dirs: FxHashMap<PackageId, PathBuf>,
    proc_dylib_paths: FxHashMap<PackageId, PathBuf>,
}

pub fn load_extern_resources(cargo_toml: &Path, cargo_features: &CargoFeatures) -> ExternResources {
    let mut args: Vec<String> = vec![
        "check".to_string(),
        "--message-format=json".to_string(),
        "--manifest-path".to_string(),
        cargo_toml.display().to_string(),
    ];

    if cargo_features.all_features {
        args.push("--all-features".to_string());
    } else if cargo_features.no_default_features {
        // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
        // https://github.com/oli-obk/cargo_metadata/issues/79
        args.push("--no-default-features".to_string());
    } else {
        args.extend(cargo_features.features.iter().cloned());
    }

    let mut acc = ExternResources::default();
    let res = run_cargo(&args, cargo_toml.parent(), &mut |message| {
        match message {
            Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => {
                acc.out_dirs.insert(package_id, out_dir);
            }

            Message::CompilerArtifact(message) => {
                if message.target.kind.contains(&"proc-macro".to_string()) {
                    let package_id = message.package_id;
                    // Skip rmeta file
                    if let Some(filename) =
                        message.filenames.iter().filter(|name| is_dylib(name)).next()
                    {
                        acc.proc_dylib_paths.insert(package_id, filename.clone());
                    }
                }
            }
            Message::CompilerMessage(_) => (),
            Message::Unknown => (),
        }
        true
    });

    if let Err(err) = res {
        log::error!("Failed to load outdirs: {:?}", err);
    }

    acc
}

// FIXME: File a better way to know if it is a dylib
fn is_dylib(path: &Path) -> bool {
    match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) {
        None => false,
        Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
    }
}
<|endoftext|>


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

use std::{
    env,
    ffi::OsStr,
    ops,
    path::{Path, PathBuf},
    process::Command,
};

use anyhow::{Context, Result};
use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId};
use ra_arena::{Arena, Idx};
use ra_db::Edition;
use rustc_hash::FxHashMap;

/// `CargoWorkspace` represents the logical structure of, well, a Cargo
/// workspace. It pretty closely mirrors `cargo metadata` output.
///
/// Note that internally, rust analyzer uses a different structure:
/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates,
/// while this knows about `Packages` & `Targets`: purely cargo-related
/// concepts.
#[derive(Debug, Clone)]
pub struct CargoWorkspace {
    packages: Arena<PackageData>,
    targets: Arena<TargetData>,
    workspace_root: PathBuf,
}

impl ops::Index<Package> for CargoWorkspace {
    type Output = PackageData;
    fn index(&self, index: Package) -> &PackageData {
        &self.packages[index]
    }
}

impl ops::Index<Target> for CargoWorkspace {
    type Output = TargetData;
    fn index(&self, index: Target) -> &TargetData {
        &self.targets[index]
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CargoConfig {
    /// Do not activate the `default` feature.
    pub no_default_features: bool,

    /// Activate all available features
    pub all_features: bool,

    /// List of features to activate.
    /// This will be ignored if `cargo_all_features` is true.
    pub features: Vec<String>,

    /// Runs cargo check on launch to figure out the correct values of OUT_DIR
    pub load_out_dirs_from_check: bool,
}

impl Default for CargoConfig {
    fn default() -> Self {
        CargoConfig {
            no_default_features: false,
            all_features: true,
            features: Vec::new(),
            load_out_dirs_from_check: false,
        }
    }
}

pub type Package = Idx<PackageData>;

pub type Target = Idx<TargetData>;

#[derive(Debug, Clone)]
pub struct PackageData {
    pub version: String,
    pub name: String,
    pub manifest: PathBuf,
    pub targets: Vec<Target>,
    pub is_member: bool,
    pub dependencies: Vec<PackageDependency>,
    pub edition: Edition,
    pub features: Vec<String>,
    pub out_dir: Option<PathBuf>,
    pub proc_macro_dylib_path: Option<PathBuf>,
}

#[derive(Debug, Clone)]
pub struct PackageDependency {
    pub pkg: Package,
    pub name: String,
}

#[derive(Debug, Clone)]
pub struct TargetData {
    pub package: Package,
    pub name: String,
    pub root: PathBuf,
    pub kind: TargetKind,
    pub is_proc_macro: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetKind {
    Bin,
    /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
    Lib,
    Example,
    Test,
    Bench,
    Other,
}

impl TargetKind {
    fn new(kinds: &[String]) -> TargetKind {
        for kind in kinds {
            return match kind.as_str() {
                "bin" => TargetKind::Bin,
                "test" => TargetKind::Test,
                "bench" => TargetKind::Bench,
                "example" => TargetKind::Example,
                "proc-macro" => TargetKind::Lib,
                _ if kind.contains("lib") => TargetKind::Lib,
                _ => continue,
            };
        }
        TargetKind::Other
    }
}

impl PackageData {
    pub fn root(&self) -> &Path {
        self.manifest.parent().unwrap()
    }
}

impl CargoWorkspace {
    pub fn from_cargo_metadata(
        cargo_toml: &Path,
        cargo_features: &CargoConfig,
    ) -> Result<CargoWorkspace> {
        let mut meta = MetadataCommand::new();
        meta.manifest_path(cargo_toml);
        if cargo_features.all_features {
            meta.features(CargoOpt::AllFeatures);
        } else if cargo_features.no_default_features {
            // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
            // https://github.com/oli-obk/cargo_metadata/issues/79
            meta.features(CargoOpt::NoDefaultFeatures);
        } else if !cargo_features.features.is_empty() {
            meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone()));
        }
        if let Some(parent) = cargo_toml.parent() {
            meta.current_dir(parent);
        }
        let meta = meta.exec().with_context(|| {
            format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
        })?;

        let mut out_dir_by_id = FxHashMap::default();
        let mut proc_macro_dylib_paths = FxHashMap::default();
        if cargo_features.load_out_dirs_from_check {
            let resources = load_extern_resources(cargo_toml, cargo_features)?;
            out_dir_by_id = resources.out_dirs;
            proc_macro_dylib_paths = resources.proc_dylib_paths;
        }

        let mut pkg_by_id = FxHashMap::default();
        let mut packages = Arena::default();
        let mut targets = Arena::default();

        let ws_members = &meta.workspace_members;

        for meta_pkg in meta.packages {
            let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } =
                meta_pkg;
            let is_member = ws_members.contains(&id);
            let edition = edition
                .parse::<Edition>()
                .with_context(|| format!("Failed to parse edition {}", edition))?;
            let pkg = packages.alloc(PackageData {
                name,
                version: version.to_string(),
                manifest: manifest_path,
                targets: Vec::new(),
                is_member,
                edition,
                dependencies: Vec::new(),
                features: Vec::new(),
                out_dir: out_dir_by_id.get(&id).cloned(),
                proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(),
            });
            let pkg_data = &mut packages[pkg];
            pkg_by_id.insert(id, pkg);
            for meta_tgt in meta_pkg.targets {
                let is_proc_macro = meta_tgt.kind.as_slice() == ["proc-macro"];
                let tgt = targets.alloc(TargetData {
                    package: pkg,
                    name: meta_tgt.name,
                    root: meta_tgt.src_path.clone(),
                    kind: TargetKind::new(meta_tgt.kind.as_slice()),
                    is_proc_macro,
                });
                pkg_data.targets.push(tgt);
            }
        }
        let resolve = meta.resolve.expect("metadata executed with deps");
        for node in resolve.nodes {
            let source = match pkg_by_id.get(&node.id) {
                Some(&src) => src,
                // FIXME: replace this and a similar branch below with `.unwrap`, once
                // https://github.com/rust-lang/cargo/issues/7841
                // is fixed and hits stable (around 1.43-is probably?).
                None => {
                    log::error!("Node id do not match in cargo metadata, ignoring {}", node.id);
                    continue;
                }
            };
            for dep_node in node.deps {
                let pkg = match pkg_by_id.get(&dep_node.pkg) {
                    Some(&pkg) => pkg,
                    None => {
                        log::error!(
                            "Dep node id do not match in cargo metadata, ignoring {}",
                            dep_node.pkg
                        );
                        continue;
                    }
                };
                let dep = PackageDependency { name: dep_node.name, pkg };
                packages[source].dependencies.push(dep);
            }
            packages[source].features.extend(node.features);
        }

        Ok(CargoWorkspace { packages, targets, workspace_root: meta.workspace_root })
    }

    pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + ExactSizeIterator + 'a {
        self.packages.iter().map(|(id, _pkg)| id)
    }

    pub fn target_by_root(&self, root: &Path) -> Option<Target> {
        self.packages()
            .filter_map(|pkg| self[pkg].targets.iter().find(|&&it| self[it].root == root))
            .next()
            .copied()
    }

    pub fn workspace_root(&self) -> &Path {
        &self.workspace_root
    }

    pub fn package_flag(&self, package: &PackageData) -> String {
        if self.is_unique(&*package.name) {
            package.name.clone()
        } else {
            format!("{}:{}", package.name, package.version)
        }
    }

    fn is_unique(&self, name: &str) -> bool {
        self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
    }
}

#[derive(Debug, Clone, Default)]
pub struct ExternResources {
    out_dirs: FxHashMap<PackageId, PathBuf>,
    proc_dylib_paths: FxHashMap<PackageId, PathBuf>,
}

pub fn load_extern_resources(
    cargo_toml: &Path,
    cargo_features: &CargoConfig,
) -> Result<ExternResources> {
    let mut cmd = Command::new(cargo_binary());
    cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml);
    if cargo_features.all_features {
        cmd.arg("--all-features");
    } else if cargo_features.no_default_features {
        // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
        // https://github.com/oli-obk/cargo_metadata/issues/79
        cmd.arg("--no-default-features");
    } else {
        cmd.args(&cargo_features.features);
    }

    let output = cmd.output()?;

    let mut res = ExternResources::default();

    for message in cargo_metadata::parse_messages(output.stdout.as_slice()) {
        if let Ok(message) = message {
            match message {
                Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => {
                    res.out_dirs.insert(package_id, out_dir);
                }

                Message::CompilerArtifact(message) => {
                    if message.target.kind.contains(&"proc-macro".to_string()) {
                        let package_id = message.package_id;
                        // Skip rmeta file
                        if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name))
                        {
                            res.proc_dylib_paths.insert(package_id, filename.clone());
                        }
                    }
                }
                Message::CompilerMessage(_) => (),
                Message::Unknown => (),
            }
        }
    }
    Ok(res)
}

// FIXME: File a better way to know if it is a dylib
fn is_dylib(path: &Path) -> bool {
    match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) {
        None => false,
        Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
    }
}

fn cargo_binary() -> String {
    env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
}
<commit_msg>More helpful error message if toolchain is not in PATH<commit_after>//! FIXME: write short doc here

use std::{
    env,
    ffi::OsStr,
    ops,
    path::{Path, PathBuf},
    process::Command,
};

use anyhow::{Context, Result};
use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId};
use ra_arena::{Arena, Idx};
use ra_db::Edition;
use rustc_hash::FxHashMap;

/// `CargoWorkspace` represents the logical structure of, well, a Cargo
/// workspace. It pretty closely mirrors `cargo metadata` output.
///
/// Note that internally, rust analyzer uses a different structure:
/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates,
/// while this knows about `Packages` & `Targets`: purely cargo-related
/// concepts.
#[derive(Debug, Clone)]
pub struct CargoWorkspace {
    packages: Arena<PackageData>,
    targets: Arena<TargetData>,
    workspace_root: PathBuf,
}

impl ops::Index<Package> for CargoWorkspace {
    type Output = PackageData;
    fn index(&self, index: Package) -> &PackageData {
        &self.packages[index]
    }
}

impl ops::Index<Target> for CargoWorkspace {
    type Output = TargetData;
    fn index(&self, index: Target) -> &TargetData {
        &self.targets[index]
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CargoConfig {
    /// Do not activate the `default` feature.
    pub no_default_features: bool,

    /// Activate all available features
    pub all_features: bool,

    /// List of features to activate.
    /// This will be ignored if `cargo_all_features` is true.
    pub features: Vec<String>,

    /// Runs cargo check on launch to figure out the correct values of OUT_DIR
    pub load_out_dirs_from_check: bool,
}

impl Default for CargoConfig {
    fn default() -> Self {
        CargoConfig {
            no_default_features: false,
            all_features: true,
            features: Vec::new(),
            load_out_dirs_from_check: false,
        }
    }
}

pub type Package = Idx<PackageData>;

pub type Target = Idx<TargetData>;

#[derive(Debug, Clone)]
pub struct PackageData {
    pub version: String,
    pub name: String,
    pub manifest: PathBuf,
    pub targets: Vec<Target>,
    pub is_member: bool,
    pub dependencies: Vec<PackageDependency>,
    pub edition: Edition,
    pub features: Vec<String>,
    pub out_dir: Option<PathBuf>,
    pub proc_macro_dylib_path: Option<PathBuf>,
}

#[derive(Debug, Clone)]
pub struct PackageDependency {
    pub pkg: Package,
    pub name: String,
}

#[derive(Debug, Clone)]
pub struct TargetData {
    pub package: Package,
    pub name: String,
    pub root: PathBuf,
    pub kind: TargetKind,
    pub is_proc_macro: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetKind {
    Bin,
    /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
    Lib,
    Example,
    Test,
    Bench,
    Other,
}

impl TargetKind {
    fn new(kinds: &[String]) -> TargetKind {
        for kind in kinds {
            return match kind.as_str() {
                "bin" => TargetKind::Bin,
                "test" => TargetKind::Test,
                "bench" => TargetKind::Bench,
                "example" => TargetKind::Example,
                "proc-macro" => TargetKind::Lib,
                _ if kind.contains("lib") => TargetKind::Lib,
                _ => continue,
            };
        }
        TargetKind::Other
    }
}

impl PackageData {
    pub fn root(&self) -> &Path {
        self.manifest.parent().unwrap()
    }
}

impl CargoWorkspace {
    pub fn from_cargo_metadata(
        cargo_toml: &Path,
        cargo_features: &CargoConfig,
    ) -> Result<CargoWorkspace> {
        let _ = Command::new(cargo_binary())
            .arg("--version")
            .status()
            .context("failed to run `cargo --version`, is `cargo` in PATH?")?;

        let mut meta = MetadataCommand::new();
        meta.manifest_path(cargo_toml);
        if cargo_features.all_features {
            meta.features(CargoOpt::AllFeatures);
        } else if cargo_features.no_default_features {
            // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
            // https://github.com/oli-obk/cargo_metadata/issues/79
            meta.features(CargoOpt::NoDefaultFeatures);
        } else if !cargo_features.features.is_empty() {
            meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone()));
        }
        if let Some(parent) = cargo_toml.parent() {
            meta.current_dir(parent);
        }
        let meta = meta.exec().with_context(|| {
            format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
        })?;

        let mut out_dir_by_id = FxHashMap::default();
        let mut proc_macro_dylib_paths = FxHashMap::default();
        if cargo_features.load_out_dirs_from_check {
            let resources = load_extern_resources(cargo_toml, cargo_features)?;
            out_dir_by_id = resources.out_dirs;
            proc_macro_dylib_paths = resources.proc_dylib_paths;
        }

        let mut pkg_by_id = FxHashMap::default();
        let mut packages = Arena::default();
        let mut targets = Arena::default();

        let ws_members = &meta.workspace_members;

        for meta_pkg in meta.packages {
            let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } =
                meta_pkg;
            let is_member = ws_members.contains(&id);
            let edition = edition
                .parse::<Edition>()
                .with_context(|| format!("Failed to parse edition {}", edition))?;
            let pkg = packages.alloc(PackageData {
                name,
                version: version.to_string(),
                manifest: manifest_path,
                targets: Vec::new(),
                is_member,
                edition,
                dependencies: Vec::new(),
                features: Vec::new(),
                out_dir: out_dir_by_id.get(&id).cloned(),
                proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(),
            });
            let pkg_data = &mut packages[pkg];
            pkg_by_id.insert(id, pkg);
            for meta_tgt in meta_pkg.targets {
                let is_proc_macro = meta_tgt.kind.as_slice() == ["proc-macro"];
                let tgt = targets.alloc(TargetData {
                    package: pkg,
                    name: meta_tgt.name,
                    root: meta_tgt.src_path.clone(),
                    kind: TargetKind::new(meta_tgt.kind.as_slice()),
                    is_proc_macro,
                });
                pkg_data.targets.push(tgt);
            }
        }
        let resolve = meta.resolve.expect("metadata executed with deps");
        for node in resolve.nodes {
            let source = match pkg_by_id.get(&node.id) {
                Some(&src) => src,
                // FIXME: replace this and a similar branch below with `.unwrap`, once
                // https://github.com/rust-lang/cargo/issues/7841
                // is fixed and hits stable (around 1.43-is probably?).
                None => {
                    log::error!("Node id do not match in cargo metadata, ignoring {}", node.id);
                    continue;
                }
            };
            for dep_node in node.deps {
                let pkg = match pkg_by_id.get(&dep_node.pkg) {
                    Some(&pkg) => pkg,
                    None => {
                        log::error!(
                            "Dep node id do not match in cargo metadata, ignoring {}",
                            dep_node.pkg
                        );
                        continue;
                    }
                };
                let dep = PackageDependency { name: dep_node.name, pkg };
                packages[source].dependencies.push(dep);
            }
            packages[source].features.extend(node.features);
        }

        Ok(CargoWorkspace { packages, targets, workspace_root: meta.workspace_root })
    }

    pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + ExactSizeIterator + 'a {
        self.packages.iter().map(|(id, _pkg)| id)
    }

    pub fn target_by_root(&self, root: &Path) -> Option<Target> {
        self.packages()
            .filter_map(|pkg| self[pkg].targets.iter().find(|&&it| self[it].root == root))
            .next()
            .copied()
    }

    pub fn workspace_root(&self) -> &Path {
        &self.workspace_root
    }

    pub fn package_flag(&self, package: &PackageData) -> String {
        if self.is_unique(&*package.name) {
            package.name.clone()
        } else {
            format!("{}:{}", package.name, package.version)
        }
    }

    fn is_unique(&self, name: &str) -> bool {
        self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
    }
}

#[derive(Debug, Clone, Default)]
pub struct ExternResources {
    out_dirs: FxHashMap<PackageId, PathBuf>,
    proc_dylib_paths: FxHashMap<PackageId, PathBuf>,
}

pub fn load_extern_resources(
    cargo_toml: &Path,
    cargo_features: &CargoConfig,
) -> Result<ExternResources> {
    let mut cmd = Command::new(cargo_binary());
    cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml);
    if cargo_features.all_features {
        cmd.arg("--all-features");
    } else if cargo_features.no_default_features {
        // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
        // https://github.com/oli-obk/cargo_metadata/issues/79
        cmd.arg("--no-default-features");
    } else {
        cmd.args(&cargo_features.features);
    }

    let output = cmd.output()?;

    let mut res = ExternResources::default();

    for message in cargo_metadata::parse_messages(output.stdout.as_slice()) {
        if let Ok(message) = message {
            match message {
                Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => {
                    res.out_dirs.insert(package_id, out_dir);
                }

                Message::CompilerArtifact(message) => {
                    if message.target.kind.contains(&"proc-macro".to_string()) {
                        let package_id = message.package_id;
                        // Skip rmeta file
                        if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name))
                        {
                            res.proc_dylib_paths.insert(package_id, filename.clone());
                        }
                    }
                }
                Message::CompilerMessage(_) => (),
                Message::Unknown => (),
            }
        }
    }
    Ok(res)
}

// FIXME: File a better way to know if it is a dylib
fn is_dylib(path: &Path) -> bool {
    match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) {
        None => false,
        Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
    }
}

fn cargo_binary() -> String {
    env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
}
<|endoftext|>