Private
Public Access
feat(scripts): Heuristic A - Result-returning recovery = INTERNAL_COMPLIANT
Phase 11.2. Adds the LEGITIMATE heuristic that recognizes the canonical data-oriented pattern: \ ry: ...; except: return Result(data=..., errors=[...])\ is the convention's canonical recovery pattern. Detection: - New _returns_result(stmts) helper on ExceptionVisitor - New step 0 in _classify_except (BEFORE BOUNDARY_CONVERSION check) - Classifies as INTERNAL_COMPLIANT with a hint that names the pattern The function-name-not-ending-in-_result is documented as a smell (rename to xxx_result for canonical naming), but the pattern itself is compliant. Tests: - 2 new tests in test_audit_exception_handling_heuristics.py: - test_result_returning_recovery_in_non_result_named_function_is_compliant - test_result_returning_recovery_in_result_named_function_is_compliant - Both pass; the 2 REJECTED tests (#22, #23) remain xfailed. Per conductor/tracks/result_migration_small_files_20260617/plan.md section 11.2.
This commit is contained in:
@@ -373,6 +373,16 @@ class ExceptionVisitor(ast.NodeVisitor):
|
||||
|
||||
# ----- Classification logic -----
|
||||
|
||||
# 0. Heuristic A: Result-returning recovery — the canonical data-oriented pattern.
|
||||
# If the except body returns `Result(data=..., errors=[ErrorInfo(...)])`,
|
||||
# the function is following the convention. Classify as INTERNAL_COMPLIANT
|
||||
# BEFORE the BOUNDARY_CONVERSION check (which also fires for ErrorInfo creation).
|
||||
if self._returns_result(body):
|
||||
return (
|
||||
"INTERNAL_COMPLIANT",
|
||||
"Compliant: `try: ...; except: return Result(data=..., errors=[...])` is the canonical Result-recovery pattern. The convention requires Result[T] for try/except sites that can fail; this pattern satisfies the requirement. The function-name-not-ending-in-`_result` is a smell (rename to `xxx_result`); the pattern itself is compliant. (per result_migration_small_files_20260617 Phase 11.2, Heuristic A)",
|
||||
)
|
||||
|
||||
# 1. ErrorInfo conversion = canonical boundary pattern
|
||||
if creates_errorinfo:
|
||||
return (
|
||||
@@ -591,6 +601,13 @@ class ExceptionVisitor(ast.NodeVisitor):
|
||||
f"Compliant: `try: ...; except Exception: return <string>` in a `-> str` tool function is the canonical MCP tool boundary pattern (per result_migration_review_pass_20260617).",
|
||||
)
|
||||
|
||||
# A. Result-returning recovery (canonical Result pattern) — Phase 11.2
|
||||
if len(except_body) > 0 and self._returns_result(except_body):
|
||||
return (
|
||||
"INTERNAL_COMPLIANT",
|
||||
f"Compliant: `try: ...; except ({', '.join(sorted(exc_set))}): return Result(data=..., errors=[...])` is the canonical Result-recovery pattern. The function-name-not-ending-in-`_result` is a smell (rename to `xxx_result`); the pattern itself is the data-oriented convention. (per result_migration_small_files_20260617 Phase 11.2)",
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def _has_string_return(self, stmts: list[ast.stmt]) -> bool:
|
||||
@@ -610,6 +627,30 @@ class ExceptionVisitor(ast.NodeVisitor):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _returns_result(self, stmts: list[ast.stmt]) -> bool:
|
||||
"""True if the body returns a `Result(...)` call (canonical Result-recovery pattern).
|
||||
|
||||
Detects `return Result(data=..., errors=[...])` — the canonical
|
||||
data-oriented error handling pattern. Matches any call to `Result(...)`
|
||||
with at least a `data=` keyword argument. The pattern is compliant
|
||||
when used in a try/except: it satisfies the convention that every
|
||||
try/except site that can fail must return `Result[T]` with structured
|
||||
`ErrorInfo`. The function-name-not-ending-in-`_result` is a smell
|
||||
(the function should be renamed to `xxx_result`), but the pattern
|
||||
itself is compliant (heuristic A from Phase 11.2).
|
||||
"""
|
||||
for s in stmts:
|
||||
if not isinstance(s, ast.Return) or s.value is None:
|
||||
continue
|
||||
if not isinstance(s.value, ast.Call):
|
||||
continue
|
||||
f = s.value.func
|
||||
if isinstance(f, ast.Name) and f.id == "Result":
|
||||
return True
|
||||
if isinstance(f, ast.Attribute) and f.attr == "Result":
|
||||
return True
|
||||
return False
|
||||
|
||||
def _uses_exception_inline(self, stmts: list[ast.stmt]) -> bool:
|
||||
"""True if the body uses `e`/`exc` in a non-pass way (Name reference)."""
|
||||
for s in stmts:
|
||||
|
||||
Reference in New Issue
Block a user