// Dummy source to keep Cargo happy

fn main() {}


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

fn main() {
    lalrpop();
    rust_nightly();
}

fn lalrpop() {
    let source = "src/syntax/grammar.lalrpop";
    println!("cargo:rerun-if-changed={}", source);
    lalrpop::Configuration::new()
        .use_cargo_dir_conventions()
        .emit_report(true)
        .process_file(source)
        .unwrap();
}

fn rust_nightly() {
    let rustc = std::env::var("RUSTC").unwrap();
    let version = std::process::Command::new(rustc)
        .arg("--version")
        .output()
        .unwrap();

    assert!(version.status.success());

    // Nightly output:
    // rustc 1.64.0-nightly (affe0d3a0 2022-08-05)
    // Stable output:
    // rustc 1.64.0 (a55dd71d5 2022-09-19)
    // Meta internal output:
    // rustc 1.64.0-dev

    let stdout = String::from_utf8(version.stdout).unwrap();
    assert!(stdout.contains("rustc"), "Sanity check");
    let nightly = stdout.contains("nightly") || stdout.contains("dev");
    if nightly {
        println!("cargo:rustc-cfg=rust_nightly");
    }
}


/*
 * 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::HashMap;
use std::collections::HashSet;
use std::fs;
use std::io;
use std::iter;
use std::path::Path;
use std::path::PathBuf;

use itertools::Either;
use lsp_types::Diagnostic;
use lsp_types::Url;
use starlark::docs::get_registered_starlark_docs;
use starlark::docs::render_docs_as_code;
use starlark::docs::Doc;
use starlark::docs::DocItem;
use starlark::environment::FrozenModule;
use starlark::environment::Globals;
use starlark::environment::Module;
use starlark::errors::EvalMessage;
use starlark::eval::Evaluator;
use starlark::lsp::server::LspContext;
use starlark::lsp::server::LspEvalResult;
use starlark::lsp::server::LspUrl;
use starlark::lsp::server::StringLiteralResult;
use starlark::syntax::AstModule;
use starlark::syntax::Dialect;

#[derive(Debug)]
pub(crate) enum ContextMode {
    Check,
    Run,
}

#[derive(Debug, thiserror::Error)]
enum ContextError {
    /// The provided Url was not absolute and it needs to be.
    #[error("Path for URL `{}` was not absolute", .0)]
    NotAbsolute(LspUrl),
    /// The scheme provided was not correct or supported.
    #[error("Url `{}` was expected to be of type `{}`", .1, .0)]
    WrongScheme(String, LspUrl),
}

#[derive(Debug)]
pub(crate) struct Context {
    pub(crate) mode: ContextMode,
    pub(crate) print_non_none: bool,
    pub(crate) prelude: Vec<FrozenModule>,
    pub(crate) module: Option<Module>,
    pub(crate) builtin_docs: HashMap<LspUrl, String>,
    pub(crate) builtin_symbols: HashMap<String, LspUrl>,
}

/// The outcome of evaluating (checking, parsing or running) given starlark code.
pub(crate) struct EvalResult<T: Iterator<Item = EvalMessage>> {
    /// The diagnostic and error messages from evaluating a given piece of starlark code.
    pub messages: T,
    /// If the code is only parsed, not run, and there were no errors, this will contain
    /// the parsed module. Otherwise, it will be `None`
    pub ast: Option<AstModule>,
}

/// Errors when [`LspContext::resolve_load()`] cannot resolve a given path.
#[derive(thiserror::Error, Debug)]
enum ResolveLoadError {
    /// Attempted to resolve a relative path, but no current_file_path was provided,
    /// so it is not known what to resolve the path against.
    #[error("Relative path `{}` provided, but current_file_path could not be determined", .0.display())]
    MissingCurrentFilePath(PathBuf),
    /// The scheme provided was not correct or supported.
    #[error("Url `{}` was expected to be of type `{}`", .1, .0)]
    WrongScheme(String, LspUrl),
}

impl Context {
    pub(crate) fn new(
        mode: ContextMode,
        print_non_none: bool,
        prelude: &[PathBuf],
        module: bool,
    ) -> anyhow::Result<Self> {
        let globals = globals();
        let prelude: Vec<_> = prelude
            .iter()
            .map(|x| {
                let env = Module::new();
                {
                    let mut eval = Evaluator::new(&env);
                    let module = AstModule::parse_file(x, &dialect())?;
                    eval.eval_module(module, &globals)?;
                }
                env.freeze()
            })
            .collect::<anyhow::Result<_>>()?;

        let module = if module {
            Some(Self::new_module(&prelude))
        } else {
            None
        };
        let mut builtins: HashMap<LspUrl, Vec<Doc>> = HashMap::new();
        let mut builtin_symbols: HashMap<String, LspUrl> = HashMap::new();
        for doc in get_registered_starlark_docs() {
            let uri = Self::url_for_doc(&doc);
            builtin_symbols.insert(doc.id.name.clone(), uri.clone());
            builtins.entry(uri).or_default().push(doc);
        }
        let builtin_docs = builtins
            .into_iter()
            .map(|(u, ds)| (u, render_docs_as_code(&ds)))
            .collect();

        Ok(Self {
            mode,
            print_non_none,
            prelude,
            module,
            builtin_docs,
            builtin_symbols,
        })
    }

    fn url_for_doc(doc: &Doc) -> LspUrl {
        let url = match &doc.item {
            DocItem::Module(_) => Url::parse("starlark:/native/builtins.bzl").unwrap(),
            DocItem::Object(_) => {
                Url::parse(&format!("starlark:/native/builtins/{}.bzl", doc.id.name)).unwrap()
            }
            DocItem::Function(_) | DocItem::Property(_) => {
                Url::parse("starlark:/native/builtins.bzl").unwrap()
            }
        };
        LspUrl::try_from(url).unwrap()
    }

    fn new_module(prelude: &[FrozenModule]) -> Module {
        let module = Module::new();
        for p in prelude {
            module.import_public_symbols(p);
        }
        module
    }

    fn go(&self, file: &str, ast: AstModule) -> EvalResult<impl Iterator<Item = EvalMessage>> {
        let mut warnings = Either::Left(iter::empty());
        let mut errors = Either::Left(iter::empty());
        let final_ast = match self.mode {
            ContextMode::Check => {
                warnings = Either::Right(self.check(&ast));
                Some(ast)
            }
            ContextMode::Run => {
                errors = Either::Right(self.run(file, ast).messages);
                None
            }
        };
        EvalResult {
            messages: warnings.chain(errors),
            ast: final_ast,
        }
    }

    // Convert an anyhow over iterator of EvalMessage, into an iterator of EvalMessage
    fn err(
        file: &str,
        result: anyhow::Result<EvalResult<impl Iterator<Item = EvalMessage>>>,
    ) -> EvalResult<impl Iterator<Item = EvalMessage>> {
        match result {
            Err(e) => EvalResult {
                messages: Either::Left(iter::once(EvalMessage::from_anyhow(Path::new(file), &e))),
                ast: None,
            },
            Ok(res) => EvalResult {
                messages: Either::Right(res.messages),
                ast: res.ast,
            },
        }
    }

    pub(crate) fn expression(
        &self,
        content: String,
    ) -> EvalResult<impl Iterator<Item = EvalMessage>> {
        let file = "expression";
        Self::err(
            file,
            AstModule::parse(file, content, &dialect()).map(|module| self.go(file, module)),
        )
    }

    pub(crate) fn file(&self, file: &Path) -> EvalResult<impl Iterator<Item = EvalMessage>> {
        let filename = &file.to_string_lossy();
        Self::err(
            filename,
            fs::read_to_string(file)
                .map(|content| self.file_with_contents(filename, content))
                .map_err(|e| e.into()),
        )
    }

    pub(crate) fn file_with_contents(
        &self,
        filename: &str,
        content: String,
    ) -> EvalResult<impl Iterator<Item = EvalMessage>> {
        Self::err(
            filename,
            AstModule::parse(filename, content, &dialect()).map(|module| self.go(filename, module)),
        )
    }

    fn run(&self, file: &str, ast: AstModule) -> EvalResult<impl Iterator<Item = EvalMessage>> {
        let new_module;
        let module = match self.module.as_ref() {
            Some(module) => module,
            None => {
                new_module = Self::new_module(&self.prelude);
                &new_module
            }
        };
        let mut eval = Evaluator::new(module);
        eval.enable_terminal_breakpoint_console();
        let globals = globals();
        Self::err(
            file,
            eval.eval_module(ast, &globals).map(|v| {
                if self.print_non_none && !v.is_none() {
                    println!("{}", v);
                }
                EvalResult {
                    messages: iter::empty(),
                    ast: None,
                }
            }),
        )
    }

    fn check(&self, module: &AstModule) -> impl Iterator<Item = EvalMessage> {
        let globals = if self.prelude.is_empty() {
            None
        } else {
            let mut globals = HashSet::new();
            for modu in &self.prelude {
                for name in modu.names() {
                    globals.insert(name.as_str().to_owned());
                }
            }

            for global_symbol in self.builtin_symbols.keys() {
                globals.insert(global_symbol.to_owned());
            }

            Some(globals)
        };

        module
            .lint(globals.as_ref())
            .into_iter()
            .map(EvalMessage::from)
    }
}

impl LspContext for Context {
    fn parse_file_with_contents(&self, uri: &LspUrl, content: String) -> LspEvalResult {
        match uri {
            LspUrl::File(uri) => {
                let EvalResult { messages, ast } =
                    self.file_with_contents(&uri.to_string_lossy(), content);
                LspEvalResult {
                    diagnostics: messages.map(Diagnostic::from).collect(),
                    ast,
                }
            }
            _ => LspEvalResult::default(),
        }
    }

    fn resolve_load(&self, path: &str, current_file: &LspUrl) -> anyhow::Result<LspUrl> {
        let path = PathBuf::from(path);
        match current_file {
            LspUrl::File(current_file_path) => {
                let current_file_dir = current_file_path.parent();
                let absolute_path = match (current_file_dir, path.is_absolute()) {
                    (_, true) => Ok(path),
                    (Some(current_file_dir), false) => Ok(current_file_dir.join(&path)),
                    (None, false) => Err(ResolveLoadError::MissingCurrentFilePath(path)),
                }?;
                Ok(Url::from_file_path(absolute_path).unwrap().try_into()?)
            }
            _ => Err(
                ResolveLoadError::WrongScheme("file://".to_owned(), current_file.clone()).into(),
            ),
        }
    }

    fn resolve_string_literal(
        &self,
        literal: &str,
        current_file: &LspUrl,
    ) -> anyhow::Result<Option<StringLiteralResult>> {
        self.resolve_load(literal, current_file).map(|url| {
            Some(StringLiteralResult {
                url,
                location_finder: None,
            })
        })
    }

    fn get_load_contents(&self, uri: &LspUrl) -> anyhow::Result<Option<String>> {
        match uri {
            LspUrl::File(path) => match path.is_absolute() {
                true => match fs::read_to_string(path) {
                    Ok(contents) => Ok(Some(contents)),
                    Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
                    Err(e) => Err(e.into()),
                },
                false => Err(ContextError::NotAbsolute(uri.clone()).into()),
            },
            LspUrl::Starlark(_) => Ok(self.builtin_docs.get(uri).cloned()),
            _ => Err(ContextError::WrongScheme("file://".to_owned(), uri.clone()).into()),
        }
    }

    fn get_url_for_global_symbol(
        &self,
        _current_file: &LspUrl,
        symbol: &str,
    ) -> anyhow::Result<Option<LspUrl>> {
        Ok(self.builtin_symbols.get(symbol).cloned())
    }
}

pub(crate) fn globals() -> Globals {
    Globals::extended()
}

pub(crate) fn dialect() -> Dialect {
    Dialect::Extended
}


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

// Plugins
#![cfg_attr(feature = "gazebo_lint", feature(plugin))]
#![cfg_attr(feature = "gazebo_lint", allow(deprecated))] // :(
#![cfg_attr(feature = "gazebo_lint", plugin(gazebo_lint))]
// Disagree these are good hints
#![allow(clippy::type_complexity)]

use std::ffi::OsStr;
use std::fmt;
use std::fmt::Display;
use std::path::PathBuf;
use std::sync::Arc;

use clap::Parser;
use clap::ValueEnum;
use dupe::Dupe;
use eval::Context;
use itertools::Either;
use itertools::Itertools;
use starlark::docs::get_registered_starlark_docs;
use starlark::docs::render_docs_as_code;
use starlark::docs::Doc;
use starlark::docs::DocItem;
use starlark::docs::MarkdownFlavor;
use starlark::docs::RenderMarkdown;
use starlark::environment::Globals;
use starlark::errors::EvalMessage;
use starlark::errors::EvalSeverity;
use starlark::lsp;
use starlark::read_line::ReadLine;
use walkdir::WalkDir;

use crate::eval::ContextMode;
use crate::types::LintMessage;

mod dap;
mod eval;
mod types;

#[derive(Debug, Parser)]
#[command(name = "starlark", about = "Evaluate Starlark code")]
struct Args {
    #[arg(
        long = "lsp",
        help = "Start an LSP server.",
        conflicts_with_all = &[
            "dap",
            "check",
            "json",
            "docs",
            "evaluate",
            "files",
        ],
    )]
    lsp: bool,

    #[arg(
        long = "dap",
        help = "Start a DAP server.",
        // Conflicts with all options.
        conflicts_with_all = &[
            "lsp",
            "check",
            "json",
            "docs",
            "extension",
            "prelude",
            "evaluate",
            "files",
        ],
    )]
    dap: bool,

    #[arg(
        long = "check",
        help = "Run checks and lints.",
        conflicts_with_all = &["lsp", "dap"],
    )]
    check: bool,

    #[arg(
        long = "json",
        help = "Show output as JSON lines.",
        conflicts_with_all = &["lsp", "dap"],
    )]
    json: bool,

    #[arg(
        long = "docs",
        help = "Generate documentation output.",
        conflicts_with_all = &["lsp", "dap"],
    )]
    docs: Option<ArgsDoc>,

    #[arg(
        long = "extension",
        help = "File extension when searching directories."
    )]
    extension: Option<String>,

    #[arg(long = "prelude", help = "Files to load in advance.", num_args = 1..)]
    prelude: Vec<PathBuf>,

    #[arg(
        long = "expression",
        short = 'e',
        id = "evaluate",
        value_name = "EXPRESSION",
        help = "Expressions to evaluate.",
        conflicts_with_all = &["lsp", "dap"],
        num_args = 1..,
    )]
    evaluate: Vec<String>,

    #[arg(
        id = "files",
        value_name = "FILE",
        help = "Files to evaluate.",
        conflicts_with_all = &["lsp", "dap"],
    )]
    files: Vec<PathBuf>,
}

#[derive(ValueEnum, Copy, Clone, Dupe, Debug, PartialEq, Eq)]
enum ArgsDoc {
    Lsp,
    Markdown,
    Code,
}

// Treat directories as things to recursively walk for .<extension> files,
// and everything else as normal files.
fn expand_dirs(extension: &str, xs: Vec<PathBuf>) -> impl Iterator<Item = PathBuf> {
    let extension = Arc::new(extension.to_owned());
    xs.into_iter().flat_map(move |x| {
        // Have to keep cloning extension so we keep ownership
        let extension = extension.dupe();
        if x.is_dir() {
            Either::Left(
                WalkDir::new(x)
                    .into_iter()
                    .filter_map(|e| e.ok())
                    .filter(move |e| e.path().extension() == Some(OsStr::new(extension.as_str())))
                    .map(|e| e.into_path()),
            )
        } else {
            Either::Right(Box::new(vec![x].into_iter()))
        }
    })
}

#[derive(Default)]
struct Stats {
    file: usize,
    error: usize,
    warning: usize,
    advice: usize,
    disabled: usize,
}

impl Display for Stats {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&format!(
            "{} files, {} errors, {} warnings, {} advices, {} disabled",
            self.file, self.error, self.warning, self.advice, self.disabled
        ))
    }
}

impl Stats {
    fn increment_file(&mut self) {
        self.file += 1;
    }

    fn increment(&mut self, x: EvalSeverity) {
        match x {
            EvalSeverity::Error => self.error += 1,
            EvalSeverity::Warning => self.warning += 1,
            EvalSeverity::Advice => self.advice += 1,
            EvalSeverity::Disabled => self.disabled += 1,
        }
    }
}

fn drain(xs: impl Iterator<Item = EvalMessage>, json: bool, stats: &mut Stats) {
    for x in xs {
        stats.increment(x.severity);
        if json {
            println!("{}", serde_json::to_string(&LintMessage::new(x)).unwrap());
        } else if let Some(error) = x.full_error_with_span {
            let mut error = error.to_owned();
            if !error.is_empty() && !error.ends_with('\n') {
                error.push('\n');
            }
            print!("{}", error);
        } else {
            println!("{}", x);
        }
    }
}

fn interactive(ctx: &Context) -> anyhow::Result<()> {
    let mut rl = ReadLine::new("STARLARK_RUST_HISTFILE")?;
    loop {
        match rl.read_line("$> ")? {
            Some(line) => {
                let mut stats = Stats::default();
                drain(ctx.expression(line).messages, false, &mut stats);
            }
            // User pressed EOF - disconnected terminal, or similar
            None => return Ok(()),
        }
    }
}

/// starlark-rust does not support panic.
/// Terminate on panic even if compiled without `-Cpanic=abort`.
fn terminate_on_panic() {
    let orig_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |panic_info| {
        orig_hook(panic_info);
        std::process::exit(1);
    }));
}

fn main() -> anyhow::Result<()> {
    terminate_on_panic();

    let args = argfile::expand_args(argfile::parse_fromfile, argfile::PREFIX)?;
    let args: Args = Args::parse_from(args);
    if args.dap {
        dap::server();
    } else {
        let is_interactive = args.evaluate.is_empty() && args.files.is_empty();

        let ext = args
            .extension
            .as_ref()
            .map_or("bzl", |x| x.strip_prefix('.').unwrap_or(x.as_str()));
        let mut ctx = Context::new(
            if args.check {
                ContextMode::Check
            } else {
                ContextMode::Run
            },
            !args.evaluate.is_empty() || is_interactive,
            &expand_dirs(ext, args.prelude).collect::<Vec<_>>(),
            is_interactive,
        )?;

        if args.lsp {
            ctx.mode = ContextMode::Check;
            lsp::server::stdio_server(ctx)?;
        } else if let Some(docs) = args.docs {
            let mut builtin = get_registered_starlark_docs();
            builtin.push(Doc::named_item(
                "globals".to_owned(),
                DocItem::Module(Globals::extended().documentation()),
            ));

            match docs {
                ArgsDoc::Markdown | ArgsDoc::Lsp => {
                    let mode = if docs == ArgsDoc::Markdown {
                        MarkdownFlavor::DocFile
                    } else {
                        MarkdownFlavor::LspSummary
                    };
                    println!(
                        "{}",
                        builtin.iter().map(|x| x.render_markdown(mode)).join("\n\n")
                    )
                }
                ArgsDoc::Code => println!("{}", render_docs_as_code(&builtin)),
            };
        } else if is_interactive {
            interactive(&ctx)?;
        } else {
            let mut stats = Stats::default();
            for e in args.evaluate.clone() {
                stats.increment_file();
                drain(ctx.expression(e).messages, args.json, &mut stats);
            }

            for file in expand_dirs(ext, args.files.clone()) {
                stats.increment_file();
                drain(ctx.file(&file).messages, args.json, &mut stats);
            }

            if !args.json {
                println!("{}", stats);
                if stats.error > 0 {
                    return Err(anyhow::anyhow!("Failed with {} errors", stats.error));
                }
            }
        }
    }
    Ok(())
}


/*
 * 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 serde::Serialize;
use starlark::errors::EvalMessage;
use starlark::errors::EvalSeverity;

/// A JSON-deriving type that gives a stable interface to downstream types.
/// Do NOT change this type, change Message instead.
///
/// [Linter JSON format](https://www.internalfb.com/intern/wiki/Linting/adding-linters/).
#[derive(Debug, Clone, Serialize)]
pub(crate) struct LintMessage {
    path: String,
    line: Option<usize>,
    char: Option<usize>,
    code: String,
    severity: EvalSeverity,
    name: String,
    description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    original: Option<String>,
}

impl LintMessage {
    pub(crate) fn new(x: EvalMessage) -> Self {
        Self {
            path: x.path,
            line: x.span.map(|x| x.begin_line + 1),
            char: x.span.map(|x| x.begin_column + 1),
            code: "STARLARK".to_owned(),
            severity: x.severity,
            name: x.name,
            description: Some(x.description),
            original: x.original,
        }
    }
}


/*
 * 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::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;

use debugserver_types::*;
use dupe::Dupe;
pub(crate) use library::*;
use serde_json::Map;
use serde_json::Value;
use starlark::debug::dap_capabilities;
use starlark::debug::prepare_dap_adapter;
use starlark::debug::resolve_breakpoints;
use starlark::debug::DapAdapter;
use starlark::debug::DapAdapterClient;
use starlark::debug::DapAdapterEvalHook;
use starlark::environment::Module;
use starlark::eval::Evaluator;
use starlark::syntax::AstModule;

use crate::eval::dialect;
use crate::eval::globals;

mod library;

#[derive(Debug)]
struct Backend {
    adapter: Arc<dyn DapAdapter>,
    eval_wrapper: Mutex<Option<Box<dyn DapAdapterEvalHook>>>,
    client: Client,
    file: Mutex<Option<String>>,
}

impl DapAdapterClient for Client {
    fn event_stopped(&self) {
        self.event_stopped(StoppedEventBody {
            reason: "breakpoint".to_owned(),
            thread_id: Some(0),
            description: Some("Hello".to_owned()),
            all_threads_stopped: Some(true),
            preserve_focus_hint: None,
            text: None,
        });
    }
}

fn get_ast(source: &str) -> anyhow::Result<Arc<AstModule>> {
    Ok(Arc::new(AstModule::parse_file(
        Path::new(source),
        &dialect(),
    )?))
}

impl Backend {
    fn execute(&self, path: &str) {
        let client = self.client.dupe();
        let client2 = self.client.dupe();
        let wrapper = self.eval_wrapper.lock().unwrap().take().unwrap();
        let path = PathBuf::from(path);

        let go = move || -> anyhow::Result<String> {
            client.log(&format!("EVALUATION PREPARE: {}", path.display()));
            let ast = AstModule::parse_file(&path, &dialect())?;
            let module = Module::new();
            let globals = globals();
            let mut eval = Evaluator::new(&module);
            wrapper.add_dap_hooks(&mut eval);

            // No way to pass back success/failure to the caller
            client.log(&format!("EVALUATION START: {}", path.display()));
            let v = eval.eval_module(ast, &globals)?;
            let s = v.to_string();
            client.log(&format!("EVALUATION FINISHED: {}", path.display()));
            Ok(s)
        };

        thread::spawn(move || {
            let res = go();
            let output = match &res {
                Err(e) => format!("{:#}", e),
                Ok(v) => v.to_owned(),
            };
            client2.event_output(OutputEventBody {
                output,
                category: None,
                column: None,
                data: None,
                line: None,
                source: None,
                variables_reference: None,
            });
            client2.event_exited(ExitedEventBody {
                exit_code: if res.is_ok() { 0 } else { 1 },
            });
            client2.event_terminated(None);
        });
    }
}

impl DebugServer for Backend {
    fn initialize(&self, _: InitializeRequestArguments) -> anyhow::Result<Option<Capabilities>> {
        self.client.event_initialized(None);
        Ok(Some(dap_capabilities()))
    }

    fn set_breakpoints(
        &self,
        x: SetBreakpointsArguments,
    ) -> anyhow::Result<SetBreakpointsResponseBody> {
        let source = x.source.path.as_ref().unwrap();
        let resolved = resolve_breakpoints(&x, &*get_ast(source)?)?;
        self.adapter.set_breakpoints(source, &resolved)?;
        Ok(resolved.to_response())
    }

    fn set_exception_breakpoints(&self, _: SetExceptionBreakpointsArguments) -> anyhow::Result<()> {
        // We just assume that break on error is always useful
        Ok(())
    }

    fn launch(&self, _: LaunchRequestArguments, args: Map<String, Value>) -> anyhow::Result<()> {
        // Expecting program of type string
        match args.get("program") {
            Some(Value::String(path)) => {
                *self.file.lock().unwrap() = Some(path.to_owned());
                Ok(())
            }
            _ => Err(anyhow::anyhow!(
                "Couldn't find a program to launch, got args {:?}",
                args
            )),
        }
    }

    fn threads(&self) -> anyhow::Result<ThreadsResponseBody> {
        Ok(ThreadsResponseBody {
            threads: vec![Thread {
                id: 0,
                name: "main".to_owned(),
            }],
        })
    }

    fn configuration_done(&self) -> anyhow::Result<()> {
        if let Some(path) = self.file.lock().unwrap().as_ref() {
            self.execute(path);
        }
        Ok(())
    }

    fn stack_trace(&self, v: StackTraceArguments) -> anyhow::Result<StackTraceResponseBody> {
        self.adapter.stack_trace(v)
    }

    fn scopes(&self, _: ScopesArguments) -> anyhow::Result<ScopesResponseBody> {
        let scopes_info = self.adapter.scopes()?;
        Ok(ScopesResponseBody {
            scopes: vec![Scope {
                name: "Locals".to_owned(),
                named_variables: Some(scopes_info.num_locals as i64),
                variables_reference: 2000,
                expensive: false,
                column: None,
                end_column: None,
                end_line: None,
                indexed_variables: None,
                line: None,
                source: None,
            }],
        })
    }

    fn variables(&self, _: VariablesArguments) -> anyhow::Result<VariablesResponseBody> {
        let vars_info = self.adapter.variables()?;
        Ok(VariablesResponseBody {
            variables: vars_info
                .locals
                .into_iter()
                .map(|var| var.to_dap())
                .collect(),
        })
    }

    fn evaluate(&self, x: EvaluateArguments) -> anyhow::Result<EvaluateResponseBody> {
        self.adapter.evaluate(&x.expression)
    }

    fn continue_(&self, _: ContinueArguments) -> anyhow::Result<ContinueResponseBody> {
        self.adapter.continue_()?;
        Ok(ContinueResponseBody::default())
    }
}

pub(crate) fn server() {
    DapService::run(|client| {
        let (adapter, wrapper) = prepare_dap_adapter(Box::new(client.dupe()));
        Backend {
            adapter: Arc::new(adapter),
            eval_wrapper: Mutex::new(Some(Box::new(wrapper))),
            client,
            file: Default::default(),
        }
    })
}


/*
 * 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 debugserver_types::*;
use dupe::Dupe;
use serde::Serialize;
use serde_json::Value;

use crate::dap::library::stream::log;
use crate::dap::library::stream::send;

#[derive(Debug, Clone, Dupe)]
pub(crate) struct Client {
    _private: (),
}

impl Client {
    pub(crate) fn new() -> Self {
        Self { _private: () }
    }

    pub(crate) fn log(&self, x: &str) {
        log(x)
    }

    fn event(&self, x: impl Serialize) {
        send(serde_json::to_value(&x).unwrap())
    }

    pub(crate) fn event_stopped(&self, body: StoppedEventBody) {
        self.event(StoppedEvent {
            type_: "event".to_owned(),
            seq: 0,
            event: "stopped".to_owned(),
            body,
        })
    }

    pub(crate) fn event_initialized(&self, body: Option<Value>) {
        self.event(InitializedEvent {
            type_: "event".to_owned(),
            seq: 0,
            event: "initialized".to_owned(),
            body,
        })
    }

    pub(crate) fn event_exited(&self, body: ExitedEventBody) {
        self.event(ExitedEvent {
            type_: "event".to_owned(),
            seq: 0,
            event: "exited".to_owned(),
            body,
        })
    }

    pub(crate) fn event_terminated(&self, body: Option<TerminatedEventBody>) {
        self.event(TerminatedEvent {
            type_: "event".to_owned(),
            seq: 0,
            event: "terminated".to_owned(),
            body,
        })
    }

    pub(crate) fn event_output(&self, body: OutputEventBody) {
        self.event(OutputEvent {
            type_: "event".to_owned(),
            seq: 0,
            event: "output".to_owned(),
            body,
        })
    }
}


/*
 * 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) use crate::dap::library::events::Client;
pub(crate) use crate::dap::library::requests::DebugServer;
pub(crate) use crate::dap::library::server::DapService;

mod events;
mod requests;
mod server;
mod stream;


/*
 * 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 debugserver_types::*;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Map;
use serde_json::Value;

pub(crate) trait DebugServer {
    fn initialize(&self, x: InitializeRequestArguments) -> anyhow::Result<Option<Capabilities>>;
    fn set_breakpoints(
        &self,
        x: SetBreakpointsArguments,
    ) -> anyhow::Result<SetBreakpointsResponseBody>;
    fn set_exception_breakpoints(&self, x: SetExceptionBreakpointsArguments) -> anyhow::Result<()>;
    fn launch(&self, x: LaunchRequestArguments, args: Map<String, Value>) -> anyhow::Result<()>;
    fn threads(&self) -> anyhow::Result<ThreadsResponseBody>;
    fn configuration_done(&self) -> anyhow::Result<()>;
    fn stack_trace(&self, x: StackTraceArguments) -> anyhow::Result<StackTraceResponseBody>;
    fn scopes(&self, x: ScopesArguments) -> anyhow::Result<ScopesResponseBody>;
    fn variables(&self, x: VariablesArguments) -> anyhow::Result<VariablesResponseBody>;
    fn continue_(&self, x: ContinueArguments) -> anyhow::Result<ContinueResponseBody>;
    fn evaluate(&self, x: EvaluateArguments) -> anyhow::Result<EvaluateResponseBody>;
    fn disconnect(&self, _x: DisconnectArguments) -> anyhow::Result<()> {
        Ok(())
    }
}

pub(crate) fn dispatch(server: &impl DebugServer, r: &Request) -> Response {
    fn arg<T: for<'a> Deserialize<'a>>(r: &Request) -> T {
        serde_json::from_value(r.arguments.clone().unwrap()).unwrap()
    }

    fn arg_extra(r: &Request) -> Map<String, Value> {
        match &r.arguments {
            Some(Value::Object(x)) => x.clone(),
            _ => Default::default(),
        }
    }

    fn ret<T: Serialize>(r: &Request, v: anyhow::Result<Option<T>>) -> Response {
        Response {
            type_: "response".to_owned(),
            command: r.command.clone(),
            request_seq: r.seq,
            seq: 0,
            success: v.is_ok(),
            message: v.as_ref().err().map(|e| format!("{:#}", e)),
            body: v.unwrap_or(None).map(|v| serde_json::to_value(v).unwrap()),
        }
    }

    fn ret_some<T: Serialize>(r: &Request, v: anyhow::Result<T>) -> Response {
        ret(r, v.map(Some))
    }

    fn ret_none(r: &Request, v: anyhow::Result<()>) -> Response {
        ret::<()>(r, v.map(|_| None))
    }

    match r.command.as_str() {
        "initialize" => ret(r, server.initialize(arg(r))),
        "setBreakpoints" => ret_some(r, server.set_breakpoints(arg(r))),
        "setExceptionBreakpoints" => ret_none(r, server.set_exception_breakpoints(arg(r))),
        "launch" => ret_none(r, server.launch(arg(r), arg_extra(r))),
        "threads" => ret_some(r, server.threads()),
        "configurationDone" => ret_none(r, server.configuration_done()),
        "stackTrace" => ret_some(r, server.stack_trace(arg(r))),
        "scopes" => ret_some(r, server.scopes(arg(r))),
        "variables" => ret_some(r, server.variables(arg(r))),
        "continue" => ret_some(r, server.continue_(arg(r))),
        "evaluate" => ret_some(r, server.evaluate(arg(r))),
        "disconnect" => ret_none(r, server.disconnect(arg(r))),
        _ => ret_none(r, Err(anyhow::anyhow!("Unknown command: {}", r.command))),
    }
}


/*
 * 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 debugserver_types as ds;

use crate::dap::library::events::*;
use crate::dap::library::requests::*;
use crate::dap::library::stream::*;

pub(crate) struct DapService {
    _private: (),
}

impl DapService {
    pub(crate) fn run<T: DebugServer>(f: impl FnOnce(Client) -> T) {
        server(f(Client::new()));
    }
}

fn server(dap: impl DebugServer) {
    log_begin();

    // Because of the eval we're running in, we probably can't see panics.
    // So mirror them to the log file.
    let orig_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |panic_info| {
        if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
            log(&format!("Panic occurred: {:?}", s));
        } else {
            log("Panic occurred: Unknown message");
        }
        orig_hook(panic_info);
    }));

    log("DEBUG ADAPTER STARTING");
    loop {
        let recv = read();
        let r: ds::Request = serde_json::from_value(recv).unwrap();
        assert_eq!(r.type_, "request");
        let resp = dispatch(&dap, &r);
        send(serde_json::to_value(resp).unwrap());

        if r.command == "disconnect" {
            break;
        }
    }
    log("DEBUG ADAPTER STOPPING");
}


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

//! Lowest level stream communication as JSON.
//! Because DAP debugging is hard, we write everything we see to stdout (for the protocol)
//! AND stderr (for debugging).

use std::env;
use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::io::Read;
use std::io::Write;
use std::path::PathBuf;

use serde_json::Value;

// Debugging anything through DAP is a nightmare, because VS Code doesn't surface any logs.
// Therefore, do the hacky thing of putting logs next to the binary.
fn log_file() -> PathBuf {
    let mut res = env::current_exe().unwrap();
    res.set_extension("dap.log");
    res
}

pub(crate) fn log_begin() {
    File::create(log_file()).unwrap();
}

pub(crate) fn log(x: &str) {
    let mut file = OpenOptions::new().append(true).open(log_file()).unwrap();
    file.write_all(format!("{}\n", x).as_bytes()).unwrap()
}

pub(crate) fn send(x: Value) {
    let s = x.to_string();
    log(&format!("SEND: {}", s));
    print!("Content-Length: {}\r\n\r\n{}", s.len(), s);
    io::stdout().flush().unwrap()
}

pub(crate) fn read() -> Value {
    let mut s = String::new();
    io::stdin().read_line(&mut s).unwrap();
    let len: usize = s
        .strip_prefix("Content-Length: ")
        .unwrap()
        .trim()
        .parse()
        .unwrap();
    io::stdin().read_line(&mut s).unwrap();
    let mut res = vec![0u8; len];
    io::stdin().lock().read_exact(&mut res).unwrap();
    let s = String::from_utf8_lossy(&res);
    log(&format!("RECV: {}", s));
    serde_json::from_str(&s).unwrap()
}


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

#![no_main]

use libfuzzer_sys::fuzz_target;
use starlark::environment::Globals;
use starlark::environment::Module;
use starlark::eval::Evaluator;
use starlark::syntax::AstModule;
use starlark::syntax::Dialect;

fn run_arbitrary_starlark_err(content: &str) -> anyhow::Result<String> {
    let ast: AstModule =
        AstModule::parse("hello_world.star", content.to_owned(), &Dialect::Standard)?;
    let globals: Globals = Globals::standard();
    let module: Module = Module::new();
    let mut eval: Evaluator = Evaluator::new(&module);
    let value = eval.eval_module(ast, &globals)?;
    Ok(format!("{value:?}"))
}

fn run_arbitrary_starlark(content: &str) -> String {
    match run_arbitrary_starlark_err(content) {
        Ok(v) => v,
        Err(e) => {
            const INTERNAL_ERROR: &str = "(internal error)";
            let s = format!("{e:?}");
            // We want to spot internal errors, but not encourage the fuzzer to write internal error in the input.
            // A sufficiently smart fuzzer might outwit us, but hopefully not too quickly.
            if s.contains(INTERNAL_ERROR) && !content.contains(INTERNAL_ERROR) {
                panic!("Internal error as anyhow::Error: {s}")
            }
            s
        }
    }
}

fuzz_target!(|content: &str| {
    let _ = run_arbitrary_starlark(content);
});


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

//! Methods that build upon the [`Any` trait](std::any::Any).

use std::any::TypeId;
use std::cell::Cell;
use std::cell::RefCell;
use std::cell::UnsafeCell;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;

pub use starlark_derive::ProvidesStaticType;

/// Provides access to the same type as `Self` but with all lifetimes dropped to `'static`
/// (including lifetimes of parameters).
///
/// This type is usually implemented with `#[derive(ProvidesStaticType)]`.
pub unsafe trait ProvidesStaticType<'a> {
    /// Same type as `Self` but with lifetimes dropped to `'static`.
    ///
    /// The trait is unsafe because if this is implemented incorrectly,
    /// the program might not work correctly.
    type StaticType: 'static + ?Sized;
}

/// Any `ProvidesStaticType` can implement `AnyLifetime`.
///
/// Note `ProvidesStaticType` and `AnyLifetime` cannot be the same type,
/// because `AnyLifetime` need to be object safe,
/// and `ProvidesStaticType` has type member.
unsafe impl<'a, T: ProvidesStaticType<'a> + 'a + ?Sized> AnyLifetime<'a> for T {
    fn static_type_id() -> TypeId
    where
        Self: Sized,
    {
        TypeId::of::<T::StaticType>()
    }

    fn static_type_of(&self) -> TypeId {
        TypeId::of::<T::StaticType>()
    }
}

/// Like [`Any`](std::any::Any), but while [`Any`](std::any::Any) requires `'static`,
/// this version allows a lifetime parameter.
///
/// Code using this trait is _unsafe_ if your implementation of the inner
/// methods do not meet the invariants listed. Therefore, it is recommended you
/// use one of the helper macros.
///
/// If your data type is of the form `Foo` or `Foo<'v>` you can derive
/// `AnyLifetime`:
///
/// ```
/// use starlark::any::ProvidesStaticType;
/// #[derive(ProvidesStaticType)]
/// struct Foo1();
/// #[derive(ProvidesStaticType)]
/// struct Foo2<'a>(&'a ());
/// ```
///
/// For more complicated context or constraints, you can implement `ProvidesStaticType`
/// directly.
///
/// ```
/// use starlark::any::ProvidesStaticType;
/// # fn main() {
/// # use std::fmt::Display;
/// struct Baz<T: Display>(T);
/// # // TODO: `#[derive(ProvidesStaticType)]` should learn to handle this case too.
/// unsafe impl<'a, T> ProvidesStaticType<'a> for Baz<T>
///     where
///         T: ProvidesStaticType<'a> + Display,
///         T::StaticType: Display + Sized,
/// {
///     type StaticType = Baz<T::StaticType>;
/// }
/// # }
/// ```
pub unsafe trait AnyLifetime<'a>: 'a {
    /// Must return the `TypeId` of `Self` but where the lifetimes are changed
    /// to `'static`. Must be consistent with `static_type_of`.
    fn static_type_id() -> TypeId
    where
        Self: Sized;

    /// Must return the `TypeId` of `Self` but where the lifetimes are changed
    /// to `'static`. Must be consistent with `static_type_id`. Must not
    /// consult the `self` parameter in any way.
    fn static_type_of(&self) -> TypeId;
    // Required so we can have a `dyn AnyLifetime`.
}

impl<'a> dyn AnyLifetime<'a> {
    /// Is the value of type `T`.
    pub fn is<T: AnyLifetime<'a>>(&self) -> bool {
        self.static_type_of() == T::static_type_id()
    }

    /// Downcast a reference to type `T`, or return [`None`](None) if it is not the
    /// right type.
    pub fn downcast_ref<T: AnyLifetime<'a>>(&self) -> Option<&T> {
        if self.is::<T>() {
            // SAFETY: just checked whether we are pointing to the correct type.
            unsafe { Some(&*(self as *const Self as *const T)) }
        } else {
            None
        }
    }

    /// Downcast a mutable reference to type `T`, or return [`None`](None) if it is not
    /// the right type.
    pub fn downcast_mut<T: AnyLifetime<'a>>(&mut self) -> Option<&mut T> {
        if self.is::<T>() {
            // SAFETY: just checked whether we are pointing to the correct type.
            unsafe { Some(&mut *(self as *mut Self as *mut T)) }
        } else {
            None
        }
    }
}

macro_rules! any_lifetime {
    ( $t:ty ) => {
        unsafe impl<'a> $crate::any::ProvidesStaticType<'a> for $t {
            type StaticType = $t;
        }
    };
}

// One of the disadvantages of AnyLifetime is there is no finite covering set of
// types so we predeclare instances for things that seem useful, but the list is
// pretty adhoc
any_lifetime!(());
any_lifetime!(bool);
any_lifetime!(u8);
any_lifetime!(u16);
any_lifetime!(u32);
any_lifetime!(u64);
any_lifetime!(u128);
any_lifetime!(usize);
any_lifetime!(i8);
any_lifetime!(i16);
any_lifetime!(i32);
any_lifetime!(i64);
any_lifetime!(i128);
any_lifetime!(isize);
any_lifetime!(f32);
any_lifetime!(f64);
any_lifetime!(String);
any_lifetime!(str);

unsafe impl<'a, T: ProvidesStaticType<'a> + ?Sized> ProvidesStaticType<'a> for &'a T {
    type StaticType = &'static T::StaticType;
}
unsafe impl<'a, T: ProvidesStaticType<'a> + ?Sized> ProvidesStaticType<'a> for &'a mut T {
    type StaticType = &'static mut T::StaticType;
}
unsafe impl<'a, T: ProvidesStaticType<'a> + ?Sized> ProvidesStaticType<'a> for *const T {
    type StaticType = *const T::StaticType;
}
unsafe impl<'a, T: ProvidesStaticType<'a> + ?Sized> ProvidesStaticType<'a> for *mut T {
    type StaticType = *mut T::StaticType;
}
unsafe impl<'a, T> ProvidesStaticType<'a> for [T]
where
    T: ProvidesStaticType<'a>,
    T::StaticType: Sized,
{
    type StaticType = [T::StaticType];
}
unsafe impl<'a, T: ProvidesStaticType<'a> + ?Sized> ProvidesStaticType<'a> for Box<T> {
    type StaticType = Box<T::StaticType>;
}
unsafe impl<'a, T: ProvidesStaticType<'a> + ?Sized> ProvidesStaticType<'a> for Rc<T> {
    type StaticType = Rc<T::StaticType>;
}
unsafe impl<'a, T: ProvidesStaticType<'a> + ?Sized> ProvidesStaticType<'a> for Arc<T> {
    type StaticType = Arc<T::StaticType>;
}
unsafe impl<'a, T: ProvidesStaticType<'a>> ProvidesStaticType<'a> for Cell<T> {
    type StaticType = Cell<T::StaticType>;
}
unsafe impl<'a, T: ProvidesStaticType<'a>> ProvidesStaticType<'a> for UnsafeCell<T> {
    type StaticType = UnsafeCell<T::StaticType>;
}
unsafe impl<'a, T: ProvidesStaticType<'a>> ProvidesStaticType<'a> for RefCell<T> {
    type StaticType = RefCell<T::StaticType>;
}
unsafe impl<'a, T> ProvidesStaticType<'a> for Option<T>
where
    T: ProvidesStaticType<'a>,
    T::StaticType: Sized,
{
    type StaticType = Option<T::StaticType>;
}
unsafe impl<'a, T, E> ProvidesStaticType<'a> for Result<T, E>
where
    T: ProvidesStaticType<'a>,
    T::StaticType: Sized,
    E: ProvidesStaticType<'a>,
    E::StaticType: Sized,
{
    type StaticType = Result<T::StaticType, E::StaticType>;
}
unsafe impl<'a, T> ProvidesStaticType<'a> for Vec<T>
where
    T: ProvidesStaticType<'a>,
    T::StaticType: Sized,
{
    type StaticType = Vec<T::StaticType>;
}
unsafe impl<'a, K, V> ProvidesStaticType<'a> for HashMap<K, V>
where
    K: ProvidesStaticType<'a>,
    K::StaticType: Sized,
    V: ProvidesStaticType<'a>,
    V::StaticType: Sized,
{
    type StaticType = HashMap<K::StaticType, V::StaticType>;
}
unsafe impl<'a, K, V> ProvidesStaticType<'a> for BTreeMap<K, V>
where
    K: ProvidesStaticType<'a>,
    K::StaticType: Sized,
    V: ProvidesStaticType<'a>,
    V::StaticType: Sized,
{
    type StaticType = BTreeMap<K::StaticType, V::StaticType>;
}

#[cfg(test)]
mod tests {
    use std::fmt::Display;

    use super::*;
    use crate as starlark;

    #[test]
    fn test_can_convert() {
        #[derive(Debug, PartialEq, ProvidesStaticType)]
        struct Value<'a>(&'a str);

        #[derive(ProvidesStaticType)]
        struct Value2<'a>(&'a str);

        // Changing the return type too `Value<'static>` causes a compile error.
        fn convert_value<'a>(x: &'a Value<'a>) -> Option<&'a Value<'a>> {
            <dyn AnyLifetime>::downcast_ref(x)
        }

        fn convert_any<'p, 'a>(x: &'p dyn AnyLifetime<'a>) -> Option<&'p Value<'a>> {
            x.downcast_ref()
        }

        let v = Value("test");
        let v2 = Value2("test");
        assert_eq!(convert_value(&v), Some(&v));
        assert_eq!(convert_any(&v), Some(&v));
        assert_eq!(convert_any(&v2), None);
    }

    #[test]
    fn test_any_lifetime() {
        fn test<'a, A: AnyLifetime<'a>>(expected: TypeId) {
            assert_eq!(expected, A::static_type_id());
        }

        test::<&str>(TypeId::of::<&str>());
        test::<&String>(TypeId::of::<&String>());
        test::<Box<str>>(TypeId::of::<Box<str>>());
    }

    #[test]
    fn test_provides_static_type_id() {
        fn test<'a, A: AnyLifetime<'a>>(expected: TypeId) {
            assert_eq!(expected, A::static_type_id());
        }

        #[derive(ProvidesStaticType)]
        struct Aaa;
        test::<Aaa>(TypeId::of::<Aaa>());

        #[derive(ProvidesStaticType)]
        struct Bbb<'a>(&'a str);
        test::<Bbb>(TypeId::of::<Bbb<'static>>());

        #[derive(ProvidesStaticType)]
        struct Ccc<X>(X);
        test::<Ccc<String>>(TypeId::of::<Ccc<String>>());

        #[derive(ProvidesStaticType)]
        struct LifetimeTypeConst<'a, T, const N: usize>([&'a T; N]);
        test::<LifetimeTypeConst<i32, 3>>(TypeId::of::<LifetimeTypeConst<'static, i32, 3>>());

        #[derive(ProvidesStaticType)]
        struct TypeWithConstraint<T: Display>(T);
        test::<TypeWithConstraint<String>>(TypeId::of::<TypeWithConstraint<String>>());

        struct TypeWhichDoesNotImplementAnyLifetime;

        #[derive(ProvidesStaticType)]
        struct TypeWithStaticLifetime<T: 'static>(T);
        test::<TypeWithStaticLifetime<TypeWhichDoesNotImplementAnyLifetime>>(TypeId::of::<
            TypeWithStaticLifetime<TypeWhichDoesNotImplementAnyLifetime>,
        >());
    }

    #[test]
    fn test_provides_static_type_when_type_parameter_has_bound_with_lifetime() {
        trait My<'a> {}

        #[derive(ProvidesStaticType)]
        struct FooBar<'x, P: My<'x>>(&'x P);
    }
}


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

#[inline(always)]
pub(crate) fn ptr_to_usize<T: ?Sized>(x: &T) -> usize {
    x as *const T as *const () as usize
}

/// Undefined behaviour if the argument is zero, or does not satisfy the alignment
/// of type `T`.
#[inline(always)]
pub(crate) unsafe fn usize_to_ptr<'a, T>(x: usize) -> &'a T {
    debug_assert!(x != 0, "Zero is not a valid pointer");
    debug_assert!(x % std::mem::align_of::<T>() == 0, "Pointer is not aligned");
    &*(x as *const T)
}

#[inline(always)]
pub(crate) unsafe fn ptr_lifetime<'a, 'b, T: ?Sized>(x: &'a T) -> &'b T {
    &*(x as *const T)
}

/// `transmute!(from-type, to-type, value)` will do a [`transmute`](std::mem::transmute),
/// but the original and result types must be specified.
macro_rules! transmute {
    ($from:ty, $to:ty, $e:expr) => {
        std::mem::transmute::<$from, $to>($e)
    };
}

pub(crate) use transmute;


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

//! A data structure for tracking source positions in language implementations
//! The `CodeMap` tracks all source files and maps positions within them to linear indexes as if all
//! source files were concatenated. This allows a source position to be represented by a small
//! 32-bit `Pos` indexing into the `CodeMap`, under the assumption that the total amount of parsed
//! source code will not exceed 4GiB. The `CodeMap` can look up the source file, line, and column
//! of a `Pos` or `Span`, as well as provide source code snippets for error reporting.
use std::cmp;
use std::fmt;
use std::fmt::Display;
use std::hash::Hash;
use std::hash::Hasher;
use std::ops::Add;
use std::ops::Deref;
use std::ops::DerefMut;
use std::ptr;
use std::sync::Arc;

use allocative::Allocative;
use dupe::Dupe;
use once_cell::sync::Lazy;

/// A small, `Copy`, value representing a position in a `CodeMap`'s file.
#[derive(
    Copy, Clone, Dupe, Hash, Eq, PartialEq, PartialOrd, Ord, Debug, Default, Allocative
)]
pub struct Pos(u32);

impl Pos {
    /// Constructor.
    pub const fn new(x: u32) -> Self {
        Self(x)
    }
}

impl Add<u32> for Pos {
    type Output = Pos;
    fn add(self, other: u32) -> Pos {
        Pos(self.0 + other)
    }
}

/// A range of text within a CodeMap.
#[derive(Copy, Dupe, Clone, Hash, Eq, PartialEq, Debug, Default, Allocative)]
pub struct Span {
    /// The position in the codemap representing the first byte of the span.
    begin: Pos,

    /// The position after the last byte of the span.
    end: Pos,
}

impl Span {
    /// Create a new span. Panics if `end < begin`.
    pub fn new(begin: Pos, end: Pos) -> Self {
        assert!(begin <= end);
        Span { begin, end }
    }

    /// The position at the first byte of the span.
    pub fn begin(self) -> Pos {
        self.begin
    }

    /// The position after the last byte of the span.
    pub fn end(self) -> Pos {
        self.end
    }

    /// The length in bytes of the text of the span
    #[cfg(test)]
    pub fn len(self) -> u32 {
        self.end.0 - self.begin.0
    }

    /// Create a span that encloses both `self` and `other`.
    pub fn merge(self, other: Span) -> Span {
        Span {
            begin: cmp::min(self.begin, other.begin),
            end: cmp::max(self.end, other.end),
        }
    }

    /// Empty span in the end of this span.
    pub(crate) fn end_span(self) -> Span {
        Span {
            begin: self.end,
            end: self.end,
        }
    }

    /// Determines whether a `pos` is within this span.
    pub fn contains(self, pos: Pos) -> bool {
        self.begin <= pos && pos <= self.end
    }
}

/// Associate a Span with a value of arbitrary type (e.g. an AST node).
#[derive(Clone, PartialEq, Eq, Hash, Debug, Copy)]
pub struct Spanned<T> {
    /// Data in the node.
    pub node: T,
    pub(crate) span: Span,
}

impl<T> Spanned<T> {
    /// Apply the function to the node, keep the span.
    pub fn into_map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
        Spanned {
            node: f(self.node),
            span: self.span,
        }
    }

    /// Apply the function to the node, keep the span.
    pub fn map<U>(&self, f: impl FnOnce(&T) -> U) -> Spanned<U> {
        Spanned {
            node: f(&self.node),
            span: self.span,
        }
    }

    pub(crate) fn as_ref(&self) -> Spanned<&T> {
        Spanned {
            node: &self.node,
            span: self.span,
        }
    }
}

impl<T> Deref for Spanned<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.node
    }
}

impl<T> DerefMut for Spanned<T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.node
    }
}

// A cheap unowned unique identifier per file/CodeMap,
// somewhat delving into internal details.
// Remains unique because we take a reference to the CodeMap.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Dupe)]
pub(crate) struct CodeMapId(*const ());

impl CodeMapId {
    pub(crate) const EMPTY: CodeMapId = CodeMapId(ptr::null());
}

#[derive(Clone, Dupe, Allocative)]
enum CodeMapImpl {
    Real(Arc<CodeMapData>),
    #[allocative(skip)]
    Native(&'static NativeCodeMap),
}

/// A data structure recording a source code file for position lookup.
#[derive(Clone, Dupe, Allocative)]
pub(crate) struct CodeMap(CodeMapImpl);

/// A `CodeMap`'s record of a source file.
#[derive(Allocative)]
struct CodeMapData {
    /// The filename as it would be displayed in an error message.
    filename: String,
    /// Contents of the file.
    source: String,
    /// Byte positions of line beginnings.
    lines: Vec<Pos>,
}

/// "Codemap" for `.rs` files.
pub(crate) struct NativeCodeMap {
    filename: &'static str,
    start: LineCol,
}

impl NativeCodeMap {
    const SOURCE: &'static str = "<native>";

    pub(crate) const FULL_SPAN: Span = Span {
        begin: Pos::new(0),
        end: Pos::new(Self::SOURCE.len() as u32),
    };

    pub(crate) const fn new(filename: &'static str, line: u32, column: u32) -> NativeCodeMap {
        Self {
            filename,
            start: LineCol {
                line: line as usize,
                column: column as usize,
            },
        }
    }

    pub(crate) const fn to_codemap(&'static self) -> CodeMap {
        CodeMap(CodeMapImpl::Native(self))
    }
}

impl Default for CodeMap {
    fn default() -> Self {
        Self::new("".to_owned(), "".to_owned())
    }
}

impl fmt::Debug for CodeMap {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "CodeMap({:?})", self.filename())
    }
}

impl PartialEq for CodeMap {
    /// Compares by identity
    fn eq(&self, other: &Self) -> bool {
        self.id() == other.id()
    }
}

impl Eq for CodeMap {}

impl Hash for CodeMap {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.id().hash(state)
    }
}

impl CodeMap {
    /// Creates an new `CodeMap`.
    pub(crate) fn new(filename: String, source: String) -> CodeMap {
        let mut lines = vec![Pos(0)];
        lines.extend(source.match_indices('\n').map(|(p, _)| Pos(p as u32 + 1)));

        CodeMap(CodeMapImpl::Real(Arc::new(CodeMapData {
            filename,
            source,
            lines,
        })))
    }

    pub(crate) fn empty_static() -> &'static CodeMap {
        static EMPTY_CODEMAP: Lazy<CodeMap> = Lazy::new(CodeMap::default);
        &EMPTY_CODEMAP
    }

    /// Only used internally for profiling optimisations
    pub(crate) fn id(&self) -> CodeMapId {
        match &self.0 {
            CodeMapImpl::Real(data) => CodeMapId(Arc::as_ptr(data) as *const ()),
            CodeMapImpl::Native(data) => CodeMapId(*data as *const NativeCodeMap as *const ()),
        }
    }

    pub(crate) fn full_span(&self) -> Span {
        let source = self.source();
        Span {
            begin: Pos(0),
            end: Pos(source.len() as u32),
        }
    }

    /// Gets the file and its line and column ranges represented by a `Span`.
    pub(crate) fn file_span(&self, span: Span) -> FileSpan {
        FileSpan {
            file: self.dupe(),
            span,
        }
    }

    /// Gets the name of the file
    pub fn filename(&self) -> &str {
        match &self.0 {
            CodeMapImpl::Real(data) => &data.filename,
            CodeMapImpl::Native(data) => data.filename,
        }
    }

    /// Gets the line number of a Pos.
    ///
    /// The lines are 0-indexed (first line is numbered 0)
    ///
    /// Panics if `pos` is not within this file's span.
    pub(crate) fn find_line(&self, pos: Pos) -> usize {
        assert!(pos <= self.full_span().end());
        match &self.0 {
            CodeMapImpl::Real(data) => match data.lines.binary_search(&pos) {
                Ok(i) => i,
                Err(i) => i - 1,
            },
            CodeMapImpl::Native(data) => data.start.line,
        }
    }

    /// Gets the line and column of a Pos.
    ///
    /// Panics if `pos` is not with this file's span or
    /// if `pos` points to a byte in the middle of a UTF-8 character.
    fn find_line_col(&self, pos: Pos) -> LineCol {
        assert!(pos <= self.full_span().end());
        match &self.0 {
            CodeMapImpl::Real(_) => {
                let line = self.find_line(pos);
                let line_span = self.line_span(line);
                let byte_col = pos.0 - line_span.begin.0;
                let column = self.source_span(line_span)[..byte_col as usize]
                    .chars()
                    .count();

                LineCol { line, column }
            }
            CodeMapImpl::Native(data) => LineCol {
                line: data.start.line,
                column: data.start.column + pos.0 as usize,
            },
        }
    }

    /// Gets the full source text of the file
    pub fn source(&self) -> &str {
        match &self.0 {
            CodeMapImpl::Real(data) => &data.source,
            CodeMapImpl::Native(_) => NativeCodeMap::SOURCE,
        }
    }

    /// Gets the source text of a Span.
    ///
    /// Panics if `span` is not entirely within this file.
    pub(crate) fn source_span(&self, span: Span) -> &str {
        &self.source()[(span.begin.0 as usize)..(span.end.0 as usize)]
    }

    /// Like `line_span_opt` but panics if the line number is out of range.
    pub(crate) fn line_span(&self, line: usize) -> Span {
        self.line_span_opt(line)
            .unwrap_or_else(|| panic!("Line {} is out of range for {:?}", line, self))
    }

    /// Gets the span representing a line by line number.
    ///
    /// The line number is 0-indexed (first line is numbered 0). The returned span includes the
    /// line terminator.
    ///
    /// Returns None if the number if out of range.
    pub(crate) fn line_span_opt(&self, line: usize) -> Option<Span> {
        match &self.0 {
            CodeMapImpl::Real(data) if line < data.lines.len() => Some(Span {
                begin: data.lines[line],
                end: *data.lines.get(line + 1).unwrap_or(&self.full_span().end),
            }),
            CodeMapImpl::Native(data) if line == data.start.line => Some(Span {
                begin: Pos(0),
                end: Pos(NativeCodeMap::SOURCE.len() as u32),
            }),
            _ => None,
        }
    }

    pub(crate) fn resolve_span(&self, span: Span) -> ResolvedSpan {
        let begin = self.find_line_col(span.begin);
        let end = self.find_line_col(span.end);
        ResolvedSpan::from_span(begin, end)
    }

    /// Gets the source text of a line.
    ///
    /// The string returned does not include the terminating \r or \n characters.
    ///
    /// Panics if the line number is out of range.
    pub fn source_line(&self, line: usize) -> &str {
        self.source_span(self.line_span(line))
            .trim_end_matches(&['\n', '\r'][..])
    }

    pub(crate) fn source_line_at_pos(&self, pos: Pos) -> &str {
        self.source_line(self.find_line(pos))
    }
}

/// A line and column.
#[derive(Copy, Clone, Dupe, Hash, Eq, PartialEq, Debug)]
pub(crate) struct LineCol {
    /// The line number within the file (0-indexed).
    pub line: usize,

    /// The column within the line (0-indexed in characters).
    pub column: usize,
}

impl LineCol {
    fn _testing_parse(line_col: &str) -> LineCol {
        let (line, col) = line_col.split_once(':').unwrap();
        LineCol {
            line: line.parse::<usize>().unwrap().checked_sub(1).unwrap(),
            column: col.parse::<usize>().unwrap().checked_sub(1).unwrap(),
        }
    }
}

/// A file, and a line and column range within it.
#[derive(Clone, Copy, Dupe, Eq, PartialEq, Debug)]
pub struct FileSpanRef<'a> {
    pub(crate) file: &'a CodeMap,
    pub(crate) span: Span,
}

/// A file, and a line and column range within it.
#[derive(Clone, Dupe, Eq, PartialEq, Debug, Hash, Allocative)]
pub struct FileSpan {
    pub(crate) file: CodeMap,
    pub(crate) span: Span,
}

impl<'a> fmt::Display for FileSpanRef<'a> {
    /// Formats the span as `filename:start_line:start_column: end_line:end_column`,
    /// or if the span is zero-length, `filename:line:column`, with a 1-indexed line and column.
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "{}:{}", self.file.filename(), self.resolve_span())
    }
}

impl fmt::Display for FileSpan {
    /// Formats the span as `filename:start_line:start_column: end_line:end_column`,
    /// or if the span is zero-length, `filename:line:column`, with a 1-indexed line and column.
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        fmt::Display::fmt(&self.as_ref(), f)
    }
}

impl<'a> FileSpanRef<'a> {
    /// Filename of this reference.
    pub fn filename(&self) -> &str {
        self.file.filename()
    }

    /// Convert to the owned span.
    pub fn to_file_span(self) -> FileSpan {
        FileSpan {
            file: self.file.dupe(),
            span: self.span,
        }
    }

    /// Resolve span offsets to lines and columns.
    pub fn resolve_span(&self) -> ResolvedSpan {
        self.file.resolve_span(self.span)
    }
}

impl FileSpan {
    /// Creates an new `FileSpan` covering the entire file.
    pub fn new(filename: String, source: String) -> Self {
        let file = CodeMap::new(filename, source);
        let span = file.full_span();
        Self { file, span }
    }

    /// Filename of this span.
    pub fn filename(&self) -> &str {
        self.file.filename()
    }

    /// Resolve the span.
    pub fn source_span(&self) -> &str {
        self.file.source_span(self.span)
    }

    /// Cheap reference to the span.
    pub fn as_ref(&self) -> FileSpanRef {
        FileSpanRef {
            file: &self.file,
            span: self.span,
        }
    }

    /// Resolve the span to lines and columns.
    pub fn resolve_span(&self) -> ResolvedSpan {
        self.as_ref().resolve_span()
    }

    /// Resolve the span to lines and columns.
    pub fn resolve(&self) -> ResolvedFileSpan {
        ResolvedFileSpan {
            file: self.file.filename().to_owned(),
            span: self.file.resolve_span(self.span),
        }
    }
}

/// The locations of values within a span.
/// All are 0-based, but print out with 1-based.
#[derive(Debug, Dupe, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ResolvedSpan {
    /// 0-based line number of the beginning of the span.
    pub begin_line: usize,
    /// 0-based character column number of the beginning of the span.
    pub begin_column: usize,
    /// 0-based line number of the end of the span.
    pub end_line: usize,
    /// 0-based character column number of the end of the span.
    pub end_column: usize,
}

impl Display for ResolvedSpan {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let single_line = self.begin_line == self.end_line;
        let is_empty = single_line && self.begin_column == self.end_column;

        if is_empty {
            write!(f, "{}:{}", self.begin_line + 1, self.begin_column + 1)
        } else if single_line {
            write!(
                f,
                "{}:{}-{}",
                self.begin_line + 1,
                self.begin_column + 1,
                self.end_column + 1
            )
        } else {
            write!(
                f,
                "{}:{}-{}:{}",
                self.begin_line + 1,
                self.begin_column + 1,
                self.end_line + 1,
                self.end_column + 1
            )
        }
    }
}

impl From<ResolvedSpan> for lsp_types::Range {
    fn from(span: ResolvedSpan) -> Self {
        lsp_types::Range::new(
            lsp_types::Position::new(span.begin_line as u32, span.begin_column as u32),
            lsp_types::Position::new(span.end_line as u32, span.end_column as u32),
        )
    }
}

impl ResolvedSpan {
    /// Check that the given position is contained within this span.
    /// Includes positions both at the beginning and the end of the range.
    pub(crate) fn contains(&self, pos: LineCol) -> bool {
        (self.begin_line < pos.line
            || (self.begin_line == pos.line && self.begin_column <= pos.column))
            && (self.end_line > pos.line
                || (self.end_line == pos.line && self.end_column >= pos.column))
    }

    fn from_span(begin: LineCol, end: LineCol) -> Self {
        Self {
            begin_line: begin.line,
            begin_column: begin.column,
            end_line: end.line,
            end_column: end.column,
        }
    }

    fn _testing_parse(span: &str) -> ResolvedSpan {
        match span.split_once('-') {
            None => {
                let line_col = LineCol::_testing_parse(span);
                ResolvedSpan::from_span(line_col, line_col)
            }
            Some((begin, end)) => {
                let begin = LineCol::_testing_parse(begin);
                if end.contains(':') {
                    let end = LineCol::_testing_parse(end);
                    ResolvedSpan::from_span(begin, end)
                } else {
                    let end_col = end.parse::<usize>().unwrap().checked_sub(1).unwrap();
                    ResolvedSpan::from_span(
                        begin,
                        LineCol {
                            line: begin.line,
                            column: end_col,
                        },
                    )
                }
            }
        }
    }
}

/// File name and line and column pairs for a span.
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct ResolvedFileSpan {
    /// File name.
    pub file: String,
    /// The span.
    pub span: ResolvedSpan,
}

impl ResolvedFileSpan {
    pub(crate) fn _testing_parse(span: &str) -> ResolvedFileSpan {
        let (file, span) = span.split_once(':').unwrap();
        ResolvedFileSpan {
            file: file.to_owned(),
            span: ResolvedSpan::_testing_parse(span),
        }
    }
}

impl Display for ResolvedFileSpan {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}:{}", self.file, self.span)
    }
}

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

    #[test]
    fn test_codemap() {
        let source = "abcd\nefghij\nqwerty";
        let codemap = CodeMap::new("test1.rs".to_owned(), source.to_owned());
        let start = codemap.full_span().begin;

        // Test .name()
        assert_eq!(codemap.filename(), "test1.rs");

        // Test .find_line_col()
        assert_eq!(codemap.find_line_col(start), LineCol { line: 0, column: 0 });
        assert_eq!(
            codemap.find_line_col(start + 4),
            LineCol { line: 0, column: 4 }
        );
        assert_eq!(
            codemap.find_line_col(start + 5),
            LineCol { line: 1, column: 0 }
        );
        assert_eq!(
            codemap.find_line_col(start + 16),
            LineCol { line: 2, column: 4 }
        );

        // Test .source() and num lines.
        assert_eq!(codemap.source(), source);
        assert!(
            matches!(&codemap, CodeMap(CodeMapImpl::Real(codemap)) if codemap.lines.len() == 3)
        );

        // Test generic properties on each line
        for line in 0..3 {
            let line_str = codemap.source_line(line);
            let line_span = codemap.line_span(line);
            // The line_str omits trailing newlines
            assert_eq!(
                line_str.len() + if line < 2 { 1 } else { 0 },
                line_span.len() as usize
            );
            assert_eq!(line_str, source.lines().nth(line).unwrap());
            assert_eq!(codemap.find_line(line_span.begin), line);
            // The final character might be a newline, which is counted as the next line.
            // Not sure this is a good thing!
            let end = Pos(line_span.end().0 - 1);
            assert_eq!(codemap.find_line(end), line);
            assert_eq!(
                codemap.find_line_col(line_span.begin),
                LineCol { line, column: 0 }
            );
            assert_eq!(
                codemap.find_line_col(end),
                LineCol {
                    line,
                    column: line_span.len() as usize - 1
                }
            );
        }
        assert_eq!(codemap.line_span_opt(4), None);
    }

    #[test]
    fn test_multibyte() {
        let content = "65Â°00â€²N 18Â°00â€²W æ±‰è¯\nðŸ”¬";
        let codemap = CodeMap::new("<test>".to_owned(), content.to_owned());

        assert_eq!(
            codemap.find_line_col(codemap.full_span().begin + 21),
            LineCol {
                line: 0,
                column: 15
            }
        );
        assert_eq!(
            codemap.find_line_col(codemap.full_span().begin + 28),
            LineCol {
                line: 0,
                column: 18
            }
        );
        assert_eq!(
            codemap.find_line_col(codemap.full_span().begin + 33),
            LineCol { line: 1, column: 1 }
        );
    }

    #[test]
    fn test_line_col_span_display_point() {
        let line_col = LineCol { line: 0, column: 0 };
        let span = ResolvedSpan::from_span(line_col, line_col);
        assert_eq!(span.to_string(), "1:1");
    }

    #[test]
    fn test_line_col_span_display_single_line_span() {
        let begin = LineCol { line: 0, column: 0 };
        let end = LineCol {
            line: 0,
            column: 32,
        };
        let span = ResolvedSpan::from_span(begin, end);
        assert_eq!(span.to_string(), "1:1-33");
    }

    #[test]
    fn test_line_col_span_display_multi_line_span() {
        let begin = LineCol { line: 0, column: 0 };
        let end = LineCol {
            line: 2,
            column: 32,
        };
        let span = ResolvedSpan::from_span(begin, end);
        assert_eq!(span.to_string(), "1:1-3:33");
    }

    #[test]
    fn test_native_code_map() {
        static NATIVE_CODEMAP: NativeCodeMap = NativeCodeMap::new("test.rs", 100, 200);
        static CODEMAP: CodeMap = NATIVE_CODEMAP.to_codemap();
        assert_eq!(NativeCodeMap::SOURCE, CODEMAP.source());
        assert_eq!(NativeCodeMap::SOURCE, CODEMAP.source_line(100));
        assert_eq!(
            ResolvedSpan {
                begin_line: 100,
                begin_column: 200,
                end_line: 100,
                end_column: 200 + NativeCodeMap::SOURCE.len(),
            },
            CODEMAP.resolve_span(CODEMAP.full_span())
        );
    }

    #[test]
    fn test_resolved_span_contains() {
        let span = ResolvedSpan {
            begin_line: 2,
            begin_column: 3,
            end_line: 4,
            end_column: 5,
        };
        assert_eq!(span.contains(LineCol { line: 0, column: 7 }), false);
        assert_eq!(span.contains(LineCol { line: 2, column: 2 }), false);
        assert_eq!(span.contains(LineCol { line: 2, column: 3 }), true);
        assert_eq!(span.contains(LineCol { line: 2, column: 9 }), true);
        assert_eq!(span.contains(LineCol { line: 3, column: 1 }), true);
        assert_eq!(span.contains(LineCol { line: 4, column: 4 }), true);
        assert_eq!(span.contains(LineCol { line: 4, column: 5 }), true);
        assert_eq!(span.contains(LineCol { line: 4, column: 6 }), false);
        assert_eq!(span.contains(LineCol { line: 5, column: 0 }), false);
    }
}


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

//! A trait to represent zero-cost conversions.

use std::alloc::Layout;
use std::collections::HashMap;
use std::collections::HashSet;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::ptr;

pub use starlark_derive::Coerce;
use starlark_map::small_map::SmallMap;

/// A marker trait such that the existence of `From: Coerce<To>` implies
/// that `From` can be treat as `To` without any data manipulation.
/// Particularly useful for containers, e.g. `Vec<From>` can be treated as
/// `Vec<To>` in _O(1)_. If such an instance is available,
/// you can use [`coerce`] to perform the conversion.
///
/// Importantly, you must make sure Rust does not change the type representation
/// between the different types (typically using a `repr` directive),
/// and it must be safe for the `From` to be treated as `To`, namely same (or less restrictive) alignment,
/// no additional invariants, value can be dropped as `To`.
///
/// One use of `Coerce` is around newtype wrappers:
///
/// ```
/// use starlark::coerce::{Coerce, coerce};
/// #[repr(transparent)]
/// #[derive(Debug, Coerce)]
/// struct Wrapper(String);
///
/// let value = vec![Wrapper("hello".to_owned()), Wrapper("world".to_owned())];
/// assert_eq!(
///     coerce::<_, &Vec<String>>(&value).join(" "),
///     "hello world"
/// );
/// let mut value = coerce::<_, Vec<String>>(value);
/// assert_eq!(value.pop(), Some("world".to_owned()));
/// ```
///
/// Another involves containers:
///
/// ```
/// use starlark::coerce::{Coerce, coerce};
/// # #[derive(Coerce)]
/// # #[repr(transparent)]
/// # struct Wrapper(String);
/// #[derive(Coerce)]
/// #[repr(C)]
/// struct Container<T>(i32, T);
///
/// let value = Container(20, Wrapper("twenty".to_owned()));
/// assert_eq!(
///     coerce::<_, &Container<String>>(&value).1,
///     "twenty"
/// );
/// ```
///
/// If you only need [`coerce`] on newtype references,
/// then the [`ref-cast` crate](https://crates.io/crates/ref-cast)
/// provides that, along with automatic derivations (no `unsafe` required).
pub unsafe trait Coerce<To: ?Sized> {}

/// A marker trait such that the existence of `From: CoerceKey<To>` implies
/// that `From` can be treat as `To` without any data manipulation.
/// Furthermore, above and beyond [`Coerce`], any provided [`Hash`](std::hash::Hash),
/// [`Eq`], [`PartialEq`], [`Ord`] and [`PartialOrd`] traits must give identical results
/// on the `From` and `To` values.
///
/// This trait is mostly expected to be a requirement for the keys of associative-map
/// containers, hence the `Key` in the name.
pub unsafe trait CoerceKey<To: ?Sized>: Coerce<To> {}

unsafe impl<'a, From: ?Sized, To: ?Sized> Coerce<&'a To> for &'a From where From: Coerce<To> {}
unsafe impl<'a, From: ?Sized, To: ?Sized> CoerceKey<&'a To> for &'a From where From: CoerceKey<To> {}

unsafe impl<From, To> Coerce<[To]> for [From] where From: Coerce<To> {}
unsafe impl<From, To> CoerceKey<[To]> for [From] where From: CoerceKey<To> {}

unsafe impl<From, To> Coerce<Vec<To>> for Vec<From> where From: Coerce<To> {}
unsafe impl<From, To> CoerceKey<Vec<To>> for Vec<From> where From: CoerceKey<To> {}

unsafe impl<From: ?Sized, To: ?Sized> CoerceKey<Box<To>> for Box<From> where From: CoerceKey<To> {}
unsafe impl<From: ?Sized, To: ?Sized> Coerce<Box<To>> for Box<From> where From: Coerce<To> {}

unsafe impl<From, To> Coerce<HashSet<To>> for HashSet<From> where From: CoerceKey<To> {}

unsafe impl<FromK, FromV, ToK, ToV> Coerce<HashMap<ToK, ToV>> for HashMap<FromK, FromV>
where
    FromK: CoerceKey<ToK>,
    FromV: Coerce<ToV>,
{
}

unsafe impl<From1: Coerce<To1>, To1> Coerce<(To1,)> for (From1,) {}
unsafe impl<From1: CoerceKey<To1>, To1> CoerceKey<(To1,)> for (From1,) {}

unsafe impl<From1: Coerce<To1>, From2: Coerce<To2>, To1, To2> Coerce<(To1, To2)>
    for (From1, From2)
{
}
unsafe impl<From1: CoerceKey<To1>, From2: CoerceKey<To2>, To1, To2> CoerceKey<(To1, To2)>
    for (From1, From2)
{
}
unsafe impl<From: Coerce<To>, To, const N: usize> Coerce<[To; N]> for [From; N] {}
unsafe impl<From: CoerceKey<To>, To, const N: usize> CoerceKey<[To; N]> for [From; N] {}

unsafe impl<From, To> Coerce<PhantomData<To>> for PhantomData<From> {}

// We can't define a blanket `Coerce<T> for T` because that conflicts with the specific traits above.
// Therefore, we define instances where we think they might be useful, rather than trying to do every concrete type.
unsafe impl Coerce<String> for String {}
unsafe impl CoerceKey<String> for String {}

unsafe impl Coerce<str> for str {}
unsafe impl CoerceKey<str> for str {}

unsafe impl Coerce<()> for () {}
unsafe impl CoerceKey<()> for () {}

unsafe impl<FromK, FromV, ToK, ToV> Coerce<SmallMap<ToK, ToV>> for SmallMap<FromK, FromV>
where
    FromK: CoerceKey<ToK>,
    FromV: Coerce<ToV>,
{
}

/// Safely convert between types which have a `Coerce` relationship.
/// Often the second type argument will need to be given explicitly,
/// e.g. `coerce::<_, ToType>(x)`.
#[inline]
pub fn coerce<From, To>(x: From) -> To
where
    From: Coerce<To>,
{
    assert_eq!(Layout::new::<From>(), Layout::new::<To>());
    let x = ManuallyDrop::new(x);
    unsafe { ptr::read(x.deref() as *const From as *const To) }
}

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

    use super::*;
    use crate as starlark;

    #[test]
    fn test_ptr_coerce() {
        fn f<'v>(x: (&'static str,)) -> (&'v str,) {
            coerce(x)
        }

        let x = "test".to_owned();
        assert_eq!(f(("test",)), (x.as_str(),))
    }

    #[test]
    fn test_coerce_lifetime() {
        #[derive(Coerce)]
        #[repr(transparent)]
        struct NewtypeWithLifetime<'v>(&'v [usize]);

        let newtype = NewtypeWithLifetime(&[1, 2]);
        assert_eq!(&[1, 2], coerce(newtype))
    }

    #[test]
    fn test_coerce_type_and_lifetime_params() {
        #[derive(Coerce)]
        #[repr(C)]
        struct Aaa<'a>(&'a u32);
        #[derive(Coerce)]
        #[repr(C)]
        struct Bbb<'a>(&'a u32);

        unsafe impl<'a> Coerce<Bbb<'a>> for Aaa<'a> {}

        #[derive(Coerce)]
        #[repr(C)]
        struct StructWithLifetimeAndTypeParams<'a, X> {
            x: X,
            _marker: marker::PhantomData<&'a u32>,
        }

        let ten = 10;
        let old = StructWithLifetimeAndTypeParams::<Aaa> {
            x: Aaa(&ten),
            _marker: marker::PhantomData,
        };

        let new: StructWithLifetimeAndTypeParams<Bbb> = coerce(old);
        assert_eq!(10, *new.x.0);
    }

    #[test]
    fn test_coerce_is_unsound() {
        // TODO(nga): fix it.

        #[derive(Coerce)]
        #[repr(transparent)]
        struct Newtype(u8);

        #[derive(Coerce)]
        #[repr(transparent)]
        struct Struct<T: Trait>(T::Assoc);

        trait Trait {
            type Assoc;
        }
        impl Trait for u8 {
            type Assoc = ();
        }
        impl Trait for Newtype {
            type Assoc = [u8; 50];
        }

        let s: &Struct<u8> = &Struct(());
        // This should fail to compile because `[u8; 50]` is not coercible to `()`.
        let _c: &Struct<Newtype> = coerce(s);
    }
}


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

#[cfg(rust_nightly)]
pub(crate) use std::intrinsics::likely;
#[cfg(rust_nightly)]
pub(crate) use std::intrinsics::unlikely;

#[cfg(not(rust_nightly))]
#[inline]
pub(crate) fn likely(b: bool) -> bool {
    b
}

#[cfg(not(rust_nightly))]
#[inline]
pub(crate) fn unlikely(b: bool) -> bool {
    b
}