88use function dirname ;
99use function extension_loaded ;
1010use function file_get_contents ;
11+ use function in_array ;
1112use function is_file ;
1213use function json_decode ;
1314use function phpversion ;
@@ -21,12 +22,14 @@ final class TurboExtensionEnabler
2122 * version is the short SHA of the last commit touching turbo-ext/src/,
2223 * enforced by the phar.yml turbo-version job.
2324 */
24- public const EXPECTED_EXTENSION_VERSION = 'dee1d70 ' ;
25+ public const EXPECTED_EXTENSION_VERSION = 'e598750 ' ;
2526
2627 private static bool $ typeCombinatorCacheEnabled = false ;
2728
2829 private static bool $ enabled = false ;
2930
31+ private static bool $ trustingOwnTypes = false ;
32+
3033 public static function isLoaded (): bool
3134 {
3235 return extension_loaded ('phpstan_turbo ' );
@@ -159,4 +162,57 @@ public static function enableIfLoaded(): void
159162 self ::$ enabled = true ;
160163 }
161164
165+ /**
166+ * Whether the extension drops the engine's argument and return type
167+ * checks from PHPStan's own code in this process, see
168+ * trustOwnTypesIfSuitable().
169+ */
170+ public static function isTrustingOwnTypes (): bool
171+ {
172+ return self ::$ trustingOwnTypes ;
173+ }
174+
175+ /**
176+ * PHPStan's code is verified by PHPStan itself at the strictest level, so
177+ * the engine's run-time checks of its parameter and return types re-check
178+ * what analysis already proved — at about 8% of the analysis CPU: a
179+ * class-typed parameter costs a class lookup and an instanceof on every
180+ * call, a typed return the same on the way out. With the extension active
181+ * and PHPStan running from a phar, its optimizer pass (TrustedTypes.cpp)
182+ * drops those checks from the code compiled out of the phar. Nothing else
183+ * is touched: extensions, bootstrap files and the analysed project keep
184+ * their checks, including on what they receive from PHPStan and return to
185+ * it — a check sits in the callee.
186+ *
187+ * What is lost is the TypeError at the boundary when such code passes a
188+ * wrong value into PHPStan: it surfaces later, deeper. That is why --debug
189+ * keeps the checks — the "run with --debug" advice on internal errors then
190+ * yields the original error. PHPUnit never gets here, so the test suites
191+ * of PHPStan and of extensions always run fully checked.
192+ *
193+ * Must run right after enableIfLoaded(), before the Composer autoloader
194+ * and preload.php are compiled: the pass rewrites scripts as they are
195+ * compiled, so whatever was compiled earlier keeps its checks.
196+ *
197+ * @param list<string> $argv
198+ */
199+ public static function trustOwnTypesIfSuitable (array $ argv ): void
200+ {
201+ if (!self ::$ enabled ) {
202+ return ;
203+ }
204+ if (in_array ('--debug ' , $ argv , true )) {
205+ return ;
206+ }
207+ if (!class_exists ('Phar ' , false )) {
208+ return ;
209+ }
210+ $ pharPath = Phar ::running (false );
211+ if ($ pharPath === '' ) {
212+ return ;
213+ }
214+
215+ self ::$ trustingOwnTypes = Runtime ::trustTypesUnder ('phar:// ' . $ pharPath . '/ ' );
216+ }
217+
162218}
0 commit comments