Skip to content

fix(hir): infer bool && value as the value's type, not Boolean (#3527) - #3771

Merged
proggeramlug merged 1 commit into
mainfrom
fix/3527-logical-and-value-type
May 31, 2026
Merged

proggeramlug merged 1 commit into
mainfrom
fix/3527-logical-and-value-type

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

infer_type_from_expr mis-typed a logical A && B / A || B as A's type whenever B inferred to Any. But && yields B's value when A is truthy, so the result can be the (unknown) right value — A's type is wrong. This advances the #3527 Express compile past object-inspect.

Root cause

A textbook feature-detect idiom:

var hasMap = typeof Map === "function" && Map.prototype;  // inferred Boolean (!)

hasMap holds an object at runtime but was typed Boolean (the left operand's type). Codegen then treated it as a boolean in object-inspect's later chain:

var mapSize = hasMap && d && typeof d.get === "function" ? d.get : null;

miscompiling the short-circuit and dereferencing null → TypeError: Cannot read properties of null (reading 'get') during init while compiling Express. (Minimal repro: var hm = typeof Map === "function" && Map.prototype; hm && null && (null).get throws; the same chain with hm typed any does not.)

Fix

In lower_types.rs, for LogicalAnd | LogicalOr: when the right operand is Any, the result is Any (the value could be that right operand), not the left operand's type. The concrete-right heuristic (x && knownTyped → knownTyped) is unchanged. This only makes the inferred type more dynamic — always codegen-safe, never less.

Verification

  • object-inspect's init now works; the Express tree advances past it.
  • New gap test test_gap_logical_and_value_type.ts (the hasMap idiom, &&/|| returning the right value, chained property guards) — byte-identical to Node.
  • cargo test -p perry-hir green. Pre-existing failures (bind_replace, async_hooks/perf_hooks timestamp nondeterminism) verified identical on baseline — not caused by this change. cargo fmt --check clean.

Part of the #3527 chain (prior: #3537/#3543/#3546, #3551, #3733, #3751, #3764). No version bump / changelog — folded in at merge per the worktree-PR convention.

`infer_type_from_expr` typed a logical `A && B` / `A || B` as A's type
whenever B inferred to `Any`. But `&&` yields B's value when A is truthy, so
the result can be the (unknown) right value — A's type is wrong. This mistyped
a common idiom:

    var hasMap = typeof Map === "function" && Map.prototype;  // → Boolean (!)

`hasMap` holds an object at runtime but was typed `Boolean`, so codegen treated
it as a boolean in a later `hasMap && d && typeof d.get === "function"` chain
(object-inspect's Map-size feature-detect), miscompiling the short-circuit and
dereferencing `null` → `TypeError: Cannot read properties of null (reading
'get')` while compiling Express (#3527).

Fix: when the right operand is `Any`, the logical result is `Any` (the value
could be that right operand), not the left operand's type. The concrete-right
heuristic (`x && knownTyped` → knownTyped) is unchanged. This only makes the
inferred type more dynamic (always codegen-safe), never less.

Verified: object-inspect's init works; the Express tree advances past it.
New gap test test_gap_logical_and_value_type.ts is byte-identical to Node.
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