Skip to content

Shadow the scalar types natively - #6499

Merged
ondrejmirtes merged 7 commits into
2.3.xfrom
turbo-type-kernel-scalars
Sep 21, 2026
Merged

ondrejmirtes merged 7 commits into
2.3.xfrom
turbo-type-kernel-scalars

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

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 plus decorateReasons() go through real Closures so instanceof Closure keeps holding. Measured alone: −1.40% user CPU (ABBA ×3, paired t = −2.47).
  • BooleanType, ConstantBooleanType
  • IntegerType, 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 through zend_gcvt at precision −1, which matches what the twin's ini_set() dance produces; casts go through the engine's own conversions, so PHP 8.5's diagnostics match too.
  • NeverType, MixedType, StrictMixedType — MixedType gets a real native constructor, so the PHP subclasses ErrorType and TemplateMixedType keep calling parent::__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 in turbo-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.php observes the whole Type interface under the real class names twice, once over the PHP twins and once with the extension activated, and smoke.php compares 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.php and smoke.php all OK. make tests with the extension loaded and active ran 22016 tests, all OK. make phpstan, make cs and make lint were clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_017MvPby652L7wUqGAHEiEcN

@ondrejmirtes
ondrejmirtes force-pushed the turbo-type-kernel-scalars branch 2 times, most recently from dd5275a to 47b9ec5 Compare September 21, 2026 18:20
ondrejmirtes and others added 6 commits September 21, 2026 20:20
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
ondrejmirtes force-pushed the turbo-type-kernel-scalars branch from 47b9ec5 to 03e3098 Compare September 21, 2026 18:31
@ondrejmirtes
ondrejmirtes force-pushed the turbo-type-kernel-scalars branch from 03e3098 to 60c0d8e Compare September 21, 2026 18:38
@ondrejmirtes
ondrejmirtes merged commit 961c54e into 2.3.x Sep 21, 2026
837 of 861 checks passed
@ondrejmirtes
ondrejmirtes deleted the turbo-type-kernel-scalars branch September 21, 2026 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant