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

use std::collections::HashSet;
use std::mem;
use std::mem::MaybeUninit;
use std::path::Path;

use dupe::Dupe;
use thiserror::Error;

use crate::any::AnyLifetime;
use crate::cast;
use crate::codemap::FileSpan;
use crate::codemap::FileSpanRef;
use crate::codemap::ResolvedFileSpan;
use crate::collections::alloca::Alloca;
use crate::collections::string_pool::StringPool;
use crate::const_frozen_string;
use crate::environment::slots::ModuleSlotId;
use crate::environment::FrozenModuleData;
use crate::environment::Module;
use crate::errors::Diagnostic;
use crate::errors::Frame;
use crate::eval::bc::addr::BcPtrAddr;
use crate::eval::bc::bytecode::Bc;
use crate::eval::bc::frame::BcFramePtr;
use crate::eval::bc::opcode::BcOpcode;
use crate::eval::bc::writer::BcStatementLocations;
use crate::eval::compiler::def::CopySlotFromParent;
use crate::eval::compiler::def::Def;
use crate::eval::compiler::def::DefInfo;
use crate::eval::compiler::def::FrozenDef;
use crate::eval::compiler::EvalException;
use crate::eval::runtime::before_stmt::BeforeStmt;
use crate::eval::runtime::before_stmt::BeforeStmtFunc;
use crate::eval::runtime::call_stack::CheapCallStack;
use crate::eval::runtime::frame_span::FrameSpan;
use crate::eval::runtime::inlined_frame::InlinedFrames;
use crate::eval::runtime::profile::bc::BcProfile;
use crate::eval::runtime::profile::data::ProfileData;
use crate::eval::runtime::profile::heap::HeapProfile;
use crate::eval::runtime::profile::heap::HeapProfileFormat;
use crate::eval::runtime::profile::heap::RetainedHeapProfileMode;
use crate::eval::runtime::profile::or_instrumentation::ProfileOrInstrumentationMode;
use crate::eval::runtime::profile::stmt::StmtProfile;
use crate::eval::runtime::profile::time_flame::TimeFlameProfile;
use crate::eval::runtime::profile::typecheck::TypecheckProfile;
use crate::eval::runtime::profile::ProfileMode;
use crate::eval::runtime::rust_loc::rust_loc;
use crate::eval::runtime::slots::LocalCapturedSlotId;
use crate::eval::runtime::slots::LocalSlotId;
use crate::eval::CallStack;
use crate::eval::FileLoader;
use crate::stdlib::breakpoint::BreakpointConsole;
use crate::stdlib::breakpoint::RealBreakpointConsole;
use crate::stdlib::extra::PrintHandler;
use crate::stdlib::extra::StderrPrintHandler;
use crate::values::function::NativeFunction;
use crate::values::layout::value_captured::value_captured_get;
use crate::values::layout::value_captured::FrozenValueCaptured;
use crate::values::layout::value_captured::ValueCaptured;
use crate::values::FrozenHeap;
use crate::values::FrozenRef;
use crate::values::Heap;
use crate::values::Trace;
use crate::values::Tracer;
use crate::values::Value;
use crate::values::ValueLike;

#[derive(Error, Debug)]
enum EvaluatorError {
    #[error("Profiling was not enabled")]
    ProfilingNotEnabled,
    #[error("Profile data already collected")]
    ProfileDataAlreadyCollected,
    #[error("Retained memory profiling can be only obtained from `FrozenModule`")]
    RetainedMemoryProfilingCannotBeObtainedFromEvaluator,
    #[error("Profile or instrumentation already enabled")]
    ProfileOrInstrumentationAlreadyEnabled,
    #[error("Top frame is not def (internal error)")]
    TopFrameNotDef,
    #[error(
        "Coverage profile generation not implemented (but can be obtained with `.coverage()` function)"
    )]
    CoverageNotImplemented,
    #[error("Coverage not enabled")]
    CoverageNotEnabled,
    #[error("Local variable `{0}` referenced before assignment")]
    LocalVariableReferencedBeforeAssignment(String),
}

/// Number of bytes to allocate between GC's.
pub(crate) const GC_THRESHOLD: usize = 100000;

/// Holds everything about an ongoing evaluation (local variables, globals, module resolution etc).
pub struct Evaluator<'v, 'a> {
    // The module that is being used for this evaluation
    pub(crate) module_env: &'v Module,
    // The module-level variables in scope at the moment.
    // If `None` then we're in the initial module, use variables from `module_env`.
    // If `Some` we've called a `def` in a loaded frozen module.
    pub(crate) module_variables: Option<FrozenRef<'static, FrozenModuleData>>,
    /// Current function (`def` or `lambda`) frame: locals and bytecode stack.
    pub(crate) current_frame: BcFramePtr<'v>,
    // How we deal with a `load` function.
    pub(crate) loader: Option<&'a dyn FileLoader>,
    // `DefInfo` of currently executed module.
    // `DefInfo` of currently execution function can be obtained from call stack.
    pub(crate) module_def_info: FrozenRef<'static, DefInfo>,
    // Should we enable heap profiling or not
    pub(crate) heap_profile: HeapProfile,
    // Should we enable flame profiling or not
    pub(crate) time_flame_profile: TimeFlameProfile<'v>,
    // Is GC disabled for some reason
    pub(crate) disable_gc: bool,
    // If true, the interpreter prints to stderr on GC.
    // This is used for debugging.
    pub(crate) verbose_gc: bool,
    // Size of the heap when we should next perform a GC.
    pub(crate) next_gc_level: usize,
    /// Run static typechecking of the module being evaluated.
    pub(crate) static_typechecking: bool,
    // Profiling or instrumentation enabled.
    pub(crate) profile_or_instrumentation_mode: ProfileOrInstrumentationMode,
    // Used for line profiling
    stmt_profile: StmtProfile,
    // Holds things that require hooking into evaluation.
    eval_instrumentation: EvaluationInstrumentation<'a>,
    // Total time spent in runtime typechecking.
    // Filled only if runtime typechecking profiling is enabled.
    pub(crate) typecheck_profile: TypecheckProfile,
    // Used for stack-like allocation
    alloca: Alloca,
    // Another stack-like allocation
    pub(crate) string_pool: StringPool,
    /// Field that can be used for any purpose you want (can store types you define).
    /// Typically accessed via native functions you also define.
    pub extra: Option<&'a dyn AnyLifetime<'a>>,
    /// Called to perform console IO each time `breakpoint` function is called.
    pub(crate) breakpoint_handler:
        Option<Box<dyn Fn() -> anyhow::Result<Box<dyn BreakpointConsole>>>>,
    /// Use in implementation of `print` function.
    pub(crate) print_handler: &'a (dyn PrintHandler + 'a),
    // The Starlark-level call-stack of functions.
    // Must go last because it's quite a big structure
    pub(crate) call_stack: CheapCallStack<'v>,
}

/// Just holds things that require using EvaluationCallbacksEnabled so that we can cache whether that needs to be enabled or not.
struct EvaluationInstrumentation<'a> {
    // Bytecode profile.
    bc_profile: BcProfile,
    // Extra functions to run on each statement, usually empty
    before_stmt: BeforeStmt<'a>,
    heap_or_flame_profile: bool,
    // Whether we need to instrument evaluation or not, should be set if before_stmt or bc_profile are enabled.
    enabled: bool,
}

impl<'a> EvaluationInstrumentation<'a> {
    fn new() -> EvaluationInstrumentation<'a> {
        Self {
            bc_profile: BcProfile::new(),
            before_stmt: BeforeStmt::default(),
            heap_or_flame_profile: false,
            enabled: false,
        }
    }

    fn enable_heap_or_flame_profile(&mut self) {
        self.heap_or_flame_profile = true;
    }

    fn change<F: FnOnce(&mut EvaluationInstrumentation<'a>)>(&mut self, f: F) {
        f(self);
        self.enabled =
            self.bc_profile.enabled() || self.before_stmt.enabled() || self.heap_or_flame_profile;
    }
}

// Implementing this forces users to be more careful about lifetimes that the Evaluator captures such that we could
// add captures of types that implement Drop without needing changes to client code.
impl Drop for Evaluator<'_, '_> {
    fn drop(&mut self) {}
}

impl<'v, 'a> Evaluator<'v, 'a> {
    /// Crate a new [`Evaluator`] specifying the [`Module`] used for module variables.
    ///
    /// If your program contains `load()` statements, you also need to call
    /// [`set_loader`](Evaluator::set_loader).
    pub fn new(module: &'v Module) -> Self {
        Evaluator {
            call_stack: CheapCallStack::default(),
            module_env: module,
            module_variables: None,
            current_frame: BcFramePtr::null(),
            loader: None,
            extra: None,
            next_gc_level: GC_THRESHOLD,
            disable_gc: false,
            alloca: Alloca::new(),
            profile_or_instrumentation_mode: ProfileOrInstrumentationMode::None,
            heap_profile: HeapProfile::new(),
            stmt_profile: StmtProfile::new(),
            typecheck_profile: TypecheckProfile::default(),
            time_flame_profile: TimeFlameProfile::new(),
            eval_instrumentation: EvaluationInstrumentation::new(),
            module_def_info: DefInfo::empty(), // Will be replaced before it is used
            string_pool: StringPool::default(),
            breakpoint_handler: None,
            print_handler: &StderrPrintHandler,
            verbose_gc: false,
            static_typechecking: false,
        }
    }

    /// Disables garbage collection from now onwards. Cannot be re-enabled.
    /// Usually called because you have captured [`Value`]'s unsafely, either in
    /// global variables or the [`extra`](Evaluator::extra) field.
    pub fn disable_gc(&mut self) {
        self.disable_gc = true;
    }

    /// Enable GC logging.
    pub fn verbose_gc(&mut self) {
        self.verbose_gc = true;
    }

    /// Enable static typechecking. For example:
    ///
    /// ```ignore
    /// def foo() -> int.type: return "hello"
    /// ```
    ///
    /// would fail when static typechecking is enabled even if `foo` is never called.
    pub fn enable_static_typechecking(&mut self, enable: bool) {
        self.static_typechecking = enable;
    }

    /// Set the [`FileLoader`] used to resolve `load()` statements.
    /// A list of all load statements can be obtained through
    /// [`AstModule::loads`](crate::syntax::AstModule::loads).
    pub fn set_loader(&mut self, loader: &'a dyn FileLoader) {
        self.loader = Some(loader);
    }

    /// Enable profiling, allowing [`Evaluator::write_profile`] to be used.
    /// Profilers add overhead, and while some profilers can be used together,
    /// it's better to run at most one profiler at a time.
    pub fn enable_profile(&mut self, mode: &ProfileMode) -> anyhow::Result<()> {
        if self.profile_or_instrumentation_mode != ProfileOrInstrumentationMode::None {
            return Err(EvaluatorError::ProfileOrInstrumentationAlreadyEnabled.into());
        }

        self.profile_or_instrumentation_mode = ProfileOrInstrumentationMode::Profile(mode.dupe());

        match mode {
            ProfileMode::HeapSummaryAllocated
            | ProfileMode::HeapFlameAllocated
            | ProfileMode::HeapSummaryRetained
            | ProfileMode::HeapFlameRetained => {
                self.heap_profile.enable();

                match mode {
                    ProfileMode::HeapFlameRetained => self
                        .module_env
                        .enable_heap_profile(RetainedHeapProfileMode::Flame),
                    ProfileMode::HeapSummaryRetained => self
                        .module_env
                        .enable_heap_profile(RetainedHeapProfileMode::Summary),
                    _ => {}
                }

                self.eval_instrumentation
                    .change(|v| v.enable_heap_or_flame_profile());

                // Disable GC because otherwise why lose the profile records, as we use the heap
                // to store a complete list of what happened in linear order.
                self.disable_gc = true;
            }
            ProfileMode::Statement | ProfileMode::Coverage => {
                self.stmt_profile.enable();
                self.before_stmt_fn(&|span, eval| eval.stmt_profile.before_stmt(span));
            }
            ProfileMode::TimeFlame => {
                self.time_flame_profile.enable();
                self.eval_instrumentation
                    .change(|v| v.enable_heap_or_flame_profile());
            }
            ProfileMode::Bytecode => {
                self.eval_instrumentation
                    .change(|v| v.bc_profile.enable_1());
            }
            ProfileMode::BytecodePairs => {
                self.eval_instrumentation
                    .change(|v| v.bc_profile.enable_2());
            }
            ProfileMode::Typecheck => {
                self.typecheck_profile.enabled = true;
            }
        }
        Ok(())
    }

    /// Write a profile to a file.
    /// Only valid if corresponding profiler was enabled.
    pub fn write_profile<P: AsRef<Path>>(&mut self, filename: P) -> anyhow::Result<()> {
        self.gen_profile()?.write(filename.as_ref())
    }

    /// Generate profile for a given mode.
    /// Only valid if corresponding profiler was enabled.
    pub fn gen_profile(&mut self) -> anyhow::Result<ProfileData> {
        let mode = match &self.profile_or_instrumentation_mode {
            ProfileOrInstrumentationMode::None => {
                return Err(EvaluatorError::ProfilingNotEnabled.into());
            }
            ProfileOrInstrumentationMode::Collected => {
                return Err(EvaluatorError::ProfileDataAlreadyCollected.into());
            }
            ProfileOrInstrumentationMode::Profile(mode) => mode.dupe(),
        };
        self.profile_or_instrumentation_mode = ProfileOrInstrumentationMode::Collected;
        match mode {
            ProfileMode::HeapSummaryAllocated => self
                .heap_profile
                .gen(self.heap(), HeapProfileFormat::Summary),
            ProfileMode::HeapFlameAllocated => self
                .heap_profile
                .gen(self.heap(), HeapProfileFormat::FlameGraph),
            ProfileMode::HeapSummaryRetained | ProfileMode::HeapFlameRetained => {
                Err(EvaluatorError::RetainedMemoryProfilingCannotBeObtainedFromEvaluator.into())
            }
            ProfileMode::Statement => self.stmt_profile.gen(),
            ProfileMode::Coverage => Err(EvaluatorError::CoverageNotImplemented.into()),
            ProfileMode::Bytecode => self.gen_bc_profile(),
            ProfileMode::BytecodePairs => self.gen_bc_pairs_profile(),
            ProfileMode::TimeFlame => self.time_flame_profile.gen(),
            ProfileMode::Typecheck => self.typecheck_profile.gen(),
        }
    }

    /// Get code coverage.
    ///
    /// Works if statement profile is enabled.
    ///
    /// Note coverage is not precise, because
    /// * some optimizer transformations may create incorrect spans
    /// * some optimizer transformations may remove statements
    pub fn coverage(&self) -> anyhow::Result<HashSet<ResolvedFileSpan>> {
        match self.profile_or_instrumentation_mode {
            ProfileOrInstrumentationMode::Profile(ProfileMode::Coverage) => {
                self.stmt_profile.coverage()
            }
            _ => Err(EvaluatorError::CoverageNotEnabled.into()),
        }
    }

    /// Enable interactive `breakpoint()`. When enabled, `breakpoint()`
    /// reads commands from stdin and write to stdout.
    /// When disabled (default), `breakpoint()` function results in error.
    pub fn enable_terminal_breakpoint_console(&mut self) {
        self.breakpoint_handler = Some(RealBreakpointConsole::factory());
    }

    /// Obtain the current call-stack, suitable for use with [`Diagnostic`].
    pub fn call_stack(&self) -> CallStack {
        self.call_stack
            .to_diagnostic_frames(InlinedFrames::default())
    }

    /// Obtain the top frame on the call-stack. May be [`None`] if the
    /// call happened via native functions.
    pub fn call_stack_top_frame(&self) -> Option<Frame> {
        self.call_stack.top_frame()
    }

    /// Current size (in frames) of the stack.
    pub fn call_stack_count(&self) -> usize {
        self.call_stack.count()
    }

    /// Obtain the top location on the call-stack. May be [`None`] if the
    /// call happened via native functions.
    pub fn call_stack_top_location(&self) -> Option<FileSpan> {
        self.call_stack.top_location()
    }

    pub(crate) fn before_stmt_fn(
        &mut self,
        f: &'a dyn for<'v1> Fn(FileSpanRef, &mut Evaluator<'v1, 'a>),
    ) {
        self.before_stmt(f.into())
    }

    pub(crate) fn before_stmt(&mut self, f: BeforeStmtFunc<'a>) {
        self.eval_instrumentation
            .change(|v| v.before_stmt.before_stmt.push(f))
    }

    /// This function is used by DAP, and it is not public API.
    // TODO(nga): pull DAP into the crate, and hide this function.
    #[doc(hidden)]
    pub fn before_stmt_for_dap(&mut self, f: BeforeStmtFunc<'a>) {
        self.before_stmt(f)
    }

    /// Set the handler invoked when `print` function is used.
    pub fn set_print_handler(&mut self, handler: &'a (dyn PrintHandler + 'a)) {
        self.print_handler = handler;
    }

    /// Called to add an entry to the call stack, by the function being invoked.
    /// Called for all types of function, including those written in Rust.
    #[inline(always)]
    pub(crate) fn with_call_stack<R>(
        &mut self,
        function: Value<'v>,
        span: Option<FrozenRef<'static, FrameSpan>>,
        within: impl FnOnce(&mut Self) -> anyhow::Result<R>,
    ) -> anyhow::Result<R> {
        #[cold]
        #[inline(never)]
        fn add_diagnostics(e: anyhow::Error, me: &Evaluator) -> anyhow::Error {
            Diagnostic::modify(e, |d: &mut Diagnostic| {
                // Make sure we capture the call_stack before popping things off it
                d.set_call_stack(|| me.call_stack.to_diagnostic_frames(InlinedFrames::default()));
            })
        }

        self.call_stack.push(function, span)?;
        // Must always call .pop regardless
        let res = within(self).map_err(|e| add_diagnostics(e, self));
        self.call_stack.pop();
        res
    }

    /// Called to change the local variables, from the callee.
    /// Only called for user written functions.
    #[inline(always)] // There is only one caller
    pub(crate) fn with_function_context(
        &mut self,
        def: Value<'v>,
        module: Option<FrozenRef<'static, FrozenModuleData>>, // None == use module_env
        bc: &Bc,
    ) -> Result<Value<'v>, EvalException> {
        // Set up for the new function call
        let old_module_variables = mem::replace(&mut self.module_variables, module);

        // Run the computation
        let res = self.eval_bc(def, bc);

        // Restore them all back
        self.module_variables = old_module_variables;
        res
    }

    /// The active heap where [`Value`]s are allocated.
    pub fn heap(&self) -> &'v Heap {
        self.module_env.heap()
    }

    /// Module which was passed to the evaluator.
    pub fn module(&self) -> &'v Module {
        self.module_env
    }

    /// The frozen heap. It's possible to allocate [`FrozenValue`](crate::values::FrozenValue)s here,
    /// but often not a great idea, as they will remain allocated as long
    /// as the results of this execution are required.
    /// Suitable for use with [`add_reference`](FrozenHeap::add_reference)
    /// and [`OwnedFrozenValue::owned_frozen_value`](crate::values::OwnedFrozenValue::owned_frozen_value).
    pub fn frozen_heap(&self) -> &'v FrozenHeap {
        self.module_env.frozen_heap()
    }

    pub(crate) fn get_slot_module(&self, slot: ModuleSlotId) -> anyhow::Result<Value<'v>> {
        // Make sure the error-path doesn't get inlined into the normal-path execution
        #[cold]
        #[inline(never)]
        fn error<'v>(eval: &Evaluator<'v, '_>, slot: ModuleSlotId) -> anyhow::Error {
            let name = match &eval.module_variables {
                None => eval
                    .module_env
                    .mutable_names()
                    .get_slot(slot)
                    .map(|s| s.as_str().to_owned()),
                Some(e) => e.get_slot_name(slot).map(|s| s.as_str().to_owned()),
            }
            .unwrap_or_else(|| "<unknown>".to_owned());
            EvaluatorError::LocalVariableReferencedBeforeAssignment(name).into()
        }

        match &self.module_variables {
            None => self.module_env.slots().get_slot(slot),
            Some(e) => e.get_slot(slot).map(Value::new_frozen),
        }
        .ok_or_else(|| error(self, slot))
    }

    // Make sure the error-path doesn't get inlined into the normal-path execution
    #[cold]
    #[inline(never)]
    pub(crate) fn local_var_referenced_before_assignment(
        &self,
        slot: LocalSlotId,
    ) -> anyhow::Error {
        let def_info = match self.top_frame_def_info() {
            Ok(def_info) => def_info,
            Err(e) => return e,
        };
        let names = &def_info.used;
        let name = names[slot.0 as usize].as_str().to_owned();
        EvaluatorError::LocalVariableReferencedBeforeAssignment(name).into()
    }

    #[inline(always)]
    pub(crate) fn get_slot_local(
        &self,
        frame: BcFramePtr<'v>,
        slot: LocalSlotId,
    ) -> anyhow::Result<Value<'v>> {
        // We access locals from explicitly passed frame because it is faster.
        debug_assert!(self.current_frame == frame);

        frame
            .get_slot(slot.to_captured_or_not())
            .ok_or_else(|| self.local_var_referenced_before_assignment(slot))
    }

    pub(crate) fn get_slot_local_captured(
        &self,
        slot: LocalCapturedSlotId,
    ) -> anyhow::Result<Value<'v>> {
        let value_captured = self.get_slot_local(self.current_frame, LocalSlotId(slot.0))?;
        let value_captured = value_captured_get(value_captured);
        value_captured
            .ok_or_else(|| self.local_var_referenced_before_assignment(LocalSlotId(slot.0)))
    }

    pub(crate) fn clone_slot_capture(
        &self,
        copy: &CopySlotFromParent,
        target_def_info: &DefInfo,
    ) -> Value<'v> {
        match self.current_frame.get_slot(copy.parent) {
            Some(value_captured) => {
                debug_assert!(
                    value_captured.downcast_ref::<ValueCaptured>().is_some()
                        || value_captured
                            .downcast_ref::<FrozenValueCaptured>()
                            .is_some(),
                    "slot {} ({}) is expected to be ValueCaptured, it is {:?} ({}); \
                        def location: {}",
                    copy.parent.0,
                    target_def_info
                        .used
                        .get(copy.child.0 as usize)
                        .copied()
                        .unwrap_or_default()
                        .as_str(),
                    value_captured,
                    value_captured.get_type(),
                    target_def_info.signature_span,
                );
                value_captured
            }
            None => {
                let value_captured = self.heap().alloc_complex(ValueCaptured::new(None));
                self.current_frame.set_slot(copy.parent, value_captured);
                value_captured
            }
        }
    }

    /// Set a variable in the top-level module currently being processed.
    /// This may not be the module the function is being called in.
    ///
    /// Any variables which are set will be available in the [`Module`] after evaluation returns.
    /// If those variables are _also_ existing top-level variables, then the program from that point on
    /// will incorporate those values. If they aren't existing top-level variables, they will be ignored.
    /// These details are subject to change.
    /// As such, use this API with a healthy dose of caution and in limited settings.
    pub fn set_module_variable_at_some_point(
        &mut self,
        name: &str,
        value: Value<'v>,
    ) -> anyhow::Result<()> {
        value.export_as(name, self);
        self.module_env.set(name, value);
        Ok(())
    }

    pub(crate) fn set_slot_module(&mut self, slot: ModuleSlotId, value: Value<'v>) {
        self.module_env.slots().set_slot(slot, value);
    }

    pub(crate) fn set_slot_local_captured(&mut self, slot: LocalCapturedSlotId, value: Value<'v>) {
        let slot = LocalSlotId(slot.0);
        match self.current_frame.get_slot(slot.to_captured_or_not()) {
            Some(value_captured) => {
                let value_captured = value_captured
                    .downcast_ref::<ValueCaptured>()
                    .expect("not a ValueCaptured");
                value_captured.set(value);
            }
            None => {
                let value_captured = self.heap().alloc_complex(ValueCaptured::new(Some(value)));
                self.current_frame
                    .set_slot(slot.to_captured_or_not(), value_captured);
            }
        };
    }

    /// Take a value from the local slot and store it back wrapped in [`ValueCaptured`].
    pub(crate) fn wrap_local_slot_captured(&mut self, slot: LocalSlotId) {
        let value = self
            .current_frame
            .get_slot(slot.to_captured_or_not())
            .expect("slot unset");
        debug_assert!(value.downcast_ref::<ValueCaptured>().is_none());
        let value_captured = self.heap().alloc_complex(ValueCaptured::new(Some(value)));
        self.current_frame
            .set_slot(slot.to_captured_or_not(), value_captured);
    }

    pub(crate) fn check_return_type(&mut self, ret: Value<'v>) -> anyhow::Result<()> {
        let func = self.call_stack.top_nth_function(0)?;
        if let Some(func) = func.downcast_ref::<Def>() {
            func.check_return_type(ret, self)
        } else if let Some(func) = func.downcast_ref::<FrozenDef>() {
            func.check_return_type(ret, self)
        } else {
            Err(EvaluatorError::TopFrameNotDef.into())
        }
    }

    fn func_to_def_info(&self, func: Value<'_>) -> anyhow::Result<FrozenRef<DefInfo>> {
        if let Some(func) = func.downcast_ref::<Def>() {
            Ok(func.def_info)
        } else if let Some(func) = func.downcast_ref::<FrozenDef>() {
            Ok(func.def_info)
        } else if func.is_none() {
            // For module, it is `None`.
            Ok(self.module_def_info)
        } else {
            Err(EvaluatorError::TopFrameNotDef.into())
        }
    }

    pub(crate) fn top_frame_def_info(&self) -> anyhow::Result<FrozenRef<DefInfo>> {
        let func = self.call_stack.top_nth_function(0)?;
        self.func_to_def_info(func)
    }

    /// Gets the "top frame" for debugging. If the real top frame is `breakpoint` or `debug_evaluate`
    /// it will be skipped. This should only be used for the starlark debugger.
    pub(crate) fn top_frame_def_info_for_debugger(&self) -> anyhow::Result<FrozenRef<DefInfo>> {
        let func = {
            let top = self.call_stack.top_nth_function(0)?;
            if top.downcast_ref::<NativeFunction>().is_some() {
                // we are in `breakpoint` or `debug_evaluate` function, get the next frame.
                self.call_stack.top_nth_function(1)?
            } else {
                top
            }
        };

        self.func_to_def_info(func)
    }

    /// Cause a GC to be triggered next time it's possible.
    pub(crate) fn trigger_gc(&mut self) {
        // We will GC next time we can, since the threshold is if 0 or more bytes are allocated
        self.next_gc_level = 0;
    }

    fn trace(&mut self, tracer: &Tracer<'v>) {
        self.module_env.trace(tracer);
        self.current_frame.trace(tracer);
        self.call_stack.trace(tracer);
        self.time_flame_profile.trace(tracer);
    }

    /// Perform a garbage collection.
    /// After this operation all [`Value`]s not reachable from the evaluator will be invalid,
    /// and using them will lead to a segfault.
    /// Do not call during Starlark evaluation.
    pub unsafe fn garbage_collect(&mut self) {
        if self.verbose_gc {
            eprintln!(
                "Starlark: allocated bytes: {}, starting GC...",
                self.heap().allocated_bytes()
            );
        }

        self.stmt_profile
            .before_stmt(rust_loc!().span.file_span_ref());

        self.time_flame_profile
            .record_call_enter(const_frozen_string!("GC").to_value());

        self.heap().garbage_collect(|tracer| self.trace(tracer));

        self.time_flame_profile.record_call_exit();

        if self.verbose_gc {
            eprintln!(
                "Starlark: GC complete. Allocated bytes: {}.",
                self.heap().allocated_bytes()
            );
        }
    }

    /// Note that the `Drop` for the `T` will not be called. That's safe if there is no `Drop`,
    /// or you call it yourself.
    #[inline(always)]
    pub(crate) fn alloca_uninit<T, R, F>(&mut self, len: usize, k: F) -> R
    where
        F: FnOnce(&mut [MaybeUninit<T>], &mut Self) -> R,
    {
        // We want to be able to access the evaluator underneath the alloca.
        // We know that the alloca will be used in a stacked way, so that's fine.
        let alloca = unsafe { cast::ptr_lifetime(&self.alloca) };
        alloca.alloca_uninit(len, |xs| k(xs, self))
    }

    /// Allocate `len` elements, initialize them with `init` function, and invoke
    /// a callback `k` with the pointer to the allocated memory and `self`.
    #[inline(always)]
    pub(crate) fn alloca_init<T, R, F>(&mut self, len: usize, init: impl Fn() -> T, k: F) -> R
    where
        F: FnOnce(&mut [T], &mut Self) -> R,
    {
        let alloca = unsafe { cast::ptr_lifetime(&self.alloca) };
        alloca.alloca_init(len, init, |xs| k(xs, self))
    }

    /// Concat two slices and invoke the callback with the result.
    pub(crate) fn alloca_concat<T: Clone, R, F>(&mut self, x: &[T], y: &[T], k: F) -> R
    where
        F: FnOnce(&[T], &mut Self) -> R,
    {
        let alloca = unsafe { cast::ptr_lifetime(&self.alloca) };
        alloca.alloca_concat(x, y, |xs| k(xs, self))
    }

    pub(crate) fn gen_bc_profile(&mut self) -> anyhow::Result<ProfileData> {
        self.eval_instrumentation.bc_profile.gen_bc_profile()
    }

    pub(crate) fn gen_bc_pairs_profile(&mut self) -> anyhow::Result<ProfileData> {
        self.eval_instrumentation.bc_profile.gen_bc_pairs_profile()
    }

    #[cold]
    #[inline(never)]
    fn eval_bc_with_callbacks(
        &mut self,
        def: Value<'v>,
        bc: &Bc,
    ) -> Result<Value<'v>, EvalException> {
        debug_assert!(self.eval_instrumentation.enabled);
        if self.eval_instrumentation.heap_or_flame_profile {
            self.heap_profile.record_call_enter(def, self.heap());
            self.time_flame_profile.record_call_enter(def);
            let res = bc.run(self, &mut EvalCallbacksDisabled);
            self.heap_profile.record_call_exit(self.heap());
            self.time_flame_profile.record_call_exit();
            res
        } else {
            bc.run(
                self,
                &mut EvalCallbacksEnabled {
                    bc_profile: self.eval_instrumentation.bc_profile.enabled(),
                    before_stmt: self.eval_instrumentation.before_stmt.enabled(),
                    stmt_locs: &bc.instrs.stmt_locs,
                    bc_start_ptr: bc.instrs.start_ptr(),
                },
            )
        }
    }

    #[inline(always)]
    pub(crate) fn eval_bc(&mut self, def: Value<'v>, bc: &Bc) -> Result<Value<'v>, EvalException> {
        if self.eval_instrumentation.enabled {
            self.eval_bc_with_callbacks(def, bc)
        } else {
            bc.run(self, &mut EvalCallbacksDisabled)
        }
    }
}

pub(crate) trait EvaluationCallbacks {
    fn before_instr(&mut self, _eval: &mut Evaluator, _ip: BcPtrAddr, _opcode: BcOpcode);
}

pub(crate) struct EvalCallbacksDisabled;

impl EvaluationCallbacks for EvalCallbacksDisabled {
    #[inline(always)]
    fn before_instr(&mut self, _eval: &mut Evaluator, _ip: BcPtrAddr, _opcode: BcOpcode) {}
}

pub(crate) struct EvalCallbacksEnabled<'a> {
    pub(crate) bc_profile: bool,
    pub(crate) before_stmt: bool,
    pub(crate) stmt_locs: &'a BcStatementLocations,
    pub(crate) bc_start_ptr: BcPtrAddr<'a>,
}

impl<'a> EvalCallbacksEnabled<'a> {
    fn before_stmt(&mut self, eval: &mut Evaluator, ip: BcPtrAddr) {
        let offset = ip.offset_from(self.bc_start_ptr);
        if let Some(loc) = self.stmt_locs.stmt_at(offset) {
            before_stmt(loc.span, eval);
        }
    }
}

impl<'a> EvaluationCallbacks for EvalCallbacksEnabled<'a> {
    #[inline(always)]
    fn before_instr(&mut self, eval: &mut Evaluator, ip: BcPtrAddr, opcode: BcOpcode) {
        if self.bc_profile {
            eval.eval_instrumentation.bc_profile.before_instr(opcode)
        }
        if self.before_stmt {
            self.before_stmt(eval, ip);
        }
    }
}

