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
3 changes: 3 additions & 0 deletions src/Analyser/ExprHandler/Helper/ClosureTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,13 +227,15 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
usedVariables: $cachedClosureData['usedVariables'],
acceptsNamedArguments: TrinaryLogic::createYes(),
mustUseReturnValue: $mustUseReturnValue,
isStatic: TrinaryLogic::createFromBoolean($expr->static),
);
}
if (self::$resolveClosureTypeDepth >= 2) {
return new ClosureType(
$parameters,
$scope->getFunctionType($expr->returnType, false, false),
$isVariadic,
isStatic: TrinaryLogic::createFromBoolean($expr->static),
);
}

Expand Down Expand Up @@ -453,6 +455,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
usedVariables: $usedVariables,
acceptsNamedArguments: TrinaryLogic::createYes(),
mustUseReturnValue: $mustUseReturnValue,
isStatic: TrinaryLogic::createFromBoolean($expr->static),
);
}

Expand Down
8 changes: 7 additions & 1 deletion src/PhpDoc/TypeNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,12 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
case 'pure-closure':
return ClosureType::createPure();

case 'static-closure':
return new ClosureType(isStatic: TrinaryLogic::createYes());

case 'static-pure-closure':
return new ClosureType(impurePoints: [], isStatic: TrinaryLogic::createYes());

case 'resource':
$type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);

Expand Down Expand Up @@ -1083,7 +1089,7 @@ function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadi
),
]);
} elseif ($mainType instanceof ClosureType) {
$closure = new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, templateTags: $templateTags, impurePoints: $mainType->getImpurePoints(), invalidateExpressions: $mainType->getInvalidateExpressions(), usedVariables: $mainType->getUsedVariables(), acceptsNamedArguments: $mainType->acceptsNamedArguments(), mustUseReturnValue: $mainType->mustUseReturnValue());
$closure = new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, templateTags: $templateTags, impurePoints: $mainType->getImpurePoints(), invalidateExpressions: $mainType->getInvalidateExpressions(), usedVariables: $mainType->getUsedVariables(), acceptsNamedArguments: $mainType->acceptsNamedArguments(), mustUseReturnValue: $mainType->mustUseReturnValue(), isStatic: $mainType->isStaticClosure());
if ($closure->isPure()->yes() && $returnType->isVoid()->yes()) {
return new ErrorType();
}
Expand Down
2 changes: 2 additions & 0 deletions src/Reflection/Callables/CallableParametersAcceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,6 @@ public function mustUseReturnValue(): TrinaryLogic;

public function getAsserts(): Assertions;

public function isStaticClosure(): TrinaryLogic;

}
5 changes: 5 additions & 0 deletions src/Reflection/Callables/FunctionCallableVariant.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,9 @@ public function getAsserts(): Assertions
return $this->function->getAsserts();
}

public function isStaticClosure(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
6 changes: 6 additions & 0 deletions src/Reflection/ExtendedCallableFunctionVariant.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public function __construct(
private TrinaryLogic $acceptsNamedArguments,
private TrinaryLogic $mustUseReturnValue,
private ?Assertions $assertions = null,
private ?TrinaryLogic $isStatic = null,
)
{
parent::__construct(
Expand Down Expand Up @@ -92,4 +93,9 @@ public function getAsserts(): Assertions
return $this->assertions ?? Assertions::createEmpty();
}

public function isStaticClosure(): TrinaryLogic
{
return $this->isStatic ?? TrinaryLogic::createMaybe();
}

}
1 change: 1 addition & 0 deletions src/Reflection/GenericParametersAcceptorResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc
$originalParametersAcceptor->acceptsNamedArguments(),
$originalParametersAcceptor->mustUseReturnValue(),
$originalParametersAcceptor->getAsserts(),
$originalParametersAcceptor->isStaticClosure(),
);
}

Expand Down
5 changes: 5 additions & 0 deletions src/Reflection/InaccessibleMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,9 @@ public function getAsserts(): Assertions
return Assertions::createEmpty();
}

public function isStaticClosure(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
4 changes: 4 additions & 0 deletions src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ public function getType(Expr $expr, InitializerExprContext $context): Type
TemplateTypeMap::createEmpty(),
TemplateTypeVarianceMap::createEmpty(),
acceptsNamedArguments: TrinaryLogic::createYes(),
isStatic: TrinaryLogic::createYes(),
);
}
if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
Expand Down Expand Up @@ -928,11 +929,13 @@ public function createFirstClassCallable(
$impurePoints = [];
$acceptsNamedArguments = TrinaryLogic::createYes();
$mustUseReturnValue = TrinaryLogic::createMaybe();
$isStaticClosure = TrinaryLogic::createMaybe();
if ($variant instanceof CallableParametersAcceptor) {
$throwPoints = $variant->getThrowPoints();
$impurePoints = $variant->getImpurePoints();
$acceptsNamedArguments = $variant->acceptsNamedArguments();
$mustUseReturnValue = $variant->mustUseReturnValue();
$isStaticClosure = $variant->isStaticClosure();
} elseif ($function !== null) {
$returnTypeForThrow = $variant->getReturnType();
$throwType = $function->getThrowType();
Expand Down Expand Up @@ -976,6 +979,7 @@ public function createFirstClassCallable(
acceptsNamedArguments: $acceptsNamedArguments,
mustUseReturnValue: $mustUseReturnValue,
assertions: $assertions,
isStatic: $isStaticClosure,
);
}

Expand Down
4 changes: 4 additions & 0 deletions src/Reflection/ParametersAcceptorSelector.php
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc
$usedVariables = [];
$acceptsNamedArguments = TrinaryLogic::createNo();
$mustUseReturnValue = TrinaryLogic::createMaybe();
$isStaticClosure = TrinaryLogic::createMaybe();

foreach ($acceptors as $acceptor) {
$returnTypes[] = $acceptor->getReturnType();
Expand All @@ -747,6 +748,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc
$usedVariables = array_merge($usedVariables, $acceptor->getUsedVariables());
$acceptsNamedArguments = $acceptsNamedArguments->or($acceptor->acceptsNamedArguments());
$mustUseReturnValue = $mustUseReturnValue->or($acceptor->mustUseReturnValue());
$isStaticClosure = $isStaticClosure->or($acceptor->isStaticClosure());
}
$isVariadic = $isVariadic || $acceptor->isVariadic();

Expand Down Expand Up @@ -864,6 +866,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc
$usedVariables,
$acceptsNamedArguments,
$mustUseReturnValue,
isStatic: $isStaticClosure,
);
}

Expand Down Expand Up @@ -902,6 +905,7 @@ private static function wrapAcceptor(ParametersAcceptor $acceptor): ExtendedPara
$acceptor->acceptsNamedArguments(),
$acceptor->mustUseReturnValue(),
$acceptor->getAsserts(),
$acceptor->isStaticClosure(),
);
}

Expand Down
6 changes: 6 additions & 0 deletions src/Reflection/ResolvedFunctionVariantWithCallable.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function __construct(
private TrinaryLogic $acceptsNamedArguments,
private TrinaryLogic $mustUseReturnValue,
private ?Assertions $assertions = null,
private ?TrinaryLogic $isStatic = null,
)
{
}
Expand Down Expand Up @@ -124,4 +125,9 @@ public function getAsserts(): Assertions
return $this->assertions ?? Assertions::createEmpty();
}

public function isStaticClosure(): TrinaryLogic
{
return $this->isStatic ?? TrinaryLogic::createMaybe();
}

}
5 changes: 5 additions & 0 deletions src/Reflection/TrivialParametersAcceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,9 @@ public function getAsserts(): Assertions
return Assertions::createEmpty();
}

public function isStaticClosure(): TrinaryLogic
{
return TrinaryLogic::createMaybe();
}

}
1 change: 1 addition & 0 deletions src/Rules/RuleLevelHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ private function transformAcceptedType(Type $acceptingType, Type $acceptedType):
$acceptedType->getUsedVariables(),
$acceptedType->acceptsNamedArguments(),
$acceptedType->mustUseReturnValue(),
isStatic: $acceptedType->isStaticClosure(),
);
}

Expand Down
5 changes: 5 additions & 0 deletions src/Type/CallableType.php
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,11 @@ public function getAsserts(): Assertions
return Assertions::createEmpty();
}

public function isStaticClosure(): TrinaryLogic
{
return TrinaryLogic::createMaybe();
}

public function toNumber(): Type
{
return new ErrorType();
Expand Down
7 changes: 7 additions & 0 deletions src/Type/CallableTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@
$result = $result->and(new IsSuperTypeOfResult($theirs->isPure()->negate(), []));
}

$ourStatic = $ours->isStaticClosure();
if ($ourStatic->yes()) {

Check warning on line 117 in src/Type/CallableTypeHelper.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ } $ourStatic = $ours->isStaticClosure(); - if ($ourStatic->yes()) { + if (!$ourStatic->no()) { $result = $result->and(new IsSuperTypeOfResult($theirs->isStaticClosure(), [])); } elseif ($ourStatic->no()) { $result = $result->and(new IsSuperTypeOfResult($theirs->isStaticClosure()->negate(), []));

Check warning on line 117 in src/Type/CallableTypeHelper.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ } $ourStatic = $ours->isStaticClosure(); - if ($ourStatic->yes()) { + if (!$ourStatic->no()) { $result = $result->and(new IsSuperTypeOfResult($theirs->isStaticClosure(), [])); } elseif ($ourStatic->no()) { $result = $result->and(new IsSuperTypeOfResult($theirs->isStaticClosure()->negate(), []));
$result = $result->and(new IsSuperTypeOfResult($theirs->isStaticClosure(), []));
} elseif ($ourStatic->no()) {
$result = $result->and(new IsSuperTypeOfResult($theirs->isStaticClosure()->negate(), []));
}

return $result->and($isReturnTypeSuperType);
}

Expand Down
87 changes: 60 additions & 27 deletions src/Type/ClosureType.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@

private Assertions $assertions;

private TrinaryLogic $isStatic;

/**
* @api
* @param list<ParameterReflection>|null $parameters
Expand All @@ -112,6 +114,7 @@
?TrinaryLogic $acceptsNamedArguments = null,
?TrinaryLogic $mustUseReturnValue = null,
?Assertions $assertions = null,
?TrinaryLogic $isStatic = null,
)
{
if ($acceptsNamedArguments === null) {
Expand All @@ -132,6 +135,7 @@
$this->callSiteVarianceMap = $callSiteVarianceMap ?? TemplateTypeVarianceMap::createEmpty();
$this->impurePoints = $impurePoints ?? [new SimpleImpurePoint('functionCall', 'call to an unknown Closure', false)];
$this->assertions = $assertions ?? Assertions::createEmpty();
$this->isStatic = $isStatic ?? TrinaryLogic::createMaybe();
}

public function getAsserts(): Assertions
Expand Down Expand Up @@ -268,7 +272,8 @@
}

return $this->describe(VerbosityLevel::precise()) === $type->describe(VerbosityLevel::precise())
&& $this->isPure()->equals($type->isPure());
&& $this->isPure()->equals($type->isPure())
&& $this->isStatic->equals($type->isStatic);
Comment thread
VincentLanglet marked this conversation as resolved.
}

public function describe(VerbosityLevel $level): string
Expand All @@ -277,38 +282,57 @@
static fn (): string => 'Closure',
function (): string {
if ($this->isCommonCallable) {
return $this->isPure()->yes() ? 'pure-Closure' : 'Closure';
$prefix = $this->isStatic->yes() ? 'static-' : '';
$name = $this->isPure()->yes() ? 'pure-Closure' : 'Closure';

Check warning on line 286 in src/Type/ClosureType.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ function (): string { if ($this->isCommonCallable) { $prefix = $this->isStatic->yes() ? 'static-' : ''; - $name = $this->isPure()->yes() ? 'pure-Closure' : 'Closure'; + $name = !$this->isPure()->no() ? 'pure-Closure' : 'Closure'; return $prefix . $name; }

Check warning on line 286 in src/Type/ClosureType.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ function (): string { if ($this->isCommonCallable) { $prefix = $this->isStatic->yes() ? 'static-' : ''; - $name = $this->isPure()->yes() ? 'pure-Closure' : 'Closure'; + $name = !$this->isPure()->no() ? 'pure-Closure' : 'Closure'; return $prefix . $name; }
return $prefix . $name;
Comment thread
VincentLanglet marked this conversation as resolved.
}

$printer = new Printer();
$selfWithoutParameterNames = new self(
array_map(static fn (ParameterReflection $p): ParameterReflection => new DummyParameter(
'',
$p->getType(),
optional: $p->isOptional() && !$p->isVariadic(),
passedByReference: PassedByReference::createNo(),
variadic: $p->isVariadic(),
defaultValue: $p->getDefaultValue(),
), $this->parameters),
$this->returnType,
$this->variadic,
$this->templateTypeMap,
$this->resolvedTemplateTypeMap,
$this->callSiteVarianceMap,
$this->templateTags,
$this->throwPoints,
$this->impurePoints,
$this->invalidateExpressions,
$this->usedVariables,
$this->acceptsNamedArguments,
$this->mustUseReturnValue,
);
return $this->describeCallable();
},
function (): string {
$prefix = $this->isStatic->yes() ? 'static-' : '';

Check warning on line 293 in src/Type/ClosureType.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ return $this->describeCallable(); }, function (): string { - $prefix = $this->isStatic->yes() ? 'static-' : ''; + $prefix = !$this->isStatic->no() ? 'static-' : ''; if ($this->isCommonCallable) { $name = $this->isPure()->yes() ? 'pure-Closure' : 'Closure';

Check warning on line 293 in src/Type/ClosureType.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ return $this->describeCallable(); }, function (): string { - $prefix = $this->isStatic->yes() ? 'static-' : ''; + $prefix = !$this->isStatic->no() ? 'static-' : ''; if ($this->isCommonCallable) { $name = $this->isPure()->yes() ? 'pure-Closure' : 'Closure';

if ($this->isCommonCallable) {
$name = $this->isPure()->yes() ? 'pure-Closure' : 'Closure';
return $prefix . $name;
}

return $printer->print($selfWithoutParameterNames->toPhpDocNode());
return $prefix . $this->describeCallable();
},
);
}

private function describeCallable(): string
{
$printer = new Printer();
$selfWithoutParameterNames = new self(
array_map(static fn (ParameterReflection $p): ParameterReflection => new DummyParameter(
'',
$p->getType(),
optional: $p->isOptional() && !$p->isVariadic(),
passedByReference: PassedByReference::createNo(),
variadic: $p->isVariadic(),
defaultValue: $p->getDefaultValue(),
), $this->parameters),
$this->returnType,
$this->variadic,
$this->templateTypeMap,
$this->resolvedTemplateTypeMap,
$this->callSiteVarianceMap,
$this->templateTags,
$this->throwPoints,
$this->impurePoints,
$this->invalidateExpressions,
$this->usedVariables,
$this->acceptsNamedArguments,
$this->mustUseReturnValue,
$this->assertions,
$this->isStatic,
);

return $printer->print($selfWithoutParameterNames->toPhpDocNode());
}

public function isOffsetAccessLegal(): TrinaryLogic
{
return TrinaryLogic::createNo();
Expand Down Expand Up @@ -496,6 +520,11 @@
return $this->mustUseReturnValue;
}

public function isStaticClosure(): TrinaryLogic
{
return $this->isStatic;
}

public function isCloneable(): TrinaryLogic
{
return TrinaryLogic::createYes();
Expand Down Expand Up @@ -709,6 +738,7 @@
$this->acceptsNamedArguments,
$this->mustUseReturnValue,
$this->assertions,
$this->isStatic,
);
}

Expand Down Expand Up @@ -761,6 +791,7 @@
$this->acceptsNamedArguments,
$this->mustUseReturnValue,
$this->assertions,
$this->isStatic,
);
}

Expand Down Expand Up @@ -897,7 +928,9 @@
public function toPhpDocNode(): TypeNode
{
if ($this->isCommonCallable) {
return new IdentifierTypeNode($this->isPure()->yes() ? 'pure-Closure' : 'Closure');
$prefix = $this->isStatic->yes() ? 'static-' : '';
$name = $this->isPure()->yes() ? 'pure-Closure' : 'Closure';
return new IdentifierTypeNode($prefix . $name);
}

$parameters = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection,
usedVariables: $variant->getUsedVariables(),
acceptsNamedArguments: $variant->acceptsNamedArguments(),
mustUseReturnValue: $variant->mustUseReturnValue(),
isStatic: $variant->isStaticClosure(),
);
}

Expand Down
5 changes: 5 additions & 0 deletions src/Type/VerbosityLevel.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc
if ($type->isCallable()->yes()) {
$moreVerbose = true;

if ($type instanceof ClosureType && !$type->isStaticClosure()->maybe()) {
$veryVerbose = true;
return $type;
}

// Keep checking if we need to be very verbose.
return $traverse($type);
}
Expand Down
Loading
Loading