#[cfg(test)]
mod test {
    use crate::{
        DiagnosticCode, FileId, InferFailReason, LuaMemberKey, LuaMemberOwner, LuaType,
        LuaUnionType, VirtualWorkspace, semantic::infer_owner_raw_member_type_with_realm,
    };
    use glua_parser::{LuaAstNode, LuaExpr, LuaIndexExpr, LuaNameExpr};

    fn infer_last_name_expr_type(
        ws: &mut VirtualWorkspace,
        code: &str,
        name: &str,
    ) -> crate::LuaType {
        let file_id = ws.def(code);
        infer_last_name_expr_type_in_file(ws, file_id, name)
    }

    fn infer_last_name_expr_type_in_file(
        ws: &VirtualWorkspace,
        file_id: FileId,
        name: &str,
    ) -> crate::LuaType {
        let semantic_model = ws
            .analysis
            .compilation
            .get_semantic_model(file_id)
            .expect("Semantic model must exist");
        let target = semantic_model
            .get_root()
            .descendants::<LuaNameExpr>()
            .filter(|expr| expr.get_name_text().as_deref() == Some(name))
            .collect::<Vec<_>>()
            .pop()
            .expect("Target name expr must exist");

        semantic_model
            .infer_expr(LuaExpr::NameExpr(target))
            .unwrap_or(crate::LuaType::Unknown)
    }

    fn infer_last_index_expr_type_in_file(
        ws: &VirtualWorkspace,
        file_id: FileId,
        field_name: &str,
    ) -> crate::LuaType {
        let semantic_model = ws
            .analysis
            .compilation
            .get_semantic_model(file_id)
            .expect("Semantic model must exist");
        let target = semantic_model
            .get_root()
            .descendants::<LuaIndexExpr>()
            .filter(|expr| {
                expr.get_index_key()
                    .is_some_and(|key| key.get_path_part() == field_name)
            })
            .collect::<Vec<_>>()
            .pop()
            .expect("Target index expr must exist");

        semantic_model
            .infer_expr(LuaExpr::IndexExpr(target))
            .unwrap_or(crate::LuaType::Unknown)
    }

    #[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_isvalid_local_cached_still_narrows() {
        let mut ws = VirtualWorkspace::new();
        let library_root = ws.virtual_url_generator.new_path("__test_library_isvalid");
        ws.analysis.add_library_workspace(library_root.clone());
        let library_uri =
            lsp_types::Uri::parse_from_file_path(&library_root.join("isvalid.lua")).unwrap();
        ws.analysis.update_file_by_uri(
            &library_uri,
            Some(
                r#"
            ---@class Entity
            ---@field health integer

            ---@param obj any
            ---@return boolean
            function _G.IsValid(obj) end
            "#
                .to_string(),
            ),
        );

        // Cached aliases of the global helper should still narrow.
        assert!(ws.check_code_for(
            DiagnosticCode::NeedCheckNil,
            r#"
            ---@class Entity
            ---@field health integer

            ---@return Entity?
            local function get_ent() end

            local IsValid = IsValid

            local ent = get_ent()
            if IsValid(ent) then
                local _health = ent.health
            end
            "#
        ));
    }

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

        assert!(!ws.check_code_for(
            DiagnosticCode::ParamTypeMismatch,
            r#"
            ---@class Entity

            ---@return Entity?
            local function get_ent() end

            function IsValid(obj)
                return obj ~= nil
            end

            ---@param value nil
            local function expects_nil(value) end

            local ent = get_ent()
            if not IsValid(ent) then
                expects_nil(ent)
            end
            "#
        ));
    }

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

        ws.def(
            r#"
            ---@class Color
            Color = {}

            ---@param r integer
            ---@param g integer
            ---@param b integer
            ---@param a? integer
            ---@return integer
            function Color(r, g, b, a)
                return r
            end

        "#,
        );

        assert_eq!(ws.expr_ty("Color(255, 255, 255)"), ws.ty("integer"));
    }

    #[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.check_code_for(DiagnosticCode::UndefinedField, code));
        assert!(!ws.check_code_for(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_isstring_guard_narrows_undefined_global_expr_to_string() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        ws.def_gmod_type_predicates();
        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            if isstring(testVar2) then ---@diagnostic disable-line: undefined-global
                print(testVar2) ---@diagnostic disable-line: undefined-global
            end
        "#,
            "testVar2",
        );

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

    #[test]
    fn test_istable_guard_preserves_annotated_specific_table_type() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        ws.def_gmod_type_predicates();
        ws.def(
            r#"
            ---@class MyData
            ---@field value integer
        "#,
        );

        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            ---@type MyData?
            local data

            if istable(data) then
                print(data)
            end
        "#,
            "data",
        );

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

    #[test]
    fn test_isstring_guard_preserves_annotated_string_subtype() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        ws.def_gmod_type_predicates();
        ws.def(
            r#"
            ---@class UserId: string
        "#,
        );

        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            ---@type UserId?
            local value

            if isstring(value) then
                print(value)
            end
        "#,
            "value",
        );

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

    #[test]
    fn test_istable_guard_does_not_broaden_incompatible_known_type() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        ws.def_gmod_type_predicates();

        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            ---@type string
            local value = "x"

            if istable(value) then
                print(value)
            end
        "#,
            "value",
        );

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

    #[test]
    fn test_truthy_guard_with_index_expr_narrows_unknown_to_any() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        // Pattern: if ctp and ctp.Disable then ... end
        // The undefined global 'ctp' should be narrowed from Unknown to Any (truthy)
        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            if ctp and ctp.Disable then ---@diagnostic disable-line: undefined-global
                print(ctp) ---@diagnostic disable-line: undefined-global
            end
        "#,
            "ctp",
        );

        // After truthy check, Unknown should be narrowed to Any
        assert_eq!(
            ty,
            ws.ty("any"),
            "Type should be Any after truthy guard, got: {:?}",
            ty
        );
    }

    #[test]
    fn test_gmod_isplayer_method_narrows_entity_to_player() {
        let mut ws = VirtualWorkspace::new();
        ws.def(
            r#"
            ---@class Entity
            ---@class Player : Entity

            ---@return boolean
            ---@return_cast self Player
            function Entity:IsPlayer() end
        "#,
        );

        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            ---@param entity Entity
            local function test(entity)
                if entity:IsPlayer() then
                    print(entity)
                end
            end
        "#,
            "entity",
        );

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

    #[test]
    fn test_gmod_isplayer_method_narrows_nullable_entity_to_player() {
        let mut ws = VirtualWorkspace::new();
        ws.def(
            r#"
            ---@class Entity
            ---@class Player : Entity

            ---@return boolean
            ---@return_cast self Player
            function Entity:IsPlayer() end
        "#,
        );

        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            ---@param entity Entity?
            local function test(entity)
                if entity:IsPlayer() then
                    print(entity)
                end
            end
        "#,
            "entity",
        );

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

    #[test]
    fn test_gmod_isplayer_method_narrows_entity_union_to_player() {
        let mut ws = VirtualWorkspace::new();
        ws.def(
            r#"
            ---@class Entity
            ---@class Player : Entity
            ---@class CSEnt : Entity

            ---@return boolean
            ---@return_cast self Player
            function Entity:IsPlayer() end
        "#,
        );

        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            ---@param entity Entity|CSEnt
            local function test(entity)
                if entity:IsPlayer() then
                    print(entity)
                end
            end
        "#,
            "entity",
        );

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

    #[test]
    fn test_guarded_unknown_index_expr_value_narrows_to_any() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            local function draw(rec)
                if not rec or not rec.data then return end

                local d = rec.data
                print(d)
            end
        "#,
            "d",
        );

        assert_eq!(
            ty,
            ws.ty("any"),
            "Guarded unknown field value should narrow to Any, got: {:?}",
            ty
        );
    }

    #[test]
    fn test_pairs_value_preserves_indexed_assignment_table_field() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            Glide = {}
            Glide.DebugSnapshots = Glide.DebugSnapshots or {}

            local entId = 1
            local rec = Glide.DebugSnapshots[entId]
            if not rec then
                rec = { data = {}, t = 0 }
                Glide.DebugSnapshots[entId] = rec
            end

            local function draw()
                local snaps = Glide.DebugSnapshots or {}
                for entId, rec in pairs(snaps) do
                    if not rec or not rec.data then return end

                    local d = rec.data
                    print(d)
                end
            end
        "#,
            "d",
        );

        assert!(
            matches!(ty, LuaType::TableConst(_)),
            "Guarded field from pairs value should preserve the assigned data table, got: {:?}",
            ty
        );
    }

    #[test]
    fn test_branch_initialized_indexed_record_alias_preserves_shape() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let mut emmyrc = ws.get_emmyrc();
        emmyrc.gmod.enabled = true;
        ws.update_emmyrc(emmyrc);

        ws.def_file(
            "lua/autorun/sh_glide.lua",
            r#"
            ---@class Glide
            Glide = Glide or {}
        "#,
        );
        ws.def_file(
            "lua/glide/sh_network.lua",
            r#"
            Glide.DebugNetwork = Glide.DebugNetwork or {}

            if CLIENT then
                local DebugNet = Glide.DebugNetwork

                function DebugNet.ReadSnapshot()
                    local entId = net.ReadUInt(16)
                    local vehicleId = nil
                    local fields = {}
                    return entId, vehicleId, fields
                end
            end
        "#,
        );
        let file_id = ws.def_file(
            "lua/glide/client/network.lua",
            r#"
            local DebugNet = Glide.DebugNetwork

            local function receive()
                local entId, vehicleId, fields = DebugNet.ReadSnapshot()
                if not entId then return end

                Glide.DebugSnapshots = Glide.DebugSnapshots or {}
                local rec = Glide.DebugSnapshots[entId]
                if not rec then
                    rec = { data = {}, t = SysTime() }
                    Glide.DebugSnapshots[entId] = rec
                end

                local data = rec.data
                print(data, rec)
            end
        "#,
        );
        let ty = infer_last_name_expr_type_in_file(&ws, file_id, "rec");

        assert!(
            matches!(ty, LuaType::TableConst(_)),
            "Branch-initialized record alias should preserve its table shape, got: {:?}",
            ty
        );
    }

    #[test]
    fn test_flow_merge_preserves_explicit_any_over_table_shape() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            ---@type any
            local value
            if condition then
                value = {}
            end

            print(value)
            "#,
            "value",
        );

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

    #[test]
    fn test_flow_merge_keeps_inferred_any_over_specific_non_table_assignment() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            ---@return any
            local function read()
            end

            local value = read()
            if condition then
                value = "translated"
            end

            print(value)
            "#,
            "value",
        );

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

    #[test]
    fn test_pairs_value_preserves_cross_file_indexed_assignment_table_field() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let mut emmyrc = ws.get_emmyrc();
        emmyrc.gmod.enabled = true;
        ws.update_emmyrc(emmyrc);

        ws.def_file(
            "lua/autorun/sh_glide.lua",
            r#"
            ---@class Glide
            Glide = Glide or {}
        "#,
        );
        let consumer_file = ws.def_file(
            "lua/glide/client/debugging.lua",
            r#"
            local SNAP_TTL = 1
            local function draw()
                local now = 2
                local snaps = Glide.DebugSnapshots or {}
                for entId, rec in pairs(snaps) do
                    if not rec or not rec.data then continue end
                    if now - rec.t > SNAP_TTL then
                        Glide.DebugSnapshots[entId] = nil
                        continue
                    end

                    local d = rec.data
                    print(d)
                end
            end
        "#,
        );
        ws.def_file(
            "lua/glide/sh_network.lua",
            r#"
            Glide.DebugNetwork = Glide.DebugNetwork or {}

            do
                local shared = Glide.DebugNetwork
                if not shared.REMOVED then
                    shared.REMOVED = {}
                end
            end

            if CLIENT then
                local DebugNet = Glide.DebugNetwork
                local REMOVED = DebugNet.REMOVED

                local function readValue()
                    return 1
                end

                function DebugNet.ReadSnapshot()
                    local entId = 1
                    local vehicleId = nil
                    local fields = {}
                    local key = "radius"
                    fields[key] = readValue()

                    return entId, vehicleId, fields
                end
            end

            Glide.CMD_DEBUG_SNAPSHOT = 14
        "#,
        );
        ws.def_file(
            "lua/glide/client/network.lua",
            r#"
            Glide.NetCommands = Glide.NetCommands or {}
            local commands = Glide.NetCommands
            local DebugNet = Glide.DebugNetwork
            local REMOVED = DebugNet.REMOVED

            commands[Glide.CMD_DEBUG_SNAPSHOT] = function()
                local entId, vehicleId, fields = DebugNet.ReadSnapshot()
                if not entId then return end

                Glide.DebugSnapshots = Glide.DebugSnapshots or {}

                local rec = Glide.DebugSnapshots[entId]
                if not rec then
                    rec = { data = {}, t = 0 }
                    Glide.DebugSnapshots[entId] = rec
                end

                local data = rec.data
                if vehicleId ~= nil then
                    data.vehicle = vehicleId
                else
                    data.vehicle = nil
                end

                for key, value in pairs(fields) do
                    if key ~= "ent" then
                        if value == REMOVED then
                            data[key] = nil
                        else
                            data[key] = value
                        end
                    end
                end
                rec.t = 0
            end
        "#,
        );

        let ty = infer_last_name_expr_type_in_file(&ws, consumer_file, "d");
        assert!(
            matches!(ty, LuaType::TableConst(_)),
            "Cross-file guarded field from pairs value should preserve the assigned data table, got: {:?}",
            ty
        );
    }

    #[test]
    fn test_dynamic_string_key_field_read_uses_index_value_type() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let mut emmyrc = ws.get_emmyrc();
        emmyrc.gmod.enabled = true;
        emmyrc.gmod.infer_dynamic_fields = true;
        ws.update_emmyrc(emmyrc);

        let file_id = ws.def_file(
            "lua/glide/client/network.lua",
            r#"
            ---@class Vector
            local Vector = {}
            ---@return Vector
            function _G.Vector() end

            net = {}
            ---@return string
            function net.ReadString() end

            local function readValue()
                if flag then
                    return Vector()
                end

                return "not a number"
            end

            local rec = { data = {}, t = 0 }
            local data = rec.data
            local key = net.ReadString()
            data[key] = readValue()

            local d = rec.data
            print(d.forwardSlip)
        "#,
        );

        let ty = infer_last_index_expr_type_in_file(&ws, file_id, "forwardSlip");
        let display = ws.humanize_type(ty.clone());
        assert!(
            !ty.is_unknown() && !ty.is_nil(),
            "A dynamic string-key field read should use the index value type, got: {display}"
        );
        assert!(
            display.contains("Vector") && display.contains("\"not a number\""),
            "A dynamic string-key field read should include the indexed value union, got: {display}"
        );
    }

    #[test]
    fn test_and_false_branch_does_not_over_narrow_left_operand() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            ---@type string?
            local x

            local cond = false
            if x and cond then
            else
                print(x)
            end
            "#,
            "x",
        );

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

    #[test]
    fn test_or_with_local_unknown_does_not_coerce_to_nil() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            ---@type unknown
            local maybe
            local result = maybe or {}
            print(result)
            "#,
            "result",
        );

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

    #[test]
    fn test_or_with_unresolved_global_keeps_nullable_fallback_behavior() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            local result = mysqloo or {}
            print(result) ---@diagnostic disable-line: undefined-global, undefined-global-argument
            "#,
            "result",
        );

        assert!(ty.is_nullable());
        assert_ne!(ty, ws.ty("any"));
    }

    #[test]
    fn test_or_with_rhs_unresolved_global_keeps_nullable_known_type() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            ---@type string?
            local known
            local result = known or unknownGlobal
            print(result) ---@diagnostic disable-line: undefined-global, undefined-global-argument
            "#,
            "result",
        );

        assert!(ty.is_nullable());
        assert_ne!(ty, ws.ty("any"));
    }

    #[test]
    fn test_or_with_both_unresolved_globals_infers_nil() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            local result = leftUnknown or rightUnknown
            print(result) ---@diagnostic disable-line: undefined-global, undefined-global-argument
            "#,
            "result",
        );

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

    #[test]
    fn test_or_with_unresolved_global_and_error_keeps_error_workaround() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let ty = infer_last_name_expr_type(
            &mut ws,
            r#"
            local result = missingGlobal or error("boom")
            print(result) ---@diagnostic disable-line: undefined-global, undefined-global-argument
            "#,
            "result",
        );

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

    /// Regression: assigning an undefined global to a (global / table) target
    /// should bind the target's type to `nil`, not `unknown`. Hovering the LHS
    /// used to show `unknown` after the user's MySQLite repro.
    ///
    /// `local x = SOME_UNDEF` deliberately keeps the legacy `unknown` binding,
    /// because the cached-alias narrowing patterns
    /// (`local IsValid = IsValid`, `local is_fn = isfunction`) rely on the
    /// local decl staying `unknown` so the flow analyzer can trace the alias
    /// back to the original global by name. See
    /// `compilation::analyzer::lua::stats::is_undefined_global_name_expr`.
    #[test]
    fn test_assignment_from_undefined_global_binds_nil() {
        let mut ws = VirtualWorkspace::new();
        ws.def(
            r#"
            multistatements = CLIENT_MULTI_STATEMENTS ---@diagnostic disable-line: undefined-global, undefined-global-assignment
            "#,
        );

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

    #[test]
    fn test_inferred_collection_index_has_no_undefined_field_diagnostic() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let code = r#"
            local keys = {}

            for key in pairs({"a", "b", "c", "d"}) do
                keys[#keys + 1] = key
            end

            print(keys[1])
        "#;

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

    #[test]
    fn test_annotated_table_index_infers_nullable_any() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let code = r#"
            ---@type table
            local keys
            local var = keys[1]
            print(var)
        "#;

        let ty = infer_last_name_expr_type(&mut ws, code, "var");
        let expected =
            LuaType::Union(LuaUnionType::from_vec(vec![LuaType::Any, LuaType::Nil]).into());

        assert!(
            ws.check_type(&ty, &expected),
            "expected nullable any, got {}",
            ws.humanize_type(ty.clone())
        );
        assert!(!ty.is_nil(), "got nil");
        assert!(!ty.is_unknown(), "got unknown");
    }

    #[test]
    fn test_annotated_table_index_has_no_undefined_field_diagnostic() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let code = r#"
            ---@type table
            local keys
            local var = keys[1]
            print(var)
        "#;

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

    #[test]
    fn test_annotated_table_nil_union_index_infers_nullable_any() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let code = r#"
            ---@type table|nil
            local keys
            local var = keys[1]
            print(var)
        "#;

        let ty = infer_last_name_expr_type(&mut ws, code, "var");
        let expected =
            LuaType::Union(LuaUnionType::from_vec(vec![LuaType::Any, LuaType::Nil]).into());

        assert!(
            ws.check_type(&ty, &expected),
            "expected nullable any, got {}",
            ws.humanize_type(ty.clone())
        );
        assert!(ws.check_code_for(DiagnosticCode::UndefinedField, code));
    }

    #[test]
    fn test_annotated_table_assigned_member_keeps_specific_type() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        ws.def(
            r#"
            GG = {} --- @type table
            function GG.fun() end
            F = GG.fun
        "#,
        );

        let ty = ws.expr_ty("F");

        assert!(matches!(ty, LuaType::Signature(_)), "got: {ty:?}");
    }

    #[test]
    fn test_table_const_exact_name_lookup_does_not_union_other_members() {
        let mut ws = VirtualWorkspace::new();
        let file_id = ws.def(
            r#"
            local tbl = { pi = 3.1415 }
            function tbl.max(a, b) return a > b and a or b end
            local _ = tbl.Max
            "#,
        );

        let tbl_type = infer_last_name_expr_type_in_file(&ws, file_id, "tbl");
        let LuaType::TableConst(table_id) = tbl_type else {
            panic!("expected table const, got: {tbl_type:?}");
        };

        let db = ws.analysis.compilation.get_db();
        let missing_key = LuaMemberKey::Name("Max".into());
        let missing_lookup = infer_owner_raw_member_type_with_realm(
            db,
            LuaMemberOwner::Element(table_id.clone()),
            &missing_key,
            file_id,
            None,
        );
        assert!(
            matches!(missing_lookup, Err(InferFailReason::FieldNotFound)),
            "unexpected lookup result for missing exact key: {missing_lookup:?}"
        );

        let existing_key = LuaMemberKey::Name("max".into());
        let existing_lookup = infer_owner_raw_member_type_with_realm(
            db,
            LuaMemberOwner::Element(table_id),
            &existing_key,
            file_id,
            None,
        );
        assert!(
            matches!(existing_lookup, Ok(LuaType::Signature(_))),
            "expected function signature for existing key, got: {existing_lookup:?}"
        );
    }

    #[test]
    fn test_inferred_collection_index_in_client_realm_has_no_undefined_field_diagnostic() {
        let mut ws = VirtualWorkspace::new_with_init_std_lib();
        let code = r#"
            local keys = {}

            for key in pairs({"a", "b", "c", "d"}) do
                keys[#keys + 1] = key
            end

            if CLIENT then
                A = keys[1]
            end
        "#;

        assert!(ws.check_code_for(DiagnosticCode::UndefinedField, code));

        let ty = infer_last_name_expr_type(&mut ws, code, "A");
        assert!(!ty.is_unknown());
    }

    /// `table_const_array_base` should only return `Some` for true shaped
    /// sequential literals (all integer-keyed, no named fields). Mixed tables
    /// with named fields or sparse numeric keys must return `None`.
    #[test]
    fn test_table_const_array_base_only_matches_shaped_sequential_literals() {
        let mut ws = VirtualWorkspace::new();
        let file_id = ws.def(
            r#"
            local shaped = { { id = 1 }, { id = 2 } }
            local mixed = { { id = 1 }, kind = "metadata" }
            local numeric_object = { [100] = { id = 1 }, name = "x" }
            print(shaped, mixed, numeric_object)
            "#,
        );

        let db = ws.analysis.compilation.get_db();

        // shaped: two sequential elements, no named fields → Some(...)
        let shaped_ty = infer_last_name_expr_type_in_file(&ws, file_id, "shaped");
        let LuaType::TableConst(shaped_range) = shaped_ty else {
            panic!("expected TableConst for shaped, got: {shaped_ty:?}");
        };
        assert!(
            crate::table_const_array_base(db, &shaped_range).is_some(),
            "shaped sequential literal should be treated as array-like"
        );

        // mixed: has integer [1] but also named field `kind` → None
        let mixed_ty = infer_last_name_expr_type_in_file(&ws, file_id, "mixed");
        let LuaType::TableConst(mixed_range) = mixed_ty else {
            panic!("expected TableConst for mixed, got: {mixed_ty:?}");
        };
        assert!(
            crate::table_const_array_base(db, &mixed_range).is_none(),
            "mixed literal with named fields should not be treated as array-like"
        );

        // numeric_object: has [100] but also named field `name` → None
        let numeric_ty = infer_last_name_expr_type_in_file(&ws, file_id, "numeric_object");
        let LuaType::TableConst(numeric_range) = numeric_ty else {
            panic!("expected TableConst for numeric_object, got: {numeric_ty:?}");
        };
        assert!(
            crate::table_const_array_base(db, &numeric_range).is_none(),
            "numeric-key object with named fields should not be treated as array-like"
        );
    }

    #[test]
    fn test_reassigned_local_table_resolves_to_new_table_identity() {
        // General Lua semantics (no GMod): after `t = {}` the local must hold the
        // NEW table literal identity, not the initializer table identity. The two
        // table literals carry distinct fields; resolving the wrong identity is the
        // root cause of reused-variable collapse.
        let mut ws = VirtualWorkspace::new();
        let file_id = ws.def(
            r#"
            local t = {}
            function t.first() end
            t = {}
            function t.second() end
            "#,
        );

        let first_t = {
            let semantic_model = ws
                .analysis
                .compilation
                .get_semantic_model(file_id)
                .expect("Semantic model must exist");
            // The first `t` reference (after the initializer) should resolve to the
            // initializer table identity.
            let exprs = semantic_model
                .get_root()
                .descendants::<LuaNameExpr>()
                .filter(|expr| expr.get_name_text().as_deref() == Some("t"))
                .collect::<Vec<_>>();
            // exprs: [t in `function t.first`, t in `t = {}` (lhs), t in `function t.second`]
            let first_use = exprs.first().expect("first t use").clone();
            let last_use = exprs.last().expect("last t use").clone();
            let first_ty = semantic_model
                .infer_expr(LuaExpr::NameExpr(first_use))
                .unwrap_or(LuaType::Unknown);
            let last_ty = semantic_model
                .infer_expr(LuaExpr::NameExpr(last_use))
                .unwrap_or(LuaType::Unknown);
            (first_ty, last_ty)
        };

        let (LuaType::TableConst(first_range), LuaType::TableConst(last_range)) = first_t else {
            panic!("expected both t uses to be table consts, got: {first_t:?}");
        };

        // The bug: both resolve to the SAME (initializer) table identity.
        // Correct Lua: the use after reassignment must be a DISTINCT table identity.
        assert!(
            first_range != last_range,
            "reassigned local `t` collapsed to the initializer table identity \
             (first={first_range:?}, last={last_range:?}); each region must keep its own table"
        );
    }
}
