diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index bb51fac85b5..8616d227c86 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -66,6 +66,12 @@ parameters: count: 2 path: src/Analyser/MutatingScope.php + - + rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated. + identifier: phpstanApi.instanceofType + count: 1 + path: src/Analyser/MutatingScope.php + - rawMessage: 'Parameter #2 $node of method PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection::__invoke() expects PhpParser\Node\Expr\ArrowFunction|PhpParser\Node\Expr\Closure|PhpParser\Node\Expr\FuncCall|PhpParser\Node\Stmt\Class_|PhpParser\Node\Stmt\Const_|PhpParser\Node\Stmt\Enum_|PhpParser\Node\Stmt\Function_|PhpParser\Node\Stmt\Interface_|PhpParser\Node\Stmt\Trait_, PhpParser\Node\Stmt\ClassLike given.' identifier: argument.type diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 8c06aca42d5..87c6128be13 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -139,6 +139,8 @@ class MutatingScope implements Scope, NodeCallbackInvoker public const KEEP_VOID_ATTRIBUTE_NAME = 'keepVoid'; private const CONTAINS_SUPER_GLOBAL_ATTRIBUTE_NAME = 'containsSuperGlobal'; + private const COMPLEX_UNION_TYPE_MEMBER_LIMIT = 8; + /** @var Type[] */ private array $resolvedTypes = []; @@ -2743,10 +2745,12 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, } if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) { - $varType = TypeCombinator::intersect( - $varType, - new HasOffsetValueType($dimType, $type), - ); + if (!$this->isComplexUnionType($varType)) { + $varType = TypeCombinator::intersect( + $varType, + new HasOffsetValueType($dimType, $type), + ); + } } $scope = $scope->specifyExpressionType( @@ -3071,9 +3075,36 @@ private function setExpressionCertainty(Expr $expr, TrinaryLogic $certainty): se ); } + /** + * Returns true when the type is a large union with non-trivial + * (IntersectionType) members — a sign of HasOffsetValueType + * combinatorial growth from array|object offset access patterns. + * Operating on such types is expensive and should be skipped. + */ + private function isComplexUnionType(Type $type): bool + { + if (!$type instanceof UnionType) { + return false; + } + $types = $type->getTypes(); + if (count($types) <= self::COMPLEX_UNION_TYPE_MEMBER_LIMIT) { + return false; + } + foreach ($types as $member) { + if ($member instanceof IntersectionType) { + return true; + } + } + return false; + } + public function addTypeToExpression(Expr $expr, Type $type): self { $originalExprType = $this->getType($expr); + if ($this->isComplexUnionType($originalExprType)) { + return $this; + } + $nativeType = $this->getNativeType($expr); if ($originalExprType->equals($nativeType)) { @@ -3100,6 +3131,10 @@ public function removeTypeFromExpression(Expr $expr, Type $typeToRemove): self return $this; } + if ($this->isComplexUnionType($exprType)) { + return $this; + } + return $this->specifyExpressionType( $expr, TypeCombinator::remove($exprType, $typeToRemove), diff --git a/tests/bench/data/wordpress-user.php b/tests/bench/data/wordpress-user.php new file mode 100644 index 00000000000..c574d4bd34d --- /dev/null +++ b/tests/bench/data/wordpress-user.php @@ -0,0 +1,81 @@ + */ + public function to_array(): array { return []; } +} + +/** @var array> */ +$hooks = []; + +/** @return mixed */ +function apply_filters(string $hook, mixed $value, mixed ...$args): mixed { + global $hooks; + if (isset($hooks[$hook])) { foreach ($hooks[$hook] as $cb) { $value = $cb($value); } } + return $value; +} + +function do_action(string $hook, mixed ...$args): void { + global $hooks; + if (isset($hooks[$hook])) { foreach ($hooks[$hook] as $cb) { $cb(...$args); } } +} + +/** @param array|object $data */ +function insert($data): int { + if ($data instanceof \stdClass) { $data = get_object_vars($data); } + elseif ($data instanceof WP_User) { $data = $data->to_array(); } + + if (!empty($data["ID"])) { + $id = (int)$data["ID"]; + $update = true; + $old_1 = $data["old_1"] ?? ""; + $old_2 = $data["old_2"] ?? ""; + $old_3 = $data["old_3"] ?? ""; + $old_4 = $data["old_4"] ?? ""; + $old_5 = $data["old_5"] ?? ""; + $old_6 = $data["old_6"] ?? ""; + $old_7 = $data["old_7"] ?? ""; + $old_8 = $data["old_8"] ?? ""; + $old_9 = $data["old_9"] ?? ""; + $old_10 = $data["old_10"] ?? ""; + } else { + $id = 0; + $update = false; + $old_1 = ""; + $old_2 = ""; + $old_3 = ""; + $old_4 = ""; + $old_5 = ""; + $old_6 = ""; + $old_7 = ""; + $old_8 = ""; + $old_9 = ""; + $old_10 = ""; + } + + $meta_1 = apply_filters("pre_f1", empty($data["f1"]) ? "" : $data["f1"]); + $meta_2 = apply_filters("pre_f2", empty($data["f2"]) ? "" : $data["f2"]); + $meta_3 = apply_filters("pre_f3", empty($data["f3"]) ? "" : $data["f3"]); + $meta_4 = apply_filters("pre_f4", empty($data["f4"]) ? "" : $data["f4"]); + $meta_5 = apply_filters("pre_f5", empty($data["f5"]) ? "" : $data["f5"]); + $meta_6 = apply_filters("pre_f6", empty($data["f6"]) ? "" : $data["f6"]); + $meta_7 = apply_filters("pre_f7", empty($data["f7"]) ? "" : $data["f7"]); + $meta_8 = apply_filters("pre_f8", empty($data["f8"]) ? "" : $data["f8"]); + $meta_9 = apply_filters("pre_f9", empty($data["f9"]) ? "" : $data["f9"]); + $meta_10 = apply_filters("pre_f10", empty($data["f10"]) ? "" : $data["f10"]); + $meta_11 = apply_filters("pre_f11", empty($data["f11"]) ? "" : $data["f11"]); + $meta_12 = apply_filters("pre_f12", empty($data["f12"]) ? "" : $data["f12"]); + $meta_13 = apply_filters("pre_f13", empty($data["f13"]) ? "" : $data["f13"]); + $meta_14 = apply_filters("pre_f14", empty($data["f14"]) ? "" : $data["f14"]); + $meta_15 = apply_filters("pre_f15", empty($data["f15"]) ? "" : $data["f15"]); + $meta_16 = apply_filters("pre_f16", empty($data["f16"]) ? "" : $data["f16"]); + $meta_17 = apply_filters("pre_f17", empty($data["f17"]) ? "" : $data["f17"]); + $meta_18 = apply_filters("pre_f18", empty($data["f18"]) ? "" : $data["f18"]); + $meta_19 = apply_filters("pre_f19", empty($data["f19"]) ? "" : $data["f19"]); + $meta_20 = apply_filters("pre_f20", empty($data["f20"]) ? "" : $data["f20"]); + + do_action("after_insert", $id, $data); + return $id; +} diff --git a/tests/bench/storage/baseline.xml b/tests/bench/storage/baseline.xml index 964b81c3440..aaa7f8c2144 100644 --- a/tests/bench/storage/baseline.xml +++ b/tests/bench/storage/baseline.xml @@ -1,10 +1,10 @@ - + Linux - runnervm35a4x + runnervmeorf1 6.17.0-1010-azure #10~24.04.1-Ubuntu SMP Fri Mar 6 22:00:57 UTC 2026 x86_64 @@ -20,9 +20,9 @@ - 0.9189453125 - 0.30517578125 - 0.109375 + 0.4375 + 0.1552734375 + 0.05615234375 git @@ -30,9 +30,9 @@ - 0.0081062316894531 - 0.2138614654541 - 1.5008449554443 + 0.0061988830566406 + 0.13899803161621 + 0.6098747253418 @@ -42,626 +42,671 @@ + + + + + + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + + + + + + + + + + + + +