Skip to content

⚡️ Speed up function _get_attrs_config by 17% in PR #1860 (fix/attrs-init-instrumentation) - #1861

Closed
codeflash-ai[bot] wants to merge 2 commits into
fix/attrs-init-instrumentationfrom
codeflash/optimize-pr1860-2026-03-18T08.05.54
Closed

⚡️ Speed up function _get_attrs_config by 17% in PR #1860 (fix/attrs-init-instrumentation)#1861
codeflash-ai[bot] wants to merge 2 commits into
fix/attrs-init-instrumentationfrom
codeflash/optimize-pr1860-2026-03-18T08.05.54

Conversation

@codeflash-ai

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

Copy link
Copy Markdown
Contributor

⚡️ This pull request contains optimizations for PR #1860

If you approve this dependent PR, these changes will be merged into the original PR branch fix/attrs-init-instrumentation.

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


📄 17% (0.17x) speedup for _get_attrs_config in codeflash/languages/python/context/code_context_extractor.py

⏱️ Runtime : 2.26 milliseconds 1.93 milliseconds (best of 189 runs)

⚡️ This change will improve the performance of the following benchmarks:

Benchmark File :: Function Original Runtime Expected New Runtime Speedup
tests.benchmarks.test_benchmark_code_extract_code_context::test_benchmark_extract 15.4 seconds 15.4 seconds 0.00%

🔻 This change will degrade the performance of the following benchmarks:

{benchmark_info_degraded}

📝 Explanation and details

The hot loop that checks decorators now extracts only the last two dotted-name segments (the namespace and decorator name) instead of building the full dotted string and splitting it, reducing profiler-reported time in _get_attrs_config from ~21 ms to ~9.7 ms (54% faster). Additionally, the boolean-literal check was inlined to eliminate ~6 ms of function-call overhead in _bool_literal. These changes together yield the 17% end-to-end runtime improvement with no semantic changes to behavior.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 1266 Passed
⏪ Replay Tests 1 Passed
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Click to see Generated Regression Tests
import ast

# add other imports as needed
from codeflash.languages.python.context.code_context_extractor import _get_attrs_config

# function to test
# The actual function is imported above; tests follow.


def _get_class_node_from_source(source: str) -> ast.ClassDef:
    """Helper to parse Python source and return the first ClassDef node found.
    This keeps tests concise while using real AST nodes constructed by the parser.
    """
    module = ast.parse(source)
    # Find the first ClassDef in the module body
    for node in module.body:
        if isinstance(node, ast.ClassDef):
            return node
    raise AssertionError("No class definition found in provided source")


def test_simple_attribute_decorator_without_call_enables_init_by_default():
    # Parse a class decorated with @attr.define (no call/parentheses).
    src = """
@attr.define
class C:
    pass
"""
    class_node = _get_class_node_from_source(src)
    # The decorator matches the 'attr.define' pattern; no keywords -> init defaults to True.
    result = _get_attrs_config(class_node, {})  # 3.91μs -> 2.50μs (56.6% faster)
    assert result == (True, True, False)  # (is_attrs, init_enabled, kw_only)


def test_call_decorator_with_boolean_keywords_respected():
    # Parse a class decorated with explicit boolean keyword args.
    src = """
@attrs.define(init=False, kw_only=True)
class D:
    pass
"""
    class_node = _get_class_node_from_source(src)
    # init=False and kw_only=True should be extracted correctly.
    result = _get_attrs_config(class_node, {})  # 4.81μs -> 3.56μs (35.2% faster)
    assert result == (True, False, True)


def test_long_namespace_attribute_chain_matches_on_penultimate_segment():
    # A longer attribute chain like pkg.attr.frozen should still match because the
    # penultimate part is 'attr' and last part is 'frozen'.
    src = """
@pkg.attr.frozen(init=False)
class E:
    pass
"""
    class_node = _get_class_node_from_source(src)
    # Only init=False provided; kw_only defaults to False.
    result = _get_attrs_config(class_node, {})  # 4.46μs -> 2.96μs (50.4% faster)
    assert result == (True, False, False)


def test_non_boolean_keyword_values_are_ignored_and_defaults_preserved():
    # Keywords with non-boolean literal values (integers/strings) should be ignored.
    src = """
@attr.define(init=1, kw_only="yes")
class F:
    pass
"""
    class_node = _get_class_node_from_source(src)
    # Both keywords are non-boolean, so defaults remain: init True, kw_only False.
    result = _get_attrs_config(class_node, {})  # 4.36μs -> 2.98μs (46.4% faster)
    assert result == (True, True, False)


def test_first_matching_decorator_is_returned_even_if_later_decorators_differ():
    # When multiple matching decorators are present, the function should return
    # the configuration from the first matching decorator in the list.
    src = """
@attrs.define(init=False)
@attrs.define(init=True, kw_only=True)
class G:
    pass
"""
    class_node = _get_class_node_from_source(src)
    # The first decorator sets init=False; function returns immediately on first match.
    result = _get_attrs_config(class_node, {})  # 4.15μs -> 2.79μs (48.9% faster)
    assert result == (True, False, False)


def test_non_matching_decorators_result_in_false_all_flags():
    # Decorators that do not match the expected namespace/name combinations should be ignored.
    src = """
@define  # unqualified name, no namespace
@other.attr.something
class H:
    pass
"""
    class_node = _get_class_node_from_source(src)
    # None of the decorators match the required (attr|attrs).(<define|mutable|...>) pattern.
    result = _get_attrs_config(class_node, {})  # 4.32μs -> 2.81μs (53.9% faster)
    assert result == (False, False, False)


def test_decorator_expression_that_yields_no_name_is_ignored():
    # A decorator that is an expression with no retrievable dotted name (e.g., a lambda)
    # should be skipped; the function should return defaults when no matching decorator exists.
    src = """
@(lambda x: x)
class I:
    pass
"""
    class_node = _get_class_node_from_source(src)
    # The lambda decorator produces no dotted name, so it is ignored.
    result = _get_attrs_config(class_node, {})  # 1.31μs -> 1.47μs (10.9% slower)
    assert result == (False, False, False)


def test_unqualified_define_name_is_ignored_even_if_keyword_booleans_present():
    # @define(init=False) is unqualified (no attr/attrs namespace) and should not match.
    src = """
@define(init=False, kw_only=True)
class J:
    pass
"""
    class_node = _get_class_node_from_source(src)
    # Despite boolean keywords, the decorator name is not within the allowed namespaces.
    result = _get_attrs_config(class_node, {})  # 1.85μs -> 1.67μs (10.8% faster)
    assert result == (False, False, False)


def test_large_scale_many_classes_with_varied_decorators():
    # Test multiple diverse decorator patterns and class structures to ensure robust handling.
    test_cases = [
        ("@attr.define\nclass A:\n    pass", (True, True, False)),
        ("@attrs.mutable\nclass B:\n    pass", (True, True, False)),
        ("@attr.frozen\nclass C:\n    pass", (True, True, False)),
        ("@attrs.s\nclass D:\n    pass", (True, True, False)),
        ("@attr.attrs\nclass E:\n    pass", (True, True, False)),
        ("@attr.define(init=False)\nclass F:\n    pass", (True, False, False)),
        ("@attr.define(init=True, kw_only=False)\nclass G:\n    pass", (True, True, False)),
        ("@attrs.define(kw_only=True)\nclass H:\n    pass", (True, True, True)),
        ("@pkg.attr.frozen(init=False, kw_only=True)\nclass I:\n    pass", (True, False, True)),
        ("@x.y.z.attr.mutable\nclass J:\n    pass", (True, True, False)),
        ("@attr.define(init=True)\nclass K:\n    pass", (True, True, False)),
        ("@attrs.frozen(kw_only=False)\nclass L:\n    pass", (True, True, False)),
        ("@attr.s(init=False, kw_only=False)\nclass M:\n    pass", (True, False, False)),
        ("@define\nclass N:\n    pass", (False, False, False)),
        ("@some_decorator\nclass O:\n    pass", (False, False, False)),
        ("@attr.other_name\nclass P:\n    pass", (False, False, False)),
        ("@other.define\nclass Q:\n    pass", (False, False, False)),
        ("class R:\n    pass", (False, False, False)),
        ("@attr.define(init=False, kw_only=True)\nclass S:\n    pass", (True, False, True)),
        ("@attrs.mutable(init=True, kw_only=True)\nclass T:\n    pass", (True, True, True)),
        ("@attr.frozen(init=True, kw_only=False)\nclass U:\n    pass", (True, True, False)),
        ("@attrs.s(init=False, kw_only=True)\nclass V:\n    pass", (True, False, True)),
    ]

    for source_code, expected_result in test_cases:
        class_node = _get_class_node_from_source(source_code)
        result = _get_attrs_config(class_node, {})  # 38.8μs -> 29.5μs (31.4% faster)
        assert result == expected_result, f"Failed for: {source_code}\nExpected: {expected_result}, Got: {result}"
import ast

# imports
# function to test
from codeflash.languages.python.context.code_context_extractor import _get_attrs_config


class TestGetAttrsConfigBasic:
    """Basic tests — Verify fundamental functionality under normal conditions with typical inputs."""

    def test_no_decorators(self):
        """Test class with no decorators returns (False, False, False)."""
        # Create a simple class with no decorators
        code = """
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 491ns -> 511ns (3.91% slower)
        assert result == (False, False, False), "Class with no decorators should return (False, False, False)"

    def test_attrs_define_decorator_simple(self):
        """Test class with @attrs.define decorator."""
        code = """
@attrs.define
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 3.54μs -> 2.22μs (59.0% faster)
        assert result == (True, True, False), "Should recognize @attrs.define with default init=True, kw_only=False"

    def test_attr_s_decorator_simple(self):
        """Test class with @attr.s decorator."""
        code = """
@attr.s
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 3.44μs -> 2.18μs (57.3% faster)
        assert result == (True, True, False), "Should recognize @attr.s with default init=True, kw_only=False"

    def test_attrs_frozen_decorator(self):
        """Test class with @attrs.frozen decorator."""
        code = """
@attrs.frozen
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 3.42μs -> 2.09μs (63.2% faster)
        assert result == (True, True, False), "Should recognize @attrs.frozen"

    def test_attrs_mutable_decorator(self):
        """Test class with @attrs.mutable decorator."""
        code = """
@attrs.mutable
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 3.37μs -> 2.13μs (57.8% faster)
        assert result == (True, True, False), "Should recognize @attrs.mutable"

    def test_attrs_define_with_init_true(self):
        """Test @attrs.define(init=True) explicitly sets init_enabled to True."""
        code = """
@attrs.define(init=True)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.19μs -> 2.98μs (40.8% faster)
        assert result == (True, True, False), "Should recognize init=True parameter"

    def test_attrs_define_with_init_false(self):
        """Test @attrs.define(init=False) sets init_enabled to False."""
        code = """
@attrs.define(init=False)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.10μs -> 2.88μs (42.5% faster)
        assert result == (True, False, False), "Should recognize init=False parameter"

    def test_attrs_define_with_kw_only_true(self):
        """Test @attrs.define(kw_only=True) sets kw_only to True."""
        code = """
@attrs.define(kw_only=True)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.17μs -> 2.98μs (39.6% faster)
        assert result == (True, True, True), "Should recognize kw_only=True parameter"

    def test_attrs_define_with_kw_only_false(self):
        """Test @attrs.define(kw_only=False) explicitly sets kw_only to False."""
        code = """
@attrs.define(kw_only=False)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.11μs -> 2.83μs (44.9% faster)
        assert result == (True, True, False), "Should recognize kw_only=False parameter"

    def test_attrs_define_with_both_params(self):
        """Test @attrs.define with both init and kw_only parameters."""
        code = """
@attrs.define(init=False, kw_only=True)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.46μs -> 3.18μs (40.4% faster)
        assert result == (True, False, True), "Should recognize both init=False and kw_only=True"

    def test_non_attrs_decorator_ignored(self):
        """Test that non-attrs decorators are ignored."""
        code = """
@dataclass
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 1.69μs -> 1.48μs (14.2% faster)
        assert result == (False, False, False), "Non-attrs decorators should be ignored"

    def test_multiple_decorators_attrs_first(self):
        """Test class with multiple decorators where attrs decorator is first."""
        code = """
@attrs.define
@some_other_decorator
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 3.44μs -> 2.16μs (58.8% faster)
        assert result == (True, True, False), "Should find attrs decorator among multiple decorators"

    def test_multiple_decorators_attrs_second(self):
        """Test class with multiple decorators where attrs decorator is second."""
        code = """
@some_other_decorator
@attrs.define
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.07μs -> 2.62μs (54.9% faster)
        assert result == (True, True, False), "Should find attrs decorator even if not first"

    def test_attr_s_with_params(self):
        """Test @attr.s with parameters."""
        code = """
@attr.s(init=False, kw_only=True)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.67μs -> 3.28μs (42.5% faster)
        assert result == (True, False, True), "Should parse @attr.s with parameters"


class TestGetAttrsConfigEdge:
    """Edge tests — Evaluate behavior under extreme or unusual conditions."""

    def test_decorator_with_no_args_call(self):
        """Test decorator that is a call with no arguments."""
        code = """
@attrs.define()
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 3.61μs -> 2.39μs (50.6% faster)
        assert result == (True, True, False), "Should handle @attrs.define() with empty parentheses"

    def test_decorator_with_non_boolean_literal(self):
        """Test decorator with non-boolean keyword argument (should be ignored)."""
        code = """
@attrs.define(slots=True, init=True)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.44μs -> 3.27μs (35.9% faster)
        assert result == (True, True, False), "Should ignore non-boolean keyword arguments"

    def test_decorator_with_string_literal_value(self):
        """Test decorator with string value for init (not a boolean)."""
        code = """
@attrs.define(init='invalid')
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.06μs -> 2.60μs (56.4% faster)
        assert result == (True, True, False), "Should ignore non-boolean values and use defaults"

    def test_decorator_with_variable_reference(self):
        """Test decorator with variable reference (not a literal)."""
        code = """
@attrs.define(init=some_var)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 3.94μs -> 2.50μs (57.8% faster)
        assert result == (True, True, False), "Should ignore variable references and use defaults"

    def test_wrong_namespace_decorator(self):
        """Test decorator with wrong namespace (e.g., @myattrs.define)."""
        code = """
@myattrs.define
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 3.03μs -> 2.01μs (50.2% faster)
        assert result == (False, False, False), "Should not recognize decorators from wrong namespace"

    def test_wrong_decorator_name(self):
        """Test decorator with wrong name (e.g., @attrs.invalid)."""
        code = """
@attrs.invalid
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 3.18μs -> 1.99μs (59.4% faster)
        assert result == (False, False, False), "Should not recognize invalid decorator names"

    def test_deeply_nested_attribute(self):
        """Test decorator with deeply nested attributes (beyond attrs namespace)."""
        code = """
@some.module.attrs.define
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 3.72μs -> 2.50μs (48.4% faster)
        assert result == (True, True, False), "Should recognize attrs.define even in nested attribute chain"

    def test_decorator_is_plain_name(self):
        """Test decorator that is a plain name without namespace (should be ignored)."""
        code = """
@define
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 1.66μs -> 1.48μs (12.1% faster)
        assert result == (False, False, False), "Should not recognize plain name without namespace"

    def test_attrs_attrs_decorator(self):
        """Test @attrs.attrs decorator (valid decorator name)."""
        code = """
@attrs.attrs
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 3.40μs -> 2.10μs (61.4% faster)
        assert result == (True, True, False), "Should recognize @attrs.attrs"

    def test_attr_define_decorator(self):
        """Test @attr.define decorator."""
        code = """
@attr.define
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 3.23μs -> 2.05μs (57.1% faster)
        assert result == (True, True, False), "Should recognize @attr.define"

    def test_empty_decorator_list(self):
        """Test class with explicitly empty decorator list."""
        code = """
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        assert len(class_node.decorator_list) == 0, "Verify decorator list is empty"  # 491ns -> 501ns (2.00% slower)
        result = _get_attrs_config(class_node, {})
        assert result == (False, False, False), "Empty decorator list should return False tuple"

    def test_keyword_argument_order_independence(self):
        """Test that keyword argument order doesn't matter."""
        code1 = """
@attrs.define(init=False, kw_only=True)
class MyClass:
    pass
"""
        code2 = """
@attrs.define(kw_only=True, init=False)
class MyClass:
    pass
"""
        tree1 = ast.parse(code1)
        tree2 = ast.parse(code2)
        class_node1 = tree1.body[0]
        class_node2 = tree2.body[0]
        result1 = _get_attrs_config(class_node1, {})  # 4.78μs -> 3.50μs (36.7% faster)
        result2 = _get_attrs_config(class_node2, {})
        assert result1 == result2 == (True, False, True), (
            "Keyword argument order should not matter"
        )  # 2.42μs -> 1.96μs (23.4% faster)

    def test_unknown_keyword_argument(self):
        """Test decorator with unknown keyword argument (should be ignored)."""
        code = """
@attrs.define(unknown_arg=True)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.10μs -> 2.87μs (43.0% faster)
        assert result == (True, True, False), "Unknown keyword arguments should be ignored, defaults used"

    def test_mixed_known_unknown_args(self):
        """Test decorator with both known and unknown keyword arguments."""
        code = """
@attrs.define(init=False, unknown_arg=True, kw_only=True)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.77μs -> 3.41μs (40.0% faster)
        assert result == (True, False, True), "Should parse known args and ignore unknown ones"

    def test_init_alias_functionality(self):
        """Test that import_aliases parameter is accepted (though not used by function)."""
        code = """
@attrs.define
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        # Function accepts import_aliases but doesn't use it in current implementation
        result = _get_attrs_config(class_node, {"some_alias": "some_module"})  # 3.37μs -> 2.14μs (57.0% faster)
        assert result == (True, True, False), "Function should accept import_aliases parameter"

    def test_multiple_attrs_decorators_returns_first_match(self):
        """Test class with multiple attrs decorators (should return first match)."""
        code = """
@attrs.define(init=False)
@attrs.mutable(kw_only=True)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.00μs -> 2.85μs (40.0% faster)
        # Should return on first match (the top decorator)
        assert result == (True, False, False), "Should return on first matching attrs decorator"


class TestGetAttrsConfigLargeScale:
    """Large-scale tests — Assess performance and scalability with realistic data volumes."""

    def test_class_with_many_decorators_before_attrs(self):
        """Test class with many non-attrs decorators before an attrs decorator."""
        decorators = [f"@decorator{i}" for i in range(100)]
        decorators.append("@attrs.define")
        decorator_str = "\n".join(decorators)
        code = f"""
{decorator_str}
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 33.3μs -> 28.1μs (18.4% faster)
        assert result == (True, True, False), "Should find attrs decorator among 100+ decorators"

    def test_class_with_many_keyword_arguments(self):
        """Test decorator with many keyword arguments."""
        kwargs = ", ".join([f"arg{i}=False" for i in range(50)] + ["init=False", "kw_only=True"])
        code = f"""
@attrs.define({kwargs})
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 16.8μs -> 15.1μs (11.6% faster)
        assert result == (True, False, True), "Should correctly parse init and kw_only among many args"

    def test_deeply_nested_attribute_chain(self):
        """Test decorator with very deep attribute chain."""
        # Create a decorator like @a.b.c.d.e.f.attrs.define
        parts = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "attrs", "define"]
        decorator_str = "@" + ".".join(parts)
        code = f"""
{decorator_str}
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        result = _get_attrs_config(class_node, {})  # 4.87μs -> 3.56μs (36.9% faster)
        assert result == (True, True, False), "Should recognize attrs.define in very deep attribute chain"

    def test_import_aliases_with_large_dictionary(self):
        """Test function accepts large import_aliases dictionary."""
        code = """
@attrs.define
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        # Create a large import_aliases dictionary (though not used by function)
        large_aliases = {f"alias{i}": f"module{i}" for i in range(1000)}
        result = _get_attrs_config(class_node, large_aliases)  # 3.54μs -> 2.38μs (48.3% faster)
        assert result == (True, True, False), "Function should handle large import_aliases dict"

    def test_performance_with_repeated_calls(self):
        """Test function performance with repeated calls (1000 calls)."""
        code = """
@attrs.define(init=True, kw_only=False)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]

        # Perform 1000 repeated calls to ensure no performance degradation
        for _ in range(1000):
            result = _get_attrs_config(class_node, {})  # 1.67ms -> 1.45ms (15.4% faster)
            assert result == (True, True, False), "Repeated calls should return consistent results"

    def test_many_classes_sequential_parsing(self):
        """Test parsing configuration from many class definitions sequentially."""
        # Create code with 100 class definitions with attrs decorators
        code_parts = []
        for i in range(100):
            init_val = "True" if i % 2 == 0 else "False"
            kw_only_val = "True" if i % 3 == 0 else "False"
            code_parts.append(f"""
@attrs.define(init={init_val}, kw_only={kw_only_val})
class MyClass{i}:
    pass
""")
        code = "\n".join(code_parts)
        tree = ast.parse(code)

        # Verify all 100 classes are parsed correctly
        for i, class_node in enumerate(tree.body):
            if isinstance(class_node, ast.ClassDef):
                result = _get_attrs_config(class_node, {})
                expected_init = i % 2 == 0
                expected_kw_only = i % 3 == 0
                assert result == (True, expected_init, expected_kw_only), f"Class {i} should have correct config"

    def test_decorator_with_maximum_nesting_levels(self):
        """Test handling of very deeply nested decorator expression."""
        # Create nested Call nodes: @attrs.define(init=False, kw_only=True)
        code = """
@attrs.define(init=False, kw_only=True)
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]

        # Call function 100 times to stress test
        for _ in range(100):
            result = _get_attrs_config(class_node, {})  # 170μs -> 146μs (16.4% faster)
            assert result == (True, False, True), "Should handle deep nesting consistently"

    def test_alternating_decorator_types(self):
        """Test class with alternating decorator styles."""
        code = """
@decorator1
@attrs.define
@decorator2
@attrs.mutable(init=False)
@decorator3
class MyClass:
    pass
"""
        tree = ast.parse(code)
        class_node = tree.body[0]
        # Should return first match (attrs.define at index 1 from top)
        result = _get_attrs_config(class_node, {})  # 4.24μs -> 2.79μs (52.1% faster)
        assert result == (True, True, False), "Should return first matching attrs decorator"
⏪ Click to see Replay Tests
Test File::Test Function Original ⏱️ Optimized ⏱️ Speedup
benchmarks/codeflash_replay_tests_46voh163/test_tests_benchmarks_test_benchmark_code_extract_code_context__replay_test_0.py::test_codeflash_languages_python_context_code_context_extractor__get_attrs_config_test_benchmark_extract 842ns 821ns 2.56%✅

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

Codeflash

The hot loop that checks decorators now extracts only the last two dotted-name segments (the namespace and decorator name) instead of building the full dotted string and splitting it, reducing profiler-reported time in `_get_attrs_config` from ~21 ms to ~9.7 ms (54% faster). Additionally, the boolean-literal check was inlined to eliminate ~6 ms of function-call overhead in `_bool_literal`. These changes together yield the 17% end-to-end runtime improvement with no semantic changes to behavior.
@codeflash-ai codeflash-ai Bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label Mar 18, 2026
@claude

claude Bot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

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


PR Review Summary

  • Triage PR scope
  • Run lint/typecheck checks
  • Resolve stale review threads (none found)
  • Review code changes
  • Duplicate detection
  • Test coverage analysis
  • Post summary

Prek Checks

uv run prek run --from-ref origin/fix/attrs-init-instrumentation — passed (ruff check + ruff format both clean)

mypy — no issues found

Code Review

Bug fixed: duplicate function definition ⚠️ → ✅ Fixed

_get_last_two_names was defined twice back-to-back at lines 1813 and 1846. The second definition was byte-for-byte identical to the first. In Python, the second definition silently shadows the first — functionally correct, but a clear defect. I've removed the duplicate and committed the fix.

The optimization logic itself looks correct:

  • The new _get_last_two_names helper traverses the AST upward collecting reversed attribute parts and correctly returns only the last two segments ((namespace, name)) — avoiding the full dotted string construction that _get_expr_name did.
  • The _bool_literal call was inlined as isinstance(keyword.value, ast.Constant) and isinstance(keyword.value.value, bool) which is semantically equivalent and avoids a function call.

Duplicate Detection

No duplicates detected. The pre-existing _get_expr_name (line 739) and _bool_literal (line 832) helpers are still used by other callers in the same file, so they remain. _get_last_two_names is a targeted, performance-oriented variant used only in _get_attrs_config.

Test Coverage

Per the PR description: 1266 generated regression tests passed at 100% coverage, plus 1 replay test. No existing unit tests were present for this function before the change.

Summary

One fix was committed: removed the duplicate _get_last_two_names definition introduced by the optimization. The optimization itself is sound and the 17% speedup claim is well-supported by the benchmark data.


@codeflash-ai

codeflash-ai Bot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

⚡️ Codeflash found optimizations for this PR

📄 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)

A dependent PR with the suggested changes has been created. Please review:

If you approve, it will be merged into this PR (branch codeflash/optimize-pr1860-2026-03-18T08.05.54).

Static Badge

@claude

claude Bot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Closing: the js-esm-async-optimization E2E test is failing (error handling tests for processItemsSequential, a JavaScript function). This failure is unrelated to the _get_attrs_config Python optimization in this PR — the optimization only touches Python code and cannot affect JavaScript E2E test correctness. This appears to be a pre-existing failure in the test suite unrelated to this change.

@claude claude Bot closed this Mar 18, 2026
@claude
claude Bot deleted the codeflash/optimize-pr1860-2026-03-18T08.05.54 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants