Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
63cd441
Compile TrinaryLogic and the result classes from their PHP twins
ondrejmirtes Oct 17, 2026
81f2030
Compile twenty more shadowed classes from their PHP twins
ondrejmirtes Oct 17, 2026
dceb679
Compile the parser visitors from their PHP twins
ondrejmirtes Oct 17, 2026
b3032c3
Speed up compiled parser visitors
ondrejmirtes Oct 17, 2026
b35b6f3
Compile the Template*, *OfType and other leaf Type shadows from their…
ondrejmirtes Oct 17, 2026
f91b443
Call hand ports' direct entries from compiled twins
ondrejmirtes Oct 17, 2026
acaac22
Compile thirty small expression and statement handlers from their twins
ondrejmirtes Oct 17, 2026
0755cde
Compile the remaining self-contained handlers from their twins
ondrejmirtes Oct 17, 2026
4d78d79
Shadow the first rules and checks with compiled twins
ondrejmirtes Oct 17, 2026
f7ce21c
Shadow every rule and check under src/Rules that compiles
ondrejmirtes Oct 17, 2026
4714e10
Shadow the hot helpers rules call outside src/Rules
ondrejmirtes Oct 17, 2026
4560f05
Dispatch interface receivers to native entries and inline trivial get…
ondrejmirtes Oct 17, 2026
384fa8a
Shadow ImpossibleCheckTypeHelper; compile optional by-reference param…
ondrejmirtes Oct 17, 2026
c1cd396
Shadow the src/Type/Php extensions that compile
ondrejmirtes Oct 17, 2026
a249d6e
Let shadowed classes share a short name
ondrejmirtes Oct 17, 2026
ed08ac6
Shadow the hot analysis-pipeline helpers outside src/Rules
ondrejmirtes Oct 17, 2026
c3ddd8d
Dispatch calls with many compiled implementations through a cached sw…
ondrejmirtes Oct 17, 2026
8bfee3a
Grow the dispatch cache and release scalars without a call
ondrejmirtes Oct 17, 2026
221fbee
Read isset($this->property) from the property's slot
ondrejmirtes Oct 17, 2026
d2183a5
Call internal functions without zend_call_function()'s callable resol…
ondrejmirtes Oct 17, 2026
6bfe3d1
Shadow FileTypeMapper, DependencyResolver and 27 more classes that no…
ondrejmirtes Oct 17, 2026
dd85201
Shadow BetterReflection's ReflectionClass, ReflectionParameter and th…
ondrejmirtes Oct 18, 2026
0fe6f33
Shadow BetterReflectionProvider, the optimized source locators and Fi…
ondrejmirtes Oct 18, 2026
b30f672
Shadow PhpDocNodeResolver and BetterReflection's IdentifierType and A…
ondrejmirtes Oct 18, 2026
49498dc
Answer trivial PHP getters without calling them
ondrejmirtes Oct 18, 2026
d266317
Compile the generated runtime identically against every supported PHP
ondrejmirtes Oct 18, 2026
b5843cf
Answer more crossings natively: strtolower(), count() and trivial bod…
ondrejmirtes Oct 18, 2026
6724ff8
Call native implementations directly from more compiled call sites
ondrejmirtes Oct 19, 2026
0d76430
Add an allocation census build of the turbo extension
ondrejmirtes Oct 19, 2026
fbaaf52
Type the TurboCodegen dispatch helpers' reflection parameters
ondrejmirtes Oct 19, 2026
7b87bda
Name the twin's source relative to the repository in generated files
ondrejmirtes Oct 19, 2026
9956a11
Compile php-parser's pretty printer and PHPStan's Printer natively
ondrejmirtes Oct 19, 2026
2bea5e4
Count which natively created objects reach PHP code in the census build
ondrejmirtes Oct 19, 2026
01c2ee7
Count expression walks per node in the census build
ondrejmirtes Oct 19, 2026
d1e17d2
Attribute the census build's expression walks to files, lines and askers
ondrejmirtes Oct 19, 2026
48485da
Cache symbol lookups in the census build's walk origin attribution
ondrejmirtes Oct 20, 2026
10b2712
Time each class's frees in the census build
ondrejmirtes Oct 20, 2026
541c7cf
Bump expected turbo version
ondrejmirtes Oct 21, 2026
3fce36d
Compile only the hot classes natively
ondrejmirtes Oct 22, 2026
96a9d2d
Rename ShadowedByTurboExtension to ImplementedInTurboExtension
ondrejmirtes Oct 22, 2026
ac83af1
Mark compiled classes with #[CompiledIntoTurboExtension]
ondrejmirtes Oct 22, 2026
c336f14
Bump expected turbo version
ondrejmirtes Oct 22, 2026
c2e1456
Generate compiled classes into turbo-ext/src/compiled/
ondrejmirtes Oct 22, 2026
a269553
Bump expected turbo version
ondrejmirtes Oct 22, 2026
8a7c8fc
Resolve classes the PHAR scoper renames through the class map
ondrejmirtes Oct 22, 2026
aee1f54
Bump expected turbo version
ondrejmirtes Oct 22, 2026
64684f3
Refuse to compile signatures the PHAR downgrade would break
ondrejmirtes Oct 22, 2026
6e9e756
Generate code the Linux builds' -Werror accepts
ondrejmirtes Oct 22, 2026
2f24da7
Bump expected turbo version
ondrejmirtes Oct 22, 2026
32a1d3d
Remove the blank line the dropped attribute left in RestrictedUsage
ondrejmirtes Oct 22, 2026
f68654b
Keep the turbo compiler analysable on every PHP version CI runs
ondrejmirtes Oct 22, 2026
5a769f3
Check compiled integer overflow without GCC builtins on MSVC
ondrejmirtes Oct 22, 2026
c3151b9
Read platform-dependent constants from the running engine
ondrejmirtes Oct 22, 2026
93fda6b
Bump expected turbo version
ondrejmirtes Oct 22, 2026
40ff674
Leave the turbo compiler out of the analysis on PHP below 8.4
ondrejmirtes Oct 22, 2026
5847573
Report why the Windows extension could not load its core
ondrejmirtes Oct 22, 2026
e97b602
Bump expected turbo version
ondrejmirtes Oct 22, 2026
707396e
Grow smart_str buffers through functions every PHP release exports
ondrejmirtes Oct 22, 2026
5a1e1e6
Bump expected turbo version
ondrejmirtes Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 6 additions & 3 deletions .claude/skills/rebase-branch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,13 @@ Decide these before replaying (ask the user if the answer isn't obvious):

## 3. Turbo mirrors

Classes carrying `#[ShadowedByTurboExtension(turboClass: ..., implementation: .../turbo-ext/src/X.cpp)]`
have a native C++ twin. Vendored twins live in `VENDORED_PAIRS` in
Classes carrying `#[ImplementedInTurboExtension(implementation: .../turbo-ext/src/X.cpp)]` (named
`ShadowedByTurboExtension` on bases from before the rename) have a hand-written native C++ twin;
classes carrying `#[CompiledIntoTurboExtension]` have one generated by `turbo-ext/bin/compile-twins.php`.
Vendored twins live in `VENDORED_PAIRS` and `VENDORED_COMPILED` in
`build/PHPStan/Build/TurboAttributeCollector.php` and change when their package's version in
`composer.lock` changes. The PHP class and the C++ class must behave identically after every replayed
`composer.lock` changes. Never resolve a conflict inside a generated file: resolve the PHP class, then
rerun `compile-twins.php` and take its output. The PHP class and the C++ class must behave identically after every replayed
commit. A mirror change goes into the same commit as the PHP change it mirrors, never into a trailing
"port" commit.

Expand Down
12 changes: 10 additions & 2 deletions .claude/skills/rebase-branch/rebase-plan.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Read-only report that prepares a rebase of a feature branch onto a fresh base
// tip: both sides' commits, commits the base already contains, files both sides
// touch, the conflicts a merge of the two tips predicts, and the turbo twins
// (#[ShadowedByTurboExtension] PHP class <-> turbo-ext .cpp) whose other half
// (#[ImplementedInTurboExtension] / #[CompiledIntoTurboExtension] PHP class <-> turbo-ext .cpp) whose other half
// needs a port during the replay.
//
// Usage: php rebase-plan.php <new-base-ref> [<feature-ref>=HEAD]
Expand Down Expand Up @@ -137,9 +137,17 @@ function short(string $sha): string
function shadowMap(string $rev): array
{
$map = [];
foreach (gitLines('grep', '-e', 'ShadowedByTurboExtension(', $rev, '--', 'src') as $line) {
// ShadowedByTurboExtension is the hand-written attribute's old name, still
// on bases from before the rename; a compiled class's .cpp follows from
// its short name (TurboAttributeCollector::COMPILED_DIRECTORY)
foreach (gitLines('grep', '-e', 'ShadowedByTurboExtension(', '-e', 'ImplementedInTurboExtension(', '-e', '#\\[CompiledIntoTurboExtension', $rev, '--', 'src') as $line) {
$line = substr($line, strlen($rev) + 1);
[$path, $content] = explode(':', $line, 2);
if (str_contains($content, 'CompiledIntoTurboExtension')) {
$class = 'PHPStan\\' . str_replace('/', '\\', substr($path, 4, -4));
$map[$class] = ['php' => $path, 'cpp' => 'turbo-ext/src/compiled/' . basename($path, '.php') . '.cpp'];
continue;
}
if (preg_match("~implementation:\\s*__DIR__\\s*\\.\\s*'([^']+)'~", $content, $m) !== 1) {
continue;
}
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/phar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ jobs:
- name: "Check the shadowed PHP and C++ implementations are in sync (method parity)"
run: "php turbo-ext/bin/side-by-side.php"

- name: "Check the compiled shadows are generated from the current PHP twins"
run: "php turbo-ext/bin/compile-twins.php --check"

- name: "Check the vendored php-parser matches the version the native parser engine was ported against"
run: |
INSTALLED="$(jq -r '.packages[] | select(.name == "nikic/php-parser") | .version' composer.lock)"
Expand Down
11 changes: 8 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,16 @@ Bugs in PHPStan's type system rarely live alone. A fix in one accessory type usu

Every threshold, fan-out cap, recursion bound, or collection-size limit must be a class constant whose name ends in `_LIMIT`, declared at the top of the owning class. Inline numerics in conditionals (`if (count($this->optionalKeys) <= 10)`) are not acceptable — extract to `self::OPTIONAL_KEYS_LIMIT` (or similar) so the threshold is named, discoverable, and tunable in one place.

### Classes marked with `#[ShadowedByTurboExtension]` have a native C++ mirror
### Classes marked with `#[ImplementedInTurboExtension]` or `#[CompiledIntoTurboExtension]` have a native C++ mirror

Classes carrying the `PHPStan\Turbo\ShadowedByTurboExtension` attribute are reimplemented natively by the phpstan_turbo extension; the attribute names the `.cpp` file implementing it (`build/generate-turbo-manifest.php` derives the full map into `vendor/turbo-shadowed-classes.json` on composer dump-autoload). The two implementations must behave identically — analysis output is bit-for-bit identical with the extension on or off. When editing such a class, always check whether the `.cpp` mirror in `turbo-ext/` needs the same change; if it does, port it following `turbo-ext/CLAUDE.md` (method parity check, differential smoke test, full test suite with the extension loaded). Any commit touching `turbo-ext/src/` requires the follow-up commit bumping `TurboExtensionEnabler::EXPECTED_EXTENSION_VERSION` to that commit's short SHA — run `make bump-turbo`.
Classes carrying one of these attributes (`PHPStan\Turbo\`) are reimplemented natively by the phpstan_turbo extension (`build/generate-turbo-manifest.php` derives the full map into `vendor/turbo-shadowed-classes.json` on composer dump-autoload). The two implementations must behave identically — analysis output is bit-for-bit identical with the extension on or off. What an edit of such a class requires depends on the attribute:

New introduced classes in these categories should be shadowed with a native C++ mirror in the same commit:
* `#[ImplementedInTurboExtension(implementation: ...)]` — the C++ is written by hand in the named `.cpp`. Check whether it needs the same change; if it does, port it following `turbo-ext/CLAUDE.md` (method parity check, differential smoke test, full test suite with the extension loaded).
* `#[CompiledIntoTurboExtension]` — the C++ is generated from the PHP class. Rerun `php turbo-ext/bin/compile-twins.php` (plus `php turbo-ext/bin/generate-declarations.php` when properties or signatures changed) and commit the regenerated files with the change; never edit them by hand. CI fails while they are stale.

Any commit touching `turbo-ext/src/` requires the follow-up commit bumping `TurboExtensionEnabler::EXPECTED_EXTENSION_VERSION` to that commit's short SHA — run `make bump-turbo`.

New introduced classes in these categories should be shadowed with a native C++ mirror in the same commit — compiled (`#[CompiledIntoTurboExtension]`) when the compiler handles them, hand-written otherwise:

* `PHPStan\Type\Type` implementations
* `PHPStan\Analyser\StmtHandler` and `PHPStan\Analyser\ExprHandler` implementations
Expand Down
159 changes: 142 additions & 17 deletions build/PHPStan/Build/TurboAttributeCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use FilesystemIterator;
use JsonException;
use Nette\Utils\Random;
use Nette\Utils\RegexpException;
use Nette\Utils\Strings;
use PhpParser\Comment\Doc;
Expand Down Expand Up @@ -187,25 +188,34 @@
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\PhpDocParser\Printer\Printer;
use PHPStan\Turbo\CompiledIntoTurboExtension;
use PHPStan\Turbo\ImplementedInTurboExtension;
use PHPStan\Turbo\ReferencedByTurboExtension;
use PHPStan\Turbo\ShadowedByTurboExtension;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ReflectionClass;
use RuntimeException;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\Glob;
use Throwable;
use function array_keys;
use function basename;
use function class_exists;
use function count;
use function file_get_contents;
use function interface_exists;
use function json_decode;
use function json_encode;
use function ksort;
use function preg_replace;
use function realpath;
use function sprintf;
use function str_replace;
use function str_starts_with;
use function strlen;
use function strpos;
use function strrpos;
use function strtolower;
use function strtr;
use function substr;
use function var_export;
Expand All @@ -215,8 +225,8 @@
use const JSON_UNESCAPED_SLASHES;

/**
* Collects the ShadowedByTurboExtension and ReferencedByTurboExtension
* attributes with runtime reflection and renders the two generated files:
* Collects the ImplementedInTurboExtension, CompiledIntoTurboExtension and
* ReferencedByTurboExtension attributes with runtime reflection and renders the two generated files:
* vendor/turbo-shadowed-classes.json and vendor/turbo-class-map.php. Shared
* by build/generate-turbo-manifest.php (which writes the artifacts on every
* autoloader dump) and turbo-ext/bin/side-by-side.php (which re-derives and
Expand All @@ -227,12 +237,39 @@
final class TurboAttributeCollector
{

// Shadowed classes living in vendor/ cannot carry the attribute, so
// their pairs are hardcoded. Class name => .cpp
// Shadowed classes living in vendor/ cannot carry the attributes, so
// they are hardcoded. Hand-written ports: class name => .cpp
// where turbo-ext/bin/compile-twins.php writes the compiled classes
public const COMPILED_DIRECTORY = 'turbo-ext/src/compiled';

private const VENDORED_PAIRS = [
NodeTraverser::class => 'turbo-ext/src/NodeTraverser.cpp',
];

// Compiled vendored classes: class name => the stem of the generated
// file, which the short names would make ambiguous (ReflectionClass,
// Standard). Their compile settings are CompiledIntoTurboExtension's
// defaults.
private const VENDORED_COMPILED = [
'PhpParser\\PrettyPrinterAbstract' => 'PhpParserPrettyPrinterAbstract',
'PhpParser\\PrettyPrinter\\Standard' => 'PhpParserStandardPrinter',
'PhpParser\\NameContext' => 'NameContext',
'PHPStan\\BetterReflection\\Identifier\\Identifier' => 'BetterReflectionIdentifier',
'PHPStan\\BetterReflection\\Identifier\\IdentifierType' => 'BetterReflectionIdentifierType',
'PHPStan\\BetterReflection\\Reflection\\Adapter\\ReflectionMethod' => 'BetterReflectionAdapterMethod',
'PHPStan\\BetterReflection\\Reflection\\Adapter\\ReflectionNamedType' => 'BetterReflectionAdapterNamedType',
'PHPStan\\BetterReflection\\Reflection\\Adapter\\ReflectionType' => 'BetterReflectionAdapterType',
'PHPStan\\BetterReflection\\Reflection\\Annotation\\AnnotationHelper' => 'BetterReflectionAnnotationHelper',
'PHPStan\\BetterReflection\\Reflection\\Attribute\\ReflectionAttributeHelper' => 'BetterReflectionAttributeHelper',
'PHPStan\\BetterReflection\\Reflection\\Deprecated\\DeprecatedHelper' => 'BetterReflectionDeprecatedHelper',
'PHPStan\\BetterReflection\\Reflection\\ReflectionAttribute' => 'BetterReflectionAttribute',
'PHPStan\\BetterReflection\\Reflection\\ReflectionClass' => 'BetterReflectionClass',
'PHPStan\\BetterReflection\\Reflection\\ReflectionNamedType' => 'BetterReflectionNamedType',
'PHPStan\\BetterReflection\\Reflection\\ReflectionParameter' => 'BetterReflectionParameter',
'PHPStan\\BetterReflection\\SourceLocator\\Located\\LocatedSource' => 'BetterReflectionLocatedSource',
'PHPStan\\BetterReflection\\Util\\FileHelper' => 'BetterReflectionFileHelper',
];

// Classes the native code references that live in vendor/ cannot carry
// the ReferencedByTurboExtension attribute either; their class-map
// entries are hardcoded. Key (pt_class_refs in turbo-ext/src/support.cpp)
Expand Down Expand Up @@ -270,6 +307,9 @@ final class TurboAttributeCollector
'constExprStringNode' => ConstExprStringNode::class,
'netteStrings' => Strings::class,
'netteRegexpException' => RegexpException::class,
'netteRandom' => Random::class,
'symfonyFinder' => Finder::class,
'symfonyGlob' => Glob::class,
'constExprFloatNode' => ConstExprFloatNode::class,
'thisTypeNode' => ThisTypeNode::class,
'objectShapeNode' => ObjectShapeNode::class,
Expand Down Expand Up @@ -487,15 +527,24 @@ public function __construct(string $root)
* in CI by the name collision detector, so the class name follows from
* the path) and merges the hardcoded vendored entries.
*
* Compiled classes get their generated file derived from the class name
* (see CompiledIntoTurboExtension) and their compile settings returned
* under `compiled`, for turbo-ext/bin/compile-twins.php.
*
* @return array{
* manifest: array<string, array{php: string, cpp: string, turboClass: string, final: bool, parent: string|null, interfaces: list<class-string>, vendored?: bool}>,
* manifest: array<string, array{php: string, cpp: string, turboClass: string, final: bool, parent: string|null, interfaces: list<class-string>, compiled?: true, vendored?: bool}>,
* classMap: array<string, string>,
* referenced: array<string, string>,
* compiled: array<class-string, array{stem: string, ce: string, defineCe: bool, new?: array{string, string}, dispatch?: array{function: string, enum: string, prefix: string, receiver: string, fallback?: string}}>,
* }
*/
public function collect(): array
{
$pairs = self::VENDORED_PAIRS;
$compiledAttributes = [];
foreach (array_keys(self::VENDORED_COMPILED) as $className) {
$compiledAttributes[$className] = new CompiledIntoTurboExtension();
}
$classMap = self::VENDORED_CLASS_MAP;
$referenced = [];

Expand Down Expand Up @@ -523,11 +572,18 @@ public function collect(): array
}
$reflection = new ReflectionClass($className);

$attributes = $reflection->getAttributes(ShadowedByTurboExtension::class);
$attributes = $reflection->getAttributes(ImplementedInTurboExtension::class);
if (count($attributes) > 0) {
$attribute = $attributes[0]->newInstance();
$pairs[$className] = $this->relativize($attribute->implementation);
}
$compiledAttribute = $reflection->getAttributes(CompiledIntoTurboExtension::class);
if (count($compiledAttribute) > 0) {
if (isset($pairs[$className])) {
throw new RuntimeException(sprintf('%s is both implemented in and compiled into the turbo extension', $className));
}
$compiledAttributes[$className] = $compiledAttribute[0]->newInstance();
}

foreach ($reflection->getAttributes(ReferencedByTurboExtension::class) as $referencedAttribute) {
$key = $referencedAttribute->newInstance()->key;
Expand All @@ -539,12 +595,24 @@ public function collect(): array
}
}

$compiled = $this->compiledSettings($compiledAttributes, $pairs);
foreach ($compiled as $className => $settings) {
$pairs[$className] = self::COMPILED_DIRECTORY . '/' . $settings['stem'] . '.cpp';
}

ksort($pairs);
ksort($classMap);
ksort($referenced);
ksort($compiled);

// how many shadowed classes have each short name
$shortNameCounts = [];
foreach (array_keys($pairs) as $className) {
$shortName = substr($className, (int) strrpos('\\' . $className, '\\'));
$shortNameCounts[$shortName] = ($shortNameCounts[$shortName] ?? 0) + 1;
}

$manifest = [];
$byShortName = [];
foreach ($pairs as $className => $cppFile) {
$reflection = new ReflectionClass($className);
$fileName = $reflection->getFileName();
Expand All @@ -553,15 +621,12 @@ public function collect(): array
}

// The name the differential tests declare the native class under
// next to its twin, derived the way the extension derives it -
// so two shadowed classes sharing a short name would arrive at
// one native name and silently shadow each other there.
// next to its twin, derived the way the extension derives it
// (Shadow.cpp, pt_shadow_declared_name()): the short name, or —
// when another shadowed class has the same one — the real name
// past its first namespace segment, so the two stay apart.
$shortName = $reflection->getShortName();
if (isset($byShortName[$shortName])) {
throw new RuntimeException(sprintf('%s and %s share the short name %s, so both would be declared as PHPStanTurbo\\%s next to their twins', $byShortName[$shortName], $className, $shortName, $shortName));
}
$byShortName[$shortName] = $className;
$turboClass = 'PHPStanTurbo\\' . $shortName;
$turboClass = 'PHPStanTurbo\\' . ($shortNameCounts[$shortName] > 1 ? substr($className, (int) strpos($className, '\\') + 1) : $shortName);

$parent = $reflection->getParentClass();
$phpFile = $this->relativize($fileName);
Expand All @@ -575,13 +640,73 @@ public function collect(): array
'parent' => $parent === false ? null : $parent->getName(),
'interfaces' => $reflection->getInterfaceNames(),
];
if (isset($compiled[$className])) {
$entry['compiled'] = true;
}
if (str_starts_with($phpFile, 'vendor/')) {
$entry['vendored'] = true;
}
$manifest[$className] = $entry;
}

return ['manifest' => $manifest, 'classMap' => $classMap, 'referenced' => $referenced];
return ['manifest' => $manifest, 'classMap' => $classMap, 'referenced' => $referenced, 'compiled' => $compiled];
}

/**
* The stem of each compiled class's generated file and its compile
* settings with the defaults filled in. The stem is the short class
* name, unless another compiled class has the same one or a hand-written
* port's file already has it — then the name past its first namespace
* segment, without the separators.
*
* @param array<class-string, CompiledIntoTurboExtension> $attributes
* @param array<string, string> $implemented class => hand-written .cpp
* @return array<class-string, array{stem: string, ce: string, defineCe: bool, new?: array{string, string}, dispatch?: array{function: string, enum: string, prefix: string, receiver: string, fallback?: string}}>
*/
private function compiledSettings(array $attributes, array $implemented): array
{
$takenStems = [];
foreach ($implemented as $cppFile) {
$takenStems[strtolower(basename($cppFile, '.cpp'))] = true;
}
$shortNameCounts = [];
foreach (array_keys($attributes) as $className) {
if (isset(self::VENDORED_COMPILED[$className])) {
continue;
}
$shortName = strtolower(substr($className, (int) strrpos('\\' . $className, '\\')));
$shortNameCounts[$shortName] = ($shortNameCounts[$shortName] ?? 0) + 1;
}

$settings = [];
$stems = [];
foreach ($attributes as $className => $attribute) {
$stem = self::VENDORED_COMPILED[$className] ?? null;
if ($stem === null) {
$shortName = substr($className, (int) strrpos('\\' . $className, '\\'));
$stem = $shortNameCounts[strtolower($shortName)] > 1 || isset($takenStems[strtolower($shortName)])
? str_replace('\\', '', substr($className, (int) strpos($className, '\\') + 1))
: $shortName;
}
if (isset($takenStems[strtolower($stem)]) || isset($stems[strtolower($stem)])) {
throw new RuntimeException(sprintf('The generated file of %s, %s.cpp, would overwrite another class\'s', $className, $stem));
}
$stems[strtolower($stem)] = true;
$entry = [
'stem' => $stem,
'ce' => $attribute->ce ?? 'pt_ce_' . strtolower((string) preg_replace('/([a-z0-9])([A-Z])/', '$1_$2', $stem)),
'defineCe' => $attribute->defineCe,
];
if ($attribute->new !== null) {
$entry['new'] = $attribute->new;
}
if ($attribute->dispatch !== null) {
$entry['dispatch'] = $attribute->dispatch;
}
$settings[$className] = $entry;
}

return $settings;
}

/**
Expand Down
35 changes: 35 additions & 0 deletions build/PHPStan/Build/TurboCodegen/ClassUnit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);

namespace PHPStan\Build\TurboCodegen;

use PhpParser\Node\Stmt\Class_;
use ReflectionClass;

/** One class of the compile set. */
final class ClassUnit
{

/** @var array<string, MethodInfo> lowercase name => method */
public array $methods = [];

/**
* @param ReflectionClass<object> $reflection
*/
public function __construct(
public readonly string $className,
public readonly string $stem,
public readonly string $ce,
public readonly ReflectionClass $reflection,
public readonly TypeOracle $oracle,
public readonly Class_ $node,
public readonly string $implementation,
public readonly bool $defineCe = false,
/** @var array{string, string}|null */
public readonly ?array $newExport = null,
/** @var array{function: string, enum: string, prefix: string, receiver: string, fallback?: string}|null */
public readonly ?array $dispatchExport = null,
)
{
}

}
11 changes: 11 additions & 0 deletions build/PHPStan/Build/TurboCodegen/CompileError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types = 1);

namespace PHPStan\Build\TurboCodegen;

use RuntimeException;

/** A construct outside the compiled subset, or a twin the compiler cannot prove safe to lower. */
final class CompileError extends RuntimeException
{

}
Loading
Loading