From 3753c715f639d247bafe51d5a17df161fbbc2d49 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Wed, 18 Mar 2026 14:14:03 -0700 Subject: [PATCH 1/4] gen_stub: use real `mixed` type --- build/gen_stub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index c64aa65530f9..2e9f1417b5e1 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -2250,7 +2250,7 @@ public function __clone() class EvaluatedValue { - public /* readonly */ /* mixed */ $value; + public /* readonly */ mixed $value; public SimpleType $type; public Expr $expr; public bool $isUnknownConstValue; From ac356e12fd6fa50b91d2154d4571c649c3961b69 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Wed, 18 Mar 2026 14:15:06 -0700 Subject: [PATCH 2/4] gen_stub: use real `readonly` modifier --- build/gen_stub.php | 138 ++++++++++++++++++++++----------------------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 2e9f1417b5e1..adb54e1bcecd 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -210,8 +210,8 @@ class Context { } class ArrayType extends SimpleType { - private /* readonly */ Type $keyType; - private /* readonly */ Type $valueType; + private readonly Type $keyType; + private readonly Type $valueType; public static function createGenericArray(): self { @@ -249,8 +249,8 @@ public function equals(SimpleType $other): bool { } class SimpleType { - public /* readonly */ string $name; - public /* readonly */ bool $isBuiltin; + public readonly string $name; + public readonly bool $isBuiltin; public static function fromNode(Node $node): SimpleType { if ($node instanceof Node\Name) { @@ -525,8 +525,8 @@ public function equals(SimpleType $other): bool { // when held by an object that is cloned class Type { /** @var SimpleType[] */ - public /* readonly */ array $types; - public /* readonly */ bool $isIntersection; + public readonly array $types; + public readonly bool $isIntersection; public static function fromNode(Node $node): Type { if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) { @@ -723,9 +723,9 @@ function ($type) { return $type->name; }, class ArginfoType { /** @var SimpleType[] $classTypes */ - public /* readonly */ array $classTypes; + public readonly array $classTypes; /** @var SimpleType[] $builtinTypes */ - private /* readonly */ array $builtinTypes; + private readonly array $builtinTypes; /** * @param SimpleType[] $classTypes @@ -761,11 +761,11 @@ class ArgInfo { public const SEND_BY_REF = "1"; public const SEND_PREFER_REF = "ZEND_SEND_PREFER_REF"; - public /* readonly */ string $name; - public /* readonly */ string $sendBy; - public /* readonly */ bool $isVariadic; + public readonly string $name; + public readonly string $sendBy; + public readonly bool $isVariadic; public ?Type $type; - private /* readonly */ ?Type $phpDocType; + private readonly ?Type $phpDocType; public ?string $defaultValue; /** @var AttributeInfo[] */ public array $attributes; @@ -895,7 +895,7 @@ public function isUnknown(): bool } class ConstName extends AbstractConstName { - private /* readonly */ string $const; + private readonly string $const; public function __construct(?Name $namespace, string $const) { @@ -926,8 +926,8 @@ public function getDeclarationName(): string } class ClassConstName extends AbstractConstName { - public /* readonly */ Name $class; - private /* readonly */ string $const; + public readonly Name $class; + private readonly string $const; public function __construct(Name $class, string $const) { @@ -947,8 +947,8 @@ public function getDeclarationName(): string } class PropertyName implements VariableLikeName { - public /* readonly */ Name $class; - private /* readonly */ string $property; + public readonly Name $class; + private readonly string $property; public function __construct(Name $class, string $property) { @@ -978,7 +978,7 @@ public function isDestructor(): bool; } class FunctionName implements FunctionOrMethodName { - private /* readonly */ Name $name; + private readonly Name $name; public function __construct(Name $name) { $this->name = $name; @@ -1042,8 +1042,8 @@ public function isDestructor(): bool { } class MethodName implements FunctionOrMethodName { - public /* readonly */ Name $className; - public /* readonly */ string $methodName; + public readonly Name $className; + public readonly string $methodName; public function __construct(Name $className, string $methodName) { $this->className = $className; @@ -1096,12 +1096,12 @@ class ReturnInfo { self::REFCOUNT_N, ]; - private /* readonly */ bool $byRef; + private readonly bool $byRef; // NOT readonly - gets removed when discarding info for older PHP versions public ?Type $type; - public /* readonly */ ?Type $phpDocType; - public /* readonly */ bool $tentativeReturnType; - public /* readonly */ string $refcount; + public readonly ?Type $phpDocType; + public readonly bool $tentativeReturnType; + public readonly string $refcount; public function __construct(bool $byRef, ?Type $type, ?Type $phpDocType, bool $tentativeReturnType, ?string $refcount) { $this->byRef = $byRef; @@ -1320,19 +1320,19 @@ public function generateVersionDependentFlagCode( } class FuncInfo { - public /* readonly */ FunctionOrMethodName $name; - private /* readonly */ int $classFlags; + public readonly FunctionOrMethodName $name; + private readonly int $classFlags; public int $flags; - public /* readonly */ ?string $aliasType; + public readonly ?string $aliasType; public ?FunctionOrMethodName $alias; - private /* readonly */ bool $isDeprecated; + private readonly bool $isDeprecated; private bool $supportsCompileTimeEval; - public /* readonly */ bool $verify; + public readonly bool $verify; /** @var ArgInfo[] */ - public /* readonly */ array $args; - public /* readonly */ ReturnInfo $return; - private /* readonly */ int $numRequiredArgs; - public /* readonly */ ?string $cond; + public readonly array $args; + public readonly ReturnInfo $return; + private readonly int $numRequiredArgs; + public readonly ?string $cond; public bool $isUndocumentable; private ?int $minimumPhpVersionIdCompatibility; /** @var AttributeInfo[] */ @@ -2250,7 +2250,7 @@ public function __clone() class EvaluatedValue { - public /* readonly */ mixed $value; + public readonly mixed $value; public SimpleType $type; public Expr $expr; public bool $isUnknownConstValue; @@ -2439,12 +2439,12 @@ abstract class VariableLike { protected int $flags; public ?Type $type; - public /* readonly */ ?Type $phpDocType; - private /* readonly */ ?string $link; + public readonly ?Type $phpDocType; + private readonly ?string $link; protected ?int $phpVersionIdMinimumCompatibility; /** @var AttributeInfo[] */ public array $attributes; - protected /* readonly */ ?ExposedDocComment $exposedDocComment; + protected readonly ?ExposedDocComment $exposedDocComment; /** * @param AttributeInfo[] $attributes @@ -2589,14 +2589,14 @@ protected function addModifiersToFieldSynopsis(DOMDocument $doc, DOMElement $fie class ConstInfo extends VariableLike { - public /* readonly */ AbstractConstName $name; - public /* readonly */ Expr $value; + public readonly AbstractConstName $name; + public readonly Expr $value; private bool $isDeprecated; - private /* readonly */ ?string $valueString; - public /* readonly */ ?string $cond; - public /* readonly */ ?string $cValue; - public /* readonly */ bool $isUndocumentable; - private /* readonly */ bool $isFileCacheAllowed; + private readonly ?string $valueString; + public readonly ?string $cond; + public readonly ?string $cValue; + public readonly bool $isUndocumentable; + private readonly bool $isFileCacheAllowed; /** * @param AttributeInfo[] $attributes @@ -3118,12 +3118,12 @@ public static function getString( class PropertyInfo extends VariableLike { - private /* readonly */ int $classFlags; - public /* readonly */ PropertyName $name; - private /* readonly */ ?Expr $defaultValue; - private /* readonly */ ?string $defaultValueString; - private /* readonly */ bool $isDocReadonly; - private /* readonly */ bool $isVirtual; + private readonly int $classFlags; + public readonly PropertyName $name; + private readonly ?Expr $defaultValue; + private readonly ?string $defaultValueString; + private readonly bool $isDocReadonly; + private readonly bool $isVirtual; /** * @param AttributeInfo[] $attributes @@ -3292,8 +3292,8 @@ protected function addModifiersToFieldSynopsis(DOMDocument $doc, DOMElement $fie } class EnumCaseInfo { - public /* readonly */ string $name; - private /* readonly */ ?Expr $value; + public readonly string $name; + private readonly ?Expr $value; public function __construct(string $name, ?Expr $value) { $this->name = $name; @@ -3319,9 +3319,9 @@ public function getDeclaration(array $allConstInfos): string { // Instances of AttributeInfo are immutable and do not need to be cloned // when held by an object that is cloned class AttributeInfo { - public /* readonly */ string $class; + public readonly string $class; /** @var \PhpParser\Node\Arg[] */ - private /* readonly */ array $args; + private readonly array $args; /** @param \PhpParser\Node\Arg[] $args */ public function __construct(string $class, array $args) { @@ -3412,32 +3412,32 @@ public static function createFromGroups(array $attributeGroups): array { } class ClassInfo { - public /* readonly */ Name $name; + public readonly Name $name; private int $flags; public string $type; - public /* readonly */ ?string $alias; - private /* readonly */ ?SimpleType $enumBackingType; - private /* readonly */ bool $isDeprecated; + public readonly ?string $alias; + private readonly ?SimpleType $enumBackingType; + private readonly bool $isDeprecated; private bool $isStrictProperties; /** @var AttributeInfo[] */ private array $attributes; private ?ExposedDocComment $exposedDocComment; private bool $isNotSerializable; /** @var Name[] */ - private /* readonly */ array $extends; + private readonly array $extends; /** @var Name[] */ - private /* readonly */ array $implements; + private readonly array $implements; /** @var ConstInfo[] */ - public /* readonly */ array $constInfos; + public readonly array $constInfos; /** @var PropertyInfo[] */ - private /* readonly */ array $propertyInfos; + private readonly array $propertyInfos; /** @var FuncInfo[] */ public array $funcInfos; /** @var EnumCaseInfo[] */ - private /* readonly */ array $enumCaseInfos; - public /* readonly */ ?string $cond; + private readonly array $enumCaseInfos; + public readonly ?string $cond; public ?int $phpVersionIdMinimumCompatibility; - public /* readonly */ bool $isUndocumentable; + public readonly bool $isUndocumentable; /** * @param AttributeInfo[] $attributes @@ -4736,8 +4736,8 @@ function (FuncInfo $funcInfo) use (&$generatedFunctionDeclarations) { } class DocCommentTag { - public /* readonly */ string $name; - public /* readonly */ ?string $value; + public readonly string $name; + public readonly ?string $value; public function __construct(string $name, ?string $value) { $this->name = $name; @@ -4828,7 +4828,7 @@ public static function makeTagMap(array $tags): array { // Instances of ExposedDocComment are immutable and do not need to be cloned // when held by an object that is cloned class ExposedDocComment { - private /* readonly */ string $docComment; + private readonly string $docComment; public function __construct(string $docComment) { $this->docComment = $docComment; @@ -4869,7 +4869,7 @@ public static function extractExposedComment(array $comments): ?ExposedDocCommen // Instances of FramelessFunctionInfo are immutable and do not need to be cloned // when held by an object that is cloned class FramelessFunctionInfo { - public /* readonly */ int $arity; + public readonly int $arity; public function __construct(string $json) { // FIXME: Should have some validation From 2802ee0e9920a88a435bf33d8a20fc782856322f Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Wed, 18 Mar 2026 14:22:09 -0700 Subject: [PATCH 3/4] gen_stub: use `match` rather than `switch` when possible --- build/gen_stub.php | 187 ++++++++++++++++----------------------------- 1 file changed, 65 insertions(+), 122 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index adb54e1bcecd..34dd4b33e25d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -329,24 +329,16 @@ public static function fromString(string $typeString): SimpleType */ public static function fromValue($value): SimpleType { - switch (gettype($value)) { - case "NULL": - return SimpleType::null(); - case "boolean": - return new SimpleType("bool", true); - case "integer": - return new SimpleType("int", true); - case "double": - return new SimpleType("float", true); - case "string": - return new SimpleType("string", true); - case "array": - return new SimpleType("array", true); - case "object": - return new SimpleType("object", true); - default: - throw new Exception("Type \"" . gettype($value) . "\" cannot be inferred based on value"); - } + return match (gettype($value)) { + "NULL" => SimpleType::null(), + "boolean" => new SimpleType("bool", true), + "integer" => new SimpleType("int", true), + "double" => new SimpleType("float", true), + "string" => new SimpleType("string", true), + "array" => new SimpleType("array", true), + "object" => new SimpleType("object", true), + default => throw new Exception("Type \"" . gettype($value) . "\" cannot be inferred based on value"), + }; } public static function null(): SimpleType @@ -394,38 +386,23 @@ public function isMixed(): bool { private function toTypeInfo(): array { assert($this->isBuiltin); - switch ($this->name) { - case "null": - return ["IS_NULL", "MAY_BE_NULL"]; - case "false": - return ["IS_FALSE", "MAY_BE_FALSE"]; - case "true": - return ["IS_TRUE", "MAY_BE_TRUE"]; - case "bool": - return ["_IS_BOOL", "MAY_BE_BOOL"]; - case "int": - return ["IS_LONG", "MAY_BE_LONG"]; - case "float": - return ["IS_DOUBLE", "MAY_BE_DOUBLE"]; - case "string": - return ["IS_STRING", "MAY_BE_STRING"]; - case "array": - return ["IS_ARRAY", "MAY_BE_ARRAY"]; - case "object": - return ["IS_OBJECT", "MAY_BE_OBJECT"]; - case "callable": - return ["IS_CALLABLE", "MAY_BE_CALLABLE"]; - case "mixed": - return ["IS_MIXED", "MAY_BE_ANY"]; - case "void": - return ["IS_VOID", "MAY_BE_VOID"]; - case "static": - return ["IS_STATIC", "MAY_BE_STATIC"]; - case "never": - return ["IS_NEVER", "MAY_BE_NEVER"]; - default: - throw new Exception("Not implemented: $this->name"); - } + return match ($this->name) { + "null" => ["IS_NULL", "MAY_BE_NULL"], + "false" => ["IS_FALSE", "MAY_BE_FALSE"], + "true" => ["IS_TRUE", "MAY_BE_TRUE"], + "bool" => ["_IS_BOOL", "MAY_BE_BOOL"], + "int" => ["IS_LONG", "MAY_BE_LONG"], + "float" => ["IS_DOUBLE", "MAY_BE_DOUBLE"], + "string" => ["IS_STRING", "MAY_BE_STRING"], + "array" => ["IS_ARRAY", "MAY_BE_ARRAY"], + "object" => ["IS_OBJECT", "MAY_BE_OBJECT"], + "callable" => ["IS_CALLABLE", "MAY_BE_CALLABLE"], + "mixed" => ["IS_MIXED", "MAY_BE_ANY"], + "void" => ["IS_VOID", "MAY_BE_VOID"], + "static" => ["IS_STATIC", "MAY_BE_STATIC"], + "never" => ["IS_NEVER", "MAY_BE_NEVER"], + default => throw new Exception("Not implemented: $this->name"), + }; } public function toTypeCode(): string { @@ -439,14 +416,11 @@ public function toTypeMask(): string { public function toOptimizerTypeMaskForArrayKey(): string { assert($this->isBuiltin); - switch ($this->name) { - case "int": - return "MAY_BE_ARRAY_KEY_LONG"; - case "string": - return "MAY_BE_ARRAY_KEY_STRING"; - default: - throw new Exception("Type $this->name cannot be an array key"); - } + return match ($this->name) { + "int" => "MAY_BE_ARRAY_KEY_LONG", + "string" => "MAY_BE_ARRAY_KEY_STRING", + default => throw new Exception("Type $this->name cannot be an array key"), + }; } public function toOptimizerTypeMaskForArrayValue(): string { @@ -454,34 +428,21 @@ public function toOptimizerTypeMaskForArrayValue(): string { return "MAY_BE_ARRAY_OF_OBJECT"; } - switch ($this->name) { - case "null": - return "MAY_BE_ARRAY_OF_NULL"; - case "false": - return "MAY_BE_ARRAY_OF_FALSE"; - case "true": - return "MAY_BE_ARRAY_OF_TRUE"; - case "bool": - return "MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE"; - case "int": - return "MAY_BE_ARRAY_OF_LONG"; - case "float": - return "MAY_BE_ARRAY_OF_DOUBLE"; - case "string": - return "MAY_BE_ARRAY_OF_STRING"; - case "array": - return "MAY_BE_ARRAY_OF_ARRAY"; - case "object": - return "MAY_BE_ARRAY_OF_OBJECT"; - case "resource": - return "MAY_BE_ARRAY_OF_RESOURCE"; - case "mixed": - return "MAY_BE_ARRAY_OF_ANY"; - case "ref": - return "MAY_BE_ARRAY_OF_REF"; - default: - throw new Exception("Type $this->name cannot be an array value"); - } + return match ($this->name) { + "null" => "MAY_BE_ARRAY_OF_NULL", + "false" => "MAY_BE_ARRAY_OF_FALSE", + "true" => "MAY_BE_ARRAY_OF_TRUE", + "bool" => "MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE", + "int" => "MAY_BE_ARRAY_OF_LONG", + "float" => "MAY_BE_ARRAY_OF_DOUBLE", + "string" => "MAY_BE_ARRAY_OF_STRING", + "array" => "MAY_BE_ARRAY_OF_ARRAY", + "object" => "MAY_BE_ARRAY_OF_OBJECT", + "resource" => "MAY_BE_ARRAY_OF_RESOURCE", + "mixed" => "MAY_BE_ARRAY_OF_ANY", + "ref" => "MAY_BE_ARRAY_OF_REF", + default => throw new Exception("Type $this->name cannot be an array value"), + }; } public function toOptimizerTypeMask(): string { @@ -489,18 +450,13 @@ public function toOptimizerTypeMask(): string { return "MAY_BE_OBJECT"; } - switch ($this->name) { - case "resource": - return "MAY_BE_RESOURCE"; - case "callable": - return "MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_OBJECT"; - case "iterable": - return "MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_OBJECT"; - case "mixed": - return "MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY"; - } - - return $this->toTypeMask(); + return match ($this->name) { + "resource" => "MAY_BE_RESOURCE", + "callable" => "MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_OBJECT", + "iterable" => "MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_OBJECT", + "mixed" => "MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY", + default => $this->toTypeMask(), + }; } public function toEscapedName(): string { @@ -824,16 +780,11 @@ private function getDefaultValueAsArginfoString(): string { } public function getDefaultValueAsMethodSynopsisString(): ?string { - switch ($this->defaultValue) { - case 'UNKNOWN': - return null; - case 'false': - case 'true': - case 'null': - return "&{$this->defaultValue};"; - } - - return $this->defaultValue; + return match ($this->defaultValue) { + 'UNKNOWN' => null, + 'false' | 'true' | 'null' => "&{$this->defaultValue};", + default => $this->defaultValue, + }; } public function toZendInfo(): string { @@ -1940,20 +1891,12 @@ private function getReturnValueSection(DOMDocument $doc): DOMElement { } else if (count($returnType->types) === 1) { $type = $returnType->types[0]; - switch ($type->name) { - case 'void': - $descriptionNode = $doc->createEntityReference('return.void'); - break; - case 'true': - $descriptionNode = $doc->createEntityReference('return.true.always'); - break; - case 'bool': - $descriptionNode = $doc->createEntityReference('return.success'); - break; - default: - $descriptionNode = new DOMText("Description."); - break; - } + $descriptionNode = match ($type->name) { + 'void' => $doc->createEntityReference('return.void'), + 'true' => $doc->createEntityReference('return.true.always'), + 'bool' => $doc->createEntityReference('return.success'), + default => new DOMText("Description."), + }; $returnDescriptionPara->appendChild($descriptionNode); } else { $returnDescriptionPara->appendChild(new DOMText("Description.")); From 999bf2b4206406d1839cc59d13b73d618ae29977 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Wed, 18 Mar 2026 14:35:08 -0700 Subject: [PATCH 4/4] gen_stub: use first class callables where possible --- build/gen_stub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 34dd4b33e25d..25933cccfa57 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -486,7 +486,7 @@ class Type { public static function fromNode(Node $node): Type { if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) { - $nestedTypeObjects = array_map(['Type', 'fromNode'], $node->types); + $nestedTypeObjects = array_map(Type::fromNode(...), $node->types); $types = []; foreach ($nestedTypeObjects as $typeObject) { array_push($types, ...$typeObject->types);