Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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 @@ -220,13 +220,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 @@ -446,6 +448,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
usedVariables: $usedVariables,
acceptsNamedArguments: TrinaryLogic::createYes(),
mustUseReturnValue: $mustUseReturnValue,
isStatic: TrinaryLogic::createFromBoolean($expr->static),
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/PhpDoc/TypeNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,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 @@ -134,6 +134,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();
}

}
1 change: 1 addition & 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
4 changes: 4 additions & 0 deletions src/Reflection/ParametersAcceptorSelector.php
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,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 @@ -753,6 +754,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 @@ -860,6 +862,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc
$usedVariables,
$acceptsNamedArguments,
$mustUseReturnValue,
isStatic: $isStaticClosure,
);
}

Expand Down Expand Up @@ -898,6 +901,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 @@ public static function isParametersAcceptorSuperTypeOf(
$result = $result->and(new IsSuperTypeOfResult($theirs->isPure()->negate(), []));
}

$ourStatic = $ours->isStaticClosure();
if ($ourStatic->yes()) {
$result = $result->and(new IsSuperTypeOfResult($theirs->isStaticClosure(), []));
} elseif ($ourStatic->no()) {
$result = $result->and(new IsSuperTypeOfResult($theirs->isStaticClosure()->negate(), []));
}

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

Expand Down
81 changes: 50 additions & 31 deletions src/Type/ClosureType.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ class ClosureType implements TypeWithClassName, CallableParametersAcceptor

private Assertions $assertions;

private TrinaryLogic $isStatic;

/**
* @api
* @param list<ParameterReflection>|null $parameters
Expand All @@ -112,6 +114,7 @@ public function __construct(
?TrinaryLogic $acceptsNamedArguments = null,
?TrinaryLogic $mustUseReturnValue = null,
?Assertions $assertions = null,
?TrinaryLogic $isStatic = null,
)
{
if ($acceptsNamedArguments === null) {
Expand All @@ -132,6 +135,7 @@ public function __construct(
$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,45 +272,53 @@ public function equals(Type $type): bool
}

return $this->describe(VerbosityLevel::precise()) === $type->describe(VerbosityLevel::precise())
&& $this->isPure()->equals($type->isPure());
&& $this->isPure()->equals($type->isPure())
&& $this->isStatic->equals($type->isStatic);
}

public function describe(VerbosityLevel $level): string
{
return $level->handle(
static fn (): string => 'Closure',
function (): string {
if ($this->isCommonCallable) {
return $this->isPure()->yes() ? 'pure-Closure' : 'Closure';
}

$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,
);
fn (): string => $this->describeBody(true, false),
fn (): string => $this->describeBody(true, true),
);
}

private function describeBody(bool $showPure, bool $showStatic): string
{
$prefix = $showStatic && $this->isStatic->yes() ? 'static-' : '';

return $printer->print($selfWithoutParameterNames->toPhpDocNode());
},
if ($this->isCommonCallable) {
$name = $showPure && $this->isPure()->yes() ? 'pure-Closure' : 'Closure';
return $prefix . $name;
}

$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 $prefix . $printer->print($selfWithoutParameterNames->toPhpDocNode());
}

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

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

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

Expand Down Expand Up @@ -761,6 +779,7 @@ public function traverseSimultaneously(Type $right, callable $cb): Type
$this->acceptsNamedArguments,
$this->mustUseReturnValue,
$this->assertions,
$this->isStatic,
);
}

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
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-14324.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function createMap(): void
'bar2' => static fn() => 'bar2',
'baz2' => static fn() => 'baz2',
];
assertType("array{foo: Closure(): 'foo', bar: Closure(): 'bar', baz: Closure(): 'baz', qux: Closure(): 'qux', quux: Closure(): 'quux', corge: Closure(): 'corge', grault: Closure(): 'grault', garply: Closure(): 'garply', waldo: Closure(): 'waldo', fred: Closure(): 'fred', plugh: Closure(): 'plugh', xyzzy: Closure(): 'xyzzy', thud: Closure(): 'thud', foo1: Closure(): 'foo1', bar1: Closure(): 'bar1', baz1: Closure(): 'baz1', qux1: Closure(): 'qux1', quux1: Closure(): 'quux1', corge1: Closure(): 'corge1', grault1: Closure(): 'grault1', garply1: Closure(): 'garply1', waldo1: Closure(): 'waldo1', fred1: Closure(): 'fred1', plugh1: Closure(): 'plugh1', xyzzy1: Closure(): 'xyzzy1', thud1: Closure(): 'thud1', foo2: Closure(): 'foo2', bar2: Closure(): 'bar2', baz2: Closure(): 'baz2'}", self::$map);
assertType("array{foo: static-Closure(): 'foo', bar: static-Closure(): 'bar', baz: static-Closure(): 'baz', qux: static-Closure(): 'qux', quux: static-Closure(): 'quux', corge: static-Closure(): 'corge', grault: static-Closure(): 'grault', garply: static-Closure(): 'garply', waldo: static-Closure(): 'waldo', fred: static-Closure(): 'fred', plugh: static-Closure(): 'plugh', xyzzy: static-Closure(): 'xyzzy', thud: static-Closure(): 'thud', foo1: static-Closure(): 'foo1', bar1: static-Closure(): 'bar1', baz1: static-Closure(): 'baz1', qux1: static-Closure(): 'qux1', quux1: static-Closure(): 'quux1', corge1: static-Closure(): 'corge1', grault1: static-Closure(): 'grault1', garply1: static-Closure(): 'garply1', waldo1: static-Closure(): 'waldo1', fred1: static-Closure(): 'fred1', plugh1: static-Closure(): 'plugh1', xyzzy1: static-Closure(): 'xyzzy1', thud1: static-Closure(): 'thud1', foo2: static-Closure(): 'foo2', bar2: static-Closure(): 'bar2', baz2: static-Closure(): 'baz2'}", self::$map);

foreach (self::ADDITIONAL_MAPS as $map) {
// added with 3 entries, breaching the closure limit of 32 entries
Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/nsrt/bug-7031.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
class SomeKey {}

function () {
assertType('Closure(int): Generator<int, Bug7031\SomeKey, mixed, void>', static fn(int $value): iterable => yield new SomeKey);
assertType('Closure(int): Generator<int, Bug7031\SomeKey, mixed, void>', static function (int $value): iterable { yield new SomeKey; });
assertType('static-Closure(int): Generator<int, Bug7031\SomeKey, mixed, void>', static fn(int $value): iterable => yield new SomeKey);
assertType('static-Closure(int): Generator<int, Bug7031\SomeKey, mixed, void>', static function (int $value): iterable { yield new SomeKey; });
};
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-9764.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function (): void {
/** @var array<non-empty-string, string> $a */
$a = [];
$c = static fn (): array => $a;
assertType('Closure(): array<non-empty-string, string>', $c);
assertType('static-Closure(): array<non-empty-string, string>', $c);

$r = result($c);
assertType('array<non-empty-string, string>', $r);
Expand Down
Loading
Loading