<commit_before>//! Driver for rust-analyzer.
//!
//! Based on cli flags, either spawns an LSP server, or runs a batch analysis
mod logger;
mod rustc_wrapper;

use std::{env, fs, path::Path, process};

use lsp_server::Connection;
use project_model::ProjectManifest;
use rust_analyzer::{cli::flags, config::Config, from_json, lsp_ext::supports_utf8, Result};
use vfs::AbsPathBuf;

#[cfg(all(feature = "mimalloc"))]
#[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;

#[cfg(all(feature = "jemalloc", not(target_env = "msvc")))]
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;

fn main() {
    if std::env::var("RA_RUSTC_WRAPPER").is_ok() {
        let mut args = std::env::args_os();
        let _me = args.next().unwrap();
        let rustc = args.next().unwrap();
        let code = match rustc_wrapper::run_rustc_skipping_cargo_checking(rustc, args.collect()) {
            Ok(rustc_wrapper::ExitCode(code)) => code.unwrap_or(102),
            Err(err) => {
                eprintln!("{}", err);
                101
            }
        };
        process::exit(code);
    }

    if let Err(err) = try_main() {
        tracing::error!("Unexpected error: {}", err);
        eprintln!("{}", err);
        process::exit(101);
    }
}

fn try_main() -> Result<()> {
    let flags = flags::RustAnalyzer::from_env()?;

    #[cfg(debug_assertions)]
    if flags.wait_dbg || env::var("RA_WAIT_DBG").is_ok() {
        #[allow(unused_mut)]
        let mut d = 4;
        while d == 4 {
            d = 4;
        }
    }

    let mut log_file = flags.log_file.as_deref();

    let env_log_file = env::var("RA_LOG_FILE").ok();
    if let Some(env_log_file) = env_log_file.as_deref() {
        log_file = Some(Path::new(env_log_file));
    }

    setup_logging(log_file)?;
    let verbosity = flags.verbosity();

    match flags.subcommand {
        flags::RustAnalyzerCmd::LspServer(cmd) => {
            if cmd.print_config_schema {
                println!("{:#}", Config::json_schema());
                return Ok(());
            }
            if cmd.version {
                println!("rust-analyzer {}", env!("REV"));
                return Ok(());
            }
            if cmd.help {
                println!("{}", flags::RustAnalyzer::HELP);
                return Ok(());
            }
            with_extra_thread("rust-analyzer server thread", run_server)?;
        }
        flags::RustAnalyzerCmd::ProcMacro(flags::ProcMacro) => {
            with_extra_thread("rust-analyzer proc-macro expander", || {
                proc_macro_srv::cli::run().map_err(Into::into)
            })?;
        }
        flags::RustAnalyzerCmd::Parse(cmd) => cmd.run()?,
        flags::RustAnalyzerCmd::Symbols(cmd) => cmd.run()?,
        flags::RustAnalyzerCmd::Highlight(cmd) => cmd.run()?,
        flags::RustAnalyzerCmd::AnalysisStats(cmd) => cmd.run(verbosity)?,
        flags::RustAnalyzerCmd::Diagnostics(cmd) => cmd.run()?,
        flags::RustAnalyzerCmd::Ssr(cmd) => cmd.run()?,
        flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?,
        flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
    }
    Ok(())
}

fn setup_logging(log_file: Option<&Path>) -> Result<()> {
    if cfg!(windows) {
        // This is required so that windows finds our pdb that is placed right beside the exe.
        // By default it doesn't look at the folder the exe resides in, only in the current working
        // directory which we set to the project workspace.
        // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/general-environment-variables
        // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize
        if let Ok(path) = env::current_exe() {
            if let Some(path) = path.parent() {
                env::set_var("_NT_SYMBOL_PATH", path);
            }
        }
    }
    if env::var("RUST_BACKTRACE").is_err() {
        env::set_var("RUST_BACKTRACE", "short");
    }

    let log_file = match log_file {
        Some(path) => {
            if let Some(parent) = path.parent() {
                let _ = fs::create_dir_all(parent);
            }
            Some(fs::File::create(path)?)
        }
        None => None,
    };
    let filter = env::var("RA_LOG").ok();
    // deliberately enable all `error` logs if the user has not set RA_LOG, as there is usually useful
    // information in there for debugging
    logger::Logger::new(log_file, filter.as_deref().or(Some("error"))).install()?;

    profile::init();

    Ok(())
}

const STACK_SIZE: usize = 1024 * 1024 * 8;

/// Parts of rust-analyzer can use a lot of stack space, and some operating systems only give us
/// 1 MB by default (eg. Windows), so this spawns a new thread with hopefully sufficient stack
/// space.
fn with_extra_thread(
    thread_name: impl Into<String>,
    f: impl FnOnce() -> Result<()> + Send + 'static,
) -> Result<()> {
    let handle =
        std::thread::Builder::new().name(thread_name.into()).stack_size(STACK_SIZE).spawn(f)?;
    match handle.join() {
        Ok(res) => res,
        Err(panic) => std::panic::resume_unwind(panic),
    }
}

fn run_server() -> Result<()> {
    tracing::info!("server version {} will start", env!("REV"));

    let (connection, io_threads) = Connection::stdio();

    let (initialize_id, initialize_params) = connection.initialize_start()?;
    tracing::info!("InitializeParams: {}", initialize_params);
    let initialize_params =
        from_json::<lsp_types::InitializeParams>("InitializeParams", &initialize_params)?;

    let root_path = match initialize_params
        .root_uri
        .and_then(|it| it.to_file_path().ok())
        .and_then(|it| AbsPathBuf::try_from(it).ok())
    {
        Some(it) => it,
        None => {
            let cwd = env::current_dir()?;
            AbsPathBuf::assert(cwd)
        }
    };

    let mut config = Config::new(root_path, initialize_params.capabilities);
    if let Some(json) = initialize_params.initialization_options {
        if let Err(e) = config.update(json) {
            use lsp_types::{
                notification::{Notification, ShowMessage},
                MessageType, ShowMessageParams,
            };
            let not = lsp_server::Notification::new(
                ShowMessage::METHOD.to_string(),
                ShowMessageParams { typ: MessageType::WARNING, message: e.to_string() },
            );
            connection.sender.send(lsp_server::Message::Notification(not)).unwrap();
        }
    }

    let server_capabilities = rust_analyzer::server_capabilities(&config);

    let initialize_result = lsp_types::InitializeResult {
        capabilities: server_capabilities,
        server_info: Some(lsp_types::ServerInfo {
            name: String::from("rust-analyzer"),
            version: Some(String::from(env!("REV"))),
        }),
        offset_encoding: if supports_utf8(config.caps()) {
            Some("utf-8".to_string())
        } else {
            None
        },
    };

    let initialize_result = serde_json::to_value(initialize_result).unwrap();

    connection.initialize_finish(initialize_id, initialize_result)?;

    if let Some(client_info) = initialize_params.client_info {
        tracing::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default());
    }

    if config.linked_projects().is_empty() && config.detached_files().is_empty() {
        let workspace_roots = initialize_params
            .workspace_folders
            .map(|workspaces| {
                workspaces
                    .into_iter()
                    .filter_map(|it| it.uri.to_file_path().ok())
                    .filter_map(|it| AbsPathBuf::try_from(it).ok())
                    .collect::<Vec<_>>()
            })
            .filter(|workspaces| !workspaces.is_empty())
            .unwrap_or_else(|| vec![config.root_path().clone()]);

        let discovered = ProjectManifest::discover_all(&workspace_roots);
        tracing::info!("discovered projects: {:?}", discovered);
        if discovered.is_empty() {
            tracing::error!("failed to find any projects in {:?}", workspace_roots);
        }
        config.discovered_projects = Some(discovered);
    }

    rust_analyzer::main_loop(config, connection)?;

    io_threads.join()?;
    tracing::info!("server did shut down");
    Ok(())
}
<commit_msg>Auto merge of #12456 - lnicola:thread-names, r=lnicola<commit_after>//! Driver for rust-analyzer.
//!
//! Based on cli flags, either spawns an LSP server, or runs a batch analysis
mod logger;
mod rustc_wrapper;

use std::{env, fs, path::Path, process};

use lsp_server::Connection;
use project_model::ProjectManifest;
use rust_analyzer::{cli::flags, config::Config, from_json, lsp_ext::supports_utf8, Result};
use vfs::AbsPathBuf;

#[cfg(all(feature = "mimalloc"))]
#[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;

#[cfg(all(feature = "jemalloc", not(target_env = "msvc")))]
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;

fn main() {
    if std::env::var("RA_RUSTC_WRAPPER").is_ok() {
        let mut args = std::env::args_os();
        let _me = args.next().unwrap();
        let rustc = args.next().unwrap();
        let code = match rustc_wrapper::run_rustc_skipping_cargo_checking(rustc, args.collect()) {
            Ok(rustc_wrapper::ExitCode(code)) => code.unwrap_or(102),
            Err(err) => {
                eprintln!("{}", err);
                101
            }
        };
        process::exit(code);
    }

    if let Err(err) = try_main() {
        tracing::error!("Unexpected error: {}", err);
        eprintln!("{}", err);
        process::exit(101);
    }
}

fn try_main() -> Result<()> {
    let flags = flags::RustAnalyzer::from_env()?;

    #[cfg(debug_assertions)]
    if flags.wait_dbg || env::var("RA_WAIT_DBG").is_ok() {
        #[allow(unused_mut)]
        let mut d = 4;
        while d == 4 {
            d = 4;
        }
    }

    let mut log_file = flags.log_file.as_deref();

    let env_log_file = env::var("RA_LOG_FILE").ok();
    if let Some(env_log_file) = env_log_file.as_deref() {
        log_file = Some(Path::new(env_log_file));
    }

    setup_logging(log_file)?;
    let verbosity = flags.verbosity();

    match flags.subcommand {
        flags::RustAnalyzerCmd::LspServer(cmd) => {
            if cmd.print_config_schema {
                println!("{:#}", Config::json_schema());
                return Ok(());
            }
            if cmd.version {
                println!("rust-analyzer {}", env!("REV"));
                return Ok(());
            }
            if cmd.help {
                println!("{}", flags::RustAnalyzer::HELP);
                return Ok(());
            }
            with_extra_thread("LspServer", run_server)?;
        }
        flags::RustAnalyzerCmd::ProcMacro(flags::ProcMacro) => {
            with_extra_thread("MacroExpander", || proc_macro_srv::cli::run().map_err(Into::into))?;
        }
        flags::RustAnalyzerCmd::Parse(cmd) => cmd.run()?,
        flags::RustAnalyzerCmd::Symbols(cmd) => cmd.run()?,
        flags::RustAnalyzerCmd::Highlight(cmd) => cmd.run()?,
        flags::RustAnalyzerCmd::AnalysisStats(cmd) => cmd.run(verbosity)?,
        flags::RustAnalyzerCmd::Diagnostics(cmd) => cmd.run()?,
        flags::RustAnalyzerCmd::Ssr(cmd) => cmd.run()?,
        flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?,
        flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
    }
    Ok(())
}

fn setup_logging(log_file: Option<&Path>) -> Result<()> {
    if cfg!(windows) {
        // This is required so that windows finds our pdb that is placed right beside the exe.
        // By default it doesn't look at the folder the exe resides in, only in the current working
        // directory which we set to the project workspace.
        // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/general-environment-variables
        // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize
        if let Ok(path) = env::current_exe() {
            if let Some(path) = path.parent() {
                env::set_var("_NT_SYMBOL_PATH", path);
            }
        }
    }
    if env::var("RUST_BACKTRACE").is_err() {
        env::set_var("RUST_BACKTRACE", "short");
    }

    let log_file = match log_file {
        Some(path) => {
            if let Some(parent) = path.parent() {
                let _ = fs::create_dir_all(parent);
            }
            Some(fs::File::create(path)?)
        }
        None => None,
    };
    let filter = env::var("RA_LOG").ok();
    // deliberately enable all `error` logs if the user has not set RA_LOG, as there is usually useful
    // information in there for debugging
    logger::Logger::new(log_file, filter.as_deref().or(Some("error"))).install()?;

    profile::init();

    Ok(())
}

const STACK_SIZE: usize = 1024 * 1024 * 8;

/// Parts of rust-analyzer can use a lot of stack space, and some operating systems only give us
/// 1 MB by default (eg. Windows), so this spawns a new thread with hopefully sufficient stack
/// space.
fn with_extra_thread(
    thread_name: impl Into<String>,
    f: impl FnOnce() -> Result<()> + Send + 'static,
) -> Result<()> {
    let handle =
        std::thread::Builder::new().name(thread_name.into()).stack_size(STACK_SIZE).spawn(f)?;
    match handle.join() {
        Ok(res) => res,
        Err(panic) => std::panic::resume_unwind(panic),
    }
}

fn run_server() -> Result<()> {
    tracing::info!("server version {} will start", env!("REV"));

    let (connection, io_threads) = Connection::stdio();

    let (initialize_id, initialize_params) = connection.initialize_start()?;
    tracing::info!("InitializeParams: {}", initialize_params);
    let initialize_params =
        from_json::<lsp_types::InitializeParams>("InitializeParams", &initialize_params)?;

    let root_path = match initialize_params
        .root_uri
        .and_then(|it| it.to_file_path().ok())
        .and_then(|it| AbsPathBuf::try_from(it).ok())
    {
        Some(it) => it,
        None => {
            let cwd = env::current_dir()?;
            AbsPathBuf::assert(cwd)
        }
    };

    let mut config = Config::new(root_path, initialize_params.capabilities);
    if let Some(json) = initialize_params.initialization_options {
        if let Err(e) = config.update(json) {
            use lsp_types::{
                notification::{Notification, ShowMessage},
                MessageType, ShowMessageParams,
            };
            let not = lsp_server::Notification::new(
                ShowMessage::METHOD.to_string(),
                ShowMessageParams { typ: MessageType::WARNING, message: e.to_string() },
            );
            connection.sender.send(lsp_server::Message::Notification(not)).unwrap();
        }
    }

    let server_capabilities = rust_analyzer::server_capabilities(&config);

    let initialize_result = lsp_types::InitializeResult {
        capabilities: server_capabilities,
        server_info: Some(lsp_types::ServerInfo {
            name: String::from("rust-analyzer"),
            version: Some(String::from(env!("REV"))),
        }),
        offset_encoding: if supports_utf8(config.caps()) {
            Some("utf-8".to_string())
        } else {
            None
        },
    };

    let initialize_result = serde_json::to_value(initialize_result).unwrap();

    connection.initialize_finish(initialize_id, initialize_result)?;

    if let Some(client_info) = initialize_params.client_info {
        tracing::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default());
    }

    if config.linked_projects().is_empty() && config.detached_files().is_empty() {
        let workspace_roots = initialize_params
            .workspace_folders
            .map(|workspaces| {
                workspaces
                    .into_iter()
                    .filter_map(|it| it.uri.to_file_path().ok())
                    .filter_map(|it| AbsPathBuf::try_from(it).ok())
                    .collect::<Vec<_>>()
            })
            .filter(|workspaces| !workspaces.is_empty())
            .unwrap_or_else(|| vec![config.root_path().clone()]);

        let discovered = ProjectManifest::discover_all(&workspace_roots);
        tracing::info!("discovered projects: {:?}", discovered);
        if discovered.is_empty() {
            tracing::error!("failed to find any projects in {:?}", workspace_roots);
        }
        config.discovered_projects = Some(discovered);
    }

    rust_analyzer::main_loop(config, connection)?;

    io_threads.join()?;
    tracing::info!("server did shut down");
    Ok(())
}
<|endoftext|>


<commit_before>use ide_db::{base_db::Upcast, helpers::pick_best_token, RootDatabase};
use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, T};

use crate::{display::TryToNav, FilePosition, NavigationTarget, RangeInfo};

// Feature: Go to Type Definition
//
// Navigates to the type of an identifier.
//
// |===
// | Editor  | Action Name
//
// | VS Code | **Go to Type Definition*
// |===
//
// image::https://user-images.githubusercontent.com/48062697/113020657-b560f500-917a-11eb-9007-0f809733a338.gif[]
pub(crate) fn goto_type_definition(
    db: &RootDatabase,
    position: FilePosition,
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
    let sema = hir::Semantics::new(db);

    let file: ast::SourceFile = sema.parse(position.file_id);
    let token: SyntaxToken =
        pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
            IDENT | INT_NUMBER | T![self] => 2,
            kind if kind.is_trivia() => 0,
            _ => 1,
        })?;

    let mut res = Vec::new();
    let mut push = |def: hir::ModuleDef| {
        if let Some(nav) = def.try_to_nav(db) {
            if !res.contains(&nav) {
                res.push(nav);
            }
        }
    };
    let range = token.text_range();
    sema.descend_into_macros(token)
        .iter()
        .filter_map(|token| {
            let ty = sema.token_ancestors_with_macros(token.clone()).find_map(|node| {
                let ty = match_ast! {
                    match node {
                        ast::Expr(it) => sema.type_of_expr(&it)?.original,
                        ast::Pat(it) => sema.type_of_pat(&it)?.original,
                        ast::SelfParam(it) => sema.type_of_self(&it)?,
                        ast::Type(it) => sema.resolve_type(&it)?,
                        ast::RecordField(it) => sema.to_def(&it).map(|d| d.ty(db.upcast()))?,
                        // can't match on RecordExprField directly as `ast::Expr` will match an iteration too early otherwise
                        ast::NameRef(it) => {
                            if let Some(record_field) = ast::RecordExprField::for_name_ref(&it) {
                                let (_, _, ty) = sema.resolve_record_field(&record_field)?;
                                ty
                            } else {
                                let record_field = ast::RecordPatField::for_field_name_ref(&it)?;
                                sema.resolve_record_pat_field(&record_field)?.ty(db)
                            }
                        },
                        _ => return None,
                    }
                };

                Some(ty)
            });
            ty
        })
        .for_each(|ty| {
            // collect from each `ty` into the `res` result vec
            let ty = ty.strip_references();
            ty.walk(db, |t| {
                if let Some(adt) = t.as_adt() {
                    push(adt.into());
                } else if let Some(trait_) = t.as_dyn_trait() {
                    push(trait_.into());
                } else if let Some(traits) = t.as_impl_traits(db) {
                    traits.into_iter().for_each(|it| push(it.into()));
                } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
                    push(trait_.into());
                }
            });
        });
    if res.is_empty() {
        None
    } else {
        Some(RangeInfo::new(range, res))
    }
}

#[cfg(test)]
mod tests {
    use ide_db::base_db::FileRange;
    use itertools::Itertools;

    use crate::fixture;

    fn check(ra_fixture: &str) {
        let (analysis, position, expected) = fixture::annotations(ra_fixture);
        let navs = analysis.goto_type_definition(position).unwrap().unwrap().info;
        assert_ne!(navs.len(), 0);

        let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
        let navs = navs
            .into_iter()
            .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
            .sorted_by_key(cmp)
            .collect::<Vec<_>>();
        let expected = expected
            .into_iter()
            .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
            .sorted_by_key(cmp)
            .collect::<Vec<_>>();
        assert_eq!(expected, navs);
    }

    #[test]
    fn goto_type_definition_works_simple() {
        check(
            r#"
struct Foo;
     //^^^
fn foo() {
    let f: Foo; f$0
}
"#,
        );
    }

    #[test]
    fn goto_type_definition_record_expr_field() {
        check(
            r#"
struct Bar;
    // ^^^
struct Foo { foo: Bar }
fn foo() {
    Foo { foo$0 }
}
"#,
        );
        check(
            r#"
struct Bar;
    // ^^^
struct Foo { foo: Bar }
fn foo() {
    Foo { foo$0: Bar }
}
"#,
        );
    }

    #[test]
    fn goto_type_definition_record_pat_field() {
        check(
            r#"
struct Bar;
    // ^^^
struct Foo { foo: Bar }
fn foo() {
    let Foo { foo$0 };
}
"#,
        );
        check(
            r#"
struct Bar;
    // ^^^
struct Foo { foo: Bar }
fn foo() {
    let Foo { foo$0: bar };
}
"#,
        );
    }

    #[test]
    fn goto_type_definition_works_simple_ref() {
        check(
            r#"
struct Foo;
     //^^^
fn foo() {
    let f: &Foo; f$0
}
"#,
        );
    }

    #[test]
    fn goto_type_definition_works_through_macro() {
        check(
            r#"
macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
struct Foo {}
     //^^^
id! {
    fn bar() { let f$0 = Foo {}; }
}
"#,
        );
    }

    #[test]
    fn goto_type_definition_for_param() {
        check(
            r#"
struct Foo;
     //^^^
fn foo($0f: Foo) {}
"#,
        );
    }

    #[test]
    fn goto_type_definition_for_tuple_field() {
        check(
            r#"
struct Foo;
     //^^^
struct Bar(Foo);
fn foo() {
    let bar = Bar(Foo);
    bar.$00;
}
"#,
        );
    }

    #[test]
    fn goto_def_for_self_param() {
        check(
            r#"
struct Foo;
     //^^^
impl Foo {
    fn f(&self$0) {}
}
"#,
        )
    }

    #[test]
    fn goto_def_for_type_fallback() {
        check(
            r#"
struct Foo;
     //^^^
impl Foo$0 {}
"#,
        )
    }

    #[test]
    fn goto_def_for_struct_field() {
        check(
            r#"
struct Bar;
     //^^^

struct Foo {
    bar$0: Bar,
}
"#,
        );
    }

    #[test]
    fn goto_def_for_enum_struct_field() {
        check(
            r#"
struct Bar;
     //^^^

enum Foo {
    Bar {
        bar$0: Bar
    },
}
"#,
        );
    }

    #[test]
    fn goto_def_considers_generics() {
        check(
            r#"
struct Foo;
     //^^^
struct Bar<T, U>(T, U);
     //^^^
struct Baz<T>(T);
     //^^^

fn foo(x$0: Bar<Baz<Foo>, Baz<usize>) {}
"#,
        );
    }
}
<commit_msg>assume valid identifier<commit_after>use ide_db::{base_db::Upcast, helpers::pick_best_token, RootDatabase};
use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, T};

use crate::{display::TryToNav, FilePosition, NavigationTarget, RangeInfo};

// Feature: Go to Type Definition
//
// Navigates to the type of an identifier.
//
// |===
// | Editor  | Action Name
//
// | VS Code | **Go to Type Definition*
// |===
//
// image::https://user-images.githubusercontent.com/48062697/113020657-b560f500-917a-11eb-9007-0f809733a338.gif[]
pub(crate) fn goto_type_definition(
    db: &RootDatabase,
    position: FilePosition,
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
    let sema = hir::Semantics::new(db);

    let file: ast::SourceFile = sema.parse(position.file_id);
    let token: SyntaxToken =
        pick_best_token(file.syntax().token_at_offset(position.offset), |kind| match kind {
            IDENT | INT_NUMBER | T![self] => 2,
            kind if kind.is_trivia() => 0,
            _ => 1,
        })?;

    let mut res = Vec::new();
    let mut push = |def: hir::ModuleDef| {
        if let Some(nav) = def.try_to_nav(db) {
            if !res.contains(&nav) {
                res.push(nav);
            }
        }
    };
    let range = token.text_range();
    sema.descend_into_macros(token)
        .iter()
        .filter_map(|token| {
            let ty = sema.token_ancestors_with_macros(token.clone()).find_map(|node| {
                let ty = match_ast! {
                    match node {
                        ast::Expr(it) => sema.type_of_expr(&it)?.original,
                        ast::Pat(it) => sema.type_of_pat(&it)?.original,
                        ast::SelfParam(it) => sema.type_of_self(&it)?,
                        ast::Type(it) => sema.resolve_type(&it)?,
                        ast::RecordField(it) => sema.to_def(&it).map(|d| d.ty(db.upcast()))?,
                        // can't match on RecordExprField directly as `ast::Expr` will match an iteration too early otherwise
                        ast::NameRef(it) => {
                            if let Some(record_field) = ast::RecordExprField::for_name_ref(&it) {
                                let (_, _, ty) = sema.resolve_record_field(&record_field)?;
                                ty
                            } else {
                                let record_field = ast::RecordPatField::for_field_name_ref(&it)?;
                                sema.resolve_record_pat_field(&record_field)?.ty(db)
                            }
                        },
                        _ => return None,
                    }
                };

                Some(ty)
            });
            ty
        })
        .for_each(|ty| {
            // collect from each `ty` into the `res` result vec
            let ty = ty.strip_references();
            ty.walk(db, |t| {
                if let Some(adt) = t.as_adt() {
                    push(adt.into());
                } else if let Some(trait_) = t.as_dyn_trait() {
                    push(trait_.into());
                } else if let Some(traits) = t.as_impl_traits(db) {
                    traits.into_iter().for_each(|it| push(it.into()));
                } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
                    push(trait_.into());
                }
            });
        });
    Some(RangeInfo::new(range, res))
}

#[cfg(test)]
mod tests {
    use ide_db::base_db::FileRange;
    use itertools::Itertools;

    use crate::fixture;

    fn check(ra_fixture: &str) {
        let (analysis, position, expected) = fixture::annotations(ra_fixture);
        let navs = analysis.goto_type_definition(position).unwrap().unwrap().info;
        assert_ne!(navs.len(), 0);

        let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
        let navs = navs
            .into_iter()
            .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
            .sorted_by_key(cmp)
            .collect::<Vec<_>>();
        let expected = expected
            .into_iter()
            .map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
            .sorted_by_key(cmp)
            .collect::<Vec<_>>();
        assert_eq!(expected, navs);
    }

    #[test]
    fn goto_type_definition_works_simple() {
        check(
            r#"
struct Foo;
     //^^^
fn foo() {
    let f: Foo; f$0
}
"#,
        );
    }

    #[test]
    fn goto_type_definition_record_expr_field() {
        check(
            r#"
struct Bar;
    // ^^^
struct Foo { foo: Bar }
fn foo() {
    Foo { foo$0 }
}
"#,
        );
        check(
            r#"
struct Bar;
    // ^^^
struct Foo { foo: Bar }
fn foo() {
    Foo { foo$0: Bar }
}
"#,
        );
    }

    #[test]
    fn goto_type_definition_record_pat_field() {
        check(
            r#"
struct Bar;
    // ^^^
struct Foo { foo: Bar }
fn foo() {
    let Foo { foo$0 };
}
"#,
        );
        check(
            r#"
struct Bar;
    // ^^^
struct Foo { foo: Bar }
fn foo() {
    let Foo { foo$0: bar };
}
"#,
        );
    }

    #[test]
    fn goto_type_definition_works_simple_ref() {
        check(
            r#"
struct Foo;
     //^^^
fn foo() {
    let f: &Foo; f$0
}
"#,
        );
    }

    #[test]
    fn goto_type_definition_works_through_macro() {
        check(
            r#"
macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
struct Foo {}
     //^^^
id! {
    fn bar() { let f$0 = Foo {}; }
}
"#,
        );
    }

    #[test]
    fn goto_type_definition_for_param() {
        check(
            r#"
struct Foo;
     //^^^
fn foo($0f: Foo) {}
"#,
        );
    }

    #[test]
    fn goto_type_definition_for_tuple_field() {
        check(
            r#"
struct Foo;
     //^^^
struct Bar(Foo);
fn foo() {
    let bar = Bar(Foo);
    bar.$00;
}
"#,
        );
    }

    #[test]
    fn goto_def_for_self_param() {
        check(
            r#"
struct Foo;
     //^^^
impl Foo {
    fn f(&self$0) {}
}
"#,
        )
    }

    #[test]
    fn goto_def_for_type_fallback() {
        check(
            r#"
struct Foo;
     //^^^
impl Foo$0 {}
"#,
        )
    }

    #[test]
    fn goto_def_for_struct_field() {
        check(
            r#"
struct Bar;
     //^^^

struct Foo {
    bar$0: Bar,
}
"#,
        );
    }

    #[test]
    fn goto_def_for_enum_struct_field() {
        check(
            r#"
struct Bar;
     //^^^

enum Foo {
    Bar {
        bar$0: Bar
    },
}
"#,
        );
    }

    #[test]
    fn goto_def_considers_generics() {
        check(
            r#"
struct Foo;
     //^^^
struct Bar<T, U>(T, U);
     //^^^
struct Baz<T>(T);
     //^^^

fn foo(x$0: Bar<Baz<Foo>, Baz<usize>) {}
"#,
        );
    }
}
<|endoftext|>


<commit_before>//! Handles build script specific information

use std::{
    path::PathBuf,
    process::{Command, Stdio},
    sync::Arc,
};

use anyhow::Result;
use cargo_metadata::camino::Utf8Path;
use cargo_metadata::{BuildScript, Message};
use itertools::Itertools;
use paths::{AbsPath, AbsPathBuf};
use rustc_hash::FxHashMap;
use serde::Deserialize;
use stdx::format_to;

use crate::{cfg_flag::CfgFlag, CargoConfig};

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct PackageBuildData {
    /// List of config flags defined by this package's build script
    pub(crate) cfgs: Vec<CfgFlag>,
    /// List of cargo-related environment variables with their value
    ///
    /// If the package has a build script which defines environment variables,
    /// they can also be found here.
    pub(crate) envs: Vec<(String, String)>,
    /// Directory where a build script might place its output
    pub(crate) out_dir: Option<AbsPathBuf>,
    /// Path to the proc-macro library file if this package exposes proc-macros
    pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
}

#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub(crate) struct WorkspaceBuildData {
    per_package: FxHashMap<String, PackageBuildData>,
    error: Option<String>,
}

#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct BuildDataResult {
    per_workspace: FxHashMap<AbsPathBuf, WorkspaceBuildData>,
}

#[derive(Clone, Debug)]
pub(crate) struct BuildDataConfig {
    cargo_toml: AbsPathBuf,
    cargo_features: CargoConfig,
    packages: Arc<Vec<cargo_metadata::Package>>,
}

impl PartialEq for BuildDataConfig {
    fn eq(&self, other: &Self) -> bool {
        Arc::ptr_eq(&self.packages, &other.packages)
    }
}

impl Eq for BuildDataConfig {}

#[derive(Debug)]
pub struct BuildDataCollector {
    wrap_rustc: bool,
    configs: FxHashMap<AbsPathBuf, BuildDataConfig>,
}

impl BuildDataCollector {
    pub fn new(wrap_rustc: bool) -> Self {
        Self { wrap_rustc, configs: FxHashMap::default() }
    }

    pub(crate) fn add_config(&mut self, workspace_root: &AbsPath, config: BuildDataConfig) {
        self.configs.insert(workspace_root.to_path_buf(), config);
    }

    pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> {
        let mut res = BuildDataResult::default();
        for (path, config) in self.configs.iter() {
            let workspace_build_data = WorkspaceBuildData::collect(
                &config.cargo_toml,
                &config.cargo_features,
                &config.packages,
                self.wrap_rustc,
                progress,
            )?;
            res.per_workspace.insert(path.clone(), workspace_build_data);
        }
        Ok(res)
    }
}

impl WorkspaceBuildData {
    pub(crate) fn get(&self, package_id: &str) -> Option<&PackageBuildData> {
        self.per_package.get(package_id)
    }
}

impl BuildDataResult {
    pub(crate) fn get(&self, workspace_root: &AbsPath) -> Option<&WorkspaceBuildData> {
        self.per_workspace.get(workspace_root)
    }
    pub fn error(&self) -> Option<String> {
        let mut buf = String::new();
        for (_workspace_root, build_data) in &self.per_workspace {
            if let Some(err) = &build_data.error {
                format_to!(buf, "cargo check failed:\n{}", err);
            }
        }
        if buf.is_empty() {
            return None;
        }

        Some(buf)
    }
}

impl BuildDataConfig {
    pub(crate) fn new(
        cargo_toml: AbsPathBuf,
        cargo_features: CargoConfig,
        packages: Arc<Vec<cargo_metadata::Package>>,
    ) -> Self {
        Self { cargo_toml, cargo_features, packages }
    }
}

impl WorkspaceBuildData {
    fn collect(
        cargo_toml: &AbsPath,
        cargo_features: &CargoConfig,
        packages: &Vec<cargo_metadata::Package>,
        wrap_rustc: bool,
        progress: &dyn Fn(String),
    ) -> Result<WorkspaceBuildData> {
        let mut cmd = Command::new(toolchain::cargo());

        if wrap_rustc {
            // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use
            // that to compile only proc macros and build scripts during the initial
            // `cargo check`.
            let myself = std::env::current_exe()?;
            cmd.env("RUSTC_WRAPPER", myself);
            cmd.env("RA_RUSTC_WRAPPER", "1");
        }

        cmd.current_dir(cargo_toml.parent().unwrap());
        cmd.args(&["check", "--quiet", "--workspace", "--message-format=json", "--manifest-path"])
            .arg(cargo_toml.as_ref());

        // --all-targets includes tests, benches and examples in addition to the
        // default lib and bins. This is an independent concept from the --targets
        // flag below.
        cmd.arg("--all-targets");

        if let Some(target) = &cargo_features.target {
            cmd.args(&["--target", target]);
        }

        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");
            }
            if !cargo_features.features.is_empty() {
                cmd.arg("--features");
                cmd.arg(cargo_features.features.join(" "));
            }
        }

        cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());

        let mut res = WorkspaceBuildData::default();

        let mut callback_err = None;
        let output = stdx::process::streaming_output(
            cmd,
            &mut |line| {
                if callback_err.is_some() {
                    return;
                }

                // Copy-pasted from existing cargo_metadata. It seems like we
                // should be using sered_stacker here?
                let mut deserializer = serde_json::Deserializer::from_str(line);
                deserializer.disable_recursion_limit();
                let message = Message::deserialize(&mut deserializer)
                    .unwrap_or_else(|_| Message::TextLine(line.to_string()));

                match message {
                    Message::BuildScriptExecuted(BuildScript {
                        package_id,
                        out_dir,
                        cfgs,
                        env,
                        ..
                    }) => {
                        let cfgs = {
                            let mut acc = Vec::new();
                            for cfg in cfgs {
                                match cfg.parse::<CfgFlag>() {
                                    Ok(it) => acc.push(it),
                                    Err(err) => {
                                        callback_err = Some(anyhow::format_err!(
                                            "invalid cfg from cargo-metadata: {}",
                                            err
                                        ));
                                        return;
                                    }
                                };
                            }
                            acc
                        };
                        let package_build_data =
                            res.per_package.entry(package_id.repr).or_default();
                        // cargo_metadata crate returns default (empty) path for
                        // older cargos, which is not absolute, so work around that.
                        if !out_dir.as_str().is_empty() {
                            let out_dir =
                                AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string()));
                            package_build_data.out_dir = Some(out_dir);
                            package_build_data.cfgs = cfgs;
                        }

                        package_build_data.envs = env;
                    }
                    Message::CompilerArtifact(message) => {
                        progress(format!("metadata {}", message.target.name));

                        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))
                            {
                                let filename = AbsPathBuf::assert(PathBuf::from(&filename));
                                let package_build_data =
                                    res.per_package.entry(package_id.repr).or_default();
                                package_build_data.proc_macro_dylib_path = Some(filename);
                            }
                        }
                    }
                    Message::CompilerMessage(message) => {
                        progress(message.target.name);
                    }
                    Message::BuildFinished(_) => {}
                    Message::TextLine(_) => {}
                    _ => {}
                }
            },
            &mut |_| (),
        )?;

        for package in packages {
            let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default();
            inject_cargo_env(package, package_build_data);
            if let Some(out_dir) = &package_build_data.out_dir {
                // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
                if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
                    package_build_data.envs.push(("OUT_DIR".to_string(), out_dir));
                }
            }
        }

        if !output.status.success() {
            let mut stderr = String::from_utf8(output.stderr).unwrap_or_default();
            if stderr.is_empty() {
                stderr = "cargo check failed".to_string();
            }
            res.error = Some(stderr)
        }

        Ok(res)
    }
}

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

/// Recreates the compile-time environment variables that Cargo sets.
///
/// Should be synced with <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
fn inject_cargo_env(package: &cargo_metadata::Package, build_data: &mut PackageBuildData) {
    let env = &mut build_data.envs;

    // FIXME: Missing variables:
    // CARGO_PKG_HOMEPAGE, CARGO_CRATE_NAME, CARGO_BIN_NAME, CARGO_BIN_EXE_<name>

    let mut manifest_dir = package.manifest_path.clone();
    manifest_dir.pop();
    env.push(("CARGO_MANIFEST_DIR".into(), manifest_dir.into_string()));

    // Not always right, but works for common cases.
    env.push(("CARGO".into(), "cargo".into()));

    env.push(("CARGO_PKG_VERSION".into(), package.version.to_string()));
    env.push(("CARGO_PKG_VERSION_MAJOR".into(), package.version.major.to_string()));
    env.push(("CARGO_PKG_VERSION_MINOR".into(), package.version.minor.to_string()));
    env.push(("CARGO_PKG_VERSION_PATCH".into(), package.version.patch.to_string()));

    let pre = package.version.pre.iter().map(|id| id.to_string()).format(".");
    env.push(("CARGO_PKG_VERSION_PRE".into(), pre.to_string()));

    let authors = package.authors.join(";");
    env.push(("CARGO_PKG_AUTHORS".into(), authors));

    env.push(("CARGO_PKG_NAME".into(), package.name.clone()));
    env.push(("CARGO_PKG_DESCRIPTION".into(), package.description.clone().unwrap_or_default()));
    //env.push(("CARGO_PKG_HOMEPAGE".into(), package.homepage.clone().unwrap_or_default()));
    env.push(("CARGO_PKG_REPOSITORY".into(), package.repository.clone().unwrap_or_default()));
    env.push(("CARGO_PKG_LICENSE".into(), package.license.clone().unwrap_or_default()));

    let license_file = package.license_file.as_ref().map(|buf| buf.to_string()).unwrap_or_default();
    env.push(("CARGO_PKG_LICENSE_FILE".into(), license_file));
}
<commit_msg>Avoid string allocation<commit_after>//! Handles build script specific information

use std::{
    path::PathBuf,
    process::{Command, Stdio},
    sync::Arc,
};

use anyhow::Result;
use cargo_metadata::camino::Utf8Path;
use cargo_metadata::{BuildScript, Message};
use itertools::Itertools;
use paths::{AbsPath, AbsPathBuf};
use rustc_hash::FxHashMap;
use serde::Deserialize;
use stdx::format_to;

use crate::{cfg_flag::CfgFlag, CargoConfig};

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct PackageBuildData {
    /// List of config flags defined by this package's build script
    pub(crate) cfgs: Vec<CfgFlag>,
    /// List of cargo-related environment variables with their value
    ///
    /// If the package has a build script which defines environment variables,
    /// they can also be found here.
    pub(crate) envs: Vec<(String, String)>,
    /// Directory where a build script might place its output
    pub(crate) out_dir: Option<AbsPathBuf>,
    /// Path to the proc-macro library file if this package exposes proc-macros
    pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
}

#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub(crate) struct WorkspaceBuildData {
    per_package: FxHashMap<String, PackageBuildData>,
    error: Option<String>,
}

#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct BuildDataResult {
    per_workspace: FxHashMap<AbsPathBuf, WorkspaceBuildData>,
}

#[derive(Clone, Debug)]
pub(crate) struct BuildDataConfig {
    cargo_toml: AbsPathBuf,
    cargo_features: CargoConfig,
    packages: Arc<Vec<cargo_metadata::Package>>,
}

impl PartialEq for BuildDataConfig {
    fn eq(&self, other: &Self) -> bool {
        Arc::ptr_eq(&self.packages, &other.packages)
    }
}

impl Eq for BuildDataConfig {}

#[derive(Debug)]
pub struct BuildDataCollector {
    wrap_rustc: bool,
    configs: FxHashMap<AbsPathBuf, BuildDataConfig>,
}

impl BuildDataCollector {
    pub fn new(wrap_rustc: bool) -> Self {
        Self { wrap_rustc, configs: FxHashMap::default() }
    }

    pub(crate) fn add_config(&mut self, workspace_root: &AbsPath, config: BuildDataConfig) {
        self.configs.insert(workspace_root.to_path_buf(), config);
    }

    pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> {
        let mut res = BuildDataResult::default();
        for (path, config) in self.configs.iter() {
            let workspace_build_data = WorkspaceBuildData::collect(
                &config.cargo_toml,
                &config.cargo_features,
                &config.packages,
                self.wrap_rustc,
                progress,
            )?;
            res.per_workspace.insert(path.clone(), workspace_build_data);
        }
        Ok(res)
    }
}

impl WorkspaceBuildData {
    pub(crate) fn get(&self, package_id: &str) -> Option<&PackageBuildData> {
        self.per_package.get(package_id)
    }
}

impl BuildDataResult {
    pub(crate) fn get(&self, workspace_root: &AbsPath) -> Option<&WorkspaceBuildData> {
        self.per_workspace.get(workspace_root)
    }
    pub fn error(&self) -> Option<String> {
        let mut buf = String::new();
        for (_workspace_root, build_data) in &self.per_workspace {
            if let Some(err) = &build_data.error {
                format_to!(buf, "cargo check failed:\n{}", err);
            }
        }
        if buf.is_empty() {
            return None;
        }

        Some(buf)
    }
}

impl BuildDataConfig {
    pub(crate) fn new(
        cargo_toml: AbsPathBuf,
        cargo_features: CargoConfig,
        packages: Arc<Vec<cargo_metadata::Package>>,
    ) -> Self {
        Self { cargo_toml, cargo_features, packages }
    }
}

impl WorkspaceBuildData {
    fn collect(
        cargo_toml: &AbsPath,
        cargo_features: &CargoConfig,
        packages: &Vec<cargo_metadata::Package>,
        wrap_rustc: bool,
        progress: &dyn Fn(String),
    ) -> Result<WorkspaceBuildData> {
        let mut cmd = Command::new(toolchain::cargo());

        if wrap_rustc {
            // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use
            // that to compile only proc macros and build scripts during the initial
            // `cargo check`.
            let myself = std::env::current_exe()?;
            cmd.env("RUSTC_WRAPPER", myself);
            cmd.env("RA_RUSTC_WRAPPER", "1");
        }

        cmd.current_dir(cargo_toml.parent().unwrap());
        cmd.args(&["check", "--quiet", "--workspace", "--message-format=json", "--manifest-path"])
            .arg(cargo_toml.as_ref());

        // --all-targets includes tests, benches and examples in addition to the
        // default lib and bins. This is an independent concept from the --targets
        // flag below.
        cmd.arg("--all-targets");

        if let Some(target) = &cargo_features.target {
            cmd.args(&["--target", target]);
        }

        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");
            }
            if !cargo_features.features.is_empty() {
                cmd.arg("--features");
                cmd.arg(cargo_features.features.join(" "));
            }
        }

        cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());

        let mut res = WorkspaceBuildData::default();

        let mut callback_err = None;
        let output = stdx::process::streaming_output(
            cmd,
            &mut |line| {
                if callback_err.is_some() {
                    return;
                }

                // Copy-pasted from existing cargo_metadata. It seems like we
                // should be using sered_stacker here?
                let mut deserializer = serde_json::Deserializer::from_str(line);
                deserializer.disable_recursion_limit();
                let message = Message::deserialize(&mut deserializer)
                    .unwrap_or_else(|_| Message::TextLine(line.to_string()));

                match message {
                    Message::BuildScriptExecuted(BuildScript {
                        package_id,
                        out_dir,
                        cfgs,
                        env,
                        ..
                    }) => {
                        let cfgs = {
                            let mut acc = Vec::new();
                            for cfg in cfgs {
                                match cfg.parse::<CfgFlag>() {
                                    Ok(it) => acc.push(it),
                                    Err(err) => {
                                        callback_err = Some(anyhow::format_err!(
                                            "invalid cfg from cargo-metadata: {}",
                                            err
                                        ));
                                        return;
                                    }
                                };
                            }
                            acc
                        };
                        let package_build_data =
                            res.per_package.entry(package_id.repr).or_default();
                        // cargo_metadata crate returns default (empty) path for
                        // older cargos, which is not absolute, so work around that.
                        if !out_dir.as_str().is_empty() {
                            let out_dir =
                                AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string()));
                            package_build_data.out_dir = Some(out_dir);
                            package_build_data.cfgs = cfgs;
                        }

                        package_build_data.envs = env;
                    }
                    Message::CompilerArtifact(message) => {
                        progress(format!("metadata {}", message.target.name));

                        if message.target.kind.iter().any(|k| k == "proc-macro") {
                            let package_id = message.package_id;
                            // Skip rmeta file
                            if let Some(filename) =
                                message.filenames.iter().find(|name| is_dylib(name))
                            {
                                let filename = AbsPathBuf::assert(PathBuf::from(&filename));
                                let package_build_data =
                                    res.per_package.entry(package_id.repr).or_default();
                                package_build_data.proc_macro_dylib_path = Some(filename);
                            }
                        }
                    }
                    Message::CompilerMessage(message) => {
                        progress(message.target.name);
                    }
                    Message::BuildFinished(_) => {}
                    Message::TextLine(_) => {}
                    _ => {}
                }
            },
            &mut |_| (),
        )?;

        for package in packages {
            let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default();
            inject_cargo_env(package, package_build_data);
            if let Some(out_dir) = &package_build_data.out_dir {
                // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
                if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
                    package_build_data.envs.push(("OUT_DIR".to_string(), out_dir));
                }
            }
        }

        if !output.status.success() {
            let mut stderr = String::from_utf8(output.stderr).unwrap_or_default();
            if stderr.is_empty() {
                stderr = "cargo check failed".to_string();
            }
            res.error = Some(stderr)
        }

        Ok(res)
    }
}

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

/// Recreates the compile-time environment variables that Cargo sets.
///
/// Should be synced with <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
fn inject_cargo_env(package: &cargo_metadata::Package, build_data: &mut PackageBuildData) {
    let env = &mut build_data.envs;

    // FIXME: Missing variables:
    // CARGO_PKG_HOMEPAGE, CARGO_CRATE_NAME, CARGO_BIN_NAME, CARGO_BIN_EXE_<name>

    let mut manifest_dir = package.manifest_path.clone();
    manifest_dir.pop();
    env.push(("CARGO_MANIFEST_DIR".into(), manifest_dir.into_string()));

    // Not always right, but works for common cases.
    env.push(("CARGO".into(), "cargo".into()));

    env.push(("CARGO_PKG_VERSION".into(), package.version.to_string()));
    env.push(("CARGO_PKG_VERSION_MAJOR".into(), package.version.major.to_string()));
    env.push(("CARGO_PKG_VERSION_MINOR".into(), package.version.minor.to_string()));
    env.push(("CARGO_PKG_VERSION_PATCH".into(), package.version.patch.to_string()));

    let pre = package.version.pre.iter().map(|id| id.to_string()).format(".");
    env.push(("CARGO_PKG_VERSION_PRE".into(), pre.to_string()));

    let authors = package.authors.join(";");
    env.push(("CARGO_PKG_AUTHORS".into(), authors));

    env.push(("CARGO_PKG_NAME".into(), package.name.clone()));
    env.push(("CARGO_PKG_DESCRIPTION".into(), package.description.clone().unwrap_or_default()));
    //env.push(("CARGO_PKG_HOMEPAGE".into(), package.homepage.clone().unwrap_or_default()));
    env.push(("CARGO_PKG_REPOSITORY".into(), package.repository.clone().unwrap_or_default()));
    env.push(("CARGO_PKG_LICENSE".into(), package.license.clone().unwrap_or_default()));

    let license_file = package.license_file.as_ref().map(|buf| buf.to_string()).unwrap_or_default();
    env.push(("CARGO_PKG_LICENSE_FILE".into(), license_file));
}
<|endoftext|>


<commit_before>//! Various extension methods to ast Nodes, which are hard to code-generate.
//! Extensions for various expressions live in a sibling `expr_extensions` module.

use itertools::Itertools;

use crate::{SmolStr, SyntaxToken, ast::{self, AstNode, children, child_opt}, SyntaxKind::*, SyntaxElement, T};
use ra_parser::SyntaxKind;

