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

use crate::assert;
use crate::syntax::lexer::Token::*;

#[test]
fn test_int_lit() {
    assert_eq!(assert::lex("0 123"), "0 123 \n");
    assert_eq!(assert::lex("0x7F 0x7d"), "127 125 \n");
    assert_eq!(assert::lex("0B1011 0b1010"), "11 10 \n");
    assert_eq!(assert::lex("0o755 0O753"), "493 491 \n");
    // Starlark requires us to ban leading zeros (confusion with implicit octal)
    assert::parse_fail("x = !01!");
}

#[test]
fn test_indentation() {
    assert_eq!(
        assert::lex(
            "
+
  -
      /
      *
  =
    %
      .
+=
",
        ),
        "\n + \n \t - \n \t / \n * \n #dedent = \n \t % \n \t . \n #dedent #dedent #dedent += \n \n"
    );
}

#[test]
fn test_symbols() {
    assert_eq!(
        assert::lex(", ; : += -= *= /= //= %= == != <= >= ** = < > - + * % / // . { } [ ] ( ) |"),
        ", ; : += -= *= /= //= %= == != <= >= ** = < > - + * % / // . { } [ ] ( ) | \n",
    );
    assert_eq!(assert::lex(",;:{}[]()|"), ", ; : { } [ ] ( ) | \n",);
}

#[test]
fn test_keywords() {
    assert_eq!(
        assert::lex(
            "and else load break for not not  in continue if or def in pass elif return lambda"
        ),
        "and else load break for not not in continue if or def in pass elif return lambda \n"
    );
}

// Regression test for https://github.com/google/starlark-rust/issues/44.
#[test]
fn test_number_collated_with_keywords_or_identifier() {
    assert_eq!(
        assert::lex("0in 1and 2else 3load 4break 5for 6not 7not  in 8continue 10identifier11"),
        "0 in 1 and 2 else 3 load 4 break 5 for 6 not 7 not in 8 continue 10 identifier11 \n"
    );
}

#[test]
fn test_reserved() {
    let reserved =
        "as import is class nonlocal del raise except try finally while from with global yield"
            .split_whitespace();
    for x in reserved {
        assert::parse_fail(&format!("!{}! = 1", x));
    }
}

#[test]
fn test_comment() {
    // Comment should be ignored
    assert_eq!(assert::lex("# a comment\n"), "\n");
    assert_eq!(assert::lex(" # a comment\n"), "\n");
    assert_eq!(assert::lex("a # a comment\n"), "a \n \n");
    // But it should not eat everything
    assert_eq!(assert::lex("[\n# a comment\n]"), "[ ] \n");
}

#[test]
fn test_identifier() {
    assert_eq!(
        assert::lex("a identifier CAPS _CAPS _0123"),
        "a identifier CAPS _CAPS _0123 \n"
    )
}

#[test]
fn test_string_lit() {
    assert_eq!(
        assert::lex("'123' \"123\" '' \"\" '\\'' \"\\\"\" '\"' \"'\" '\\n' '\\w'"),
        "\"123\" \"123\" \"\" \"\" \"\'\" \"\\\"\" \"\\\"\" \"\'\" \"\\n\" \"\\\\w\" \n"
    );

    // unfinished string literal
    assert::parse_fail("!'!\n'");
    assert::parse_fail("!\"!\n\"");
    assert::parse_fail("this = a + test + !r\"!");
    assert::parse_fail("test + !\' of thing that!");
    assert::parse_fail("test + !\' of thing that!\n'");

    // Multiline string
    assert_eq!(
        assert::lex("'''''' '''\\n''' '''\n''' \"\"\"\"\"\" \"\"\"\\n\"\"\" \"\"\"\n\"\"\""),
        "\"\" \"\\n\" \"\\n\" \"\" \"\\n\" \"\\n\" \n"
    );
    // Raw string
    assert_eq!(
        assert::lex("r'' r\"\" r'\\'' r\"\\\"\" r'\"' r\"'\" r'\\n'"),
        "\"\" \"\" \"\'\" \"\\\"\" \"\\\"\" \"\'\" \"\\\\n\" \n"
    );
}

#[test]
fn test_string_escape() {
    assert_eq!(assert::lex("'\\0\\0\\1n'"), "\"\\u0000\\u0000\\u0001n\" \n");
    assert_eq!(
        assert::lex("'\\0\\00\\000\\0000'"),
        "\"\\u0000\\u0000\\u0000\\u00000\" \n"
    );
    assert_eq!(assert::lex("'\\x000'"), "\"\\u00000\" \n");
    assert_eq!(assert::lex("'\\372x'"), "\"Ãºx\" \n");
    assert::parse_fail("test 'more !\\xT!Z");
    assert::parse_fail("test + 'more !\\UFFFFFFFF! overflows'");
    assert::parse_fail("test 'more !\\x0y!abc'");
    assert::parse_fail("test 'more !\\x0!");
}

#[test]
fn test_simple_example() {
    assert_eq!(
        assert::lex(
            "\"\"\"A docstring.\"\"\"

def _impl(ctx):
  # Print Hello, World!
  print('Hello, World!')
"
        ),
        "\"A docstring.\" \n \n def _impl ( ctx ) : \n \t print ( \"Hello, World!\" ) \n \n #dedent"
    );
}

#[test]
fn test_escape_newline() {
    assert_eq!(assert::lex("a \\\nb"), "a b \n");
}

#[test]
fn test_lexer_multiline_triple() {
    assert_eq!(
        assert::lex(
            r#"
cmd = """A \
    B \
    C \
    """"#,
        ),
        "\n cmd = \"A     B     C     \" \n"
    );
}

#[test]
fn test_span() {
    let expected = vec![
        (0, Newline, 1),
        (1, Def, 4),
        (5, Identifier("test".to_owned()), 9),
        (9, OpeningRound, 10),
        (10, Identifier("a".to_owned()), 11),
        (11, ClosingRound, 12),
        (12, Colon, 13),
        (13, Newline, 14),
        (14, Indent, 16),
        (16, Identifier("fail".to_owned()), 20),
        (20, OpeningRound, 21),
        (21, Identifier("a".to_owned()), 22),
        (22, ClosingRound, 23),
        (23, Newline, 24),
        (24, Newline, 25),
        (25, Dedent, 25),
        (25, Identifier("test".to_owned()), 29),
        (29, OpeningRound, 30),
        (30, String("abc".to_owned()), 35),
        (35, ClosingRound, 36),
        (36, Newline, 37),
        (37, Newline, 37),
    ];

    let actual = assert::lex_tokens(
        r#"
def test(a):
  fail(a)

test("abc")
"#,
    );
    assert_eq!(expected, actual);
}

#[test]
fn test_lexer_final_comment() {
    assert_eq!(
        assert::lex(
            r#"
x
# test"#,
        ),
        "\n x \n \n"
    );
}

#[test]
fn test_lexer_dedent() {
    assert_eq!(
        assert::lex(
            r#"
def stuff():
  if 1:
    if 1:
      pass
  pass
"#
        ),
        "\n def stuff ( ) : \n \t if 1 : \n \t if 1 : \n \t pass \n #dedent #dedent pass \n \n #dedent"
    );
}

#[test]
fn test_lexer_operators() {
    assert_eq!(assert::lex("1+-2"), "1 + - 2 \n");
    assert_eq!(assert::lex("1+------2"), "1 + - - - - - - 2 \n");
    assert::eq("1+------2", "3");
    assert_eq!(assert::lex("///==/+-"), "// /= = / + - \n");
}

#[test]
fn test_lexer_error_messages() {
    // What are the common errors people make.
    // Do they have good error messages and span locations.
    fn f(program: &str, msg: &str) {
        assert::parse_fail(program);
        assert::fail(&program.replace('!', ""), msg);
    }

    f("unknown !$!&%+ operator", "invalid input `$`");
    f("an !'incomplete string!\nends", "unfinished string literal");
    f(
        "an + 'invalid escape !\\x3 ! character'",
        "invalid string escape sequence `x3 `",
    );
    f(
        "leading_zero = !003! + 8",
        "integer cannot have leading 0, got `003`",
    );
    f("a + (test!]! + c", "unexpected symbol ']' here");
    f(
        "reserved_word = !raise! + 1",
        "cannot use reserved keyword `raise`",
    );
}

#[test]
fn test_float_lit() {
    assert_eq!(assert::lex("0.0 0. .0"), "0 0 0 \n");
    assert_eq!(
        assert::lex("1e10 1e+10 1e-10"),
        "10000000000 10000000000 0.0000000001 \n"
    );
    assert_eq!(
        assert::lex("1.1e10 1.1e+10 1.1e-10"),
        "11000000000 11000000000 0.00000000011 \n"
    );
    assert_eq!(
        assert::lex("0. .123 3.14 .2e3 1E+4"),
        "0 0.123 3.14 200 10000 \n"
    );
}


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

//! The AST of Starlark as [`AstModule`], along with a [`parse`](AstModule::parse) function.

pub use dialect::Dialect;
pub use dialect::DialectTypes;
pub use module::AstModule;
pub use parser::AstLoad;

pub(crate) mod ast;
pub(crate) mod cursors;
mod dialect;
#[cfg(test)]
mod grammar_tests;
pub(crate) mod lexer;
#[cfg(test)]
mod lexer_tests;
pub(crate) mod module;
pub(crate) mod parser;
pub(crate) mod payload_map;
#[cfg(test)]
mod testcases;
pub(crate) mod type_expr;
pub(crate) mod uniplate;
pub(crate) mod validate;

#[allow(clippy::all)]
// Things we explicitly turn on need to be explicitly turned off
#[allow(clippy::inefficient_to_string)]
#[allow(clippy::trivially_copy_pass_by_ref)]
#[allow(clippy::too_many_arguments)]
#[allow(clippy::cloned_instead_of_copied)]
#[allow(unused_extern_crates)]
#[allow(unused_braces)]
#[cfg_attr(feature = "gazebo_lint", allow(gazebo_lint_use_dupe))]
mod grammar {
    include!(concat!(env!("OUT_DIR"), "/syntax/grammar.rs"));
}


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

use std::fmt::Write;
use std::fs;
use std::path::Path;

use derivative::Derivative;
use dupe::Dupe;
use lalrpop_util as lu;

use crate::codemap::CodeMap;
use crate::codemap::FileSpan;
use crate::codemap::Pos;
use crate::codemap::Span;
use crate::errors::Diagnostic;
use crate::eval::compiler::EvalException;
use crate::syntax::ast::AstStmt;
use crate::syntax::ast::Stmt;
use crate::syntax::ast::StmtP;
use crate::syntax::grammar::StarlarkParser;
use crate::syntax::lexer::Lexer;
use crate::syntax::lexer::Token;
use crate::syntax::AstLoad;
use crate::syntax::Dialect;

fn one_of(expected: &[String]) -> String {
    let mut result = String::new();
    for (i, e) in expected.iter().enumerate() {
        let sep = match i {
            0 => "one of",
            _ if i < expected.len() - 1 => ",",
            // Last expected message to be written
            _ => " or",
        };
        write!(result, "{} {}", sep, e).unwrap();
    }
    result
}

/// Convert the error to a codemap diagnostic.
///
/// To build this diagnostic, the method needs the file span corresponding
/// to the parsed file.
fn parse_error_add_span(
    err: lu::ParseError<usize, Token, EvalException>,
    pos: usize,
    codemap: &CodeMap,
) -> anyhow::Error {
    let (message, span) = match err {
        lu::ParseError::InvalidToken { location } => (
            "Parse error: invalid token".to_owned(),
            Span::new(Pos::new(location as u32), Pos::new(location as u32)),
        ),
        lu::ParseError::UnrecognizedToken {
            token: (x, t, y),
            expected,
        } => (
            format!(
                "Parse error: unexpected {} here, expected {}",
                t,
                one_of(&expected)
            ),
            Span::new(Pos::new(x as u32), Pos::new(y as u32)),
        ),
        lu::ParseError::UnrecognizedEOF { .. } => (
            "Parse error: unexpected end of file".to_owned(),
            Span::new(Pos::new(pos as u32), Pos::new(pos as u32)),
        ),
        lu::ParseError::ExtraToken { token: (x, t, y) } => (
            format!("Parse error: extraneous token {}", t),
            Span::new(Pos::new(x as u32), Pos::new(y as u32)),
        ),
        lu::ParseError::User { error } => return error.into_anyhow(),
    };

    Diagnostic::new(anyhow::anyhow!(message), span, codemap)
}

/// A representation of a Starlark module abstract syntax tree.
///
/// Created with either [`parse`](AstModule::parse) or [`parse_file`](AstModule::parse_file),
/// and evaluated with [`eval_module`](crate::eval::Evaluator::eval_module).
///
/// The internal details (statements/expressions) are deliberately omitted, as they change
/// more regularly. A few methods to obtain information about the AST are provided.
#[derive(Derivative)]
#[derivative(Debug)]
pub struct AstModule {
    #[derivative(Debug = "ignore")]
    pub(crate) codemap: CodeMap,
    pub(crate) statement: AstStmt,
    pub(crate) dialect: Dialect,
}

impl AstModule {
    /// List the top-level statements in the AST.
    pub(crate) fn top_level_statements(&self) -> Vec<&AstStmt> {
        fn f<'a>(ast: &'a AstStmt, res: &mut Vec<&'a AstStmt>) {
            match &**ast {
                StmtP::Statements(xs) => {
                    for x in xs {
                        f(x, res);
                    }
                }
                _ => res.push(ast),
            }
        }

        let mut res = Vec::new();
        f(&self.statement, &mut res);
        res
    }

    fn create(
        codemap: CodeMap,
        statement: AstStmt,
        dialect: &Dialect,
    ) -> anyhow::Result<AstModule> {
        Stmt::validate(&codemap, &statement, dialect).map_err(EvalException::into_anyhow)?;
        Ok(AstModule {
            codemap,
            statement,
            dialect: dialect.clone(),
        })
    }

    /// Parse a file stored on disk. For details see [`parse`](AstModule::parse).
    pub fn parse_file(path: &Path, dialect: &Dialect) -> anyhow::Result<Self> {
        let content = fs::read_to_string(path)?;
        Self::parse(&path.to_string_lossy(), content, dialect)
    }

    /// Parse a Starlark module to produce an [`AstModule`], or an error if there are syntax errors.
    /// The `filename` is for error messages only, and does not have to be a valid file.
    /// The [`Dialect`] selects which Starlark constructs are valid.
    ///
    /// Errors will be reported using the [`Diagnostic`] type. For example:
    ///
    /// ```
    /// use starlark::syntax::{AstModule, Dialect};
    /// use starlark::errors::Diagnostic;
    ///
    /// let err: anyhow::Error = AstModule::parse("filename", "\n(unmatched".to_owned(), &Dialect::Standard).unwrap_err();
    /// let err: Diagnostic = err.downcast::<Diagnostic>().unwrap();
    /// assert_eq!(err.span.unwrap().to_string(), "filename:2:11");
    /// ```
    pub fn parse(filename: &str, content: String, dialect: &Dialect) -> anyhow::Result<Self> {
        let codemap = CodeMap::new(filename.to_owned(), content);
        let lexer = Lexer::new(codemap.source(), dialect, codemap.dupe());
        match StarlarkParser::new().parse(&codemap, dialect, lexer) {
            Ok(v) => Ok(AstModule::create(codemap, v, dialect)?),
            Err(p) => Err(parse_error_add_span(p, codemap.source().len(), &codemap)),
        }
    }

    /// Return the file names of all the `load` statements in the module.
    /// If the [`Dialect`] had [`enable_load`](Dialect::enable_load) set to [`false`] this will be an empty list.
    pub fn loads(&self) -> Vec<AstLoad> {
        // We know that `load` statements must be at the top-level, so no need to descend inside `if`, `for`, `def` etc.
        // There is a suggestion that `load` statements should be at the top of a file, but we tolerate that not being true.
        fn f<'a>(ast: &'a AstStmt, codemap: &CodeMap, vec: &mut Vec<AstLoad<'a>>) {
            match &ast.node {
                Stmt::Load(load) => vec.push(AstLoad {
                    span: FileSpan {
                        file: codemap.dupe(),
                        span: load.module.span,
                    },
                    module_id: &load.module.node,
                    symbols: load
                        .args
                        .iter()
                        .map(|(name, sym)| (name.node.0.as_str(), sym.node.as_str()))
                        .collect(),
                }),
                Stmt::Statements(stmts) => {
                    for s in stmts {
                        f(s, codemap, vec);
                    }
                }
                _ => {}
            }
        }

        let mut loads = Vec::new();
        f(&self.statement, &self.codemap, &mut loads);
        loads
    }

    /// Look up a [`Span`] contained in this module to a [`FileSpan`].
    pub(crate) fn file_span(&self, x: Span) -> FileSpan {
        self.codemap.file_span(x)
    }

    /// Locations where statements occur.
    pub fn stmt_locations(&self) -> Vec<FileSpan> {
        fn go(x: &AstStmt, codemap: &CodeMap, res: &mut Vec<FileSpan>) {
            match &**x {
                Stmt::Statements(_) => {} // These are not interesting statements that come up
                _ => res.push(FileSpan {
                    span: x.span,
                    file: codemap.dupe(),
                }),
            }
            x.visit_stmt(|x| go(x, codemap, res))
        }

        let mut res = Vec::new();
        go(&self.statement, &self.codemap, &mut res);
        res
    }
}

#[cfg(test)]
mod tests {
    use crate::assert;
    use crate::slice_vec_ext::SliceExt;

    #[test]
    fn test_locations() {
        fn get(code: &str) -> String {
            assert::parse_ast(code)
                .stmt_locations()
                .map(|x| x.resolve_span().to_string())
                .join(" ")
        }

        assert_eq!(&get("foo"), "1:1-4");
        assert_eq!(&get("foo\ndef x():\n   pass"), "1:1-4 2:1-3:8 3:4-8");
    }
}


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

use crate::codemap::FileSpan;
use crate::collections::SmallMap;

/// A `load` statement loading zero or more symbols from another module.
#[derive(Debug)]
pub struct AstLoad<'a> {
    /// Span where this load is written
    pub span: FileSpan,
    /// Module being loaded
    pub module_id: &'a str,
    /// Symbols loaded from that module (local ident -> source ident)
    pub symbols: SmallMap<&'a str, &'a str>,
}


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

//! Map AST payload.

use crate::codemap::Spanned;
use crate::slice_vec_ext::VecExt;
use crate::syntax::ast::ArgumentP;
use crate::syntax::ast::AssignIdentP;
use crate::syntax::ast::AssignP;
use crate::syntax::ast::AstPayload;
use crate::syntax::ast::ClauseP;
use crate::syntax::ast::DefP;
use crate::syntax::ast::ExprP;
use crate::syntax::ast::ForClauseP;
use crate::syntax::ast::IdentP;
use crate::syntax::ast::LambdaP;
use crate::syntax::ast::LoadP;
use crate::syntax::ast::ParameterP;
use crate::syntax::ast::StmtP;
use crate::syntax::ast::TypeExprP;

pub(crate) trait AstPayloadFunction<A: AstPayload, B: AstPayload> {
    fn map_load(&mut self, import_path: &str, a: A::LoadPayload) -> B::LoadPayload;
    fn map_ident(&mut self, a: A::IdentPayload) -> B::IdentPayload;
    fn map_ident_assign(&mut self, a: A::IdentAssignPayload) -> B::IdentAssignPayload;
    fn map_def(&mut self, a: A::DefPayload) -> B::DefPayload;
    fn map_type_expr(&mut self, a: A::TypeExprPayload) -> B::TypeExprPayload;
}

impl<A: AstPayload> LoadP<A> {
    pub(crate) fn into_map_payload<B: AstPayload>(
        self,
        f: &mut impl AstPayloadFunction<A, B>,
    ) -> LoadP<B> {
        let LoadP {
            module,
            args,
            payload,
        } = self;
        let payload = f.map_load(&module.node, payload);
        LoadP {
            module,
            args: args.into_map(|(local, their)| (local.into_map_payload(f), their)),
            payload,
        }
    }
}

impl<A: AstPayload> StmtP<A> {
    pub(crate) fn into_map_payload<B: AstPayload>(
        self,
        f: &mut impl AstPayloadFunction<A, B>,
    ) -> StmtP<B> {
        match self {
            StmtP::Break => StmtP::Break,
            StmtP::Continue => StmtP::Continue,
            StmtP::Pass => StmtP::Pass,
            StmtP::Return(None) => StmtP::Return(None),
            StmtP::Return(Some(e)) => StmtP::Return(Some(e.into_map_payload(f))),
            StmtP::Expression(e) => StmtP::Expression(e.into_map_payload(f)),
            StmtP::Assign(lhs, ty_rhs) => {
                let (ty, rhs) = *ty_rhs;
                StmtP::Assign(
                    lhs.into_map_payload(f),
                    Box::new((ty.map(|ty| ty.into_map_payload(f)), rhs.into_map_payload(f))),
                )
            }
            StmtP::AssignModify(lhs, op, rhs) => StmtP::AssignModify(
                lhs.into_map_payload(f),
                op,
                Box::new(rhs.into_map_payload(f)),
            ),
            StmtP::Statements(stmts) => {
                StmtP::Statements(stmts.into_map(|s| s.into_map_payload(f)))
            }
            StmtP::If(cond, then_block) => StmtP::If(
                cond.into_map_payload(f),
                Box::new(then_block.into_map_payload(f)),
            ),
            StmtP::IfElse(cond, then_block_else_block) => {
                let (then_block, else_block) = *then_block_else_block;
                StmtP::IfElse(
                    cond.into_map_payload(f),
                    Box::new((
                        then_block.into_map_payload(f),
                        else_block.into_map_payload(f),
                    )),
                )
            }
            StmtP::For(assign, coll_body) => {
                let (coll, body) = *coll_body;
                StmtP::For(
                    assign.into_map_payload(f),
                    Box::new((coll.into_map_payload(f), body.into_map_payload(f))),
                )
            }
            StmtP::Def(DefP {
                name,
                params,
                return_type,
                body,
                payload,
            }) => StmtP::Def(DefP {
                name: name.into_map_payload(f),
                params: params.into_map(|p| p.into_map_payload(f)),
                return_type: return_type.map(|ret| Box::new(ret.into_map_payload(f))),
                body: Box::new(body.into_map_payload(f)),
                payload: f.map_def(payload),
            }),
            StmtP::Load(load) => StmtP::Load(load.into_map_payload(f)),
        }
    }
}

impl<A: AstPayload> ExprP<A> {
    pub(crate) fn into_map_payload<B: AstPayload>(
        self,
        f: &mut impl AstPayloadFunction<A, B>,
    ) -> ExprP<B> {
        match self {
            ExprP::Tuple(exprs) => ExprP::Tuple(exprs.into_map(|e| e.into_map_payload(f))),
            ExprP::Dot(object, field) => ExprP::Dot(Box::new(object.into_map_payload(f)), field),
            ExprP::Call(ca, args) => ExprP::Call(
                Box::new(ca.into_map_payload(f)),
                args.into_map(|a| a.into_map_payload(f)),
            ),
            ExprP::Index(array_index) => {
                let (array, index) = *array_index;
                ExprP::Index(Box::new((
                    array.into_map_payload(f),
                    index.into_map_payload(f),
                )))
            }
            ExprP::Index2(a_i0_i1) => {
                let (array, i0, i1) = *a_i0_i1;
                ExprP::Index2(Box::new((
                    array.into_map_payload(f),
                    i0.into_map_payload(f),
                    i1.into_map_payload(f),
                )))
            }
            ExprP::Slice(x, a, b, c) => ExprP::Slice(
                Box::new(x.into_map_payload(f)),
                a.map(|e| Box::new(e.into_map_payload(f))),
                b.map(|e| Box::new(e.into_map_payload(f))),
                c.map(|e| Box::new(e.into_map_payload(f))),
            ),
            ExprP::Identifier(id) => ExprP::Identifier(id.into_map_payload(f)),
            ExprP::Lambda(LambdaP {
                params,
                body,
                payload,
            }) => ExprP::Lambda(LambdaP {
                params: params.into_map(|p| p.into_map_payload(f)),
                body: Box::new(body.into_map_payload(f)),
                payload: f.map_def(payload),
            }),
            ExprP::Literal(l) => ExprP::Literal(l),
            ExprP::Not(e) => ExprP::Not(Box::new(e.into_map_payload(f))),
            ExprP::Minus(e) => ExprP::Minus(Box::new(e.into_map_payload(f))),
            ExprP::Plus(e) => ExprP::Plus(Box::new(e.into_map_payload(f))),
            ExprP::BitNot(e) => ExprP::BitNot(Box::new(e.into_map_payload(f))),
            ExprP::Op(l, op, r) => ExprP::Op(
                Box::new(l.into_map_payload(f)),
                op,
                Box::new(r.into_map_payload(f)),
            ),
            ExprP::If(a_b_c) => {
                let (a, b, c) = *a_b_c;
                ExprP::If(Box::new((
                    a.into_map_payload(f),
                    b.into_map_payload(f),
                    c.into_map_payload(f),
                )))
            }
            ExprP::List(es) => ExprP::List(es.into_map(|e| e.into_map_payload(f))),
            ExprP::Dict(kvs) => {
                ExprP::Dict(kvs.into_map(|(k, v)| (k.into_map_payload(f), v.into_map_payload(f))))
            }
            ExprP::ListComprehension(e, c0, cs) => ExprP::ListComprehension(
                Box::new(e.into_map_payload(f)),
                Box::new(c0.into_map_payload(f)),
                cs.into_map(|c| c.into_map_payload(f)),
            ),
            ExprP::DictComprehension(k_v, c0, cs) => {
                let (k, v) = *k_v;
                ExprP::DictComprehension(
                    Box::new((k.into_map_payload(f), v.into_map_payload(f))),
                    Box::new(c0.into_map_payload(f)),
                    cs.into_map(|c| c.into_map_payload(f)),
                )
            }
        }
    }
}

impl<A: AstPayload> TypeExprP<A> {
    pub(crate) fn into_map_payload<B: AstPayload>(
        self,
        f: &mut impl AstPayloadFunction<A, B>,
    ) -> TypeExprP<B> {
        let TypeExprP { expr, payload } = self;
        TypeExprP {
            expr: expr.into_map(|e| e.into_map_payload(f)),
            payload: f.map_type_expr(payload),
        }
    }
}

impl<A: AstPayload> AssignP<A> {
    pub(crate) fn into_map_payload<B: AstPayload>(
        self,
        f: &mut impl AstPayloadFunction<A, B>,
    ) -> AssignP<B> {
        match self {
            AssignP::Tuple(args) => AssignP::Tuple(args.into_map(|a| a.into_map_payload(f))),
            AssignP::Index(array_index) => {
                let (array, index) = *array_index;
                AssignP::Index(Box::new((
                    array.into_map_payload(f),
                    index.into_map_payload(f),
                )))
            }
            AssignP::Dot(object, field) => {
                AssignP::Dot(Box::new(object.into_map_payload(f)), field)
            }
            AssignP::Identifier(ident) => AssignP::Identifier(ident.into_map_payload(f)),
        }
    }
}

impl<A: AstPayload> AssignIdentP<A> {
    pub(crate) fn into_map_payload<B: AstPayload>(
        self,
        f: &mut impl AstPayloadFunction<A, B>,
    ) -> AssignIdentP<B> {
        let AssignIdentP(s, p) = self;
        AssignIdentP(s, f.map_ident_assign(p))
    }
}

impl<A: AstPayload> IdentP<A> {
    pub(crate) fn into_map_payload<B: AstPayload>(
        self,
        f: &mut impl AstPayloadFunction<A, B>,
    ) -> IdentP<B> {
        let IdentP(s, p) = self;
        IdentP(s, f.map_ident(p))
    }
}

impl<A: AstPayload> ParameterP<A> {
    pub(crate) fn into_map_payload<B: AstPayload>(
        self,
        f: &mut impl AstPayloadFunction<A, B>,
    ) -> ParameterP<B> {
        match self {
            ParameterP::Normal(name, ty) => ParameterP::Normal(
                name.into_map_payload(f),
                ty.map(|defa| Box::new(defa.into_map_payload(f))),
            ),
            ParameterP::WithDefaultValue(name, ty, defa) => ParameterP::WithDefaultValue(
                name.into_map_payload(f),
                ty.map(|defa| Box::new(defa.into_map_payload(f))),
                Box::new(defa.into_map_payload(f)),
            ),
            ParameterP::NoArgs => ParameterP::NoArgs,
            ParameterP::Args(name, ty) => ParameterP::Args(
                name.into_map_payload(f),
                ty.map(|defa| Box::new(defa.into_map_payload(f))),
            ),
            ParameterP::KwArgs(name, ty) => ParameterP::KwArgs(
                name.into_map_payload(f),
                ty.map(|defa| Box::new(defa.into_map_payload(f))),
            ),
        }
    }
}

impl<A: AstPayload> ArgumentP<A> {
    pub(crate) fn into_map_payload<B: AstPayload>(
        self,
        f: &mut impl AstPayloadFunction<A, B>,
    ) -> ArgumentP<B> {
        match self {
            ArgumentP::Positional(e) => ArgumentP::Positional(e.into_map_payload(f)),
            ArgumentP::Named(n, e) => ArgumentP::Named(n, e.into_map_payload(f)),
            ArgumentP::Args(e) => ArgumentP::Args(e.into_map_payload(f)),
            ArgumentP::KwArgs(e) => ArgumentP::KwArgs(e.into_map_payload(f)),
        }
    }
}

impl<A: AstPayload> ClauseP<A> {
    pub(crate) fn into_map_payload<B: AstPayload>(
        self,
        f: &mut impl AstPayloadFunction<A, B>,
    ) -> ClauseP<B> {
        match self {
            ClauseP::For(c) => ClauseP::For(c.into_map_payload(f)),
            ClauseP::If(e) => ClauseP::If(e.into_map_payload(f)),
        }
    }
}

impl<A: AstPayload> ForClauseP<A> {
    pub(crate) fn into_map_payload<B: AstPayload>(
        self,
        f: &mut impl AstPayloadFunction<A, B>,
    ) -> ForClauseP<B> {
        ForClauseP {
            var: self.var.into_map_payload(f),
            over: self.over.into_map_payload(f),
        }
    }
}

macro_rules! ast_payload_map_stub {
    ($ast_type:ident) => {
        impl<A: AstPayload> Spanned<$ast_type<A>> {
            pub(crate) fn into_map_payload<B: AstPayload>(
                self,
                f: &mut impl AstPayloadFunction<A, B>,
            ) -> Spanned<$ast_type<B>> {
                let Spanned { span, node } = self;
                Spanned {
                    span,
                    node: node.into_map_payload(f),
                }
            }
        }
    };
}

ast_payload_map_stub!(ExprP);
ast_payload_map_stub!(TypeExprP);
ast_payload_map_stub!(AssignP);
ast_payload_map_stub!(AssignIdentP);
ast_payload_map_stub!(IdentP);
ast_payload_map_stub!(ParameterP);
ast_payload_map_stub!(ArgumentP);
ast_payload_map_stub!(StmtP);


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

use crate::assert;

macro_rules! testcases_parse {
    ($($x:expr)*) => {
        &[
            $(
                (
                    $x,
                    include_str!(concat!(
                        env!("CARGO_MANIFEST_DIR"),
                        "/testcases/parse/",
                        $x,
                    ))
                )
            ),*
         ]
    }
}

const TESTCASE_FILES: &[(&str, &str)] = testcases_parse!(
    // A list of all files from testcases/parse, minus README.md
    // If you add additional parse tests, make sure to update this list.
    // If Rust adds list_directory! as a macro, remove this list.
    "action.star"
    "alias_rules.star"
    "android_sdk_repository_template.star"
    "archive.star"
    "asciidoc.star"
    "asm.star"
    "aspect.star"
    "bazel_hash_dict.star"
    "bazel_integration_test.star"
    "bazel_java_integration_test.star"
    "bazel_py_integration_test.star"
    "bazel_tests.star"
    "binary.star"
    "bindata.star"
    "bower_archives.star"
    "bower_components.star"
    "build.star"
    "build_defs.star"
    "bundle.star"
    "bzl.star"
    "cc.star"
    "cc_configure.star"
    "cgo.star"
    "classpath.star"
    "cm.star"
    "common.star"
    "compile.star"
    "compiler.star"
    "config.star"
    "container.star"
    "cover.star"
    "csharp.star"
    "def.star"
    "dicts.star"
    "docker.star"
    "docker_base.star"
    "docker_pull.star"
    "docker_repository.star"
    "dockerfile_build.star"
    "dummy_toolchain.star"
    "e4b_aspect.star"
    "eclipse.star"
    "eclipse_platform.star"
    "embed_data.star"
    "embedded_tools.star"
    "empty.star"
    "executable.star"
    "extension.star"
    "external_plugin_deps.star"
    "files_equal_test.star"
    "filetype.star"
    "flatten.star"
    "flavours.star"
    "foo.star"
    "gazelle.star"
    "generate_test.star"
    "generate_workspace.star"
    "genproto.star"
    "genrule2.star"
    "git.star"
    "git_repositories.star"
    "go.star"
    "go_proto_library.star"
    "go_repository.star"
    "go_toolchain.star"
    "guava.star"
    "gwt.star"
    "hash.star"
    "hello.star"
    "http.star"
    "image.star"
    "import.star"
    "info.star"
    "intellij_plugin.star"
    "intellij_plugin_debug_target.star"
    "java.star"
    "java_rules_skylark.star"
    "javadoc.star"
    "jekyll.star"
    "jenkins.star"
    "jenkins_base.star"
    "jenkins_docker_build.star"
    "jenkins_job.star"
    "jenkins_node.star"
    "jenkins_nodes.star"
    "jetty.star"
    "jgit.star"
    "jobs.star"
    "js.star"
    "junit.star"
    "label.star"
    "layers.star"
    "lib_cc_configure.star"
    "library.star"
    "license.star"
    "line_length.star"
    "lines_sorted_test.star"
    "link.star"
    "list.star"
    "list_source_repository.star"
    "load.star"
    "maven.star"
    "maven_jar.star"
    "maven_rules.star"
    "mode.star"
    "oci.star"
    "osx_archs.star"
    "osx_cc_configure.star"
    "pack.star"
    "package.star"
    "passwd.star"
    "path.star"
    "paths.star"
    "pkg.star"
    "pkg_war.star"
    "plugin.star"
    "plugins.star"
    "popular_repos.star"
    "prefix.star"
    "printer.star"
    "prolog.star"
    "proto_alias.star"
    "protobuf.star"
    "providers.star"
    "pull.star"
    "push-all.star"
    "push.star"
    "python.star"
    "redirects.star"
    "remote.star"
    "repositories.star"
    "repository_tools.star"
    "rpm.star"
    "rust.star"
    "self_extract_binary.star"
    "serialize.star"
    "sets.star"
    "shared.star"
    "shell.star"
    "single_output_test.star"
    "site.star"
    "source.star"
    "stdlib.star"
    "structs.star"
    "templates.star"
    "test.star"
    "test_defs.star"
    "test_rules.star"
    "tests.star"
    "toolchain.star"
    "toolchain_utils.star"
    "toolchains.star"
    "transitive_maven_jar.star"
    "unix_cc_configure.star"
    "utilities.star"
    "vars.star"
    "version.star"
    "vet.star"
    "win_rules.star"
    "windows_cc_configure.star"
    "with-defaults.star"
    "with-tag.star"
    "workspace.star"
    "wrappers.star"
    "xcode_configure.star"
    "xcode_version_flag.star"
    "zip.star"
);

#[test]
fn parsing_testcases() {
    for (_, content) in TESTCASE_FILES {
        // Worth doing the lex and parse as lex checks for additional invariants
        assert::lex(content);
        assert::parse(content);
    }
}


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

use crate::codemap::CodeMap;
use crate::codemap::Spanned;
use crate::eval::compiler::EvalException;
use crate::slice_vec_ext::SliceExt;
use crate::syntax::ast::AstExprP;
use crate::syntax::ast::AstIdentP;
use crate::syntax::ast::AstLiteral;
use crate::syntax::ast::AstPayload;
use crate::syntax::ast::ExprP;

#[derive(Debug, thiserror::Error)]
enum TypeExprUnpackError {
    #[error("{0} expression is not allowed in type expression")]
    InvalidType(&'static str),
    #[error("Empty list is not allowed in type expression")]
    EmptyListInType,
    #[error("Only dict literal with single entry is allowed in type expression")]
    DictNot1InType,
    #[error("Only dot expression of form `ident.ident` is allowed in type expression")]
    DotInType,
}

/// This type should be used instead of `TypeExprP`, but a lot of code needs to be updated.
#[derive(Debug)]
pub(crate) enum TypeExprUnpackP<'a, P: AstPayload> {
    Path(&'a AstIdentP<P>, Vec<Spanned<&'a str>>),
    /// `list[str]`.
    Index(&'a AstIdentP<P>, Box<Spanned<TypeExprUnpackP<'a, P>>>),
    /// `dict[str, int]`.
    Index2(
        &'a AstIdentP<P>,
        Box<Spanned<TypeExprUnpackP<'a, P>>>,
        Box<Spanned<TypeExprUnpackP<'a, P>>>,
    ),
    Any(Vec<Spanned<TypeExprUnpackP<'a, P>>>),
    ListOf(Box<Spanned<TypeExprUnpackP<'a, P>>>),
    DictOf(
        Box<Spanned<TypeExprUnpackP<'a, P>>>,
        Box<Spanned<TypeExprUnpackP<'a, P>>>,
    ),
    Tuple(Vec<Spanned<TypeExprUnpackP<'a, P>>>),
    Literal(Spanned<&'a str>),
}

impl<'a, P: AstPayload> TypeExprUnpackP<'a, P> {
    pub(crate) fn unpack(
        expr: &'a AstExprP<P>,
        codemap: &CodeMap,
    ) -> Result<Spanned<TypeExprUnpackP<'a, P>>, EvalException> {
        let span = expr.span;
        let err = |t| {
            Err(EvalException::new(
                TypeExprUnpackError::InvalidType(t).into(),
                expr.span,
                codemap,
            ))
        };

        match &expr.node {
            ExprP::Tuple(xs) => {
                let xs = xs.try_map(|x| TypeExprUnpackP::unpack(x, codemap))?;
                Ok(Spanned {
                    span,
                    node: TypeExprUnpackP::Tuple(xs),
                })
            }
            ExprP::Dot(object, field) => {
                let mut current: &AstExprP<P> = object;
                let mut rem: Vec<Spanned<_>> = vec![field.as_ref().into_map(|x| x.as_str())];
                loop {
                    match &current.node {
                        ExprP::Dot(o, f) => {
                            current = o;
                            rem.push(f.as_ref().into_map(|x| x.as_str()));
                        }
                        ExprP::Identifier(i) => {
                            rem.reverse();
                            return Ok(Spanned {
                                span,
                                node: TypeExprUnpackP::Path(i, rem),
                            });
                        }
                        _ => {
                            return Err(EvalException::new(
                                TypeExprUnpackError::DotInType.into(),
                                current.span,
                                codemap,
                            ));
                        }
                    }
                }
                // We would also want to ban expressions like `x.y` where `x` is not `type`,
                // or `x.y.z` but these are used now.
                // Try `xbgs metalos.ProvisioningConfig`.
                // That expression has type string which is the type name.
            }
            ExprP::Call(..) => err("call"),
            ExprP::Index(a_i) => {
                let (a, i) = &**a_i;
                match &a.node {
                    ExprP::Identifier(ident) => {
                        let i = TypeExprUnpackP::unpack(i, codemap)?;
                        Ok(Spanned {
                            span,
                            node: TypeExprUnpackP::Index(ident, Box::new(i)),
                        })
                    }
                    _ => err("array indirection where array is not an identifier"),
                }
            }
            ExprP::Index2(a_i0_i1) => {
                let (a, i0, i1) = &**a_i0_i1;
                match &a.node {
                    ExprP::Identifier(ident) => {
                        let i0 = TypeExprUnpackP::unpack(i0, codemap)?;
                        let i1 = TypeExprUnpackP::unpack(i1, codemap)?;
                        Ok(Spanned {
                            span,
                            node: TypeExprUnpackP::Index2(ident, Box::new(i0), Box::new(i1)),
                        })
                    }
                    _ => err("array indirection 2 where array is not an identifier"),
                }
            }
            ExprP::Slice(..) => err("slice"),
            ExprP::Identifier(ident) => Ok(Spanned {
                span,
                node: TypeExprUnpackP::Path(ident, Vec::new()),
            }),
            ExprP::Lambda(..) => err("lambda"),
            ExprP::Literal(AstLiteral::String(s)) => Ok(Spanned {
                span,
                node: TypeExprUnpackP::Literal(s.as_ref().into_map(|x| x.as_str())),
            }),
            ExprP::Literal(AstLiteral::Int(_)) => err("int"),
            ExprP::Literal(AstLiteral::Float(_)) => err("float"),
            ExprP::Not(..) => err("not"),
            ExprP::Minus(..) => err("minus"),
            ExprP::Plus(..) => err("plus"),
            ExprP::BitNot(..) => err("bit not"),
            ExprP::Op(..) => err("bin op"),
            ExprP::If(..) => err("if"),
            ExprP::List(xs) => {
                if xs.is_empty() {
                    Err(EvalException::new(
                        TypeExprUnpackError::EmptyListInType.into(),
                        expr.span,
                        codemap,
                    ))
                } else if xs.len() == 1 {
                    Ok(Spanned {
                        span,
                        node: TypeExprUnpackP::ListOf(Box::new(TypeExprUnpackP::unpack(
                            &xs[0], codemap,
                        )?)),
                    })
                } else {
                    let xs = xs.try_map(|x| TypeExprUnpackP::unpack(x, codemap))?;
                    Ok(Spanned {
                        span,
                        node: TypeExprUnpackP::Any(xs),
                    })
                }
            }
            ExprP::Dict(xs) => {
                if xs.len() != 1 {
                    Err(EvalException::new(
                        TypeExprUnpackError::DictNot1InType.into(),
                        expr.span,
                        codemap,
                    ))
                } else {
                    let (k, v) = &xs[0];
                    Ok(Spanned {
                        span,
                        node: TypeExprUnpackP::DictOf(
                            Box::new(TypeExprUnpackP::unpack(k, codemap)?),
                            Box::new(TypeExprUnpackP::unpack(v, codemap)?),
                        ),
                    })
                }
            }
            ExprP::ListComprehension(..) => err("list comprehension"),
            ExprP::DictComprehension(..) => err("dict comprehension"),
        }
    }
}


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

// These are more readable for formulaic code like Uniplate
#![allow(clippy::many_single_char_names)]
// Sometimes we need |x| f(x) to do type/lifetime conversion, sometimes we don't.
// Most consistent to use the closure everywhere.
#![allow(clippy::redundant_closure)]

use crate::syntax::ast::AssignP;
use crate::syntax::ast::AstAssignIdentP;
use crate::syntax::ast::AstExprP;
use crate::syntax::ast::AstPayload;
use crate::syntax::ast::AstStmtP;
use crate::syntax::ast::AstTypeExprP;
use crate::syntax::ast::ClauseP;
use crate::syntax::ast::DefP;
use crate::syntax::ast::ExprP;
use crate::syntax::ast::ForClauseP;
use crate::syntax::ast::LambdaP;
use crate::syntax::ast::ParameterP;
use crate::syntax::ast::StmtP;
use crate::syntax::ast::TypeExprP;

pub(crate) enum Visit<'a, P: AstPayload> {
    Stmt(&'a AstStmtP<P>),
    Expr(&'a AstExprP<P>),
}

pub(crate) enum VisitMut<'a, P: AstPayload> {
    Stmt(&'a mut AstStmtP<P>),
    Expr(&'a mut AstExprP<P>),
}

impl<'a, P: AstPayload> Visit<'a, P> {
    pub(crate) fn visit_children(&self, mut f: impl FnMut(Visit<'a, P>)) {
        match self {
            Self::Stmt(x) => x.visit_children(f),
            Self::Expr(x) => x.visit_expr(|x| f(Visit::Expr(x))),
        }
    }

    pub(crate) fn visit_children_err<E>(
        &self,
        mut f: impl FnMut(Visit<'a, P>) -> Result<(), E>,
    ) -> Result<(), E> {
        match self {
            Self::Stmt(x) => x.visit_children_err(f),
            Self::Expr(x) => x.visit_expr_err(|x| f(Visit::Expr(x))),
        }
    }
}

impl<P: AstPayload> StmtP<P> {
    pub(crate) fn visit_children<'a>(&'a self, mut f: impl FnMut(Visit<'a, P>)) {
        match self {
            StmtP::Statements(xs) => xs.iter().for_each(|x| f(Visit::Stmt(x))),
            StmtP::If(condition, then_block) => {
                f(Visit::Expr(condition));
                f(Visit::Stmt(then_block));
            }
            StmtP::IfElse(condition, then_block_else_block) => {
                let (then_block, else_block) = &**then_block_else_block;
                f(Visit::Expr(condition));
                f(Visit::Stmt(then_block));
                f(Visit::Stmt(else_block));
            }
            StmtP::Def(DefP {
                name: _,
                params,
                return_type,
                body,
                payload: _,
            }) => {
                params
                    .iter()
                    .for_each(|x| x.visit_expr(|x| f(Visit::Expr(x))));
                return_type
                    .iter()
                    .for_each(|x| x.visit_expr(|x| f(Visit::Expr(x))));
                f(Visit::Stmt(body));
            }
            StmtP::For(lhs, over_body) => {
                let (over, body) = &**over_body;
                lhs.visit_expr(|x| f(Visit::Expr(x)));
                f(Visit::Expr(over));
                f(Visit::Stmt(body));
            }
            // Nothing else contains nested statements
            StmtP::Break => {}
            StmtP::Continue => {}
            StmtP::Pass => {}
            StmtP::Return(ret) => {
                ret.iter().for_each(|x| f(Visit::Expr(x)));
            }
            StmtP::Expression(e) => f(Visit::Expr(e)),
            StmtP::Assign(lhs, ty_rhs) => {
                let (ty, rhs) = &**ty_rhs;
                lhs.visit_expr(|x| f(Visit::Expr(x)));
                ty.iter().for_each(|x| x.visit_expr(|x| f(Visit::Expr(x))));
                f(Visit::Expr(rhs));
            }
            StmtP::AssignModify(lhs, _, rhs) => {
                lhs.visit_expr(|x| f(Visit::Expr(x)));
                f(Visit::Expr(rhs));
            }
            StmtP::Load(..) => {}
        }
    }

    pub(crate) fn visit_children_mut<'a>(&'a mut self, mut f: impl FnMut(VisitMut<'a, P>)) {
        match self {
            StmtP::Statements(xs) => xs.iter_mut().for_each(|x| f(VisitMut::Stmt(x))),
            StmtP::If(condition, then_block) => {
                f(VisitMut::Expr(condition));
                f(VisitMut::Stmt(then_block));
            }
            StmtP::IfElse(condition, then_block_else_block) => {
                let (then_block, else_block) = &mut **then_block_else_block;
                f(VisitMut::Expr(condition));
                f(VisitMut::Stmt(then_block));
                f(VisitMut::Stmt(else_block));
            }
            StmtP::Def(DefP {
                name: _,
                params,
                return_type,
                body,
                payload: _,
            }) => {
                params
                    .iter_mut()
                    .for_each(|x| x.visit_expr_mut(|x| f(VisitMut::Expr(x))));
                return_type
                    .iter_mut()
                    .for_each(|x| x.visit_expr_mut(|x| f(VisitMut::Expr(x))));
                f(VisitMut::Stmt(body));
            }
            StmtP::For(lhs, over_body) => {
                let (over, body) = &mut **over_body;
                lhs.visit_expr_mut(|x| f(VisitMut::Expr(x)));
                f(VisitMut::Expr(over));
                f(VisitMut::Stmt(body));
            }
            // Nothing else contains nested statements
            StmtP::Break => {}
            StmtP::Continue => {}
            StmtP::Pass => {}
            StmtP::Return(ret) => {
                ret.iter_mut().for_each(|x| f(VisitMut::Expr(x)));
            }
            StmtP::Expression(e) => f(VisitMut::Expr(e)),
            StmtP::Assign(lhs, ty_rhs) => {
                let (ty, rhs) = &mut **ty_rhs;
                lhs.visit_expr_mut(|x| f(VisitMut::Expr(x)));
                ty.iter_mut()
                    .for_each(|x| x.visit_expr_mut(|x| f(VisitMut::Expr(x))));
                f(VisitMut::Expr(rhs));
            }
            StmtP::AssignModify(lhs, _, rhs) => {
                lhs.visit_expr_mut(|x| f(VisitMut::Expr(x)));
                f(VisitMut::Expr(rhs));
            }
            StmtP::Load(..) => {}
        }
    }

    pub(crate) fn visit_children_err<'a, E>(
        &'a self,
        mut f: impl FnMut(Visit<'a, P>) -> Result<(), E>,
    ) -> Result<(), E> {
        let mut result = Ok(());
        self.visit_children(|x| {
            if result.is_ok() {
                result = f(x);
            }
        });
        result
    }

    pub(crate) fn visit_children_err_mut<'a, E>(
        &'a mut self,
        mut f: impl FnMut(VisitMut<'a, P>) -> Result<(), E>,
    ) -> Result<(), E> {
        let mut result = Ok(());
        self.visit_children_mut(|x| {
            if result.is_ok() {
                result = f(x);
            }
        });
        result
    }

    pub(crate) fn visit_stmt<'a>(&'a self, mut f: impl FnMut(&'a AstStmtP<P>)) {
        self.visit_children(|x| match x {
            Visit::Stmt(x) => f(x),
            Visit::Expr(_) => {} // Nothing to do
        })
    }

    pub(crate) fn visit_stmt_mut<'a>(&'a mut self, mut f: impl FnMut(&'a mut AstStmtP<P>)) {
        self.visit_children_mut(|x| match x {
            VisitMut::Stmt(x) => f(x),
            VisitMut::Expr(_) => {} // Nothing to do
        })
    }

    pub(crate) fn visit_expr<'a>(&'a self, mut f: impl FnMut(&'a AstExprP<P>)) {
        // Note the &mut impl on f, it's subtle, see
        // https://stackoverflow.com/questions/54613966/error-reached-the-recursion-limit-while-instantiating-funcclosure
        fn pick<'a, P: AstPayload>(x: Visit<'a, P>, f: &mut impl FnMut(&'a AstExprP<P>)) {
            match x {
                Visit::Stmt(x) => x.visit_children(|x| pick(x, f)),
                Visit::Expr(x) => f(x),
            }
        }
        self.visit_children(|x| pick(x, &mut f))
    }

    pub(crate) fn visit_stmt_result<E>(
        &self,
        mut f: impl FnMut(&AstStmtP<P>) -> Result<(), E>,
    ) -> Result<(), E> {
        let mut result = Ok(());
        let f2 = |x: &AstStmtP<P>| {
            if result.is_ok() {
                result = f(x);
            }
        };
        self.visit_stmt(f2);
        result
    }
}

impl<P: AstPayload> ParameterP<P> {
    // Split a parameter into name, type, default value
    pub(crate) fn split(
        &self,
    ) -> (
        Option<&AstAssignIdentP<P>>,
        Option<&AstTypeExprP<P>>,
        Option<&AstExprP<P>>,
    ) {
        match self {
            ParameterP::Normal(a, b) | ParameterP::Args(a, b) | ParameterP::KwArgs(a, b) => {
                (Some(a), b.as_ref().map(|x| &**x), None)
            }
            ParameterP::WithDefaultValue(a, b, c) => {
                (Some(a), b.as_ref().map(|x| &**x), Some(&**c))
            }
            ParameterP::NoArgs => (None, None, None),
        }
    }

    // Split a parameter into name, type, default value
    pub(crate) fn split_mut(
        &mut self,
    ) -> (
        Option<&mut AstAssignIdentP<P>>,
        Option<&mut AstTypeExprP<P>>,
        Option<&mut AstExprP<P>>,
    ) {
        match self {
            ParameterP::Normal(a, b) | ParameterP::Args(a, b) | ParameterP::KwArgs(a, b) => {
                (Some(a), b.as_mut().map(|x| &mut **x), None)
            }
            ParameterP::WithDefaultValue(a, b, c) => {
                (Some(a), b.as_mut().map(|x| &mut **x), Some(&mut **c))
            }
            ParameterP::NoArgs => (None, None, None),
        }
    }

    pub(crate) fn visit_expr<'a>(&'a self, mut f: impl FnMut(&'a AstExprP<P>)) {
        let (_, typ, def) = self.split();
        typ.iter().for_each(|x| x.visit_expr(&mut f));
        def.iter().for_each(|x| f(x));
    }

    pub(crate) fn visit_expr_mut<'a>(&'a mut self, mut f: impl FnMut(&'a mut AstExprP<P>)) {
        let (_, typ, def) = self.split_mut();
        if let Some(typ) = typ {
            typ.visit_expr_mut(&mut f);
        }
        if let Some(def) = def {
            f(def);
        }
    }
}

impl<P: AstPayload> ExprP<P> {
    pub(crate) fn visit_expr<'a>(&'a self, mut f: impl FnMut(&'a AstExprP<P>)) {
        match self {
            ExprP::Tuple(xs) => xs.iter().for_each(|x| f(x)),
            ExprP::Dot(x, _) => f(x),
            ExprP::Call(a, b) => {
                f(a);
                b.iter().for_each(|x| f(x.expr()));
            }
            ExprP::Index(a_b) => {
                let (a, b) = &**a_b;
                f(a);
                f(b);
            }
            ExprP::Index2(a_i0_i1) => {
                let (a, i0, i1) = &**a_i0_i1;
                f(a);
                f(i0);
                f(i1);
            }
            ExprP::Slice(a, b, c, d) => {
                f(a);
                b.iter().for_each(|x| f(x));
                c.iter().for_each(|x| f(x));
                d.iter().for_each(|x| f(x));
            }
            ExprP::Identifier(..) => {}
            ExprP::Lambda(LambdaP {
                params,
                body,
                payload: _,
            }) => {
                params.iter().for_each(|x| x.visit_expr(|x| f(x)));
                f(body);
            }
            ExprP::Literal(_) => {}
            ExprP::Not(x) => f(x),
            ExprP::Minus(x) => f(x),
            ExprP::Plus(x) => f(x),
            ExprP::BitNot(x) => f(x),
            ExprP::Op(x, _, y) => {
                f(x);
                f(y);
            }
            ExprP::If(a_b_c) => {
                let (a, b, c) = &**a_b_c;
                f(a);
                f(b);
                f(c);
            }
            ExprP::List(x) => x.iter().for_each(|x| f(x)),
            ExprP::Dict(x) => x.iter().for_each(|(x, y)| {
                f(x);
                f(y);
            }),
            ExprP::ListComprehension(x, for_, y) => {
                for_.visit_expr(|x| f(x));
                y.iter().for_each(|x| x.visit_expr(|x| f(x)));
                f(x);
            }
            ExprP::DictComprehension(x, for_, y) => {
                for_.visit_expr(|x| f(x));
                y.iter().for_each(|x| x.visit_expr(|x| f(x)));
                f(&x.0);
                f(&x.1);
            }
        }
    }

    pub(crate) fn visit_expr_err<'a, E>(
        &'a self,
        mut f: impl FnMut(&'a AstExprP<P>) -> Result<(), E>,
    ) -> Result<(), E> {
        let mut ok = Ok(());
        self.visit_expr(|x| {
            if ok.is_ok() {
                ok = f(x);
            }
        });
        ok
    }

    pub(crate) fn visit_expr_err_mut<'a, E>(
        &'a mut self,
        mut f: impl FnMut(&'a mut AstExprP<P>) -> Result<(), E>,
    ) -> Result<(), E> {
        let mut ok = Ok(());
        self.visit_expr_mut(|x| {
            if ok.is_ok() {
                ok = f(x);
            }
        });
        ok
    }

    pub(crate) fn visit_expr_mut<'a>(&'a mut self, mut f: impl FnMut(&'a mut AstExprP<P>)) {
        match self {
            ExprP::Tuple(xs) => xs.iter_mut().for_each(|x| f(x)),
            ExprP::Dot(x, _) => f(x),
            ExprP::Call(a, b) => {
                f(a);
                b.iter_mut().for_each(|x| f(x.expr_mut()));
            }
            ExprP::Index(a_b) => {
                let (a, b) = &mut **a_b;
                f(a);
                f(b);
            }
            ExprP::Index2(a_i0_i1) => {
                let (a, i0, i1) = &mut **a_i0_i1;
                f(a);
                f(i0);
                f(i1);
            }
            ExprP::Slice(a, b, c, d) => {
                f(a);
                b.iter_mut().for_each(|x| f(x));
                c.iter_mut().for_each(|x| f(x));
                d.iter_mut().for_each(|x| f(x));
            }
            ExprP::Identifier(..) => {}
            ExprP::Lambda(LambdaP {
                params,
                body,
                payload: _,
            }) => {
                params.iter_mut().for_each(|x| x.visit_expr_mut(|x| f(x)));
                f(body);
            }
            ExprP::Literal(_) => {}
            ExprP::Not(x) => f(x),
            ExprP::Minus(x) => f(x),
            ExprP::Plus(x) => f(x),
            ExprP::BitNot(x) => f(x),
            ExprP::Op(x, _, y) => {
                f(x);
                f(y);
            }
            ExprP::If(a_b_c) => {
                let (a, b, c) = &mut **a_b_c;
                f(a);
                f(b);
                f(c);
            }
            ExprP::List(x) => x.iter_mut().for_each(|x| f(x)),
            ExprP::Dict(x) => x.iter_mut().for_each(|(x, y)| {
                f(x);
                f(y);
            }),
            ExprP::ListComprehension(x, for_, y) => {
                for_.visit_expr_mut(|x| f(x));
                y.iter_mut().for_each(|x| x.visit_expr_mut(|x| f(x)));
                f(x);
            }
            ExprP::DictComprehension(x, for_, y) => {
                for_.visit_expr_mut(|x| f(x));
                y.iter_mut().for_each(|x| x.visit_expr_mut(|x| f(x)));
                f(&mut x.0);
                f(&mut x.1);
            }
        }
    }
}

impl<P: AstPayload> TypeExprP<P> {
    fn visit_expr<'a>(&'a self, mut f: impl FnMut(&'a AstExprP<P>)) {
        f(&self.expr)
    }

    fn visit_expr_mut<'a>(&'a mut self, mut f: impl FnMut(&'a mut AstExprP<P>)) {
        f(&mut self.expr)
    }
}

impl<P: AstPayload> AssignP<P> {
    pub(crate) fn visit_expr<'a>(&'a self, mut f: impl FnMut(&'a AstExprP<P>)) {
        fn recurse<'a, P: AstPayload>(x: &'a AssignP<P>, f: &mut impl FnMut(&'a AstExprP<P>)) {
            match x {
                AssignP::Tuple(xs) => xs.iter().for_each(|x| recurse(x, f)),
                AssignP::Dot(a, _) => f(a),
                AssignP::Index(a_b) => {
                    let (a, b) = &**a_b;
                    f(a);
                    f(b);
                }
                AssignP::Identifier(..) => {}
            }
        }
        recurse(self, &mut f)
    }

    pub(crate) fn visit_expr_mut<'a>(&'a mut self, mut f: impl FnMut(&'a mut AstExprP<P>)) {
        fn recurse<'a, P: AstPayload>(
            x: &'a mut AssignP<P>,
            f: &mut impl FnMut(&'a mut AstExprP<P>),
        ) {
            match x {
                AssignP::Tuple(ref mut xs) => xs.iter_mut().for_each(|x| recurse(&mut *x, f)),
                AssignP::Dot(a, _) => f(a),
                AssignP::Index(a_b) => {
                    let (a, b) = &mut **a_b;
                    f(a);
                    f(b);
                }
                AssignP::Identifier(..) => {}
            }
        }
        recurse(self, &mut f)
    }

    /// Assuming this expression was on the left-hand-side of an assignment,
    /// visit all the names that are bound by this assignment.
    /// Note that assignments like `x[i] = n` don't bind any names.
    pub(crate) fn visit_lvalue<'a>(&'a self, mut f: impl FnMut(&'a AstAssignIdentP<P>)) {
        fn recurse<'a, P: AstPayload>(
            x: &'a AssignP<P>,
            f: &mut impl FnMut(&'a AstAssignIdentP<P>),
        ) {
            match x {
                AssignP::Identifier(x) => f(x),
                AssignP::Tuple(xs) => xs.iter().for_each(|x| recurse(x, f)),
                _ => {}
            }
        }
        recurse(self, &mut f)
    }

    pub(crate) fn visit_lvalue_mut<'a>(
        &'a mut self,
        mut f: impl FnMut(&'a mut AstAssignIdentP<P>),
    ) {
        fn recurse<'a, P: AstPayload>(
            x: &'a mut AssignP<P>,
            f: &mut impl FnMut(&'a mut AstAssignIdentP<P>),
        ) {
            match x {
                AssignP::Identifier(x) => f(x),
                AssignP::Tuple(xs) => xs.iter_mut().for_each(|x| recurse(x, f)),
                _ => {}
            }
        }
        recurse(self, &mut f)
    }
}

impl<P: AstPayload> ForClauseP<P> {
    pub(crate) fn visit_expr<'a>(&'a self, mut f: impl FnMut(&'a AstExprP<P>)) {
        self.var.visit_expr(&mut f);
        f(&self.over);
    }

    pub(crate) fn visit_expr_mut<'a>(&'a mut self, mut f: impl FnMut(&'a mut AstExprP<P>)) {
        self.var.visit_expr_mut(&mut f);
        f(&mut self.over);
    }
}

impl<P: AstPayload> ClauseP<P> {
    pub(crate) fn visit_expr<'a>(&'a self, mut f: impl FnMut(&'a AstExprP<P>)) {
        match self {
            ClauseP::For(x) => x.visit_expr(f),
            ClauseP::If(x) => f(x),
        }
    }

    pub(crate) fn visit_expr_mut<'a>(&'a mut self, mut f: impl FnMut(&'a mut AstExprP<P>)) {
        match self {
            ClauseP::For(x) => x.visit_expr_mut(f),
            ClauseP::If(x) => f(x),
        }
    }
}


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

//! AST for parsed starlark files.

use std::collections::HashSet;

use thiserror::Error;

use crate::codemap::CodeMap;
use crate::codemap::Spanned;
use crate::eval::compiler::EvalException;
use crate::slice_vec_ext::VecExt;
use crate::syntax::ast::Argument;
use crate::syntax::ast::Assign;
use crate::syntax::ast::AssignIdentP;
use crate::syntax::ast::AssignOp;
use crate::syntax::ast::AssignP;
use crate::syntax::ast::AstArgument;
use crate::syntax::ast::AstAssign;
use crate::syntax::ast::AstAssignIdent;
use crate::syntax::ast::AstExpr;
use crate::syntax::ast::AstParameter;
use crate::syntax::ast::AstStmt;
use crate::syntax::ast::AstString;
use crate::syntax::ast::AstTypeExpr;
use crate::syntax::ast::DefP;
use crate::syntax::ast::Expr;
use crate::syntax::ast::LambdaP;
use crate::syntax::ast::Parameter;
use crate::syntax::ast::Stmt;
use crate::syntax::dialect::DialectError;
use crate::syntax::Dialect;

#[derive(Error, Debug)]
enum ValidateError {
    #[error("`break` cannot be used outside of a `for` loop")]
    BreakOutsideLoop,
    #[error("`continue` cannot be used outside of a `for` loop")]
    ContinueOutsideLoop,
    #[error("`return` cannot be used outside of a `def` function")]
    ReturnOutsideDef,
    #[error("`load` must only occur at the top of a module")]
    LoadNotTop,
    #[error("`if` cannot be used outside `def` in this dialect")]
    NoTopLevelIf,
    #[error("`for` cannot be used outside `def` in this dialect")]
    NoTopLevelFor,
    #[error("left-hand-side of assignment must take the form `a`, `a.b` or `a[b]`")]
    InvalidLhs,
    #[error("left-hand-side of modifying assignment cannot be a list or tuple")]
    InvalidModifyLhs,
    #[error("type annotations not allowed on augmented assignments")]
    TypeAnnotationOnAssignOp,
    #[error("type annotations not allowed on multiple assignments")]
    TypeAnnotationOnTupleAssign,
}

#[derive(Eq, PartialEq, PartialOrd, Ord)]
enum ArgsStage {
    Positional,
    Named,
    Args,
    Kwargs,
}

#[derive(Error, Debug)]
enum ArgumentDefinitionOrderError {
    #[error("positional argument after non positional")]
    PositionalThenNonPositional,
    #[error("named argument after *args or **kwargs")]
    NamedArgumentAfterStars,
    #[error("repeated named argument")]
    RepeatedNamed,
    #[error("Args array after another args or kwargs")]
    ArgsArrayAfterArgsOrKwargs,
    #[error("Multiple kwargs dictionary in arguments")]
    MultipleKwargs,
}

impl Expr {
    /// We want to check a function call is well-formed.
    /// Our eventual plan is to follow the Python invariants, but for now, we are closer
    /// to the Starlark invariants.
    ///
    /// Python invariants are no positional arguments after named arguments,
    /// no *args after **kwargs, no repeated argument names.
    ///
    /// Starlark invariants are the above, plus at most one *args and the *args must appear
    /// after all positional and named arguments. The spec is silent on whether you are allowed
    /// multiple **kwargs.
    ///
    /// We allow at most one **kwargs.
    pub(crate) fn check_call(
        f: AstExpr,
        args: Vec<AstArgument>,
        codemap: &CodeMap,
    ) -> Result<Expr, EvalException> {
        let err = |span, msg: ArgumentDefinitionOrderError| {
            Err(EvalException::new(msg.into(), span, codemap))
        };

        let mut stage = ArgsStage::Positional;
        let mut named_args = HashSet::new();
        for arg in &args {
            match &arg.node {
                Argument::Positional(_) => {
                    if stage != ArgsStage::Positional {
                        return err(
                            arg.span,
                            ArgumentDefinitionOrderError::PositionalThenNonPositional,
                        );
                    }
                }
                Argument::Named(n, _) => {
                    if stage > ArgsStage::Named {
                        return err(
                            arg.span,
                            ArgumentDefinitionOrderError::NamedArgumentAfterStars,
                        );
                    } else if !named_args.insert(&n.node) {
                        // Check the names are distinct
                        return err(n.span, ArgumentDefinitionOrderError::RepeatedNamed);
                    } else {
                        stage = ArgsStage::Named;
                    }
                }
                Argument::Args(_) => {
                    if stage > ArgsStage::Named {
                        return err(
                            arg.span,
                            ArgumentDefinitionOrderError::ArgsArrayAfterArgsOrKwargs,
                        );
                    } else {
                        stage = ArgsStage::Args;
                    }
                }
                Argument::KwArgs(_) => {
                    if stage == ArgsStage::Kwargs {
                        return err(arg.span, ArgumentDefinitionOrderError::MultipleKwargs);
                    } else {
                        stage = ArgsStage::Kwargs;
                    }
                }
            }
        }
        Ok(Expr::Call(Box::new(f), args))
    }
}

fn test_param_name<'a, T>(
    argset: &mut HashSet<&'a str>,
    n: &'a AstAssignIdent,
    arg: &Spanned<T>,
    codemap: &CodeMap,
) -> Result<(), EvalException> {
    if argset.contains(n.node.0.as_str()) {
        return Err(EvalException::new(
            ArgumentUseOrderError::DuplicateParameterName.into(),
            arg.span,
            codemap,
        ));
    }
    argset.insert(&n.node.0);
    Ok(())
}

#[derive(Error, Debug)]
enum ArgumentUseOrderError {
    #[error("duplicated parameter name")]
    DuplicateParameterName,
    #[error("positional parameter after non positional")]
    PositionalThenNonPositional,
    #[error("Default parameter after args array or kwargs dictionary")]
    DefaultParameterAfterStars,
    #[error("Args parameter after another args or kwargs parameter")]
    ArgsParameterAfterStars,
    #[error("Multiple kwargs dictionary in parameters")]
    MultipleKwargs,
}

fn check_parameters(parameters: &[AstParameter], codemap: &CodeMap) -> Result<(), EvalException> {
    let err = |span, msg: ArgumentUseOrderError| Err(EvalException::new(msg.into(), span, codemap));

    // you can't repeat argument names
    let mut argset = HashSet::new();
    // You can't have more than one *args/*, **kwargs
    // **kwargs must be last
    // You can't have a required `x` after an optional `y=1`
    let mut seen_args = false;
    let mut seen_kwargs = false;
    let mut seen_optional = false;

    for arg in parameters.iter() {
        match &arg.node {
            Parameter::Normal(n, ..) => {
                if seen_kwargs || seen_optional {
                    return err(arg.span, ArgumentUseOrderError::PositionalThenNonPositional);
                }
                test_param_name(&mut argset, n, arg, codemap)?;
            }
            Parameter::WithDefaultValue(n, ..) => {
                if seen_kwargs {
                    return err(arg.span, ArgumentUseOrderError::DefaultParameterAfterStars);
                }
                seen_optional = true;
                test_param_name(&mut argset, n, arg, codemap)?;
            }
            Parameter::NoArgs => {
                if seen_args || seen_kwargs {
                    return err(arg.span, ArgumentUseOrderError::ArgsParameterAfterStars);
                }
                seen_args = true;
            }
            Parameter::Args(n, ..) => {
                if seen_args || seen_kwargs {
                    return err(arg.span, ArgumentUseOrderError::ArgsParameterAfterStars);
                }
                seen_args = true;
                test_param_name(&mut argset, n, arg, codemap)?;
            }
            Parameter::KwArgs(n, ..) => {
                if seen_kwargs {
                    return err(arg.span, ArgumentUseOrderError::MultipleKwargs);
                }
                seen_kwargs = true;
                test_param_name(&mut argset, n, arg, codemap)?;
            }
        }
    }
    Ok(())
}

impl Expr {
    pub(crate) fn check_lambda(
        params: Vec<AstParameter>,
        body: AstExpr,
        codemap: &CodeMap,
    ) -> Result<Expr, EvalException> {
        check_parameters(&params, codemap)?;
        Ok(Expr::Lambda(LambdaP {
            params,
            body: Box::new(body),
            payload: (),
        }))
    }
}

impl Stmt {
    pub(crate) fn check_def(
        name: AstString,
        params: Vec<AstParameter>,
        return_type: Option<Box<AstTypeExpr>>,
        stmts: AstStmt,
        codemap: &CodeMap,
    ) -> Result<Stmt, EvalException> {
        check_parameters(&params, codemap)?;
        let name = name.into_map(|s| AssignIdentP(s, ()));
        Ok(Stmt::Def(DefP {
            name,
            params,
            return_type,
            body: Box::new(stmts),
            payload: (),
        }))
    }

    pub(crate) fn check_assign(codemap: &CodeMap, x: AstExpr) -> Result<AstAssign, EvalException> {
        Ok(Spanned {
            span: x.span,
            node: match x.node {
                Expr::Tuple(xs) | Expr::List(xs) => {
                    Assign::Tuple(xs.into_try_map(|x| Self::check_assign(codemap, x))?)
                }
                Expr::Dot(a, b) => Assign::Dot(a, b),
                Expr::Index(a_b) => Assign::Index(a_b),
                Expr::Identifier(x) => Assign::Identifier(x.into_map(|s| AssignIdentP(s.0, ()))),
                _ => {
                    return Err(EvalException::new(
                        ValidateError::InvalidLhs.into(),
                        x.span,
                        codemap,
                    ));
                }
            },
        })
    }

    pub(crate) fn check_assignment(
        codemap: &CodeMap,
        lhs: AstExpr,
        ty: Option<Box<AstTypeExpr>>,
        op: Option<AssignOp>,
        rhs: AstExpr,
    ) -> Result<Stmt, EvalException> {
        if op.is_some() {
            // for augmented assignment, Starlark doesn't allow tuple/list
            match &lhs.node {
                Expr::Tuple(_) | Expr::List(_) => {
                    return Err(EvalException::new(
                        ValidateError::InvalidModifyLhs.into(),
                        lhs.span,
                        codemap,
                    ));
                }
                _ => {}
            }
        }
        let lhs = Self::check_assign(codemap, lhs)?;
        if let Some(ty) = &ty {
            let err = if op.is_some() {
                Some(ValidateError::TypeAnnotationOnAssignOp)
            } else if matches!(lhs.node, AssignP::Tuple(_)) {
                Some(ValidateError::TypeAnnotationOnTupleAssign)
            } else {
                None
            };
            if let Some(err) = err {
                return Err(EvalException::new(err.into(), ty.span, codemap));
            }
        }
        Ok(match op {
            None => Stmt::Assign(lhs, Box::new((ty.map(|x| *x), rhs))),
            Some(op) => Stmt::AssignModify(lhs, op, Box::new(rhs)),
        })
    }

    /// Validate all statements only occur where they are allowed to.
    pub(crate) fn validate(
        codemap: &CodeMap,
        stmt: &AstStmt,
        dialect: &Dialect,
    ) -> Result<(), EvalException> {
        // Inside a for, we allow continue/break, unless we go beneath a def.
        // Inside a def, we allow return.
        // All load's must occur at the top-level.
        // At the top-level we only allow for/if when the dialect permits it.
        fn f(
            codemap: &CodeMap,
            dialect: &Dialect,
            stmt: &AstStmt,
            top_level: bool,
            inside_for: bool,
            inside_def: bool,
        ) -> Result<(), EvalException> {
            let err = |x: anyhow::Error| Err(EvalException::new(x, stmt.span, codemap));

            match &stmt.node {
                Stmt::Def(DefP { body, .. }) => f(codemap, dialect, body, false, false, true),
                Stmt::For(_, over_body) => {
                    let (_, body) = &**over_body;
                    if top_level && !dialect.enable_top_level_stmt {
                        err(ValidateError::NoTopLevelFor.into())
                    } else {
                        f(codemap, dialect, body, false, true, inside_def)
                    }
                }
                Stmt::If(..) | Stmt::IfElse(..) => {
                    if top_level && !dialect.enable_top_level_stmt {
                        err(ValidateError::NoTopLevelIf.into())
                    } else {
                        stmt.node.visit_stmt_result(|x| {
                            f(codemap, dialect, x, false, inside_for, inside_def)
                        })
                    }
                }
                Stmt::Break if !inside_for => err(ValidateError::BreakOutsideLoop.into()),
                Stmt::Continue if !inside_for => err(ValidateError::ContinueOutsideLoop.into()),
                Stmt::Return(_) if !inside_def => err(ValidateError::ReturnOutsideDef.into()),
                Stmt::Load(..) => {
                    if !top_level {
                        return err(ValidateError::LoadNotTop.into());
                    }
                    if !dialect.enable_load {
                        return err(DialectError::Load.into());
                    }
                    Ok(())
                }
                _ => stmt.node.visit_stmt_result(|x| {
                    f(codemap, dialect, x, top_level, inside_for, inside_def)
                }),
            }
        }

        f(codemap, dialect, stmt, true, false, 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.
 */

//! Basic expression tests.

use crate::assert;
use crate::assert::Assert;
use crate::environment::GlobalsBuilder;
use crate::values::OwnedFrozenValue;

#[test]
fn arithmetic_test() {
    assert::is_true("(1 + 2 == 3)");
    assert::is_true("(1 * 2 == 2)");
    assert::is_true("(-1 * 2 == -2)");
    assert::is_true("(5 // 2 == 2)");
    assert::is_true("(5 % 2 == 1)");
}

#[test]
fn bitwise_test() {
    assert::all_true(
        r#"
3 & 6 == 2
3 & 6 == 2
-3 & 6 == 4
3 | 6 == 7
3 | -6 == -5
3 ^ 6 == 5
-3 ^ 6 == -5
-3 ^ -6 == 7
1 << 2 == 4
-1 << 2 == -4
1 >> 0 == 1
111 >> 2 == 27
~31 == -32
~-31 == 30
"#,
    );

    assert::fail("1 << -13", "Negative left shift");
    assert::fail("1 >> -13", "Negative right shift");
}

#[test]
fn test_equality() {
    assert::all_true(
        r#"
None == None
True == True
True != False
1 == 1
1 != 2
"test" == "test"
"test" != "x"
[1, 2] == [1, 2]
[1, 3] != [1, 2]
[1, 3] != [1, 3, 4]
(1, 2) == (1, 2)
(1, 3) != (1, 2)
noop((1, 3)) != (1, 3, 4)
noop(range(4)) == range(0, 4, 1)
noop(range(4)) != range(0, 4, 2)
noop(range(4)) != [0,1,2,3,4]
{1: 2} == {1: 2}
{1: 2} != {}
{1: 2, 3: 4} == {1: 2, 3: 4}
{1: 2, 3: 4} == {3: 4, 1: 2}  # Spec is a little ambiguous here
repr == repr
repr != str
[].clear != [1].clear
x = []; x.clear != x.clear
x = []; y = x.clear; y == y
x = repr; y = repr; x == y
"#,
    );
}

#[test]
fn test_frozen_equality() {
    let program = "(str, (), 1, range(4), True, None, [8], {'test':3})";
    let a = assert::pass(program);
    let b = assert::pass(program);
    assert_eq!(a.value(), b.value());

    let mut a = Assert::new();
    a.module("saved", &format!("val = {}", program));
    a.is_true(&format!("load('saved', 'val'); val == {}", program));
}

#[test]
fn test_equality_multiple_globals() {
    fn mk_repr() -> OwnedFrozenValue {
        let mut a = Assert::new();
        let globals = GlobalsBuilder::extended().build();
        a.globals(globals);
        a.pass("repr")
    }

    // Do things that compare by pointer still work if you
    // create fresh Globals for each of them.
    assert_eq!(mk_repr().value(), mk_repr().value());
}

#[test]
fn test_comparison() {
    assert::all_true(
        r#"
False < True
1 < 2
"test" < "x"
[1, 3] > [1, 2]
[1, 3] < [1, 3, 4]
(1, 3) > (1, 2)
(1, 3) < (1, 3, 4)
"#,
    );
    assert::fail("noop(None) < None", "`compare` not supported");
    assert::fail_skip_typecheck("(None, ) < (None, )", "`compare` not supported");
    assert::fail_skip_typecheck("x = (None,); x < x", "`compare` not supported");
    assert::fail_skip_typecheck("x = {}; x < x", "`compare` not supported");
    assert::fail_skip_typecheck("{} < {1: 2}", "`compare` not supported");
    assert::fail_skip_typecheck("range(1) < range(2)", "`compare` not supported");
    assert::fail_skip_typecheck("repr < str", "`compare` not supported");
}

#[test]
fn test_frozen_hash() {
    let exprs = &["\"test\"", "\"x\""];
    let mut a = Assert::new();
    // TODO(nga): fix and enable.
    a.disable_static_typechecking();
    a.module(
        "m",
        &format!(
            r#"
dict = {{x:len(x) for x in [{}]}}
"#,
            exprs.join(",")
        ),
    );
    a.pass(&format!(
        r#"
load('m', frozen_dict='dict')
values = [{}]
assert_eq(all([frozen_dict[x] != None for x in values]), True)
"#,
        exprs.join(","),
    ));
}

#[test]
fn test_compare() {
    assert::fail("noop(1) > False", "Operation `compare` not supported");
    assert::is_true("[1, 2] == [1, 2]");
    assert::is_true("noop(1) != True");
    assert::is_true("not (noop(None) == [1])");
    assert::is_true(
        r#"
xs = [1]
xs[0] = xs
xs == xs
"#,
    );
    assert::is_true(
        r#"
ys = [1]
xs = [ys]
ys[0] = xs
xs == xs
"#,
    );
    let mut a = Assert::new();
    // TODO(nga): fix and enable.
    a.disable_static_typechecking();
    a.fail(
        r#"
ys = [1]
xs = [ys]
ys[0] = xs
xs == ys
"#,
        "recursion",
    );
}

#[test]
fn test_not_in_unhashable() {
    // Note that [] can't be hashed
    assert::fail("[] not in {123: 456}", "not hashable");
}

#[test]
fn test_not_hashable() {
    assert::fail(
        r#"
x = {}
y = {}
x[y] = 1
"#,
        "not hashable",
    );
    assert::fail(
        r#"
x = {'x': 1}
y = {}
x.get(y)
"#,
        "not hashable",
    );
    assert::fail(
        r#"
x = {'x': 1}
y = {}
x[y]
"#,
        "not hashable",
    );
}


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

use std::cell::Cell;

use crate::codemap::FileSpanRef;
use crate::environment::Globals;
use crate::environment::Module;
use crate::eval::Evaluator;
use crate::syntax::AstModule;
use crate::syntax::Dialect;

#[test]
fn before_stmt() {
    let module = Module::new();
    let globals = Globals::new();
    let counter = Cell::new(0);
    let before_stmt = |_span: FileSpanRef, _eval: &mut Evaluator<'_, '_>| {
        counter.set(counter.get() + 1);
    };

    let mut evaluator = Evaluator::new(&module);
    evaluator.before_stmt_fn(&before_stmt);

    // For a top-level statement, we get an additional before_stmt call for the possible gc.
    let program = "\
x = 1          # 0 + 1
def f():       # 1 + 1
  return x + 1 # 3
f()            # 2 + 1
";
    let ast = AstModule::parse("a.star", program.to_owned(), &Dialect::Extended).unwrap();
    evaluator.eval_module(ast, &globals).unwrap();
    assert_eq!(7, counter.get());
}


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

//! Test call expression and parameter binding.

use starlark_derive::starlark_module;

use crate as starlark;
use crate::assert;
use crate::assert::Assert;
use crate::environment::GlobalsBuilder;
use crate::values::UnpackValue;
use crate::values::Value;

#[test]
fn funcall_test() {
    fn f(x: &str) -> String {
        format!(
            "
def f1():
  return 1

def f2(a): return a

def f3(a, b, c):
   return a + b + c

def f4(a, *args):
    r = a
    for i in args:
      r += i
    return r

def f5(a, **kwargs): return kwargs
def f6(*args): return args

def rec1(): rec1()

def rec2(): rec3()
def rec3(): rec4()
def rec4(): rec5()
def rec5(): rec6()
def rec6(): rec2()
{}",
            x
        )
    }
    assert::is_true(&f("(f1() == 1)"));
    assert::is_true(&f("(f2(2) == 2)"));
    assert::is_true(&f("(f3(1, 2, 3) == 6)"));
    assert::is_true(&f("(f4(1, 2, 3) == 6)"));
    assert::is_true(&f("(f5(2) == {})"));
    assert::is_true(&f("(f5(a=2) == {})"));
    assert::is_true(&f("(f5(1, b=2) == {'b': 2})"));
    assert::is_true(&f("(f6(1, 2, 3) == (1, 2, 3))"));
    // Recursion limit
    assert::fail(&f("rec1()"), "Starlark call stack overflow");
    assert::fail(&f("rec2()"), "Starlark call stack overflow");
    // multiple argument with the same name should not be allowed
    assert::fail("def f(a, a=2): pass", "duplicated parameter");
    // Invalid order of parameter
    assert::is_true("def f(a, *args, b): return b\nf(1, b=True)");
    assert::is_true("def f(a, *args, b=True): return b\nf(1)");
    assert::is_true("NAME=True\ndef f(*args, pkg=NAME, **kwargs): return pkg\nf()");
    assert::is_true("def f(*args, pkg=False, **kwargs): return pkg\nf(pkg=True)");
    assert::is_true("def f(a, b=1, *args, c=False): return c\nf(a=1,c=True)");
    assert::fail("def f(a, **kwargs, b=1): pass", "Default parameter after");
    assert::fail(
        "def f(a, b=1, **kwargs, c=1): pass",
        "Default parameter after",
    );
    assert::fail("def f(a, **kwargs, *args): pass", "parameter after another");
}

#[test]
fn funcall_extra_args_def() {
    fn f(x: &str) -> String {
        format!(
            "
def f3(a, b, c):
   return a + b + c
{}",
            x
        )
    }
    assert::fail(&f("noop(f3)(1,2,3,4)"), "extra positional");
    assert::fail(&f("noop(f3)(1,2)"), "Missing parameter");
    assert::fail(&f("noop(f3)(a=1, b=2)"), "Missing parameter");
    assert::fail(&f("noop(f3)(a=1, b=2, c=3, d=4)"), "extra named");
}

#[test]
fn test_repeated_parameters() {
    // Starlark requires both these types of errors are _static_ errors
    assert::fail("def f(x,x): pass", "duplicated parameter");
    assert::fail("def f(): pass\ndef g(): f(x=1,x=1)", "repeated named");
}

#[test]
fn test_bad_application() {
    assert::fail("noop(['1'])(2)", "not supported");
    assert::fail("noop('test')(2)", "not supported");
    assert::fail("noop(1 == 1)(2)", "not supported");
}

#[test]
fn test_extra_args_native() {
    // Check that extra native arguments fail.
    // In this module to use String functions as a test suite.
    assert::is_true(r#"("bonbon".find("on") == 1)"#);
    // Should fail because find declares #needle, so hide the parameter
    assert::fail(r#"("bonbon".find(needle = "on") == 1)"#, "extra named");
    assert::fail(r#""bonbon".find("on", 2, 3, 4)"#, "Wrong number of");
    assert::fail(r#""bonbon".find("on", needless="on")"#, "extra named");
    assert::fail(r#""bonbon".find()"#, "Wrong number of");
}

#[test]
fn test_insufficient_args_native() {
    assert::fails(
        "noop(filter)([])",
        &["Wrong number of positional", "expected 2", "got 1"],
    );
}

#[test]
fn test_parameter_defaults() {
    assert::is_true(
        "
def f(x=[x for x in [1]]):
    return x
f() == [1]",
    );
    assert::is_true(
        "
y = 7
def f(x=y):
    y = 1
    return x
f() == 7",
    );
    assert::is_true(
        "
def f(x, xs = []):
    xs.append(x)
    return xs
pre = str(f(8, [6,7]))
f(1)
post = str(f(2))
pre == '[6, 7, 8]' and post == '[1, 2]'",
    );
}

#[test]
fn test_parameter_defaults_frozen() {
    let mut a = Assert::new();
    // Frozen parameter defaults are meant to error on mutation, check that
    a.module("f.bzl", "def f(x, xs = []):\n xs.append(x)\n return xs");
    // It works if we call it with an explicit parameter
    a.is_true("load('f.bzl', 'f')\nf(1, [2]) == [2, 1]");
    // But fails if we don't, with a frozen error
    a.fail("load('f.bzl', 'f')\nf(1) == [1]", "Immutable");
}

#[test]
fn test_arguments() {
    fn f(x: &str) -> String {
        format!(
            "
def f(a, b, c=5):
    return a * b + c
def g(a=1, b=2):
    return a+b
def h(a=1, *, b=2):
    return a+b
{}",
            x
        )
    }
    assert::is_true(&f("f(*[2, 3]) == 11"));
    assert::is_true(&f("f(*[2, 3, 7]) == 13"));
    assert::fail(&f("f(*[2])"), "Missing parameter");
    assert::is_true(&f("f(**{'b':3, 'a':2}) == 11"));
    assert::is_true(&f("f(**{'c':7, 'a':2, 'b':3}) == 13"));
    assert::fail(&f("f(**{'a':2})"), "Missing parameter");
    assert::fail(&f("f(**{'c':7, 'a':2, 'b':3, 'd':5})"), "extra named");
    assert::fail(&f("noop(f)(1, a=1, b=2)"), "occurs more");
    assert::fail(&f("noop(g)(a=1,*[2])"), "occurs more");
    assert::fail(&f("noop(h)(1, 2)"), "extra positional");
    assert::is_true(&f("h(2, b=3) == 5"));
    assert::is_true(&f("h(a=2, b=3) == 5"));
    assert::fail(
        &f("def bad(x, *, *args):\n  pass"),
        "parameter after another",
    );
}

#[test]
fn test_argument_evaluation_order() {
    assert::pass(
        r#"
r = []

def id(x):
    r.append(x)
    return x

def f(*args, **kwargs):
    return (args, kwargs)

y = f(id(1), id(2), x=id(3), *[id(4)], **dict(z=id(5)))
assert_eq(y, ((1, 2, 4), dict(x=3, z=5)))
assert_eq(r, [1,2,3,4,5])
"#,
    );
}

#[test]
fn test_empty_args_kwargs() {
    // This was a bug that was introduced in the past, so make sure you don't forget
    assert::pass(
        r#"
def f(x, *args, **kwargs):
    assert_eq(args, ())
    assert_eq(kwargs, {})
f(1)
"#,
    );
    assert::fail(
        r#"
def f(x, *, y):
    pass
noop(f)(1)
"#,
        "Missing parameter `y`",
    );
}

#[test]
fn test_frame_size() {
    #[starlark_module]
    fn natives(builder: &mut GlobalsBuilder) {
        fn stack_ptr(args: Vec<Value>) -> anyhow::Result<usize> {
            drop(args);

            let x = 1;
            let ptr = &x;
            Ok(ptr as *const i32 as usize)
        }
    }

    let program = r#"
def f(x):
    return stack_ptr(x)

def g(x):
    noop(x)
    return f(x)

F_PTR = f([])
G_F_PTR = g([])
    "#;

    let mut a = Assert::new();
    a.globals_add(natives);
    let module = a.pass_module(program);
    let one = usize::unpack_value(module.get("F_PTR").unwrap().value()).unwrap();
    let two = usize::unpack_value(module.get("G_F_PTR").unwrap().value()).unwrap();
    assert!(
        two < one,
        "stack grows down everywhere we support starlark-rust"
    );
    // At the moment of writing it is about 1K in O1 and O2 modes, but about 10K in O0.
    // Note, actual frame size may be larger a frame contains for loops.
    let frame_native_size = one - two;
    assert!(frame_native_size > 20, "sanity check");
    assert!(
        frame_native_size < 20000,
        "native frame size is too large: {}, evaluation may result in native stack overflow",
        frame_native_size,
    );
}


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

//! Test dict and list comprehension.

use crate::assert;

// comprehensions should work whether they are at the root, or under a def
// but these are actually quite different locations semantically, so test both
fn check_comp(lines: &[&str]) {
    assert::is_true(&lines.join("\n"));
    let (last, init) = lines.split_last().unwrap();
    assert::is_true(&format!(
        "def f():\n  {}\n  return {}\nf()",
        init.join("\n  "),
        last
    ));
}

#[test]
fn test_spec() {
    // From the Starlark spec
    check_comp(&["[x*x for x in [0,1,2,3,4]] == [0, 1, 4, 9, 16]"]);
    check_comp(&["[x*x for x in [0,1,2,3,4] if x%2 == 0] == [0, 4, 16]"]);
    check_comp(&[
        "[(x, y) for x in [0,1,2,3,4] if x%2 == 0 for y in [0,1,2,3,4] if y > x] == [(0, 1), (0, 2), (0, 3), (0, 4), (2, 3), (2, 4)]",
    ]);
    check_comp(&[r#"[x*y+z for (x, y), z in [((2, 3), 5), (("o", 2), "!")]] == [11, 'oo!']"#]);
    assert::fail("[x*x for x in 1, 2, 3]", "Parse error");
    check_comp(&["x = 1", "_ = [x for x in [2]]", "x == 1"]);
}

#[test]
fn test_scopes() {
    // In the (unnatural) examples below, the scope of the variables x, y, and z
    // is the entire comprehension block, except the operand of the first loop ([], [1] or w),
    // which is resolved in the enclosing environment. The second loop may thus refer
    // to variables defined by the third (z), even though such references would fail
    // if actually executed.
    check_comp(&["[1//0 for x in [] for y in z for z in ()] == []"]);
    assert::fail(
        "[1//0 for x in [1] for y in z for z in ()]",
        "Local variable `z` referenced before assignment",
    );
    assert::fail("[() for x in w for w in [1]]", "Variable `w` not found");
}

#[test]
fn test_dict() {
    // Dict comprehensions
    check_comp(&["{x: 1 for x in [0,1,2]} == {0: 1, 1: 1, 2: 1}"]);
}

#[test]
fn test_nested() {
    // Nested comprehensions
    check_comp(&["[[y for y in x] for x in [[1],[2,3]]] == [[1],[2,3]]"]);
    check_comp(&["[[x for x in x] for x in [[1],[2,3]]] == [[1],[2,3]]"]);
    check_comp(&["[x for x in [[1],[2,3]] for x in x if x >= 2] == [2,3]"]);
    check_comp(&[
        "items = {8: [1,2], 9: [3,4,6]}",
        "[[x for x in items[x] if x%2==0] for x in items] == [[2],[4,6]]",
    ]);
}

#[test]
fn test_sequential() {
    // Sequential comprehensions
    check_comp(&[
        "x = [x*x for x in [0,1,2,3,4]]",
        "[x*x for x in x] == [0, 1, 16, 81, 256]",
    ]);
}

#[test]
fn test_if_only() {
    // If only comprehensions are parse errors
    assert::fail("[1 if 0 == 0] == [0]", "Parse error");
}

#[test]
fn test_same_var_twice_in_assignment() {
    check_comp(&["[x for (x, x) in [(1, 2), (3, 4)]] == [2, 4]"]);
}

#[test]
fn test_same_var_in_two_fors() {
    check_comp(&["[x for x in [[1, 2], [3]] for x in x] == [1, 2, 3]"]);
}

#[test]
fn test_comprehension_blocks() {
    assert::fail(
        r#"
x = [1, 2]
res = [x for _ in [3] for x in x]
assert_eq(res, [1,2])
"#,
        "variable `x` referenced before assignment",
    );
}