/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Arc;

use allocative::Allocative;
use anyhow::Context;
use async_trait::async_trait;
use buck2_common::dice::cells::HasCellResolver;
use buck2_common::dice::cycles::CycleGuard;
use buck2_common::dice::file_ops::DiceFileOps;
use buck2_common::dice::file_ops::HasFileOps;
use buck2_common::error_report::CreateErrorReport;
use buck2_common::file_ops::FileOps;
use buck2_common::legacy_configs::dice::HasLegacyConfigs;
use buck2_common::legacy_configs::dice::LegacyBuckConfigOnDice;
use buck2_common::package_boundary::HasPackageBoundaryExceptions;
use buck2_common::package_listing::dice::HasPackageListingResolver;
use buck2_common::package_listing::listing::PackageListing;
use buck2_common::result::SharedResult;
use buck2_common::result::ToSharedResultExt;
use buck2_common::result::ToUnsharedResultExt;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::cells::build_file_cell::BuildFileCell;
use buck2_core::cells::name::CellName;
use buck2_core::package::PackageLabel;
use buck2_events::dispatch::span;
use buck2_events::dispatch::span_async;
use buck2_interpreter::dice::starlark_provider::with_starlark_eval_provider;
use buck2_interpreter::file_loader::LoadedModule;
use buck2_interpreter::file_loader::ModuleDeps;
use buck2_interpreter::import_paths::HasImportPaths;
use buck2_interpreter::path::OwnedStarlarkModulePath;
use buck2_interpreter::path::PackageFilePath;
use buck2_interpreter::path::StarlarkModulePath;
use buck2_interpreter::path::StarlarkPath;
use buck2_interpreter::starlark_profiler::StarlarkProfilerOrInstrumentation;
use buck2_node::nodes::eval_result::EvaluationResult;
use derive_more::Display;
use dice::DiceComputations;
use dice::Key;
use dupe::Dupe;
use futures::future;
use more_futures::cancellation::CancellationContext;
use starlark::codemap::FileSpan;
use starlark::syntax::AstModule;

use crate::interpreter::cycles::LoadCycleDescriptor;
use crate::interpreter::dice_calculation_delegate::keys::EvalImportKey;
use crate::interpreter::global_interpreter_state::HasGlobalInterpreterState;
use crate::interpreter::interpreter_for_cell::InterpreterForCell;
use crate::interpreter::interpreter_for_cell::ParseResult;
use crate::super_package::data::SuperPackage;

#[derive(Debug, thiserror::Error)]
enum DiceCalculationDelegateError {
    #[error("Error evaluating build file: `{0}`")]
    EvalBuildFileError(BuildFilePath),
    #[error("Error evaluating module: `{0}`")]
    EvalModuleError(String),
}

#[async_trait]
pub trait HasCalculationDelegate<'c> {
    /// Get calculator for a file evaluation.
    ///
    /// This function only accepts cell names, but it is created
    /// per evaluated file (build file or `.bzl`).
    async fn get_interpreter_calculator(
        &'c self,
        cell: CellName,
        build_file_cell: BuildFileCell,
    ) -> anyhow::Result<DiceCalculationDelegate<'c>>;
}

#[async_trait]
impl<'c> HasCalculationDelegate<'c> for DiceComputations {
    async fn get_interpreter_calculator(
        &'c self,
        cell: CellName,
        build_file_cell: BuildFileCell,
    ) -> anyhow::Result<DiceCalculationDelegate<'c>> {
        #[derive(Clone, Dupe, Display, Debug, Eq, Hash, PartialEq, Allocative)]
        #[display(fmt = "{}@{}", _0, _1)]
        struct InterpreterConfigForCellKey(CellName, BuildFileCell);

        #[async_trait]
        impl Key for InterpreterConfigForCellKey {
            type Value = SharedResult<Arc<InterpreterForCell>>;
            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> Self::Value {
                let cell_resolver = ctx.get_cell_resolver().await?;
                let global_state = ctx.get_global_interpreter_state().await?;

                let cell = cell_resolver.get(self.0)?;

                let implicit_import_paths = ctx.import_paths_for_cell(self.1).await?;

                Ok(Arc::new(InterpreterForCell::new(
                    cell.cell_alias_resolver().dupe(),
                    global_state.dupe(),
                    implicit_import_paths,
                )?))
            }

            fn equality(_: &Self::Value, _: &Self::Value) -> bool {
                false
            }
        }

        let file_ops = self.file_ops();
        let configs = self
            .compute(&InterpreterConfigForCellKey(cell, build_file_cell))
            .await??;

        Ok(DiceCalculationDelegate {
            build_file_cell,
            ctx: self,
            fs: file_ops,
            configs,
        })
    }
}

#[derive(Clone)]
pub struct DiceCalculationDelegate<'c> {
    build_file_cell: BuildFileCell,
    ctx: &'c DiceComputations,
    fs: DiceFileOps<'c>,
    configs: Arc<InterpreterForCell>,
}

impl<'c> DiceCalculationDelegate<'c> {
    /// InterpreterCalculation invokes eval_import recursively. To support
    /// an embedder caching the result of that, the InterpreterCalculation will
    /// call into the delegate instead of calling itself directly.
    ///
    /// The delegate implementation should have roughly the same behavior as
    /// just forwarding directly back to the calculation.
    ///
    /// ```ignore
    /// async fn eval_module(...) -> ... { calculation.eval_module(...).await }
    /// ```
    pub async fn eval_module(
        &self,
        starlark_path: StarlarkModulePath<'_>,
    ) -> anyhow::Result<LoadedModule> {
        #[async_trait]
        impl Key for EvalImportKey {
            type Value = SharedResult<LoadedModule>;
            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> Self::Value {
                let starlark_path = self.0.borrow();
                // We cannot just use the inner default delegate's eval_import
                // because that wouldn't delegate back to us for inner eval_import calls.
                Ok(ctx
                    .get_interpreter_calculator(
                        starlark_path.cell(),
                        starlark_path.build_file_cell(),
                    )
                    .await?
                    .eval_module_uncached(starlark_path)
                    .await?)
            }

            fn equality(_: &Self::Value, _: &Self::Value) -> bool {
                // While it is technically possible to compare the modules
                // at least for simple modules (like modules defining only string constants),
                // practically it is too hard to make it work correctly for every case.
                false
            }

            fn validity(x: &Self::Value) -> bool {
                x.is_ok()
            }
        }

        self.ctx
            .compute(&EvalImportKey(OwnedStarlarkModulePath::new(starlark_path)))
            .await?
            .unshared_error()
    }

