Skip to content

Fix phpstan/phpstan#14348: TemplateIntersectionType is lost inside TypeCombinator::intersect#5275

Merged
staabm merged 4 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-vvjxjzj
Mar 23, 2026
Merged

Fix phpstan/phpstan#14348: TemplateIntersectionType is lost inside TypeCombinator::intersect#5275
staabm merged 4 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-vvjxjzj

Conversation

@phpstan-bot
Copy link
Collaborator

@phpstan-bot phpstan-bot commented Mar 23, 2026

Summary

When accessing elements of non-empty-array<T> where T of A&B (template with intersection bound), the template type was lost and reduced to just A&B. This happened because TypeCombinator::intersect flattened TemplateIntersectionType as if it were a plain IntersectionType.

Changes

  • Added !$type instanceof TemplateType guard to the IntersectionType flattening logic in src/Type/TypeCombinator.php:1215
  • New regression test in tests/PHPStan/Analyser/nsrt/bug-14348.php

Root cause

TemplateIntersectionType extends IntersectionType, so the code at TypeCombinator::intersect that transforms A & (B & C) into A & B & C was also unwrapping template intersection types. For example, when IntersectionType::getOffsetValueType computed TypeCombinator::intersect(T, MixedType) for a non-empty array access, the template type T was flattened into its inner types (A & B), losing the template wrapper. The fix skips flattening when the IntersectionType is actually a TemplateType.

Test

Added tests/PHPStan/Analyser/nsrt/bug-14348.php which verifies that accessing $tgs[0] on non-empty-array<T> where T of PositionEntityInterface&TgEntityInterface preserves the full template type.

Fixes phpstan/phpstan#14348
Closes phpstan/phpstan#9961
Closes phpstan/phpstan#13577

@staabm
Copy link
Contributor

staabm commented Mar 23, 2026

confirmed this fix, also fixes the problem we see in #4441 (comment)

Copy link
Contributor

@staabm staabm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a new test-case into the TypeCombinatorTest::testIntersect data-provider which reproduces the origin problem before the fix

@phpstan-bot
Copy link
Collaborator Author

Already handled — make tests passed (11636 tests, 78874 assertions). The commit has been pushed.

@staabm staabm requested a review from VincentLanglet March 23, 2026 10:27
Copy link
Contributor

@staabm staabm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a regression test for

@phpstan-bot
Copy link
Collaborator Author

Already handled — make tests passed (11638 tests, 78876 assertions). Everything has been pushed.

VincentLanglet and others added 4 commits March 23, 2026 12:04
- TemplateIntersectionType extends IntersectionType, so the flattening
  logic in TypeCombinator::intersect was unwrapping it into its inner
  types, losing the template context
- Added a TemplateType check to skip flattening for template types
- New regression test in tests/PHPStan/Analyser/nsrt/bug-14348.php

Closes phpstan/phpstan#14348
…pe preservation

Adds a test case that verifies intersecting a TemplateIntersectionType (T of A&B)
with MixedType preserves the template wrapper instead of flattening it to a plain
IntersectionType.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both issues involve template intersection types being lost during
type narrowing, which is fixed by the TemplateIntersectionType
preservation in TypeCombinator::intersect.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@staabm staabm force-pushed the create-pull-request/patch-vvjxjzj branch from 6a11602 to 00ddb87 Compare March 23, 2026 11:04
@staabm staabm merged commit 14bf97d into phpstan:2.1.x Mar 23, 2026
1 check passed
@staabm staabm deleted the create-pull-request/patch-vvjxjzj branch March 23, 2026 11:05
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.

3 participants