Skip to content

Extract Type::truncateListToSize() from TypeSpecifier#5713

Merged
ondrejmirtes merged 1 commit into
2.1.xfrom
truncate-array-to-size
May 19, 2026
Merged

Extract Type::truncateListToSize() from TypeSpecifier#5713
ondrejmirtes merged 1 commit into
2.1.xfrom
truncate-array-to-size

Conversation

@ondrejmirtes
Copy link
Copy Markdown
Member

Summary

TypeSpecifier::specifyTypesForCountFuncCall had ~100 lines of inline shape-specific narrowing — rebuild as N-element list, build with required prefix + optional middle, probe hasOffsetValueType for unbounded max, intersect with HasOffsetValueType accessories for non-CAT lists, plus several LIMIT bail-outs that returned the original array. Five interleaved branches sharing the same outer count()-call / size-superType filters, hard to read.

This PR extracts the per-array rebuild into Type::truncateArrayToSize(Type \$sizeType): Type:

  • ConstantArrayType: required prefix [0, min), optional middle [min, max) when max is set, or probe explicit offsets via hasOffsetValueType until no when max is unbounded.
  • ArrayType: same prefix/middle for bounded ranges; for unbounded max, intersect with HasOffsetValueType accessories.
  • Non-array types (NonArrayTypeTrait, MaybeArrayTypeTrait): return ErrorType.
  • LateResolvableTypeTrait: delegate to the resolved type.
  • UnionType / IntersectionType / StaticType: dispatch.
  • NeverType / MixedType / accessory types: identity-ish — the accessories represent metadata orthogonal to size, so the narrowing flows through IntersectionType's dispatcher unchanged.

isList() gate stays at the call site

The per-array methods deliberately do not check isList(). The outer aggregate gate stays at the call site — a CAT inside a non-empty-list<T> intersection may have its own isList() weakened to Maybe even though the aggregate is definitely a list. Hoisting the check into the methods broke ~30 tests; restoring it at the call site preserved exact behavior.

Helper

A private static helper ConstantArrayType::extractTruncateBounds() peels [min, max] out of either a ConstantIntegerType ([N, N]) or an IntegerRangeType, sparing both implementations the same two-line shape check. The Type signature accepts Type \$sizeType (not IntegerRangeType) because IntegerRangeType::fromInterval(N, N) collapses to ConstantIntegerType.

Behavior preserved.

Test plan

  • make tests — all 12,170 tests pass, no regressions vs. origin/2.1.x

🤖 Generated with Claude Code

@ondrejmirtes ondrejmirtes force-pushed the truncate-array-to-size branch from eead4a2 to 0b52def Compare May 19, 2026 15:00
@ondrejmirtes ondrejmirtes changed the title Extract Type::truncateArrayToSize() from TypeSpecifier Extract Type::truncateListToSize() from TypeSpecifier May 19, 2026
`specifyTypesForCountFuncCall` had ~100 lines of inline shape-specific
narrowing (rebuild as N-element list, build with required prefix +
optional middle, probe `hasOffsetValueType` for unbounded max,
intersect with `HasOffsetValueType` accessories for non-CAT lists,
plus several `LIMIT` bail-outs that returned the original array). The
five branches were interleaved with the same outer `count()`-call /
size-superType filters, making the per-shape logic hard to read.

Push the per-list rebuild into a new `Type::truncateListToSize(Type
$sizeType): Type`:

  - `ConstantArrayType`: required prefix `[0, min)`, optional middle
    `[min, max)` when `max` is set, or probe explicit offsets via
    `hasOffsetValueType` until `no` when `max` is unbounded.

  - `ArrayType`: same prefix/middle for bounded ranges; for unbounded
    `max`, intersect with `HasOffsetValueType` accessories.

  - Non-array types (`NonArrayTypeTrait`, `MaybeArrayTypeTrait`): return
    `ErrorType`.

  - `LateResolvableTypeTrait`: delegate to the resolved type.

  - `UnionType` / `IntersectionType` / `StaticType`: dispatch.

  - `NeverType` / `MixedType` / accessory types: identity-ish — the
    accessories represent metadata orthogonal to size, so the narrowing
    flows through `IntersectionType`'s dispatcher unchanged.

The method is named for its actual contract: each implementation
assumes a list shape. The call site (`TypeSpecifier`) is responsible
for gating on outer list-ness — a CAT inside a `non-empty-list<T>`
intersection may have its own `isList()` weakened to `Maybe` even
though the aggregate is definitely a list. Letting the call site
decide preserves the original behavior exactly.

A private static helper `ConstantArrayType::extractTruncateListBounds()`
peels `[min, max]` out of either a `ConstantIntegerType` (`[N, N]`) or
an `IntegerRangeType`, sparing both implementations the same two-line
shape check.

Behavior preserved: full test suite green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ondrejmirtes ondrejmirtes force-pushed the truncate-array-to-size branch from 0b52def to b3ead90 Compare May 19, 2026 15:09
@ondrejmirtes ondrejmirtes merged commit 852f99c into 2.1.x May 19, 2026
655 of 659 checks passed
@ondrejmirtes ondrejmirtes deleted the truncate-array-to-size branch May 19, 2026 15:20
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