Skip to content

exclude noreturn, undefined and @compileError peers in branchin… - #3242

Open
DaliVana wants to merge 1 commit into
zigtools:masterfrom
DaliVana:exclude-noreturn-peers
Open

exclude noreturn, undefined and @compileError peers in branchin…#3242
DaliVana wants to merge 1 commit into
zigtools:masterfrom
DaliVana:exclude-noreturn-peers

Conversation

@DaliVana

@DaliVana DaliVana commented Aug 6, 2026

Copy link
Copy Markdown

Fixes #3210

switch and if type resolution now ignores branches that never produce a value, mirroring Zig's peer type resolution: unreachable/break/continue/return (noreturn values), undefined, and @compileError(...) no longer prevent the remaining peer from determining the result. All four examples from the issue now resolve to S.

Root cause

Two defects, both around Type.fromEither:

  1. resolvePeerTypes opens with if (a.is_type_val or b.is_type_val) return null;, so the existing exclusions in resolvePeerTypesInternal (.compile_error => return b, .noreturn => return b) never run when one peer is a type like S. (This is why x orelse unreachable already worked — no type-valued peer there.)
  2. After PTR bails, the either-construction in fromEither returns null on any is_type_val mismatch, so a noreturn/undefined value entry next to the type S poisoned the whole result (issue examples B and C resolved to nothing). .compile_error entries were skipped by the mismatch check but still entered the deduplicator, producing the messy either { S, @compileError(...) } from example D.

Fix

fromEither filters never-producing entries up front: .compile_error entries, and .ip_index entries whose type is .noreturn_type or .undefined_type. Keying on payload.type (not payload.index) keeps the literal type values noreturn/undefined as valid peers (if (cond) u8 else noreturn still resolves to an either type). The now-dead .compile_error special-casing in the either-construction is removed.

Design notes:

  • The filter runs after the entries.len == 1 early return, so single-branch resolution (if without else) is unchanged.
  • If every branch is filtered out, an undefined branch determines the result; otherwise a noreturn-typed entry is preferred over .compile_error so the result does not depend on branch order ({ unreachable, @compileError("") } resolves to noreturn in both arm orders; all @compileError still resolves to the .compile_error type for richer hover output).
  • resolvePeerTypes' is_type_val guard is intentionally left untouched to keep the change scoped to branching constructs; hoisting the noreturn/compile_error exclusions above that guard could be a follow-up.

Tests

  • tests/analysis/switch.zig: the issue's repro shapes (unreachable/undefined/@compileError arms each resolving to (type)(A)), plus all-noreturn switches in both arm orders and an unreachable+undefined switch pinning the fallback ordering.
  • tests/analysis/either.zig: the four existing if-based assertions encoded the buggy behavior ((unknown)()/(either type)()) and now expect (i32)(); a new case pins that the literal type value noreturn is not filtered.
  • tests/analysis/peer_type_resolution.zig: undefined_0/undefined_1 value-level pair alongside the existing noreturn_0/noreturn_1, since the fromEither filter is the only mechanism that excludes undefined next to a non-ip_index peer (resolvePeerTypesInternal has no .undefined arm).

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.

branching type resolution doesn't exclude noreturn and other coercible values

1 participant