    async fn get_legacy_buck_config_for_starlark(
        &self,
    ) -> anyhow::Result<LegacyBuckConfigOnDice<'c>> {
        self.ctx
            .get_legacy_config_on_dice(self.build_file_cell.name())
            .await
    }

    async fn parse_file(&self, starlark_path: StarlarkPath<'_>) -> anyhow::Result<ParseResult> {
        let content =
            <dyn FileOps>::read_file(&self.fs, starlark_path.path().as_ref().as_ref()).await?;
        self.configs.parse(starlark_path, content)
    }

    async fn eval_deps(
        &self,
        modules: &[(Option<FileSpan>, OwnedStarlarkModulePath)],
    ) -> anyhow::Result<ModuleDeps> {
        Ok(ModuleDeps(
            futures::future::join_all(modules.iter().map(|(span, import)| async move {
                self.eval_module(import.borrow()).await.with_context(|| {
                    format!(
                        "From `load` at {}",
                        span.as_ref()
                            .map_or("implicit location".to_owned(), FileSpan::to_string)
                    )
                })
            }))
            .await
            .into_iter()
            .collect::<anyhow::Result<_>>()?,
        ))
    }

    pub async fn prepare_eval<'a>(
        &'a self,
        starlark_file: StarlarkPath<'_>,
    ) -> anyhow::Result<(AstModule, ModuleDeps)> {
        let ParseResult(ast, imports) = self.parse_file(starlark_file).await?;
        let fut = self.eval_deps(&imports);
        let deps = LoadCycleDescriptor::guard_this(self.ctx, fut).await???;
        Ok((ast, deps))
    }

    pub fn prepare_eval_with_content<'a>(
        &'a self,
        starlark_file: StarlarkPath<'_>,
        content: String,
    ) -> anyhow::Result<AstModule> {
        let ParseResult(ast, _) = self.configs.parse(starlark_file, content)?;
        Ok(ast)
    }

    pub async fn resolve_load(
        &self,
        starlark_file: StarlarkPath<'_>,
        load_string: &str,
    ) -> anyhow::Result<OwnedStarlarkModulePath> {
        self.configs.resolve_path(starlark_file, load_string)
    }

    pub async fn eval_module_uncached(
        &self,
        starlark_file: StarlarkModulePath<'_>,
    ) -> anyhow::Result<LoadedModule> {
        let (ast, deps) = self.prepare_eval(starlark_file.into()).await?;
        let loaded_modules = deps.get_loaded_modules();
        let buckconfig = self.get_legacy_buck_config_for_starlark().await?;
        let root_buckconfig = self.ctx.get_legacy_root_config_on_dice().await?;

        with_starlark_eval_provider(
            self.ctx,
            &mut StarlarkProfilerOrInstrumentation::disabled(),
            format!("load:{}", &starlark_file),
            move |provider| {
                let evaluation = self
                    .configs
                    .eval_module(
                        starlark_file,
                        &buckconfig,
                        &root_buckconfig,
                        ast,
                        loaded_modules.clone(),
                        provider,
                    )
                    .with_context(|| {
                        DiceCalculationDelegateError::EvalModuleError(starlark_file.to_string())
                    })?;

                Ok(LoadedModule::new(
                    OwnedStarlarkModulePath::new(starlark_file),
                    loaded_modules,
                    evaluation,
                ))
            },
        )
        .await
    }

    /// Eval parent `PACKAGE` file for given `PACKAGE` file.
    async fn eval_parent_package_file(
        &self,
        file: &PackageFilePath,
    ) -> anyhow::Result<SuperPackage> {
        match file.parent_package_file() {
            None => {
                // We are in the cell root, there's no parent.
                Ok(SuperPackage::default())
            }
            Some(parent) => self.eval_package_file(&parent).await,
        }
    }

    /// Return `None` if there's no `PACKAGE` file in the directory.
    pub async fn prepare_package_file_eval(
        &self,
        path: &PackageFilePath,
    ) -> anyhow::Result<Option<(AstModule, ModuleDeps)>> {
        // This is cached if evaluating a `PACKAGE` file next to a `BUCK` file.
        let dir = self.fs.read_dir(path.dir()).await?;
        // Note:
        // * we are using `read_dir` instead of `read_path_metadata` because
        //   * it is an extra IO, and `read_dir` is likely already cached.
        //   * `read_path_metadata` would not tell us if the file name is `PACKAGE`
        //     and not `package` on case-insensitive filesystems.
        //     We do case-sensitive comparison for `BUCK` files, so we do the same here.
        //   * we fail here if `PACKAGE` (but not `package`) exists, and it is not a file.
        if !dir.contains(PackageFilePath::PACKAGE_FILE_NAME) {
            return Ok(None);
        }
        Ok(Some(
            self.prepare_eval(StarlarkPath::PackageFile(path)).await?,
        ))
    }

    async fn eval_package_file_uncached(
        &self,
        path: &PackageFilePath,
    ) -> anyhow::Result<SuperPackage> {
        let parent = self.eval_parent_package_file(path);
        let prepare_eval = self.prepare_package_file_eval(path);

        let (parent, ast_deps) = future::try_join(parent, prepare_eval).await?;

        let (ast, deps) = match ast_deps {
            Some(x) => x,
            None => return Ok(parent),
        };

        let buckconfig = self.get_legacy_buck_config_for_starlark().await?;
        let root_buckconfig = self.ctx.get_legacy_root_config_on_dice().await?;
        with_starlark_eval_provider(
            self.ctx,
            &mut StarlarkProfilerOrInstrumentation::disabled(),
            format!("load:{}", path),
            move |provider| {
                self.configs
                    .eval_package_file(
                        path,
                        ast,
                        parent,
                        &buckconfig,
                        &root_buckconfig,
                        deps.get_loaded_modules(),
                        provider,
                    )
                    .with_context(|| format!("evaluating Starlark PACKAGE file `{}`", path))
            },
        )
        .await
    }

    async fn eval_package_file(&self, path: &PackageFilePath) -> anyhow::Result<SuperPackage> {
        #[derive(Debug, Display, Clone, Allocative, Eq, PartialEq, Hash)]
        struct PackageFileKey(PackageFilePath);

        #[async_trait]
        impl Key for PackageFileKey {
            type Value = SharedResult<SuperPackage>;

            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> Self::Value {
                let interpreter = ctx
                    .get_interpreter_calculator(self.0.cell(), self.0.build_file_cell())
                    .await
                    .shared_error()?;
                interpreter
                    .eval_package_file_uncached(&self.0)
                    .await
                    .shared_error()
            }

            fn equality(x: &Self::Value, y: &Self::Value) -> bool {
                match (x, y) {
                    (Ok(x), Ok(y)) => x == y,
                    _ => false,
                }
            }

            fn validity(x: &Self::Value) -> bool {
                x.is_ok()
            }
        }

        self.ctx
            .compute(&PackageFileKey(path.clone()))
            .await?
            .unshared_error()
    }

    /// Most directories do not contain a `PACKAGE` file, this function
    /// optimizes `eval_package_file` for this case by avoiding creation of DICE key.
    async fn eval_package_file_for_build_file(
        &self,
        package: PackageLabel,
        package_listing: &PackageListing,
    ) -> anyhow::Result<SuperPackage> {
        let package_file_path = PackageFilePath::for_dir(package.as_cell_path());
        if package_listing
            .get_file(PackageFilePath::PACKAGE_FILE_NAME.as_ref())
            .is_none()
        {
            // Without this optimization, `cquery <that android target>` has 6% time regression.
            // With this optimization, check for `PACKAGE` files adds 2% to time.
            self.eval_parent_package_file(&package_file_path).await
        } else {
            self.eval_package_file(&package_file_path).await
        }
    }

    async fn resolve_package_listing(
        &self,
        package: PackageLabel,
    ) -> anyhow::Result<PackageListing> {
        span_async(
            buck2_data::LoadPackageStart {
                path: package.as_cell_path().to_string(),
            },
            async {
                let result = self.ctx.resolve_package_listing(package.dupe()).await;
                let error = result.create_error_report();
                (
                    result,
                    buck2_data::LoadPackageEnd {
                        path: package.as_cell_path().to_string(),
                        error,
                    },
                )
            },
        )
        .await
    }

    pub async fn eval_build_file(
        &self,
        package: PackageLabel,
        profiler_instrumentation: &mut StarlarkProfilerOrInstrumentation<'_>,
    ) -> anyhow::Result<EvaluationResult> {
        let listing = self.resolve_package_listing(package.dupe()).await?;

        let build_file_path = BuildFilePath::new(package.dupe(), listing.buildfile().to_owned());
        let ast_deps = self.prepare_eval(StarlarkPath::BuildFile(&build_file_path));

        let super_package = self.eval_package_file_for_build_file(package.dupe(), &listing);

        let ((ast, deps), super_package) = future::try_join(ast_deps, super_package).await?;

        let package_boundary_exception = self
            .ctx
            .get_package_boundary_exception(package.as_cell_path())
            .await?;
        let buckconfig = self.get_legacy_buck_config_for_starlark().await?;
        let root_buckconfig = self.ctx.get_legacy_root_config_on_dice().await?;
        let module_id = build_file_path.to_string();
        let cell_str = build_file_path.cell().as_str().to_owned();
        let start_event = buck2_data::LoadBuildFileStart {
            cell: cell_str.clone(),
            module_id: module_id.clone(),
        };
        with_starlark_eval_provider(
            self.ctx,
            profiler_instrumentation,
            format!("load_buildfile:{}", &package),
            move |provider| {
                span(start_event, move || {
                    let result = self
                        .configs
                        .eval_build_file(
                            &build_file_path,
                            &buckconfig,
                            &root_buckconfig,
                            listing,
                            super_package,
                            package_boundary_exception,
                            ast,
                            deps.get_loaded_modules(),
                            provider,
                        )
                        .with_context(|| {
                            DiceCalculationDelegateError::EvalBuildFileError(build_file_path)
                        });
                    let error = result.as_ref().err().map(|e| format!("{:#}", e));

                    (
                        result,
                        buck2_data::LoadBuildFileEnd {
                            module_id,
                            cell: cell_str,
                            error,
                        },
                    )
                })
            },
        )
        .await
    }
}

mod keys {
    use allocative::Allocative;
    use buck2_interpreter::path::OwnedStarlarkModulePath;
    use derive_more::Display;

    #[derive(Clone, Display, Debug, Eq, Hash, PartialEq, Allocative)]
    pub struct EvalImportKey(pub OwnedStarlarkModulePath);
}

pub mod testing {
    // re-exports for testing
    pub use super::keys::EvalImportKey;
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_interpreter::bxl::BXL_FUNCTIONS;
use buck2_interpreter::functions::more::MORE_FUNCTIONS;
use buck2_interpreter::functions::transition::REGISTER_TRANSITION;
use starlark::environment::GlobalsBuilder;

use crate::attrs::attrs_global::register_attrs;
use crate::interpreter::build_defs::register_base_natives;
use crate::interpreter::functions::host_info::register_host_info;
use crate::interpreter::functions::load_symbols::register_load_symbols;
use crate::interpreter::functions::read_config::register_read_config;
use crate::interpreter::functions::regex::register_regex;
use crate::interpreter::functions::soft_error::register_soft_error;
use crate::interpreter::functions::warning::register_warning;
use crate::interpreter::natives::register_module_natives;
use crate::rule::register_rule_function;
use crate::super_package::defs::register_package_natives;
use crate::super_package::package_value::register_read_package_value;

/// Natives for `BUCK` and `bzl` files.
fn register_build_bzl_natives(builder: &mut GlobalsBuilder) {
    (MORE_FUNCTIONS.get().unwrap().register_provider)(builder);
    (MORE_FUNCTIONS.get().unwrap().register_transitive_set)(builder);
    register_module_natives(builder);
    register_host_info(builder);
    register_read_config(builder);
    register_read_package_value(builder);
}

/// Globals for `BUCK` files.
pub fn configure_build_file_globals(globals_builder: &mut GlobalsBuilder) {
    // TODO(cjhopman): This unconditionally adds the native symbols to the global
    // env, but that needs to be a cell-based config.
    register_build_bzl_natives(globals_builder);
}

/// Globals for `PACKAGE` files.
pub fn configure_package_file_globals(globals_builder: &mut GlobalsBuilder) {
    // TODO(cjhopman): This unconditionally adds the native symbols to the global
    // env, but that needs to be a cell-based config.
    register_build_bzl_natives(globals_builder);
    register_package_natives(globals_builder);
}

/// Globals for `.bxl` files.
pub fn configure_bxl_file_globals(globals_builder: &mut GlobalsBuilder) {
    register_base_natives(globals_builder);
    (MORE_FUNCTIONS.get().unwrap().register_cmd_args)(globals_builder);
    (BXL_FUNCTIONS.get().unwrap().register_cli_args_struct)(globals_builder);
    (BXL_FUNCTIONS.get().unwrap().register_bxl_function)(globals_builder);
    (BXL_FUNCTIONS.get().unwrap().register_artifact_function)(globals_builder);
    (BXL_FUNCTIONS.get().unwrap().register_label_function)(globals_builder);
    (BXL_FUNCTIONS.get().unwrap().register_target_function)(globals_builder);
    register_read_config(globals_builder);
    register_host_info(globals_builder);
    (BXL_FUNCTIONS.get().unwrap().register_instant_function)(globals_builder);
    (BXL_FUNCTIONS
        .get()
        .unwrap()
        .register_error_handling_function)(globals_builder);
    (MORE_FUNCTIONS.get().unwrap().register_builtin_providers)(globals_builder);
}

/// Globals for `.bzl` files.
pub fn configure_extension_file_globals(globals_builder: &mut GlobalsBuilder) {
    // TODO(cjhopman): This unconditionally adds the native symbols to the global
    // env, but that needs to be a cell-based config.
    register_build_bzl_natives(globals_builder);
    (MORE_FUNCTIONS.get().unwrap().register_cmd_args)(globals_builder);
    (MORE_FUNCTIONS.get().unwrap().register_rule_defs)(globals_builder);
    register_warning(globals_builder);
    register_soft_error(globals_builder);
    register_regex(globals_builder);
    register_load_symbols(globals_builder);
    register_rule_function(globals_builder);
    register_attrs(globals_builder);
    (REGISTER_TRANSITION.get().unwrap())(globals_builder);
    (MORE_FUNCTIONS
        .get()
        .unwrap()
        .register_command_executor_config)(globals_builder);
    register_package_natives(globals_builder);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::collections::HashMap;
use std::sync::Arc;

use allocative::Allocative;
use async_trait::async_trait;
use buck2_common::dice::cells::HasCellResolver;
use buck2_common::legacy_configs::dice::HasLegacyConfigs;
use buck2_common::legacy_configs::view::LegacyBuckConfigsView;
use buck2_common::result::SharedResult;
use buck2_core::cells::build_file_cell::BuildFileCell;
use buck2_core::cells::CellResolver;
use buck2_interpreter::dice::starlark_types::GetDisableStarlarkTypes;
use buck2_interpreter::file_type::StarlarkFileType;
use dice::DiceComputations;
use dice::Key;
use dupe::Dupe;
use more_futures::cancellation::CancellationContext;
use starlark::environment::Globals;

use crate::interpreter::cell_info::InterpreterCellInfo;
use crate::interpreter::configuror::BuildInterpreterConfiguror;
use crate::interpreter::context::HasInterpreterContext;

/// Information shared across interpreters. Contains no cell-specific
/// information.
#[derive(Allocative)]
pub struct GlobalInterpreterState {
    pub cell_resolver: CellResolver,

    pub(crate) cell_configs: HashMap<BuildFileCell, InterpreterCellInfo>,

    /// The GlobalEnvironment contains all the globally available symbols
    /// (primarily starlark stdlib and Buck-provided functions) that should
    /// be available in a build file.
    pub build_file_global_env: Globals,

    /// Symbols for `PACKAGE` files.
    pub package_file_global_env: Globals,

    /// The GlobalEnvironment contains all the globally available symbols
    /// (primarily starlark stdlib and Buck-provided functions) that should
    /// be available in an extension file.
    pub extension_file_global_env: Globals,

    /// The GlobalEnvironment contains all the globally available symbols
    /// (primarily starlark stdlib and Buck-provided functions) that should
    /// be available in a bxl file.
    pub bxl_file_global_env: Globals,

    /// Interpreter Configurer
    pub configuror: Arc<BuildInterpreterConfiguror>,

    /// Check types in Starlark (or just parse and ignore).
    pub disable_starlark_types: bool,
}

impl GlobalInterpreterState {
    pub fn new(
        legacy_configs: &dyn LegacyBuckConfigsView,
        cell_resolver: CellResolver,
        interpreter_configuror: Arc<BuildInterpreterConfiguror>,
        disable_starlark_types: bool,
    ) -> anyhow::Result<Self> {
        // TODO: There should be one of these that also does not have native functions
        // in the global       namespace so that it can be configured per-cell
        let build_file_global_env = interpreter_configuror.build_file_globals();
        let package_file_global_env = interpreter_configuror.package_file_globals();
        let extension_file_global_env = interpreter_configuror.extension_file_globals();
        let bxl_file_global_env = interpreter_configuror.bxl_file_globals();

        let mut cell_configs = HashMap::new();
        for (cell_name, _config) in legacy_configs.iter() {
            cell_configs.insert(
                BuildFileCell::new(cell_name),
                InterpreterCellInfo::new(BuildFileCell::new(cell_name), cell_resolver.dupe())?,
            );
        }
        Ok(Self {
            cell_resolver,
            cell_configs,
            build_file_global_env,
            package_file_global_env,
            extension_file_global_env,
            bxl_file_global_env,
            configuror: interpreter_configuror,
            disable_starlark_types,
        })
    }

    pub fn configuror(&self) -> &Arc<BuildInterpreterConfiguror> {
        &self.configuror
    }

    pub fn globals_for_file_type(&self, file_type: StarlarkFileType) -> &Globals {
        match file_type {
            StarlarkFileType::Buck => &self.build_file_global_env,
            StarlarkFileType::Package => &self.package_file_global_env,
            StarlarkFileType::Bzl => &self.extension_file_global_env,
            StarlarkFileType::Bxl => &self.bxl_file_global_env,
        }
    }
}

#[async_trait]
pub trait HasGlobalInterpreterState {
    async fn get_global_interpreter_state(&self) -> anyhow::Result<Arc<GlobalInterpreterState>>;
}

#[async_trait]
impl HasGlobalInterpreterState for DiceComputations {
    async fn get_global_interpreter_state(&self) -> anyhow::Result<Arc<GlobalInterpreterState>> {
        #[derive(Clone, Dupe, Allocative)]
        struct GisValue(Arc<GlobalInterpreterState>);

        #[derive(
            Clone,
            derive_more::Display,
            Dupe,
            Debug,
            Eq,
            Hash,
            PartialEq,
            Allocative
        )]
        #[display(fmt = "{:?}", self)]
        struct GisKey();

        #[async_trait]
        impl Key for GisKey {
            type Value = SharedResult<GisValue>;
            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> Self::Value {
                let interpreter_configuror = ctx.get_interpreter_configuror().await?;
                let legacy_configs = ctx.get_legacy_configs_on_dice().await?;
                let cell_resolver = ctx.get_cell_resolver().await?;
                let disable_starlark_types = ctx.get_disable_starlark_types().await?;

                Ok(GisValue(Arc::new(GlobalInterpreterState::new(
                    &legacy_configs,
                    cell_resolver,
                    interpreter_configuror,
                    disable_starlark_types,
                )?)))
            }

            fn equality(_: &Self::Value, _: &Self::Value) -> bool {
                false
            }
        }

        Ok(self.compute(&GisKey()).await??.0)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::collections::HashSet;
use std::fmt;
use std::fmt::Debug;

use anyhow::Context;
use buck2_common::package_listing::file_listing::PackageFileListing;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
use buck2_core::package::package_relative_path::PackageRelativePath;
use buck2_core::soft_error;
use derivative::Derivative;

#[derive(Debug, thiserror::Error)]
enum GlobError {
    #[error(
        "Exact pattern must be valid forward relative path: `{0}` \
        (this pattern cannot match anything)"
    )]
    ExactIncludeMustBeForwardRelativePath(String),
    #[error("Double slash in pattern: `{0}` (this pattern cannot match anything)")]
    DoubleSlash(String),
    #[error("Pattern must not start with `/`: `{0}` (this pattern cannot match anything)")]
    LeadingSlash(String),
    #[error("Pattern must not end with `/`: `{0}` (this pattern cannot match anything)")]
    TrailingSlash(String),
    #[error("Pattern must not contain `..`: `{0}` (this pattern cannot match anything)")]
    DotDot(String),
    #[error("Pattern must not contain `.`: `{0}` (this pattern cannot match anything)")]
    Dot(String),
}

/// The default Debug for Pattern is horribly verbose with lots of internal
/// details we don't care about, so create `GlobPattern` so we can have a
/// readable `Debug`.
struct GlobPattern(glob::Pattern);

impl Debug for GlobPattern {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_tuple("Pattern").field(&self.0.as_str()).finish()
    }
}

impl GlobPattern {
    fn new(pattern: &str) -> anyhow::Result<GlobPattern> {
        let parsed_pattern = glob::Pattern::new(pattern)
            .with_context(|| format!("Error creating globspec for `{}`", pattern))?;
        if pattern.contains("//") {
            return Err(GlobError::DoubleSlash(pattern.to_owned()).into());
        }
        if pattern.starts_with('/') {
            return Err(GlobError::LeadingSlash(pattern.to_owned()).into());
        }
        if pattern.ends_with('/') {
            // Please write a test when converting this error to hard error.
            soft_error!(
                "glob_trailing_slash",
                GlobError::TrailingSlash(pattern.to_owned()).into()
            )?;
        }
        // TODO(nga): also ban single dot.
        if pattern.split('/').any(|p| p == "..") {
            return Err(GlobError::DotDot(pattern.to_owned()).into());
        }
        if pattern.split('/').any(|p| p == ".") {
            return Err(GlobError::Dot(pattern.to_owned()).into());
        }
        Ok(GlobPattern(parsed_pattern))
    }
}

#[derive(Derivative)]
#[derivative(Debug)]
pub(crate) struct GlobSpec {
    common_prefix: String,
    exact_matches: HashSet<String>,
    patterns: Vec<GlobPattern>,
    excludes: Vec<GlobPattern>,
}

/// Returns the no-special-characters common prefix of all patterns. Suitable for binary search.
fn longest_common_glob_prefix<P: AsRef<str>>(patterns: &[P]) -> &str {
    if patterns.is_empty() {
        return "";
    }
    let first: &str = patterns[0].as_ref();
    // Globs have some special characters, ensure they're not present.
    let mut len = first
        .find(|c: char| "*[]?".contains(c))
        .unwrap_or(first.len());
    for pattern in patterns {
        if len == 0 {
            break;
        }
        len = std::cmp::min(
            len,
            pattern
                .as_ref()
                .chars()
                .take(len)
                .zip(first.chars())
                .take_while(|&(a, b)| a == b)
                .count(),
        );
    }
    match first.char_indices().nth(len) {
        None => first,
        Some((idx, _)) => &first[..idx],
    }
}

