Skip to content

Commit 807d030

Browse files
SanderMullerclaude
authored andcommitted
Let another program that loads the phar use PHPStan Turbo
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent e4f45f9 commit 807d030

4 files changed

Lines changed: 51 additions & 13 deletions

File tree

‎src/Turbo/TurboExtensionEnabler.php‎

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use function is_file;
1313
use function json_decode;
1414
use function phpversion;
15+
use function realpath;
1516

1617
/**
1718
* Activates the phpstan_turbo extension's shadowing classes.
@@ -62,6 +63,27 @@ public static function getIncompatibleLoadedVersion(): ?string
6263
return $version === false ? 'unknown' : $version;
6364
}
6465

66+
/**
67+
* The phar the running code is compiled from. Code compiled out of a phar
68+
* carries the phar:// prefix of the path the phar was opened with, and
69+
* Composer's bootstrap.php opens it through __DIR__, a resolved path. A
70+
* path another program passes in, such as one with '..' in it, is
71+
* resolved the same way, so that it matches that prefix.
72+
*/
73+
private static function resolvePharPath(?string $pharPath): string
74+
{
75+
if ($pharPath === null) {
76+
return Phar::running(false);
77+
}
78+
79+
$resolvedPath = realpath($pharPath);
80+
if ($resolvedPath === false) {
81+
return '';
82+
}
83+
84+
return $resolvedPath;
85+
}
86+
6587
private static function isCompatible(): bool
6688
{
6789
return self::isLoaded() && phpversion('phpstan_turbo') === self::EXPECTED_EXTENSION_VERSION;
@@ -90,8 +112,13 @@ public static function isTypeCombinatorCacheEnabled(): bool
90112
* implement userland interfaces and may extend userland classes, resolved
91113
* through it) and before anything could autoload one of the shadowed
92114
* classes — a twin already declared cannot be shadowed.
115+
*
116+
* Another program that loads PHPStan's phar, Rector for example, passes
117+
* the path to that phar: Phar::running() only knows the phar that runs.
118+
*
119+
* @api
93120
*/
94-
public static function activateIfCompatible(): void
121+
public static function activateIfCompatible(?string $pharPath = null): void
95122
{
96123
if (!self::isCompatible()) {
97124
return;
@@ -136,7 +163,7 @@ public static function activateIfCompatible(): void
136163
// would otherwise race on. Fork mode requires the guard, and
137164
// ForkParallelChecker only allows fork with the extension active.
138165
if (class_exists('Phar', false)) {
139-
$pharPath = Phar::running(false);
166+
$pharPath = self::resolvePharPath($pharPath);
140167
if ($pharPath !== '') {
141168
Runtime::enablePharForkGuard($pharPath);
142169
}
@@ -178,9 +205,13 @@ public static function isTrustingOwnTypes(): bool
178205
* compiled earlier keeps its checks. Independent of activateIfCompatible()
179206
* — only the version gate matters here.
180207
*
208+
* Another program that loads PHPStan's phar, Rector for example, passes
209+
* the path to that phar: Phar::running() only knows the phar that runs.
210+
*
211+
* @api
181212
* @param list<string> $argv
182213
*/
183-
public static function trustOwnTypesIfSuitable(array $argv): void
214+
public static function trustOwnTypesIfSuitable(array $argv, ?string $pharPath = null): void
184215
{
185216
if (!self::isCompatible()) {
186217
return;
@@ -191,7 +222,7 @@ public static function trustOwnTypesIfSuitable(array $argv): void
191222
if (!class_exists('Phar', false)) {
192223
return;
193224
}
194-
$pharPath = Phar::running(false);
225+
$pharPath = self::resolvePharPath($pharPath);
195226
if ($pharPath === '') {
196227
return;
197228
}

‎src/Turbo/TurboExtensionSelector.php‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ final class TurboExtensionSelector
5555
*/
5656
public const MINIMUM_PHP_VERSION_ID = 80300;
5757

58-
public static function findExtensionForWorkers(): ?string
58+
public static function findExtensionForWorkers(?string $pharPath = null): ?string
5959
{
6060
if (TurboExtensionEnabler::isLoaded()) {
6161
$restartPath = TurboProcessRestarter::getRestartExtensionPath();
@@ -71,7 +71,7 @@ public static function findExtensionForWorkers(): ?string
7171
return null;
7272
}
7373

74-
return self::findExtension();
74+
return self::findExtension($pharPath);
7575
}
7676

7777
/**
@@ -96,10 +96,13 @@ public static function isMissingNextToPhar(): bool
9696
/**
9797
* Locates the distributed extension binary for the current platform —
9898
* present only next to a phar-based installation.
99+
*
100+
* Another program that loads PHPStan's phar, Rector for example, passes
101+
* the path to that phar: Phar::running() only knows the phar that runs.
99102
*/
100-
public static function findExtension(): ?string
103+
public static function findExtension(?string $pharPath = null): ?string
101104
{
102-
$platformDirectory = self::getPlatformDirectoryNextToPhar();
105+
$platformDirectory = self::getPlatformDirectoryNextToPhar($pharPath);
103106
if ($platformDirectory === null) {
104107
return null;
105108
}
@@ -131,7 +134,7 @@ public static function findExtension(): ?string
131134
* binary is built for this runtime: PHP older than the minimum, a debug
132135
* build, a run from source, or a platform without binaries.
133136
*/
134-
private static function getPlatformDirectoryNextToPhar(): ?string
137+
private static function getPlatformDirectoryNextToPhar(?string $pharPath = null): ?string
135138
{
136139
if (PHP_VERSION_ID < self::MINIMUM_PHP_VERSION_ID) {
137140
return null;
@@ -140,7 +143,7 @@ private static function getPlatformDirectoryNextToPhar(): ?string
140143
return null;
141144
}
142145

143-
$pharPath = Phar::running(false);
146+
$pharPath ??= Phar::running(false);
144147
if ($pharPath === '') {
145148
return null;
146149
}

‎src/Turbo/TurboProcessRestarter.php‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,13 @@ public static function getRestartExtensionPath(): ?string
9797
/**
9898
* On success the call never returns — the process image is replaced.
9999
*
100+
* Another program that loads PHPStan's phar, Rector for example, passes
101+
* the path to that phar: Phar::running() only knows the phar that runs.
102+
*
103+
* @api
100104
* @param list<string> $argv
101105
*/
102-
public static function restartIfSuitable(array $argv): void
106+
public static function restartIfSuitable(array $argv, ?string $pharPath = null): void
103107
{
104108
if (get_cfg_var(self::RESTARTED_INI) !== false) {
105109
// already restarted — whatever did not take effect (a binary that
@@ -127,7 +131,7 @@ public static function restartIfSuitable(array $argv): void
127131
return;
128132
}
129133

130-
$extensionPath = extension_loaded('phpstan_turbo') ? null : TurboExtensionSelector::findExtension();
134+
$extensionPath = extension_loaded('phpstan_turbo') ? null : TurboExtensionSelector::findExtension($pharPath);
131135
$opcacheArgs = self::getOpcacheArgs();
132136
if (
133137
$extensionPath === null

‎tests/bootstrap.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
error_reporting(E_ALL);
77

88
require_once __DIR__ . '/../vendor/autoload.php';
9-
TurboExtensionEnabler::activateIfCompatible(); // @phpstan-ignore phpstanApi.method
9+
TurboExtensionEnabler::activateIfCompatible();
1010
require_once __DIR__ . '/PHPStan/Rules/AlwaysFailRule.php';
1111
require_once __DIR__ . '/PHPStan/Rules/DummyRule.php';
1212
require_once __DIR__ . '/phpstan-bootstrap.php';

0 commit comments

Comments
 (0)