diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 4425f8aa52..5af1a88189 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -125,6 +125,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StaticType; +use PHPStan\Type\StaticTypeFactory; use PHPStan\Type\StringType; use PHPStan\Type\ThisType; use PHPStan\Type\Type; @@ -3349,26 +3350,22 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, if ($dimType->isInteger()->yes() || $dimType->isString()->yes()) { $exprVarType = $scope->getType($expr->var); if (!$exprVarType instanceof MixedType && !$exprVarType->isArray()->no()) { - $types = [ - new ArrayType(new MixedType(), new MixedType()), - new ObjectType(ArrayAccess::class), - new NullType(), - ]; if ($dimType->isInteger()->yes()) { - $types[] = new StringType(); + $varType = TypeCombinator::intersect($exprVarType, StaticTypeFactory::intOffsetAccessibleType()); + } else { + $varType = TypeCombinator::intersect($exprVarType, StaticTypeFactory::generalOffsetAccessibleType()); } - $offsetValueType = TypeCombinator::intersect($exprVarType, TypeCombinator::union(...$types)); if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) { - $offsetValueType = TypeCombinator::intersect( - $offsetValueType, + $varType = TypeCombinator::intersect( + $varType, new HasOffsetValueType($dimType, $type), ); } $scope = $scope->specifyExpressionType( $expr->var, - $offsetValueType, + $varType, $scope->getNativeType($expr->var), $certainty, ); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 54a92fce7c..8ec4836e4f 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -249,10 +249,6 @@ class NodeScopeResolver /** @var array */ private array $calledMethodResults = []; - private ?Type $nonIntKeyOffsetValueType = null; - - private ?Type $intKeyOffsetValueType = null; - /** * @param string[][] $earlyTerminatingMethodCalls className(string) => methods(string[]) * @param array $earlyTerminatingFunctionCalls @@ -6610,21 +6606,10 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar !$offsetValueType instanceof MixedType && !$offsetValueType->isArray()->yes() ) { - $this->nonIntKeyOffsetValueType ??= TypeCombinator::union( - new ArrayType(new MixedType(), new MixedType()), - new ObjectType(ArrayAccess::class), - new NullType(), - ); - if ($offsetType !== null && $offsetType->isInteger()->yes()) { - $this->intKeyOffsetValueType ??= TypeCombinator::union( - $this->nonIntKeyOffsetValueType, - new StringType(), - ); - - $offsetValueType = TypeCombinator::intersect($offsetValueType, $this->intKeyOffsetValueType); + $offsetValueType = TypeCombinator::intersect($offsetValueType, StaticTypeFactory::intOffsetAccessibleType()); } else { - $offsetValueType = TypeCombinator::intersect($offsetValueType, $this->nonIntKeyOffsetValueType); + $offsetValueType = TypeCombinator::intersect($offsetValueType, StaticTypeFactory::generalOffsetAccessibleType()); } } diff --git a/src/Type/StaticTypeFactory.php b/src/Type/StaticTypeFactory.php index 93fe12d555..b727a386d6 100644 --- a/src/Type/StaticTypeFactory.php +++ b/src/Type/StaticTypeFactory.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use ArrayAccess; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; @@ -16,7 +17,7 @@ public static function falsey(): Type static $falsey; if ($falsey === null) { - $falsey = new UnionType([ + $falsey = TypeCombinator::union( new NullType(), new ConstantBooleanType(false), new ConstantIntegerType(0), @@ -24,7 +25,7 @@ public static function falsey(): Type new ConstantStringType(''), new ConstantStringType('0'), new ConstantArrayType([], []), - ]); + ); } return $falsey; @@ -41,4 +42,33 @@ public static function truthy(): Type return $truthy; } + public static function generalOffsetAccessibleType(): Type + { + static $generalOffsetAccessible; + + if ($generalOffsetAccessible === null) { + $generalOffsetAccessible = TypeCombinator::union( + new ArrayType(new MixedType(), new MixedType()), + new ObjectType(ArrayAccess::class), + new NullType(), + ); + } + + return $generalOffsetAccessible; + } + + public static function intOffsetAccessibleType(): Type + { + static $intOffsetAccessible; + + if ($intOffsetAccessible === null) { + $intOffsetAccessible = TypeCombinator::union( + self::generalOffsetAccessibleType(), + new StringType(), + ); + } + + return $intOffsetAccessible; + } + }