impl GlobSpec {
    const BINARY_SEARCH_CUTOFF: usize = 100;
    pub(crate) fn new<P: AsRef<str>, Q: AsRef<str>>(
        patterns: &[P],
        excludes: &[Q],
    ) -> anyhow::Result<Self> {
        let mut glob_patterns = Vec::new();
        let mut glob_excludes = Vec::new();
        let mut exact_matches = HashSet::new();
        for pattern in patterns {
            let pattern = pattern.as_ref();
            if pattern.contains('*') {
                glob_patterns.push(GlobPattern::new(pattern)?);
            } else {
                // TODO(nga): pattern `*/[bc]` is parsed as glob pattern,
                //   but `a/[bc]` is parsed as exact match?
                //   Does not look right.

                if ForwardRelativePath::new(&pattern).is_err() {
                    return Err(GlobError::ExactIncludeMustBeForwardRelativePath(
                        pattern.to_owned(),
                    )
                    .into());
                }
                exact_matches.insert(pattern.to_owned());
            }
        }
        for pattern in excludes {
            let pattern = pattern.as_ref();
            glob_excludes.push(GlobPattern::new(pattern)?);
        }
        Ok(Self {
            common_prefix: longest_common_glob_prefix(patterns).to_owned(),
            exact_matches,
            patterns: glob_patterns,
            excludes: glob_excludes,
        })
    }

    pub(crate) fn matches(&self, path: &str) -> bool {
        let options = glob::MatchOptions {
            require_literal_separator: true,
            require_literal_leading_dot: true,
            // FIXME: We should have case sensitive globs
            case_sensitive: false,
        };
        let include_matches = self.exact_matches.contains(path)
            || self
                .patterns
                .iter()
                .any(|p| p.0.matches_with(path, options));
        include_matches
            && !self
                .excludes
                .iter()
                .any(|p| p.0.matches_with(path, options))
    }

    pub(crate) fn resolve_glob<'a>(
        &'a self,
        spec: &'a PackageFileListing,
    ) -> Box<dyn Iterator<Item = &'a PackageRelativePath> + 'a> {
        if spec.files().len() >= Self::BINARY_SEARCH_CUTOFF && !self.common_prefix.is_empty() {
            return Box::new(
                spec.files_with_prefix(&self.common_prefix)
                    .filter(move |v| self.matches(v.as_str()))
                    .map(|p| &**p),
            );
        }
        Box::new(spec.files().filter(move |v| self.matches(v.as_str())))
    }
}

#[cfg(test)]
mod tests {
    use buck2_core::package::package_relative_path::PackageRelativePathBuf;

    use super::*;

    #[test]
    fn test_glob_match() -> anyhow::Result<()> {
        let spec = GlobSpec::new(
            &[
                "abc*",
                "**/*.java",
                "*/*/*.txt",
                "unglobbed",
                "excluded/unglobbed",
            ],
            &["excluded/**/*"],
        )?;

        assert!(spec.matches("abcxyz"));
        assert!(spec.matches("abcsomething"));
        assert!(spec.matches("1/2/3.txt"));
        assert!(spec.matches("dir/other/more/some.java"));
        assert!(spec.matches("unglobbed"));

        assert!(!spec.matches("xyz"));
        assert!(!spec.matches("abc/something"));
        assert!(!spec.matches("1/2.txt"));
        assert!(!spec.matches("1/2/3/4.txt"));

        assert!(!spec.matches("excluded/subdir/some.java"));
        assert!(!spec.matches("excluded/some.java"));
        assert!(!spec.matches("excluded/unglobbed"));

        Ok(())
    }

    #[test]
    fn test_glob_match_case_insensitive() -> anyhow::Result<()> {
        // NOTE: We probably should change this. But for now, let's codify the current behavior
        // since that's probably something that should require a migration.

        let spec = GlobSpec::new(&["src/**/*"], &["src/excluded/**/*"])?;
        assert!(spec.matches("src/foo"));
        assert!(spec.matches("SRC/foo"));
        assert!(!spec.matches("src/EXCLUDED/bar"));

        Ok(())
    }

    #[test]
    fn test_resolve_glob() -> anyhow::Result<()> {
        let spec = GlobSpec::new(&["abc*", "**/*.java", "*/*/*.txt"], &["excluded/**/*"])?;

        let package_listing = PackageFileListing::testing_new(&[
            "abcxyz",
            "abcsomething",
            "1/2/3.txt",
            "dir/other/more/some.java",
            "xyz",
            "abc/something",
            "1/2.txt",
            "1/2/3/4.txt",
            "excluded/subdir/some.java",
            "excluded/some.java",
        ]);

        let matches: Vec<_> = spec.resolve_glob(&package_listing).collect();

        assert_eq!(
            vec![
                "1/2/3.txt",
                "abcsomething",
                "abcxyz",
                "dir/other/more/some.java",
            ],
            matches
        );

        Ok(())
    }

    #[test]
    fn test_resolve_glob_dot() {
        fn glob(pattern: &str, listing: &PackageFileListing) -> Vec<PackageRelativePathBuf> {
            GlobSpec::new(&[pattern], &[""; 0])
                .unwrap()
                .resolve_glob(listing)
                .map(PackageRelativePath::to_owned)
                .collect()
        }

        let listing = PackageFileListing::testing_new(&["a", ".a", "b/c", "b/.c", ".d/e", ".d/.e"]);

        // Simple difference between dot and non-dot patterns.
        assert_eq!(vec![".a"], glob(".*", &listing));
        assert_eq!(vec!["a"], glob("*", &listing));

        // Match works in subdirectories too, even if directory name starts with dot.
        assert_eq!(vec![".d/e"], glob(".d/*", &listing));
        assert_eq!(vec![".d/.e"], glob(".d/.*", &listing));

        // Star matches directory, but not dot-directory.
        assert_eq!(vec!["b/.c"], glob("*/.*", &listing));
        assert_eq!(vec!["b/c"], glob("*/*", &listing));

        // Dot-star matches dot-directory, but not directory.
        assert_eq!(vec![".d/.e"], glob(".*/.*", &listing));
        assert_eq!(vec![".d/e"], glob(".*/*", &listing));

        // Star-star matches only non-star directories and files.
        assert_eq!(vec!["a", "b/c"], glob("**", &listing));
        assert_eq!(vec![".a", "b/.c"], glob("**/.*", &listing));
    }

    #[test]
    fn test_resolve_glob_square_brackets_bug() {
        let glob = GlobSpec::new(&["*/[bc]"], &[""; 0]).unwrap();
        assert!(glob.matches("a/b"));
        let glob = GlobSpec::new(&["a/[bc]"], &[""; 0]).unwrap();
        // TODO(nga): This is a bug.
        assert!(!glob.matches("a/b"));
    }

    #[test]
    fn test_invalid_patterns() {
        fn test_invalid(pattern: &str) {
            assert!(
                GlobSpec::new(&[pattern], &[""; 0]).is_err(),
                "Pattern must not be accepted: `{pattern}`"
            );
            assert!(
                GlobSpec::new(&[""; 0], &[pattern]).is_err(),
                "Pattern must not be accepted as exclude as well: `{pattern}`"
            );
        }

        test_invalid("a**");
        test_invalid("**a");
        test_invalid(".**");
    }

    #[test]
    fn test_leading_slash_in_pattern() {
        assert!(GlobSpec::new(&["/*"], &[""; 0]).is_err());
        assert!(GlobSpec::new(&["/a"], &[""; 0]).is_err());
    }

    #[test]
    fn test_dot_dot_in_pattern() {
        assert!(GlobSpec::new(&[".."], &[""; 0]).is_err());
        assert!(GlobSpec::new(&["../*"], &[""; 0]).is_err());
        assert!(GlobSpec::new(&["a/../*"], &[""; 0]).is_err());
        assert!(GlobSpec::new(&["*/../b"], &[""; 0]).is_err());
    }

    #[test]
    fn test_dot_in_pattern() {
        assert!(GlobSpec::new(&["."], &[""; 0]).is_err());
        assert!(GlobSpec::new(&["./a"], &[""; 0]).is_err());
        assert!(GlobSpec::new(&["a/./b"], &[""; 0]).is_err());
        assert!(GlobSpec::new(&["a/."], &[""; 0]).is_err());
    }

    #[test]
    fn test_double_slash_in_pattern() {
        assert!(GlobSpec::new(&["//"], &[""; 0]).is_err());
        assert!(GlobSpec::new(&["a//**"], &[""; 0]).is_err());
        assert!(GlobSpec::new(&["a/*//b"], &[""; 0]).is_err());
    }

    #[test]
    fn test_glob_prefix() {
        assert_eq!("a/", longest_common_glob_prefix(&["a/1", "a/2"]));
        assert_eq!("", longest_common_glob_prefix(&["a/1", "b/1"]));
        assert_eq!("a/", longest_common_glob_prefix(&["a/**/*.h", "a/**/*.c"]));
        assert_eq!("a/", longest_common_glob_prefix(&["a/in/*.h", "a/**/*.c"]));
        // '?' is a single-character wildcard.
        assert_eq!("a/", longest_common_glob_prefix(&["a/foo", "a/?"]));
        // '[' and ']' are special character set tokens.
        assert_eq!("f/", longest_common_glob_prefix(&["f/[ab]/*", "f/[abc]/*"]));
        // These codepoints have a common prefix in bytes, but not in chars.
        assert_eq!("", longest_common_glob_prefix(&["\u{0306}", "\u{0307}"]));
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

//! Implements the core skylark interpreter. This encodes the primitive
//! operations of converting file content to ASTs and evaluating import and
//! build files.

use std::cell::RefCell;
use std::sync::Arc;

use allocative::Allocative;
use anyhow::Context;
use buck2_common::legacy_configs::view::LegacyBuckConfigView;
use buck2_common::package_listing::listing::PackageListing;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::bzl::ImportPath;
use buck2_core::cells::build_file_cell::BuildFileCell;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::CellAliasResolver;
use buck2_events::dispatch::get_dispatcher;
use buck2_interpreter::factory::StarlarkEvaluatorProvider;
use buck2_interpreter::file_loader::InterpreterFileLoader;
use buck2_interpreter::file_loader::LoadResolver;
use buck2_interpreter::file_loader::LoadedModules;
use buck2_interpreter::file_type::StarlarkFileType;
use buck2_interpreter::import_paths::ImplicitImportPaths;
use buck2_interpreter::package_imports::ImplicitImport;
use buck2_interpreter::parse_import::parse_import;
use buck2_interpreter::path::BxlFilePath;
use buck2_interpreter::path::OwnedStarlarkModulePath;
use buck2_interpreter::path::OwnedStarlarkPath;
use buck2_interpreter::path::PackageFilePath;
use buck2_interpreter::path::StarlarkModulePath;
use buck2_interpreter::path::StarlarkPath;
use buck2_interpreter::prelude_path::PreludePath;
use buck2_interpreter::print_handler::EventDispatcherPrintHandler;
use buck2_node::nodes::eval_result::EvaluationResult;
use dupe::Dupe;
use gazebo::prelude::*;
use starlark::codemap::FileSpan;
use starlark::environment::FrozenModule;
use starlark::environment::Module;
use starlark::syntax::AstModule;
use starlark::values::list::ListRef;
use starlark::values::OwnedFrozenValue;
use starlark::values::ValueLike;
use starlark_map::small_map::SmallMap;
use thiserror::Error;

use crate::interpreter::build_context::BuildContext;
use crate::interpreter::build_context::PerFileTypeContext;
use crate::interpreter::cell_info::InterpreterCellInfo;
use crate::interpreter::global_interpreter_state::GlobalInterpreterState;
use crate::interpreter::module_internals::ModuleInternals;
use crate::super_package::data::SuperPackage;
use crate::super_package::eval_ctx::PackageFileEvalCtx;
use crate::super_package::package_value::PackageValues;

#[derive(Debug, Error)]
enum StarlarkParseError {
    #[error("Error parsing: `{0}`")]
    InFile(OwnedStarlarkPath),
    #[error("Tabs are not allowed in Buck files: `{0}`")]
    Tabs(OwnedStarlarkPath),
}

/// A ParseResult includes the parsed AST and a list of the imported files.
///
/// The imports are under a separate Arc so that that can be shared with
/// the evaluation result (which needs the imports but no longer needs the AST).
#[derive(Debug)]
pub struct ParseResult(
    pub AstModule,
    pub Arc<Vec<(Option<FileSpan>, OwnedStarlarkModulePath)>>,
);

impl ParseResult {
    fn new(
        ast: AstModule,
        implicit_imports: Vec<OwnedStarlarkModulePath>,
        resolver: &dyn LoadResolver,
    ) -> anyhow::Result<Self> {
        let mut loads = implicit_imports.into_map(|x| (None, x));
        for x in ast.loads() {
            let path = resolver
                .resolve_load(x.module_id, Some(&x.span))
                .with_context(|| {
                    format!(
                        "Error loading `load` of `{}` from `{}`",
                        x.module_id, x.span
                    )
                })?;
            loads.push((Some(x.span), path));
        }
        Ok(Self(ast, Arc::new(loads)))
    }

    pub fn ast(&self) -> &AstModule {
        &self.0
    }

    pub fn imports(&self) -> &Arc<Vec<(Option<FileSpan>, OwnedStarlarkModulePath)>> {
        &self.1
    }
}

/// Interpreter for build files.
///
/// The Interpreter is responsible for parsing files to an AST and then
/// evaluating that AST. The Interpreter doesn't maintain state or cache results
/// of parsing or loading imports.
#[derive(Allocative)]
pub(crate) struct InterpreterForCell {
    /// Non-cell-specific information.
    global_state: Arc<GlobalInterpreterState>,
    /// Cell-specific alias resolver.
    cell_names: CellAliasResolver,
    /// Log GC.
    verbose_gc: bool,
    /// When true, rule function creates a node with no attributes.
    /// (Which won't work correctly, but useful for profiling of starlark).
    ignore_attrs_for_profiling: bool,
    /// Implicit imports. These are only used for build files (e.g. `BUCK`),
    /// not for `bzl` or other files, because we only have implicit imports for build files.
    implicit_import_paths: Arc<ImplicitImportPaths>,
}

struct InterpreterLoadResolver {
    config: Arc<InterpreterForCell>,
    loader_path: CellPath,
    loader_file_type: StarlarkFileType,
    build_file_cell: BuildFileCell,
}

#[derive(Debug, Error)]
enum LoadResolutionError {
    #[error(
        "Cannot load `{0}`. Bxl loads are not allowed from within this context. bxl files can only be loaded from other bxl files."
    )]
    BxlLoadNotAllowed(CellPath),
    #[error("The `load` at {location} of `{got}` should use the canonical name `{wanted}`")]
    WrongCell {
        got: CellPath,
        wanted: CellPath,
        location: String,
    },
}

impl LoadResolver for InterpreterLoadResolver {
    fn resolve_load(
        &self,
        path: &str,
        location: Option<&FileSpan>,
    ) -> anyhow::Result<OwnedStarlarkModulePath> {
        // This is to be removed when we finish migration to Buck2.
        let path = path.trim_end_match("?v2_only");

        let path = parse_import(&self.config.cell_names, &self.loader_path, path)?;

        // check for bxl files first before checking for prelude.
        // All bxl imports are parsed the same regardless of prelude or not.
        if path.path().extension() == Some("bxl") {
            match self.loader_file_type {
                StarlarkFileType::Bzl | StarlarkFileType::Buck | StarlarkFileType::Package => {
                    return Err(LoadResolutionError::BxlLoadNotAllowed(path).into());
                }
                StarlarkFileType::Bxl => {
                    return Ok(OwnedStarlarkModulePath::BxlFile(BxlFilePath::new(path)?));
                }
            }
        }

        // If you load the same .bzl file twice via different aliases (e.g. fbcode//buck2/prelude/foo.bzl and prelude.bzl)
        // then anything doing pointer equality (t-sets, provider identities) will go wrong.
        let project_path = self
            .config
            .global_state
            .cell_resolver
            .resolve_path(path.as_ref())?;
        let reformed_path = self
            .config
            .global_state
            .cell_resolver
            .get_cell_path(&project_path)?;
        if reformed_path.cell() != path.cell() {
            // We actually call resolve_load twice for each loadable - once with all load's up front,
            // then again on each one when we are loading. The second time we don't have a location,
            // so just omit the soft_error that time. Once it is a real error, we should real error on either.
            if let Some(location) = location {
                return Err(LoadResolutionError::WrongCell {
                    got: path,
                    wanted: reformed_path,
                    location: location.to_string(),
                }
                .into());
            }
        }

        // If importing from the prelude, then do not let that inherit the configuration. This
        // ensures that if you define a UDR outside of the prelude's cell, it gets the same prelude
        // as using the exported rules from the prelude would. This matters notably for identity
        // checks in t-sets, which would fail if we had > 1 copy of the prelude.
        if let Some(prelude_import) = self.config.global_state.configuror.prelude_import() {
            if prelude_import.is_prelude_path(&path) {
                return Ok(OwnedStarlarkModulePath::LoadFile(
                    ImportPath::new_same_cell(path)?,
                ));
            }
        }

        Ok(OwnedStarlarkModulePath::LoadFile(
            ImportPath::new_with_build_file_cells(path, self.build_file_cell)?,
        ))
    }
}

impl InterpreterForCell {
    fn verbose_gc() -> anyhow::Result<bool> {
        match std::env::var_os("BUCK2_STARLARK_VERBOSE_GC") {
            Some(val) => Ok(!val.is_empty()),
            None => Ok(false),
        }
    }

    fn is_ignore_attrs_for_profiling() -> anyhow::Result<bool> {
        // If unsure, feel free to break this code or just delete it.
        // It is intended only for profiling of very specific use cases.
        let ignore_attrs_for_profiling = match std::env::var_os("BUCK2_IGNORE_ATTRS_FOR_PROFILING")
        {
            Some(val) => !val.is_empty(),
            None => false,
        };
        if ignore_attrs_for_profiling {
            // This messages is printed in each run once per cell.
            // Somewhat inconvenient, but it is safe.
            eprintln!("Ignoring rule attributes");
        }
        Ok(ignore_attrs_for_profiling)
    }

    //, configuror: Arc<dyn InterpreterConfigurer>
    pub(crate) fn new(
        cell_names: CellAliasResolver,
        global_state: Arc<GlobalInterpreterState>,
        implicit_import_paths: Arc<ImplicitImportPaths>,
    ) -> anyhow::Result<Self> {
        Ok(Self {
            global_state,
            cell_names,
            verbose_gc: Self::verbose_gc()?,
            ignore_attrs_for_profiling: Self::is_ignore_attrs_for_profiling()?,
            implicit_import_paths,
        })
    }

    fn create_env(
        &self,
        starlark_path: StarlarkPath<'_>,
        loaded_modules: &LoadedModules,
    ) -> anyhow::Result<Module> {
        let env = Module::new();

        if let Some(prelude_import) = self.prelude_import(starlark_path) {
            let prelude_env = loaded_modules
                .map
                .get(&StarlarkModulePath::LoadFile(prelude_import.import_path()))
                .with_context(|| {
                    format!(
                        "Should've had an env for the prelude import `{}` (internal error)",
                        prelude_import,
                    )
                })?
                .env();
            env.import_public_symbols(prelude_env);
            if let StarlarkPath::BuildFile(_) = starlark_path {
                if let Some(native) = prelude_env.get_option("native")? {
                    let native = native.value();
                    for attr in native.dir_attr() {
                        if let Some(value) = native.get_attr(&attr, env.heap())? {
                            env.set(&attr, value);
                        } else {
                            // Should not be possible.
                        }
                    }
                }
            }
        }

        Ok(env)
    }

    // The environment for evaluating a build file contains additional information
    // to support the extra things available in that context. For example, rule
    // functions can be invoked when evaluating a build file, the package (cell
    // + path) is available. It also includes the implicit root include and
    // implicit package include.
    fn create_build_env(
        &self,
        build_file: &BuildFilePath,
        package_listing: &PackageListing,
        super_package: SuperPackage,
        package_boundary_exception: bool,
        loaded_modules: &LoadedModules,
    ) -> anyhow::Result<(Module, ModuleInternals)> {
        let internals = self.global_state.configuror.new_extra_context(
            self.get_cell_config(build_file.build_file_cell()),
            build_file.clone(),
            package_listing.dupe(),
            super_package,
            package_boundary_exception,
            loaded_modules,
            self.package_import(build_file),
        )?;
        let env = self.create_env(StarlarkPath::BuildFile(build_file), loaded_modules)?;

        if let Some(root_import) = self.root_import() {
            let root_env = loaded_modules
                .map
                .get(&StarlarkModulePath::LoadFile(&root_import))
                .with_context(|| {
                    format!(
                        "Should've had an env for the root import `{}` (internal error)",
                        root_import,
                    )
                })?
                .env();
            env.import_public_symbols(root_env);
        }

        Ok((env, internals))
    }

    fn get_cell_config(&self, build_file_cell: BuildFileCell) -> &InterpreterCellInfo {
        self.global_state
            .cell_configs
            .get(&build_file_cell)
            .unwrap_or_else(|| panic!("Should've had cell config for {}", build_file_cell))
    }

    fn load_resolver(
        self: &Arc<Self>,
        current_file_path: StarlarkPath<'_>,
    ) -> InterpreterLoadResolver {
        InterpreterLoadResolver {
            config: self.dupe(),
            loader_path: current_file_path
                .path()
                .parent()
                .expect("loading file should have parent directory")
                .to_owned(),
            loader_file_type: current_file_path.file_type(),
            build_file_cell: current_file_path.build_file_cell(),
        }
    }

    fn package_import(&self, build_file_import: &BuildFilePath) -> Option<&Arc<ImplicitImport>> {
        self.implicit_import_paths
            .package_imports
            .get(build_file_import.package())
    }

    fn root_import(&self) -> Option<ImportPath> {
        self.implicit_import_paths.root_import.clone()
    }

    fn prelude_import(&self, import: StarlarkPath) -> Option<&PreludePath> {
        let prelude_import = self.global_state.configuror.prelude_import();
        if let Some(prelude_import) = prelude_import {
            let import_path = import.path();

            // Only return the prelude for things outside the prelude directory.
            if import.unpack_build_file().is_some() || !prelude_import.is_prelude_path(&import_path)
            {
                return Some(prelude_import);
            }
        }

        None
    }

    /// Parses skylark code to an AST.
    pub(crate) fn parse(
        self: &Arc<Self>,
        import: StarlarkPath,
        content: String,
    ) -> anyhow::Result<ParseResult> {
        // Indentation with tabs is prohibited by starlark spec and configured starlark dialect.
        // This check also prohibits tabs even where spaces are not significant,
        // for example inside parentheses in function call arguments,
        // which restricts what the spec allows.
        if content.contains('\t') {
            return Err(StarlarkParseError::Tabs(OwnedStarlarkPath::new(import)).into());
        }

        let project_relative_path = self
            .global_state
            .cell_resolver
            .resolve_path(import.path().as_ref().as_ref())?;
        let result: anyhow::Result<_> = try {
            let disable_starlark_types = self.global_state.disable_starlark_types;
            let ast = AstModule::parse(
                project_relative_path.as_str(),
                content,
                &import.file_type().dialect(disable_starlark_types),
            )?;
            let mut implicit_imports = Vec::new();
            if let Some(i) = self.prelude_import(import) {
                implicit_imports.push(OwnedStarlarkModulePath::LoadFile(i.import_path().clone()));
            }
            if let StarlarkPath::BuildFile(build_file) = import {
                if let Some(i) = self.package_import(build_file) {
                    implicit_imports.push(OwnedStarlarkModulePath::LoadFile(i.import().clone()));
                }
                if let Some(i) = self.root_import() {
                    implicit_imports.push(OwnedStarlarkModulePath::LoadFile(i));
                }
            }
            ParseResult::new(ast, implicit_imports, &self.load_resolver(import))?
        };
        result.with_context(|| StarlarkParseError::InFile(OwnedStarlarkPath::new(import)))
    }

    pub(crate) fn resolve_path(
        self: &Arc<Self>,
        import: StarlarkPath<'_>,
        import_string: &str,
    ) -> anyhow::Result<OwnedStarlarkModulePath> {
        self.load_resolver(import).resolve_load(import_string, None)
    }

    fn eval(
        self: &Arc<Self>,
        env: &Module,
        ast: AstModule,
        buckconfig: &dyn LegacyBuckConfigView,
        root_buckconfig: &dyn LegacyBuckConfigView,
        loaded_modules: LoadedModules,
        extra_context: PerFileTypeContext,
        eval_provider: &mut dyn StarlarkEvaluatorProvider,
    ) -> anyhow::Result<PerFileTypeContext> {
        let import = extra_context.starlark_path();
        let globals = self
            .global_state
            .globals_for_file_type(extra_context.file_type());
        let file_loader =
            InterpreterFileLoader::new(loaded_modules, Arc::new(self.load_resolver(import)));
        let cell_info = self.get_cell_config(import.build_file_cell());
        let host_info = self.global_state.configuror.host_info();
        let extra = BuildContext::new_for_module(
            env,
            cell_info,
            buckconfig,
            root_buckconfig,
            host_info,
            extra_context,
            self.ignore_attrs_for_profiling,
        );
        let print = EventDispatcherPrintHandler(get_dispatcher());
        {
            let mut eval = eval_provider.make(env)?;
            eval.set_print_handler(&print);
            eval.set_loader(&file_loader);
            eval.extra = Some(&extra);
            if self.verbose_gc {
                eval.verbose_gc();
            }
            match eval.eval_module(ast, globals) {
                Ok(_) => {
                    eval_provider
                        .evaluation_complete(&mut eval)
                        .context("Profiler finalization failed")?;
                    eval_provider
                        .visit_frozen_module(None)
                        .context("Profiler heap visitation failed")?
                }
                Err(p) => return Err(p),
            }
        };
        Ok(extra.additional)
    }

    /// Evaluates the AST for a parsed module. Loaded modules must contain the loaded
    /// environment for all (transitive) required imports.
    /// Returns the FrozenModule for the module.
    pub(crate) fn eval_module(
        self: &Arc<Self>,
        starlark_path: StarlarkModulePath<'_>,
        buckconfig: &dyn LegacyBuckConfigView,
        root_buckconfig: &dyn LegacyBuckConfigView,
        ast: AstModule,
        loaded_modules: LoadedModules,
        eval_provider: &mut dyn StarlarkEvaluatorProvider,
    ) -> anyhow::Result<FrozenModule> {
        let env = self.create_env(starlark_path.into(), &loaded_modules)?;
        self.eval(
            &env,
            ast,
            buckconfig,
            root_buckconfig,
            loaded_modules,
            PerFileTypeContext::for_module(starlark_path),
            eval_provider,
        )?;
        env.freeze()
    }

    pub(crate) fn eval_package_file(
        self: &Arc<Self>,
        package_file_path: &PackageFilePath,
        ast: AstModule,
        parent: SuperPackage,
        buckconfig: &dyn LegacyBuckConfigView,
        root_buckconfig: &dyn LegacyBuckConfigView,
        loaded_modules: LoadedModules,
        eval_provider: &mut dyn StarlarkEvaluatorProvider,
    ) -> anyhow::Result<SuperPackage> {
        let env = self.create_env(
            StarlarkPath::PackageFile(package_file_path),
            &loaded_modules,
        )?;

        let package_values = env.heap().alloc_complex_no_freeze(PackageValues::default());
        env.set_extra_value(package_values);

        let extra_context = PerFileTypeContext::Package(
            package_file_path.clone(),
            PackageFileEvalCtx {
                parent,
                visibility: RefCell::new(None),
            },
        );

        let per_file_context = self.eval(
            &env,
            ast,
            buckconfig,
            root_buckconfig,
            loaded_modules,
            extra_context,
            eval_provider,
        )?;

        let package_values = env
            .extra_value()
            .context("extra_value not set (internal error)")?;
        let package_values = package_values
            .downcast_ref::<PackageValues>()
            .context("extra_value is not a PackageValues (internal error)")?;

        let keys = package_values
            .values
            .borrow()
            .keys()
            .cloned()
            .collect::<Vec<_>>();
        let values = package_values
            .values
            .borrow()
            .values()
            .copied()
            .collect::<Vec<_>>();
        // Starlark list of strings. We only need to freeze values, not keys.
        // We could freeze full `PackageValues` but if we do that we:
        // * would have have to implement `Freeze` for `PackageValue`
        // * will store a bit more memory in frozen heap.
        let values = env.heap().alloc(values);

        env.set_extra_value(values);

        let env = env.freeze().context("Freezing package values")?;

        let values = env
            .extra_value()
            .context("extra_value not set (internal error)")?;
        let values = ListRef::from_frozen_value(values)
            .context("extra_value is not a list (internal error)")?;

        let package_values: SmallMap<String, OwnedFrozenValue> = keys
            .into_iter()
            .zip(values.content().map(|v| {
                let frozen_value = v.unpack_frozen().unwrap();
                unsafe { OwnedFrozenValue::new(env.frozen_heap().dupe(), frozen_value) }
            }))
            .collect();

        let package_file_eval_ctx = per_file_context.into_package_file()?;

        Ok(package_file_eval_ctx.build_super_package(package_values))
    }

