Private
Public Access
fix(audit): real line numbers + entry.get() field-access detection + Optional/dict/Union patterns
Three real bugs fixed:
1. FunctionRef always used line=0. Now passes node.lineno from AST.
2. P3_pass results were discarded with bare pass. Now stored in
ProducerConsumerGraph.field_accesses.
3. Field-access detector only saw entry['key']; missed entry.get('key')
which is the dominant pattern in this codebase. Now handles both.
Plus _extract_type_name() helper handles Optional[T], dict[str, T],
list[T], Result[T], Union[T, ...], and T | None (PEP 604) so P1/P2
catch more annotation patterns.
Real numbers (Metadata aggregate):
- producers: 77 -> 117
- consumers: 35 -> 66
- field-access sites: 130 -> 173
- line numbers: all real (line 1281, 1746, etc.)
AUDIT_REPORT.md grew 2009 -> 3140 lines with real evidence.
Total audit output: 5176 lines / 50 files (was 2415 / 49).
All 131 tests still passing.
This commit is contained in:
@@ -341,7 +341,7 @@ def test_p1_pass_finds_producer_of_T() -> None:
|
||||
''')
|
||||
tree = ast.parse(source)
|
||||
producers = P1_pass(tree, file="synthetic.py")
|
||||
assert ("send_result", "Metadata", "producer", "high") in producers
|
||||
assert ("send_result", "Metadata", "producer", "high", 2) in producers
|
||||
|
||||
def test_p1_pass_finds_producer_of_Result_T() -> None:
|
||||
"""P1 detects a function whose return annotation is Result[T] (producer of T)."""
|
||||
@@ -351,7 +351,7 @@ def test_p1_pass_finds_producer_of_Result_T() -> None:
|
||||
''')
|
||||
tree = ast.parse(source)
|
||||
producers = P1_pass(tree, file="synthetic.py")
|
||||
assert ("fetch", "FileItems", "producer", "high") in producers
|
||||
assert ("fetch", "FileItems", "producer", "high", 2) in producers
|
||||
|
||||
def test_p1_pass_skips_non_annotated_return() -> None:
|
||||
"""P1 returns [] for functions without return annotations."""
|
||||
@@ -371,7 +371,7 @@ def test_p2_pass_finds_consumer_of_T() -> None:
|
||||
''')
|
||||
tree = ast.parse(source)
|
||||
consumers = P2_pass(tree, file="synthetic.py")
|
||||
assert ("process", "Metadata", "consumer", "high") in consumers
|
||||
assert ("process", "Metadata", "consumer", "high", 2) in consumers
|
||||
|
||||
def test_p2_pass_finds_consumer_of_list_T() -> None:
|
||||
"""P2 detects a function whose parameter is list[T] (consumer of T)."""
|
||||
@@ -381,7 +381,7 @@ def test_p2_pass_finds_consumer_of_list_T() -> None:
|
||||
''')
|
||||
tree = ast.parse(source)
|
||||
consumers = P2_pass(tree, file="synthetic.py")
|
||||
assert ("aggregate", "FileItems", "consumer", "high") in consumers
|
||||
assert ("aggregate", "FileItems", "consumer", "high", 2) in consumers
|
||||
|
||||
def test_p2_pass_skips_untyped_parameter() -> None:
|
||||
"""P2 returns [] for parameters without type annotations."""
|
||||
@@ -401,7 +401,7 @@ def test_p3_pass_finds_consumer_via_subscript() -> None:
|
||||
''')
|
||||
tree = ast.parse(source)
|
||||
accesses = P3_pass(tree, file="synthetic.py", type_registry={})
|
||||
assert ("process", "path", "subscript", 1) in accesses
|
||||
assert ("process", "path", "subscript", 1, 2) in accesses
|
||||
|
||||
def test_p3_pass_finds_consumer_via_attribute() -> None:
|
||||
"""P3 detects a function that reads entry.attr; returns (function, attr, kind, count)."""
|
||||
@@ -411,7 +411,7 @@ def test_p3_pass_finds_consumer_via_attribute() -> None:
|
||||
''')
|
||||
tree = ast.parse(source)
|
||||
accesses = P3_pass(tree, file="synthetic.py", type_registry={})
|
||||
assert ("process", "path", "attribute", 1) in accesses
|
||||
assert ("process", "path", "attribute", 1, 2) in accesses
|
||||
|
||||
def test_p3_pass_counts_multiple_accesses() -> None:
|
||||
"""P3 counts multiple accesses to the same key within a single function."""
|
||||
@@ -423,7 +423,7 @@ def test_p3_pass_counts_multiple_accesses() -> None:
|
||||
''')
|
||||
tree = ast.parse(source)
|
||||
accesses = P3_pass(tree, file="synthetic.py", type_registry={})
|
||||
path_count = sum(c for fn, k, kind, c in accesses if fn == "process" and k == "path")
|
||||
path_count = sum(c for fn, k, kind, c, line in accesses if fn == "process" and k == "path")
|
||||
assert path_count == 2
|
||||
|
||||
def test_build_pcg_returns_result() -> None:
|
||||
|
||||
Reference in New Issue
Block a user