Skip to content

Do not report undefined property in isset/empty/?? for types without class names#5673

Closed
phpstan-bot wants to merge 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-0qv7ihc
Closed

Do not report undefined property in isset/empty/?? for types without class names#5673
phpstan-bot wants to merge 1 commit into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-0qv7ihc

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Closes phpstan/phpstan#13539

When checkDynamicProperties is enabled (e.g. via phpstan-strict-rules), PHPStan incorrectly reported "Access to an undefined property" for chained isset() checks on mixed types.

The scenario:

$tmp = json_decode($x, false); // mixed
if (!isset($tmp->foo) || !isset($tmp->bar)) { }

The first isset($tmp->foo) narrows mixed to object&hasProperty(foo). In the falsey branch (where we evaluate the right side of ||), the second isset($tmp->bar) triggers AccessPropertiesCheck which calls pickProperty on an IntersectionType(ObjectWithoutClassType, HasPropertyType(foo)). Since this type has no concrete class names, pickProperty returns null, and the code treated that as "property definitely doesn't exist" — reporting a false positive.

The fix adds a check: when the type has no class names (bare object, object&hasProperty(...)) we skip the dynamic property check in isset/empty/?? context, since we cannot know what properties such a type might have. For concrete classes, the existing behavior is preserved.

This also fixes the related bug #13529 which had a test expecting the buggy behavior.

Construct Before After
!isset($tmp->foo) || !isset($tmp->bar) false positive no error
isset($tmp->foo, $tmp->bar) no error no error
isset($tmp->foo) && isset($tmp->bar) no error no error
$tmp->bar ?? null after isset false positive no error
empty($tmp->bar) after isset false positive no error
Concrete class with #[AllowDynamicProperties] reports reports (preserved)

…class names

When checkDynamicProperties is enabled, PHPStan incorrectly reported
"Access to an undefined property" for chained isset() checks on mixed
types. After the first isset() narrows mixed to object&hasProperty(foo),
the second isset($tmp->bar) was being flagged because pickProperty
returned null for the intersection type that has no concrete class names.

The fix skips the dynamic property check when the type has no class names
(bare object or object&hasProperty), since we cannot know what properties
such a type might have.

Closes phpstan/phpstan#13539
@VincentLanglet VincentLanglet deleted the create-pull-request/patch-0qv7ihc branch May 16, 2026 12:00
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