    /// Evaluates the AST for a parsed build file. Loaded modules must contain the
    /// loaded environment for all (transitive) required imports.
    /// Returns the result of evaluation.
    pub(crate) fn eval_build_file(
        self: &Arc<Self>,
        build_file: &BuildFilePath,
        buckconfig: &dyn LegacyBuckConfigView,
        root_buckconfig: &dyn LegacyBuckConfigView,
        listing: PackageListing,
        super_package: SuperPackage,
        package_boundary_exception: bool,
        ast: AstModule,
        loaded_modules: LoadedModules,
        eval_provider: &mut dyn StarlarkEvaluatorProvider,
    ) -> anyhow::Result<EvaluationResult> {
        let (env, internals) = self.create_build_env(
            build_file,
            &listing,
            super_package,
            package_boundary_exception,
            &loaded_modules,
        )?;
        let internals = self
            .eval(
                &env,
                ast,
                buckconfig,
                root_buckconfig,
                loaded_modules,
                PerFileTypeContext::Build(build_file.clone(), internals),
                eval_provider,
            )?
            .into_build()?;

        Ok(EvaluationResult::from(internals))
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Arc;

use buck2_common::dice::cells::SetCellResolver;
use buck2_common::legacy_configs::dice::SetLegacyConfigs;
use buck2_common::legacy_configs::LegacyBuckConfigs;
use buck2_core::cells::CellResolver;
use buck2_interpreter::dice::starlark_profiler::SetStarlarkProfilerInstrumentation;
use buck2_interpreter::dice::starlark_profiler::StarlarkProfilerConfiguration;
use buck2_interpreter::dice::starlark_types::SetDisableStarlarkTypes;
use dice::DiceTransactionUpdater;

use crate::interpreter::configuror::BuildInterpreterConfiguror;
use crate::interpreter::context::SetInterpreterContext;

/// Common code to initialize Starlark interpreter globals.
pub fn setup_interpreter(
    updater: &mut DiceTransactionUpdater,
    cell_resolver: CellResolver,
    configuror: Arc<BuildInterpreterConfiguror>,
    legacy_configs: LegacyBuckConfigs,
    starlark_profiler_instrumentation_override: StarlarkProfilerConfiguration,
    disable_starlark_types: bool,
) -> anyhow::Result<()> {
    updater.set_cell_resolver(cell_resolver)?;
    updater.set_interpreter_context(configuror)?;
    updater.set_legacy_configs(legacy_configs)?;
    updater.set_starlark_profiler_instrumentation_override(
        starlark_profiler_instrumentation_override,
    )?;
    updater.set_disable_starlark_types(disable_starlark_types)?;

    Ok(())
}

pub fn setup_interpreter_basic(
    dice: &mut DiceTransactionUpdater,
    cell_resolver: CellResolver,
    configuror: Arc<BuildInterpreterConfiguror>,
    legacy_configs: LegacyBuckConfigs,
) -> anyhow::Result<()> {
    setup_interpreter(
        dice,
        cell_resolver,
        configuror,
        legacy_configs,
        StarlarkProfilerConfiguration::default(),
        false,
    )
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

pub mod buckconfig;
pub mod build_context;
pub mod build_defs;
pub mod calculation;
pub(crate) mod cell_info;
pub mod configuror;
pub mod context;
pub mod cycles;
pub mod dice_calculation_delegate;
pub mod functions;
pub mod global_interpreter_state;
pub mod globals;
pub mod globspec;
pub mod interpreter_for_cell;
pub mod interpreter_setup;
pub mod module_internals;
pub mod natives;
pub mod selector;
pub mod testing;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::cell::RefCell;
use std::cell::RefMut;
use std::fmt;
use std::fmt::Debug;
use std::mem;
use std::sync::Arc;

use buck2_common::package_listing::listing::PackageListing;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::bzl::ImportPath;
use buck2_core::package::package_relative_path::PackageRelativePath;
use buck2_core::target::name::TargetNameRef;
use buck2_events::dispatch::console_message;
use buck2_interpreter::package_imports::ImplicitImport;
use buck2_node::nodes::eval_result::EvaluationResult;
use buck2_node::nodes::targets_map::TargetsMap;
use buck2_node::nodes::targets_map::TargetsMapRecordError;
use buck2_node::nodes::unconfigured::TargetNode;
use buck2_node::package::Package;
use dupe::Dupe;
use starlark::environment::FrozenModule;
use starlark::values::OwnedFrozenValue;

use crate::attrs::coerce::ctx::BuildAttrCoercionContext;
use crate::interpreter::globspec::GlobSpec;
use crate::super_package::data::SuperPackage;

impl From<ModuleInternals> for EvaluationResult {
    // TODO(cjhopman): Let's make this an `into_evaluation_result()` on ModuleInternals instead.
    fn from(internals: ModuleInternals) -> Self {
        let ModuleInternals {
            state,
            imports,
            buildfile_path,
            ..
        } = internals;
        let recorder = match state.into_inner() {
            State::BeforeTargets(_) => TargetsRecorder::new(),
            State::Targets(RecordingTargets { recorder, .. }) => recorder,
        };
        EvaluationResult::new(buildfile_path, imports, recorder.take())
    }
}

#[derive(Debug)]
struct Oncall(Arc<String>);

#[derive(Debug)]
struct RecordingTargets {
    package: Arc<Package>,
    recorder: TargetsRecorder,
}

#[derive(Debug)]
enum State {
    /// No targets recorded yet, `oncall` call is allowed unless it was already called.
    BeforeTargets(Option<Oncall>),
    /// First target seen.
    Targets(RecordingTargets),
}

/// ModuleInternals contains the module/package-specific information for
/// evaluating build files. Built-in functions that need access to
/// package-specific information or objects can get them by acquiring the
/// ModuleInternals.
#[derive(Debug)]
pub struct ModuleInternals {
    attr_coercion_context: BuildAttrCoercionContext,
    buildfile_path: Arc<BuildFilePath>,
    /// Have you seen an oncall annotation yet
    state: RefCell<State>,
    /// Directly imported modules.
    imports: Vec<ImportPath>,
    package_implicits: Option<PackageImplicits>,
    record_target_call_stacks: bool,
    skip_targets_with_duplicate_names: bool,
    /// The files owned by this directory. Is `None` for .bzl files.
    package_listing: PackageListing,
    pub(crate) super_package: SuperPackage,
}

#[derive(Debug)]
pub(crate) struct PackageImplicits {
    import_spec: Arc<ImplicitImport>,
    env: FrozenModule,
}

impl PackageImplicits {
    pub(crate) fn new(import_spec: Arc<ImplicitImport>, env: FrozenModule) -> Self {
        Self { import_spec, env }
    }

    fn lookup(&self, name: &str) -> Option<OwnedFrozenValue> {
        self.env
            .get_option(self.import_spec.lookup_alias(name))
            .ok()
            .flatten()
    }
}

#[derive(Debug, thiserror::Error)]
enum OncallErrors {
    #[error("Called `oncall` after one or more targets were declared, `oncall` must be first.")]
    OncallAfterTargets,
    #[error("Called `oncall` more than once in the file.")]
    DuplicateOncall,
}

impl ModuleInternals {
    pub(crate) fn new(
        attr_coercion_context: BuildAttrCoercionContext,
        buildfile_path: Arc<BuildFilePath>,
        imports: Vec<ImportPath>,
        package_implicits: Option<PackageImplicits>,
        record_target_call_stacks: bool,
        skip_targets_with_duplicate_names: bool,
        package_listing: PackageListing,
        super_package: SuperPackage,
    ) -> Self {
        Self {
            attr_coercion_context,
            buildfile_path,
            state: RefCell::new(State::BeforeTargets(None)),
            imports,
            package_implicits,
            record_target_call_stacks,
            skip_targets_with_duplicate_names,
            package_listing,
            super_package,
        }
    }

    pub(crate) fn attr_coercion_context(&self) -> &BuildAttrCoercionContext {
        &self.attr_coercion_context
    }

    pub fn record(&self, target_node: TargetNode) -> anyhow::Result<()> {
        match self.recording_targets().recorder.record(target_node) {
            Ok(()) => Ok(()),
            Err(e @ TargetsMapRecordError::RegisteredTargetTwice { .. }) => {
                if self.skip_targets_with_duplicate_names {
                    console_message(e.to_string());
                    Ok(())
                } else {
                    Err(e.into())
                }
            }
        }
    }

    pub(crate) fn set_oncall(&self, name: &str) -> anyhow::Result<()> {
        match &mut *self.state.borrow_mut() {
            State::BeforeTargets(Some(_)) => Err(OncallErrors::DuplicateOncall.into()),
            State::BeforeTargets(oncall) => {
                assert!(oncall.is_none());
                *oncall = Some(Oncall(Arc::new(name.to_owned())));
                Ok(())
            }
            State::Targets(..) => {
                // We require oncall to be first both so users can find it,
                // and so we can propagate it to all targets more easily.
                Err(OncallErrors::OncallAfterTargets.into())
            }
        }
    }

    fn recording_targets(&self) -> RefMut<RecordingTargets> {
        RefMut::map(self.state.borrow_mut(), |state| {
            loop {
                match state {
                    State::BeforeTargets(oncall) => {
                        let oncall = mem::take(oncall).map(|Oncall(name)| name);
                        *state = State::Targets(RecordingTargets {
                            package: Arc::new(Package {
                                buildfile_path: self.buildfile_path.dupe(),
                                oncall,
                            }),
                            recorder: TargetsRecorder::new(),
                        });
                        continue;
                    }
                    State::Targets(r) => return r,
                }
            }
        })
    }

    pub(crate) fn target_exists(&self, name: &str) -> bool {
        self.recording_targets()
            .recorder
            .targets
            .contains_key(TargetNameRef::unchecked_new(name))
    }

    pub fn buildfile_path(&self) -> &Arc<BuildFilePath> {
        &self.buildfile_path
    }

    pub fn package(&self) -> Arc<Package> {
        self.recording_targets().package.dupe()
    }

    pub(crate) fn get_package_implicit(&self, name: &str) -> Option<OwnedFrozenValue> {
        self.package_implicits
            .as_ref()
            .and_then(|implicits| implicits.lookup(name))
    }

    pub fn record_target_call_stacks(&self) -> bool {
        self.record_target_call_stacks
    }

    pub(crate) fn resolve_glob<'a>(
        &'a self,
        spec: &'a GlobSpec,
    ) -> impl Iterator<Item = &'a PackageRelativePath> {
        spec.resolve_glob(self.package_listing.files())
    }
}

// Records the targets declared when evaluating a build file.
struct TargetsRecorder {
    targets: TargetsMap,
}

impl Debug for TargetsRecorder {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("TargetsRecorder").finish_non_exhaustive()
    }
}

impl TargetsRecorder {
    fn new() -> Self {
        Self {
            targets: TargetsMap::new(),
        }
    }

    fn record(&mut self, target_node: TargetNode) -> Result<(), TargetsMapRecordError> {
        self.targets.record(target_node)
    }

    fn take(self) -> TargetsMap {
        self.targets
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use starlark::environment::GlobalsBuilder;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::none::NoneType;
use starlark::values::Value;

use crate::interpreter::module_internals::ModuleInternals;

#[starlark_module]
pub fn register_module_natives(globals: &mut GlobalsBuilder) {
    /// Check if the target with `name` has already been defined,
    /// returns `True` if it has.
    ///
    /// Note that this function checks for the existence of a _target_ rather than a _rule_.
    /// In general use of this function is discouraged, as it makes definitions of rules not compose.
    fn rule_exists(name: &str, eval: &mut Evaluator) -> anyhow::Result<bool> {
        Ok(ModuleInternals::from_context(eval, "rule_exists")?.target_exists(name))
    }

    /// Called in a `BUCK` file to declare the oncall contact details for
    /// all the targets defined. Must be called at most once, before any targets
    /// have been declared. Errors if called from a `.bzl` file.
    fn oncall(
        #[starlark(require = pos)] name: &str,
        eval: &mut Evaluator,
    ) -> anyhow::Result<NoneType> {
        let internals = ModuleInternals::from_context(eval, "oncall")?;
        internals.set_oncall(name)?;
        Ok(NoneType)
    }

    fn implicit_package_symbol<'v>(
        name: &str,
        default: Option<Value<'v>>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        let internals = ModuleInternals::from_context(eval, "implicit_package_symbol")?;
        match internals.get_package_implicit(name) {
            None => Ok(default.unwrap_or_else(Value::new_none)),
            Some(v) => {
                // FIXME(ndmitchell): Document why this is safe
                Ok(unsafe { v.unchecked_frozen_value().to_value() })
            }
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fmt;
use std::fmt::Display;

use allocative::Allocative;
use starlark::any::ProvidesStaticType;
use starlark::coerce::Coerce;
use starlark::collections::SmallMap;
use starlark::environment::GlobalsBuilder;
use starlark::eval::Evaluator;
use starlark::starlark_complex_value;
use starlark::starlark_module;
use starlark::values::dict::Dict;
use starlark::values::dict::DictRef;
use starlark::values::starlark_value;
use starlark::values::Freeze;
use starlark::values::Freezer;
use starlark::values::FrozenValue;
use starlark::values::Heap;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::Trace;
use starlark::values::Tracer;
use starlark::values::Value;
use starlark::values::ValueLike;

/// Representation of `select()` in Starlark.
#[derive(Debug, ProvidesStaticType, NoSerialize, Allocative)] // TODO selector should probably support serializing
#[repr(C)]
pub enum StarlarkSelectorGen<ValueType> {
    Inner(ValueType),
    Added(ValueType, ValueType),
}

impl<V: Display> Display for StarlarkSelectorGen<V> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            StarlarkSelectorGen::Inner(v) => {
                f.write_str("select(")?;
                v.fmt(f)?;
                f.write_str(")")
            }
            StarlarkSelectorGen::Added(l, r) => {
                l.fmt(f)?;
                f.write_str(" + ")?;
                r.fmt(f)
            }
        }
    }
}

unsafe impl<From: Coerce<To>, To> Coerce<StarlarkSelectorGen<To>> for StarlarkSelectorGen<From> {}

starlark_complex_value!(pub StarlarkSelector);

impl<'v> StarlarkSelector<'v> {
    fn new(d: Value<'v>) -> Self {
        StarlarkSelector::Inner(d)
    }

    fn added(left: Value<'v>, right: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(heap.alloc(StarlarkSelector::Added(left, right)))
    }

    /// Tests that two selects are equal to each other. For testing use only.
    /// We simply compare their string representations.
    fn select_equal_internal(left: Value, right: Value) -> anyhow::Result<bool> {
        Ok(left.to_repr() == right.to_repr())
    }

    /// Maps a selector.
    ///
    /// Each value within a selector map and on each side of an addition will be passed to the
    /// mapping function. The returned selector will have the same structure as this one.
    ///
    /// Ex:
    /// ```starlark
    /// def increment_items(a):
    ///     return [v + 1 for v in a]
    ///
    /// select_map([1, 2] + select({"c": [2]}}, increment_items) == [2, 3] + select({"c": [3]})
    /// ```
    fn select_map<'a>(
        val: Value<'a>,
        eval: &mut Evaluator<'a, '_>,
        func: Value<'a>,
    ) -> anyhow::Result<Value<'a>> {
        fn invoke<'v>(
            eval: &mut Evaluator<'v, '_>,
            func: Value<'v>,
            val: Value<'v>,
        ) -> anyhow::Result<Value<'v>> {
            eval.eval_function(func, &[val], &[])
        }

        if let Some(selector) = StarlarkSelector::from_value(val) {
            match *selector {
                StarlarkSelectorGen::Inner(selector) => {
                    let selector = DictRef::from_value(selector).unwrap();
                    let mut mapped = SmallMap::with_capacity(selector.len());
                    for (k, v) in selector.iter_hashed() {
                        mapped.insert_hashed(k, invoke(eval, func, v)?);
                    }
                    Ok(eval
                        .heap()
                        .alloc(StarlarkSelector::new(eval.heap().alloc(Dict::new(mapped)))))
                }
                StarlarkSelectorGen::Added(left, right) => {
                    Ok(eval.heap().alloc(StarlarkSelectorGen::Added(
                        Self::select_map(left, eval, func)?,
                        Self::select_map(right, eval, func)?,
                    )))
                }
            }
        } else {
            invoke(eval, func, val)
        }
    }

    /// Test values in the select expression using the given function.
    ///
    /// Returns True, if any value in the select passes, else False.
    ///
    /// Ex:
    /// ```starlark
    /// select_test([1] + select({"c": [1]}), lambda a: len(a) > 1) == False
    /// select_test([1, 2] + select({"c": [1]}), lambda a: len(a) > 1) == True
    /// select_test([1] + select({"c": [1, 2]}), lambda a: len(a) > 1) == True
    /// ```
    ///
    fn select_test<'a>(
        val: Value<'a>,
        eval: &mut Evaluator<'a, '_>,
        func: Value<'a>,
    ) -> anyhow::Result<bool> {
        fn invoke<'v>(
            eval: &mut Evaluator<'v, '_>,
            func: Value<'v>,
            val: Value<'v>,
        ) -> anyhow::Result<bool> {
            eval.eval_function(func, &[val], &[])?
                .unpack_bool()
                .ok_or_else(|| {
                    anyhow::anyhow!("Expected testing function to have a boolean return type")
                })
        }

        if let Some(selector) = StarlarkSelector::from_value(val) {
            match *selector {
                StarlarkSelectorGen::Inner(selector) => {
                    let selector = DictRef::from_value(selector).unwrap();
                    for v in selector.values() {
                        let result = invoke(eval, func, v)?;
                        if result {
                            return Ok(true);
                        }
                    }
                    Ok(false)
                }
                StarlarkSelectorGen::Added(left, right) => {
                    Ok(Self::select_test(left, eval, func)?
                        || Self::select_test(right, eval, func)?)
                }
            }
        } else {
            invoke(eval, func, val)
        }
    }
}

trait StarlarkSelectorBase<'v> {
    type Item: ValueLike<'v>;
}

impl<'v> StarlarkSelectorBase<'v> for StarlarkSelector<'v> {
    type Item = Value<'v>;
}

unsafe impl<'v> Trace<'v> for StarlarkSelector<'v> {
    fn trace(&mut self, tracer: &Tracer<'v>) {
        match self {
            Self::Inner(a) => tracer.trace(a),
            Self::Added(a, b) => {
                tracer.trace(a);
                tracer.trace(b);
            }
        }
    }
}

impl<'v> Freeze for StarlarkSelector<'v> {
    type Frozen = FrozenStarlarkSelector;
    fn freeze(self, freezer: &Freezer) -> anyhow::Result<Self::Frozen> {
        Ok(match self {
            StarlarkSelector::Inner(v) => FrozenStarlarkSelector::Inner(v.freeze(freezer)?),
            StarlarkSelector::Added(l, r) => {
                FrozenStarlarkSelector::Added(l.freeze(freezer)?, r.freeze(freezer)?)
            }
        })
    }
}

impl StarlarkSelectorBase<'_> for FrozenStarlarkSelector {
    type Item = FrozenValue;
}

#[starlark_value(type = "selector")]
impl<'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for StarlarkSelectorGen<V>
where
    Self: ProvidesStaticType<'v> + StarlarkSelectorBase<'v, Item = V>,
{
    fn to_bool(&self) -> bool {
        true
    }

    fn radd(&self, left: Value<'v>, heap: &'v Heap) -> Option<anyhow::Result<Value<'v>>> {
        let right = heap.alloc(match self {
            StarlarkSelectorGen::Inner(x) => StarlarkSelectorGen::Inner(x.to_value()),
            StarlarkSelectorGen::Added(x, y) => {
                StarlarkSelectorGen::Added(x.to_value(), y.to_value())
            }
        });
        Some(StarlarkSelector::added(left, right, heap))
    }

    fn add(&self, other: Value<'v>, heap: &'v Heap) -> Option<anyhow::Result<Value<'v>>> {
        let this = match self {
            Self::Inner(ref v) => heap.alloc(StarlarkSelector::new(v.to_value())),
            Self::Added(ref l, ref r) => {
                match StarlarkSelector::added(l.to_value(), r.to_value(), heap) {
                    Err(e) => return Some(Err(e)),
                    Ok(v) => v,
                }
            }
        };

        Some(StarlarkSelector::added(this, other, heap))
    }
}

#[starlark_module]
pub fn register_select(globals: &mut GlobalsBuilder) {
    fn select<'v>(#[starlark(require = pos)] d: Value<'v>) -> anyhow::Result<StarlarkSelector<'v>> {
        Ok(StarlarkSelector::new(d))
    }

    /// Applies a mapping function to a selector. See [StarlarkSelector::select_map].
    fn select_map<'v>(
        #[starlark(require = pos)] d: Value<'v>,
        #[starlark(require = pos)] func: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        StarlarkSelector::select_map(d, eval, func)
    }

    /// Applies a test function to a selector. See [StarlarkSelector::select_test].
    fn select_test<'v>(
        #[starlark(require = pos)] d: Value<'v>,
        #[starlark(require = pos)] func: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<bool> {
        StarlarkSelector::select_test(d, eval, func)
    }

    /// Tests that two selects are equal to each other. For testing use only.
    fn select_equal_internal<'v>(
        #[starlark(require = pos)] left: Value<'v>,
        #[starlark(require = pos)] right: Value<'v>,
    ) -> anyhow::Result<bool> {
        StarlarkSelector::select_equal_internal(left, right)
    }
}