impl ast::Name {
    pub fn text(&self) -> &SmolStr {
        let ident = self.syntax().first_child_or_token().unwrap().as_token().unwrap();
        ident.text()
    }
}

impl ast::NameRef {
    pub fn text(&self) -> &SmolStr {
        let ident = self.syntax().first_child_or_token().unwrap().as_token().unwrap();
        ident.text()
    }
}

impl ast::Attr {
    pub fn is_inner(&self) -> bool {
        let tt = match self.value() {
            None => return false,
            Some(tt) => tt,
        };

        let prev = match tt.syntax().prev_sibling() {
            None => return false,
            Some(prev) => prev,
        };

        prev.kind() == T![!]
    }

    pub fn as_atom(&self) -> Option<SmolStr> {
        let tt = self.value()?;
        let (_bra, attr, _ket) = tt.syntax().children_with_tokens().collect_tuple()?;
        if attr.kind() == IDENT {
            Some(attr.as_token()?.text().clone())
        } else {
            None
        }
    }

    pub fn as_call(&self) -> Option<(SmolStr, &ast::TokenTree)> {
        let tt = self.value()?;
        let (_bra, attr, args, _ket) = tt.syntax().children_with_tokens().collect_tuple()?;
        let args = ast::TokenTree::cast(args.as_node()?)?;
        if attr.kind() == IDENT {
            Some((attr.as_token()?.text().clone(), args))
        } else {
            None
        }
    }

    pub fn as_named(&self) -> Option<SmolStr> {
        let tt = self.value()?;
        let attr = tt.syntax().children_with_tokens().nth(1)?;
        if attr.kind() == IDENT {
            Some(attr.as_token()?.text().clone())
        } else {
            None
        }
    }

    pub fn as_key_value(&self) -> Option<(SmolStr, SmolStr)> {
        let tt = self.value()?;
        let tt_node = tt.syntax();
        let attr = tt_node.children_with_tokens().nth(1)?;
        if attr.kind() == IDENT {
            let key = attr.as_token()?.text().clone();
            let val_node = tt_node.children_with_tokens().find(|t| t.kind() == STRING)?;
            let val = val_node.as_token()?.text().trim_start_matches("\"").trim_end_matches("\"");
            Some((key, SmolStr::new(val)))
        } else {
            None
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathSegmentKind<'a> {
    Name(&'a ast::NameRef),
    SelfKw,
    SuperKw,
    CrateKw,
}

impl ast::PathSegment {
    pub fn parent_path(&self) -> &ast::Path {
        self.syntax()
            .parent()
            .and_then(ast::Path::cast)
            .expect("segments are always nested in paths")
    }

    pub fn kind(&self) -> Option<PathSegmentKind> {
        let res = if let Some(name_ref) = self.name_ref() {
            PathSegmentKind::Name(name_ref)
        } else {
            match self.syntax().first_child_or_token()?.kind() {
                T![self] => PathSegmentKind::SelfKw,
                T![super] => PathSegmentKind::SuperKw,
                T![crate] => PathSegmentKind::CrateKw,
                _ => return None,
            }
        };
        Some(res)
    }

    pub fn has_colon_colon(&self) -> bool {
        match self.syntax.first_child_or_token().map(|s| s.kind()) {
            Some(T![::]) => true,
            _ => false,
        }
    }
}

impl ast::Path {
    pub fn parent_path(&self) -> Option<&ast::Path> {
        self.syntax().parent().and_then(ast::Path::cast)
    }
}

impl ast::Module {
    pub fn has_semi(&self) -> bool {
        match self.syntax().last_child_or_token() {
            None => false,
            Some(node) => node.kind() == T![;],
        }
    }
}

impl ast::UseTree {
    pub fn has_star(&self) -> bool {
        self.syntax().children_with_tokens().any(|it| it.kind() == T![*])
    }
}

impl ast::UseTreeList {
    pub fn parent_use_tree(&self) -> &ast::UseTree {
        self.syntax()
            .parent()
            .and_then(ast::UseTree::cast)
            .expect("UseTreeLists are always nested in UseTrees")
    }
}

impl ast::ImplBlock {
    pub fn target_type(&self) -> Option<&ast::TypeRef> {
        match self.target() {
            (Some(t), None) | (_, Some(t)) => Some(t),
            _ => None,
        }
    }

    pub fn target_trait(&self) -> Option<&ast::TypeRef> {
        match self.target() {
            (Some(t), Some(_)) => Some(t),
            _ => None,
        }
    }

    fn target(&self) -> (Option<&ast::TypeRef>, Option<&ast::TypeRef>) {
        let mut types = children(self);
        let first = types.next();
        let second = types.next();
        (first, second)
    }

    pub fn is_negative(&self) -> bool {
        self.syntax().children_with_tokens().any(|t| t.kind() == T![!])
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StructKind<'a> {
    Tuple(&'a ast::PosFieldDefList),
    Named(&'a ast::NamedFieldDefList),
    Unit,
}

impl StructKind<'_> {
    fn from_node<N: AstNode>(node: &N) -> StructKind {
        if let Some(nfdl) = child_opt::<_, ast::NamedFieldDefList>(node) {
            StructKind::Named(nfdl)
        } else if let Some(pfl) = child_opt::<_, ast::PosFieldDefList>(node) {
            StructKind::Tuple(pfl)
        } else {
            StructKind::Unit
        }
    }
}

impl ast::StructDef {
    pub fn kind(&self) -> StructKind {
        StructKind::from_node(self)
    }
}

impl ast::EnumVariant {
    pub fn parent_enum(&self) -> &ast::EnumDef {
        self.syntax()
            .parent()
            .and_then(|it| it.parent())
            .and_then(ast::EnumDef::cast)
            .expect("EnumVariants are always nested in Enums")
    }
    pub fn kind(&self) -> StructKind {
        StructKind::from_node(self)
    }
}

impl ast::FnDef {
    pub fn semicolon_token(&self) -> Option<SyntaxToken<'_>> {
        self.syntax()
            .last_child_or_token()
            .and_then(|it| it.as_token())
            .filter(|it| it.kind() == T![;])
    }
}

impl ast::LetStmt {
    pub fn has_semi(&self) -> bool {
        match self.syntax().last_child_or_token() {
            None => false,
            Some(node) => node.kind() == T![;],
        }
    }
}

impl ast::ExprStmt {
    pub fn has_semi(&self) -> bool {
        match self.syntax().last_child_or_token() {
            None => false,
            Some(node) => node.kind() == T![;],
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FieldKind<'a> {
    Name(&'a ast::NameRef),
    Index(SyntaxToken<'a>),
}

impl ast::FieldExpr {
    pub fn index_token(&self) -> Option<SyntaxToken> {
        self.syntax
            .children_with_tokens()
            // FIXME: Accepting floats here to reject them in validation later
            .find(|c| c.kind() == SyntaxKind::INT_NUMBER || c.kind() == SyntaxKind::FLOAT_NUMBER)
            .as_ref()
            .and_then(SyntaxElement::as_token)
    }

    pub fn field_access(&self) -> Option<FieldKind> {
        if let Some(nr) = self.name_ref() {
            Some(FieldKind::Name(nr))
        } else if let Some(tok) = self.index_token() {
            Some(FieldKind::Index(tok))
        } else {
            None
        }
    }
}

impl ast::RefPat {
    pub fn is_mut(&self) -> bool {
        self.syntax().children_with_tokens().any(|n| n.kind() == T![mut])
    }
}

impl ast::BindPat {
    pub fn is_mutable(&self) -> bool {
        self.syntax().children_with_tokens().any(|n| n.kind() == T![mut])
    }

    pub fn is_ref(&self) -> bool {
        self.syntax().children_with_tokens().any(|n| n.kind() == T![ref])
    }
}

impl ast::PointerType {
    pub fn is_mut(&self) -> bool {
        self.syntax().children_with_tokens().any(|n| n.kind() == T![mut])
    }
}

impl ast::ReferenceType {
    pub fn is_mut(&self) -> bool {
        self.syntax().children_with_tokens().any(|n| n.kind() == T![mut])
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum SelfParamKind {
    /// self
    Owned,
    /// &self
    Ref,
    /// &mut self
    MutRef,
}

impl ast::SelfParam {
    pub fn self_kw_token(&self) -> SyntaxToken {
        self.syntax()
            .children_with_tokens()
            .filter_map(|it| it.as_token())
            .find(|it| it.kind() == T![self])
            .expect("invalid tree: self param must have self")
    }

    pub fn kind(&self) -> SelfParamKind {
        let borrowed = self.syntax().children_with_tokens().any(|n| n.kind() == T![&]);
        if borrowed {
            // check for a `mut` coming after the & -- `mut &self` != `&mut self`
            if self
                .syntax()
                .children_with_tokens()
                .skip_while(|n| n.kind() != T![&])
                .any(|n| n.kind() == T![mut])
            {
                SelfParamKind::MutRef
            } else {
                SelfParamKind::Ref
            }
        } else {
            SelfParamKind::Owned
        }
    }
}

impl ast::LifetimeParam {
    pub fn lifetime_token(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|it| it.as_token())
            .find(|it| it.kind() == LIFETIME)
    }
}

impl ast::WherePred {
    pub fn lifetime_token(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|it| it.as_token())
            .find(|it| it.kind() == LIFETIME)
    }
}

impl ast::TraitDef {
    pub fn is_auto(&self) -> bool {
        self.syntax().children_with_tokens().any(|t| t.kind() == T![auto])
    }
}
<commit_msg>add is_union to structs AST<commit_after>//! Various extension methods to ast Nodes, which are hard to code-generate.
//! Extensions for various expressions live in a sibling `expr_extensions` module.

use itertools::Itertools;

use crate::{
    SmolStr, SyntaxToken,
    ast::{self, AstNode, children, child_opt},
    SyntaxKind::*,
    SyntaxElement, T,
};
use ra_parser::SyntaxKind;

impl ast::Name {
    pub fn text(&self) -> &SmolStr {
        let ident = self.syntax().first_child_or_token().unwrap().as_token().unwrap();
        ident.text()
    }
}

impl ast::NameRef {
    pub fn text(&self) -> &SmolStr {
        let ident = self.syntax().first_child_or_token().unwrap().as_token().unwrap();
        ident.text()
    }
}

impl ast::Attr {
    pub fn is_inner(&self) -> bool {
        let tt = match self.value() {
            None => return false,
            Some(tt) => tt,
        };

        let prev = match tt.syntax().prev_sibling() {
            None => return false,
            Some(prev) => prev,
        };

        prev.kind() == T![!]
    }

    pub fn as_atom(&self) -> Option<SmolStr> {
        let tt = self.value()?;
        let (_bra, attr, _ket) = tt.syntax().children_with_tokens().collect_tuple()?;
        if attr.kind() == IDENT {
            Some(attr.as_token()?.text().clone())
        } else {
            None
        }
    }

    pub fn as_call(&self) -> Option<(SmolStr, &ast::TokenTree)> {
        let tt = self.value()?;
        let (_bra, attr, args, _ket) = tt.syntax().children_with_tokens().collect_tuple()?;
        let args = ast::TokenTree::cast(args.as_node()?)?;
        if attr.kind() == IDENT {
            Some((attr.as_token()?.text().clone(), args))
        } else {
            None
        }
    }

    pub fn as_named(&self) -> Option<SmolStr> {
        let tt = self.value()?;
        let attr = tt.syntax().children_with_tokens().nth(1)?;
        if attr.kind() == IDENT {
            Some(attr.as_token()?.text().clone())
        } else {
            None
        }
    }

    pub fn as_key_value(&self) -> Option<(SmolStr, SmolStr)> {
        let tt = self.value()?;
        let tt_node = tt.syntax();
        let attr = tt_node.children_with_tokens().nth(1)?;
        if attr.kind() == IDENT {
            let key = attr.as_token()?.text().clone();
            let val_node = tt_node.children_with_tokens().find(|t| t.kind() == STRING)?;
            let val = val_node.as_token()?.text().trim_start_matches("\"").trim_end_matches("\"");
            Some((key, SmolStr::new(val)))
        } else {
            None
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathSegmentKind<'a> {
    Name(&'a ast::NameRef),
    SelfKw,
    SuperKw,
    CrateKw,
}

impl ast::PathSegment {
    pub fn parent_path(&self) -> &ast::Path {
        self.syntax()
            .parent()
            .and_then(ast::Path::cast)
            .expect("segments are always nested in paths")
    }

    pub fn kind(&self) -> Option<PathSegmentKind> {
        let res = if let Some(name_ref) = self.name_ref() {
            PathSegmentKind::Name(name_ref)
        } else {
            match self.syntax().first_child_or_token()?.kind() {
                T![self] => PathSegmentKind::SelfKw,
                T![super] => PathSegmentKind::SuperKw,
                T![crate] => PathSegmentKind::CrateKw,
                _ => return None,
            }
        };
        Some(res)
    }

    pub fn has_colon_colon(&self) -> bool {
        match self.syntax.first_child_or_token().map(|s| s.kind()) {
            Some(T![::]) => true,
            _ => false,
        }
    }
}

impl ast::Path {
    pub fn parent_path(&self) -> Option<&ast::Path> {
        self.syntax().parent().and_then(ast::Path::cast)
    }
}

impl ast::Module {
    pub fn has_semi(&self) -> bool {
        match self.syntax().last_child_or_token() {
            None => false,
            Some(node) => node.kind() == T![;],
        }
    }
}

impl ast::UseTree {
    pub fn has_star(&self) -> bool {
        self.syntax().children_with_tokens().any(|it| it.kind() == T![*])
    }
}

impl ast::UseTreeList {
    pub fn parent_use_tree(&self) -> &ast::UseTree {
        self.syntax()
            .parent()
            .and_then(ast::UseTree::cast)
            .expect("UseTreeLists are always nested in UseTrees")
    }
}

impl ast::ImplBlock {
    pub fn target_type(&self) -> Option<&ast::TypeRef> {
        match self.target() {
            (Some(t), None) | (_, Some(t)) => Some(t),
            _ => None,
        }
    }

    pub fn target_trait(&self) -> Option<&ast::TypeRef> {
        match self.target() {
            (Some(t), Some(_)) => Some(t),
            _ => None,
        }
    }

    fn target(&self) -> (Option<&ast::TypeRef>, Option<&ast::TypeRef>) {
        let mut types = children(self);
        let first = types.next();
        let second = types.next();
        (first, second)
    }

    pub fn is_negative(&self) -> bool {
        self.syntax().children_with_tokens().any(|t| t.kind() == T![!])
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StructKind<'a> {
    Tuple(&'a ast::PosFieldDefList),
    Named(&'a ast::NamedFieldDefList),
    Unit,
}

impl StructKind<'_> {
    fn from_node<N: AstNode>(node: &N) -> StructKind {
        if let Some(nfdl) = child_opt::<_, ast::NamedFieldDefList>(node) {
            StructKind::Named(nfdl)
        } else if let Some(pfl) = child_opt::<_, ast::PosFieldDefList>(node) {
            StructKind::Tuple(pfl)
        } else {
            StructKind::Unit
        }
    }
}

impl ast::StructDef {
    pub fn is_union(&self) -> bool {
        for child in self.syntax().children_with_tokens() {
            match child.kind() {
                T![struct] => return false,
                T![union] => return true,
                _ => (),
            }
        }
        false
    }

    pub fn kind(&self) -> StructKind {
        StructKind::from_node(self)
    }
}

impl ast::EnumVariant {
    pub fn parent_enum(&self) -> &ast::EnumDef {
        self.syntax()
            .parent()
            .and_then(|it| it.parent())
            .and_then(ast::EnumDef::cast)
            .expect("EnumVariants are always nested in Enums")
    }
    pub fn kind(&self) -> StructKind {
        StructKind::from_node(self)
    }
}

impl ast::FnDef {
    pub fn semicolon_token(&self) -> Option<SyntaxToken<'_>> {
        self.syntax()
            .last_child_or_token()
            .and_then(|it| it.as_token())
            .filter(|it| it.kind() == T![;])
    }
}

impl ast::LetStmt {
    pub fn has_semi(&self) -> bool {
        match self.syntax().last_child_or_token() {
            None => false,
            Some(node) => node.kind() == T![;],
        }
    }
}

impl ast::ExprStmt {
    pub fn has_semi(&self) -> bool {
        match self.syntax().last_child_or_token() {
            None => false,
            Some(node) => node.kind() == T![;],
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FieldKind<'a> {
    Name(&'a ast::NameRef),
    Index(SyntaxToken<'a>),
}

impl ast::FieldExpr {
    pub fn index_token(&self) -> Option<SyntaxToken> {
        self.syntax
            .children_with_tokens()
            // FIXME: Accepting floats here to reject them in validation later
            .find(|c| c.kind() == SyntaxKind::INT_NUMBER || c.kind() == SyntaxKind::FLOAT_NUMBER)
            .as_ref()
            .and_then(SyntaxElement::as_token)
    }

    pub fn field_access(&self) -> Option<FieldKind> {
        if let Some(nr) = self.name_ref() {
            Some(FieldKind::Name(nr))
        } else if let Some(tok) = self.index_token() {
            Some(FieldKind::Index(tok))
        } else {
            None
        }
    }
}

impl ast::RefPat {
    pub fn is_mut(&self) -> bool {
        self.syntax().children_with_tokens().any(|n| n.kind() == T![mut])
    }
}

impl ast::BindPat {
    pub fn is_mutable(&self) -> bool {
        self.syntax().children_with_tokens().any(|n| n.kind() == T![mut])
    }

    pub fn is_ref(&self) -> bool {
        self.syntax().children_with_tokens().any(|n| n.kind() == T![ref])
    }
}

impl ast::PointerType {
    pub fn is_mut(&self) -> bool {
        self.syntax().children_with_tokens().any(|n| n.kind() == T![mut])
    }
}

impl ast::ReferenceType {
    pub fn is_mut(&self) -> bool {
        self.syntax().children_with_tokens().any(|n| n.kind() == T![mut])
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum SelfParamKind {
    /// self
    Owned,
    /// &self
    Ref,
    /// &mut self
    MutRef,
}

impl ast::SelfParam {
    pub fn self_kw_token(&self) -> SyntaxToken {
        self.syntax()
            .children_with_tokens()
            .filter_map(|it| it.as_token())
            .find(|it| it.kind() == T![self])
            .expect("invalid tree: self param must have self")
    }

    pub fn kind(&self) -> SelfParamKind {
        let borrowed = self.syntax().children_with_tokens().any(|n| n.kind() == T![&]);
        if borrowed {
            // check for a `mut` coming after the & -- `mut &self` != `&mut self`
            if self
                .syntax()
                .children_with_tokens()
                .skip_while(|n| n.kind() != T![&])
                .any(|n| n.kind() == T![mut])
            {
                SelfParamKind::MutRef
            } else {
                SelfParamKind::Ref
            }
        } else {
            SelfParamKind::Owned
        }
    }
}

impl ast::LifetimeParam {
    pub fn lifetime_token(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|it| it.as_token())
            .find(|it| it.kind() == LIFETIME)
    }
}

impl ast::WherePred {
    pub fn lifetime_token(&self) -> Option<SyntaxToken> {
        self.syntax()
            .children_with_tokens()
            .filter_map(|it| it.as_token())
            .find(|it| it.kind() == LIFETIME)
    }
}

impl ast::TraitDef {
    pub fn is_auto(&self) -> bool {
        self.syntax().children_with_tokens().any(|t| t.kind() == T![auto])
    }
}
<|endoftext|>


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

use {ExtendMode, Gradient, GradientStop, LayoutPoint, LayoutSize, RadialGradient};

/// Construct a gradient to be used in display lists.
///
/// Each gradient needs at least two stops.
pub struct GradientBuilder {
    stops: Vec<GradientStop>,
}

impl GradientBuilder {
    /// Create a new gradient builder.
    pub fn new() -> GradientBuilder {
        GradientBuilder {
            stops: Vec::new(),
        }
    }

    /// Create a gradient builder with a list of stops.
    pub fn with_stops(stops: Vec<GradientStop>) -> GradientBuilder {
        GradientBuilder { stops }
    }

    /// Push an additional stop for the gradient.
    pub fn push(&mut self, stop: GradientStop) {
        self.stops.push(stop);
    }

    /// Get a reference to the list of stops.
    pub fn stops(&self) -> &[GradientStop] {
        self.stops.as_ref()
    }

    /// Produce a linear gradient, normalize the stops.
    pub fn gradient(
        &mut self,
        start_point: LayoutPoint,
        end_point: LayoutPoint,
        extend_mode: ExtendMode,
    ) -> Gradient {
        let (start_offset, end_offset) = self.normalize(extend_mode);
        let start_to_end = end_point - start_point;

        Gradient {
            start_point: start_point + start_to_end * start_offset,
            end_point: start_point + start_to_end * end_offset,
            extend_mode,
        }
    }

    /// Produce a radial gradient, normalize the stops.
    ///
    /// Will replace the gradient with a single color
    /// if the radius negative.
    pub fn radial_gradient(
        &mut self,
        center: LayoutPoint,
        radius: LayoutSize,
        extend_mode: ExtendMode,
    ) -> RadialGradient {
        if radius.width <= 0.0 || radius.height <= 0.0 {
            // The shader cannot handle a non positive radius. So
            // reuse the stops vector and construct an equivalent
            // gradient.
            let last_color = self.stops.last().unwrap().color;

            self.stops.clear();
            self.stops.push(GradientStop { offset: 0.0, color: last_color, });
            self.stops.push(GradientStop { offset: 1.0, color: last_color, });

            return RadialGradient {
                center,
                radius: LayoutSize::new(1.0, 1.0),
                start_offset: 0.0,
                end_offset: 1.0,
                extend_mode,
            };
        }

        let (start_offset, end_offset) =
            self.normalize(extend_mode);

        RadialGradient {
            center,
            radius,
            start_offset,
            end_offset,
            extend_mode,
        }
    }

    /// Gradients can be defined with stops outside the range of [0, 1]
    /// when this happens the gradient needs to be normalized by adjusting
    /// the gradient stops and gradient line into an equivalent gradient
    /// with stops in the range [0, 1]. this is done by moving the beginning
    /// of the gradient line to where stop[0] and the end of the gradient line
    /// to stop[n-1]. this function adjusts the stops in place, and returns
    /// the amount to adjust the gradient line start and stop.
    fn normalize(&mut self, extend_mode: ExtendMode) -> (f32, f32) {
        let stops = &mut self.stops;
        assert!(stops.len() >= 2);

        let first = *stops.first().unwrap();
        let last = *stops.last().unwrap();

        assert!(first.offset <= last.offset);

        let stops_delta = last.offset - first.offset;

        if stops_delta > 0.000001 {
            for stop in stops {
                stop.offset = (stop.offset - first.offset) / stops_delta;
            }

            (first.offset, last.offset)
        } else {
            // We have a degenerate gradient and can't accurately transform the stops
            // what happens here depends on the repeat behavior, but in any case
            // we reconstruct the gradient stops to something simpler and equivalent
            stops.clear();

            match extend_mode {
                ExtendMode::Clamp => {
                    // This gradient is two colors split at the offset of the stops,
                    // so create a gradient with two colors split at 0.5 and adjust
                    // the gradient line so 0.5 is at the offset of the stops
                    stops.push(GradientStop { color: first.color, offset: 0.0, });
                    stops.push(GradientStop { color: first.color, offset: 0.5, });
                    stops.push(GradientStop { color: last.color, offset: 0.5, });
                    stops.push(GradientStop { color: last.color, offset: 1.0, });

                    let offset = last.offset;

                    (offset - 0.5, offset + 0.5)
                }
                ExtendMode::Repeat => {
                    // A repeating gradient with stops that are all in the same
                    // position should just display the last color. I believe the
                    // spec says that it should be the average color of the gradient,
                    // but this matches what Gecko and Blink does
                    stops.push(GradientStop { color: last.color, offset: 0.0, });
                    stops.push(GradientStop { color: last.color, offset: 1.0, });

                    (0.0, 1.0)
                }
            }
        }
    }
}
<commit_msg>Auto merge of #3235 - pyfisch:into-stops, r=gw3583<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use {ExtendMode, Gradient, GradientStop, LayoutPoint, LayoutSize, RadialGradient};

/// Construct a gradient to be used in display lists.
///
/// Each gradient needs at least two stops.
pub struct GradientBuilder {
    stops: Vec<GradientStop>,
}

impl GradientBuilder {
    /// Create a new gradient builder.
    pub fn new() -> GradientBuilder {
        GradientBuilder {
            stops: Vec::new(),
        }
    }

    /// Create a gradient builder with a list of stops.
    pub fn with_stops(stops: Vec<GradientStop>) -> GradientBuilder {
        GradientBuilder { stops }
    }

    /// Push an additional stop for the gradient.
    pub fn push(&mut self, stop: GradientStop) {
        self.stops.push(stop);
    }

    /// Get a reference to the list of stops.
    pub fn stops(&self) -> &[GradientStop] {
        self.stops.as_ref()
    }

    /// Return the gradient stops vector.
    pub fn into_stops(self) -> Vec<GradientStop> {
        self.stops
    }

    /// Produce a linear gradient, normalize the stops.
    pub fn gradient(
        &mut self,
        start_point: LayoutPoint,
        end_point: LayoutPoint,
        extend_mode: ExtendMode,
    ) -> Gradient {
        let (start_offset, end_offset) = self.normalize(extend_mode);
        let start_to_end = end_point - start_point;

        Gradient {
            start_point: start_point + start_to_end * start_offset,
            end_point: start_point + start_to_end * end_offset,
            extend_mode,
        }
    }

    /// Produce a radial gradient, normalize the stops.
    ///
    /// Will replace the gradient with a single color
    /// if the radius negative.
    pub fn radial_gradient(
        &mut self,
        center: LayoutPoint,
        radius: LayoutSize,
        extend_mode: ExtendMode,
    ) -> RadialGradient {
        if radius.width <= 0.0 || radius.height <= 0.0 {
            // The shader cannot handle a non positive radius. So
            // reuse the stops vector and construct an equivalent
            // gradient.
            let last_color = self.stops.last().unwrap().color;

            self.stops.clear();
            self.stops.push(GradientStop { offset: 0.0, color: last_color, });
            self.stops.push(GradientStop { offset: 1.0, color: last_color, });

            return RadialGradient {
                center,
                radius: LayoutSize::new(1.0, 1.0),
                start_offset: 0.0,
                end_offset: 1.0,
                extend_mode,
            };
        }

        let (start_offset, end_offset) =
            self.normalize(extend_mode);

        RadialGradient {
            center,
            radius,
            start_offset,
            end_offset,
            extend_mode,
        }
    }

    /// Gradients can be defined with stops outside the range of [0, 1]
    /// when this happens the gradient needs to be normalized by adjusting
    /// the gradient stops and gradient line into an equivalent gradient
    /// with stops in the range [0, 1]. this is done by moving the beginning
    /// of the gradient line to where stop[0] and the end of the gradient line
    /// to stop[n-1]. this function adjusts the stops in place, and returns
    /// the amount to adjust the gradient line start and stop.
    fn normalize(&mut self, extend_mode: ExtendMode) -> (f32, f32) {
        let stops = &mut self.stops;
        assert!(stops.len() >= 2);

        let first = *stops.first().unwrap();
        let last = *stops.last().unwrap();

        assert!(first.offset <= last.offset);

        let stops_delta = last.offset - first.offset;

        if stops_delta > 0.000001 {
            for stop in stops {
                stop.offset = (stop.offset - first.offset) / stops_delta;
            }

            (first.offset, last.offset)
        } else {
            // We have a degenerate gradient and can't accurately transform the stops
            // what happens here depends on the repeat behavior, but in any case
            // we reconstruct the gradient stops to something simpler and equivalent
            stops.clear();

            match extend_mode {
                ExtendMode::Clamp => {
                    // This gradient is two colors split at the offset of the stops,
                    // so create a gradient with two colors split at 0.5 and adjust
                    // the gradient line so 0.5 is at the offset of the stops
                    stops.push(GradientStop { color: first.color, offset: 0.0, });
                    stops.push(GradientStop { color: first.color, offset: 0.5, });
                    stops.push(GradientStop { color: last.color, offset: 0.5, });
                    stops.push(GradientStop { color: last.color, offset: 1.0, });

                    let offset = last.offset;

                    (offset - 0.5, offset + 0.5)
                }
                ExtendMode::Repeat => {
                    // A repeating gradient with stops that are all in the same
                    // position should just display the last color. I believe the
                    // spec says that it should be the average color of the gradient,
                    // but this matches what Gecko and Blink does
                    stops.push(GradientStop { color: last.color, offset: 0.0, });
                    stops.push(GradientStop { color: last.color, offset: 1.0, });

                    (0.0, 1.0)
                }
            }
        }
    }
}
<|endoftext|>