Skip to content

fix: instrument attrs classes in __init__ capture (crash fix + monkey-patch wrapper)#1860

Open
KRRT7 wants to merge 10 commits intomainfrom
fix/attrs-init-instrumentation
Open

fix: instrument attrs classes in __init__ capture (crash fix + monkey-patch wrapper)#1860
KRRT7 wants to merge 10 commits intomainfrom
fix/attrs-init-instrumentation

Conversation

@KRRT7
Copy link
Collaborator

@KRRT7 KRRT7 commented Mar 18, 2026

Problem

When codeflash tried to optimize a method on an @attrs.define (or @attr.s) class, it crashed with:

TypeError: super(type, obj): obj (instance of X) is not an instance or subtype of X

Root cause: attrs.define(slots=True) (the default) replaces the original class with a brand-new slots class at import time. The synthetic def __init__(self, *args, **kwargs): super().__init__(...) that codeflash injects into the class body bakes a __class__ cell pointing to the original class, but self is already an instance of the new slots class — so the super() call explodes.

Additionally, the test-generation context extractor had no knowledge of attrs field conventions, so it couldn't build correct __init__ stubs for attrs classes.

Changes

1. Crash fix → module-level monkey-patch wrapper (instrument_codeflash_capture.py)

Instead of injecting a synthetic __init__ body into the class (which triggers the crash), we now emit a module-level patch block immediately after the class definition:

_codeflash_orig_MyClass_init = MyClass.__init__
def _codeflash_patched_MyClass_init(self, *args, **kwargs):
    return _codeflash_orig_MyClass_init(self, *args, **kwargs)
MyClass.__init__ = codeflash_capture(...)(_codeflash_patched_MyClass_init)

The wrapper is a plain module-level function with no __class__ cell, so it's immune to the slot-class replacement. Covers @attrs.define, @attrs.define(frozen=True), @attrs.mutable, @attrs.frozen, @attr.s, @attr.attrs.

2. Test-gen context: attrs support (code_context_extractor.py)

  • Added _get_attrs_config() helper (parallel to _get_dataclass_config)
  • _collect_synthetic_constructor_type_names: attrs classes now included
  • _build_synthetic_init_stub: generates correct stub with kw_only support
  • _extract_synthetic_init_parameters: handles attrs factory= keyword (equivalent to dataclass default_factory=)

3. Tests

6 new tests, all with exact expected-output assertions:

  • 3 in test_instrument_codeflash_capture.py — verify the monkey-patch block is emitted correctly for @attrs.define, @attrs.define(frozen=True), and @attr.s
  • 3 in test_code_context_extractor.py — verify __init__ stub generation for attrs classes (required fields, factory= defaults, init=False)

Co-Authored-By: Oz oz-agent@warp.dev

KRRT7 and others added 2 commits March 18, 2026 01:33
…t to code_context_extractor

- instrument_codeflash_capture: detect @attrs.define / @attr.s / etc. in the
  'no explicit __init__' branch and return early, same as dataclass/NamedTuple.
  Prevents a TypeError caused by attrs(slots=True) creating a new class whose
  __class__ cell no longer matches the injected super().__init__ wrapper.

- code_context_extractor: add _get_attrs_config() helper; update
  _collect_synthetic_constructor_type_names, _build_synthetic_init_stub, and
  _extract_synthetic_init_parameters to handle attrs field conventions
  (factory= keyword, init=False, kw_only).

- tests: add 3 exact-output tests for instrumentation skip behaviour and
  3 exact-output tests for attrs stub generation.

Co-Authored-By: Oz <oz-agent@warp.dev>
Instead of skipping attrs classes entirely (previous approach), emit a
module-level patch block immediately after the class definition:

  _codeflash_orig_ClassName_init = ClassName.__init__
  def _codeflash_patched_ClassName_init(self, *args, **kwargs):
      return _codeflash_orig_ClassName_init(self, *args, **kwargs)
  ClassName.__init__ = codeflash_capture(...)(_codeflash_patched_ClassName_init)

This sidesteps the __class__ cell TypeError that attrs(slots=True) triggers
when a synthetic super().__init__() body is injected into the original class,
because the patched wrapper is a plain module-level function with no __class__
cell.

Changes:
- InitDecorator.__init__: add _attrs_classes_to_patch dict
- visit_ClassDef: for attrs classes, record (name -> decorator) instead of
  returning immediately; set inserted_decorator=True
- visit_Module: splice patch block statements after each attrs ClassDef
- _build_attrs_patch_block: new helper that builds the 3-statement AST block
- Tests: rename *_no_init_skipped -> *_patched_via_module_wrapper and update
  expected strings to assert the exact generated patch block

Co-Authored-By: Oz <oz-agent@warp.dev>
@claude
Copy link
Contributor

claude bot commented Mar 18, 2026

Claude finished @KRRT7's task in 9m 36s —— View job


PR Review Summary

  • Triage PR scope
  • Lint and typecheck
  • Resolve stale threads
  • Code review
  • Duplicate detection
  • Test coverage
  • Merge optimization PRs

Prek Checks

uv run prek run --from-ref origin/main passes with no issues.

Mypy

One new type error introduced in _build_attrs_patch_block (line 270):

instrument_codeflash_capture.py:270: error: No overload variant of "FunctionDef" matches
argument types "str", "arguments", "list[Return]", "list[Never]", "None"  [call-overload]

This is the same class of error as the pre-existing one at line 244 (init_func = ast.FunctionDef(...) without type_params=[]). Both stem from Python 3.12's addition of type_params to ast.FunctionDef. Since type_params=[] would fail on Python 3.9–3.11, the appropriate fix is to use ast.fix_missing_locations (already done) and suppress with # type: ignore[call-overload] to match the intent of the pre-existing instance. Neither error is blocking, but the new one at line 270 should at least be consistent with how the team handles line 244.

Code Review

1. Naming convention violation_ATTRS_NAMESPACES and _ATTRS_DECORATOR_NAMES have leading underscores, violating the repo's no-underscore-prefix rule (CLAUDE.md). These are not private; they're cross-imported into a sibling module. Fix this →

2. Cross-module import of private constantsinstrument_codeflash_capture.py:9 imports _ATTRS_DECORATOR_NAMES, _ATTRS_NAMESPACES from code_context_extractor.py. Instrumentation code coupling to context-extraction internals is fragile. These constants should either live in a shared place (e.g. models/) or be inlined in both files.

3. from attrs import define@define not recognized in InitDecoratorvisit_ClassDef (line 229) checks parts = dec_name.split(".") and requires len(parts) >= 2. A bare @define decorator (from from attrs import define) would have parts = ["define"] and never match. The context extractor correctly handles this via _resolve_decorator_name/import_aliases, but the instrumentor does not. This is the same gap that exists for @dataclass vs @dataclasses.dataclass, so it's arguably a pre-existing limitation scope, but worth a follow-up issue.

4. attr.ib() fields not recognized in _extract_synthetic_init_parameters_expr_matches_name(item.value.func, import_aliases, "field") (line 974) matches calls named field or ending in .field. Old-style attr.ib() fields don't match, so they fall into the else branch and get default_value = "attr.ib()" — producing a stub parameter with a literal attr.ib() as its default. Unlikely to affect real tests since new-style attrs.field() is preferred, but it's a gap in the @attr.s stub generation.

5. Logic correctness of frozen=True patching — ✅ ClassName.__init__ = ... is a class-level assignment, which works for frozen attrs classes (frozen only prevents instance attribute assignment). No issue here.

6. init=False short-circuit (line 231–234) — ✅ Only checked when isinstance(dec, ast.Call), which is correct since bare @attrs.define (non-call) can't pass init=False.

Duplicate Detection

_get_attrs_config (lines 873–899) is structurally very similar to _get_dataclass_config. Both iterate decorators, check init and kw_only keywords, and return a (bool, bool, bool) tuple. This is intentional parallel structure — the decorator namespaces differ enough that a shared helper would add complexity. No actionable duplicate.

Test Coverage

File Coverage
instrument_codeflash_capture.py 98%
code_context_extractor.py 83%

All 6 new tests pass. Gaps in code_context_extractor.py are pre-existing (API-key-dependent tests excluded).


Overall: The crash fix is correct and the approach (module-level monkey-patch instead of class-body injection) is sound. The main actionable issues are the naming convention (#1) and the cross-module constant import (#2). Items #3 and #4 are pre-existing limitation gaps worth tracking but not blockers for this PR.

Co-authored-by: Kevin Turcios <undefined@users.noreply.github.com>
@codeflash-ai
Copy link
Contributor

codeflash-ai bot commented Mar 18, 2026

⚡️ Codeflash found optimizations for this PR

📄 19,597% (195.97x) speedup for _extract_synthetic_init_parameters in codeflash/languages/python/context/code_context_extractor.py

⏱️ Runtime : 468 milliseconds 2.38 milliseconds (best of 87 runs)

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

If you approve, it will be merged into this PR (branch fix/attrs-init-instrumentation).

Static Badge

The optimization pre-allocates reusable AST node fragments in `__init__` (such as `ast.Load()`, `ast.Store()`, `ast.Name(id="self")`, and `ast.Starred`) that previously were reconstructed on every call to `_build_attrs_patch_block`. Because AST nodes are immutable value objects that Python interns, referencing the same instances avoids repeated allocation overhead—profiler data shows lines constructing `ast.Name`, `ast.arg`, and `ast.Starred` nodes dropped from ~1–3 µs each to ~0.1–0.4 µs. Across 2868 invocations (per profiler), this yields the observed 40% runtime reduction from 22.7 ms to 16.2 ms with no correctness regressions.
@codeflash-ai
Copy link
Contributor

codeflash-ai bot commented Mar 18, 2026

⚡️ Codeflash found optimizations for this PR

📄 40% (0.40x) speedup for InitDecorator._build_attrs_patch_block in codeflash/languages/python/instrument_codeflash_capture.py

⏱️ Runtime : 22.7 milliseconds 16.2 milliseconds (best of 130 runs)

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

If you approve, it will be merged into this PR (branch fix/attrs-init-instrumentation).

Static Badge

⚡️ Speed up method `InitDecorator._build_attrs_patch_block` by 40% in PR #1860 (`fix/attrs-init-instrumentation`)
@codeflash-ai
Copy link
Contributor

codeflash-ai bot commented Mar 18, 2026

@codeflash-ai
Copy link
Contributor

codeflash-ai bot commented Mar 18, 2026

⚡️ Codeflash found optimizations for this PR

📄 33% (0.33x) speedup for _collect_synthetic_constructor_type_names in codeflash/languages/python/context/code_context_extractor.py

⏱️ Runtime : 1.61 milliseconds 1.21 milliseconds (best of 117 runs)

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

If you approve, it will be merged into this PR (branch fix/attrs-init-instrumentation).

Static Badge

- Fix bug: skip attrs classes with init=False (no __init__ to patch)
- Deduplicate attrs namespace/name sets into shared constants
- Fix _get_attrs_config to resolve import aliases properly
- Add test for init=False case with exact expected output
github-actions bot and others added 2 commits March 18, 2026 09:47
…ormat

Co-Authored-By: Kevin Turcios <undefined@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: codeflash-ai[bot] <148906541+codeflash-ai[bot]@users.noreply.github.com>
@codeflash-ai
Copy link
Contributor

codeflash-ai bot commented Mar 18, 2026

This PR is now faster! 🚀 Kevin Turcios accepted my code suggestion above.

The optimization pre-parses the `codeflash_capture` import statement once in `__init__` and stores it in `self._import_stmt`, eliminating the repeated `ast.parse` call inside `visit_Module`. Line profiler confirms the original code spent ~186 µs (1% of runtime) parsing the import on every module visit (11 hits × 16.9 µs each), which is now reduced to a one-time ~8 µs insertion cost. This reduces total `visit_Module` time by ~2.6% (17.87 ms → 17.41 ms) with no correctness trade-offs, preserving all AST structure and behavior across diverse test scenarios including large modules with 100+ classes.
@codeflash-ai
Copy link
Contributor

codeflash-ai bot commented Mar 18, 2026

⚡️ Codeflash found optimizations for this PR

📄 12% (0.12x) speedup for InitDecorator.visit_Module in codeflash/languages/python/instrument_codeflash_capture.py

⏱️ Runtime : 376 microseconds 336 microseconds (best of 137 runs)

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

If you approve, it will be merged into this PR (branch fix/attrs-init-instrumentation).

Static Badge

@codeflash-ai
Copy link
Contributor

codeflash-ai bot commented Mar 18, 2026

⚡️ Codeflash found optimizations for this PR

📄 10% (0.10x) speedup for InitDecorator.visit_ClassDef in codeflash/languages/python/instrument_codeflash_capture.py

⏱️ Runtime : 405 microseconds 367 microseconds (best of 250 runs)

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

If you approve, it will be merged into this PR (branch fix/attrs-init-instrumentation).

Static Badge

…2026-03-18T10.30.36

⚡️ Speed up method `InitDecorator.visit_Module` by 12% in PR #1860 (`fix/attrs-init-instrumentation`)
@codeflash-ai
Copy link
Contributor

codeflash-ai bot commented Mar 18, 2026

This PR is now faster! 🚀 @claude[bot] accepted my optimizations from:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant