Skip to content
Closed
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
1 change: 1 addition & 0 deletions conf/bleedingEdge.neon
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ parameters:
unusedVariable: true
unusedParameters: true
reuseCollectedDataRuleErrors: true
disallowClassLevelTemplatesInStaticMethods: true
1 change: 1 addition & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ parameters:
unusedVariable: false
unusedParameters: false
reuseCollectedDataRuleErrors: false
disallowClassLevelTemplatesInStaticMethods: false
fileExtensions:
- php
checkAdvancedIsset: false
Expand Down
1 change: 1 addition & 0 deletions conf/parametersSchema.neon
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ parametersSchema:
unusedVariable: bool()
unusedParameters: bool()
reuseCollectedDataRuleErrors: bool()
disallowClassLevelTemplatesInStaticMethods: bool()
])
fileExtensions: listOf(string())
checkAdvancedIsset: bool()
Expand Down
1 change: 1 addition & 0 deletions conf/services.neon
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ services:
phpParser: @stubParser
resolvedPhpDocBlockCacheCountMax: %cache.resolvedPhpDocBlockCacheCountMax%
nameScopeMapMemoryCacheCountMax: %cache.nameScopeMapMemoryCacheCountMax%
disallowClassLevelTemplatesInStaticMethods: %featureToggles.disallowClassLevelTemplatesInStaticMethods%
autowired: false

fileExcluderAnalyse:
Expand Down
23 changes: 19 additions & 4 deletions src/Type/FileTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ public function __construct(
private int $resolvedPhpDocBlockCacheCountMax,
#[AutowiredParameter(ref: '%cache.nameScopeMapMemoryCacheCountMax%')]
int $nameScopeMapMemoryCacheCountMax,
#[AutowiredParameter(ref: '%featureToggles.disallowClassLevelTemplatesInStaticMethods%')]
private bool $disallowClassLevelTemplatesInStaticMethods,
)
{
// 0 kept one entry here rather than meaning "no limit" as it does for the other bounded
Expand Down Expand Up @@ -387,7 +389,12 @@ private function getNameScopeMap(string $fileName): array
// a map built from the simplified AST of a file outside the analysed paths lacks
// the name scopes of classes declared inside function bodies, so it must not be
// reused once the file gets analysed, nor the other way around
$cacheKey = sprintf('ftm-%s%s', $this->returnsSimplifiedAst($fileName) ? 'simplified-' : '', $fileName);
$cacheKey = sprintf(
'ftm-%s%s%s',
$this->returnsSimplifiedAst($fileName) ? 'simplified-' : '',
$this->disallowClassLevelTemplatesInStaticMethods ? 'static-own-scope-' : '',
$fileName,
);
// v6: v5 entries may be poisoned by the turbo comment-loss bug
// (https://github.com/phpstan/phpstan/issues/15037) - a parse that
// silently dropped every comment cached PHPDoc-less name-scope maps,
Expand Down Expand Up @@ -553,13 +560,22 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
}

if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_ || $node instanceof Node\PropertyHook) {
$parentNameScope = array_last($typeMapStack);

// static methods exist in their own scope
if (
$this->disallowClassLevelTemplatesInStaticMethods
&& $node instanceof Node\Stmt\ClassMethod
&& $node->isStatic()
) {
$parentNameScope = null;
}

if ($phpDocNode !== null) {
if ($node instanceof Node\Stmt\ClassLike) {
$typeAliasStack[] = $this->getTypeAliasesMap($phpDocNode);
}

$parentNameScope = array_last($typeMapStack);

$typeMapStack[] = new IntermediaryNameScope(
$namespaceUsesTracker->getNamespaceUses(),
$className,
Expand All @@ -572,7 +588,6 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
} elseif ($node instanceof Node\Stmt\ClassLike) {
$typeAliasStack[] = [];
} else {
$parentNameScope = array_last($typeMapStack);
$typeMapStack[] = new IntermediaryNameScope(
$namespaceUsesTracker->getNamespaceUses(),
$className,
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/generics.php
Original file line number Diff line number Diff line change
Expand Up @@ -1410,7 +1410,7 @@ class CarFactoryProcessor {
*/
public function process($class): void {
$car = $class::create();
assertType(Car::class, $car);
assertType(T::class, $car); // @template T is unknown in static method
}
}

Expand Down
49 changes: 49 additions & 0 deletions tests/PHPStan/Analyser/nsrt/static-methods-template-types.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace StaticMethodsTemplateTypes;

use function PHPStan\Testing\assertType;

/**
* @template T
*/
class Foo
{

/**
* @template U
* @param T $t
* @return T
*/
public static function bar($t)
{
}

/**
* @template U
* @param U $u
* @return U
*/
public static function baz($u)
{
}

/**
* @return T
*/
public static function qux()
{
}

public static function doFoo()
{
assertType('StaticMethodsTemplateTypes\T', self::bar(42));
assertType('42', self::baz(42));
assertType('StaticMethodsTemplateTypes\T', self::qux());
}

}

assertType('StaticMethodsTemplateTypes\T', Foo::bar(42));
assertType('42', Foo::baz(42));
assertType('StaticMethodsTemplateTypes\T', Foo::qux());
28 changes: 2 additions & 26 deletions tests/PHPStan/Rules/Generics/MethodSignatureVarianceRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,32 +186,8 @@ public function testRule(): void

$this->analyse([__DIR__ . '/data/method-signature-variance-constructor.php'], []);

$this->analyse([__DIR__ . '/data/method-signature-variance-static.php'], [
[
'Template type X is declared as covariant, but occurs in contravariant position in parameter a of method MethodSignatureVariance\StaticMethod\B::a().',
43,
],
[
'Template type X is declared as covariant, but occurs in contravariant position in parameter c of method MethodSignatureVariance\StaticMethod\B::a().',
43,
],
[
'Template type X is declared as covariant, but occurs in contravariant position in return type of method MethodSignatureVariance\StaticMethod\B::c().',
49,
],
[
'Template type X is declared as contravariant, but occurs in covariant position in parameter b of method MethodSignatureVariance\StaticMethod\C::a().',
62,
],
[
'Template type X is declared as contravariant, but occurs in covariant position in return type of method MethodSignatureVariance\StaticMethod\C::b().',
65,
],
[
'Template type X is declared as contravariant, but occurs in covariant position in return type of method MethodSignatureVariance\StaticMethod\C::d().',
71,
],
]);
// class-level @template X is unknown in static methods
$this->analyse([__DIR__ . '/data/method-signature-variance-static.php'], []);
}

public function testBug8880(): void
Expand Down
7 changes: 6 additions & 1 deletion tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,12 @@ public function testBug6249(): void
// discussion https://github.com/phpstan/phpstan/discussions/6249
$this->checkThisOnly = false;
$this->checkExplicitMixed = true;
$this->analyse([__DIR__ . '/data/bug-6249.php'], []);
$this->analyse([__DIR__ . '/data/bug-6249.php'], [
[
'Parameter #1 $iterable of static method Bug6249N3\Cw<(int|string),mixed>::fromIterable() expects iterable<Bug6249N3\TKey, Bug6249N3\T>, Bug6249N2\Eii given.',
145,
],
]);
}

public function testBug5749(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ public function testExistingClassInTypehint(): void
'Template type U of method TestMethodTypehints\TemplateTypeMissingInParameter::doFoo() is not referenced in a parameter.',
130,
],
[
'Method TestMethodTypehints\TemplateInStaticMethod::doBar() has invalid return type TestMethodTypehints\T.',
163,
],
]);
}

Expand Down
24 changes: 23 additions & 1 deletion tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1248,7 +1248,29 @@ public function testBug13043(): void

public function testBug12739(): void
{
$this->analyse([__DIR__ . '/data/bug-12739.php'], []);
// @template TScalar is unknown in static methods
$this->analyse([__DIR__ . '/data/bug-12739.php'], [
[
"Method MyInt::getClassIdentify() should return class-string<ScalarType<TScalar>> but returns 'MyInt'.",
20,
],
[
"Method MyString::getClassIdentify() should return class-string<ScalarType<TScalar>> but returns 'MyString'.",
29,
],
[
"Method MyLowerString::getClassIdentify() should return class-string<ScalarType<TScalar>> but returns 'MyLowerString'.",
38,
],
[
"Method MyUpperString::getClassIdentify() should return class-string<ScalarType<TScalar>> but returns 'MyUpperString'.",
47,
],
[
"Method MyClassString::getClassIdentify() should return class-string<ScalarType<TScalar>> but returns 'MyClassString'.",
56,
],
]);
}

public function testBug12928(): void
Expand Down
13 changes: 13 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-6249.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,18 @@ public static function fromIterable(iterable $iterable): self
return new self($iterable);
}

/**
* @template UKey of array-key
* @template U
* @param iterable<UKey, U> $iterable
*
* @return self<UKey, U>
*/
public static function fromIterableCorrect(iterable $iterable): self
{
return new self($iterable);
}

public function count(): int
{
if (is_array($this->iterable) || $this->iterable instanceof Countable) {
Expand All @@ -131,6 +143,7 @@ class Foo
public function doFoo()
{
\Bug6249N3\Cw::fromIterable(new \Bug6249N2\Eii([]));
\Bug6249N3\Cw::fromIterableCorrect(new \Bug6249N2\Eii([]));
}

}
24 changes: 24 additions & 0 deletions tests/PHPStan/Rules/Methods/data/typehints.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,27 @@ public function doBar(string $class): void
}

}

/**
* @template T
*/
class TemplateInStaticMethod
{

/**
* @return T
*/
public function doFoo()
{

}

/**
* @return T
*/
public static function doBar()
{

}

}
1 change: 1 addition & 0 deletions tests/PHPStan/Type/FileTypeMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ private function createFileTypeMapper(CacheStorage $storage): FileTypeMapper
$container->getByType(FileContentHasher::class),
2048,
128,
false,
);
}

Expand Down
Loading