Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 56 additions & 13 deletions codeflash/discovery/functions_to_optimize.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import ast
import os
import random
import warnings
from _ast import AsyncFunctionDef, ClassDef, FunctionDef
Expand Down Expand Up @@ -669,30 +668,74 @@ def filter_functions(
submodule_ignored_paths_count: int = 0
blocklist_funcs_removed_count: int = 0
previous_checkpoint_functions_removed_count: int = 0
tests_root_str = str(tests_root)
module_root_str = str(module_root)

def _resolve_path(path: Path | str) -> Path:
# Use strict=False so we don't fail on paths that don't exist yet (e.g. worktree paths)
return Path(path).resolve(strict=False)

# Resolve all root paths to absolute paths for consistent comparison
tests_root_resolved = _resolve_path(tests_root)
module_root_resolved = _resolve_path(module_root)

# Resolve ignore paths and submodule paths
ignore_paths_resolved = [_resolve_path(p) for p in ignore_paths]
submodule_paths_resolved = [_resolve_path(p) for p in submodule_paths]

# We desperately need Python 3.10+ only support to make this code readable with structural pattern matching
for file_path_path, functions in modified_functions.items():
_functions = functions
file_path = str(file_path_path)
if file_path.startswith(tests_root_str + os.sep):
# Resolve file path to absolute path
# Convert to Path if it's a string (e.g., from get_functions_within_git_diff)
file_path_obj = _resolve_path(file_path_path)
try:
file_path_resolved = file_path_obj.resolve()
except (OSError, RuntimeError):
file_path_resolved = file_path_obj.absolute() if not file_path_obj.is_absolute() else file_path_obj

file_path = str(file_path_obj)

# Check if file is in tests root using resolved paths
try:
file_path_resolved.relative_to(tests_root_resolved)
test_functions_removed_count += len(_functions)
continue
if file_path in ignore_paths or any(
file_path.startswith(str(ignore_path) + os.sep) for ignore_path in ignore_paths
):
except ValueError:
pass # File is not in tests root, continue checking

# Check if file is in ignore paths using resolved paths
is_ignored = False
for ignore_path_resolved in ignore_paths_resolved:
try:
file_path_resolved.relative_to(ignore_path_resolved)
is_ignored = True
break
except ValueError:
pass
if is_ignored:
ignore_paths_removed_count += 1
continue
if file_path in submodule_paths or any(
file_path.startswith(str(submodule_path) + os.sep) for submodule_path in submodule_paths
):

# Check if file is in submodule paths using resolved paths
is_in_submodule = False
for submodule_path_resolved in submodule_paths_resolved:
try:
file_path_resolved.relative_to(submodule_path_resolved)
is_in_submodule = True
break
except ValueError:
pass
if is_in_submodule:
submodule_ignored_paths_count += 1
continue
if path_belongs_to_site_packages(Path(file_path)):

if path_belongs_to_site_packages(file_path_resolved):
site_packages_removed_count += len(_functions)
continue
if not file_path.startswith(module_root_str + os.sep):

# Check if file is in module root using resolved paths
try:
file_path_resolved.relative_to(module_root_resolved)
except ValueError:
non_modules_removed_count += len(_functions)
continue
try:
Expand Down
7 changes: 5 additions & 2 deletions codeflash/lsp/beta.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,10 +479,13 @@ def initialize_function_optimization(params: FunctionOptimizationInitParams) ->
server.current_optimization_init_result = initialization_result.unwrap()
server.show_message_log(f"Successfully initialized optimization for {params.functionName}", "Info")

files = [document.path]
# Use the worktree file path (which is normalized) instead of document.path
# document.path might be malformed on Windows (missing drive letter)
worktree_file_path = str(server.optimizer.args.file.resolve())
files = [worktree_file_path]

_, _, original_helpers = server.current_optimization_init_result
files.extend([str(helper_path) for helper_path in original_helpers])
files.extend([str(helper_path.resolve()) for helper_path in original_helpers])

return {"functionName": params.functionName, "status": "success", "files_inside_context": files}

Expand Down
44 changes: 43 additions & 1 deletion codeflash/optimization/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,49 @@ def mirror_paths_for_worktree_mode(self, worktree_dir: Path) -> None:


def mirror_path(path: Path, src_root: Path, dest_root: Path) -> Path:
relative_path = path.relative_to(src_root)
# Resolve both paths to absolute paths to handle Windows path normalization issues
# This ensures paths with/without drive letters are handled correctly
try:
path_resolved = path.resolve()
except (OSError, RuntimeError):
# If resolve fails (e.g., path doesn't exist or is malformed), try absolute()
path_resolved = path.absolute() if not path.is_absolute() else path

try:
src_root_resolved = src_root.resolve()
except (OSError, RuntimeError):
src_root_resolved = src_root.absolute() if not src_root.is_absolute() else src_root

# Normalize paths using os.path.normpath and normcase for cross-platform compatibility
path_str = os.path.normpath(str(path_resolved))
src_root_str = os.path.normpath(str(src_root_resolved))

# Convert to lowercase for case-insensitive comparison on Windows
path_normalized = os.path.normcase(path_str)
src_root_normalized = os.path.normcase(src_root_str)

# Try using Path.relative_to with resolved paths first
try:
relative_path = path_resolved.relative_to(src_root_resolved)
except ValueError as err:
# If relative_to fails, manually extract the relative path using normalized strings
if path_normalized.startswith(src_root_normalized):
# Extract relative path manually
# Use the original path_str to preserve proper path format
if path_str.startswith(src_root_str):
relative_str = path_str[len(src_root_str) :].lstrip(os.sep)
relative_path = Path(relative_str)
else:
# Fallback: use normalized paths
relative_str = path_normalized[len(src_root_normalized) :].lstrip(os.sep)
relative_path = Path(relative_str)
else:
error_msg = (
f"Path {path_resolved} (normalized: {path_normalized}) is not relative to "
f"{src_root_resolved} (normalized: {src_root_normalized})"
)
raise ValueError(error_msg) from err

return dest_root / relative_path


Expand Down
18 changes: 11 additions & 7 deletions tests/test_function_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,13 +411,17 @@ def not_in_checkpoint_function():

discovered = find_all_functions_in_file(test_file_path)
modified_functions = {test_file_path: discovered[test_file_path]}
filtered, count = filter_functions(
modified_functions,
tests_root=Path("tests"),
ignore_paths=[],
project_root=temp_dir,
module_root=temp_dir,
)
# Use an absolute path for tests_root that won't match the temp directory
# This avoids path resolution issues in CI where the working directory might differ
tests_root_absolute = (temp_dir.parent / "nonexistent_tests_dir").resolve()
with unittest.mock.patch("codeflash.discovery.functions_to_optimize.get_blocklisted_functions", return_value={}):
filtered, count = filter_functions(
modified_functions,
tests_root=tests_root_absolute,
ignore_paths=[],
project_root=temp_dir,
module_root=temp_dir,
)
function_names = [fn.function_name for fn in filtered.get(test_file_path, [])]
assert "propagate_attributes" in function_names
assert count == 3
Expand Down
Loading