Skip to content

Fix #12280: Analysis depends on order of union type#5098

Closed
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-xtb3yxo
Closed

Fix #12280: Analysis depends on order of union type#5098
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-xtb3yxo

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When accessing a property on a union type where the members have different visibilities (e.g., A|B where A::$date is public and B::$date is private), PHPStan incorrectly reported "Access to protected property" depending on the order of types in the union.

Changes

  • Added getProperties() getter to src/Reflection/Type/UnionTypePropertyReflection.php to expose inner property reflections
  • Modified canReadProperty() and canWriteProperty() in src/Analyser/MutatingScope.php to check each inner property's accessibility individually when given a UnionTypePropertyReflection
  • Added regression test in tests/PHPStan/Rules/Properties/data/bug-12280.php and tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php

Root cause

UnionTypePropertyReflection::isPublic() uses AND semantics across all inner properties — it returns true only if ALL inner properties are public. Similarly for isPrivate(). When one property is public and another is private, both return false, causing canAccessClassMember() to fall through to the "protected" branch.

Additionally, getDeclaringClass() returns only the first property's declaring class, making the protected-access check order-dependent: with A|self, A's declaring class was checked (not related to current class B → error); with self|A, B's declaring class was checked (matches current scope → no error).

The fix checks each inner property individually through canReadProperty/canWriteProperty, which correctly handles mixed visibilities — A::$date passes as public, and B::$date passes as accessible from within B's scope.

Test

Added tests/PHPStan/Rules/Properties/data/bug-12280.php with two test methods: test1 uses A|self order and test2 uses self|A order, both accessing a property where A::$date is public and B::$date is private from within B's static method. Both should produce no errors.

Fixes phpstan/phpstan#12280

- UnionTypePropertyReflection's isPublic()/isPrivate() use AND semantics,
  returning false when union members have different visibilities
- canAccessClassMember then fell through to the "protected" branch with
  an order-dependent getDeclaringClass(), causing false positives
- Fix canReadProperty/canWriteProperty in MutatingScope to check each
  inner property individually when given a UnionTypePropertyReflection
- Added getProperties() getter to UnionTypePropertyReflection
- New regression test in tests/PHPStan/Rules/Properties/data/bug-12280.php

Closes phpstan/phpstan#12280
@staabm staabm deleted the create-pull-request/patch-xtb3yxo branch February 28, 2026 19:18
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.

2 participants