3838use PHPStan \Type \NullType ;
3939use PHPStan \Type \ObjectType ;
4040use PHPStan \Type \ObjectWithoutClassType ;
41+ use PHPStan \Type \StringType ;
4142use PHPStan \Type \ThisType ;
4243use PHPStan \Type \Type ;
4344use PHPStan \Type \TypeCombinator ;
5455use Rector \NodeTypeResolver \NodeTypeCorrector \AccessoryNonEmptyStringTypeCorrector ;
5556use Rector \NodeTypeResolver \NodeTypeCorrector \GenericClassStringTypeCorrector ;
5657use Rector \NodeTypeResolver \PHPStan \ObjectWithoutClassTypeWithParentTypes ;
58+ use Rector \Php \PhpVersionProvider ;
5759use Rector \StaticTypeMapper \ValueObject \Type \AliasedObjectType ;
5860use Rector \StaticTypeMapper \ValueObject \Type \ShortenedObjectType ;
5961use Rector \TypeDeclaration \PHPStan \ObjectTypeSpecifier ;
62+ use Rector \ValueObject \PhpVersion ;
6063final class NodeTypeResolver
6164{
6265 /**
@@ -91,6 +94,10 @@ final class NodeTypeResolver
9194 * @readonly
9295 */
9396 private NodeNameResolver $ nodeNameResolver ;
97+ /**
98+ * @readonly
99+ */
100+ private PhpVersionProvider $ phpVersionProvider ;
94101 /**
95102 * @var string
96103 */
@@ -102,7 +109,7 @@ final class NodeTypeResolver
102109 /**
103110 * @param NodeTypeResolverInterface[] $nodeTypeResolvers
104111 */
105- public function __construct (ObjectTypeSpecifier $ objectTypeSpecifier , ClassAnalyzer $ classAnalyzer , GenericClassStringTypeCorrector $ genericClassStringTypeCorrector , ReflectionProvider $ reflectionProvider , AccessoryNonEmptyStringTypeCorrector $ accessoryNonEmptyStringTypeCorrector , AccessoryNonEmptyArrayTypeCorrector $ accessoryNonEmptyArrayTypeCorrector , RenamedClassesDataCollector $ renamedClassesDataCollector , NodeNameResolver $ nodeNameResolver , iterable $ nodeTypeResolvers )
112+ public function __construct (ObjectTypeSpecifier $ objectTypeSpecifier , ClassAnalyzer $ classAnalyzer , GenericClassStringTypeCorrector $ genericClassStringTypeCorrector , ReflectionProvider $ reflectionProvider , AccessoryNonEmptyStringTypeCorrector $ accessoryNonEmptyStringTypeCorrector , AccessoryNonEmptyArrayTypeCorrector $ accessoryNonEmptyArrayTypeCorrector , RenamedClassesDataCollector $ renamedClassesDataCollector , NodeNameResolver $ nodeNameResolver , PhpVersionProvider $ phpVersionProvider , iterable $ nodeTypeResolvers )
106113 {
107114 $ this ->objectTypeSpecifier = $ objectTypeSpecifier ;
108115 $ this ->classAnalyzer = $ classAnalyzer ;
@@ -112,6 +119,7 @@ public function __construct(ObjectTypeSpecifier $objectTypeSpecifier, ClassAnaly
112119 $ this ->accessoryNonEmptyArrayTypeCorrector = $ accessoryNonEmptyArrayTypeCorrector ;
113120 $ this ->renamedClassesDataCollector = $ renamedClassesDataCollector ;
114121 $ this ->nodeNameResolver = $ nodeNameResolver ;
122+ $ this ->phpVersionProvider = $ phpVersionProvider ;
115123 foreach ($ nodeTypeResolvers as $ nodeTypeResolver ) {
116124 if ($ nodeTypeResolver instanceof NodeTypeResolverAwareInterface) {
117125 $ nodeTypeResolver ->autowire ($ this );
@@ -527,6 +535,9 @@ private function resolveNativeTypeWithBuiltinMethodCallFallback(Expr $expr, Scop
527535 if (!$ functionReflection instanceof NativeFunctionReflection) {
528536 return $ scope ->getNativeType ($ expr );
529537 }
538+ if ($ this ->isSubstrOnPHP74 ($ expr )) {
539+ return new UnionType ([new StringType (), new ConstantBooleanType (\false)]);
540+ }
530541 return $ scope ->getType ($ expr );
531542 }
532543 return $ scope ->getNativeType ($ expr );
@@ -554,4 +565,17 @@ private function isEnumTypeMatch($call, ObjectType $objectType): bool
554565 }
555566 return $ classReflection ->getName () === $ objectType ->getClassName ();
556567 }
568+ /**
569+ * substr can return false on php 7.x and bellow
570+ */
571+ private function isSubstrOnPHP74 (FuncCall $ funcCall ): bool
572+ {
573+ if ($ funcCall ->isFirstClassCallable ()) {
574+ return \false;
575+ }
576+ if (!$ this ->nodeNameResolver ->isName ($ funcCall , 'substr ' )) {
577+ return \false;
578+ }
579+ return !$ this ->phpVersionProvider ->isAtLeastPhpVersion (PhpVersion::PHP_80 );
580+ }
557581}
0 commit comments