Skip to content
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
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 @@ 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
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 @@ 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,7 +272,8 @@ 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);
Comment thread
VincentLanglet marked this conversation as resolved.
}

public function describe(VerbosityLevel $level): string
Expand All @@ -277,38 +282,57 @@ public function describe(VerbosityLevel $level): string
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';
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-' : '';

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 @@ 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 +738,7 @@ public function traverse(callable $cb): Type
$this->acceptsNamedArguments,
$this->mustUseReturnValue,
$this->assertions,
$this->isStatic,
);
}

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

Expand Down Expand Up @@ -897,7 +928,9 @@ public function getFiniteTypes(): array
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