/*
 * 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 buck2_common::legacy_configs::LegacyBuckConfig;
use buck2_common::package_listing::listing::testing::PackageListingExt;
use buck2_common::package_listing::listing::PackageListing;
use buck2_core::bzl::ImportPath;
use buck2_core::cells::alias::NonEmptyCellAlias;
use buck2_core::cells::build_file_cell::BuildFileCell;
use buck2_core::cells::cell_root_path::CellRootPathBuf;
use buck2_core::cells::name::CellName;
use buck2_core::cells::CellResolver;
use buck2_core::package::PackageLabel;
use buck2_interpreter::extra::InterpreterHostArchitecture;
use buck2_interpreter::extra::InterpreterHostPlatform;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use maplit::hashmap;
use starlark::environment::Globals;
use starlark::environment::Module;
use starlark::eval::Evaluator;
use starlark::syntax::AstModule;
use starlark::syntax::Dialect;
use starlark::values::Value;

use crate::attrs::coerce::ctx::BuildAttrCoercionContext;
use crate::interpreter::build_context::BuildContext;
use crate::interpreter::build_context::PerFileTypeContext;
use crate::interpreter::cell_info::InterpreterCellInfo;
use crate::interpreter::functions::host_info::HostInfo;

pub fn coercion_ctx() -> impl AttrCoercionContext {
    coercion_ctx_listing(PackageListing::testing_empty())
}

pub fn coercion_ctx_listing(package_listing: PackageListing) -> impl AttrCoercionContext {
    let package = PackageLabel::testing();
    let aliases = hashmap![
        NonEmptyCellAlias::new("cell1".to_owned()).unwrap() => CellName::testing_new("cell1"),
    ];

    let cell_resolver = CellResolver::testing_with_names_and_paths_with_alias(&[
        (
            package.cell_name(),
            CellRootPathBuf::testing_new(""),
            aliases,
        ),
        (
            CellName::testing_new("cell1"),
            CellRootPathBuf::testing_new("cell1"),
            HashMap::new(),
        ),
    ]);

    BuildAttrCoercionContext::new_with_package(cell_resolver, (package, package_listing), false)
}

fn cell_resolver() -> CellResolver {
    CellResolver::testing_with_name_and_path(
        CellName::testing_new("root"),
        CellRootPathBuf::testing_new(""),
    )
}

pub fn to_value<'v>(env: &'v Module, globals: &Globals, content: &str) -> Value<'v> {
    let import_path = ImportPath::testing_new("root//:defs.bzl");
    let ast = AstModule::parse(
        &import_path.to_string(),
        content.to_owned(),
        &Dialect::Extended,
    )
    .unwrap_or_else(|err| panic!("Failed parsing `{}`. Error: `{}`", content, err));
    let cell_info = InterpreterCellInfo::new(
        BuildFileCell::new(CellName::testing_new("root")),
        cell_resolver(),
    )
    .unwrap();
    let buckconfig = LegacyBuckConfig::empty();
    let root_buckconfig = LegacyBuckConfig::empty();
    let host_platform = InterpreterHostPlatform::Linux;
    let host_architecture = InterpreterHostArchitecture::X86_64;
    let host_info = HostInfo::new(host_platform, host_architecture, None);
    let build_ctx = BuildContext::new_for_module(
        env,
        &cell_info,
        &buckconfig,
        &root_buckconfig,
        &host_info,
        PerFileTypeContext::Bzl(import_path),
        false,
    );

    let mut eval = Evaluator::new(env);
    eval.extra = Some(&build_ctx);
    eval.eval_module(ast, globals)
        .unwrap_or_else(|err| panic!("Failed interpreting `{}`. Error: `{}`", content, err))
}


/*
 * 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_core::soft_error;
use buck2_node::attrs::attr_type::any::AnyAttrType;
use buck2_node::attrs::attr_type::bool::BoolLiteral;
use buck2_node::attrs::attr_type::list::ListLiteral;
use buck2_node::attrs::attr_type::string::StringLiteral;
use buck2_node::attrs::attr_type::tuple::TupleLiteral;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use starlark::typing::Ty;
use starlark::values::dict::DictRef;
use starlark::values::list::ListRef;
use starlark::values::tuple::TupleRef;
use starlark::values::Value;

use crate::attrs::coerce::AttrTypeCoerce;

#[derive(Debug, thiserror::Error)]
enum AnyError {
    #[error("Cannot coerce value of type `{0}` to any: `{1}`")]
    CannotCoerce(&'static str, String),
}

fn to_literal(value: Value, ctx: &dyn AttrCoercionContext) -> anyhow::Result<CoercedAttr> {
    if value.is_none() {
        Ok(CoercedAttr::None)
    } else if let Some(x) = value.unpack_bool() {
        Ok(CoercedAttr::Bool(BoolLiteral(x)))
    } else if let Some(x) = value.unpack_i32() {
        Ok(CoercedAttr::Int(x))
    } else if let Some(x) = DictRef::from_value(value) {
        Ok(CoercedAttr::Dict(
            x.iter()
                .map(|(k, v)| Ok((to_literal(k, ctx)?, to_literal(v, ctx)?)))
                .collect::<anyhow::Result<_>>()?,
        ))
    } else if let Some(x) = TupleRef::from_value(value) {
        Ok(CoercedAttr::Tuple(TupleLiteral(
            ctx.intern_list(
                x.iter()
                    .map(|v| to_literal(v, ctx))
                    .collect::<anyhow::Result<Vec<_>>>()?,
            ),
        )))
    } else if let Some(x) = ListRef::from_value(value) {
        Ok(CoercedAttr::List(ListLiteral(
            ctx.intern_list(
                x.iter()
                    .map(|v| to_literal(v, ctx))
                    .collect::<anyhow::Result<Vec<_>>>()?,
            ),
        )))
    } else if let Some(s) = value.unpack_str() {
        Ok(CoercedAttr::String(StringLiteral(ctx.intern_str(s))))
    } else {
        soft_error!(
            "coerce_to_any",
            AnyError::CannotCoerce(value.get_type(), value.to_repr()).into()
        )?;
        Ok(CoercedAttr::String(StringLiteral(
            ctx.intern_str(&value.to_str()),
        )))
    }
}

impl AttrTypeCoerce for AnyAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        to_literal(value, ctx)
    }

    fn starlark_type(&self) -> Ty {
        Ty::Any
    }
}


/*
 * 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::Debug;
use std::mem;

use buck2_core::provider::label::ProvidersLabel;
use buck2_node::attrs::attr_type::arg::parser;
use buck2_node::attrs::attr_type::arg::parser::parse_macros;
use buck2_node::attrs::attr_type::arg::parser::ParsedMacro;
use buck2_node::attrs::attr_type::arg::ArgAttrType;
use buck2_node::attrs::attr_type::arg::MacroBase;
use buck2_node::attrs::attr_type::arg::QueryExpansion;
use buck2_node::attrs::attr_type::arg::StringWithMacrosPart;
use buck2_node::attrs::attr_type::arg::UnconfiguredMacro;
use buck2_node::attrs::attr_type::arg::UnconfiguredStringWithMacros;
use buck2_node::attrs::attr_type::arg::UnrecognizedMacro;
use buck2_node::attrs::attr_type::query::QueryAttrType;
use buck2_node::attrs::attr_type::query::QueryMacroBase;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use maplit::hashset;
use once_cell::sync::Lazy;
use starlark::typing::Ty;
use starlark::values::string::STRING_TYPE;
use starlark::values::Value;
use thiserror::Error;

use crate::attrs::coerce::attr_type::query::QueryAttrTypeExt;
use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

// These are the macros we haven't yet implemented yet, we should make sure not
// to try and resolve them to user defined macros with a target parameter,
// because some of them don't take a target.
// Taken from https://buck.build/function/string_parameter_macros.html.
static UNIMPLEMENTED_MACROS: Lazy<HashSet<&'static str>> =
    Lazy::new(|| hashset!["classpath_abi", "maven_coords", "output", "query_paths",]);

#[derive(Debug, Error)]
enum MacroError {
    #[error("Expected a single target label argument. Got `[{}]`", (.0).join(", "))]
    ExpectedSingleTargetArgument(Vec<String>),
    #[error("Incorrect number of args to macro `{0}` (had {1} args)")]
    InvalidNumberOfArgs(String, usize),
}

impl AttrTypeCoerce for ArgAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        let value = value
            .unpack_str()
            .ok_or_else(|| anyhow::anyhow!(CoercionError::type_error(STRING_TYPE, value)))?;

        let mut items = parse_macros(value)?.into_items();
        if let [parser::ArgItem::String(val)] = items.as_mut_slice() {
            // Specialize single-item StringWithMacros, which are in fact the common case.
            return Ok(CoercedAttr::Arg(UnconfiguredStringWithMacros::StringPart(
                ctx.intern_str(&mem::take(val)),
            )));
        }

        let mut parts = Vec::with_capacity(items.len());

        for item in items {
            match item {
                parser::ArgItem::String(val) => {
                    parts.push(StringWithMacrosPart::String(ctx.intern_str(&val)))
                }
                parser::ArgItem::Macro(ParsedMacro {
                    write_to_file,
                    macro_type,
                    args,
                }) => {
                    let part = match macro_type.as_ref() {
                        "location" | "location-platform" if args.len() == 1 => {
                            UnconfiguredMacro::new_location(ctx, args)?
                        }
                        "exe" => UnconfiguredMacro::new_exe(ctx, args, true)?,
                        "exe_target" => UnconfiguredMacro::new_exe(ctx, args, false)?,
                        "query_outputs" | "query_targets" | "query_targets_and_outputs" => {
                            UnconfiguredMacro::new_query(ctx, &macro_type, args)?
                        }
                        _ if args.is_empty() => {
                            UnconfiguredMacro::new_user_unkeyed_placeholder(macro_type)
                        }
                        name if (args.len() == 1 || args.len() == 2)
                            && !UNIMPLEMENTED_MACROS.contains(name) =>
                        {
                            UnconfiguredMacro::new_user_keyed_placeholder(ctx, macro_type, args)?
                        }
                        _ => UnconfiguredMacro::new_unrecognized(macro_type, args),
                    };
                    parts.push(StringWithMacrosPart::Macro(write_to_file, part));
                }
            }
        }

        Ok(CoercedAttr::Arg(UnconfiguredStringWithMacros::ManyParts(
            parts.into_boxed_slice(),
        )))
    }

    fn starlark_type(&self) -> Ty {
        Ty::string()
    }
}

/// A simple helper for a macro that accepts a single target argument (the most common case, by far).
fn get_single_target_arg(
    args: Vec<String>,
    ctx: &dyn AttrCoercionContext,
) -> anyhow::Result<ProvidersLabel> {
    if args.len() != 1 {
        return Err(anyhow::anyhow!(MacroError::ExpectedSingleTargetArgument(
            args
        )));
    }
    ctx.coerce_label(&args[0])
}

pub trait UnconfiguredMacroExt {
    fn new_location(
        ctx: &dyn AttrCoercionContext,
        args: Vec<String>,
    ) -> anyhow::Result<UnconfiguredMacro> {
        Ok(UnconfiguredMacro::Location(get_single_target_arg(
            args, ctx,
        )?))
    }

    fn new_exe(
        ctx: &dyn AttrCoercionContext,
        args: Vec<String>,
        exec_dep: bool,
    ) -> anyhow::Result<UnconfiguredMacro> {
        Ok(UnconfiguredMacro::Exe {
            label: get_single_target_arg(args, ctx)?,
            exec_dep,
        })
    }

    fn new_user_keyed_placeholder(
        ctx: &dyn AttrCoercionContext,
        var_name: String,
        mut args: Vec<String>,
    ) -> anyhow::Result<UnconfiguredMacro> {
        // args should've already been checked to have len == 1 or 2
        let arg = if args.len() == 2 {
            Some(args.pop().unwrap())
        } else {
            None
        };
        Ok(UnconfiguredMacro::UserKeyedPlaceholder(Box::new((
            var_name.into_boxed_str(),
            ctx.coerce_label(&args[0])?,
            arg.map(|a| a.into_boxed_str()),
        ))))
    }

    fn new_query(
        ctx: &dyn AttrCoercionContext,
        expansion_type: &str,
        args: Vec<String>,
    ) -> anyhow::Result<UnconfiguredMacro> {
        if args.is_empty() || args.len() > 2 {
            return Err(
                MacroError::InvalidNumberOfArgs(expansion_type.to_owned(), args.len()).into(),
            );
        }
        let mut args_iter = args.into_iter();
        let (query, separator) = match (args_iter.next(), args_iter.next()) {
            (Some(query), None) => (query, None),
            (separator, Some(query)) => (query, separator),
            _ => unreachable!(),
        };

        let expansion_type = match expansion_type {
            "query_targets" => QueryExpansion::Target,
            "query_outputs" => QueryExpansion::Output,
            "query_targets_and_outputs" => QueryExpansion::TargetAndOutput(separator),
            _ => panic!("invalid expansion type {}", expansion_type),
        };

        // TODO(cjhopman): errors when args aren't the right size (too many, or separator for non-separator query)

        Ok(MacroBase::Query(Box::new(QueryMacroBase {
            expansion_type,
            query: QueryAttrType::coerce(ctx, query)?,
        })))
    }

    fn new_user_unkeyed_placeholder(var_name: String) -> UnconfiguredMacro {
        UnconfiguredMacro::UserUnkeyedPlaceholder(var_name.into_boxed_str())
    }

    fn new_unrecognized(macro_type: String, args: Vec<String>) -> UnconfiguredMacro {
        UnconfiguredMacro::UnrecognizedMacro(Box::new(UnrecognizedMacro {
            macro_type: macro_type.into_boxed_str(),
            args: args.into_boxed_slice(),
        }))
    }
}

impl UnconfiguredMacroExt for UnconfiguredMacro {}

#[cfg(test)]
mod tests {
    use buck2_core::configuration::data::ConfigurationData;
    use buck2_core::target::label::TargetLabel;
    use buck2_node::attrs::attr_type::AttrType;
    use buck2_node::attrs::coerced_deps_collector::CoercedDepsCollector;
    use buck2_node::attrs::configurable::AttrIsConfigurable;
    use buck2_node::attrs::configuration_context::AttrConfigurationContext;
    use buck2_node::attrs::configured_info::ConfiguredAttrInfo;
    use buck2_node::attrs::display::AttrDisplayWithContextExt;
    use buck2_node::attrs::testing::configuration_ctx;
    use gazebo::prelude::SliceExt;
    use starlark::environment::GlobalsBuilder;
    use starlark::environment::Module;
    use starlark_map::smallset;

    use super::*;
    use crate::attrs::coerce::attr_type::AttrTypeExt;
    use crate::attrs::coerce::testing::coercion_ctx;
    use crate::attrs::coerce::testing::to_value;
    use crate::interpreter::selector::register_select;

    trait GetMacroDeps {
        type DepsType;
        fn get_deps(&self) -> anyhow::Result<Vec<Self::DepsType>>;
    }

    impl GetMacroDeps for UnconfiguredMacro {
        type DepsType = TargetLabel;
        fn get_deps(&self) -> anyhow::Result<Vec<Self::DepsType>> {
            let mut visitor = CoercedDepsCollector::new();
            self.traverse(&mut visitor)?;
            let CoercedDepsCollector {
                deps, exec_deps, ..
            } = visitor;
            Ok(deps.into_iter().chain(exec_deps.into_iter()).collect())
        }
    }

    #[test]
    fn test_concat() -> anyhow::Result<()> {
        let env = Module::new();
        let globals = GlobalsBuilder::extended().with(register_select).build();
        let attr = AttrType::arg(true);
        let value = to_value(
            &env,
            &globals,
            r#""$(exe //:foo) " + select({"DEFAULT": "$(location //:bar)"})"#,
        );

        let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
        let configured = coerced.configure(&attr, &configuration_ctx())?;
        assert_eq!(
            format!(
                r#""$(exe root//:foo ({})) $(location root//:bar ({}))""#,
                configuration_ctx().exec_cfg(),
                ConfigurationData::testing_new(),
            ),
            configured.as_display_no_ctx().to_string(),
        );

        Ok(())
    }

    #[test]
    fn test_location() -> anyhow::Result<()> {
        let ctx = coercion_ctx();
        let location = UnconfiguredMacro::new_location(&ctx, vec!["//some:target".to_owned()])?;
        let deps = location.get_deps()?.map(|t| t.to_string());
        assert_eq!(vec!["root//some:target".to_owned()], deps);

        let configured = location.configure(&configuration_ctx())?;

        if let MacroBase::Location(target) = &configured {
            let mut info = ConfiguredAttrInfo::new();
            configured.traverse(&mut info)?;
            assert_eq!(smallset![target.clone()], info.deps);
        } else {
            return Err(anyhow::anyhow!("Expected Location"));
        }

        Ok(())
    }

    #[test]
    fn test_exe() -> anyhow::Result<()> {
        let ctx = coercion_ctx();
        let exe = UnconfiguredMacro::new_exe(&ctx, vec!["//some:target".to_owned()], true)?;
        let deps = exe.get_deps()?.map(|t| t.to_string());
        assert_eq!(vec!["root//some:target".to_owned()], deps);
        assert_eq!("exe root//some:target", &exe.to_string());

        let config_ctx = configuration_ctx();
        let configured = exe.configure(&config_ctx)?;

        if let MacroBase::Exe { label, .. } = &configured {
            let mut info = ConfiguredAttrInfo::new();
            configured.traverse(&mut info)?;
            assert_eq!(label.cfg(), config_ctx.exec_cfg().cfg());
            assert_eq!(smallset![label.clone()], info.execution_deps);
            assert_eq!(smallset![], info.deps);
        } else {
            return Err(anyhow::anyhow!("Expected Exe"));
        }

        Ok(())
    }

    #[test]
    fn test_exe_target() -> anyhow::Result<()> {
        let ctx = coercion_ctx();
        let exe = UnconfiguredMacro::new_exe(&ctx, vec!["//some:target".to_owned()], false)?;
        let deps = exe.get_deps()?.map(|t| t.to_string());
        assert_eq!(vec!["root//some:target".to_owned()], deps);
        assert_eq!("exe_target root//some:target", &exe.to_string());

        let config_ctx = configuration_ctx();
        let configured = exe.configure(&config_ctx)?;

        if let MacroBase::Exe { label, .. } = &configured {
            let mut info = ConfiguredAttrInfo::new();
            configured.traverse(&mut info)?;
            assert_eq!(label.cfg(), config_ctx.cfg().cfg());
            assert_eq!(smallset![], info.execution_deps);
            assert_eq!(smallset![label.clone()], info.deps);
        } else {
            return Err(anyhow::anyhow!("Expected Exe"));
        }

        Ok(())
    }
}


/*
 * 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_node::attrs::attr_type::bool::BoolAttrType;
use buck2_node::attrs::attr_type::bool::BoolLiteral;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use starlark::typing::Ty;
use starlark::values::Value;

use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for BoolAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        _ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        match value.unpack_bool() {
            Some(s) => Ok(CoercedAttr::Bool(BoolLiteral(s))),
            None => Err(anyhow::anyhow!(CoercionError::type_error("bool", value))),
        }
    }

    fn starlark_type(&self) -> Ty {
        Ty::bool()
    }
}


/*
 * 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_core::provider::label::ProvidersLabel;
use buck2_core::provider::label::ProvidersName;
use buck2_node::attrs::attr_type::configuration_dep::ConfigurationDepAttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use starlark::typing::Ty;
use starlark::values::string::STRING_TYPE;
use starlark::values::Value;

use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for ConfigurationDepAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        let label = value
            .unpack_str()
            .ok_or_else(|| anyhow::anyhow!(CoercionError::type_error(STRING_TYPE, value)))?;

        let label = ctx.coerce_label(label)?;

        let (label, name) = label.into_parts();
        match name {
            ProvidersName::Default => Ok(CoercedAttr::ConfigurationDep(label)),
            _ => Err(CoercionError::UnexpectedSubTarget(ProvidersLabel::new(label, name)).into()),
        }
    }

    fn starlark_type(&self) -> Ty {
        Ty::string()
    }
}


/*
 * 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_node::attrs::attr_type::configured_dep::ExplicitConfiguredDepAttrType;
use buck2_node::attrs::attr_type::configured_dep::UnconfiguredExplicitConfiguredDep;
use buck2_node::attrs::attr_type::dep::DepAttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use dupe::Dupe;
use starlark::typing::Ty;
use starlark::values::string::STRING_TYPE;
use starlark::values::UnpackValue;
use starlark::values::Value;

use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for DepAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        let label = value
            .unpack_str()
            .ok_or_else(|| anyhow::anyhow!(CoercionError::type_error(STRING_TYPE, value)))?;

        let label = ctx.coerce_label(label)?;

        Ok(CoercedAttr::Dep(label))
    }

    fn starlark_type(&self) -> Ty {
        Ty::string()
    }
}

impl AttrTypeCoerce for ExplicitConfiguredDepAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        let (label_value, platform_value): (Value, Value) = UnpackValue::unpack_value(value)
            .ok_or_else(|| {
                anyhow::anyhow!(CoercionError::type_error(
                    "Tuple must be a pair of two strings",
                    value,
                ))
            })?;

        let label_string = label_value
            .unpack_str()
            .ok_or_else(|| anyhow::anyhow!(CoercionError::type_error(STRING_TYPE, value)))?;
        let label = ctx.coerce_label(label_string)?;

        let platform_string = platform_value
            .unpack_str()
            .ok_or_else(|| anyhow::anyhow!(CoercionError::type_error(STRING_TYPE, value)))?;
        let platform = ctx.coerce_target(platform_string)?;

        Ok(CoercedAttr::ExplicitConfiguredDep(Box::new(
            UnconfiguredExplicitConfiguredDep {
                attr_type: self.dupe(),
                label,
                platform,
            },
        )))
    }

    fn starlark_type(&self) -> Ty {
        Ty::tuple2(Ty::string(), Ty::string())
    }
}


/*
 * 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::cmp::Ordering;

use buck2_node::attrs::attr_type::dict::DictAttrType;
use buck2_node::attrs::attr_type::dict::DictLiteral;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use starlark::typing::Ty;
use starlark::values::dict::Dict;
use starlark::values::dict::DictRef;
use starlark::values::Value;

use crate::attrs::coerce::attr_type::AttrTypeExt;
use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for DictAttrType {
    fn coerce_item(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        if let Some(dict) = DictRef::from_value(value) {
            let mut res = Vec::with_capacity(dict.len());
            if self.sorted {
                // First sort the values
                let mut items = dict.iter().collect::<Vec<_>>();
                // If two things are incompatible, just return Eq. The resulting order is undefined, but safely undefined.
                items.sort_by(|a, b| a.0.compare(b.0).unwrap_or(Ordering::Equal));

                for (k, v) in items {
                    res.push((
                        self.key.coerce(configurable, ctx, k)?,
                        self.value.coerce(configurable, ctx, v)?,
                    ));
                }
            } else {
                for (k, v) in dict.iter() {
                    res.push((
                        self.key.coerce(configurable, ctx, k)?,
                        self.value.coerce(configurable, ctx, v)?,
                    ));
                }
            }
            Ok(CoercedAttr::Dict(DictLiteral(ctx.intern_dict(res))))
        } else {
            Err(anyhow::anyhow!(CoercionError::type_error(
                Dict::TYPE,
                value,
            )))
        }
    }

    fn starlark_type(&self) -> Ty {
        Ty::dict(self.key.starlark_type(), self.value.starlark_type())
    }
}


/*
 * 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_node::attrs::attr_type::enumeration::EnumAttrType;
use buck2_node::attrs::attr_type::string::StringLiteral;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use dupe::Dupe;
use starlark::typing::Ty;
use starlark::values::string::STRING_TYPE;
use starlark::values::Value;

use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for EnumAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        _ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        match value.unpack_str() {
            Some(s) => {
                // Enum names in Buck can be specified upper or lower case,
                // so we normalise them to lowercase to make rule implementations easier
                let s = s.to_lowercase();
                if let Some(s) = self.variants.get(s.as_str()) {
                    Ok(CoercedAttr::EnumVariant(StringLiteral(s.dupe())))
                } else {
                    let wanted = self
                        .variants
                        .iter()
                        .map(|x| x.as_str().to_owned())
                        .collect();
                    Err(CoercionError::InvalidEnumVariant(s, wanted).into())
                }
            }
            None => Err(CoercionError::type_error(STRING_TYPE, value).into()),
        }
    }

    fn starlark_type(&self) -> Ty {
        Ty::string()
    }
}


/*
 * 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_node::attrs::attr_type::int::IntAttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use starlark::typing::Ty;
use starlark::values::Value;

use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for IntAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        _ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        match value.unpack_i32() {
            Some(x) => Ok(CoercedAttr::Int(x)),
            None => Err(anyhow::anyhow!(CoercionError::type_error("int", value))),
        }
    }

    fn starlark_type(&self) -> Ty {
        Ty::int()
    }
}


/*
 * 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_node::attrs::attr_type::label::LabelAttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use starlark::typing::Ty;
use starlark::values::string::STRING_TYPE;
use starlark::values::Value;

use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for LabelAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        let label = value
            .unpack_str()
            .ok_or_else(|| CoercionError::type_error(STRING_TYPE, value))?;

        let label = ctx.coerce_label(label)?;

        Ok(CoercedAttr::Label(label))
    }

    fn starlark_type(&self) -> Ty {
        Ty::string()
    }
}


/*
 * 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_node::attrs::attr_type::list::ListAttrType;
use buck2_node::attrs::attr_type::list::ListLiteral;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use gazebo::prelude::*;
use starlark::typing::Ty;
use starlark::values::list::ListRef;
use starlark::values::tuple::TupleRef;
use starlark::values::Value;

use crate::attrs::coerce::attr_type::AttrTypeExt;
use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for ListAttrType {
    fn coerce_item(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        let list = coerce_list(value)?;
        Ok(CoercedAttr::List(ListLiteral(ctx.intern_list(
            list.try_map(|v| (self.inner).coerce(configurable, ctx, *v))?,
        ))))
    }

    fn starlark_type(&self) -> Ty {
        Ty::list(self.inner.starlark_type())
    }
}

pub(crate) fn coerce_list<'v>(value: Value<'v>) -> anyhow::Result<&'v [Value<'v>]> {
    if let Some(list) = ListRef::from_value(value) {
        Ok(list.content())
    } else if let Some(list) = TupleRef::from_value(value) {
        Ok(list.content())
    } else {
        Err(CoercionError::type_error(ListRef::TYPE, value).into())
    }
}


/*
 * 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_node::attrs::attr_type::AttrType;
use buck2_node::attrs::attr_type::AttrTypeInner;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use starlark::typing::Ty;
use starlark::values::Value;

use crate::attrs::coerce::coerced_attr::CoercedAttrExr;
use crate::attrs::coerce::AttrTypeCoerce;

pub mod any;
pub mod arg;
pub mod bool;
pub mod configuration_dep;
pub mod dep;
mod dict;
mod enumeration;
pub mod int;
pub mod label;
mod list;
mod one_of;
mod option;
pub mod query;
pub mod source;
pub mod split_transition_dep;
mod string;
mod tuple;
mod visibility;
mod within_view;

pub trait AttrTypeExt {
    fn this(&self) -> &AttrType;

    fn coerce_item(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        self.this().0.coerce_item(configurable, ctx, value)
    }

    fn coerce(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        self.coerce_with_default(configurable, ctx, value, None)
    }

    fn coerce_with_default(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
        default: Option<&CoercedAttr>,
    ) -> anyhow::Result<CoercedAttr> {
        CoercedAttr::coerce(self.this(), configurable, ctx, value, default)
    }

    fn starlark_type(&self) -> Ty {
        self.this().0.starlark_type()
    }
}

impl AttrTypeExt for AttrType {
    fn this(&self) -> &AttrType {
        self
    }
}

pub trait AttrTypeInnerExt {
    fn coerce_item(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr>;

    fn starlark_type(&self) -> Ty;
}

impl AttrTypeInnerExt for AttrTypeInner {
    fn coerce_item(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        match self {
            Self::Any(x) => x.coerce_item(configurable, ctx, value),
            Self::Arg(x) => x.coerce_item(configurable, ctx, value),
            Self::Bool(x) => x.coerce_item(configurable, ctx, value),
            Self::Int(x) => x.coerce_item(configurable, ctx, value),
            Self::Dep(x) => x.coerce_item(configurable, ctx, value),
            Self::Dict(x) => x.coerce_item(configurable, ctx, value),
            Self::List(x) => x.coerce_item(configurable, ctx, value),
            Self::Tuple(x) => x.coerce_item(configurable, ctx, value),
            Self::OneOf(x) => x.coerce_item(configurable, ctx, value),
            Self::Option(x) => x.coerce_item(configurable, ctx, value),
            Self::Source(x) => x.coerce_item(configurable, ctx, value),
            Self::String(x) => x.coerce_item(configurable, ctx, value),
            Self::Query(x) => x.coerce_item(configurable, ctx, value),
            Self::ConfigurationDep(x) => x.coerce_item(configurable, ctx, value),
            Self::ConfiguredDep(x) => x.coerce_item(configurable, ctx, value),
            Self::Enum(x) => x.coerce_item(configurable, ctx, value),
            Self::SplitTransitionDep(x) => x.coerce_item(configurable, ctx, value),
            Self::Label(x) => x.coerce_item(configurable, ctx, value),
            Self::Visibility(x) => x.coerce_item(configurable, ctx, value),
            Self::WithinView(x) => x.coerce_item(configurable, ctx, value),
        }
    }

    /// Returns a starlark-compatible typing string, e.g. `[str.type]` for values coerced by this
    /// attr.
    fn starlark_type(&self) -> Ty {
        match self {
            AttrTypeInner::Any(x) => x.starlark_type(),
            AttrTypeInner::Arg(x) => x.starlark_type(),
            AttrTypeInner::ConfigurationDep(x) => x.starlark_type(),
            AttrTypeInner::ConfiguredDep(x) => x.starlark_type(),
            AttrTypeInner::Bool(x) => x.starlark_type(),
            AttrTypeInner::Int(x) => x.starlark_type(),
            AttrTypeInner::Dep(x) => x.starlark_type(),
            AttrTypeInner::Dict(x) => x.starlark_type(),
            AttrTypeInner::Enum(x) => x.starlark_type(),
            AttrTypeInner::List(x) => x.starlark_type(),
            AttrTypeInner::Tuple(x) => x.starlark_type(),
            AttrTypeInner::OneOf(x) => x.starlark_type(),
            AttrTypeInner::Option(x) => x.starlark_type(),
            AttrTypeInner::Query(x) => x.starlark_type(),
            AttrTypeInner::Source(x) => x.starlark_type(),
            AttrTypeInner::String(x) => x.starlark_type(),
            AttrTypeInner::SplitTransitionDep(x) => x.starlark_type(),
            AttrTypeInner::Label(x) => x.starlark_type(),
            AttrTypeInner::Visibility(x) => x.starlark_type(),
            AttrTypeInner::WithinView(x) => x.starlark_type(),
        }
    }
}


/*
 * 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_node::attrs::attr_type::one_of::OneOfAttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use gazebo::prelude::SliceExt;
use starlark::typing::Ty;
use starlark::values::Value;

use crate::attrs::coerce::attr_type::AttrTypeExt;
use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for OneOfAttrType {
    fn coerce_item(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        let mut errs = Vec::new();
        // Bias towards the start of the list - try and use success/failure from first in preference
        for (i, x) in self.xs.iter().enumerate() {
            match x.coerce_item(configurable, ctx, value) {
                Ok(v) => return Ok(CoercedAttr::OneOf(Box::new(v), i as u32)),
                Err(e) => {
                    // TODO(nga): anyhow error creation is expensive.
                    errs.push(e)
                }
            }
        }
        Err(CoercionError::one_of_many(errs))
    }

    fn starlark_type(&self) -> Ty {
        Ty::unions(self.xs.map(|x| x.starlark_type()))
    }
}


/*
 * 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_node::attrs::attr_type::option::OptionAttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use starlark::typing::Ty;
use starlark::values::Value;

use crate::attrs::coerce::attr_type::AttrTypeExt;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for OptionAttrType {
    fn coerce_item(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        if value.is_none() {
            Ok(CoercedAttr::None)
        } else {
            Ok(self.inner.coerce_item(configurable, ctx, value)?)
        }
    }

    fn starlark_type(&self) -> Ty {
        Ty::union2(Ty::none(), self.inner.starlark_type())
    }
}


/*
 * 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::BTreeMap;

use buck2_core::provider::label::ProvidersLabel;
use buck2_node::attrs::attr_type::query::QueryAttr;
use buck2_node::attrs::attr_type::query::QueryAttrBase;
use buck2_node::attrs::attr_type::query::QueryAttrType;
use buck2_node::attrs::attr_type::query::ResolvedQueryLiterals;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use buck2_node::provider_id_set::ProviderIdSet;
use buck2_query::query::syntax::simple::functions::QueryLiteralVisitor;
use buck2_query_parser::parse_expr;
use starlark::typing::Ty;
use starlark::values::string::STRING_TYPE;
use starlark::values::Value;

use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

pub trait QueryAttrTypeExt {
    fn coerce(
        ctx: &dyn AttrCoercionContext,
        query: String,
    ) -> anyhow::Result<QueryAttrBase<ProvidersLabel>>;
}

impl QueryAttrTypeExt for QueryAttrType {
    fn coerce(
        ctx: &dyn AttrCoercionContext,
        query: String,
    ) -> anyhow::Result<QueryAttrBase<ProvidersLabel>> {
        // parse the expr to do validation and to extract the literals.
        let parsed_query = parse_expr(&query)?;

        struct Collector<'a> {
            ctx: &'a dyn AttrCoercionContext,
            literals: BTreeMap<String, ProvidersLabel>,
        }

        impl QueryLiteralVisitor for Collector<'_> {
            fn target_pattern(&mut self, pattern: &str) -> anyhow::Result<()> {
                // TODO(cjhopman): We could probably parse the pattern first. This would likely at least give a better error message when the query contains a non-literal target pattern.
                // We could optimize this to do less work for duplicates, but it's generally not helpful.
                if pattern == "$declared_deps" || pattern == "$declared" {
                    return Ok(());
                }
                let label = self.ctx.coerce_label(pattern)?;

                /*
                if label.name() != &ProvidersName::Default {
                    return Err(
                        MacroError::ProviderNameUnsupportedInQuery(pattern.to_owned()).into(),
                    );
                }*/

                self.literals.insert(pattern.to_owned(), label);
                Ok(())
            }
        }

        let mut collector = Collector {
            ctx,
            literals: BTreeMap::new(),
        };

        ctx.visit_query_function_literals(&mut collector, &parsed_query, &query)?;

        Ok(QueryAttrBase {
            query,
            resolved_literals: ResolvedQueryLiterals(collector.literals),
        })
    }
}

impl AttrTypeCoerce for QueryAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        let query = value
            .unpack_str()
            .ok_or_else(|| CoercionError::type_error(STRING_TYPE, value))?;

        Ok(CoercedAttr::Query(Box::new(QueryAttr {
            query: Self::coerce(ctx, query.to_owned())?,
            providers: ProviderIdSet::EMPTY,
        })))
    }

    fn starlark_type(&self) -> Ty {
        Ty::string()
    }
}


/*
 * 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_node::attrs::attr_type::source::SourceAttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use gazebo::prelude::*;
use starlark::typing::Ty;
use starlark::values::string::STRING_TYPE;
use starlark::values::Value;

use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

#[derive(Debug, thiserror::Error)]
enum SourceLabelCoercionError {
    #[error(
        "Couldn't coerce `{0}` as a source.\n  Error when treated as a target: {1:#}\n  Error when treated as a path: {2:#}"
    )]
    CoercionFailed(String, anyhow::Error, anyhow::Error),
}

/// Try cleaning up irrelevant details users often type
fn cleanup_path(value: &str) -> &str {
    let value = value.trim_start_match("./");
    let value = value.trim_end_match("/");
    if value == "." { "" } else { value }
}

impl AttrTypeCoerce for SourceAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        let source_label = value
            .unpack_str()
            .ok_or_else(|| anyhow::anyhow!(CoercionError::type_error(STRING_TYPE, value)))?;
        match ctx.coerce_label(source_label) {
            Ok(label) => Ok(CoercedAttr::SourceLabel(label)),
            Err(label_err) => {
                match ctx.coerce_path(cleanup_path(source_label), self.allow_directory) {
                    Ok(path) => Ok(CoercedAttr::SourceFile(path)),
                    Err(path_err) => Err(SourceLabelCoercionError::CoercionFailed(
                        value.to_str(),
                        label_err,
                        path_err,
                    )
                    .into()),
                }
            }
        }
    }

    fn starlark_type(&self) -> Ty {
        Ty::string()
    }
}


/*
 * 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_node::attrs::attr_type::split_transition_dep::SplitTransitionDepAttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use starlark::typing::Ty;
use starlark::values::string::STRING_TYPE;
use starlark::values::Value;

use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for SplitTransitionDepAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        let label = value
            .unpack_str()
            .ok_or_else(|| CoercionError::type_error(STRING_TYPE, value))?;

        let label = ctx.coerce_label(label)?;

        Ok(CoercedAttr::SplitTransitionDep(label))
    }

    fn starlark_type(&self) -> Ty {
        Ty::string()
    }
}


/*
 * 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_node::attrs::attr_type::string::StringAttrType;
use buck2_node::attrs::attr_type::string::StringLiteral;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use starlark::typing::Ty;
use starlark::values::string::STRING_TYPE;
use starlark::values::Value;

use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for StringAttrType {
    fn coerce_item(
        &self,
        _configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        match value.unpack_str() {
            Some(s) => Ok(CoercedAttr::String(StringLiteral(ctx.intern_str(s)))),
            None => Err(anyhow::anyhow!(CoercionError::type_error(
                STRING_TYPE,
                value
            ))),
        }
    }

    fn starlark_type(&self) -> Ty {
        Ty::string()
    }
}


/*
 * 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::iter;

use buck2_node::attrs::attr_type::tuple::TupleAttrType;
use buck2_node::attrs::attr_type::tuple::TupleLiteral;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use dupe::IterDupedExt;
use gazebo::prelude::SliceExt;
use starlark::typing::Ty;
use starlark::values::list::ListRef;
use starlark::values::tuple::TupleRef;
use starlark::values::Value;

use crate::attrs::coerce::attr_type::AttrTypeExt;
use crate::attrs::coerce::error::CoercionError;
use crate::attrs::coerce::AttrTypeCoerce;

impl AttrTypeCoerce for TupleAttrType {
    fn coerce_item(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        let coerce = |value, items: &[Value]| {
            // Use comparison rather than equality below. If the tuple is too short,
            // it is implicitly extended using None.
            if items.len() <= self.xs.len() {
                let mut res = Vec::with_capacity(self.xs.len());
                for (c, v) in self
                    .xs
                    .iter()
                    .zip(items.iter().duped().chain(iter::repeat(Value::new_none())))
                {
                    res.push(c.coerce(configurable, ctx, v)?);
                }
                Ok(CoercedAttr::Tuple(TupleLiteral(ctx.intern_list(res))))
            } else {
                Err(anyhow::anyhow!(CoercionError::type_error(
                    &format!("Tuple of at most length {}", self.xs.len()),
                    value
                )))
            }
        };
        if let Some(list) = TupleRef::from_value(value) {
            coerce(value, list.content())
        } else if let Some(list) = ListRef::from_value(value) {
            coerce(value, list.content())
        } else {
            Err(anyhow::anyhow!(CoercionError::type_error(
                TupleRef::TYPE,
                value,
            )))
        }
    }

    fn starlark_type(&self) -> Ty {
        Ty::Tuple(self.xs.map(|x| x.starlark_type()))
    }
}


/*
 * 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_node::attrs::attr_type::visibility::VisibilityAttrType;
use buck2_node::attrs::attr_type::AttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use buck2_node::visibility::VisibilityPattern;
use buck2_node::visibility::VisibilityWithinViewBuilder;
use starlark::typing::Ty;
use starlark::values::Value;

use crate::attrs::coerce::attr_type::list::coerce_list;
use crate::attrs::coerce::attr_type::AttrTypeExt;
use crate::attrs::coerce::AttrTypeCoerce;
use crate::interpreter::selector::StarlarkSelector;

#[derive(Debug, thiserror::Error)]
enum VisibilityAttrTypeCoerceError {
    #[error("Visibility attribute is not configurable (internal error)")]
    AttrTypeNotConfigurable,
    #[error("Visibility must be a list of string, got `{0}`")]
    WrongType(String),
    #[error("Visibility attribute is not configurable (i.e. cannot use `select()`): `{0}`")]
    NotConfigurable(String),
}

impl AttrTypeCoerce for VisibilityAttrType {
    fn coerce_item(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        if configurable == AttrIsConfigurable::Yes {
            return Err(VisibilityAttrTypeCoerceError::AttrTypeNotConfigurable.into());
        }
        Ok(CoercedAttr::Visibility(
            parse_visibility_with_view(ctx, value)?.build_visibility(),
        ))
    }

    fn starlark_type(&self) -> Ty {
        AttrType::list(AttrType::string()).starlark_type()
    }
}

pub(crate) fn parse_visibility_with_view(
    ctx: &dyn AttrCoercionContext,
    attr: Value,
) -> anyhow::Result<VisibilityWithinViewBuilder> {
    let list = match coerce_list(attr) {
        Ok(list) => list,
        Err(e) => {
            if StarlarkSelector::from_value(attr).is_some() {
                return Err(VisibilityAttrTypeCoerceError::NotConfigurable(attr.to_repr()).into());
            }
            return Err(e);
        }
    };

    let mut builder = VisibilityWithinViewBuilder::with_capacity(list.len());
    for item in list {
        let Some(item) = item.unpack_str() else {
            if StarlarkSelector::from_value(*item).is_some() {
                return Err(VisibilityAttrTypeCoerceError::NotConfigurable(
                    attr.to_repr(),
                )
                .into());
            }
            return Err(VisibilityAttrTypeCoerceError::WrongType(
                attr.to_repr(),
            ).into());
        };

        if item == VisibilityPattern::PUBLIC {
            // TODO(cjhopman): We should probably enforce that this is the only entry.
            builder.add_public();
        } else {
            builder.add(VisibilityPattern(ctx.coerce_target_pattern(item)?));
        }
    }
    Ok(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 buck2_node::attrs::attr_type::visibility::VisibilityAttrType;
use buck2_node::attrs::attr_type::within_view::WithinViewAttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use starlark::typing::Ty;
use starlark::values::Value;

use crate::attrs::coerce::attr_type::visibility::parse_visibility_with_view;
use crate::attrs::coerce::AttrTypeCoerce;

#[derive(Debug, thiserror::Error)]
enum WithinViewAttrTypeCoerceError {
    #[error("Within view attribute is not configurable (internal error)")]
    AttrTypeNotConfigurable,
}

impl AttrTypeCoerce for WithinViewAttrType {
    fn coerce_item(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &dyn AttrCoercionContext,
        value: Value,
    ) -> anyhow::Result<CoercedAttr> {
        if configurable == AttrIsConfigurable::Yes {
            return Err(WithinViewAttrTypeCoerceError::AttrTypeNotConfigurable.into());
        }
        Ok(CoercedAttr::WithinView(
            parse_visibility_with_view(ctx, value)?.build_within_view(),
        ))
    }

    fn starlark_type(&self) -> Ty {
        // Starlark type of the attribute is the same.
        VisibilityAttrType.starlark_type()
    }
}


/*
 * 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::fmt;

use buck2_common::legacy_configs::view::LegacyBuckConfigView;
use hashbrown::raw::RawTable;
use starlark::collections::Hashed;
use starlark::environment::Module;
use starlark::values::FrozenStringValue;
use starlark::values::StringValue;

struct BuckConfigEntry {
    section: Hashed<String>,
    key: Hashed<String>,
    value: Option<FrozenStringValue>,
}

/// Version of cell buckconfig optimized for fast query from `read_config` Starlark function.
pub(crate) struct LegacyBuckConfigForStarlark<'a> {
    module: &'a Module,
    buckconfig: &'a (dyn LegacyBuckConfigView + 'a),
    /// Hash map by `(section, key)` pair, so we do one table lookup per request.
    /// So we hash the `key` even if the section does not exist,
    /// but this is practically not an issue, because keys usually come with cached hash.
    cache: RefCell<RawTable<BuckConfigEntry>>,
}

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

impl<'a> LegacyBuckConfigForStarlark<'a> {
    // `section` or `key` 32 bit hashes are well swizzled,
    // but concatenation of them into 64 bit integer is not.
    // This function tries to fix that.
    fn mix_hashes(a: u32, b: u32) -> u64 {
        fn murmur3_mix64(mut x: u64) -> u64 {
            x ^= x >> 33;
            x = x.wrapping_mul(0xff51afd7ed558ccd);
            x ^= x >> 33;
            x = x.wrapping_mul(0xc4ceb9fe1a85ec53);
            x ^= x >> 33;
            x
        }

        murmur3_mix64(((a as u64) << 32) | (b as u64))
    }

    /// Constructor.
    pub(crate) fn new(
        module: &'a Module,
        buckconfig: &'a (dyn LegacyBuckConfigView + 'a),
    ) -> LegacyBuckConfigForStarlark<'a> {
        LegacyBuckConfigForStarlark {
            module,
            buckconfig,
            cache: RefCell::new(RawTable::new()),
        }
    }

    fn get_impl(
        &self,
        section: Hashed<&str>,
        key: Hashed<&str>,
    ) -> anyhow::Result<Option<FrozenStringValue>> {
        let hash = Self::mix_hashes(section.hash().get(), key.hash().get());
        let mut cache = self.cache.borrow_mut();
        if let Some(e) = cache.get(hash, |e| {
            e.section.key() == section.key() && e.key.as_str() == *key.key()
        }) {
            return Ok(e.value);
        }

        let value = self
            .buckconfig
            .get(section.key(), key.key())?
            .map(|v| self.module.frozen_heap().alloc_str(&v));

        cache.insert(
            hash,
            BuckConfigEntry {
                section: Hashed::new_unchecked(section.hash(), (*section.key()).to_owned()),
                key: Hashed::new_unchecked(key.hash(), (*key.key()).to_owned()),
                value,
            },
            |e| Self::mix_hashes(e.section.hash().get(), e.key.hash().get()),
        );

        Ok(value)
    }

    /// Find the buckconfig entry.
    pub(crate) fn get(
        &self,
        section: StringValue,
        key: StringValue,
    ) -> anyhow::Result<Option<FrozenStringValue>> {
        // Note here we reuse the hashes of `section` and `key`,
        // if `read_config` is called repeatedly with the same constant arguments:
        // `StringValue` caches the hashes.
        self.get_impl(section.get_hashed_str(), key.get_hashed_str())
    }
}


/*
 * 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::Debug;

use buck2_common::legacy_configs::view::LegacyBuckConfigView;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::bzl::ImportPath;
use buck2_core::cells::build_file_cell::BuildFileCell;
use buck2_core::cells::CellResolver;
use buck2_core::package::PackageLabel;
use buck2_interpreter::build_context::STARLARK_PATH_FROM_BUILD_CONTEXT;
use buck2_interpreter::file_type::StarlarkFileType;
use buck2_interpreter::path::BxlFilePath;
use buck2_interpreter::path::PackageFilePath;
use buck2_interpreter::path::StarlarkModulePath;
use buck2_interpreter::path::StarlarkPath;
use starlark::any::ProvidesStaticType;
use starlark::environment::Module;
use starlark::eval::Evaluator;
use thiserror::Error;

use crate::interpreter::buckconfig::LegacyBuckConfigForStarlark;
use crate::interpreter::cell_info::InterpreterCellInfo;
use crate::interpreter::functions::host_info::HostInfo;
use crate::interpreter::module_internals::ModuleInternals;
use crate::super_package::eval_ctx::PackageFileEvalCtx;

#[derive(Error, Debug)]
enum BuildContextError {
    #[error(
        "This function is unavailable during analysis (usual solution is to place the information on a toolchain)"
    )]
    UnavailableDuringAnalysis,
    #[error("`{0}()` can only be called from a build file context; current file context is {1:?}")]
    NotBuildFile(String, StarlarkFileType),
    #[error("Expecting build file context, but current file context is {0:?}")]
    NotBuildFileNoFunction(StarlarkFileType),
    #[error(
        "`{0}()` can only be called from a package file context; current file context is {1:?}"
    )]
    NotPackageFile(String, StarlarkFileType),
    #[error("Expecting package file context, but current file context is {0:?}")]
    NotPackageFileNoFunction(StarlarkFileType),
    #[error("Package can only be fetched from a build file")]
    PackageOnlyFromBuildFile,
}

#[derive(Debug)]
pub(crate) enum PerFileTypeContext {
    /// Context for evaluating `BUCK` files.
    Build(BuildFilePath, ModuleInternals),
    /// Context for evaluating `PACKAGE` files.
    Package(PackageFilePath, PackageFileEvalCtx),
    Bzl(ImportPath),
    Bxl(BxlFilePath),
}

impl PerFileTypeContext {
    pub(crate) fn starlark_path(&self) -> StarlarkPath {
        match self {
            PerFileTypeContext::Build(path, _) => StarlarkPath::BuildFile(path),
            PerFileTypeContext::Package(path, _) => StarlarkPath::PackageFile(path),
            PerFileTypeContext::Bzl(path) => StarlarkPath::LoadFile(path),
            PerFileTypeContext::Bxl(path) => StarlarkPath::BxlFile(path),
        }
    }

    pub(crate) fn file_type(&self) -> StarlarkFileType {
        self.starlark_path().file_type()
    }

    pub(crate) fn require_build(&self, function_name: &str) -> anyhow::Result<&ModuleInternals> {
        match self {
            PerFileTypeContext::Build(_, internals) => Ok(internals),
            x => {
                Err(BuildContextError::NotBuildFile(function_name.to_owned(), x.file_type()).into())
            }
        }
    }

    pub(crate) fn into_build(self) -> anyhow::Result<ModuleInternals> {
        match self {
            PerFileTypeContext::Build(_, internals) => Ok(internals),
            x => Err(BuildContextError::NotBuildFileNoFunction(x.file_type()).into()),
        }
    }

    pub(crate) fn require_package_file(
        &self,
        function_name: &str,
    ) -> anyhow::Result<&PackageFileEvalCtx> {
        match self {
            PerFileTypeContext::Package(_, ctx) => Ok(ctx),
            x => Err(
                BuildContextError::NotPackageFile(function_name.to_owned(), x.file_type()).into(),
            ),
        }
    }

    pub(crate) fn into_package_file(self) -> anyhow::Result<PackageFileEvalCtx> {
        match self {
            PerFileTypeContext::Package(_, ctx) => Ok(ctx),
            x => Err(BuildContextError::NotPackageFileNoFunction(x.file_type()).into()),
        }
    }

    pub(crate) fn for_module(path: StarlarkModulePath) -> PerFileTypeContext {
        match path {
            StarlarkModulePath::LoadFile(bzl) => PerFileTypeContext::Bzl(bzl.clone()),
            StarlarkModulePath::BxlFile(bxl) => PerFileTypeContext::Bxl(bxl.clone()),
        }
    }
}

/// Buck-specific information exposed to the starlark environment via the context's extra field.
/// This would include things like the current cell or package name or the package listing. It's
/// used for quite a few other things, including recording declared rules.
///
/// Many of the functions available in build files reach out to this as part of
/// their implementation.
#[derive(ProvidesStaticType, Debug)]
pub struct BuildContext<'a> {
    /// The CellInfo for the top-level module being interpreted. Note that this
    /// is not necessarily the same as the file currently being processed
    /// (i.e. if `cell1//BUCK` loads `cell2//defs.bzl` the cell info when
    /// loading `cell2//defs.bzl` will be based on `cell1`).
    ///
    /// This cell_info is used only for things that should be resolved in the
    /// context of the top-level module. The most important of these is the
    /// `read_config()` implementation.
    ///
    /// For example of where this isn't used, it is not used for resolving
    /// `load()` statements.
    cell_info: &'a InterpreterCellInfo,

    /// Current cell file buckconfig.
    pub(crate) buckconfig: LegacyBuckConfigForStarlark<'a>,
    /// Buckconfig of the root cell.
    pub(crate) root_buckconfig: LegacyBuckConfigForStarlark<'a>,

    pub host_info: &'a HostInfo,

    /// Context specific to type type.
    pub(crate) additional: PerFileTypeContext,

    /// When true, rule function is no-op.
    pub ignore_attrs_for_profiling: bool,
}

impl<'a> BuildContext<'a> {
    /// Create a build context for the given module.
    pub(crate) fn new_for_module(
        module: &'a Module,
        cell_info: &'a InterpreterCellInfo,
        buckconfig: &'a (dyn LegacyBuckConfigView + 'a),
        root_buckconfig: &'a (dyn LegacyBuckConfigView + 'a),
        host_info: &'a HostInfo,
        additional: PerFileTypeContext,
        ignore_attrs_for_profiling: bool,
    ) -> BuildContext<'a> {
        let buckconfig = LegacyBuckConfigForStarlark::new(module, buckconfig);
        let root_buckconfig = LegacyBuckConfigForStarlark::new(module, root_buckconfig);
        BuildContext {
            cell_info,
            buckconfig,
            root_buckconfig,
            host_info,
            additional,
            ignore_attrs_for_profiling,
        }
    }

    pub fn from_context<'v>(eval: &Evaluator<'v, 'a>) -> anyhow::Result<&'a BuildContext<'a>> {
        match eval.extra {
            None => Err(BuildContextError::UnavailableDuringAnalysis.into()),
            Some(extra) => Ok(extra
                .downcast_ref::<BuildContext>()
                .unwrap_or_else(|| panic!("Unable to access context extra. Wrong type."))),
        }
    }

    pub(crate) fn cell_info(&self) -> &InterpreterCellInfo {
        self.cell_info
    }

    pub fn build_file_cell(&self) -> BuildFileCell {
        self.cell_info.name()
    }

    pub fn cell_resolver(&self) -> &CellResolver {
        self.cell_info.cell_resolver()
    }

    pub fn require_package(&self) -> anyhow::Result<PackageLabel> {
        match &self.additional {
            PerFileTypeContext::Build(path, _) => Ok(path.package()),
            _ => Err(BuildContextError::PackageOnlyFromBuildFile.into()),
        }
    }

    pub fn starlark_path(&self) -> StarlarkPath {
        self.additional.starlark_path()
    }
}

pub(crate) fn init_starlark_path_from_build_context() {
    STARLARK_PATH_FROM_BUILD_CONTEXT
        .init(|eval| Ok(BuildContext::from_context(eval)?.starlark_path()))
}

/// Arbitrary object made available to the execution context. Converted to
/// EvalResult at the end of interpreting
impl ModuleInternals {
    /// Try to get this inner context from the `ctx.extra` property.
    pub fn from_context<'a>(ctx: &'a Evaluator, function_name: &str) -> anyhow::Result<&'a Self> {
        BuildContext::from_context(ctx)?
            .additional
            .require_build(function_name)
    }
}


/*
 * 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::environment::LibraryExtension;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::list::AllocList;
use starlark::values::list::ListOf;
use starlark::values::ValueOfUnchecked;

use crate::interpreter::build_context::BuildContext;
use crate::interpreter::functions::dedupe::register_dedupe;
use crate::interpreter::functions::sha256::register_sha256;
use crate::interpreter::globspec::GlobSpec;
use crate::interpreter::module_internals::ModuleInternals;
use crate::interpreter::selector::register_select;

#[starlark_module]
pub fn native_module(builder: &mut GlobalsBuilder) {
    /// The `glob()` function specifies a set of files using patterns.
    /// Only available from `BUCK` files.
    ///
    /// A typical `glob` call looks like:
    ///
    /// ```python
    /// glob(["foo/**/*.h"])
    /// ```
    ///
    /// This call will match all header files in the `foo` directory, recursively.
    ///
    /// You can also pass a named `exclude` parameter to remove files matching a pattern:
    ///
    /// ```python
    /// glob(["foo/**/*.h"], exclude = ["**/config.h"])
    /// ```
    ///
    /// This call will remove all `config.h` files from the initial match.
    ///
    /// The `glob()` call is evaluated against the list of files owned by this `BUCK` file.
    /// A file is owned by whichever `BUCK` file is closest above it - so given `foo/BUCK` and
    /// `foo/bar/BUCK` the file `foo/file.txt` would be owned by `foo/BUCK` (and available from
    /// its `glob` results) but the file `foo/bar/file.txt` would be owned by `foo/bar/BUCk`
    /// and _not_ appear in the glob result of `foo/BUCK`, even if you write `glob(["bar/file.txt"])`.
    /// As a consequence of this rule, `glob(["../foo.txt"])` will always return an empty list of files.
    ///
    /// Currently `glob` is evaluated case-insensitively on all file systems, but we expect
    /// that to change to case sensitive in the near future.
    fn glob<'v>(
        include: Vec<String>,
        #[starlark(require = named, default=Vec::new())] exclude: Vec<String>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<ValueOfUnchecked<'v, ListOf<'v, String>>> {
        let extra = ModuleInternals::from_context(eval, "glob")?;
        let spec = GlobSpec::new(&include, &exclude)?;
        let res = extra.resolve_glob(&spec).map(|path| path.as_str());
        Ok(ValueOfUnchecked::new(eval.heap().alloc(AllocList(res))))
    }

    /// `package_name()` can only be called in `BUCK` files, and returns the name of the package.
    /// E.g. inside `foo//bar/baz/BUCK` the output will be `bar/baz`.
    fn package_name(eval: &mut Evaluator) -> anyhow::Result<String> {
        // An (IMO) unfortunate choice in the skylark api is that this just gives the cell-relative
        //  path of the package (which isn't a unique "name" for the package)
        Ok(BuildContext::from_context(eval)?
            .require_package()?
            .cell_relative_path()
            .to_string())
    }

    /// `get_base_path()` can only be called in `BUCK` files, and returns the name of the package.
    /// E.g. inside `foo//bar/baz/BUCK` the output will be `bar/baz`.
    ///
    /// This function is identical to `package_name`.
    fn get_base_path(eval: &mut Evaluator) -> anyhow::Result<String> {
        Ok(BuildContext::from_context(eval)?
            .require_package()?
            .cell_relative_path()
            .to_string())
    }

    /// Like `get_cell_name()` but prepends a leading `@` for compatibility with Buck1.
    /// You should call `get_cell_name()` instead, and if you really want the `@`,
    /// prepend it yourself.
    fn repository_name(eval: &mut Evaluator) -> anyhow::Result<String> {
        // In Buck v1 the repository name has a leading `@` on it, so match that with v2.
        // In practice, most users do `repository_name()[1:]` to drop it.
        Ok(format!(
            "@{}",
            BuildContext::from_context(eval)?.cell_info().name()
        ))
    }

    /// `get_cell_name()` can be called from either a `BUCK` file or a `.bzl` file,
    /// and returns the name of the cell where the `BUCK` file that started the call
    /// lives.
    ///
    /// For example, inside `foo//bar/baz/BUCK` the output will be `foo`.
    /// If that `BUCK` file does a `load("hello//world.bzl", "something")` then
    /// the result in that `.bzl` file will also be `foo`.
    fn get_cell_name(eval: &mut Evaluator) -> anyhow::Result<String> {
        Ok(BuildContext::from_context(eval)?
            .cell_info()
            .name()
            .to_string())
    }
}

/// Native functions included in all contexts (`BUCK`, `bzl`, `bxl`).
pub(crate) fn register_base_natives(registry: &mut GlobalsBuilder) {
    native_module(registry);
    register_select(registry);
    register_sha256(registry);
}

/// Configure globals for all three possible environments: `BUCK`, `bzl` and `bxl`.
pub fn configure_base_globals(
    configure_native_struct: impl FnOnce(&mut GlobalsBuilder),
) -> GlobalsBuilder {
    let starlark_extensions = [
        LibraryExtension::Abs,
        LibraryExtension::Breakpoint,
        LibraryExtension::Debug,
        LibraryExtension::EnumType,
        LibraryExtension::Filter,
        LibraryExtension::Json,
        LibraryExtension::Map,
        LibraryExtension::Partial,
        LibraryExtension::Pprint,
        LibraryExtension::Print,
        LibraryExtension::RecordType,
        LibraryExtension::ExperimentalRegex,
        LibraryExtension::StructType,
        LibraryExtension::Typing,
    ];
    let mut global_env = GlobalsBuilder::extended_by(&starlark_extensions)
        .with(register_base_natives)
        .with(register_dedupe);
    global_env.struct_("__internal__", |x| {
        register_base_natives(x);
        // If `native.` symbols need to be added to the global env, they should be done
        // in `configure_build_file_globals()` or
        // `configure_extension_file_globals()`
        for ext in starlark_extensions {
            ext.add(x)
        }
        configure_native_struct(x);
    });
    global_env
}


/*
 * 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.
 */

//! Interpreter related Dice calculations

use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;

use allocative::Allocative;
use async_trait::async_trait;
use buck2_common::package_listing::dice::HasPackageListingResolver;
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::bzl::ImportPath;
use buck2_core::cells::build_file_cell::BuildFileCell;
use buck2_core::package::PackageLabel;
use buck2_events::dispatch::async_record_root_spans;
use buck2_events::span::SpanId;
use buck2_interpreter::file_loader::LoadedModule;
use buck2_interpreter::file_loader::ModuleDeps;
use buck2_interpreter::file_type::StarlarkFileType;
use buck2_interpreter::load_module::InterpreterCalculationImpl;
use buck2_interpreter::load_module::INTERPRETER_CALCULATION_IMPL;
use buck2_interpreter::path::PackageFilePath;
use buck2_interpreter::path::StarlarkModulePath;
use buck2_interpreter::path::StarlarkPath;
use buck2_interpreter::prelude_path::PreludePath;
use buck2_interpreter::starlark_profiler::StarlarkProfilerOrInstrumentation;
use buck2_node::nodes::eval_result::EvaluationResult;
use buck2_node::nodes::frontend::TargetGraphCalculation;
use buck2_node::nodes::frontend::TargetGraphCalculationImpl;
use buck2_node::nodes::frontend::TARGET_GRAPH_CALCULATION_IMPL;
use derive_more::Display;
use dice::DiceComputations;
use dice::Key;
use dupe::Dupe;
use futures::future::BoxFuture;
use futures::FutureExt;
use more_futures::cancellation::CancellationContext;
use smallvec::SmallVec;
use starlark::environment::Globals;

use crate::interpreter::dice_calculation_delegate::HasCalculationDelegate;
use crate::interpreter::global_interpreter_state::HasGlobalInterpreterState;

// Key for 'InterpreterCalculation::get_interpreter_results'
#[derive(Clone, Dupe, Display, Debug, Eq, Hash, PartialEq, Allocative)]
pub struct InterpreterResultsKey(pub PackageLabel);

struct TargetGraphCalculationInstance;

pub(crate) fn init_target_graph_calculation_impl() {
    TARGET_GRAPH_CALCULATION_IMPL.init(&TargetGraphCalculationInstance);
}

#[async_trait]
impl TargetGraphCalculationImpl for TargetGraphCalculationInstance {
    async fn get_interpreter_results_uncached(
        &self,
        ctx: &DiceComputations,
        package: PackageLabel,
    ) -> anyhow::Result<Arc<EvaluationResult>> {
        let interpreter = ctx
            .get_interpreter_calculator(
                package.cell_name(),
                BuildFileCell::new(package.cell_name()),
            )
            .await?;
        Ok(Arc::new(
            interpreter
                .eval_build_file(
                    package.dupe(),
                    &mut StarlarkProfilerOrInstrumentation::disabled(),
                )
                .await?,
        ))
    }

    fn get_interpreter_results<'a>(
        &self,
        ctx: &'a DiceComputations,
        package: PackageLabel,
    ) -> BoxFuture<'a, anyhow::Result<Arc<EvaluationResult>>> {
        #[async_trait]
        impl Key for InterpreterResultsKey {
            type Value = SharedResult<Arc<EvaluationResult>>;
            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> Self::Value {
                let now = Instant::now();

                let (result, spans) =
                    async_record_root_spans(ctx.get_interpreter_results_uncached(self.0.dupe()))
                        .await;

                let result = result.shared_error();

                ctx.store_evaluation_data(IntepreterResultsKeyActivationData {
                    duration: now.elapsed(),
                    result: result.dupe(),
                    spans,
                })?;

                result
            }

            fn equality(_: &Self::Value, _: &Self::Value) -> bool {
                // TODO consider if we want to impl eq for this
                false
            }

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

        ctx.compute(&InterpreterResultsKey(package.dupe()))
            .map(|res| res?.unshared_error())
            .boxed()
    }
}

struct InterpreterCalculationInstance;

pub(crate) fn init_interpreter_calculation_impl() {
    INTERPRETER_CALCULATION_IMPL.init(&InterpreterCalculationInstance);
}

#[async_trait]
impl InterpreterCalculationImpl for InterpreterCalculationInstance {
    async fn get_loaded_module(
        &self,
        ctx: &DiceComputations,
        path: StarlarkModulePath<'_>,
    ) -> anyhow::Result<LoadedModule> {
        ctx.get_interpreter_calculator(path.cell(), path.build_file_cell())
            .await?
            .eval_module(path)
            .await
    }

    async fn get_module_deps(
        &self,
        ctx: &DiceComputations,
        package: PackageLabel,
        build_file_cell: BuildFileCell,
    ) -> anyhow::Result<ModuleDeps> {
        let calc = ctx
            .get_interpreter_calculator(package.cell_name(), build_file_cell)
            .await?;

        let build_file_name = ctx
            .resolve_package_listing(package.dupe())
            .await?
            .buildfile()
            .to_owned();

        let (_module, module_deps) = calc
            .prepare_eval(StarlarkPath::BuildFile(&BuildFilePath::new(
                package.dupe(),
                build_file_name,
            )))
            .await?;

        Ok(module_deps)
    }

    async fn get_package_file_deps(
        &self,
        ctx: &DiceComputations,
        package: &PackageFilePath,
    ) -> anyhow::Result<Option<Vec<ImportPath>>> {
        // These aren't cached on the DICE graph, since in normal evaluation there aren't that many, and we can cache at a higher level.
        // Therefore we re-parse the file, if it exists.
        // Fortunately, there are only a small number (currently a few hundred)
        let interpreter = ctx
            .get_interpreter_calculator(package.cell(), package.build_file_cell())
            .await?;
        Ok(interpreter
            .prepare_package_file_eval(package)
            .await?
            .map(|x| x.1.get_loaded_modules().imports().cloned().collect()))
    }

    async fn global_env_for_file_type(
        &self,
        ctx: &DiceComputations,
        file_type: StarlarkFileType,
    ) -> anyhow::Result<Globals> {
        Ok(ctx
            .get_global_interpreter_state()
            .await?
            .globals_for_file_type(file_type)
            .dupe())
    }

    async fn prelude_import(&self, ctx: &DiceComputations) -> anyhow::Result<Option<PreludePath>> {
        Ok(ctx
            .get_global_interpreter_state()
            .await?
            .configuror
            .prelude_import()
            .cloned())
    }
}

pub struct IntepreterResultsKeyActivationData {
    pub duration: Duration,
    pub result: SharedResult<Arc<EvaluationResult>>,
    pub spans: SmallVec<[SpanId; 1]>,
}


/*
 * 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 buck2_core::cells::build_file_cell::BuildFileCell;
use buck2_core::cells::CellResolver;
use dupe::Dupe;

#[derive(Clone, Dupe, Debug, Allocative)]
pub(crate) struct InterpreterCellInfo(Arc<Data>);

#[derive(Debug, Allocative)]
struct Data {
    cell_name: BuildFileCell,
    cell_resolver: CellResolver,
}

impl InterpreterCellInfo {
    pub(crate) fn new(
        cell_name: BuildFileCell,
        cell_resolver: CellResolver,
    ) -> anyhow::Result<Self> {
        Ok(Self(Arc::new(Data {
            cell_name,
            cell_resolver,
        })))
    }

    pub(crate) fn name(&self) -> BuildFileCell {
        self.0.cell_name
    }

    pub(crate) fn cell_resolver(&self) -> &CellResolver {
        &self.0.cell_resolver
    }
}


/*
 * 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::Debug;
use std::ptr;
use std::sync::Arc;

use allocative::Allocative;
use buck2_common::package_listing::listing::PackageListing;
use buck2_core::build_file_path::BuildFilePath;
use buck2_interpreter::extra::xcode::XcodeVersionInfo;
use buck2_interpreter::extra::InterpreterHostArchitecture;
use buck2_interpreter::extra::InterpreterHostPlatform;
use buck2_interpreter::file_loader::LoadedModules;
use buck2_interpreter::package_imports::ImplicitImport;
use buck2_interpreter::path::StarlarkModulePath;
use buck2_interpreter::prelude_path::PreludePath;
use dupe::Dupe;
use starlark::environment::Globals;
use starlark::environment::GlobalsBuilder;

use crate::attrs::coerce::ctx::BuildAttrCoercionContext;
use crate::interpreter::build_defs::configure_base_globals;
use crate::interpreter::cell_info::InterpreterCellInfo;
use crate::interpreter::functions::host_info::HostInfo;
use crate::interpreter::module_internals::ModuleInternals;
use crate::interpreter::module_internals::PackageImplicits;
use crate::super_package::data::SuperPackage;

#[derive(Clone, Allocative)]
struct ConfigureGlobalsFn(#[allocative(skip)] fn(&mut GlobalsBuilder));

impl Debug for ConfigureGlobalsFn {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "ConfiguredGlobalsFn")
    }
}

impl PartialEq for ConfigureGlobalsFn {
    fn eq(&self, other: &Self) -> bool {
        ptr::eq(self.0 as *const (), other.0 as *const ())
    }
}

#[derive(Clone, Dupe, Allocative)]
pub struct AdditionalGlobalsFn(
    #[allocative(skip)] pub Arc<dyn Fn(&mut GlobalsBuilder) + Sync + Send>,
);

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

impl PartialEq for AdditionalGlobalsFn {
    fn eq(&self, other: &Self) -> bool {
        // https://rust-lang.github.io/rust-clippy/master/index.html#vtable_address_comparisons
        // `ptr_eq` compares both data addresses and vtables.
        // And if compiler merges or splits vtables, we don't care,
        // because we behavior will be correct either way.
        // Anyway, this code is used only in tests.
        #[allow(clippy::vtable_address_comparisons)]
        Arc::ptr_eq(&self.0, &other.0)
    }
}

#[derive(Clone, Debug, PartialEq, Allocative)]
pub struct BuildInterpreterConfiguror {
    /// Path to prelude import (typically `prelude//:prelude.bzl`).
    ///
    /// It serves two purposes:
    /// * It defines symbols imported into each file (e.g. rule definitions)
    /// * Parent directory of prelude import (e.g. `prelude//`) is considered special:
    ///   imports from that directory are evaluated with prelude cell context,
    ///   not with caller cell context (see the comments in `resolve_load`)
    prelude_import: Option<PreludePath>,
    host_info: HostInfo,
    record_target_call_stack: bool,
    skip_targets_with_duplicate_names: bool,
    configure_build_file_globals: ConfigureGlobalsFn,
    configure_package_file_globals: ConfigureGlobalsFn,
    configure_extension_file_globals: ConfigureGlobalsFn,
    configure_bxl_file_globals: ConfigureGlobalsFn,
    /// For test.
    additional_globals: Option<AdditionalGlobalsFn>,
}

impl BuildInterpreterConfiguror {
    pub fn new(
        prelude_import: Option<PreludePath>,
        host_platform: InterpreterHostPlatform,
        host_architecture: InterpreterHostArchitecture,
        host_xcode_version: Option<XcodeVersionInfo>,
        record_target_call_stack: bool,
        skip_targets_with_duplicate_names: bool,
        configure_build_file_globals: fn(&mut GlobalsBuilder),
        configure_package_file_globals: fn(&mut GlobalsBuilder),
        configure_extension_file_globals: fn(&mut GlobalsBuilder),
        configure_bxl_file_globals: fn(&mut GlobalsBuilder),
        additional_globals: Option<AdditionalGlobalsFn>,
    ) -> anyhow::Result<Arc<Self>> {
        Ok(Arc::new(Self {
            prelude_import,
            host_info: HostInfo::new(host_platform, host_architecture, host_xcode_version),
            record_target_call_stack,
            skip_targets_with_duplicate_names,
            configure_build_file_globals: ConfigureGlobalsFn(configure_build_file_globals),
            configure_package_file_globals: ConfigureGlobalsFn(configure_package_file_globals),
            configure_extension_file_globals: ConfigureGlobalsFn(configure_extension_file_globals),
            configure_bxl_file_globals: ConfigureGlobalsFn(configure_bxl_file_globals),
            additional_globals,
        }))
    }

    pub(crate) fn build_file_globals(&self) -> Globals {
        // We want the `native` module to contain most things, so match what is in extension files
        configure_base_globals(self.configure_extension_file_globals.0)
            .with(|g| {
                (self.configure_build_file_globals.0)(g);
                if let Some(additional_globals) = &self.additional_globals {
                    (additional_globals.0)(g);
                }
            })
            .build()
    }

    pub(crate) fn package_file_globals(&self) -> Globals {
        configure_base_globals(self.configure_extension_file_globals.0)
            .with(|g| {
                (self.configure_package_file_globals.0)(g);
                if let Some(additional_globals) = &self.additional_globals {
                    (additional_globals.0)(g);
                }
            })
            .build()
    }

    pub(crate) fn extension_file_globals(&self) -> Globals {
        configure_base_globals(self.configure_extension_file_globals.0)
            .with(|g| {
                (self.configure_extension_file_globals.0)(g);
                if let Some(additional_globals) = &self.additional_globals {
                    (additional_globals.0)(g);
                }
            })
            .build()
    }

    pub(crate) fn bxl_file_globals(&self) -> Globals {
        configure_base_globals(self.configure_extension_file_globals.0)
            .with(|g| {
                (self.configure_bxl_file_globals.0)(g);
                if let Some(additional_globals) = &self.additional_globals {
                    (additional_globals.0)(g);
                }
            })
            .build()
    }

    pub(crate) fn host_info(&self) -> &HostInfo {
        &self.host_info
    }

    pub(crate) fn new_extra_context(
        &self,
        cell_info: &InterpreterCellInfo,
        buildfile_path: BuildFilePath,
        package_listing: PackageListing,
        super_package: SuperPackage,
        package_boundary_exception: bool,
        loaded_modules: &LoadedModules,
        implicit_import: Option<&Arc<ImplicitImport>>,
    ) -> anyhow::Result<ModuleInternals> {
        let record_target_call_stack = self.record_target_call_stack;
        let skip_targets_with_duplicate_names = self.skip_targets_with_duplicate_names;
        let package_implicits = implicit_import.map(|spec| {
            PackageImplicits::new(
                spec.dupe(),
                loaded_modules
                    .map
                    .get(&StarlarkModulePath::LoadFile(spec.import()))
                    .unwrap_or_else(|| {
                        panic!(
                            "Should've had an env for the package implicit import (`{}`).",
                            spec.import(),
                        )
                    })
                    .env()
                    .dupe(),
            )
        });
        let attr_coercer = BuildAttrCoercionContext::new_with_package(
            cell_info.cell_resolver().dupe(),
            (buildfile_path.package().dupe(), package_listing.dupe()),
            package_boundary_exception,
        );

        let imports = loaded_modules.imports().cloned().collect();

        Ok(ModuleInternals::new(
            attr_coercer,
            Arc::new(buildfile_path),
            imports,
            package_implicits,
            record_target_call_stack,
            skip_targets_with_duplicate_names,
            package_listing,
            super_package,
        ))
    }

    pub fn prelude_import(&self) -> Option<&PreludePath> {
        self.prelude_import.as_ref()
    }
}


/*
 * 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 async_trait::async_trait;
use derive_more::Display;
use dice::DiceComputations;
use dice::DiceTransactionUpdater;
use dice::InjectedKey;
use dupe::Dupe;

use crate::interpreter::configuror::BuildInterpreterConfiguror;

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

impl InjectedKey for BuildContextKey {
    type Value = Arc<BuildInterpreterConfiguror>;

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

#[async_trait]
pub trait HasInterpreterContext {
    async fn get_interpreter_configuror(&self) -> anyhow::Result<Arc<BuildInterpreterConfiguror>>;
}

#[async_trait]
impl HasInterpreterContext for DiceComputations {
    async fn get_interpreter_configuror(&self) -> anyhow::Result<Arc<BuildInterpreterConfiguror>> {
        Ok(self.compute(&BuildContextKey()).await?.dupe())
    }
}

pub trait SetInterpreterContext {
    fn set_interpreter_context(
        &mut self,
        interpreter_configuror: Arc<BuildInterpreterConfiguror>,
    ) -> anyhow::Result<()>;
}

impl SetInterpreterContext for DiceTransactionUpdater {
    fn set_interpreter_context(
        &mut self,
        interpreter_configuror: Arc<BuildInterpreterConfiguror>,
    ) -> anyhow::Result<()> {
        Ok(self.changed_to(vec![(BuildContextKey(), interpreter_configuror)])?)
    }
}


/*
 * 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::Display;
use std::sync::Arc;

use buck2_common::dice::cycles::CycleAdapterDescriptor;
use buck2_interpreter::path::OwnedStarlarkModulePath;
use buck2_util::cycle_detector::CycleDescriptor;
use derive_more::Display;
use gazebo::prelude::VecExt;
use thiserror::Error;

use crate::interpreter::dice_calculation_delegate::testing::EvalImportKey;

#[derive(Debug)]
pub struct LoadCycleDescriptor;

#[derive(Debug, Clone, Eq, PartialEq, Hash, Display)]
pub enum LoadCycleKey {
    #[display(fmt = "{}", _0)]
    Module(OwnedStarlarkModulePath),
}

#[derive(Debug, Error, Clone)]
pub struct LoadCycleError {
    cycle: Arc<Vec<OwnedStarlarkModulePath>>,
}

impl Display for LoadCycleError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "Load cycle detected (`->` means \"loads\"):")?;
        for p in self.cycle.iter() {
            writeln!(f, "  {} ->", p)?;
        }
        // point back at the first item in the cycle.
        writeln!(f, "  {}", self.cycle.first().unwrap())?;
        Ok(())
    }
}

impl CycleDescriptor for LoadCycleDescriptor {
    type Key = LoadCycleKey;

    type Error = LoadCycleError;

    fn cycle_error(cycle: Vec<&Self::Key>) -> Self::Error {
        LoadCycleError {
            cycle: Arc::new(cycle.into_map(|v| match v {
                LoadCycleKey::Module(p) => p.clone(),
            })),
        }
    }
}

impl CycleAdapterDescriptor for LoadCycleDescriptor {
    fn to_key(key: &dyn std::any::Any) -> Option<Self::Key> {
        key.downcast_ref::<EvalImportKey>()
            .map(|v| LoadCycleKey::Module(v.0.clone()))
    }
}