Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,46 @@ All notable changes to this project are documented here. This project follows
(Principle VI), output changes that are *strictly more accurate* are MINOR, not
breaking.

## [Unreleased] — Control-Flow Graph Foundation

### Added — more accurate conditional analysis (MINOR)

The per-function control-flow graph (previously used only to annotate branches)
is now retained as a compact, queryable reachability + dominance model, and the
conditional-analysis consumers resolve over it instead of a source-text-position
heuristic. This sharpens several cases:

- **Conditional status codes** (the #39 / #50 / #57 pain) are computed
structurally: a status contributes iff its assignment can *reach* the response
write and is not overwritten on every path by a later, call-dominating
assignment. Mutually-exclusive `if`/`else` (and `switch`) arms fan out; an
early-`return` before the write is excluded; an unconditional overwrite shadows
earlier assignments. **A value assigned inside a loop body now reaches a write
after the loop** (the analysis terminates across the back-edge).
- **Helper-internal type-switch binding**: when a handler funnels a value into a
shared responder that `switch v.(type)`s on it, the call-site argument is bound
to the matching arm — `Respond(w, &NotFound{})` fans out only that arm's status,
not every arm. When the argument's concrete type is not statically known (a bare
`error`/`any`), the analyzer degrades to the unconditionally-reachable result
and emits a warning rather than over-approximating.
- **Method dispatch via `if r.Method == http.MethodPost`** now splits into one
operation per method, the same as a `switch r.Method` already did.
- **Branch-dependent response bodies** are attributed to the status on which they
are written (e.g. `FullUser`/200 vs `ErrorBody`/404), never merged.

Each behavior ships with its own targeted fixture (`cfg_helper_typeswitch`,
`cfg_loop_status`, `cfg_method_if_dispatch`, `cfg_branch_bodies`); the existing
framework golden corpus does not exercise these constructs, so it — and the
determinism suite — stays byte-identical.

### Changed — internals

- The conditional-status fan-out's source-position heuristic (`positionAfter`,
`positionLineCol`, and the "last unconditional index") was **removed** in favor
of the structural reachability predicate. When control flow cannot be modeled,
the analyzer falls back to the unconditionally-reachable (single-path) result
and warns — it never guesses a conditional set.

## [Unreleased] — TypeRef Metadata Integration (Phase 2)

### Added — more accurate schema output (MINOR)
Expand Down
13 changes: 13 additions & 0 deletions internal/engine/engine_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ func allFrameworks(t *testing.T) []frameworkTestCase {
{name: "writejson_helper", inputDir: "../../testdata/writejson_helper", configFn: spec.DefaultHTTPConfig},
{name: "error_switch_minimal", inputDir: "../../testdata/error_switch_minimal", configFn: spec.DefaultChiConfig},
{name: "error_switch_file_service", inputDir: "../../testdata/error_switch_file_service", configFn: spec.DefaultChiConfig},
// spec 009: a response helper that type-switches on its argument. The
// concrete-type route fans out only the matched arm; the imprecise route
// degrades to the default arm + warns (FR-011/FR-012).
{name: "cfg_helper_typeswitch", inputDir: "../../testdata/cfg_helper_typeswitch", configFn: spec.DefaultHTTPConfig},
// spec 009: a status assigned inside a loop body still reaches the response
// write (FR-010 — the loop back-edge terminates and the value contributes).
{name: "cfg_loop_status", inputDir: "../../testdata/cfg_loop_status", configFn: spec.DefaultHTTPConfig},
// spec 009 US2: an `if r.Method == …` dispatch splits into one operation per
// method, the same as a `switch r.Method` (FR-003).
{name: "cfg_method_if_dispatch", inputDir: "../../testdata/cfg_method_if_dispatch", configFn: spec.DefaultHTTPConfig},
// spec 009 US3: branch-dependent response bodies are attributed to the status
// on which they are written — 200/FullUser vs 404/ErrorBody, not merged (FR-005).
{name: "cfg_branch_bodies", inputDir: "../../testdata/cfg_branch_bodies", configFn: spec.DefaultHTTPConfig},
{name: "bodyless_status", inputDir: "../../testdata/bodyless_status", configFn: spec.DefaultHTTPConfig},
{name: "wrapped_response", inputDir: "../../testdata/wrapped_response", configFn: spec.DefaultHTTPConfig},
{name: "echo_handler_factory", inputDir: "../../testdata/echo_handler_factory", configFn: spec.DefaultEchoConfig},
Expand Down
Loading
Loading