Skip to content

Operator typing and request-position if/switch: refuse the pairs and forms TradingView refuses, evaluate block-valued selections in the request's own context - #308

Merged
luisleo526 merged 8 commits into
mainfrom
fix/operator-typing-and-request-position
Nov 2, 2026
Merged

luisleo526 merged 8 commits into
mainfrom
fix/operator-typing-and-request-position

Conversation

@luisleo526

Copy link
Copy Markdown
Contributor

This pull request combines two reviewed changes onto the current main. Each section below is one change; the two share the diagnostics catalog and its digest pin, so their codes are numbered together (PF-E2076 to PF-E2078, PF-E3102 to PF-E3105, PF-E5083).

Part 1. Operator typing: refuse an operator pair TradingView refuses, at the operand

What changes

One analyzer relation now types every unary and binary operator, the ?: condition and the compound assignment. A pair of operand types is refused when TradingView refuses it, at the operand TradingView names, and only when both types are proven by a certain source: a literal, a declared type, a typed parameter, a structured collection type, or a builtin call whose overload is resolved from its arguments. A coarse registry result, void and unknown stay unresolved and are accepted, as before.

  • A comparison always has type bool.
  • A history read of an array, matrix, map or user-type field used as an operator operand is refused at the operand, including when the other operand is unresolved, and including a collection bound through a declaration or builtin result whose scalar type the relation cannot derive (for example str.split).
  • A math function result is a number of open width: it fits every numeric operand slot and fails the string and bool slots; int against float is never refused.
  • A function's free names resolve where the function is defined, so a caller's block-local cannot change what a callee reads.
  • A while condition of type na, and a switch discriminator that fits none of its case types, are refused.
  • The proofs live in a side table that only the refusal relation reads. Symbol types, return types and storage facts that code generation reads keep their previous values.

No emitted-C++ change from this part

The operator relation touches no file under pineforge_codegen/codegen. Over the pinned population (1,384 sources), the second wave (1,939 sources) and 52 import pairs: no source changes its generated C++ because of it. The only status changes are sources TradingView also refuses: five (string compared with a number) were translated by main and failed to build; two keep their refusal and only the reported column moved.

New diagnostic codes

PF-E2076 (numeric operand), PF-E2077 (?: condition must be bool), PF-E2078 (operator cannot combine the two operand types). The message of PF-E2066 now names the operand roles.

Pins that flipped from accepted to refused (22)

All are forms TradingView refuses (its reply is cited per row in the test fixtures).
1-13: a direct comparison with na, thirteen spellings (string, string field, array; both operand orders; a call result).
14-18: if na, na ? true : false, na and true, na or true, not na (the valid runtime control now uses an explicit bool).
19: na ? 1 : 0.
20: the na condition row of the numeric-condition test.
21: a bool source compared with != 0 in the lazy-clock test (the valid control uses != false).
22: the shadowed builtin in the finite-length test: a bool compared with an int is now refused at the comparison instead of translated.
Also: the a > b == c parameter row now isolates the equality after a valid numeric comparison, and the original bool source is restored as its own refusal row. Four refusal locations were re-pinned to the operand's first token (22 to 13, 31 to 26, 15 to 9 twice); they have no exact-form position reply and are not claimed as measured column matches.

Newly refused without an exact reply of that form

Refused as unproven pairs, witnessed by the operator family replies: an operator on a user object returned by a call; box, table, map and matrix operands under arithmetic and ordering; a string or bool under - * / %; a proven float ?: condition; a switch whose discriminator and cases mismatch other than string against int; enums compared across types under ==. Replies for most of these exist (string and bool arithmetic, box, table, map, matrix, float condition, switch int against string, enum across types), all refused with the same code.

Not in part 1

  • A function parameter typed int called with math.abs(2): TradingView accepts it, PineForge refuses it (PF-E2073): a recorded compile gap.
  • The history of an array parameter is refused by its own older code (PF-E6073) although TradingView accepts the form: a recorded gap.
  • A top-level block binding that shadows a script binding of another type is refused (pre-existing), although TradingView accepts it: a recorded gap.
  • Missing-string storage (string s = na; s == ""), float x = "1" and its siblings, the missing-string concatenation lowering, assignability and the bool wrapper typing are not claimed and stay open.
  • The five math acceptance controls (math.round(close) + 1.5, math.abs(x) * 2, math.max(a, b) > 3, math.max(1, 2.0) * 3, math.round(close) % 2 == 0) are accepted by TradingView and by the relation, and translate on both trees.
  • "a" + close position and the nested a[1][0] location are not compared against an exact reply.
  • Not covered by a row: a request.security call returning a collection, a tuple-destructured array local in a function body, an int-typed slot fed by math.round, and the enum-across-types row beyond its reply.

Part 2. Request-position if/switch: evaluate a block-valued selection in the request's own context, refuse the forms not measured against TradingView

The defect

A global bound to an if or switch whose arms carry statements and used as a request expression was rendered by the chart renderer inside the request evaluator: bar_index read the chart's counter, one bar behind the request's own counter, and block locals were chart members written from the evaluator. The request therefore returned the previous bar's value for such a source (a silent wrong value for a source that compiled), and several block-armed forms failed in g++ or were refused although TradingView accepts them.

What changes

  • The selection is lowered through the request-context expression renderer; block locals are evaluator-local; no chart member is written from the evaluator. var and varip writes are persisted by one lowering shared by if and switch arms.
  • Pure-expression arms keep their previous route unchanged.
  • Located refusals with catalog codes (PF-E3102 to PF-E3105, PF-E5083): a write to a plain binding declared outside a requested selection (assignment, compound assignment, collection mutation, or a helper write); a request expression that yields no value (a void arm among value arms); a requested timeframe selection with an empty-string arm; the history of a block-local; a stateful switch. The last two are named gaps.
  • An if/switch in an inline switch arm, a ternary operand, a for bound, a type field default or an enum title is refused at its first token, as TradingView refuses it (its replies: inline arm CE10013, for bound CE10161, field default and enum title CE10133, ternary operand CE10150 for the keyword in an operand position).
  • A numeric arm is no longer typed as a string. The string series storage is registered only for a string var declared inside the selection being lowered, so a plain request.security call gains no member, checkpoint field or shifted slot.
  • A timeframe.* member read is typed from the signature table (previously every member was a string): timeframe.multiplier is an int, the is* members are bool, period and main_period stay strings. This changes the inferred type of every chart expression that reads one of those members.

Measured results (TradingView replies and the engine on the same context)

  • The three value-bearing rows that were wrong or failing now equal TradingView on all seven entries: a value if with a block arm, a single-expression if bound to a global, and a global read through a second global (main returned a constant for the last).
  • One form TradingView accepts that main refused now compiles and equals its TradingView reply: a helper whose single statement is a block if.
  • 58 value keys: 55 equal to TradingView, 3 not run (two block-local history forms refused by design, and one form TradingView refuses). The 31 keys that were equal before are unchanged.
  • Population (1,384 sources): one source changes its generated C++: a comparison of two plain doubles takes Pine's float comparator (the band the library already uses for series operands) after the timeframe.* typing correction; its five probes grade excellent with identical trades on both sides. Second wave (1,939): no C++ change from this part; seven sources TradingView refuses are now refused at the parser instead of failing to build. 52 import pairs: no change.

Not in part 2 (existing gaps, tracked as follow-ups)

  • A plain binding written inside a requested arm: TradingView accepts the form; it is refused here with a located code (thirteen measured forms); the value semantics are not uniform and are unmeasured beyond them.
  • The history of a block-local in a request position (two forms) is refused; TradingView accepts it.
  • A requested timeframe selection with an empty-string arm is refused (three forms); TradingView accepts them. An earlier build that did not refuse these forms produced a value equal to TradingView's at one default-input point; the value matrix is measured by TradingView but not yet compared, so these stay refused here.
  • The same mixed value and void if outside a request is accepted by this compiler although TradingView refuses it; it is unchanged by this change.
  • A selection whose symbol or ignore qualifier is chosen by a block is not changed.

Verification

Both parts were reviewed on their own heads and again as the union on this head (two merge commits, no rewritten commit).

  • Suite: 11,529 passed, 0 failed on this head without the engine tests (the base run of the same command: 10,114 passed, 0 failed; no test fails only on the head). The full suite with the engine tests: 16,462 passed, 221 skipped, 0 failed.
  • The corpus compile (312 strategies) passes and the 268-fixture parity gate passes.
  • The gate sweep over the pinned population (134 cases) against the current baseline passes for this exact head: no improvement and no regression.
  • Before and after translation against the current main: population 1,384 sources: 1,373 identical output, 10 refused on both sides, one source changes its generated C++ (the plain-double comparison named in part 2); second wave 1,939 sources: no generated C++ changes, twelve sources move from translated to refused (seven by part 2, five by part 1), all twelve are sources TradingView refuses; two sources keep their refusal and only the reported column moves; 52 import pairs identical.

🤖 Generated with Claude Code

luisleo526 and others added 8 commits November 2, 2026 09:51
…perand

One analyzer relation types every unary and binary operator, the ?: condition
and the compound assignment. A pair is refused only when both operand types
come from a certain source (a literal, a declared hint, a typed parameter, a
structured type, a variant-resolved builtin call); coarse registry results,
VOID and UNKNOWN stay unresolved and are accepted. The proofs live in a side
table that only the refusal relation reads: the symbol types, callable return
types and storage facts that code generation reads keep their previous values,
so no emitted C++ changes.

A direct comparison with na is refused from the comparison start. A history
read of an array, matrix, map or user-type field used as an operator operand
is refused, and twenty such sources are pinned with their TradingView code
family. A proven operand that fits no overload of an operator is refused
whatever the other operand is, so a collection read beside an unresolved
operand no longer reaches code generation. A comparison always has bool type.
The call-site check resolves a function's free names in its definition scope.
A while condition of type na and a switch discriminator that fits no case type
are refused as TradingView refuses them. The operand-pair refusal has its own
catalog code, pinned in the digest fixture.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The positional-argument change keeps PF-E2074 on main; the operator typing codes stay
PF-E2076 to PF-E2078. The catalog, its digest pin and the code-delta list carry both sets.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…request's own context, refuse the forms not measured against TradingView

A global bound to an if or switch whose arms carry statements, used as a request
expression, was rendered by the chart renderer inside the request evaluator: bar_index
read the chart counter (one bar behind the request's own) and block locals were chart
members written from the evaluator. The selection is now lowered through the
request-context renderer with evaluator-local block locals; var and varip writes are
persisted by one lowering shared by if and switch arms.

An if or switch placed in an inline switch arm, a ternary operand, a for bound, a type
field default or an enum title is refused at its first token, as TradingView refuses it,
and an unlowerable value stops generation with a located refusal instead of emitting a
placeholder.

Refused with located catalog codes: a write to a plain binding declared outside a
requested selection (assignment, compound assignment, collection mutation, or a helper
write); a request expression that yields no value; a requested timeframe selection with
an empty-string arm (its value against TradingView is not measured). A numeric arm is no
longer typed as a string. The string series storage is registered only for a string var
declared inside the selection being lowered, so a plain request.security call gains no
member, checkpoint field or shifted checkpoint slot. A timeframe member read is typed from
the signature table (multiplier int, the is* members bool).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…E3105 and PF-E5083 in the catalog and the digest pin

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…al rows, timeframe member types, grammar slots

Expected quantities come from TradingView tapes and formulas, not from engine output.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

# Conflicts:
#	tests/test_diagnostic_notes.py
Three test comments pointed at probe directories that exist only on the
machine that measured them. Each now states the reply itself.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@graphify-labs graphify-labs Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View in Graphify

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.

Not checked: tests were not run; formal verification proved 0 of 48 changed function(s) (1 sampled, not proven, 47 not verified).

Formal verification. PR-changed functions: 1/48 verified (0 proven, 1 may-equivalent, 0 distinguished) · 47 not verified (1 vacuous, 23 unsupported, 23 over cap).

Not verified on this run: \_generate (vacuous: never exercised), \_\_init\_\_ (unsupported), \_check\_incompatible\_binary\_operands (unsupported), \_numeric\_condition\_type (unsupported), \_visit\_Assignment (unsupported), \_visit\_BinOp (unsupported), \_visit\_FuncCall (unsupported), \_visit\_FuncDef (unsupported), \_visit\_IfStmt (unsupported), \_visit\_MemberAccess (unsupported), and 14 more; 23 changed function(s) beyond the cap and 0 skipped when the time budget ran out.


Graphify review — findings

Adds a single operator-typing relation to the analyzer that checks ?:, compound assignments (+=, -=, …) and switch-case == operands from lexical type facts and written-call facts instead of a float fallback. Mismatches are reported at the operand's first token, and an unresolved declaration stays unresolved rather than recovering a storage type. The shared _check_operator_operands path now carries unary, not and binary mismatch reporting, and if and ternary conditions go through the same ?: lookup, so a missing (na) condition is rejected too and v5 library definitions are skipped. Converts a CodegenInvariantError raised during code generation into a CompileError with an "Internal error: no value lowering for AST node" diagnostic at the node's location. If the node has no location, it falls back to a plain RuntimeError.

Worth a look

  • Unary operator mismatch crashes on unpacking operands[-2:] — pineforge_codegen/analyzer/base.py · Escalate · high
    • NOT verified (no proof, no reproducing execution) — needs human review
  • Specific binary diagnostics also emit the generic 'cannot combine' error — pineforge_codegen/analyzer/base.py · Escalate · medium
    • NOT verified (no proof, no reproducing execution) — needs human review
  • not on a non-bool operand emits two contradictory errors — pineforge_codegen/analyzer/base.py · Escalate · medium
    • NOT verified (no proof, no reproducing execution) — needs human review
  • Cached operand type is reused for Identifiers even when per-call parameter_types are supplied — pineforge_codegen/analyzer/base.py · Escalate · medium
    • NOT verified (no proof, no reproducing execution) — needs human review

Review partial — this diff was larger than one review pass covers, so later files were not reviewed; some findings may be missing.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 5335 functions depend on the 2153 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: Parser — 160 callers, 42 callees
  • worse: Analyzer — 72 callers, 13 callees
  • worse: CodeGen — 48 callers, 12 callees
  • worse: resolve() — 22 callers, 3 callees
  • new: resolve_operator() — 16 callers, 3 callees
  • new: expression() — 3 callers, 8 callees
  • new: _generator() — 3 callers, 7 callees
  • new: walk() — 1 callers, 9 callees
  • …and 13 more — each is listed as a finding

Verification — 5335 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 5320 function(s) in the blast radius were not formally verified this run

Cross-repo impact

Cross-repo impact

No cross-repo impact detected among the indexed peer repo(s).

1 peer repo(s) in this group are not yet indexed — cross-repo impact may be incomplete.

Cross-repo linkage is derived (SCIP-moniker matches are sound; name/contract matches are heuristic) and ADVISORY — it never affects this check's conclusion.

Docs that may be stale (advisory)

…and 10 more.

Formal verification

Could not verify: Could not verify \_generate.

The verifier did not have enough to check \_generate, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 241 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly CompileError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_\_init\_\_.

The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_check\_incompatible\_binary\_operands.

The verifier did not have enough to check \_check\_incompatible\_binary\_operands, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_numeric\_condition\_type.

The verifier did not have enough to check \_numeric\_condition\_type, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_Assignment.

The verifier did not have enough to check \_visit\_Assignment, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_BinOp.

The verifier did not have enough to check \_visit\_BinOp, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_FuncCall.

The verifier did not have enough to check \_visit\_FuncCall, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_FuncDef.

The verifier did not have enough to check \_visit\_FuncDef, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_IfStmt.

The verifier did not have enough to check \_visit\_IfStmt, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_MemberAccess.

The verifier did not have enough to check \_visit\_MemberAccess, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_MethodDef.

The verifier did not have enough to check \_visit\_MethodDef, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_Subscript.

The verifier did not have enough to check \_visit\_Subscript, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_SwitchStmt.

The verifier did not have enough to check \_visit\_SwitchStmt, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_Ternary.

The verifier did not have enough to check \_visit\_Ternary, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_UnaryOp.

The verifier did not have enough to check \_visit\_UnaryOp, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_VarDecl.

The verifier did not have enough to check \_visit\_VarDecl, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_visit\_WhileStmt.

The verifier did not have enough to check \_visit\_WhileStmt, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify analyze.

The verifier did not have enough to check analyze, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_handle\_user\_func\_call.

The verifier did not have enough to check \_handle\_user\_func\_call, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_\_init\_\_.

The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_generate\_cpp.

The verifier did not have enough to check \_generate\_cpp, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_build\_security\_expr.

The verifier did not have enough to check \_build\_security\_expr, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

No difference found (not proven): No behavior difference found in \_check\_security\_tf\_arms (not a proof).

The verifier ran both versions of \_check\_security\_tf\_arms on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: the synth_receiver tier. A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify \_emit\_security\_evaluator\_body.

The verifier did not have enough to check \_emit\_security\_evaluator\_body, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify \_emit\_security\_linear\_helper\_call\_scoped.

The verifier did not have enough to check \_emit\_security\_linear\_helper\_call\_scoped, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

· 5 grounded finding(s) anchored inline below; 16 more finding(s) on lines outside this diff (see the check run).

return TypeSpec(left.kind, name=left.name or right.name, **arguments)


def resolve_operator(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression — resolve_operator()

16 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

def fail(value, kind, detail):
self._security_selection_refusal(value, kind, detail)

def expression(value, env):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression — expression()

fans out to 8 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

loc=value.loc) for a, b in zip(left, right)]
fail(owner, "statement", "tuple declaration with unresolved elements")

def block(body, outer):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression — block()

fans out to 9 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

states = dict(saved["states"]) if saved is not None else {}
types = dict(saved["types"]) if saved is not None else {}

def walk(value, env):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression — walk()

fans out to 9 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

if stmt.target.name in local:
local[stmt.target.name] = origin(stmt.value, local)

def walk(value, local):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression — walk()

fans out to 8 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@luisleo526
luisleo526 merged commit 4fd63ec into main Nov 2, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant