Skip to content

fix(switch): narrow the subject after a switch without default whose cases all return - #12972

Open
jirkace wants to merge 1 commit into
vimeo:masterfrom
jirkace:fix/3373-switch-all-cases-return
Open

jirkace wants to merge 1 commit into
vimeo:masterfrom
jirkace:fix/3373-switch-all-cases-return

Conversation

@jirkace

@jirkace jirkace commented Nov 1, 2026

Copy link
Copy Markdown

After a switch without default whose cases all return or throw, the subject keeps its null from a case null:, so PossiblyNullArgument is reported.

Before

function f(?string $type): int {
    switch ($type) {
        case null:
            return 0;
        case "a":
            return 1;
    }

    throw new InvalidArgumentException(sprintf("Unknown %s", $type)); // PossiblyNullArgument
}

After

    throw new InvalidArgumentException(sprintf("Unknown %s", $type)); // no issue

Fixes #3373

…cases all return vimeo#3373

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

jirkace commented Nov 1, 2026

Copy link
Copy Markdown
Author

Behaviour change

  • After a switch without default in which every case returns or throws, the subject variable has the type left after negating all case conditions (?string after case null: is string; "a"|"b"|"c" after cases "a", "b" is "c"), so PossiblyNullArgument and similar issues after the switch disappear.
  • Unchanged: a switch where some case breaks or falls out keeps the previous type (guard test); a switch with default or an exhaustive set of cases.
  • Known imprecision that this exposes in one more place, not changed here: case null: is negated as "truthy", so ?string becomes non-falsy-string ("0" is lost), exactly as it already is in default: and after if ($x == null) return;.
  • No existing test expectation was edited.
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

✅ Performance: unchanged · Issues: unchanged

Project Memory (MB) Wall (s) Issues Weighted
✅ laravel 1 916 121.8 $\color{gray}{\textsf{-0.1\%}}$ 30 863 51 878
✅ symfony 3 387 200.0 $\color{gray}{\textsf{+2.4\%}}$ 52 910 88 509
✅ doctrine-orm 444 18.3 $\color{gray}{\textsf{-0.2\%}}$ 4 601 5 589
✅ psl 665 22.9 $\color{gray}{\textsf{+0.4\%}}$ 3 625 5 577
✅ phpunit 528 19.2 $\color{gray}{\textsf{+4.8\%}}$ 4 196 1 288
✅ php-parser 255 18.5 $\color{gray}{\textsf{+2.0\%}}$ 18 020 20 643
✅ amphp-amp 59 1.6 $\color{gray}{\textsf{-0.6\%}}$ 69 79
✅ webmozarts-assert 269 3.5 $\color{gray}{\textsf{-2.2\%}}$ 642 2 717
✅ drupal 5 398 207.5 $\color{gray}{\textsf{+1.8\%}}$ 107 696 162 791
Total 12 919 613.5 $\color{gray}{\textsf{+1.6\%}}$ 222 622 339 071
How to read this table
  • Memory: Psalm's own peak figure, threads=1. More than +5 % fails, unless the PR changes composer.json.
  • Wall: base and head run on the same runner; ±10 % is noise. Above +20 % warns, never fails. re-run: a reversed second run was taken and the faster kept.
  • Issues: head count, then $\color{red}{\textsf{+added}}$ $\color{green}{\textsf{-removed}}$ against base.
  • Weighted: issues weighted by error level (levels 1–8 as is, always-errors 10, feature-specific and purity suggestions 0). The projects' own code is pinned and their dependencies resolve identically for base and head, so a change means Psalm reports more, fewer or differently serious issues. A rise is not necessarily a regression.
  • Total: sum over the projects compared on both sides. Thresholds apply per project, not to the total.
  • Colors: $\color{red}{\textsf{red}}$ past a fail or warn threshold (or more issues), $\color{green}{\textsf{green}}$ an improvement past noise (or fewer issues), $\color{gray}{\textsf{gray}}$ noise.
  • Performance: regressed if a head run crashed or timed out or a memory or wall delta is red, else improved if one is green, else unchanged. Its icon is the check result: ❌ fail · ⚠️ warn · ✅ pass; the icon next to each project is that project's result, 💥 = not comparable: base crashed or timed out (counts as ⚠️).
  • Issues (heading): the total weighted change, increased · decreased · unchanged. Informational, never fails the check.

head 873343b · base 7cce3b0 (merge-base) · workflow run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant