""" Test that evaluating expressions from within C++ lambdas works
    Particularly, we test the case of capturing "this" and
    using members of the captured object in expression evaluation
    while we're on a breakpoint inside a lambda.
"""


import lldb
from lldbsuite.test.lldbtest import *


class ExprInsideLambdaTestCase(TestBase):
    def expectExprError(self, expr: str, expected: str):
        frame = self.thread.GetFrameAtIndex(0)
        value = frame.EvaluateExpression(expr)
        errmsg = value.GetError().GetCString()
        self.assertIn(expected, errmsg)

    def test_expr_inside_lambda(self):
        """Test that lldb evaluating expressions inside lambda expressions works correctly."""
        self.build()
        (target, process, self.thread, bkpt) = lldbutil.run_to_source_breakpoint(
            self, "break here", lldb.SBFileSpec("main.cpp")
        )

        # Inside 'Foo::method'

        # Check access to captured 'this'
        self.expect_expr("class_var", result_type="int", result_value="109")
        self.expect_expr("this->class_var", result_type="int", result_value="109")

        # Check that captured shadowed variables take preference over the
        # corresponding member variable
        self.expect_expr("shadowed", result_type="int", result_value="5")
        self.expect_expr("this->shadowed", result_type="int", result_value="-137")

        # Check access to local captures
        self.expect_expr("local_var", result_type="int", result_value="137")
        self.expect_expr("*class_ptr", result_type="int", result_value="137")

        # Check access to base class variables
        self.expect_expr("base_var", result_type="int", result_value="14")
        self.expect_expr("base_base_var", result_type="int", result_value="11")

        # Check access to global variable
        self.expect_expr("global_var", result_type="int", result_value="-5")

        # Check access to multiple captures/member variables
        self.expect_expr(
            "(shadowed + this->shadowed) * (base_base_var + local_var - class_var)",
            result_type="int",
            result_value="-5148",
        )

        # Check base-class function call
        self.expect_expr("baz_virt()", result_type="int", result_value="2")
        self.expect_expr("base_var", result_type="int", result_value="14")
        self.expect_expr("this->shadowed", result_type="int", result_value="-1")

        # 'p this' should yield 'struct Foo*'
        frame = self.thread.GetFrameAtIndex(0)
        outer_class_addr = frame.GetValueForVariablePath("this->this")
        self.expect_expr("this", result_value=outer_class_addr.GetValue())

        lldbutil.continue_to_breakpoint(process, bkpt)

        # Inside 'nested_lambda'

        # Check access to captured 'this'. Should still be 'struct Foo*'
        self.expect_expr("class_var", result_type="int", result_value="109")
        self.expect_expr("global_var", result_type="int", result_value="-5")
        self.expect_expr("this", result_value=outer_class_addr.GetValue())

        # Check access to captures
        self.expect_expr("lambda_local_var", result_type="int", result_value="5")
        self.expect_expr("local_var", result_type="int", result_value="137")

        # Check access to variable in previous frame which we didn't capture
        self.expectExprError("local_var_copy", "use of undeclared identifier")

        lldbutil.continue_to_breakpoint(process, bkpt)

        # By-ref mutates source variable
        self.expect_expr("lambda_local_var", result_type="int", result_value="0")

        # By-value doesn't mutate source variable
        self.expect_expr("local_var_copy", result_type="int", result_value="136")
        self.expect_expr("local_var", result_type="int", result_value="137")

        lldbutil.continue_to_breakpoint(process, bkpt)

        # Inside 'LocalLambdaClass::inner_method'

        # Check access to captured 'this'
        self.expect_expr("lambda_class_local", result_type="int", result_value="-12345")
        self.expect_expr(
            "this->lambda_class_local", result_type="int", result_value="-12345"
        )
        self.expect_expr("outer_ptr->class_var", result_type="int", result_value="109")

        # 'p this' should yield 'struct LocalLambdaClass*'
        frame = self.thread.GetFrameAtIndex(0)
        local_class_addr = frame.GetValueForVariablePath("this->this")
        self.assertNotEqual(local_class_addr, outer_class_addr)
        self.expect_expr("this", result_value=local_class_addr.GetValue())

        # Can still access global variable
        self.expect_expr("global_var", result_type="int", result_value="-5")

        # Check access to outer top-level structure's members
        self.expectExprError(
            "class_var",
            ("use of non-static data member" " 'class_var' of 'Foo' from nested type"),
        )

        self.expectExprError(
            "base_var", ("use of non-static data member" " 'base_var'")
        )

        self.expectExprError(
            "local_var",
            (
                "use of non-static data member 'local_var'"
                " of '(unnamed class)' from nested type 'LocalLambdaClass'"
            ),
        )

        # Inside non_capturing_method
        lldbutil.continue_to_breakpoint(process, bkpt)
        self.expect_expr("local", result_type="int", result_value="5")
        self.expect_expr("local2", result_type="int", result_value="10")
        self.expect_expr("local2 * local", result_type="int", result_value="50")

        self.expectExprError(
            "class_var",
            ("use of non-static data member" " 'class_var' of 'Foo' from nested type"),
        )
