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
6 changes: 6 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 39 additions & 4 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)) {
Expand All @@ -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),
Expand Down
81 changes: 81 additions & 0 deletions tests/bench/data/wordpress-user.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace BenchComplexUnion;

class WP_User {
/** @return array<string, mixed> */
public function to_array(): array { return []; }
}

/** @var array<string, list<callable>> */
$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;
}
Loading
Loading