emmylua_code_analysis 0.23.2

A library for analyzing lua code.
Documentation
#[cfg(test)]
mod test {
    use crate::{DiagnosticCode, VirtualWorkspace};
    use emmylua_parser::{LuaCallExpr, LuaExpr};

    #[test]
    fn test_custom_binary() {
        let mut ws = VirtualWorkspace::new();

        ws.def(
            r#"
        ---@class AA
        ---@operator pow(number): AA

        ---@type AA
        a = {}
        "#,
        );

        let ty = ws.expr_ty(
            r#"
        a ^ 1
        "#,
        );
        let expected = ws.ty("AA");
        assert_eq!(ty, expected);
    }

    #[test]
    fn test_issue_559() {
        let mut ws = VirtualWorkspace::new();

        ws.def(
            r#"
            ---@class Origin
            ---@operator add(Origin):Origin

            ---@alias AliasType Origin

            ---@type AliasType
            local x1
            ---@type AliasType
            local x2

            A = x1 + x2
        "#,
        );

        let ty = ws.expr_ty("A");
        let expected = ws.ty("Origin");
        assert_eq!(ty, expected);
    }

    #[test]
    fn test_issue_867() {
        let mut ws = VirtualWorkspace::new();

        ws.def(
            r#"
            local a --- @type { foo? : { bar: { baz: number } } }

            local b = a.foo.bar -- a.foo may be nil (correct)

            c = b.baz -- b may be nil (incorrect)
        "#,
        );

        let ty = ws.expr_ty("c");
        let expected = ws.ty("number");
        assert_eq!(ty, expected);
    }

    #[test]
    fn test_intersection_call_infers_return_type() {
        let mut ws = VirtualWorkspace::new();
        ws.def(
            r#"
            ---@type { field: string } & fun(): string
            F = nil
        "#,
        );

        assert_eq!(ws.expr_ty("F()"), ws.ty("string"));
    }

    #[test]
    fn test_no_flow_overload_call_keeps_shared_return_when_arg_declines() {
        let mut ws = VirtualWorkspace::new();
        let file_id = ws.def(
            r#"
            ---@overload fun(value: string): boolean
            ---@overload fun(value: integer): boolean
            ---@param value string|integer
            ---@return boolean
            local function classify(value)
            end

            local result = classify({})
        "#,
        );

        let call_expr = ws.get_node::<LuaCallExpr>(file_id);
        let semantic_model = ws
            .analysis
            .compilation
            .get_semantic_model(file_id)
            .expect("Semantic model must exist");
        let ty = crate::semantic::infer::try_infer_expr_no_flow(
            semantic_model.get_db(),
            &mut semantic_model.get_cache().borrow_mut(),
            LuaExpr::CallExpr(call_expr),
        )
        .expect("no-flow call replay should not error")
        .expect("no-flow call replay should keep shared overload return");

        assert_eq!(ty, ws.ty("boolean"));
    }

    #[test]
    fn test_no_flow_overload_call_declines_when_declined_arg_returns_differ() {
        let mut ws = VirtualWorkspace::new();
        let file_id = ws.def(
            r#"
            ---@overload fun(value: string): string
            ---@overload fun(value: integer): integer
            ---@param value string|integer
            ---@return string|integer
            local function classify(value)
            end

            local result = classify({})
        "#,
        );

        let call_expr = ws.get_node::<LuaCallExpr>(file_id);
        let semantic_model = ws
            .analysis
            .compilation
            .get_semantic_model(file_id)
            .expect("Semantic model must exist");
        let ty = crate::semantic::infer::try_infer_expr_no_flow(
            semantic_model.get_db(),
            &mut semantic_model.get_cache().borrow_mut(),
            LuaExpr::CallExpr(call_expr),
        )
        .expect("no-flow call replay should not error");

        assert!(ty.is_none());
    }

    #[test]
    fn test_infer_expr_list_types_tolerates_infer_failures() {
        let mut ws = VirtualWorkspace::new();
        let code = r#"
            local t ---@type { a: number }

            ---@type string, string
            local y, x

            x, y = t.b, 1
        "#;

        assert!(!ws.has_no_diagnostic(DiagnosticCode::UndefinedField, code));
        assert!(!ws.has_no_diagnostic(DiagnosticCode::AssignTypeMismatch, code));
    }

    #[test]
    fn test_flow_assign_preserves_doc_type_on_infer_error() {
        let mut ws = VirtualWorkspace::new();
        ws.def(
            r#"
            local t ---@type { a: number }
            local x ---@type string
            x = t.b
            R = x
        "#,
        );

        assert_eq!(ws.expr_ty("R"), ws.ty("nil"));
    }

    #[test]
    fn test_issue_1071_repeated_any_member_arithmetic_loads() {
        let mut ws = VirtualWorkspace::new();
        ws.def(
            r#"
            function fun()
                local a = {} ---@type any
                if a then
                    c = c + a.f_a1b2c3 / 100 * b.f_a1b2c3;
                    c = c + a.f_d4e5f6 / 100 * b.f_d4e5f6;
                    c = c + a.f_111aaa / 100 * b.f_111aaa;
                    c = c + a.f_222bbb / 100 * b.f_222bbb;
                    c = c + a.f_333ccc / 100 * b.f_333ccc;
                    c = c + a.f_444ddd / 100 * b.f_444ddd;
                    c = c + a.f_555eee / 100 * b.f_555eee;
                    c = c + a.f_666fff / 100 * b.f_666fff;
                    c = c + a.f_777000 / 100 * b.f_777000;
                    c = c + a.f_888111 / 100 * b.f_888111;
                    c = c + a.f_999222 / 100 * b.f_999222;
                    c = c + a.f_aaa333 / 100 * b.f_aaa333;
                    c = c + a.f_bbb444 / 100 * b.f_bbb444;
                    c = c + a.f_ccc555 / 100 * b.f_ccc555;
                    c = c + a.f_ddd666 * b.f_ddd666;
                    c = c + a.f_eee777 * b.f_eee777;
                    c = c + a.f_fff888 * b.f_fff888;
                    c = c + a.f_000999 * b.f_000999;
                    c = c + a.f_123abc * b.f_123abc;
                    c = c + a.f_234bcd * b.f_234bcd;
                    c = c + a.f_345cde * b.f_345cde;
                    c = c + a.f_456def * b.f_456def;
                    c = c + a.f_567ef0 * b.f_567ef0;
                    c = c + a.f_678f01 * b.f_678f01;
                    c = c + a.f_789012 * b.f_789012;
                    c = c + a.f_89a123 * b.f_89a123;
                    c = c + a.f_9ab234/100 * b.f_9ab234;
                    c = c + a.f_abd345 * b.f_abd345;
                    c = c + a.f_bce456 / 100 * b.f_bce456;
                    c = c + a.f_cdf567 / 100 * b.f_cdf567;
                    c = c + a.f_def678 / 100 * b.f_def678;
                    c = c + a.f_ef0123 / 100 * b.f_ef0123;
                    c = c + a.f_f01234 / 100 * b.f_f01234;
                end
                return c
            end
        "#,
        );

        assert_eq!(ws.expr_ty("c"), ws.ty("any"));
    }
}