Skip to content
Open
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
12 changes: 6 additions & 6 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1352,12 +1352,6 @@ jobs:
OUTPUT=$(../bashunit -a exit_code "0" "../../bin/phpstan analyse -vvv --error-format raw")
echo "$OUTPUT"
../bashunit -a not_contains 'not found' "$OUTPUT"
# A reflection error, here a class that extends itself, still keeps the result cache from
# being saved.
cp loop/Loop.php src/Loop.php
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vvv --error-format raw")
echo "$OUTPUT"
../bashunit -a contains 'Result cache was not saved because of non-ignorable exception: Reflection error: Circular reference' "$OUTPUT"
- script: |
cd e2e/result-cache-internal-error
composer dump-autoload
Expand All @@ -1367,6 +1361,12 @@ jobs:
../bashunit -a contains 'Internal error: The rule crashed.' "$OUTPUT"
../bashunit -a contains 'Result cache was not saved' "$OUTPUT"
test ! -f tmp/resultCache.php
# An exception about the analysed code other than a parse error, here a class that is not
# found, also keeps the result cache from being saved.
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -c class-not-found.neon -vvv --error-format raw")
echo "$OUTPUT"
../bashunit -a contains 'Result cache was not saved because of non-ignorable exception: Class ResultCacheE2EInternalError\Missing was not found' "$OUTPUT"
test ! -f tmp/resultCache.php
- script: |
cd e2e/result-cache-string-class
../../bin/phpstan analyse
Expand Down
8 changes: 8 additions & 0 deletions e2e/result-cache-internal-error/class-not-found.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
parameters:
level: 8
tmpDir: tmp
paths:
- src

rules:
- ResultCacheE2EInternalError\ClassNotFoundRule
27 changes: 27 additions & 0 deletions e2e/result-cache-internal-error/rule/ClassNotFoundRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types = 1);

namespace ResultCacheE2EInternalError;

use PhpParser\Node;
use PhpParser\Node\Stmt\Function_;
use PHPStan\Analyser\Scope;
use PHPStan\Broker\ClassNotFoundException;
use PHPStan\Rules\Rule;

/**
* @implements Rule<Function_>
*/
final class ClassNotFoundRule implements Rule
{

public function getNodeType(): string
{
return Function_::class;
}

public function processNode(Node $node, Scope $scope): array
{
throw new ClassNotFoundException('ResultCacheE2EInternalError\Missing');
}

}
8 changes: 0 additions & 8 deletions e2e/result-cache-parse-error/loop/Loop.php

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use PHPStan\Reflection\Php\ExtendedDummyParameter;
use PHPStan\Reflection\ResolvedMethodReflection;
use PHPStan\Turbo\ShadowedByTurboExtension;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\GenericStaticType;
use PHPStan\Type\StaticType;
use PHPStan\Type\ThisType;
Expand Down Expand Up @@ -149,7 +150,19 @@ private function transformStaticType(Type $type): Type
if (count($calledOnTypeReflections) === 1) {
$calledOnTypeReflection = $calledOnTypeReflections[0];

return $traverse($type->changeBaseClass($calledOnTypeReflection)->getStaticObjectType());
$staticObjectType = $type->changeBaseClass($calledOnTypeReflection)->getStaticObjectType();
if ($staticObjectType instanceof GenericObjectType) {
// do not carry over the class reflection of the declaring class,
// it would not know the type arguments of the resolved type
$staticObjectType = new GenericObjectType(
$staticObjectType->getClassName(),
$staticObjectType->getTypes(),
$staticObjectType->getSubtractedType(),
variances: $staticObjectType->getVariances(),
);
}

return $traverse($staticObjectType);
}

return $this->calledOnType;
Expand Down
2 changes: 1 addition & 1 deletion src/Turbo/TurboExtensionEnabler.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
final class TurboExtensionEnabler
{

public const EXPECTED_EXTENSION_VERSION = '1cb5faf';
public const EXPECTED_EXTENSION_VERSION = '529120b';

private static bool $active = false;

Expand Down
49 changes: 49 additions & 0 deletions tests/PHPStan/Analyser/nsrt/generic-static-concrete-arguments.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php declare(strict_types = 1);

namespace GenericStaticConcreteArguments;

use function PHPStan\Testing\assertType;

/**
* @template TKey of array-key
* @template TValue
*/
class Box
{

/** @return static<int, string> */
public function concrete()
{
return new static();
}

/** @return static<int, TValue> */
public function withTemplate()
{
return new static();
}

/** @return array<TKey, TValue> */
public function all(): array
{
return [];
}

/** @return TValue */
public function first()
{
return 1;
}

}

/**
* @param Box<string, bool> $box
*/
function test(Box $box): void
{
assertType('GenericStaticConcreteArguments\Box<int, string>', $box->concrete());
assertType('array<int, string>', $box->concrete()->all());
assertType('string', $box->concrete()->first());
assertType('array<int, bool>', $box->withTemplate()->all());
}
23 changes: 23 additions & 0 deletions turbo-ext/src/CalledOnTypeUnresolvedMethodPrototypeReflection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,29 @@ class CalledOnTypeUnresolvedMethodPrototypeReflection
}
zv::Val staticObjectType = pt_type_call(Z_OBJ_P(changed.raw()), PT_LC("getstaticobjecttype"), 0, NULL);
if (UNEXPECTED(staticObjectType.isUndef())) return;
/* a GenericObjectType is rebuilt without the class reflection of the
* declaring class, which would not know the resolved type arguments:
* new GenericObjectType($t->getClassName(), $t->getTypes(), $t->getSubtractedType(), variances: $t->getVariances()) */
bool isGenericObject;
pt_type_instanceof_ce(staticObjectType.raw(), pt_ce_generic_object_type, isGenericObject);
if (isGenericObject) {
zend_object *objectType = Z_OBJ_P(staticObjectType.raw());
zv::Val className = pt_type_call(objectType, PT_LC("getclassname"), 0, NULL);
if (UNEXPECTED(className.isUndef())) return;
zv::Val objectTypes = pt_type_call(objectType, PT_LC("gettypes"), 0, NULL);
if (UNEXPECTED(objectTypes.isUndef())) return;
zv::Val subtracted = pt_type_call(objectType, PT_LC("getsubtractedtype"), 0, NULL);
if (UNEXPECTED(subtracted.isUndef())) return;
zv::Val objectVariances = pt_type_call(objectType, PT_LC("getvariances"), 0, NULL);
if (UNEXPECTED(objectVariances.isUndef())) return;
if (UNEXPECTED(Z_TYPE_P(className.raw()) != IS_STRING)) {
zend_type_error("phpstan_turbo: getClassName() must return a string");
return;
}
zval rebuiltRaw;
if (UNEXPECTED(!pt_generic_object_type_new(&rebuiltRaw, Z_STR_P(className.raw()), objectTypes.raw(), subtracted.raw(), NULL, objectVariances.raw()))) return;
staticObjectType = zv::Val::adopt(rebuiltRaw);
}
zv::Val traversed = pt_type_call_callable(&argv[1], 1, staticObjectType.raw());
if (UNEXPECTED(traversed.isUndef())) return;
traversed.intoReturnValue(return_value);
Expand Down
Loading