Skip to content

Commit 0d793ec

Browse files
committed
IdenticalHandler continued
1 parent dff7a99 commit 0d793ec

File tree

8 files changed

+327
-27
lines changed

8 files changed

+327
-27
lines changed

phpstan-baseline.neon

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ parameters:
1818
count: 1
1919
path: src/Analyser/Generator/ExprHandler/AssignHandler.php
2020

21+
-
22+
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantBooleanType is error-prone and deprecated. Use Type::isTrue() or Type::isFalse() instead.'
23+
identifier: phpstanApi.instanceofType
24+
count: 2
25+
path: src/Analyser/Generator/ExprHandler/BooleanNotHandler.php
26+
2127
-
2228
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantBooleanType is error-prone and deprecated. Use Type::isTrue() or Type::isFalse() instead.'
2329
identifier: phpstanApi.instanceofType
@@ -30,6 +36,12 @@ parameters:
3036
count: 1
3137
path: src/Analyser/Generator/ExprHandler/EqualHandler.php
3238

39+
-
40+
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
41+
identifier: phpstanApi.instanceofType
42+
count: 1
43+
path: src/Analyser/Generator/ExprHandler/IdenticalHandler.php
44+
3345
-
3446
rawMessage: Casting to string something that's already string.
3547
identifier: cast.useless

src/Analyser/Generator/ExprHandler/CastDoubleHandler.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PhpParser\Node\Expr;
77
use PhpParser\Node\Expr\BinaryOp\NotEqual;
88
use PhpParser\Node\Scalar\Float_;
9-
use PhpParser\Node\Scalar\Int_ as ScalarInt;
109
use PhpParser\Node\Stmt;
1110
use PHPStan\Analyser\ExpressionContext;
1211
use PHPStan\Analyser\Generator\ExprAnalysisRequest;

src/Analyser/Generator/ExprHandler/IdenticalHandler.php

Lines changed: 297 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,36 @@
2020
use PHPStan\Analyser\SpecifiedTypes;
2121
use PHPStan\Analyser\TypeSpecifierContext;
2222
use PHPStan\DependencyInjection\AutowiredService;
23-
use PHPStan\Node\Expr\AlwaysRememberedExpr;
24-
use PHPStan\Node\Printer\ExprPrinter;
2523
use PHPStan\Reflection\InitializerExprTypeResolver;
2624
use PHPStan\Reflection\ReflectionProvider;
2725
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
2826
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
2927
use PHPStan\Type\Accessory\NonEmptyArrayType;
28+
use PHPStan\Type\ArrayType;
29+
use PHPStan\Type\BooleanType;
3030
use PHPStan\Type\Constant\ConstantArrayType;
3131
use PHPStan\Type\Constant\ConstantBooleanType;
3232
use PHPStan\Type\Constant\ConstantIntegerType;
3333
use PHPStan\Type\Constant\ConstantStringType;
34+
use PHPStan\Type\FloatType;
35+
use PHPStan\Type\Generic\GenericClassStringType;
3436
use PHPStan\Type\IntegerRangeType;
37+
use PHPStan\Type\IntegerType;
38+
use PHPStan\Type\MixedType;
3539
use PHPStan\Type\NeverType;
40+
use PHPStan\Type\NullType;
3641
use PHPStan\Type\ObjectType;
42+
use PHPStan\Type\ObjectWithoutClassType;
43+
use PHPStan\Type\ResourceType;
44+
use PHPStan\Type\StringType;
3745
use PHPStan\Type\Type;
3846
use PHPStan\Type\TypeCombinator;
3947
use PHPStan\Type\UnionType;
48+
use function array_merge;
49+
use function count;
50+
use function in_array;
51+
use function is_string;
52+
use function strtolower;
4053

4154
/**
4255
* @implements ExprHandler<Identical>
@@ -51,7 +64,6 @@ public function __construct(
5164
private InitializerExprTypeResolver $initializerExprTypeResolver,
5265
private SpecifiedTypesHelper $specifiedTypesHelper,
5366
private ReflectionProvider $reflectionProvider,
54-
private ExprPrinter $exprPrinter,
5567
)
5668
{
5769
}
@@ -119,13 +131,14 @@ private function resolveIdenticalSpecifiedTypes(
119131
}
120132

121133
$unwrappedLeftExpr = $leftExpr;
134+
/*$unwrappedRightExpr = $rightExpr;
122135
if ($leftExpr instanceof AlwaysRememberedExpr) {
123-
//$unwrappedLeftExpr = $leftExpr->getExpr();
136+
$unwrappedLeftExpr = $leftExpr->getExpr();
124137
}
125-
$unwrappedRightExpr = $rightExpr;
138+
126139
if ($rightExpr instanceof AlwaysRememberedExpr) {
127-
//$unwrappedRightExpr = $rightExpr->getExpr();
128-
}
140+
$unwrappedRightExpr = $rightExpr->getExpr();
141+
}*/
129142

130143
$rightType = $rightResult->type;
131144

@@ -177,7 +190,7 @@ private function resolveIdenticalSpecifiedTypes(
177190

178191
$modeArgType = null;
179192
if (count($unwrappedLeftExpr->getArgs()) > 1) {
180-
$modeArgType = yield ExprAnalysisRequest::createNoopRequest($unwrappedLeftExpr->getArgs()[1]->value, $scope);
193+
$modeArgType = (yield ExprAnalysisRequest::createNoopRequest($unwrappedLeftExpr->getArgs()[1]->value, $scope))->type;
181194
}
182195

183196
$specifiedTypes = $this->specifiedTypesHelper->specifyTypesForCountFuncCall(
@@ -297,11 +310,287 @@ private function resolveIdenticalSpecifiedTypes(
297310
}
298311
}
299312

313+
if (
314+
$unwrappedLeftExpr instanceof FuncCall
315+
&& $unwrappedLeftExpr->name instanceof Name
316+
&& in_array(strtolower($unwrappedLeftExpr->name->toString()), [
317+
'substr', 'strstr', 'stristr', 'strchr', 'strrchr', 'strtolower', 'strtoupper', 'ucfirst', 'lcfirst',
318+
'mb_substr', 'mb_strstr', 'mb_stristr', 'mb_strchr', 'mb_strrchr', 'mb_strtolower', 'mb_strtoupper', 'mb_ucfirst', 'mb_lcfirst',
319+
'ucwords', 'mb_convert_case', 'mb_convert_kana',
320+
], true)
321+
&& isset($unwrappedLeftExpr->getArgs()[0])
322+
&& $rightType->isNonEmptyString()->yes()
323+
) {
324+
$argType = (yield ExprAnalysisRequest::createNoopRequest($unwrappedLeftExpr->getArgs()[0]->value, $scope))->type;
325+
326+
if ($argType->isString()->yes()) {
327+
if ($rightType->isNonFalsyString()->yes()) {
328+
return [
329+
$this->specifiedTypesHelper->create(
330+
$unwrappedLeftExpr->getArgs()[0]->value,
331+
TypeCombinator::intersect($argType, new AccessoryNonFalsyStringType()),
332+
TypeSpecifierContext::createTruthy(),
333+
)->setRootExpr($identicalExpr),
334+
new SpecifiedTypes(),
335+
];
336+
}
337+
338+
return [
339+
$this->specifiedTypesHelper->create(
340+
$unwrappedLeftExpr->getArgs()[0]->value,
341+
TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()),
342+
TypeSpecifierContext::createTruthy(),
343+
)->setRootExpr($identicalExpr),
344+
new SpecifiedTypes(),
345+
];
346+
}
347+
}
348+
349+
if ($rightType->isString()->yes()) {
350+
$truthyTypes = null;
351+
$falseyTypes = null;
352+
foreach ($rightType->getConstantStrings() as $constantString) {
353+
$specifiedTypesGen = $this->specifyTypesForConstantStringBinaryExpression($unwrappedLeftExpr, $constantString, $identicalExpr, $scope);
354+
yield from $specifiedTypesGen;
355+
$specifiedTypes = $specifiedTypesGen->getReturn();
356+
357+
if ($specifiedTypes === null) {
358+
continue;
359+
}
360+
if ($truthyTypes === null) {
361+
$truthyTypes = $specifiedTypes[0];
362+
continue;
363+
}
364+
if ($falseyTypes === null) {
365+
$falseyTypes = $specifiedTypes[1];
366+
continue;
367+
}
368+
369+
$truthyTypes = $truthyTypes->intersectWith($specifiedTypes[0]);
370+
$falseyTypes = $falseyTypes->intersectWith($specifiedTypes[1]);
371+
}
372+
373+
if ($truthyTypes !== null && $falseyTypes !== null) {
374+
if ($leftExpr !== $unwrappedLeftExpr) {
375+
$truthyTypes = $truthyTypes->unionWith($this->specifiedTypesHelper->create($leftExpr, $rightType, TypeSpecifierContext::createTruthy())->setRootExpr($identicalExpr));
376+
$falseyTypes = $falseyTypes->unionWith($this->specifiedTypesHelper->create($leftExpr, $rightType, TypeSpecifierContext::createFalsey())->setRootExpr($identicalExpr));
377+
}
378+
return [
379+
$truthyTypes,
380+
$falseyTypes,
381+
];
382+
}
383+
}
384+
385+
$expressions = $this->specifiedTypesHelper->findTypeExpressionsFromBinaryOperation($leftResult->type, $rightResult->type, $identicalExpr);
386+
if ($expressions !== null) {
387+
$exprNode = $expressions[0];
388+
$constantType = $expressions[1];
389+
390+
$unwrappedExprNode = $exprNode;
391+
/*if ($exprNode instanceof AlwaysRememberedExpr) {
392+
$unwrappedExprNode = $exprNode->getExpr();
393+
}*/
394+
395+
$specifiedTypesGen = $this->specifyTypesForConstantBinaryExpression($unwrappedExprNode, $constantType, $identicalExpr, $scope);
396+
yield from $specifiedTypesGen;
397+
$specifiedTypes = $specifiedTypesGen->getReturn();
398+
if ($specifiedTypes !== null) {
399+
if ($exprNode !== $unwrappedExprNode) {
400+
return [
401+
$specifiedTypes[0]->unionWith(
402+
$this->specifiedTypesHelper->create($exprNode, $constantType, TypeSpecifierContext::createTruthy())->setRootExpr($identicalExpr),
403+
),
404+
$specifiedTypes[1]->unionWith(
405+
$this->specifiedTypesHelper->create($exprNode, $constantType, TypeSpecifierContext::createFalsey())->setRootExpr($identicalExpr),
406+
),
407+
];
408+
}
409+
410+
return $specifiedTypes;
411+
}
412+
}
413+
414+
// todo
415+
// $a::class === 'Foo'
300416

301417
return [
302418
new SpecifiedTypes(),
303419
new SpecifiedTypes(),
304420
];
305421
}
306422

423+
/**
424+
* @return Generator<int, GeneratorTValueType, GeneratorTSendType, array{SpecifiedTypes, SpecifiedTypes}|null>
425+
*/
426+
private function specifyTypesForConstantBinaryExpression(
427+
Expr $exprNode,
428+
Type $constantType,
429+
Identical $identicalExpr,
430+
GeneratorScope $scope,
431+
): Generator
432+
{
433+
if ($constantType->isFalse()->yes()) {
434+
$truthyTypes = $this->specifiedTypesHelper->create($exprNode, $constantType, TypeSpecifierContext::createTruthy())->setRootExpr($identicalExpr);
435+
$falseyTypes = $this->specifiedTypesHelper->create($exprNode, $constantType, TypeSpecifierContext::createFalsey())->setRootExpr($identicalExpr);
436+
437+
$exprNodeResult = yield ExprAnalysisRequest::createNoopRequest($exprNode, $scope);
438+
if (!$exprNode instanceof Expr\NullsafeMethodCall && !$exprNode instanceof Expr\NullsafePropertyFetch) {
439+
$falseyTypes = $falseyTypes->unionWith($exprNodeResult->specifiedTruthyTypes);
440+
}
441+
442+
return [
443+
$truthyTypes->unionWith($exprNodeResult->specifiedFalseyTypes)->setRootExpr($identicalExpr),
444+
$falseyTypes->setRootExpr($identicalExpr),
445+
];
446+
}
447+
448+
if ($constantType->isTrue()->yes()) {
449+
$truthyTypes = $this->specifiedTypesHelper->create($exprNode, $constantType, TypeSpecifierContext::createTruthy())->setRootExpr($identicalExpr);
450+
$falseyTypes = $this->specifiedTypesHelper->create($exprNode, $constantType, TypeSpecifierContext::createFalsey())->setRootExpr($identicalExpr);
451+
452+
$exprNodeResult = yield ExprAnalysisRequest::createNoopRequest($exprNode, $scope);
453+
if (!$exprNode instanceof Expr\NullsafeMethodCall && !$exprNode instanceof Expr\NullsafePropertyFetch) {
454+
$falseyTypes = $falseyTypes->unionWith($exprNodeResult->specifiedFalseyTypes);
455+
}
456+
457+
return [
458+
$truthyTypes->unionWith($exprNodeResult->specifiedTruthyTypes)->setRootExpr($identicalExpr),
459+
$falseyTypes->setRootExpr($identicalExpr),
460+
];
461+
}
462+
463+
return null;
464+
}
465+
466+
/**
467+
* @return Generator<int, GeneratorTValueType, GeneratorTSendType, ?array{SpecifiedTypes, SpecifiedTypes}>
468+
*/
469+
private function specifyTypesForConstantStringBinaryExpression(
470+
Expr $exprNode,
471+
Type $constantType,
472+
Expr $identicalExpr,
473+
GeneratorScope $scope,
474+
): Generator
475+
{
476+
$scalarValues = $constantType->getConstantScalarValues();
477+
if (count($scalarValues) !== 1 || !is_string($scalarValues[0])) {
478+
return null;
479+
}
480+
$constantStringValue = $scalarValues[0];
481+
482+
if (
483+
$exprNode instanceof FuncCall
484+
&& $exprNode->name instanceof Name
485+
&& strtolower($exprNode->name->toString()) === 'gettype'
486+
&& isset($exprNode->getArgs()[0])
487+
) {
488+
$type = null;
489+
if ($constantStringValue === 'string') {
490+
$type = new StringType();
491+
}
492+
if ($constantStringValue === 'array') {
493+
$type = new ArrayType(new MixedType(), new MixedType());
494+
}
495+
if ($constantStringValue === 'boolean') {
496+
$type = new BooleanType();
497+
}
498+
if (in_array($constantStringValue, ['resource', 'resource (closed)'], true)) {
499+
$type = new ResourceType();
500+
}
501+
if ($constantStringValue === 'integer') {
502+
$type = new IntegerType();
503+
}
504+
if ($constantStringValue === 'double') {
505+
$type = new FloatType();
506+
}
507+
if ($constantStringValue === 'NULL') {
508+
$type = new NullType();
509+
}
510+
if ($constantStringValue === 'object') {
511+
$type = new ObjectWithoutClassType();
512+
}
513+
514+
if ($type !== null) {
515+
return [
516+
$this->specifiedTypesHelper->create($exprNode, $constantType, TypeSpecifierContext::createTruthy())->setRootExpr($identicalExpr)
517+
->unionWith($this->specifiedTypesHelper->create($exprNode->getArgs()[0]->value, $type, TypeSpecifierContext::createTruthy())->setRootExpr($identicalExpr)),
518+
$this->specifiedTypesHelper->create($exprNode, $constantType, TypeSpecifierContext::createFalsey())->setRootExpr($identicalExpr)
519+
->unionWith($this->specifiedTypesHelper->create($exprNode->getArgs()[0]->value, $type, TypeSpecifierContext::createFalsey())->setRootExpr($identicalExpr)),
520+
];
521+
}
522+
}
523+
524+
if (
525+
$exprNode instanceof FuncCall
526+
&& $exprNode->name instanceof Name
527+
&& strtolower((string) $exprNode->name) === 'get_parent_class'
528+
&& isset($exprNode->getArgs()[0])
529+
) {
530+
$argType = (yield ExprAnalysisRequest::createNoopRequest($exprNode->getArgs()[0]->value, $scope))->type;
531+
$objectType = new ObjectType($constantStringValue);
532+
$classStringType = new GenericClassStringType($objectType);
533+
534+
if ($argType->isString()->yes()) {
535+
return [
536+
$this->specifiedTypesHelper->create(
537+
$exprNode->getArgs()[0]->value,
538+
$classStringType,
539+
TypeSpecifierContext::createTruthy(),
540+
)->setRootExpr($identicalExpr),
541+
new SpecifiedTypes(),
542+
];
543+
}
544+
545+
if ($argType->isObject()->yes()) {
546+
return [
547+
$this->specifiedTypesHelper->create(
548+
$exprNode->getArgs()[0]->value,
549+
$objectType,
550+
TypeSpecifierContext::createTruthy(),
551+
)->setRootExpr($identicalExpr),
552+
new SpecifiedTypes(),
553+
];
554+
}
555+
556+
return [
557+
$this->specifiedTypesHelper->create(
558+
$exprNode->getArgs()[0]->value,
559+
TypeCombinator::union($objectType, $classStringType),
560+
TypeSpecifierContext::createTruthy(),
561+
)->setRootExpr($identicalExpr),
562+
new SpecifiedTypes(),
563+
];
564+
}
565+
566+
if (
567+
$exprNode instanceof FuncCall
568+
&& $exprNode->name instanceof Name
569+
&& in_array(strtolower((string) $exprNode->name), [
570+
'trim', 'ltrim', 'rtrim',
571+
'mb_trim', 'mb_ltrim', 'mb_rtrim',
572+
], true)
573+
&& isset($exprNode->getArgs()[0])
574+
&& $constantStringValue === ''
575+
) {
576+
$argValue = $exprNode->getArgs()[0]->value;
577+
$argType = (yield ExprAnalysisRequest::createNoopRequest($argValue, $scope))->type;
578+
if ($argType->isString()->yes()) {
579+
return [
580+
new SpecifiedTypes(),
581+
$this->specifiedTypesHelper->create(
582+
$argValue,
583+
TypeCombinator::intersect(
584+
new StringType(),
585+
new AccessoryNonEmptyStringType(),
586+
),
587+
TypeSpecifierContext::createTruthy(),
588+
)->setRootExpr($identicalExpr),
589+
];
590+
}
591+
}
592+
593+
return null;
594+
}
595+
307596
}

0 commit comments

Comments
 (0)