serc-cli 0.1.2

An experimental CPython bytecode compiler, written in Rust.
mod common;

use serc::{CompileError, CompileOptions, compile_with_options};

#[test]
fn attribute_named_expression_diagnostic_matches_python() {
    let sources = [
        "value = (obj.attr := 0)\n",
        "(obj.attr.more := 0)\n",
        "(factory().attr := 0)\n",
        "(obj[0].attr := 0)\n",
        "(((obj.attr)) := 0)\n",
        "((obj).attr := 0)\n",
        "(obj . attr := 0)\n",
        "(obj[α + 1].β := 1)\n",
        "(obj.\n attr := 1)\n",
        "(obj.\r\n attr := 1)\r\n",
        "(1 .real := 0)\n",
        "f'{(obj.attr := 0)}'\n",
        "f'{(obj.attr := 0)=}'\n",
        "class C:\n value = (obj.attr := 0)\n",
        "def f():\n return (obj.attr := 0)\n",
    ];
    for version in common::PYTHON_VERSIONS {
        let options = CompileOptions {
            python_version: version,
            ..CompileOptions::default()
        };
        for source in sources {
            let diagnostic = common::diagnostics::syntax_diagnostic_with_bytes(version, source);
            let normalized = source.replace("\r\n", "\n");
            let expected = diagnostic.character_range(&normalized);
            diagnostic.assert_error(source, &options, expected);
        }
    }
}

#[test]
fn other_named_expression_errors_are_preserved() {
    for source in [
        "(obj[0] := 0)\n",
        "(obj.attr :=)\n",
        "(obj.attr := 0\n",
        "(obj.attr := 0)\nx = 'unterminated\n",
        "(obj.attr := 0)\n)\n",
        "@\n(obj.attr := 0)\n",
        "(obj.attr := (other.x := 3))\n",
    ] {
        let error =
            compile_with_options(source, "case.py", &CompileOptions::default()).unwrap_err();
        assert!(
            matches!(error, CompileError::Parse(_)),
            "{source:?}: {error}"
        );
    }
    for source in [
        "(obj := 0)\n",
        "f'{obj.attr := 0}'\n",
        "value = '(obj.attr := 0)'\n",
        "# (obj.attr := 0)\npass\n",
    ] {
        assert!(compile_with_options(source, "case.py", &CompileOptions::default()).is_ok());
    }
}

#[test]
fn deep_attribute_target_does_not_overflow_visitor_stack() {
    let source = format!("(obj{} := 0)\n", ".field".repeat(2000));
    let error = compile_with_options(&source, "case.py", &CompileOptions::default()).unwrap_err();
    assert!(
        error
            .to_string()
            .ends_with("cannot use assignment expressions with attribute"),
        "{error}"
    );
}

#[test]
fn unknown_decimal_parse_limit_takes_precedence() {
    let source = format!(
        "match value:\n    case _:\n        value = (obj.attr := 0)\n        integer = {}\n",
        "9".repeat(641)
    );
    let error = compile_with_options(&source, "case.py", &CompileOptions::default()).unwrap_err();
    assert!(
        matches!(
            error,
            CompileError::Unsupported(ref message) if message == "decimal integer parsing limit is not known"
        ),
        "{error}"
    );
}