// This function should be called before every meaningful statement.
// The purposes are GC, profiling and debugging.
//
// This function is called only if `before_stmt` is set before compilation start.
pub(crate) fn before_stmt(span: FrameSpan, eval: &mut Evaluator) {
    assert!(
        eval.eval_instrumentation.before_stmt.enabled(),
        "this code should only be called if `before_stmt` is set"
    );
    let mut fs = mem::take(&mut eval.eval_instrumentation.before_stmt.before_stmt);
    for f in &mut fs {
        f.call(span.span.file_span_ref(), eval)
    }
    let added = mem::replace(&mut eval.eval_instrumentation.before_stmt.before_stmt, fs);
    assert!(
        added.is_empty(),
        "`before_stmt` cannot be modified during evaluation"
    );
}


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

//! Define variants of the evaluation function with different support
//! for the `load(...)` statement.

use std::collections::HashMap;

use dupe::Dupe;

use crate::environment::FrozenModule;

/// A trait for turning a `path` given by a `load()` statement into a [`FrozenModule`].
pub trait FileLoader {
    /// Open the file given by the load statement `path`.
    fn load(&self, path: &str) -> anyhow::Result<FrozenModule>;
}

/// [`FileLoader`] that looks up modules by name from a [`HashMap`].
///
/// A list of all load statements can be obtained through
/// [`AstModule::loads`](crate::syntax::AstModule::loads).
/// This struct will raise an error if any requested files are not available.
pub struct ReturnFileLoader<'a> {
    /// Map from module name (first argument to `load` statement) to the actual module.
    pub modules: &'a HashMap<&'a str, &'a FrozenModule>,
}

impl<'a> FileLoader for ReturnFileLoader<'a> {
    fn load(&self, path: &str) -> anyhow::Result<FrozenModule> {
        match self.modules.get(path) {
            Some(v) => Ok((*v).dupe()),
            None => Err(anyhow::anyhow!(
                "ReturnFileLoader does not know the module `{}`",
                path
            )),
        }
    }
}

/// Same as [`ReturnFileLoader`], but does not require fighting the borrow checker.
pub(crate) struct ReturnOwnedFileLoader {
    pub(crate) modules: HashMap<String, FrozenModule>,
}

impl FileLoader for ReturnOwnedFileLoader {
    fn load(&self, path: &str) -> anyhow::Result<FrozenModule> {
        match self.modules.get(path) {
            Some(v) => Ok(v.dupe()),
            None => Err(anyhow::anyhow!(
                "ReturnOwnedFileLoader does not know the module `{}`",
                path
            )),
        }
    }
}


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

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

use dupe::Dupe;

use crate::eval::runtime::frozen_file_span::FrozenFileSpan;
use crate::eval::runtime::inlined_frame::InlinedFrames;

/// Span of the call frame (including inlined call frames).
#[derive(Debug, Clone, Copy, Dupe, PartialEq, Eq, Default)]
pub(crate) struct FrameSpan {
    pub(crate) span: FrozenFileSpan,
    /// Parent frames.
    pub(crate) inlined_frames: InlinedFrames,
}

impl FrameSpan {
    pub(crate) const fn new(span: FrozenFileSpan) -> FrameSpan {
        FrameSpan {
            span,
            inlined_frames: InlinedFrames { frames: None },
        }
    }

    pub(crate) fn end_span(&self) -> FrameSpan {
        FrameSpan {
            span: self.span.end_span(),
            inlined_frames: self.inlined_frames,
        }
    }
}

impl Display for FrameSpan {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // TODO(nga): either display inlined frames or remove this.
        Display::fmt(&self.span, f)
    }
}

impl FrameSpan {
    pub(crate) fn merge(&self, other: &FrameSpan) -> FrameSpan {
        // TODO(nga): merge inlined frames.
        FrameSpan {
            span: self.span.merge(&other.span),
            inlined_frames: self.inlined_frames,
        }
    }
}


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

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

use dupe::Dupe;

use crate::codemap::CodeMap;
use crate::codemap::FileSpan;
use crate::codemap::FileSpanRef;
use crate::codemap::Span;
use crate::values::FrozenRef;

#[derive(Debug, Copy, Clone, Dupe, PartialEq, Eq)]
pub(crate) struct FrozenFileSpan {
    file: FrozenRef<'static, CodeMap>,
    span: Span,
}

impl Display for FrozenFileSpan {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.to_file_span(), f)
    }
}

impl Default for FrozenFileSpan {
    fn default() -> FrozenFileSpan {
        FrozenFileSpan::new(FrozenRef::new(CodeMap::empty_static()), Span::default())
    }
}

impl FrozenFileSpan {
    pub(crate) const fn new_unchecked(
        file: FrozenRef<'static, CodeMap>,
        span: Span,
    ) -> FrozenFileSpan {
        FrozenFileSpan { file, span }
    }

    pub(crate) fn new(file: FrozenRef<'static, CodeMap>, span: Span) -> FrozenFileSpan {
        // Check the span is valid: this will panic if the span is not valid.
        file.source_span(span);

        Self::new_unchecked(file, span)
    }

    pub(crate) fn file(&self) -> FrozenRef<'static, CodeMap> {
        self.file
    }

    pub(crate) fn span(&self) -> Span {
        self.span
    }

    pub(crate) fn end_span(&self) -> FrozenFileSpan {
        FrozenFileSpan {
            file: self.file,
            span: self.span.end_span(),
        }
    }

    pub(crate) fn file_span_ref(&self) -> FileSpanRef<'static> {
        FileSpanRef {
            file: self.file.as_ref(),
            span: self.span,
        }
    }

    pub(crate) fn to_file_span(&self) -> FileSpan {
        FileSpan {
            file: (*self.file).dupe(),
            span: self.span,
        }
    }

    pub(crate) fn merge(&self, other: &FrozenFileSpan) -> FrozenFileSpan {
        if self.file == other.file {
            FrozenFileSpan {
                file: self.file,
                span: self.span.merge(other.span),
            }
        } else {
            // We need to pick something if we merge two spans from different files.
            *self
        }
    }
}


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

use std::ptr;

use dupe::Dupe;

use crate::errors::Frame;
use crate::eval::runtime::frame_span::FrameSpan;
use crate::values::FrozenHeap;
use crate::values::FrozenRef;
use crate::values::FrozenValue;

/// When a function `a` is inlined into `b`, this struct contains
/// the inlined frame for expressions in `a` which now reside in `b`.
#[derive(Debug, PartialEq)]
pub(crate) struct InlinedFrame {
    pub(crate) span: FrameSpan,
    pub(crate) fun: FrozenValue,
}

impl InlinedFrame {
    /// Recursively collect frames.
    ///
    /// Resulting frames are ordered bottom-to-top, same order as in `CallStack`.
    pub(crate) fn extend_frames(&self, frames: &mut Vec<Frame>) {
        frames.push(Frame {
            name: self.fun.to_value().name_for_call_stack(),
            location: Some(self.span.span.to_file_span()),
        });
        self.span.inlined_frames.extend_frames(frames);
    }
}

/// Stack of inlined frames (maybe empty).
#[derive(Copy, Clone, Dupe, Debug, Default)]
pub(crate) struct InlinedFrames {
    /// Linked list.
    pub(crate) frames: Option<FrozenRef<'static, InlinedFrame>>,
}

impl PartialEq for InlinedFrames {
    fn eq(&self, other: &Self) -> bool {
        match (self.frames, other.frames) {
            (Some(a), Some(b)) => ptr::eq(a.as_ref(), b.as_ref()),
            (None, None) => true,
            (Some(_), None) | (None, Some(_)) => false,
        }
    }
}

impl Eq for InlinedFrames {}

impl InlinedFrames {
    /// Collect frames, bottom-to-top, same order as in `CallStack`.
    pub(crate) fn extend_frames(self, frames: &mut Vec<Frame>) {
        if let Some(f) = self.frames {
            f.extend_frames(frames);
        }
    }

    fn to_inlined_frames(self) -> Vec<FrozenRef<'static, InlinedFrame>> {
        let mut r = Vec::new();
        let mut frames_iter = self;
        while let Some(frames) = frames_iter.frames {
            r.push(frames);
            frames_iter = frames.span.inlined_frames;
        }
        r
    }

    /// Inline this stack into given span.
    ///
    /// E. g. when inlining `def a(): return {}` into `def b(): a()`,
    /// self is empty stack for expression `{}`, `span` is `a()` and `fun` is `a`.
    pub(crate) fn inline_into(
        &mut self,
        span: FrameSpan,
        fun: FrozenValue,
        span_alloc: &mut InlinedFrameAlloc,
    ) {
        self.frames = Some(span_alloc.alloc_frame(InlinedFrame {
            span: FrameSpan {
                span: span.span,
                inlined_frames: *self,
            },
            fun,
        }));
        for f in span.inlined_frames.to_inlined_frames().into_iter().rev() {
            self.frames = Some(span_alloc.alloc_frame(InlinedFrame {
                span: FrameSpan {
                    span: f.span.span,
                    inlined_frames: *self,
                },
                fun: f.fun,
            }));
        }
    }
}

/// Heap allocator for `InlinedFrame` which attempts to reuse previous allocation.
pub(crate) struct InlinedFrameAlloc<'f> {
    frozen_heap: &'f FrozenHeap,
    last_alloc: Option<FrozenRef<'static, InlinedFrame>>,
}

impl<'f> InlinedFrameAlloc<'f> {
    pub(crate) fn new(frozen_heap: &'f FrozenHeap) -> Self {
        Self {
            frozen_heap,
            last_alloc: None,
        }
    }

    pub(crate) fn alloc_frame(&mut self, frame: InlinedFrame) -> FrozenRef<'static, InlinedFrame> {
        if let Some(last_alloc) = self.last_alloc {
            if *last_alloc == frame {
                return last_alloc;
            }
        }
        let frame = self.frozen_heap.alloc_any_display_from_debug(frame);
        self.last_alloc = Some(frame);
        frame
    }
}

#[cfg(test)]
mod tests {
    use crate::codemap::CodeMap;
    use crate::eval::runtime::frame_span::FrameSpan;
    use crate::eval::runtime::frozen_file_span::FrozenFileSpan;
    use crate::eval::runtime::inlined_frame::InlinedFrameAlloc;
    use crate::eval::runtime::inlined_frame::InlinedFrames;
    use crate::slice_vec_ext::SliceExt;
    use crate::values::FrozenHeap;

    #[test]
    fn test_inline_into() {
        // Test frame inlining with this code:
        //
        // ```
        // def a(): return {}
        // def b(): return a()
        // def c(): return b()
        //
        // def d(): return c()
        // def e(): return d()
        // def f(): return e()
        // ```
        //
        // If `a` is inlined into `b` and then inlined into `c`,
        // then if `d` is inlined into `e` and then inlined into `f`,
        // and then `c` is inlined into `d` (which is already inlined into `f`),
        // the resulting stack trace should be `f`, `e`, `d`, `c`, `b`, `a`.

        let frozen_heap = FrozenHeap::new();

        fn make_span(heap: &FrozenHeap, text: &str) -> FrameSpan {
            let codemap = CodeMap::new(format!("{}.bzl", text), text.to_owned());
            let codemap = heap.alloc_any_display_from_debug(codemap);
            FrameSpan {
                span: FrozenFileSpan::new(codemap, codemap.full_span()),
                inlined_frames: InlinedFrames::default(),
            }
        }

        let mut span_alloc = InlinedFrameAlloc::new(&frozen_heap);

        fn assert_stack(expected: &[&str], span: &FrameSpan) {
            let mut frames = Vec::new();
            span.inlined_frames.extend_frames(&mut frames);
            let frames = frames.map(|f| {
                let span = f.location.as_ref().unwrap().source_span();
                let f = f.name.trim_matches('"');
                format!("{} in {}", span, f)
            });
            assert_eq!(expected, &frames);
        }

        let mut a = make_span(&frozen_heap, "{}");
        let b = make_span(&frozen_heap, "a()");
        let c = make_span(&frozen_heap, "b()");
        a.inlined_frames.inline_into(
            b,
            frozen_heap.alloc_str("b").to_frozen_value(),
            &mut span_alloc,
        );
        a.inlined_frames.inline_into(
            c,
            frozen_heap.alloc_str("c").to_frozen_value(),
            &mut span_alloc,
        );

        assert_stack(&["b() in c", "a() in b"], &a);

        let mut d = make_span(&frozen_heap, "c()");
        let e = make_span(&frozen_heap, "d()");
        let f = make_span(&frozen_heap, "e()");

        d.inlined_frames.inline_into(
            e,
            frozen_heap.alloc_str("e").to_frozen_value(),
            &mut span_alloc,
        );
        d.inlined_frames.inline_into(
            f,
            frozen_heap.alloc_str("f").to_frozen_value(),
            &mut span_alloc,
        );

        assert_stack(&["e() in f", "d() in e"], &d);

        a.inlined_frames.inline_into(
            d,
            frozen_heap.alloc_str("d").to_frozen_value(),
            &mut span_alloc,
        );

        assert_stack(
            &["e() in f", "d() in e", "c() in d", "b() in c", "a() in b"],
            &a,
        );
    }
}


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

pub(crate) mod arguments;
pub(crate) mod before_stmt;
pub(crate) mod call_stack;
pub(crate) mod evaluator;
pub(crate) mod file_loader;
pub(crate) mod frame_span;
pub(crate) mod frozen_file_span;
pub(crate) mod inlined_frame;
pub(crate) mod params;
pub(crate) mod profile;
pub(crate) mod rust_loc;
pub(crate) mod slots;
pub(crate) mod small_duration;
pub(crate) mod visit_span;


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

//! Function parameters.

use std::cell::Cell;
use std::cmp;
use std::collections::HashMap;

use allocative::Allocative;
use dupe::Dupe;
use starlark_derive::Freeze;
use starlark_derive::Trace;
use starlark_map::small_map::SmallMap;
use starlark_map::Hashed;

use crate as starlark;
use crate::coerce::coerce;
use crate::coerce::Coerce;
use crate::collections::symbol_map::SymbolMap;
use crate::docs::DocParam;
use crate::docs::DocString;
use crate::docs::DocType;
use crate::eval::runtime::arguments::ArgSymbol;
use crate::eval::runtime::arguments::ArgumentsImpl;
use crate::eval::runtime::arguments::FunctionError;
use crate::eval::runtime::arguments::ResolvedArgName;
use crate::eval::Arguments;
use crate::eval::Evaluator;
use crate::hint::unlikely;
use crate::values::dict::Dict;
use crate::values::dict::DictRef;
use crate::values::Heap;
use crate::values::StringValue;
use crate::values::UnpackValue;
use crate::values::Value;
use crate::values::ValueLike;

#[derive(Debug, Copy, Clone, Dupe, Coerce, PartialEq, Trace, Freeze, Allocative)]
#[repr(C)]
pub(crate) enum ParameterKind<V> {
    Required,
    /// When optional parameter is not supplied, there's no error,
    /// but the slot remains `None`.
    ///
    /// This is used only in native code, parameters of type `Option<T>` become `Optional`.
    Optional,
    Defaulted(V),
    Args,
    KWargs,
}

#[derive(Debug, Copy, Clone, Dupe, PartialEq, Eq, PartialOrd, Ord)]
enum CurrentParameterStyle {
    /// Parameter can be only filled positionally.
    PosOnly,
    /// Parameter can be filled positionally or by name.
    PosOrNamed,
    /// Parameter can be filled by name only.
    NamedOnly,
    /// No more args accepted.
    NoMore,
}

/// Builder for [`ParametersSpec`]
pub struct ParametersSpecBuilder<V> {
    function_name: String,
    params: Vec<(String, ParameterKind<V>)>,
    names: SymbolMap<u32>,
    positional: usize,

    /// Has the no_args been passed
    current_style: CurrentParameterStyle,

    args: Option<usize>,
    kwargs: Option<usize>,
}

/// Define a list of parameters. This code assumes that all names are distinct and that
/// `*args`/`**kwargs` occur in well-formed locations.
// V = Value, or FrozenValue
#[derive(Debug, Clone, Trace, Freeze, Allocative)]
#[repr(C)]
pub struct ParametersSpec<V> {
    /// Only used in error messages
    function_name: String,

    /// Parameters in the order they occur.
    param_kinds: Box<[ParameterKind<V>]>,
    /// Parameter names in the order they occur.
    param_names: Box<[String]>,
    /// Mapping from name to index where the argument lives.
    #[freeze(identity)]
    pub(crate) names: SymbolMap<u32>,

    /// Number of arguments that can be filled positionally.
    /// Excludes *args/**kwargs, keyword arguments after *args
    positional: u32,

    /// The index at which *args should go
    args: Option<u32>,
    /// The index at which **kwargs should go
    kwargs: Option<u32>,
}

// Can't derive this since we don't want ParameterKind to be public
unsafe impl<From: Coerce<To>, To> Coerce<ParametersSpec<To>> for ParametersSpec<From> {}

impl<V: Copy> ParametersSpecBuilder<V> {
    fn add(&mut self, name: &str, val: ParameterKind<V>) {
        assert!(!matches!(val, ParameterKind::Args | ParameterKind::KWargs));

        // Regular arguments cannot follow `**kwargs`, but can follow `*args`.
        assert!(self.current_style < CurrentParameterStyle::NoMore);
        assert!(self.kwargs.is_none());

        let i = self.params.len();
        self.params.push((name.to_owned(), val));
        if self.current_style != CurrentParameterStyle::PosOnly {
            let old = self.names.insert(name, i.try_into().unwrap());
            assert!(old.is_none(), "Repeated parameter `{}`", name);
        }
        if self.args.is_none() && self.current_style != CurrentParameterStyle::NamedOnly {
            // If you've already seen `args` or `no_args`, you can't enter these
            // positionally
            self.positional = i + 1;
        }
    }

    /// Add a required parameter. Will be an error if the caller doesn't supply
    /// it. If you want to supply a position-only argument, prepend a `$` to
    /// the name.
    pub fn required(&mut self, name: &str) {
        self.add(name, ParameterKind::Required);
    }

    /// Add an optional parameter. Will be None if the caller doesn't supply it.
    /// If you want to supply a position-only argument, prepend a `$` to the
    /// name.
    pub fn optional(&mut self, name: &str) {
        self.add(name, ParameterKind::Optional);
    }

    /// Add an optional parameter. Will be the default value if the caller
    /// doesn't supply it. If you want to supply a position-only argument,
    /// prepend a `$` to the name.
    pub fn defaulted(&mut self, name: &str, val: V) {
        self.add(name, ParameterKind::Defaulted(val));
    }

    /// Add an `*args` parameter which will be an iterable sequence of parameters,
    /// recorded into a [`Vec`]. A function can only have one `args`
    /// parameter. After this call, any subsequent
    /// [`required`](ParametersSpecBuilder::required),
    /// [`optional`](ParametersSpecBuilder::optional) or
    /// [`defaulted`](ParametersSpecBuilder::defaulted)
    /// parameters can _only_ be supplied by name.
    pub fn args(&mut self) {
        assert!(self.args.is_none());
        assert!(self.current_style < CurrentParameterStyle::NamedOnly);
        assert!(self.kwargs.is_none());
        self.params.push(("*args".to_owned(), ParameterKind::Args));
        self.args = Some(self.params.len() - 1);
        self.current_style = CurrentParameterStyle::NamedOnly;
    }

    /// Following parameters can be filled positionally or by name.
    pub fn no_more_positional_only_args(&mut self) {
        assert_eq!(self.current_style, CurrentParameterStyle::PosOnly);
        self.current_style = CurrentParameterStyle::PosOrNamed;
    }

    /// This function has no `*args` parameter, corresponds to the Python parameter `*`.
    /// After this call, any subsequent
    /// [`required`](ParametersSpecBuilder::required),
    /// [`optional`](ParametersSpecBuilder::optional) or
    /// [`defaulted`](ParametersSpecBuilder::defaulted)
    /// parameters can _only_ be supplied by name.
    pub fn no_more_positional_args(&mut self) {
        assert!(self.args.is_none());
        assert!(self.current_style < CurrentParameterStyle::NamedOnly);
        assert!(self.kwargs.is_none());
        self.current_style = CurrentParameterStyle::NamedOnly;
    }

    /// Add a `**kwargs` parameter which will be a dictionary, recorded into a [`SmallMap`].
    /// A function can only have one `kwargs` parameter.
    /// parameter. After this call, any subsequent
    /// [`required`](ParametersSpecBuilder::required),
    /// [`optional`](ParametersSpecBuilder::optional) or
    /// [`defaulted`](ParametersSpecBuilder::defaulted)
    /// parameters can _only_ be supplied by position.
    pub fn kwargs(&mut self) {
        assert!(self.kwargs.is_none());
        self.params
            .push(("**kwargs".to_owned(), ParameterKind::KWargs));
        self.current_style = CurrentParameterStyle::NoMore;
        self.kwargs = Some(self.params.len() - 1);
    }

    /// Construct the parameters specification.
    pub fn finish(self) -> ParametersSpec<V> {
        let ParametersSpecBuilder {
            function_name,
            positional,
            args,
            current_style,
            kwargs,
            params,
            names,
        } = self;
        let _ = current_style;
        ParametersSpec {
            function_name,
            param_kinds: params.iter().map(|p| p.1).collect(),
            param_names: params.into_iter().map(|p| p.0).collect(),
            names,
            positional: positional.try_into().unwrap(),
            args: args.map(|args| args.try_into().unwrap()),
            kwargs: kwargs.map(|kwargs| kwargs.try_into().unwrap()),
        }
    }
}

impl<V> ParametersSpec<V> {
    /// Create a new [`ParametersSpec`] with the given function name.
    pub fn new(function_name: String) -> ParametersSpecBuilder<V> {
        Self::with_capacity(function_name, 0)
    }

