Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions src/Analyser/ExprHandler/Helper/IdenticalNarrowingHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -385,12 +385,13 @@

// a `$a::class` side falls back only where the old instanceof-style
// blocks would fire: a true context with a single class-name string
// on the other side; everything else narrows generically
// or a `static::class`-like fetch on the other side; everything else
// narrows generically
if ($context->true()) {
foreach ([
[$unwrappedLeft, $left, $leftResult, $rightResult],
[$unwrappedRight, $right, $rightResult, $leftResult],
] as [$sideUnwrapped, $side, $sideResult, $otherResult]) {
[$unwrappedLeft, $left, $leftResult, $unwrappedRight, $rightResult],
[$unwrappedRight, $right, $rightResult, $unwrappedLeft, $leftResult],
] as [$sideUnwrapped, $side, $sideResult, $otherUnwrapped, $otherResult]) {
if (!($sideUnwrapped instanceof Expr\ClassConstFetch) || !($sideUnwrapped->class instanceof Expr)) {
continue;
}
Expand All @@ -400,8 +401,21 @@
if ($sideUnwrapped->name instanceof Expr || $sideUnwrapped->name->toLowerString() !== 'class') {
continue;
}
$otherStrings = $otherResult->getTypeOnScope($evaluationScope, $evaluationScope->nativeTypesPromoted)->getConstantStrings();
$otherType = $otherResult->getTypeOnScope($evaluationScope, $evaluationScope->nativeTypesPromoted);
$otherStrings = $otherType->getConstantStrings();
if (count($otherStrings) !== 1 || $otherStrings[0]->getValue() === '') {
// $a::class === static::class narrows $a to the class-string's object type
if ($otherUnwrapped instanceof Expr\ClassConstFetch && $otherUnwrapped->class instanceof Name) {
$otherObjectType = $otherType->getClassStringObjectType();
if ($otherObjectType->isObject()->yes()) {

Check warning on line 410 in src/Analyser/ExprHandler/Helper/IdenticalNarrowingHelper.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ // $a::class === static::class narrows $a to the class-string's object type if ($otherUnwrapped instanceof Expr\ClassConstFetch && $otherUnwrapped->class instanceof Name) { $otherObjectType = $otherType->getClassStringObjectType(); - if ($otherObjectType->isObject()->yes()) { + if (!$otherObjectType->isObject()->no()) { return $this->defaultNarrowingHelper->createForSubject( $sideUnwrapped->class, $otherObjectType,

Check warning on line 410 in src/Analyser/ExprHandler/Helper/IdenticalNarrowingHelper.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ // $a::class === static::class narrows $a to the class-string's object type if ($otherUnwrapped instanceof Expr\ClassConstFetch && $otherUnwrapped->class instanceof Name) { $otherObjectType = $otherType->getClassStringObjectType(); - if ($otherObjectType->isObject()->yes()) { + if (!$otherObjectType->isObject()->no()) { return $this->defaultNarrowingHelper->createForSubject( $sideUnwrapped->class, $otherObjectType,
return $this->defaultNarrowingHelper->createForSubject(
$sideUnwrapped->class,
$otherObjectType,
$context,
$evaluationScope,
)->unionWith($this->defaultNarrowingHelper->createSubjectTypes($evaluationScope, $side, $sideResult, $otherType, $context));
}
}
continue;
}
if (!$this->reflectionProvider->hasClass($otherStrings[0]->getValue())) {
Expand Down
57 changes: 57 additions & 0 deletions tests/PHPStan/Analyser/nsrt/static-class-const-equals.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace StaticClassConstEquals;

use function PHPStan\Testing\assertType;

interface A {
public function equals(A $other): bool;
}

class B implements A {
public function equals(A $other): bool {
if ($other::class === static::class) {
assertType('static(StaticClassConstEquals\\B)', $other);
return true;
} else {
assertType('StaticClassConstEquals\\A', $other);
return false;
}
}
}

class C implements A {
public function equals(A $other): bool {
if (get_class($other) === static::class) {
assertType('static(StaticClassConstEquals\\C)', $other);
return true;
} else {
assertType('StaticClassConstEquals\\A', $other);
return false;
}
}
}

class D implements A {
public function equals(A $other): bool {
if ($other::class === self::class) {
assertType('StaticClassConstEquals\\D', $other);
return true;
} else {
assertType('StaticClassConstEquals\\A', $other);
return false;
}
}
}

class E implements A {
public function equals(A $other): bool {
if (get_class($other) === self::class) {
assertType('StaticClassConstEquals\\E', $other);
return true;
} else {
assertType('StaticClassConstEquals\\A', $other);
return false;
}
}
}
35 changes: 29 additions & 6 deletions turbo-ext/src/IdenticalNarrowingHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1011,14 +1011,16 @@ class IdenticalNarrowingHelper

// a `$a::class` side falls back only where the old instanceof-style
// blocks would fire: a true context with a single class-name string
// on the other side; everything else narrows generically
// or a `static::class`-like fetch on the other side; everything else
// narrows generically
bool contextTrue;
if (UNEXPECTED(!ctxTrue(context, contextTrue))) return zv::Val();
if (contextTrue) {
for (int i = 0; i < 2; i++) {
zval *sideUnwrapped = i == 0 ? unwrappedLeft : unwrappedRight;
zval *side = i == 0 ? left : right;
zval *sideResult = i == 0 ? leftResult : rightResult;
zval *otherUnwrapped = i == 0 ? unwrappedRight : unwrappedLeft;
zval *otherResult = i == 0 ? rightResult : leftResult;
if (!isA(sideUnwrapped, PT_CLASS_CLASS_CONST_FETCH) || !isA(classOf(sideUnwrapped), PT_CLASS_EXPR)) {
if (UNEXPECTED(EG(exception))) return zv::Val();
Expand All @@ -1031,11 +1033,32 @@ class IdenticalNarrowingHelper
if (UNEXPECTED(otherType.isUndef())) return zv::Val();
zv::Val otherStrings = constantStringsOf(otherType.raw());
if (UNEXPECTED(otherStrings.isUndef())) return zv::Val();
if (countOf(otherStrings.raw()) != 1) continue;
zval *otherString = firstOf(otherStrings.raw());
zv::Val value = constantStringValue(otherString);
if (UNEXPECTED(value.isUndef())) return zv::Val();
if (Z_TYPE_P(value.raw()) == IS_STRING && ZSTR_LEN(Z_STR_P(value.raw())) == 0) continue;
bool singleNonEmpty = countOf(otherStrings.raw()) == 1;
zval *otherString = NULL;
zv::Val value;
if (singleNonEmpty) {
otherString = firstOf(otherStrings.raw());
value = constantStringValue(otherString);
if (UNEXPECTED(value.isUndef())) return zv::Val();
singleNonEmpty = !(Z_TYPE_P(value.raw()) == IS_STRING && ZSTR_LEN(Z_STR_P(value.raw())) == 0);
}
if (!singleNonEmpty) {
// $a::class === static::class narrows $a to the class-string's object type
bool otherIsClassConstFetch = isA(otherUnwrapped, PT_CLASS_CLASS_CONST_FETCH) && isA(classOf(otherUnwrapped), PT_CLASS_NAME);
if (UNEXPECTED(EG(exception))) return zv::Val();
if (otherIsClassConstFetch) {
zv::Val otherObjectType = typeCall(otherType.raw(), "getClassStringObjectType", PT_LC("getclassstringobjecttype"));
if (UNEXPECTED(otherObjectType.isUndef())) return zv::Val();
zend_long isObject = trinaryByName(otherObjectType.raw(), "isObject", PT_LC("isobject"));
if (UNEXPECTED(isObject < 0)) return zv::Val();
if (isObject == PT_TRI_YES) {
zv::Val classTypes = createForSubject(classOf(sideUnwrapped), otherObjectType.raw(), context, evaluationScope);
if (UNEXPECTED(classTypes.isUndef())) return zv::Val();
return unionWith(std::move(classTypes), createSubjectTypes(evaluationScope, side, sideResult, otherType.raw(), context));
}
}
continue;
}

bool known;
if (UNEXPECTED(!hasClass(value.raw(), known))) return zv::Val();
Expand Down
Loading