Skip to content

⚡️ Speed up function _get_last_two_names by 126% in PR #1861 (codeflash/optimize-pr1860-2026-03-18T08.05.54) - #1862

Closed
codeflash-ai[bot] wants to merge 1 commit into
codeflash/optimize-pr1860-2026-03-18T08.05.54from
codeflash/optimize-pr1861-2026-03-18T08.19.42
Closed

⚡️ Speed up function _get_last_two_names by 126% in PR #1861 (codeflash/optimize-pr1860-2026-03-18T08.05.54)#1862
codeflash-ai[bot] wants to merge 1 commit into
codeflash/optimize-pr1860-2026-03-18T08.05.54from
codeflash/optimize-pr1861-2026-03-18T08.19.42

Conversation

@codeflash-ai

@codeflash-ai codeflash-ai Bot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

⚡️ This pull request contains optimizations for PR #1861

If you approve this dependent PR, these changes will be merged into the original PR branch codeflash/optimize-pr1860-2026-03-18T08.05.54.

This PR will be automatically closed if the original PR is merged.


📄 126% (1.26x) speedup for _get_last_two_names in codeflash/languages/python/context/code_context_extractor.py

⏱️ Runtime : 148 microseconds 65.4 microseconds (best of 145 runs)

📝 Explanation and details

The optimized code returns immediately when two attributes are collected (line 14-15), rather than continuing to traverse the entire attribute chain and performing cleanup checks at the end. This short-circuits deep attribute chains (e.g., a.b.c.d.e.f.g.h) after seeing just g and h, eliminating ~85% of loop iterations in the worst case. Line profiler confirms the while-loop hit count dropped from 1,135 to 172 (85% reduction), directly causing the 126% speedup. The dead code path checking len(attrs_rev) >= 2 after the loop was also removed since early return makes it unreachable.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 76 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Click to see Generated Regression Tests
import ast  # used to build AST nodes for the function under test

# import the function under test from the provided module path
from codeflash.languages.python.context.code_context_extractor import _get_last_two_names


def test_none_input_returns_none_none():
    # Passing None should directly return (None, None) per the early check.
    assert _get_last_two_names(None) == (None, None)  # 370ns -> 371ns (0.270% slower)


def test_single_name_returns_none_none():
    # A lone Name node (e.g. "foo") has fewer than 2 parts -> (None, None).
    node = ast.parse("foo", mode="eval").body  # body is an ast.Name here
    assert isinstance(node, ast.Name)  # 1.08μs -> 1.11μs (2.70% slower)
    assert _get_last_two_names(node) == (None, None)


def test_two_part_attribute_returns_base_and_attr():
    # For "a.b" we expect the base name and the single attribute -> ("a", "b").
    node = ast.parse("a.b", mode="eval").body  # ast.Attribute chain with base Name "a" and attr "b"
    assert isinstance(node, ast.Attribute)  # 1.49μs -> 1.42μs (4.92% faster)
    assert _get_last_two_names(node) == ("a", "b")


def test_long_attribute_chain_returns_last_two():
    # For "a.b.c.d" there are multiple attributes; expect the last two attributes ("c", "d").
    node = ast.parse("a.b.c.d", mode="eval").body
    # Ensure AST shape before asserting behavior
    assert isinstance(node, ast.Attribute)  # 1.76μs -> 1.14μs (54.4% faster)
    assert _get_last_two_names(node) == ("c", "d")


def test_attribute_with_call_in_middle():
    # "a.b().c" -> the call is ignored for name-collection; last two names are ("b", "c").
    node = ast.parse("a.b().c", mode="eval").body
    assert isinstance(node, ast.Attribute)  # 1.76μs -> 1.35μs (30.5% faster)
    assert _get_last_two_names(node) == ("b", "c")


def test_base_not_name_but_two_attrs_returns_second_and_first_attr():
    # For "(1).b.c" the base is a Constant (not a Name) but there are two attributes -> ("b", "c").
    node = ast.parse("(1).b.c", mode="eval").body
    assert isinstance(node, ast.Attribute)  # 1.52μs -> 1.08μs (40.8% faster)
    assert _get_last_two_names(node) == ("b", "c")


def test_single_attribute_on_non_name_returns_none_none():
    # For "(1).b" base is not a Name and only one attribute -> fewer than 2 total parts -> (None, None).
    node = ast.parse("(1).b", mode="eval").body
    assert isinstance(node, ast.Attribute)  # 1.21μs -> 1.38μs (12.2% slower)
    assert _get_last_two_names(node) == (None, None)


def test_subscript_base_with_two_attrs():
    # For "x[0].y.z" the base is a Subscript (not a Name) but there are two attributes -> ("y", "z").
    node = ast.parse("x[0].y.z", mode="eval").body
    assert isinstance(node, ast.Attribute)  # 1.50μs -> 1.16μs (29.3% faster)
    assert _get_last_two_names(node) == ("y", "z")


def test_complex_with_nested_calls_and_attributes():
    # Complex: "a.b(1).c.d" -> call inside doesn't change the attribute extraction; expect ("c", "d").
    node = ast.parse("a.b(1).c.d", mode="eval").body
    assert isinstance(node, ast.Attribute)  # 1.92μs -> 1.13μs (69.9% faster)
    assert _get_last_two_names(node) == ("c", "d")


def test_large_chain_1000_returns_last_two():
    # Test with single large chains to verify the function handles deep nesting correctly.
    # Diverse chains ensure realistic production-like patterns without artificial repetition.

    # First large chain: 50 attributes with numeric suffix
    base = "module_name"
    attrs = [f"submodule_{i}" for i in range(50)]
    expr = base + "".join(f".{a}" for a in attrs)
    node = ast.parse(expr, mode="eval").body
    assert isinstance(node, ast.Attribute)
    expected = (attrs[-2], attrs[-1])
    assert _get_last_two_names(node) == expected

    # Second large chain: 48 attributes with alphabetic suffix (different naming)
    base2 = "library"
    attrs2 = [f"component_{chr(97 + (i % 26))}" for i in range(48)]
    expr2 = base2 + "".join(f".{a}" for a in attrs2)
    node2 = ast.parse(expr2, mode="eval").body
    assert isinstance(node2, ast.Attribute)
    expected2 = (attrs2[-2], attrs2[-1])
    assert _get_last_two_names(node2) == expected2

    # Third large chain: 100 attributes (deeper than first two)
    base3 = "deep"
    attrs3 = [f"l{j}" for j in range(100)]
    expr3 = base3 + "".join(f".{a}" for a in attrs3)
    node3 = ast.parse(expr3, mode="eval").body
    assert isinstance(node3, ast.Attribute)
    expected3 = (attrs3[-2], attrs3[-1])
    assert _get_last_two_names(node3) == expected3

    # Fourth chain: alternating pattern to vary structure
    base4 = "system"
    attrs4 = [f"{'odd' if i % 2 else 'even'}_{i}" for i in range(60)]
    expr4 = base4 + "".join(f".{a}" for a in attrs4)
    node4 = ast.parse(expr4, mode="eval").body
    assert isinstance(node4, ast.Attribute)
    expected4 = (attrs4[-2], attrs4[-1])
    assert _get_last_two_names(node4) == expected4

    # Fifth chain: numeric prefixes instead of suffixes
    base5 = "pkg"
    attrs5 = [f"{i}_attr" for i in range(55)]
    expr5 = base5 + "".join(f".{a}" for a in attrs5)
    node5 = ast.parse(expr5, mode="eval").body
    assert isinstance(node5, ast.Attribute)
    expected5 = (attrs5[-2], attrs5[-1])
    assert _get_last_two_names(node5) == expected5


def test_many_chains_varying_length_iterations():
    # Test diverse attribute chain patterns with different lengths and base types.
    # Each test case is unique (no repetition) to ensure real-world production patterns.

    unique_test_cases = [
        ("pkg", 0, "case_pkg_0"),
        ("lib_a", 1, "case_lib_a_1"),
        ("mod_b", 2, "case_mod_b_2"),
        ("sys_c", 3, "case_sys_c_3"),
        ("tool_d", 4, "case_tool_d_4"),
        ("framework_e", 5, "case_framework_e_5"),
        ("util_f", 2, "case_util_f_2"),
        ("helper_g", 3, "case_helper_g_3"),
        ("core_h", 4, "case_core_h_4"),
        ("ext_i", 1, "case_ext_i_1"),
        ("plugin_j", 2, "case_plugin_j_2"),
        ("service_k", 3, "case_service_k_3"),
        ("data_l", 5, "case_data_l_5"),
        ("handler_m", 2, "case_handler_m_2"),
        ("manager_n", 4, "case_manager_n_4"),
        ("config_o", 3, "case_config_o_3"),
        ("cache_p", 1, "case_cache_p_1"),
        ("storage_q", 2, "case_storage_q_2"),
        ("network_r", 3, "case_network_r_3"),
        ("security_s", 4, "case_security_s_4"),
    ]

    for base, length, label in unique_test_cases:
        attrs = [f"attr_{label}_{j}" for j in range(length)]
        expr = base + "".join(f".{a}" for a in attrs)
        node = ast.parse(expr, mode="eval").body
        if len(attrs) + 1 < 2:
            expected = (None, None)
        elif len(attrs) >= 2:
            expected = (attrs[-2], attrs[-1])
        else:
            expected = (base, attrs[0])
        assert _get_last_two_names(node) == expected  # 17.8μs -> 11.4μs (56.6% faster)

    # Additional tests with non-Name bases using diverse expressions
    non_name_cases = [
        ("(42).a.b", ("a", "b")),
        ("[1, 2].x.y", ("x", "y")),
        ("func().m.n", ("m", "n")),
        ("(obj or default).p.q", ("p", "q")),
        ("{'key': 'value'}.attr1.attr2", ("attr1", "attr2")),
        ("(a if b else c).foo.bar", ("foo", "bar")),
        ("(lambda x: x).result.value", ("result", "value")),
    ]
    for expr, expected in non_name_cases:
        node = ast.parse(expr, mode="eval").body
        assert _get_last_two_names(node) == expected  # 5.68μs -> 3.32μs (70.9% faster)

    # Test edge cases with varying structures and bases
    edge_cases = [
        ("simple_base.a.b.c.d.e", ("d", "e")),
        ("short.xy", ("short", "xy")),
        ("mid.x.y.z", ("y", "z")),
        ("another_base.single_attr", (None, None)),
    ]
    for expr, expected in edge_cases:
        node = ast.parse(expr, mode="eval").body
        assert _get_last_two_names(node) == expected  # 3.28μs -> 2.25μs (45.4% faster)
import ast

# imports
from codeflash.languages.python.context.code_context_extractor import _get_last_two_names


def test_none_input():
    """Test that None input returns (None, None)."""
    result = _get_last_two_names(None)  # 381ns -> 351ns (8.55% faster)
    assert result == (None, None)


def test_simple_attribute_two_levels():
    """Test basic attribute access with exactly two levels: obj.attr."""
    # Create AST for: obj.attr
    node = ast.Attribute(value=ast.Name(id="obj", ctx=ast.Load()), attr="attr", ctx=ast.Load())
    result = _get_last_two_names(node)  # 1.52μs -> 1.38μs (10.1% faster)
    # Should return (base_name, last_attr) = ('obj', 'attr')
    assert result == ("obj", "attr")


def test_attribute_three_levels():
    """Test attribute access with three levels: a.b.c."""
    # Create AST for: a.b.c
    node = ast.Attribute(
        value=ast.Attribute(value=ast.Name(id="a", ctx=ast.Load()), attr="b", ctx=ast.Load()), attr="c", ctx=ast.Load()
    )
    result = _get_last_two_names(node)  # 1.64μs -> 1.08μs (51.9% faster)
    # Should return the last two: (b, c)
    assert result == ("b", "c")


def test_attribute_four_levels():
    """Test attribute access with four levels: a.b.c.d."""
    # Create AST for: a.b.c.d
    node = ast.Attribute(
        value=ast.Attribute(
            value=ast.Attribute(value=ast.Name(id="a", ctx=ast.Load()), attr="b", ctx=ast.Load()),
            attr="c",
            ctx=ast.Load(),
        ),
        attr="d",
        ctx=ast.Load(),
    )
    result = _get_last_two_names(node)  # 1.66μs -> 1.03μs (61.2% faster)
    # Should return the last two: (c, d)
    assert result == ("c", "d")