    /// Create a new [`ParametersSpec`] with the given function name and an advance capacity hint.
    pub fn with_capacity(function_name: String, capacity: usize) -> ParametersSpecBuilder<V> {
        ParametersSpecBuilder {
            function_name,
            params: Vec::with_capacity(capacity),
            names: SymbolMap::with_capacity(capacity),
            positional: 0,
            current_style: CurrentParameterStyle::PosOnly,
            args: None,
            kwargs: None,
        }
    }

    /// Produce an approximate signature for the function, combining the name and arguments.
    pub fn signature(&self) -> String {
        let mut collector = String::new();
        self.collect_signature(&mut collector);
        collector
    }

    // Generate a good error message for it
    pub(crate) fn collect_signature(&self, collector: &mut String) {
        collector.push_str(&self.function_name);

        // We used to make the "name" of a function include all its parameters, but that is a lot of
        // details and visually crowds out everything else. Try disabling, although we might want it
        // in some contexts, so don't delete it.
    }

    /// Function parameter as they would appear in `def`
    /// (excluding types, default values and formatting).
    pub fn parameters_str(&self) -> String {
        let mut emitted_star = false;
        let mut collector = String::new();
        for (i, typ) in self.iter_params().enumerate() {
            if !collector.is_empty() {
                collector.push_str(", ");
            }

            // TODO: also print `/` for positional-only parameters.

            if i == (self.positional as usize)
                && !emitted_star
                && !matches!(typ.1, ParameterKind::Args | ParameterKind::KWargs)
            {
                collector.push_str("*, ");
                emitted_star = true;
            }

            match typ.1 {
                ParameterKind::Args | ParameterKind::KWargs => {
                    // For `*args` or `**kwargs` param name includes the `*` or `**`.
                    collector.push_str(typ.0);
                    emitted_star = true;
                }
                ParameterKind::Required => {
                    collector.push_str(typ.0);
                }
                ParameterKind::Optional | ParameterKind::Defaulted(_) => {
                    collector.push_str(typ.0);
                    collector.push_str(" = ...");
                }
            }
        }
        collector
    }

    /// Get the index where a user would have supplied "*" as a parameter.
    pub(crate) fn no_args_param_index(&self) -> Option<usize> {
        if (self.positional as usize) < self.param_kinds.len() {
            match self.param_kinds.get(self.positional as usize) {
                Some(ParameterKind::Args) | Some(ParameterKind::KWargs) => None,
                _ => Some(self.positional as usize),
            }
        } else {
            None
        }
    }

    /// Iterate over the parameters
    ///
    /// Returns an iterator over (parameter index, name, kind)
    pub(crate) fn iter_params(&self) -> impl Iterator<Item = (&str, &ParameterKind<V>)> {
        assert_eq!(self.param_names.len(), self.param_kinds.len());
        self.param_names
            .iter()
            .map(|name| name.as_str())
            .zip(&*self.param_kinds)
    }

    pub(crate) fn resolve_name(&self, name: Hashed<&str>) -> ResolvedArgName {
        let hash = name.hash();
        let param_index = self.names.get_hashed_str(name).copied();
        ResolvedArgName { hash, param_index }
    }

    pub(crate) fn has_args_or_kwargs(&self) -> bool {
        self.args.is_some() || self.kwargs.is_some()
    }
}

impl<'v, V: ValueLike<'v>> ParametersSpec<V> {
    /// Number of function parameters.
    pub fn len(&self) -> usize {
        self.param_kinds.len()
    }

    /// Move parameters from [`Arguments`] to a list of [`Value`],
    /// using the supplied [`ParametersSpec`].
    pub fn collect(
        &self,
        args: &Arguments<'v, '_>,
        slots: &[Cell<Option<Value<'v>>>],
        heap: &'v Heap,
    ) -> anyhow::Result<()> {
        self.collect_inline(&args.0, slots, heap)
    }

    /// Collect `N` arguments.
    ///
    /// This function is called by generated code.
    pub fn collect_into<const N: usize>(
        &self,
        args: &Arguments<'v, '_>,
        heap: &'v Heap,
    ) -> anyhow::Result<[Cell<Option<Value<'v>>>; N]> {
        let slots = [(); N].map(|_| Cell::new(None));
        self.collect(args, &slots, heap)?;
        Ok(slots)
    }

    /// A variant of collect that is always inlined
    /// for Def and NativeFunction that are hot-spots
    #[inline(always)]
    pub(crate) fn collect_inline<'a, A: ArgumentsImpl<'v, 'a>>(
        &self,
        args: &A,
        slots: &[Cell<Option<Value<'v>>>],
        heap: &'v Heap,
    ) -> anyhow::Result<()>
    where
        'v: 'a,
    {
        // If the arguments equal the length and the kinds, and we don't have any other args,
        // then no_args, *args and **kwargs must all be unset,
        // and we don't have to crate args/kwargs objects, we can skip everything else
        if args.pos().len() == (self.positional as usize)
            && args.pos().len() == self.param_kinds.len()
            && args.named().is_empty()
            && args.args().is_none()
            && args.kwargs().is_none()
        {
            for (v, s) in args.pos().iter().zip(slots.iter()) {
                s.set(Some(*v));
            }

            return Ok(());
        }

        self.collect_slow(args, slots, heap)
    }

    fn collect_slow<'a, A: ArgumentsImpl<'v, 'a>>(
        &self,
        args: &A,
        slots: &[Cell<Option<Value<'v>>>],
        heap: &'v Heap,
    ) -> anyhow::Result<()>
    where
        'v: 'a,
    {
        /// Lazily initialized `kwargs` object.
        #[derive(Default)]
        struct LazyKwargs<'v> {
            kwargs: Option<SmallMap<StringValue<'v>, Value<'v>>>,
        }

        impl<'v> LazyKwargs<'v> {
            // Return true if the value is a duplicate
            #[inline(always)]
            fn insert(&mut self, key: Hashed<StringValue<'v>>, val: Value<'v>) -> bool {
                match &mut self.kwargs {
                    None => {
                        let mut mp = SmallMap::with_capacity(12);
                        mp.insert_hashed_unique_unchecked(key, val);
                        self.kwargs = Some(mp);
                        false
                    }
                    Some(mp) => mp.insert_hashed(key, val).is_some(),
                }
            }

            fn alloc(self, heap: &'v Heap) -> Value<'v> {
                let kwargs = match self.kwargs {
                    Some(kwargs) => Dict::new(coerce(kwargs)),
                    None => Dict::default(),
                };
                heap.alloc(kwargs)
            }
        }

        let len = self.param_kinds.len();
        // We might do unchecked stuff later on, so make sure we have as many slots as we expect
        assert!(slots.len() >= len);

        let mut star_args = Vec::new();
        let mut kwargs = LazyKwargs::default();
        let mut next_position = 0;

        // First deal with positional parameters
        if args.pos().len() <= (self.positional as usize) {
            // fast path for when we don't need to bounce down to filling in args
            for (v, s) in args.pos().iter().zip(slots.iter()) {
                s.set(Some(*v));
            }
            next_position = args.pos().len();
        } else {
            for v in args.pos() {
                if next_position < (self.positional as usize) {
                    slots[next_position].set(Some(*v));
                    next_position += 1;
                } else {
                    star_args.push(*v);
                }
            }
        }

        // Next deal with named parameters
        // The lowest position at which we've written a name.
        // If at the end lowest_name is less than next_position, we got the same variable twice.
        // So no duplicate checking until after all positional arguments
        let mut lowest_name = usize::MAX;
        // Avoid a lot of loop setup etc in the common case
        if !args.names().is_empty() {
            for ((name, name_value), v) in args.names().iter().zip(args.named()) {
                // Safe to use new_unchecked because hash for the Value and str are the same
                match name.get_index_from_param_spec(self) {
                    None => {
                        kwargs.insert(Hashed::new_unchecked(name.small_hash(), *name_value), *v);
                    }
                    Some(i) => {
                        slots[i].set(Some(*v));
                        lowest_name = cmp::min(lowest_name, i);
                    }
                }
            }
        }

        // Next up are the *args parameters
        if let Some(param_args) = args.args() {
            for v in param_args
                .iterate(heap)
                .map_err(|_| FunctionError::ArgsArrayIsNotIterable)?
            {
                if next_position < (self.positional as usize) {
                    slots[next_position].set(Some(v));
                    next_position += 1;
                } else {
                    star_args.push(v);
                }
            }
        }

        // Check if the named arguments clashed with the positional arguments
        if unlikely(next_position > lowest_name) {
            return Err(FunctionError::RepeatedArg {
                name: self.param_names[lowest_name].clone(),
            }
            .into());
        }

        // Now insert the kwargs, if there are any
        if let Some(param_kwargs) = args.kwargs() {
            match DictRef::from_value(param_kwargs) {
                Some(y) => {
                    for (k, v) in y.iter_hashed() {
                        match StringValue::new(*k.key()) {
                            None => return Err(FunctionError::ArgsValueIsNotString.into()),
                            Some(s) => {
                                let repeat = match self
                                    .names
                                    .get_hashed_string_value(Hashed::new_unchecked(k.hash(), s))
                                {
                                    None => kwargs.insert(Hashed::new_unchecked(k.hash(), s), v),
                                    Some(i) => {
                                        let this_slot = &slots[*i as usize];
                                        let repeat = this_slot.get().is_some();
                                        this_slot.set(Some(v));
                                        repeat
                                    }
                                };
                                if unlikely(repeat) {
                                    return Err(FunctionError::RepeatedArg {
                                        name: s.as_str().to_owned(),
                                    }
                                    .into());
                                }
                            }
                        }
                    }
                }
                None => return Err(FunctionError::KwArgsIsNotDict.into()),
            }
        }

        // We have moved parameters into all the relevant slots, so need to finalise things.
        // We need to set default values and error if any required values are missing
        let kinds = &*self.param_kinds;
        // This code is very hot, and setting up iterators was a noticeable bottleneck.
        for index in next_position..kinds.len() {
            // The number of locals must be at least the number of parameters, see `collect`
            // which reserves `max(_, kinds.len())`.
            let slot = unsafe { slots.get_unchecked(index) };
            let def = unsafe { kinds.get_unchecked(index) };

            // We know that up to next_position got filled positionally, so we don't need to check those
            if slot.get().is_some() {
                continue;
            }
            match def {
                ParameterKind::Required => {
                    return Err(FunctionError::MissingParameter {
                        name: self.param_names[index].clone(),
                        function: self.signature(),
                    }
                    .into());
                }
                ParameterKind::Defaulted(x) => {
                    slot.set(Some(x.to_value()));
                }
                _ => {}
            }
        }

        // Now set the kwargs/args slots, if they are requested, and fail it they are absent but used
        // Note that we deliberately give warnings about missing parameters _before_ giving warnings
        // about unexpected extra parameters, so if a user misspells an argument they get a better error.
        if let Some(args_pos) = self.args {
            slots[args_pos as usize].set(Some(heap.alloc_tuple(&star_args)));
        } else if unlikely(!star_args.is_empty()) {
            return Err(FunctionError::ExtraPositionalArg {
                count: star_args.len(),
                function: self.signature(),
            }
            .into());
        }

        if let Some(kwargs_pos) = self.kwargs {
            slots[kwargs_pos as usize].set(Some(kwargs.alloc(heap)));
        } else if let Some(kwargs) = kwargs.kwargs {
            return Err(FunctionError::ExtraNamedArg {
                names: kwargs.keys().map(|x| x.as_str().to_owned()).collect(),
                function: self.signature(),
            }
            .into());
        }
        Ok(())
    }

    /// Check if current parameters can be filled with given arguments signature.
    #[allow(clippy::needless_range_loop)]
    pub fn can_fill_with_args(&self, pos: usize, names: &[&str]) -> bool {
        let mut filled = vec![false; self.param_kinds.len()];
        for p in 0..pos {
            if p < (self.positional as usize) {
                filled[p] = true;
            } else if self.args.is_some() {
                // Filled into `*args`.
            } else {
                return false;
            }
        }
        if pos > (self.positional as usize) && self.args.is_none() {
            return false;
        }
        for name in names {
            match self.names.get_str(name) {
                Some(i) => {
                    if filled[*i as usize] {
                        // Duplicate argument.
                        return false;
                    }
                    filled[*i as usize] = true;
                }
                None => {
                    if self.kwargs.is_none() {
                        return false;
                    }
                }
            }
        }
        for (filled, p) in filled.iter().zip(self.param_kinds.iter()) {
            if *filled {
                continue;
            }
            match p {
                ParameterKind::Args => {}
                ParameterKind::KWargs => {}
                ParameterKind::Defaulted(_) => {}
                ParameterKind::Optional => {}
                ParameterKind::Required => return false,
            }
        }
        true
    }

    /// Generate documentation for each of the parameters.
    ///
    /// # Arguments
    /// * `parameter_types` should be a mapping of parameter index to type
    /// * `parameter_docs` should be a mapping of parameter name to possible documentation for
    ///                    that parameter
    pub fn documentation(
        &self,
        mut parameter_types: HashMap<usize, DocType>,
        mut parameter_docs: HashMap<String, Option<DocString>>,
    ) -> Vec<DocParam> {
        let mut pos_only = 0;
        let mut params: Vec<DocParam> = self
            .iter_params()
            .enumerate()
            .map(|(i, (name, kind))| {
                let typ = parameter_types.remove(&i);
                let docs = parameter_docs.remove(name).flatten();
                if pos_only == i
                    && !matches!(kind, ParameterKind::Args | ParameterKind::KWargs)
                    && self.names.get_str(name).is_none()
                {
                    pos_only += 1;
                }
                let name = name.to_owned();
                match kind {
                    ParameterKind::Required => DocParam::Arg {
                        name,
                        docs,
                        typ,
                        default_value: None,
                    },
                    ParameterKind::Optional => DocParam::Arg {
                        name,
                        docs,
                        typ,
                        default_value: Some("_".to_owned()),
                    },
                    ParameterKind::Defaulted(v) => DocParam::Arg {
                        name,
                        docs,
                        typ,
                        default_value: Some(v.to_value().to_repr()),
                    },
                    ParameterKind::Args => DocParam::Args { name, docs, typ },
                    ParameterKind::KWargs => DocParam::Kwargs { name, docs, typ },
                }
            })
            .collect();

        // Go back and add the "*" arg if it's present
        if let Some(i) = self.no_args_param_index() {
            params.insert(i, DocParam::NoArgs);
        }

        if pos_only > 0 {
            params.insert(pos_only, DocParam::OnlyPosBefore);
        }

        params
    }

    /// Create a [`ParametersParser`] for given arguments.
    pub fn parser<R, F>(
        &self,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
        k: F,
    ) -> anyhow::Result<R>
    where
        F: FnOnce(ParametersParser<'v, '_>, &mut Evaluator<'v, '_>) -> anyhow::Result<R>,
    {
        eval.alloca_init(
            self.len(),
            || Cell::new(None),
            |slots, eval| {
                self.collect_inline(&args.0, slots, eval.heap())?;
                let parser = ParametersParser::new(slots);
                k(parser, eval)
            },
        )
    }
}

/// Parse a series of parameters which were specified by [`ParametersSpec`].
///
/// This is usually created with [`ParametersSpec::parser`].
pub struct ParametersParser<'v, 'a>(std::slice::Iter<'a, Cell<Option<Value<'v>>>>);

impl<'v, 'a> ParametersParser<'v, 'a> {
    /// Create a parameter parser, which stored parameters into provided slots reference.
    pub fn new(slots: &'a [Cell<Option<Value<'v>>>]) -> Self {
        Self(slots.iter())
    }

    fn get_next(&mut self) -> Option<Value<'v>> {
        let v = self
            .0
            .next()
            .expect("ParametersParser: wrong number of requested arguments");
        v.get()
    }

    /// Obtain the next parameter, corresponding to [`ParametersSpecBuilder::optional`].
    /// It is an error to request more parameters than were specified.
    /// The `name` is only used for error messages.
    pub fn next_opt<T: UnpackValue<'v>>(&mut self, name: &str) -> anyhow::Result<Option<T>> {
        match self.get_next() {
            None => Ok(None),
            Some(v) => Ok(Some(T::unpack_named_param(v, name)?)),
        }
    }

    /// Obtain the next parameter, which can't be defined by [`ParametersSpecBuilder::optional`].
    /// It is an error to request more parameters than were specified.
    /// The `name` is only used for error messages.
    pub fn next<T: UnpackValue<'v>>(&mut self, name: &str) -> anyhow::Result<T> {
        // After ParametersCollect.done() all variables will be Some,
        // apart from those where we called ParametersSpec.optional(),
        // and for those we should call next_opt()

        // This is definitely not unassigned because ParametersCollect.done checked
        // that.
        let v = self.get_next().unwrap();
        T::unpack_named_param(v, name)
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use crate::assert::Assert;
    use crate::docs::DocParam;
    use crate::docs::DocString;
    use crate::docs::DocStringKind;
    use crate::docs::DocType;
    use crate::eval::compiler::def::FrozenDef;
    use crate::eval::runtime::params::ParameterKind;
    use crate::eval::ParametersSpec;
    use crate::typing::Ty;
    use crate::values::FrozenValue;

    #[test]
    fn test_parameter_iteration() {
        let mut p = ParametersSpec::<FrozenValue>::new("f".to_owned());
        p.required("a");
        p.optional("b");
        p.no_more_positional_args();
        p.optional("c");
        p.kwargs();
        let p = p.finish();

        let params: Vec<(&str, &ParameterKind<FrozenValue>)> = p.iter_params().collect();

        let expected: Vec<(&str, &ParameterKind<FrozenValue>)> = vec![
            ("a", &ParameterKind::Required),
            ("b", &ParameterKind::Optional),
            ("c", &ParameterKind::Optional),
            ("**kwargs", &ParameterKind::KWargs),
        ];

        assert_eq!(expected, params);
        assert_eq!(Some(2), p.no_args_param_index());

        let mut p = ParametersSpec::<FrozenValue>::new("f".to_owned());
        p.required("a");
        p.args();
        p.kwargs();
        let p = p.finish();

        let params: Vec<(&str, &ParameterKind<FrozenValue>)> = p.iter_params().collect();

        let expected: Vec<(&str, &ParameterKind<FrozenValue>)> = vec![
            ("a", &ParameterKind::Required),
            ("*args", &ParameterKind::Args),
            ("**kwargs", &ParameterKind::KWargs),
        ];

        assert_eq!(expected, params);
        assert_eq!(None, p.no_args_param_index());

        let mut p = ParametersSpec::<FrozenValue>::new("f".to_owned());
        p.args();
        p.optional("a");
        p.optional("b");
        let p = p.finish();

        let params: Vec<(&str, &ParameterKind<FrozenValue>)> = p.iter_params().collect();

        let expected: Vec<(&str, &ParameterKind<FrozenValue>)> = vec![
            ("*args", &ParameterKind::Args),
            ("a", &ParameterKind::Optional),
            ("b", &ParameterKind::Optional),
        ];

        assert_eq!(expected, params);
    }

    #[test]
    fn test_documentation() -> anyhow::Result<()> {
        // Make sure that documentation for some odder parameter specs works properly.
        let mut p = ParametersSpec::<FrozenValue>::new("f".to_owned());
        p.args();
        p.optional("a");
        p.optional("b");
        let p = p.finish();

        let expected = vec![
            DocParam::Args {
                name: "*args".to_owned(),
                docs: None,
                typ: None,
            },
            DocParam::Arg {
                name: "a".to_owned(),
                docs: None,
                typ: Some(DocType {
                    raw_type: Ty::int(),
                }),
                default_value: Some("_".to_owned()),
            },
            DocParam::Arg {
                name: "b".to_owned(),
                docs: DocString::from_docstring(DocStringKind::Rust, "param b docs"),
                typ: None,
                default_value: Some("_".to_owned()),
            },
        ];
        let mut types = HashMap::new();
        types.insert(
            1,
            DocType {
                raw_type: Ty::int(),
            },
        );
        let mut docs = HashMap::new();
        docs.insert("a".to_owned(), None);
        docs.insert(
            "b".to_owned(),
            DocString::from_docstring(DocStringKind::Rust, "param b docs"),
        );

        let params = p.documentation(types, docs);
        assert_eq!(expected, params);
        Ok(())
    }

    #[test]
    fn test_parameters_str() {
        fn test(sig: &str) {
            let a = Assert::new();
            let f = a
                .pass_module(&format!("def f({sig}): pass"))
                .get("f")
                .unwrap();
            assert_eq!(sig, &f.value().parameters_spec().unwrap().parameters_str());
        }

        test("");

        test("a, b, c, d, e, f, g, h, *args, **kwargs");

        test("*, a");
        test("x, *, a");

        test("*args, a");
        test("x, *args, a");

        test("**kwargs");
        test("a, **kwargs");
    }

    #[test]
    fn test_can_fill_with_args() {
        fn test(sig: &str, pos: usize, names: &[&str], expected: bool) {
            let a = Assert::new();
            let module = a.pass_module(&format!("def f({}): pass", sig));
            let f = module.get("f").unwrap().downcast::<FrozenDef>().unwrap();
            let parameters_spec = &f.parameters;
            assert_eq!(expected, parameters_spec.can_fill_with_args(pos, names));
        }

        test("", 0, &[], true);
        test("", 1, &[], false);
        test("", 0, &["a"], false);

        test("a", 1, &[], true);
        test("a", 0, &["a"], true);
        test("a", 1, &["a"], false);
        test("a", 0, &["x"], false);

        test("a, b = 1", 1, &[], true);
        test("a, b = 1", 2, &[], true);
        test("a, b = 1", 0, &["a"], true);
        test("a, b = 1", 0, &["b"], false);
        test("a, b = 1", 0, &["a", "b"], true);

        test("*, a", 0, &[], false);
        test("*, a", 1, &[], false);
        test("*, a", 0, &["a"], true);

        test("a, *args", 0, &[], false);
        test("a, *args", 1, &[], true);
        test("a, *args", 10, &[], true);

        test("*args, b", 0, &[], false);
        test("*args, b", 1, &[], false);
        test("*args, b", 0, &["b"], true);

        test("**kwargs", 0, &[], true);
        test("**kwargs", 0, &["a"], true);
        test("**kwargs", 1, &[], false);

        // No test for positional-only args because we can't create them in starlark.
    }
}


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

/// Initialize `$loc` to `FrozenRef<FrameSpan>` with rust file and line number.
macro_rules! rust_loc {
    () => {{
        use crate::codemap::CodeMap;
        use crate::codemap::NativeCodeMap;
        use crate::eval::runtime::frame_span::FrameSpan;
        use crate::eval::runtime::frozen_file_span::FrozenFileSpan;
        use crate::values::FrozenRef;

        static NATIVE_CODEMAP: NativeCodeMap = NativeCodeMap::new(file!(), line!(), column!());
        static CODEMAP: CodeMap = NATIVE_CODEMAP.to_codemap();
        static FROZEN_FILE_SPAN: FrameSpan = FrameSpan::new(FrozenFileSpan::new_unchecked(
            FrozenRef::new(&CODEMAP),
            NativeCodeMap::FULL_SPAN,
        ));
        static LOC: FrozenRef<'static, FrameSpan> = FrozenRef::new(&FROZEN_FILE_SPAN);
        LOC
    }};
}

pub(crate) use rust_loc;

#[cfg(test)]
mod tests {
    use starlark_derive::starlark_module;

    use crate as starlark;
    use crate::assert::Assert;
    use crate::environment::GlobalsBuilder;
    use crate::eval::Arguments;
    use crate::eval::Evaluator;
    use crate::values::Value;

    #[starlark_module]
    fn rust_loc_globals(globals: &mut GlobalsBuilder) {
        fn invoke<'v>(f: Value<'v>, eval: &mut Evaluator<'v, '_>) -> anyhow::Result<Value<'v>> {
            f.invoke_with_loc(Some(rust_loc!()), &Arguments::default(), eval)
        }
    }

    #[test]
    fn test_rust_loc() {
        let mut a = Assert::new();
        a.globals_add(rust_loc_globals);
        let err = a.fail("invoke(fail)", "");
        let err = err.to_string();
        // Stack trace should contain invocation in `invoke`.
        assert!(
            // Make test compatible with Windows.
            err.replace('\\', "/")
                .contains("src/eval/runtime/rust_loc.rs"),
            "output: {:?}",
            err
        );
        assert!(err.contains("<native>"), "output: {:?}", err);
    }
}


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

use allocative::Allocative;
use dupe::Dupe;
use starlark_derive::Trace;
use starlark_derive::VisitSpanMut;

use crate as starlark;
use crate::eval::bc::stack_ptr::BcSlot;
use crate::values::Freeze;

/// Not captured.
#[derive(
    Clone,
    Copy,
    Dupe,
    Debug,
    PartialEq,
    Eq,
    Trace,
    Freeze,
    VisitSpanMut,
    Allocative
)]
pub(crate) struct LocalSlotId(pub(crate) u32);

impl LocalSlotId {
    /// Each local slot is a valid BC slot.
    /// When it is:
    /// * known to be initialized
    /// * or used for writing
    /// * but not captured
    #[inline]
    pub(crate) fn to_bc_slot(self) -> BcSlot {
        BcSlot(self.0)
    }

    #[inline]
    pub(crate) fn to_captured_or_not(self) -> LocalSlotIdCapturedOrNot {
        LocalSlotIdCapturedOrNot(self.0)
    }
}

/// Captured local slot id.
///
/// E. g. in code:
///
/// ```ignore
/// def f():
///   x = 1
///   return lambda: x
/// ```
///
/// `x` slots (in both `f` and `lambda`) are captured.
#[derive(Clone, Copy, Dupe, Debug, PartialEq, Eq, Trace, VisitSpanMut)]
pub(crate) struct LocalCapturedSlotId(pub(crate) u32);

impl LocalCapturedSlotId {
    #[inline]
    pub(crate) fn to_bc_slot(self) -> BcSlot {
        BcSlot(self.0)
    }
}

/// Local slot id, when we don't know if it is captured or not.
///
/// This is used only during AST analysis.
#[derive(Clone, Copy, Dupe, Debug, PartialEq, Eq, Trace)]
pub(crate) struct LocalSlotIdCapturedOrNot(pub(crate) u32);


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

use std::iter::Sum;
use std::ops::Add;
use std::ops::AddAssign;
use std::ops::Div;
use std::time::Duration;

use allocative::Allocative;
use dupe::Dupe;

/// Slightly faster than `Duration`.
#[derive(Copy, Clone, Dupe, Default, Debug, Allocative)]
pub(crate) struct SmallDuration {
    /// `u64::MAX` nanos is 500 years.
    pub(crate) nanos: u64,
}

impl SmallDuration {
    pub(crate) fn from_duration(duration: Duration) -> SmallDuration {
        SmallDuration {
            nanos: duration.as_nanos() as u64,
        }
    }

    pub(crate) fn to_duration(self) -> Duration {
        Duration::from_nanos(self.nanos)
    }
}

impl AddAssign for SmallDuration {
    fn add_assign(&mut self, other: Self) {
        self.nanos += other.nanos;
    }
}

impl AddAssign<Duration> for SmallDuration {
    fn add_assign(&mut self, other: Duration) {
        self.nanos += other.as_nanos() as u64;
    }
}

impl Add<Duration> for SmallDuration {
    type Output = SmallDuration;

    fn add(self, other: Duration) -> Self::Output {
        SmallDuration {
            nanos: self.nanos + other.as_nanos() as u64,
        }
    }
}

impl Add<SmallDuration> for SmallDuration {
    type Output = SmallDuration;

    fn add(self, other: SmallDuration) -> SmallDuration {
        SmallDuration {
            nanos: self.nanos + other.nanos,
        }
    }
}

impl Div<u64> for SmallDuration {
    type Output = SmallDuration;

    fn div(self, other: u64) -> SmallDuration {
        SmallDuration {
            nanos: self.nanos / other,
        }
    }
}

impl<'a> Sum<&'a SmallDuration> for SmallDuration {
    fn sum<I>(iter: I) -> SmallDuration
    where
        I: Iterator<Item = &'a SmallDuration>,
    {
        iter.fold(SmallDuration::default(), |acc, x| acc + *x)
    }
}

impl Sum<SmallDuration> for SmallDuration {
    fn sum<I>(iter: I) -> SmallDuration
    where
        I: Iterator<Item = SmallDuration>,
    {
        iter.fold(SmallDuration::default(), |acc, x| acc + x)
    }
}


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

use crate::collections::symbol_map::Symbol;
use crate::environment::slots::ModuleSlotId;
use crate::eval::compiler::expr::CompareOp;
use crate::eval::compiler::span::IrSpanned;
use crate::eval::runtime::frame_span::FrameSpan;
use crate::values::typing::TypeCompiled;
use crate::values::FrozenRef;
use crate::values::FrozenValue;
use crate::values::FrozenValueTyped;
use crate::values::StarlarkValue;

/// Visitor for code spans in the IR.
pub(crate) trait VisitSpanMut {
    fn visit_spans(&mut self, visitor: &mut impl FnMut(&mut FrameSpan));
}

impl<V: VisitSpanMut> VisitSpanMut for IrSpanned<V> {
    fn visit_spans(&mut self, visitor: &mut impl FnMut(&mut FrameSpan)) {
        visitor(&mut self.span);
        self.node.visit_spans(visitor);
    }
}

impl VisitSpanMut for FrozenValue {
    fn visit_spans(&mut self, _visitor: &mut impl FnMut(&mut FrameSpan)) {}
}

impl VisitSpanMut for TypeCompiled<FrozenValue> {
    fn visit_spans(&mut self, _visitor: &mut impl FnMut(&mut FrameSpan)) {}
}

impl VisitSpanMut for String {
    fn visit_spans(&mut self, _visitor: &mut impl FnMut(&mut FrameSpan)) {}
}

impl VisitSpanMut for bool {
    fn visit_spans(&mut self, _visitor: &mut impl FnMut(&mut FrameSpan)) {}
}

impl VisitSpanMut for u32 {
    fn visit_spans(&mut self, _visitor: &mut impl FnMut(&mut FrameSpan)) {}
}

impl VisitSpanMut for ModuleSlotId {
    fn visit_spans(&mut self, _visitor: &mut impl FnMut(&mut FrameSpan)) {}
}

impl VisitSpanMut for CompareOp {
    fn visit_spans(&mut self, _visitor: &mut impl FnMut(&mut FrameSpan)) {}
}

impl<V: VisitSpanMut> VisitSpanMut for Box<V> {
    fn visit_spans(&mut self, visitor: &mut impl FnMut(&mut FrameSpan)) {
        (**self).visit_spans(visitor);
    }
}

impl<T: StarlarkValue<'static>> VisitSpanMut for FrozenValueTyped<'static, T> {
    fn visit_spans(&mut self, _visitor: &mut impl FnMut(&mut FrameSpan)) {}
}

impl<T> VisitSpanMut for FrozenRef<'static, T> {
    fn visit_spans(&mut self, _visitor: &mut impl FnMut(&mut FrameSpan)) {}
}

impl VisitSpanMut for Symbol {
    fn visit_spans(&mut self, _visitor: &mut impl FnMut(&mut FrameSpan)) {}
}

impl<A: VisitSpanMut, B: VisitSpanMut> VisitSpanMut for (A, B) {
    fn visit_spans(&mut self, visitor: &mut impl FnMut(&mut FrameSpan)) {
        self.0.visit_spans(visitor);
        self.1.visit_spans(visitor);
    }
}

impl<A: VisitSpanMut, B: VisitSpanMut, C: VisitSpanMut> VisitSpanMut for (A, B, C) {
    fn visit_spans(&mut self, visitor: &mut impl FnMut(&mut FrameSpan)) {
        self.0.visit_spans(visitor);
        self.1.visit_spans(visitor);
        self.2.visit_spans(visitor);
    }
}

impl<A: VisitSpanMut, B: VisitSpanMut, C: VisitSpanMut, D: VisitSpanMut> VisitSpanMut
    for (A, B, C, D)
{
    fn visit_spans(&mut self, visitor: &mut impl FnMut(&mut FrameSpan)) {
        self.0.visit_spans(visitor);
        self.1.visit_spans(visitor);
        self.2.visit_spans(visitor);
        self.3.visit_spans(visitor);
    }
}

impl<V: VisitSpanMut> VisitSpanMut for Vec<V> {
    fn visit_spans(&mut self, visitor: &mut impl FnMut(&mut FrameSpan)) {
        for v in self {
            v.visit_spans(visitor);
        }
    }
}

impl<V: VisitSpanMut> VisitSpanMut for Option<V> {
    fn visit_spans(&mut self, visitor: &mut impl FnMut(&mut FrameSpan)) {
        if let Some(v) = self {
            v.visit_spans(visitor);
        }
    }
}