Skip to content

Commit f21c12e

Browse files
Merge pull request #1285 from codeflash-ai/fix/move-function-to-optimize-to-models
refactor: move FunctionToOptimize to models/function_types to fix circular imports
2 parents b1d952d + fd59fb8 commit f21c12e

File tree

2 files changed

+80
-95
lines changed

2 files changed

+80
-95
lines changed

codeflash/discovery/functions_to_optimize.py

Lines changed: 5 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import git
1414
import libcst as cst
15-
from pydantic import Field
1615
from pydantic.dataclasses import dataclass
1716
from rich.tree import Tree
1817

@@ -30,16 +29,18 @@
3029
from codeflash.languages.language_enum import Language
3130
from codeflash.languages.registry import get_language_support, get_supported_extensions, is_language_supported
3231
from codeflash.lsp.helpers import is_LSP_enabled
33-
from codeflash.models.models import FunctionParent
32+
from codeflash.models.function_types import FunctionParent, FunctionToOptimize
3433
from codeflash.telemetry.posthog_cf import ph
3534

35+
# Re-export for backward compatibility
36+
__all__ = ["FunctionParent", "FunctionToOptimize"]
37+
3638
if TYPE_CHECKING:
3739
from argparse import Namespace
3840

3941
from libcst import CSTNode
4042
from libcst.metadata import CodeRange
4143

42-
from codeflash.languages.base import FunctionInfo
4344
from codeflash.models.models import CodeOptimizationContext
4445
from codeflash.verification.verification_utils import TestConfig
4546
import contextlib
@@ -130,97 +131,6 @@ def generic_visit(self, node: ast.AST) -> None:
130131
self.ast_path.pop()
131132

132133

133-
@dataclass(frozen=True, config={"arbitrary_types_allowed": True})
134-
class FunctionToOptimize:
135-
"""Represent a function that is a candidate for optimization.
136-
137-
This is the canonical dataclass for representing functions across all languages
138-
(Python, JavaScript, TypeScript). It captures all information needed to identify,
139-
locate, and work with a function.
140-
141-
Attributes
142-
----------
143-
function_name: The name of the function.
144-
file_path: The absolute file path where the function is located.
145-
parents: A list of parent scopes, which could be classes or functions.
146-
starting_line: The starting line number of the function in the file (1-indexed).
147-
ending_line: The ending line number of the function in the file (1-indexed).
148-
starting_col: The starting column offset (0-indexed, for precise location).
149-
ending_col: The ending column offset (0-indexed, for precise location).
150-
is_async: Whether this function is defined as async.
151-
is_method: Whether this is a method (belongs to a class).
152-
language: The programming language of this function (default: "python").
153-
doc_start_line: Line where docstring/JSDoc starts (or None if no doc comment).
154-
155-
The qualified_name property provides the full name of the function, including
156-
any parent class or function names. The qualified_name_with_modules_from_root
157-
method extends this with the module name from the project root.
158-
159-
"""
160-
161-
function_name: str
162-
file_path: Path
163-
parents: list[FunctionParent] = Field(default_factory=list) # list[ClassDef | FunctionDef | AsyncFunctionDef]
164-
starting_line: Optional[int] = None
165-
ending_line: Optional[int] = None
166-
starting_col: Optional[int] = None # Column offset for precise location
167-
ending_col: Optional[int] = None # Column offset for precise location
168-
is_async: bool = False
169-
is_method: bool = False # Whether this is a method (belongs to a class)
170-
language: str = "python" # Language identifier for multi-language support
171-
doc_start_line: Optional[int] = None # Line where docstring/JSDoc starts
172-
173-
@property
174-
def top_level_parent_name(self) -> str:
175-
return self.function_name if not self.parents else self.parents[0].name
176-
177-
@property
178-
def class_name(self) -> str | None:
179-
"""Get the immediate parent class name, if any."""
180-
for parent in reversed(self.parents):
181-
if parent.type == "ClassDef":
182-
return parent.name
183-
return None
184-
185-
def __str__(self) -> str:
186-
qualified = f"{'.'.join([p.name for p in self.parents])}{'.' if self.parents else ''}{self.function_name}"
187-
line_info = f":{self.starting_line}-{self.ending_line}" if self.starting_line and self.ending_line else ""
188-
return f"{self.file_path}:{qualified}{line_info}"
189-
190-
@property
191-
def qualified_name(self) -> str:
192-
if not self.parents:
193-
return self.function_name
194-
# Join all parent names with dots to handle nested classes properly
195-
parent_path = ".".join(parent.name for parent in self.parents)
196-
return f"{parent_path}.{self.function_name}"
197-
198-
def qualified_name_with_modules_from_root(self, project_root_path: Path) -> str:
199-
return f"{module_name_from_file_path(self.file_path, project_root_path)}.{self.qualified_name}"
200-
201-
@classmethod
202-
def from_function_info(cls, func_info: FunctionInfo) -> FunctionToOptimize:
203-
"""Create a FunctionToOptimize from a FunctionInfo instance.
204-
205-
This is a temporary method for backward compatibility during migration.
206-
Once FunctionInfo is fully removed, this method can be deleted.
207-
"""
208-
parents = [FunctionParent(name=p.name, type=p.type) for p in func_info.parents]
209-
return cls(
210-
function_name=func_info.name,
211-
file_path=func_info.file_path,
212-
parents=parents,
213-
starting_line=func_info.start_line,
214-
ending_line=func_info.end_line,
215-
starting_col=func_info.start_col,
216-
ending_col=func_info.end_col,
217-
is_async=func_info.is_async,
218-
is_method=func_info.is_method,
219-
language=func_info.language.value,
220-
doc_start_line=func_info.doc_start_line,
221-
)
222-
223-
224134
# =============================================================================
225135
# Multi-language support helpers
226136
# =============================================================================
@@ -421,7 +331,7 @@ def get_functions_to_optimize(
421331
# It's a standalone function - check if the function is exported
422332
name_to_check = found_function.function_name
423333

424-
is_exported, export_name = _is_js_ts_function_exported(file, name_to_check)
334+
is_exported, _ = _is_js_ts_function_exported(file, name_to_check)
425335
if not is_exported:
426336
if found_function.parents:
427337
logger.debug(

codeflash/models/function_types.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
from __future__ import annotations
88

9+
from pathlib import Path
10+
from typing import Optional
11+
12+
from pydantic import Field
913
from pydantic.dataclasses import dataclass
1014

1115

@@ -16,3 +20,74 @@ class FunctionParent:
1620

1721
def __str__(self) -> str:
1822
return f"{self.type}:{self.name}"
23+
24+
25+
@dataclass(frozen=True, config={"arbitrary_types_allowed": True})
26+
class FunctionToOptimize:
27+
"""Represent a function that is a candidate for optimization.
28+
29+
This is the canonical dataclass for representing functions across all languages
30+
(Python, JavaScript, TypeScript). It captures all information needed to identify,
31+
locate, and work with a function.
32+
33+
Attributes
34+
----------
35+
function_name: The name of the function.
36+
file_path: The absolute file path where the function is located.
37+
parents: A list of parent scopes, which could be classes or functions.
38+
starting_line: The starting line number of the function in the file (1-indexed).
39+
ending_line: The ending line number of the function in the file (1-indexed).
40+
starting_col: The starting column offset (0-indexed, for precise location).
41+
ending_col: The ending column offset (0-indexed, for precise location).
42+
is_async: Whether this function is defined as async.
43+
is_method: Whether this is a method (belongs to a class).
44+
language: The programming language of this function (default: "python").
45+
doc_start_line: Line where docstring/JSDoc starts (or None if no doc comment).
46+
47+
The qualified_name property provides the full name of the function, including
48+
any parent class or function names. The qualified_name_with_modules_from_root
49+
method extends this with the module name from the project root.
50+
51+
"""
52+
53+
function_name: str
54+
file_path: Path
55+
parents: list[FunctionParent] = Field(default_factory=list)
56+
starting_line: Optional[int] = None
57+
ending_line: Optional[int] = None
58+
starting_col: Optional[int] = None
59+
ending_col: Optional[int] = None
60+
is_async: bool = False
61+
is_method: bool = False
62+
language: str = "python"
63+
doc_start_line: Optional[int] = None
64+
65+
@property
66+
def top_level_parent_name(self) -> str:
67+
return self.function_name if not self.parents else self.parents[0].name
68+
69+
@property
70+
def class_name(self) -> str | None:
71+
"""Get the immediate parent class name, if any."""
72+
for parent in reversed(self.parents):
73+
if parent.type == "ClassDef":
74+
return parent.name
75+
return None
76+
77+
def __str__(self) -> str:
78+
qualified = f"{'.'.join([p.name for p in self.parents])}{'.' if self.parents else ''}{self.function_name}"
79+
line_info = f":{self.starting_line}-{self.ending_line}" if self.starting_line and self.ending_line else ""
80+
return f"{self.file_path}:{qualified}{line_info}"
81+
82+
@property
83+
def qualified_name(self) -> str:
84+
if not self.parents:
85+
return self.function_name
86+
parent_path = ".".join(parent.name for parent in self.parents)
87+
return f"{parent_path}.{self.function_name}"
88+
89+
def qualified_name_with_modules_from_root(self, project_root_path: Path) -> str:
90+
# Import here to avoid circular imports
91+
from codeflash.code_utils.code_utils import module_name_from_file_path
92+
93+
return f"{module_name_from_file_path(self.file_path, project_root_path)}.{self.qualified_name}"

0 commit comments

Comments
 (0)