def test_simple_name_only():
    """Test single Name node returns (None, None) since we need at least 2 parts."""
    node = ast.Name(id="x", ctx=ast.Load())
    result = _get_last_two_names(node)  # 1.07μs -> 1.05μs (1.90% faster)
    assert result == (None, None)


def test_call_on_attribute():
    """Test function call on attribute: obj.method()."""
    # Create AST for: obj.method()
    node = ast.Call(
        func=ast.Attribute(value=ast.Name(id="obj", ctx=ast.Load()), attr="method", ctx=ast.Load()),
        args=[],
        keywords=[],
    )
    result = _get_last_two_names(node)  # 1.61μs -> 1.44μs (11.8% faster)
    # Should unwrap the call and return ('obj', 'method')
    assert result == ("obj", "method")


def test_call_on_nested_attribute():
    """Test function call on nested attribute: a.b.c()."""
    # Create AST for: a.b.c()
    node = ast.Call(
        func=ast.Attribute(
            value=ast.Attribute(value=ast.Name(id="a", ctx=ast.Load()), attr="b", ctx=ast.Load()),
            attr="c",
            ctx=ast.Load(),
        ),
        args=[],
        keywords=[],
    )
    result = _get_last_two_names(node)  # 1.68μs -> 1.24μs (35.5% faster)
    # Should return the last two: ('b', 'c')
    assert result == ("b", "c")


def test_nested_call_on_attribute():
    """Test nested function calls: obj.method()() - call on call on attribute."""
    # Create AST for: obj.method()()
    node = ast.Call(
        func=ast.Call(
            func=ast.Attribute(value=ast.Name(id="obj", ctx=ast.Load()), attr="method", ctx=ast.Load()),
            args=[],
            keywords=[],
        ),
        args=[],
        keywords=[],
    )
    result = _get_last_two_names(node)  # 1.67μs -> 1.57μs (6.36% faster)
    # Should unwrap the nested calls and return ('obj', 'method')
    assert result == ("obj", "method")


def test_attribute_on_call():
    """Test attribute access on a call result: obj().attr."""
    # Create AST for: obj().attr
    node = ast.Attribute(
        value=ast.Call(func=ast.Name(id="obj", ctx=ast.Load()), args=[], keywords=[]), attr="attr", ctx=ast.Load()
    )
    result = _get_last_two_names(node)  # 1.42μs -> 1.45μs (2.06% slower)
    # Should unwrap the call and return (obj, attr)
    assert result == ("obj", "attr")


def test_call_without_name_base():
    """Test call on non-Name, non-Attribute node (e.g., on a literal)."""
    # Create AST for: (5)() - calling an integer literal (invalid, but test robustness)
    node = ast.Call(func=ast.Constant(value=5), args=[], keywords=[])
    result = _get_last_two_names(node)  # 1.16μs -> 1.13μs (2.56% faster)
    # base_name will be None, so total_parts will be < 2
    assert result == (None, None)


def test_attribute_with_empty_chain():
    """Test that attribute with only one level returns (None, None)."""
    # Create AST for: obj.x (only one level)
    node = ast.Attribute(value=ast.Name(id="obj", ctx=ast.Load()), attr="x", ctx=ast.Load())
    # This has base_name='obj' and one attr 'x', so total_parts=2, should return ('obj', 'x')
    result = _get_last_two_names(node)  # 1.38μs -> 1.32μs (4.54% faster)
    assert result == ("obj", "x")


def test_constant_node():
    """Test that a Constant node (literal) returns (None, None)."""
    node = ast.Constant(value="string")
    result = _get_last_two_names(node)  # 902ns -> 922ns (2.17% slower)
    assert result == (None, None)


def test_binop_node():
    """Test that a BinOp node returns (None, None)."""
    # Create AST for: 1 + 2
    node = ast.BinOp(left=ast.Constant(value=1), op=ast.Add(), right=ast.Constant(value=2))
    result = _get_last_two_names(node)  # 882ns -> 862ns (2.32% faster)
    assert result == (None, None)


def test_list_node():
    """Test that a List node returns (None, None)."""
    node = ast.List(elts=[], ctx=ast.Load())
    result = _get_last_two_names(node)  # 902ns -> 922ns (2.17% slower)
    assert result == (None, None)


def test_very_deep_attribute_chain():
    """Test deeply nested attribute access with many levels."""
    # Create AST for: a.b.c.d.e.f.g.h (8 levels)
    node = ast.Name(id="a", ctx=ast.Load())
    for attr_name in ["b", "c", "d", "e", "f", "g", "h"]:
        node = ast.Attribute(value=node, attr=attr_name, ctx=ast.Load())

    result = _get_last_two_names(node)  # 2.21μs -> 1.13μs (95.6% faster)
    # Should return the last two attributes
    assert result == ("g", "h")


def test_attribute_chain_with_multiple_calls():
    """Test attribute chain with calls at multiple levels: obj().method().attr."""
    # Create AST for: obj().method().attr
    node = ast.Attribute(
        value=ast.Call(
            func=ast.Attribute(
                value=ast.Call(func=ast.Name(id="obj", ctx=ast.Load()), args=[], keywords=[]),
                attr="method",
                ctx=ast.Load(),
            ),
            args=[],
            keywords=[],
        ),
        attr="attr",
        ctx=ast.Load(),
    )
    result = _get_last_two_names(node)  # 1.82μs -> 1.29μs (41.1% faster)
    # Should unwrap all calls and return (method, attr)
    assert result == ("method", "attr")


def test_call_with_arguments():
    """Test that function calls with arguments are handled correctly."""
    # Create AST for: obj.method(1, 2, x=3)
    node = ast.Call(
        func=ast.Attribute(value=ast.Name(id="obj", ctx=ast.Load()), attr="method", ctx=ast.Load()),
        args=[ast.Constant(value=1), ast.Constant(value=2)],
        keywords=[ast.keyword(arg="x", value=ast.Constant(value=3))],
    )
    result = _get_last_two_names(node)  # 1.51μs -> 1.49μs (1.41% faster)
    # Arguments should not affect the result
    assert result == ("obj", "method")


def test_attribute_names_with_underscores():
    """Test attribute names that contain underscores."""
    # Create AST for: obj._private_method
    node = ast.Attribute(value=ast.Name(id="obj", ctx=ast.Load()), attr="_private_method", ctx=ast.Load())
    result = _get_last_two_names(node)  # 1.34μs -> 1.30μs (3.07% faster)
    assert result == ("obj", "_private_method")


def test_attribute_names_with_numbers():
    """Test attribute names that contain numbers."""
    # Create AST for: obj.method123
    node = ast.Attribute(value=ast.Name(id="obj", ctx=ast.Load()), attr="method123", ctx=ast.Load())
    result = _get_last_two_names(node)  # 1.33μs -> 1.23μs (8.12% faster)
    assert result == ("obj", "method123")


def test_name_with_underscores():
    """Test Name node with underscores."""
    node = ast.Name(id="_private", ctx=ast.Load())
    result = _get_last_two_names(node)  # 971ns -> 992ns (2.12% slower)
    # Single name, so should return (None, None)
    assert result == (None, None)


def test_name_with_numbers():
    """Test Name node with numbers (variable names like x123)."""
    node = ast.Name(id="x123", ctx=ast.Load())
    result = _get_last_two_names(node)  # 962ns -> 952ns (1.05% faster)
    # Single name, so should return (None, None)
    assert result == (None, None)


def test_complex_call_chain():
    """Test complex chain: obj.a().b().c."""
    # Create AST for: obj.a().b().c
    node = ast.Attribute(
        value=ast.Call(
            func=ast.Attribute(
                value=ast.Call(
                    func=ast.Attribute(value=ast.Name(id="obj", ctx=ast.Load()), attr="a", ctx=ast.Load()),
                    args=[],
                    keywords=[],
                ),
                attr="b",
                ctx=ast.Load(),
            ),
            args=[],
            keywords=[],
        ),
        attr="c",
        ctx=ast.Load(),
    )
    result = _get_last_two_names(node)  # 1.90μs -> 1.29μs (47.3% faster)
    # Should return (b, c)
    assert result == ("b", "c")


def test_extremely_deep_attribute_chain():
    """Test with a very deep attribute chain (100 levels) to verify scalability."""
    # Create AST for: a.x0.x1.x2...x99 (100 attribute accesses)
    node = ast.Name(id="a", ctx=ast.Load())
    for i in range(100):
        node = ast.Attribute(value=node, attr=f"x{i}", ctx=ast.Load())

    result = _get_last_two_names(node)  # 12.0μs -> 1.14μs (950% faster)
    # Should return the last two attributes
    assert result == ("x98", "x99")


def test_extremely_deep_call_chain():
    """Test with a deep call chain with attributes to verify scalability and correctness."""
    # Create AST for: a.m().n().o().p().q (50 call levels but with meaningful attributes)
    node = ast.Name(id="a", ctx=ast.Load())
    attrs = ["m", "n", "o", "p", "q", "r", "s", "t", "u", "v"]
    for i, attr in enumerate(attrs):
        node = ast.Attribute(value=node, attr=attr, ctx=ast.Load())
        if i < len(attrs) - 1:
            node = ast.Call(func=node, args=[], keywords=[])

    result = _get_last_two_names(node)  # 3.41μs -> 1.24μs (174% faster)
    # Should return the last two attributes: (t, u)
    assert result == ("t", "u")


def test_mixed_deep_chain():
    """Test with a deep chain of mixed calls and attributes (alternating)."""
    # Create AST for: a.m0().m1().m2().m3 (alternating attributes and calls)
    node = ast.Name(id="a", ctx=ast.Load())
    for i in range(50):
        node = ast.Attribute(value=node, attr=f"m{i}", ctx=ast.Load())
        if i < 49:  # Don't add a call after the last attribute
            node = ast.Call(func=node, args=[], keywords=[])

    result = _get_last_two_names(node)  # 11.0μs -> 1.28μs (756% faster)
    # Should return the last two attributes
    assert result == ("m48", "m49")


def test_attribute_chain_with_many_attributes():
    """Test attribute chain with 200 sequential attributes."""
    # Create AST for: a.b0.b1.b2...b199
    node = ast.Name(id="a", ctx=ast.Load())
    for i in range(200):
        node = ast.Attribute(value=node, attr=f"b{i}", ctx=ast.Load())

    result = _get_last_two_names(node)  # 20.9μs -> 1.02μs (1944% faster)
    # Should return the last two: (b198, b199)
    assert result == ("b198", "b199")


def test_performance_many_calls_on_single_attribute():
    """Test individual cases of calls on attribute chains to verify realistic performance."""
    # Case 1: obj.method1 with 5 calls - obj.method1()()()()()
    node1 = ast.Attribute(value=ast.Name(id="obj", ctx=ast.Load()), attr="method1", ctx=ast.Load())
    for _ in range(5):
        node1 = ast.Call(func=node1, args=[], keywords=[])
    result1 = _get_last_two_names(node1)  # 2.02μs -> 1.93μs (4.65% faster)
    assert result1 == ("obj", "method1")

    # Case 2: service.api with 10 calls - service.api()()()()()()()()()()
    node2 = ast.Attribute(value=ast.Name(id="service", ctx=ast.Load()), attr="api", ctx=ast.Load())
    for _ in range(10):
        node2 = ast.Call(func=node2, args=[], keywords=[])
    result2 = _get_last_two_names(node2)  # 1.71μs -> 1.67μs (2.39% faster)
    assert result2 == ("service", "api")

    # Case 3: factory.create with 3 calls - factory.create()()()
    node3 = ast.Attribute(value=ast.Name(id="factory", ctx=ast.Load()), attr="create", ctx=ast.Load())
    for _ in range(3):
        node3 = ast.Call(func=node3, args=[], keywords=[])
    result3 = _get_last_two_names(node3)  # 942ns -> 921ns (2.28% faster)
    assert result3 == ("factory", "create")


def test_large_balanced_tree():
    """Test a large balanced attribute tree to ensure consistent behavior."""
    # Create a moderately deep tree: a.x0.x1.x2...x74
    node = ast.Name(id="a", ctx=ast.Load())
    for i in range(75):
        node = ast.Attribute(value=node, attr=f"x{i}", ctx=ast.Load())

    result = _get_last_two_names(node)  # 8.65μs -> 1.09μs (692% faster)
    # Should always return the last two attributes
    assert result == ("x73", "x74")


def test_alternating_names_and_attributes():
    """Test alternating between different name bases (edge case)."""
    # Create one large chain and verify correctness
    node = ast.Name(id="base", ctx=ast.Load())
    for i in range(150):
        node = ast.Attribute(value=node, attr=f"attr_{i}", ctx=ast.Load())

    result = _get_last_two_names(node)  # 16.1μs -> 1.08μs (1389% faster)
    # Should return last two attributes
    assert result == ("attr_148", "attr_149")


def test_call_chain_preserves_structure():
    """Test that call unwrapping preserves the underlying structure."""
    # Create: a.x.y()()().z
    node = ast.Attribute(
        value=ast.Call(
            func=ast.Call(
                func=ast.Call(
                    func=ast.Attribute(
                        value=ast.Attribute(value=ast.Name(id="a", ctx=ast.Load()), attr="x", ctx=ast.Load()),
                        attr="y",
                        ctx=ast.Load(),
                    ),
                    args=[],
                    keywords=[],
                ),
                args=[],
                keywords=[],
            ),
            args=[],
            keywords=[],
        ),
        attr="z",
        ctx=ast.Load(),
    )
    result = _get_last_two_names(node)  # 2.00μs -> 1.47μs (36.0% faster)
    # Should return (y, z)
    assert result == ("y", "z")

To edit these changes git checkout codeflash/optimize-pr1861-2026-03-18T08.19.42 and push.

Codeflash Static Badge

The optimized code returns immediately when two attributes are collected (line 14-15), rather than continuing to traverse the entire attribute chain and performing cleanup checks at the end. This short-circuits deep attribute chains (e.g., `a.b.c.d.e.f.g.h`) after seeing just `g` and `h`, eliminating ~85% of loop iterations in the worst case. Line profiler confirms the while-loop hit count dropped from 1,135 to 172 (85% reduction), directly causing the 126% speedup. The dead code path checking `len(attrs_rev) >= 2` after the loop was also removed since early return makes it unreachable.
@codeflash-ai codeflash-ai Bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Mar 18, 2026
@claude

claude Bot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @codeflash-ai[bot]'s task in 16s —— View job


PR Review Summary

Prek Checks

ruff check and ruff format both pass on the changed file. No issues to fix.

Code Review

Verdict: LGTM ✅

The optimization is correct and clean. The change adds an early return in _get_last_two_names (codeflash/languages/python/context/code_context_extractor.py:1821-1822) when 2 attributes have been collected, short-circuiting traversal of the rest of the attribute chain.

Logic correctness:

  • For a.b.c.d: collects attrs_rev = ['d'], then ['d', 'c'], early return ('c', 'd')
  • For a.b: collects attrs_rev = ['b'], falls through to base_name path, returns ('a', 'b')
  • For a (Name only): attrs_rev = [], falls through, total_parts < 2, returns (None, None)

The removed dead code path (if len(attrs_rev) >= 2 after the while loop, lines 1835-1836 in the original) was unreachable with the new early return — correctly removed.

The 126% speedup claim is well-supported: the line profiler shows a 85% reduction in while-loop iterations for deep attribute chains (e.g., a.b.c...z), and all 76 generated regression tests pass with 100% coverage.

Duplicate Detection

No duplicates detected. _get_last_two_names is defined in exactly one location.

Test Coverage

  • 76 generated regression tests pass, 100% coverage on the changed function
  • No existing unit tests for this function (noted in the PR)

Other Optimization PRs Processed


Last updated: 2026-03-18

@claude
claude Bot deleted the branch codeflash/optimize-pr1860-2026-03-18T08.05.54 March 18, 2026 08:30
@claude claude Bot closed this Mar 18, 2026
@codeflash-ai
codeflash-ai Bot deleted the codeflash/optimize-pr1861-2026-03-18T08.19.42 branch March 18, 2026 08:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants