Repository navigation
Shadow the scalar types natively - #6499
Merged
Merged
Conversation
ondrejmirtes
force-pushed
the
turbo-type-kernel-scalars
branch
2 times, most recently
from
September 21, 2026 18:20
dd5275a to
47b9ec5
Compare
Three value classes called 18M times per self-analysis run (ScopeContext 10.6M, IsSuperTypeOfResult 6.8M, AcceptsResult 0.6M) get PHPStanTurbo twins: state in declared property slots, singletons per request (RINIT/RSHUTDOWN), lazy reasons and decorateReasons() through real Closures so instanceof Closure keeps holding. Measured on src/Analyser + src/Rules + src/Type, single-threaded, ABBA x3 against the PHP twins with the same .so: -1.40% user CPU (paired t = -2.47), matching the 40ns-per-absorbed-frame estimate. Analysis output byte-identical; smoke, signature parity and the NodeScopeResolver/Type/Rules test suites green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSCEwuXjDzsNw2wSNsDW6D
The first Type classes: PHPStan\Type\BooleanType (implementing PHPStan\Type\Type) and its child PHPStan\Type\Constant\ConstantBooleanType (implementing ConstantScalarType) are declared natively at activation — the first port using a userland interface, native-to-native inheritance and a non-final class that PHP (TemplateBooleanType) may extend, so every $this->method() the twins make goes through the object's class entry. The ten traits the twins are composed of become reusable registrars in TypeTraits.cpp (one per PHP trait, methods declared with the twin's signatures so the engine's covariance checks pass at link time); a class registers its own methods first and reg::Class::traitMethod() skips the names it declared, the precedence PHP applies. TrinaryLogic::toBooleanType() instantiates the shadowing classes directly; the class-reference table gains the types and helpers the ports call into. bin/side-by-side.php now merges trait methods (recursively, with as/insteadof adaptations) into the PHP twin's method list and expands the registrars a C++ class runs, so pairing keeps covering every method; the smoke test compares the pair against the PHP twins over the whole Type interface. Measured on src/Analyser + src/Rules + src/Type, single-threaded, ABBA x3 against a HEAD build: -0.07% user CPU — as expected for a 2M-call leaf pair, the kernel's value accrues as more of it moves native. Output byte-identical; smoke, side-by-side, signature parity (388 methods), the NodeScopeResolver/Type/Rules suites and the self-analysis green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSCEwuXjDzsNw2wSNsDW6D
The integer family: PHPStan\Type\IntegerType and its two children ConstantIntegerType (ConstantScalarType) and IntegerRangeType (CompoundType, with its interval arithmetic mirrored exactly, including the overflow-into-float cases through the engine's own operators). Two more trait registrars (ConstantScalarToBooleanTrait, ConstantNumericComparisonTypeTrait) join TypeTraits.cpp. The Type-family differential moves out of the prefixed one-process mode: a native result object handed to PHP code typed `self` (UnionType -> IsSuperTypeOfResult::extremeIdentity()) cannot work there, so tests/type-family.php observes the whole Type interface under the real names twice - PHP twins and activated extension - and smoke.php compares the observations (~12k data points), asserting which implementation ran. IntegerRangeType::getFiniteTypes() in the PHP twin iterated $i past PHP_INT_MAX into a float and threw a TypeError for any range touching it; the loop now indexes from the minimum, and the port mirrors that. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSCEwuXjDzsNw2wSNsDW6D
…ssStringType natively The string family, a three-level hierarchy: PHPStan\Type\StringType, its children ConstantStringType (ConstantScalarType: class-string detection, callable strings resolved through the reflection provider, offsets, generalization, escaped descriptions) and ClassStringType, and the grandchild Generic\ClassStringType. MaybeCallableTypeTrait joins the trait registrars; typed properties may now carry a class name and null/[] defaults, and class constants their flags. tests/type-family.php gains the string subjects (~39k observations, non-UTF-8 values base64-encoded); the class-reference table gains the collaborators the family calls into (ObjectType, the static accessors, the accessory string types, callable variants, template types). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSCEwuXjDzsNw2wSNsDW6D
FloatType and its child ConstantFloatType (ConstantScalarType; float formatting through zend_gcvt at precision -1 exactly as the twin's ini_set() dance produces it, casts through the engine's own conversions so PHP 8.5's diagnostics match), NullType and VoidType. Two more trait registrars (FalseyBooleanTypeTrait, NonRemoveableTypeTrait) and a float parameter kind for the builder; the type-family differential gains the float/null/void subjects including NAN, the infinities and the integer limits (22k observations). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSCEwuXjDzsNw2wSNsDW6D
NeverType (CompoundType), MixedType (CompoundType + SubtractableType with the subtracted-type logic; a proper native constructor so the PHP subclasses ErrorType and TemplateMixedType keep calling parent::__construct) and StrictMixedType. Three more trait registrars; the identity closures the twins hand to the callback prototype reflections are Closures over an internal static method. The type-family differential gains 30 subjects including the PHP subclasses over the native parents (55k observations). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSCEwuXjDzsNw2wSNsDW6D
ondrejmirtes
force-pushed
the
turbo-type-kernel-scalars
branch
from
September 21, 2026 18:31
47b9ec5 to
03e3098
Compare
ondrejmirtes
force-pushed
the
turbo-type-kernel-scalars
branch
from
September 21, 2026 18:38
03e3098 to
60c0d8e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves the scalar half of the Type kernel, and the value classes every type comparison produces, into the native extension.
What goes native
ScopeContext,IsSuperTypeOfResult,AcceptsResult— value classes constructed about 18M times per self-analysis run. Their state lives in declared property slots, the singletons are per request, and lazy reasons plusdecorateReasons()go through real Closures soinstanceof Closurekeeps holding. Measured alone: −1.40% user CPU (ABBA ×3, paired t = −2.47).BooleanType,ConstantBooleanTypeIntegerType,ConstantIntegerType,IntegerRangeType— the interval arithmetic is mirrored exactly, including the overflow into float through the engine's own operators.StringType,ConstantStringType,ClassStringType,Generic\ClassStringType— class-string detection, callable strings resolved through the reflection provider, offsets, generalization and escaped descriptions.FloatType,ConstantFloatType,NullType,VoidType— float formatting throughzend_gcvtat precision −1, which matches what the twin'sini_set()dance produces; casts go through the engine's own conversions, so PHP 8.5's diagnostics match too.NeverType,MixedType,StrictMixedType—MixedTypegets a real native constructor, so the PHP subclassesErrorTypeandTemplateMixedTypekeep callingparent::__construct().How
The classes are declared under their real names at activation, with the twins' parents, interfaces and finality. Where PHP code extends a native class (
TemplateBooleanType,ErrorType,TemplateMixedType, …), it stays non-final, and every$this->method()the twin makes dispatches through the object's class entry. The PHP traits the types are composed of become reusable registrars inturbo-ext/src/TypeTraits.cpp: one per trait, with methods declared by the twin's signatures so the engine's covariance checks pass at link time. A class registers its own methods first, which is the precedence PHP applies.Tests
turbo-ext/tests/type-family.phpobserves the wholeTypeinterface under the real class names twice, once over the PHP twins and once with the extension activated, andsmoke.phpcompares the two runs. The subjects include the PHP subclasses over the native parents, NAN, the infinities, the integer limits and non-UTF-8 strings.Verification
On this commit, before the rebase onto the current 2.3.x tip: strict build,
side-by-side.php,signature-parity.phpandsmoke.phpall OK.make testswith the extension loaded and active ran 22016 tests, all OK.make phpstan,make csandmake lintwere clean.🤖 Generated with Claude Code
https://claude.ai/code/session_017MvPby652L7wUqGAHEiEcN