From bf26f73e45297620cb594aed1760b4c8795f8744 Mon Sep 17 00:00:00 2001 From: David Meister Date: Sat, 15 Aug 2026 12:48:19 +0000 Subject: [PATCH] Seed `suiteNames`'s named return before the loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `suiteNames` declared `names` and assigned it only inside `for (uint256 i = 0; i < suites.length; i++)`. Judged from this function's body alone, `suites.length == 0` reaches the closing brace with `names` unassigned and returns the empty string. What makes that path unreachable is `checkedCandidateSuites` reverting `NoDeployCandidates`, two functions away through `allSuites` — so the return is total only via a guard this reader cannot see. Seeding `names = ""` before the loop makes the assignment provable from this body. The loop and its `i == 0` seed are untouched: with an empty seed, `string.concat("", ", ", suites[0].suite)` would prefix the list with a comma, so the ternary is still load-bearing. No early return on the empty set. `checkedCandidateSuites`' NatSpec and `testNoCandidateReverts` both require every reader to refuse an empty declaration rather than answer from one, and an `if (suites.length == 0) { return names; }` would write that answer into the source as unreachable code. Behaviour is unchanged on every reachable input, so the existing `testSuiteNamesIsTheRegistry` and `testNoCandidateReverts` remain the coverage. Closes https://github.com/rainlanguage/rain.deploy/issues/72 Co-Authored-By: Claude Opus 5 (1M context) --- src/abstract/RainDeploySuitesBase.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/abstract/RainDeploySuitesBase.sol b/src/abstract/RainDeploySuitesBase.sol index 24785f5..a875bd5 100644 --- a/src/abstract/RainDeploySuitesBase.sol +++ b/src/abstract/RainDeploySuitesBase.sol @@ -197,6 +197,10 @@ abstract contract RainDeploySuitesBase { /// @return names The declared keys. function suiteNames() internal pure returns (string memory names) { DeploySuite[] memory suites = allSuites(); + // Seeded here so every path out of this function assigns `names`, + // provable from this body alone. `allSuites` never yields an empty set, + // but that is a fact about another function. + names = ""; for (uint256 i = 0; i < suites.length; i++) { names = i == 0 ? suites[i].suite : string.concat(names, ", ", suites[i].suite); }