Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge 1.12.x into 2.0.x #3560

Merged
merged 4 commits into from
Oct 9, 2024
Merged
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
2 changes: 1 addition & 1 deletion resources/functionMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -12606,7 +12606,7 @@
'timezone_version_get' => ['string'],
'tmpfile' => ['__benevolent<resource|false>'],
'token_get_all' => ['list<string|array{0:int,1:string,2:int}>', 'source'=>'string', 'flags='=>'int'],
'token_name' => ['non-empty-string', 'type'=>'int'],
'token_name' => ['non-falsy-string', 'type'=>'int'],
'TokyoTyrant::__construct' => ['void', 'host='=>'string', 'port='=>'int', 'options='=>'array'],
'TokyoTyrant::add' => ['int|float', 'key'=>'string', 'increment'=>'float', 'type='=>'int'],
'TokyoTyrant::connect' => ['TokyoTyrant', 'host'=>'string', 'port='=>'int', 'options='=>'array'],
Expand Down
2 changes: 1 addition & 1 deletion resources/functionMap_php80delta.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
'PhpToken::tokenize' => ['list<PhpToken>', 'code'=>'string', 'flags='=>'int'],
'PhpToken::is' => ['bool', 'kind'=>'string|int|string[]|int[]'],
'PhpToken::isIgnorable' => ['bool'],
'PhpToken::getTokenName' => ['string'],
'PhpToken::getTokenName' => ['non-falsy-string'],
'preg_match_all' => ['0|positive-int|false', 'pattern'=>'string', 'subject'=>'string', '&w_subpatterns='=>'array', 'flags='=>'int', 'offset='=>'int'],
'proc_get_status' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}', 'process'=>'resource'],
'set_error_handler' => ['?callable', 'callback'=>'null|callable(int,string,string,int):bool', 'error_types='=>'int'],
Expand Down
3 changes: 2 additions & 1 deletion src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -2536,6 +2536,7 @@ private function createFirstClassCallable(
$templateTags[$templateType->getName()] = new TemplateTag(
$templateType->getName(),
$templateType->getBound(),
$templateType->getDefault(),
$templateType->getVariance(),
);
}
Expand Down Expand Up @@ -5547,7 +5548,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type
if ($type instanceof TemplateType && !$type->isArgument()) {
$newType = $resolvedTemplateTypeMap->getType($type->getName());
if ($newType === null || $newType instanceof ErrorType) {
return $type->getBound();
return $type->getDefault() ?? $type->getBound();
}

return TemplateTypeHelper::generalizeInferredTemplateType($type, $newType);
Expand Down
11 changes: 11 additions & 0 deletions src/Dependency/DependencyResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,17 @@ private function addClassToDependencies(string $className, array &$dependenciesR
}
$dependenciesReflections[] = $this->reflectionProvider->getClass($referencedClass);
}

$default = $templateTag->getDefault();
if ($default === null) {
continue;
}
foreach ($default->getReferencedClasses() as $referencedClass) {
if (!$this->reflectionProvider->hasClass($referencedClass)) {
continue;
}
$dependenciesReflections[] = $this->reflectionProvider->getClass($referencedClass);
}
}

foreach ($classReflection->getPropertyTags() as $propertyTag) {
Expand Down
25 changes: 20 additions & 5 deletions src/Parser/ArrowFunctionArgVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,28 @@ final class ArrowFunctionArgVisitor extends NodeVisitorAbstract

public function enterNode(Node $node): ?Node
{
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Expr\ArrowFunction && !$node->isFirstClassCallable()) {
$args = $node->getArgs();
if (!$node instanceof Node\Expr\FuncCall) {
return null;
}

if ($node->isFirstClassCallable()) {
return null;
}

if (count($args) > 0) {
$node->name->setAttribute(self::ATTRIBUTE_NAME, $args);
}
if ($node->name instanceof Node\Expr\Assign && $node->name->expr instanceof Node\Expr\ArrowFunction) {
$arrow = $node->name->expr;
} elseif ($node->name instanceof Node\Expr\ArrowFunction) {
$arrow = $node->name;
} else {
return null;
}

$args = $node->getArgs();

if (count($args) > 0) {
$arrow->setAttribute(self::ATTRIBUTE_NAME, $args);
}

return null;
}

Expand Down
25 changes: 20 additions & 5 deletions src/Parser/ClosureArgVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,28 @@ final class ClosureArgVisitor extends NodeVisitorAbstract

public function enterNode(Node $node): ?Node
{
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Expr\Closure && !$node->isFirstClassCallable()) {
$args = $node->getArgs();
if (!$node instanceof Node\Expr\FuncCall) {
return null;
}

if ($node->isFirstClassCallable()) {
return null;
}

if (count($args) > 0) {
$node->name->setAttribute(self::ATTRIBUTE_NAME, $args);
}
if ($node->name instanceof Node\Expr\Assign && $node->name->expr instanceof Node\Expr\Closure) {
$closure = $node->name->expr;
} elseif ($node->name instanceof Node\Expr\Closure) {
$closure = $node->name;
} else {
return null;
}

$args = $node->getArgs();

if (count($args) > 0) {
$closure->setAttribute(self::ATTRIBUTE_NAME, $args);
}

return null;
}

Expand Down
8 changes: 7 additions & 1 deletion src/PhpDoc/PhpDocNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ public function resolveMethodTags(PhpDocNode $phpDocNode, NameScope $nameScope):
$templateType->bound !== null
? $this->typeNodeResolver->resolve($templateType->bound, $nameScope)
: new MixedType(),
$templateType->default !== null
? $this->typeNodeResolver->resolve($templateType->default, $nameScope)
: null,
TemplateTypeVariance::createInvariant(),
);
}
Expand Down Expand Up @@ -324,9 +327,12 @@ public function resolveTemplateTags(PhpDocNode $phpDocNode, NameScope $nameScope
}
}

$nameScopeWithoutCurrent = $nameScope->unsetTemplateType($valueNode->name);

$resolved[$valueNode->name] = new TemplateTag(
$valueNode->name,
$valueNode->bound !== null ? $this->typeNodeResolver->resolve($valueNode->bound, $nameScope->unsetTemplateType($valueNode->name)) : new MixedType(true),
$valueNode->bound !== null ? $this->typeNodeResolver->resolve($valueNode->bound, $nameScopeWithoutCurrent) : new MixedType(true),
$valueNode->default !== null ? $this->typeNodeResolver->resolve($valueNode->default, $nameScopeWithoutCurrent) : null,
$variance,
);
$resolvedPrefix[$valueNode->name] = $prefix;
Expand Down
7 changes: 6 additions & 1 deletion src/PhpDoc/Tag/TemplateTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class TemplateTag
/**
* @param non-empty-string $name
*/
public function __construct(private string $name, private Type $bound, private TemplateTypeVariance $variance)
public function __construct(private string $name, private Type $bound, private ?Type $default, private TemplateTypeVariance $variance)
{
}

Expand All @@ -31,6 +31,11 @@ public function getBound(): Type
return $this->bound;
}

public function getDefault(): ?Type
{
return $this->default;
}

public function getVariance(): TemplateTypeVariance
{
return $this->variance;
Expand Down
12 changes: 12 additions & 0 deletions src/PhpDoc/TypeNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,15 @@ static function (string $variance): TemplateTypeVariance {

$classReflection = $this->getReflectionProvider()->getClass($mainTypeClassName);
if ($classReflection->isGeneric()) {
$templateTypes = array_values($classReflection->getTemplateTypeMap()->getTypes());
for ($i = count($genericTypes), $templateTypesCount = count($templateTypes); $i < $templateTypesCount; $i++) {
$templateType = $templateTypes[$i];
if (!$templateType instanceof TemplateType || $templateType->getDefault() === null) {
continue;
}
$genericTypes[] = $templateType->getDefault();
}

if (in_array($mainTypeClassName, [
Traversable::class,
IteratorAggregate::class,
Expand Down Expand Up @@ -910,6 +919,9 @@ private function resolveCallableTypeNode(CallableTypeNode $typeNode, NameScope $
$templateType->bound !== null
? $this->resolve($templateType->bound, $nameScope)
: new MixedType(),
$templateType->default !== null
? $this->resolve($templateType->default, $nameScope)
: null,
TemplateTypeVariance::createInvariant(),
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Reflection/ClassReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -1432,7 +1432,7 @@ public function typeMapFromList(array $types): TemplateTypeMap
$map = [];
$i = 0;
foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
$map[$tag->getName()] = $types[$i] ?? $tag->getBound();
$map[$tag->getName()] = $types[$i] ?? $tag->getDefault() ?? $tag->getBound();
$i++;
}

Expand Down Expand Up @@ -1469,7 +1469,7 @@ public function typeMapToList(TemplateTypeMap $typeMap): array

$list = [];
foreach ($resolvedPhpDoc->getTemplateTags() as $tag) {
$list[] = $typeMap->getType($tag->getName()) ?? $tag->getBound();
$list[] = $typeMap->getType($tag->getName()) ?? $tag->getDefault() ?? $tag->getBound();
}

return $list;
Expand Down
3 changes: 1 addition & 2 deletions src/Rules/Classes/LocalTypeAliasesCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
use PHPStan\Type\VerbosityLevel;
use function array_key_exists;
use function array_merge;
use function implode;
use function in_array;
use function sprintf;

Expand Down Expand Up @@ -206,7 +205,7 @@ public function checkInTraitDefinitionContext(ClassReflection $reflection): arra
$reflection->getDisplayName(),
$aliasName,
$name,
implode(', ', $genericTypeNames),
$genericTypeNames,
))
->identifier('missingType.generics')
->build();
Expand Down
3 changes: 1 addition & 2 deletions src/Rules/Classes/MethodTagCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use function array_merge;
use function implode;
use function sprintf;

final class MethodTagCheck
Expand Down Expand Up @@ -174,7 +173,7 @@ private function checkMethodTypeInTraitDefinitionContext(ClassReflection $classR
$methodName,
$description,
$innerName,
implode(', ', $genericTypeNames),
$genericTypeNames,
))
->identifier('missingType.generics')
->build();
Expand Down
3 changes: 1 addition & 2 deletions src/Rules/Classes/MixinCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\VerbosityLevel;
use function array_merge;
use function implode;
use function sprintf;

final class MixinCheck
Expand Down Expand Up @@ -85,7 +84,7 @@ public function checkInTraitDefinitionContext(ClassReflection $classReflection):
$errors[] = RuleErrorBuilder::message(sprintf(
'PHPDoc tag @mixin contains generic %s but does not specify its types: %s',
$innerName,
implode(', ', $genericTypeNames),
$genericTypeNames,
))
->identifier('missingType.generics')
->build();
Expand Down
3 changes: 1 addition & 2 deletions src/Rules/Classes/PropertyTagCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use function array_merge;
use function implode;
use function sprintf;

final class PropertyTagCheck
Expand Down Expand Up @@ -155,7 +154,7 @@ private function checkPropertyTypeInTraitDefinitionContext(ClassReflection $clas
$classReflection->getDisplayName(),
$propertyName,
$innerName,
implode(', ', $genericTypeNames),
$genericTypeNames,
))
->identifier('missingType.generics')
->build();
Expand Down
3 changes: 1 addition & 2 deletions src/Rules/Constants/MissingClassConstantTypehintRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\VerbosityLevel;
use function array_merge;
use function implode;
use function sprintf;

/**
Expand Down Expand Up @@ -76,7 +75,7 @@ private function processSingleConstant(ClassReflection $classReflection, string
$constantReflection->getDeclaringClass()->getDisplayName(),
$constantName,
$name,
implode(', ', $genericTypeNames),
$genericTypeNames,
))
->identifier('missingType.generics')
->build();
Expand Down
4 changes: 2 additions & 2 deletions src/Rules/FunctionCallParametersCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ static function (Type $type, callable $traverse) use (&$returnTemplateTypes): Ty
$type = $type->resolve();
}

if ($type instanceof TemplateType) {
if ($type instanceof TemplateType && $type->getDefault() === null) {
$returnTemplateTypes[$type->getName()] = true;
return $type;
}
Expand All @@ -453,7 +453,7 @@ static function (Type $type, callable $traverse) use (&$returnTemplateTypes): Ty
$parameterTemplateTypes = [];
foreach ($originalParametersAcceptor->getParameters() as $parameter) {
TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$parameterTemplateTypes): Type {
if ($type instanceof TemplateType) {
if ($type instanceof TemplateType && $type->getDefault() === null) {
$parameterTemplateTypes[$type->getName()] = true;
return $type;
}
Expand Down
3 changes: 1 addition & 2 deletions src/Rules/Functions/MissingFunctionParameterTypehintRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use function implode;
use function sprintf;

/**
Expand Down Expand Up @@ -96,7 +95,7 @@ private function checkFunctionParameter(FunctionReflection $functionReflection,
$functionReflection->getName(),
$parameterMessage,
$name,
implode(', ', $genericTypeNames),
$genericTypeNames,
))
->identifier('missingType.generics')
->build();
Expand Down
3 changes: 1 addition & 2 deletions src/Rules/Functions/MissingFunctionReturnTypehintRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\MixedType;
use PHPStan\Type\VerbosityLevel;
use function implode;
use function sprintf;

/**
Expand Down Expand Up @@ -58,7 +57,7 @@ public function processNode(Node $node, Scope $scope): array
'Function %s() return type with generic %s does not specify its types: %s',
$functionReflection->getName(),
$name,
implode(', ', $genericTypeNames),
$genericTypeNames,
))
->identifier('missingType.generics')
->build();
Expand Down
3 changes: 3 additions & 0 deletions src/Rules/Generics/ClassTemplateTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ public function processNode(Node $node, Scope $scope): array
sprintf('PHPDoc tag @template for %s cannot have existing type alias %%s as its name.', $displayName),
sprintf('PHPDoc tag @template %%s for %s has invalid bound type %%s.', $displayName),
sprintf('PHPDoc tag @template %%s for %s with bound type %%s is not supported.', $displayName),
sprintf('PHPDoc tag @template %%s for %s has invalid default type %%s.', $displayName),
sprintf('Default type %%s in PHPDoc tag @template %%s for %s is not subtype of bound type %%s.', $displayName),
sprintf('PHPDoc tag @template %%s for %s does not have a default type but follows an optional @template %%s.', $displayName),
);
}

Expand Down
3 changes: 3 additions & 0 deletions src/Rules/Generics/FunctionTemplateTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public function processNode(Node $node, Scope $scope): array
sprintf('PHPDoc tag @template for function %s() cannot have existing type alias %%s as its name.', $escapedFunctionName),
sprintf('PHPDoc tag @template %%s for function %s() has invalid bound type %%s.', $escapedFunctionName),
sprintf('PHPDoc tag @template %%s for function %s() with bound type %%s is not supported.', $escapedFunctionName),
sprintf('PHPDoc tag @template %%s for function %s() has invalid default type %%s.', $escapedFunctionName),
sprintf('Default type %%s in PHPDoc tag @template %%s for function %s() is not subtype of bound type %%s.', $escapedFunctionName),
sprintf('PHPDoc tag @template %%s for function %s() does not have a default type but follows an optional @template %%s.', $escapedFunctionName),
);
}

Expand Down
Loading
Loading