PR Code Suggestions ✨Explore these optional code suggestions: | Category | Suggestion | Impact | | Possible issue | Make module path resolution robust
Guard against paths outsideproject_root_path to avoidValueError and wrong module paths on symlinked or resolved paths. Use.resolve() on both paths and fall back to the existingmodule_name_from_file_path traversal logic when directrelative_to fails. codeflash/code_utils/code_utils.py [257-259] def get_qualified_function_path(file_path: Path, project_root_path: Path, qualified_name: str) -> str:- module_path = file_path.relative_to(project_root_path).with_suffix("").as_posix().replace("/", ".")+ try:+ module_path = file_path.resolve().relative_to(project_root_path.resolve()).with_suffix("").as_posix().replace("/", ".")+ except ValueError:+ # Fall back to robust traversal if file is not directly under project_root_path+ module_path = module_name_from_file_path(file_path, project_root_path) return f"{module_path}.{qualified_name}"Suggestion importance[1-10]: 7__ Why: The enhancement correctly guards Path.relative_to with resolve() and sensibly falls back to existingmodule_name_from_file_path, improving robustness for symlinks/out-of-root cases. Moderate impact and accurate to the PR context where this helper is newly added. | Medium | Avoid NameError for settings decorator
Ensure thesettings decorator is fully qualified when onlyhypothesis.settings is available. Before inserting, detect existingfrom hypothesis import settings or add
from hypothesis import settings; otherwise, wrap ashypothesis.settings(...) if only
import hypothesis exists to avoid NameError. codeflash/verification/hypothesis_testing.py [101-180] def make_hypothesis_tests_deterministic(code: str) -> str: """Add @settings(derandomize=True) decorator and constrain strategies to make Hypothesis tests deterministic.""" try: tree = ast.parse(code) except SyntaxError: return code-...- if settings_decorator:- if not any(k.arg == "derandomize" for k in settings_decorator.keywords):- settings_decorator.keywords.append(ast.keyword(arg="derandomize", value=ast.Constant(value=True)))- else:- node.decorator_list.append(- ast.Call(- func=ast.Name(id="settings", ctx=ast.Load()),- args=[],- keywords=[ast.keyword(arg="derandomize", value=ast.Constant(value=True))],++ has_from_import_settings = any(+ isinstance(node, ast.ImportFrom)+ and node.module == "hypothesis"+ and any(alias.name == "settings" for alias in node.names)+ for node in tree.body+ )+ has_import_hypothesis = any(isinstance(node, ast.Import) and any(a.name == "hypothesis" for a in node.names) for node in tree.body)++ # Insert import if needed+ if not has_from_import_settings and not has_import_hypothesis:+ tree.body.insert(0, ast.parse("from hypothesis import settings").body[0])++ class StrategyConstrainer(ast.NodeTransformer):+ def visit_Call(self, node: ast.Call) -> ast.Call:+ self.generic_visit(node)+ if (+ isinstance(node.func, ast.Attribute)+ and isinstance(node.func.value, ast.Name)+ and node.func.value.id == "st"+ ):+ if node.func.attr == "floats" and not any(+ k.arg in ["min_value", "max_value", "allow_nan", "allow_infinity"] for k in node.keywords+ ):+ node.keywords.extend(+ [+ ast.keyword(arg="min_value", value=ast.UnaryOp(op=ast.USub(), operand=ast.Constant(value=1e6))),+ ast.keyword(arg="max_value", value=ast.Constant(value=1e6)),+ ast.keyword(arg="allow_nan", value=ast.Constant(value=False)),+ ast.keyword(arg="allow_infinity", value=ast.Constant(value=False)),+ ]+ )+ elif node.func.attr == "integers" and not any(k.arg in ["min_value", "max_value"] for k in node.keywords):+ node.keywords.extend(+ [+ ast.keyword(arg="min_value", value=ast.Constant(value=-10000)),+ ast.keyword(arg="max_value", value=ast.Constant(value=10000)),+ ]+ )+ return node++ tree = StrategyConstrainer().visit(tree)+ ast.fix_missing_locations(tree)++ def settings_call() -> ast.expr:+ if has_from_import_settings or not has_import_hypothesis:+ return ast.Name(id="settings", ctx=ast.Load())+ # Use fully-qualified call if only `import hypothesis` exists+ return ast.Attribute(value=ast.Name(id="hypothesis", ctx=ast.Load()), attr="settings", ctx=ast.Load())++ for node in ast.walk(tree):+ if isinstance(node, ast.FunctionDef):+ settings_decorator = next(+ (d for d in node.decorator_list if isinstance(d, ast.Call) and (+ (isinstance(d.func, ast.Name) and d.func.id == "settings") or+ (isinstance(d.func, ast.Attribute) and isinstance(d.func.value, ast.Name) and d.func.value.id == "hypothesis" and d.func.attr == "settings")+ )),+ None,+ )+ if settings_decorator:+ if not any(k.arg == "derandomize" for k in settings_decorator.keywords):+ settings_decorator.keywords.append(ast.keyword(arg="derandomize", value=ast.Constant(value=True)))+ else:+ node.decorator_list.append(+ ast.Call(func=settings_call(), args=[], keywords=[ast.keyword(arg="derandomize", value=ast.Constant(value=True))]) )- )+ return ast.unparse(tree)+Suggestion importance[1-10]: 6__ Why: The change strengthensmake_hypothesis_tests_deterministic to work when onlyimport hypothesis exists, preventing potential NameError and maintaining functionality. It's contextually accurate and beneficial though not critical. | Low | Normalize import resolution in AST
Normalize resolved names to avoid false negatives caused by relative imports and aliasing. Expand leading dots inast.ImportFrom and normalize bothresolved and
target_qualified_names by stripping redundant dots. codeflash/discovery/discover_unit_tests.py [83-136] def _discover_calls_via_ast( test_file: Path, test_functions: set[TestFunction], target_qualified_names: set[str] ) -> dict[str, list[tuple[TestFunction, CodePosition]]]: try: with test_file.open("r", encoding="utf-8") as f: source = f.read() tree = ast.parse(source, filename=str(test_file)) except (SyntaxError, FileNotFoundError) as e: logger.debug(f"AST parsing failed for {test_file}: {e}") return {}-...- if parts[0] in import_map:- resolved = f"{import_map[parts[0]]}.{parts[1]}" if len(parts) == 2 else import_map[parts[0]]- if resolved in target_qualified_names:- result[resolved].append((test_func, CodePosition(line_no=child.lineno, col_no=child.col_offset)))+ import_map: dict[str, str] = {}+ module_pkg: str | None = None+ # Attempt to infer current module path for relative imports+ try:+ rel = test_file.with_suffix("").as_posix().replace("/", ".")+ module_pkg = rel.rsplit(".", 1)[0] if "." in rel else None+ except Exception:+ module_pkg = None+ for node in ast.walk(tree):+ if isinstance(node, ast.Import):+ for alias in node.names:+ name = alias.asname or alias.name+ import_map[name] = alias.name+ elif isinstance(node, ast.ImportFrom) and node.module is not None:+ base = node.module+ # Handle relative levels (e.g., from .sub import x)+ if getattr(node, "level", 0):+ if module_pkg:+ parts = module_pkg.split(".")+ level = int(node.level)+ base = ".".join(parts[: max(0, len(parts) - level)]) + (("." + base) if base else "")+ base = base.strip(".")+ for alias in node.names:+ if alias.name != "*":+ full_name = f"{base}.{alias.name}" if base else alias.name+ name = alias.asname or alias.name+ import_map[name] = full_name++ test_funcs_by_name = {tf.function_name: tf for tf in test_functions}+ result = defaultdict(list)+ normalized_targets = {t.strip(".") for t in target_qualified_names}++ for node in ast.walk(tree):+ if not isinstance(node, ast.FunctionDef) or node.name not in test_funcs_by_name:+ continue+ test_func = test_funcs_by_name[node.name]+ for child in ast.walk(node):+ if not isinstance(child, ast.Call):+ continue+ call_name = _extract_dotted_call_name(child.func)+ if not call_name:+ continue+ # direct match+ if call_name.strip(".") in normalized_targets:+ result[call_name.strip(".")].append(+ (test_func, CodePosition(line_no=child.lineno, col_no=child.col_offset))+ )+ continue+ parts = call_name.split(".", 1)+ if parts[0] in import_map:+ resolved = f"{import_map[parts[0]]}.{parts[1]}" if len(parts) == 2 else import_map[parts[0]]+ norm = resolved.strip(".")+ if norm in normalized_targets:+ result[norm].append((test_func, CodePosition(line_no=child.lineno, col_no=child.col_offset)))++ return dict(result)+Suggestion importance[1-10]: 5__ Why: Accounting for relative imports and normalizing names can reduce false negatives; the proposed logic aligns with the added AST discovery block. It's a reasonable improvement but not critical and introduces heuristic complexity without clear PR evidence of such issues. | Low |
|
Uh oh!
There was an error while loading.Please reload this page.
PR Type
Enhancement, Tests
Description
Add Hypothesis test generation pipeline
Discover and track Hypothesis tests
Compare Hypothesis results semantically
Cleanup Hypothesis temp directories
Diagram Walkthrough
File Walkthrough
8 files
Add helper to build qualified function pathAST-based discovery and Hypothesis test supportIntroduce Hypothesis test type and labelIntegrate Hypothesis generation, merge, and cleanupTrack and cleanup Hypothesis test directoriesUse helper for qualified function path; filter discoverySemantic comparison for Hypothesis test resultsImplement Hypothesis ghostwriter generation and filtering1 files
No-op whitespace change in is_pr_draft1 files
Unit tests for deterministic Hypothesis adjustments1 files
Add Hypothesis dependency