diff --git a/src/Analyser/ExprHandler/Helper/ClosureTypeResolver.php b/src/Analyser/ExprHandler/Helper/ClosureTypeResolver.php index 2a519ebcdea..733a8436568 100644 --- a/src/Analyser/ExprHandler/Helper/ClosureTypeResolver.php +++ b/src/Analyser/ExprHandler/Helper/ClosureTypeResolver.php @@ -229,6 +229,9 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $closureImpurePoints = []; $invalidateExpressions = []; + // a body walked while the enclosing body observes keeps its unresolved + // template arguments for the enclosing body to resolve + $resolveTemplateArguments = $scope->getCurrentTemplateArgumentFrame()?->isObserving() !== true; try { $walkStorage = new ExpressionResultStorage(); $closureScope->pushExpressionResultStorage($walkStorage); @@ -268,7 +271,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu } $closureYieldStatements[] = [$node, $scope]; - }, StatementContext::createTopLevel())->toPublic(); + }, StatementContext::createTopLevel($resolveTemplateArguments))->toPublic(); } finally { $closureScope->popExpressionResultStorage(); self::$resolveClosureTypeDepth--; diff --git a/src/Analyser/ExprHandler/NewHandler.php b/src/Analyser/ExprHandler/NewHandler.php index 61244aa9fbe..4097b770b47 100644 --- a/src/Analyser/ExprHandler/NewHandler.php +++ b/src/Analyser/ExprHandler/NewHandler.php @@ -783,9 +783,13 @@ private function unresolvedArgumentList(ClassReflection $classReflection, New_ $ if (!$type instanceof TemplateType) { return $type; } - if ($synthetic || ($allowUnresolved && $frame->isObserving())) { + if ($synthetic) { return new UnresolvedTemplateArgumentType($site, $type, null); } + if ($allowUnresolved && $frame->isObserving()) { + // a send in a body enclosing this one decided the site + return $frame->resolveSentFromEnclosingBody($site, $type->getName()) ?? new UnresolvedTemplateArgumentType($site, $type, null); + } return $frame->resolveOrUnconstrained($site, $type); })); @@ -797,7 +801,8 @@ private function rekeyParentTemplateArgument(UnresolvedTemplateArgumentType $typ return $type->getInitialType() ?? $template->getDefault() ?? $template->getBound(); } if ($allowUnresolved && $frame->isObserving()) { - return $type->withSite($site, $template); + // a send in a body enclosing this one decided the site + return $frame->resolveSentFromEnclosingBody($site, $template->getName()) ?? $type->withSite($site, $template); } return $frame->resolve($site, $template->getName()) ?? $type->getInitialType() ?? $frame->resolveOrUnconstrained($site, $template); diff --git a/src/Analyser/Generics/TemplateArgumentFrame.php b/src/Analyser/Generics/TemplateArgumentFrame.php index 87481248043..b4325c665c8 100644 --- a/src/Analyser/Generics/TemplateArgumentFrame.php +++ b/src/Analyser/Generics/TemplateArgumentFrame.php @@ -68,6 +68,7 @@ public static function returnTypeOfCall(ParametersAcceptor $acceptor, MutatingSc * @param array $byRefSites spl_object_id() of the closure node => the node, its statement index, whether every invocation was seen * @param array $staticVariableTypes spl_object_id() of a `static` variable node => the node, its phpdoc and native type - see StaticVariableInference * @param array}> $staticVariableConditionalExpressions spl_object_id() of the last statement of a run of `static` statements => the statement, the conditional expressions between the run's variables - see StaticVariableInference::getRuns() + * @param array $sentResolutionKeys the keys of the resolutions a send decided - see resolveSentFromEnclosingBody() */ public function __construct( private readonly ?self $parent, @@ -80,6 +81,7 @@ public function __construct( private readonly array $byRefSites = [], private readonly array $staticVariableTypes = [], private readonly array $staticVariableConditionalExpressions = [], + private readonly array $sentResolutionKeys = [], ) { } @@ -131,6 +133,7 @@ public function withStaticVariableTypes(array $staticVariableTypes, array $state $this->byRefSites, $staticVariableTypes, $conditionalExpressions, + $this->sentResolutionKeys, ); } @@ -190,6 +193,7 @@ public function withObservedClosures(array $closureResolutions, array $closureSi byRefSites: $byRefSites, staticVariableTypes: $this->staticVariableTypes, staticVariableConditionalExpressions: $this->staticVariableConditionalExpressions, + sentResolutionKeys: $this->sentResolutionKeys, ); } @@ -331,6 +335,26 @@ public function resolve(Expr $site, string $templateName): ?Type return null; } + /** + * The resolution a body enclosing this one gave a site of a closure body it + * encloses, when a send in it decided the argument - the closure's own walk + * cannot see that send. An argument the enclosing body merely inferred is + * left to the closure's walk, which infers it from its own state. + */ + public function resolveSentFromEnclosingBody(Expr $site, string $templateName): ?Type + { + $key = self::key($site, $templateName); + if (isset($this->sentResolutionKeys[$key], $this->resolutions[$key])) { + return $this->resolutions[$key]; + } + + if ($this->parent !== null) { + return $this->parent->resolveSentFromEnclosingBody($site, $templateName); + } + + return null; + } + /** * Distinguishes cache entries computed while observing from those computed * with the resolutions installed (the closure type cache keys on scope state diff --git a/src/Analyser/Generics/TemplateArgumentResolver.php b/src/Analyser/Generics/TemplateArgumentResolver.php index 9a7c80b12ef..40627a62bad 100644 --- a/src/Analyser/Generics/TemplateArgumentResolver.php +++ b/src/Analyser/Generics/TemplateArgumentResolver.php @@ -58,14 +58,14 @@ public function resolve(TemplateArgumentConstraints $constraints, ?TemplateArgum TemplateArgumentStats::increment('closureObservationPasses'); } - return new TemplateArgumentFrame($parent, array_diff_key($resolutions, $closureKeys), $templateSiteStatementIndexes, $closureSignatureBody, $closureSignatureStmts, observingClosures: true); + return new TemplateArgumentFrame($parent, array_diff_key($resolutions, $closureKeys), $templateSiteStatementIndexes, $closureSignatureBody, $closureSignatureStmts, observingClosures: true, sentResolutionKeys: $this->collectSentResolutionKeys($observations)); } if (TemplateArgumentStats::$enabled && count($settledClosureSites) > 0) { TemplateArgumentStats::increment('closureSitesSettled', count($settledClosureSites)); } - return new TemplateArgumentFrame($parent, $resolutions, $this->collectSiteStatementIndexes($siteIndexes, $settledClosureSites), $closureSignatureBody, $closureSignatureStmts, $settledClosureSites, byRefSites: $this->collectByRefSites($observations, $siteIndexes)); + return new TemplateArgumentFrame($parent, $resolutions, $this->collectSiteStatementIndexes($siteIndexes, $settledClosureSites), $closureSignatureBody, $closureSignatureStmts, $settledClosureSites, byRefSites: $this->collectByRefSites($observations, $siteIndexes), sentResolutionKeys: $this->collectSentResolutionKeys($observations)); } /** @@ -225,6 +225,23 @@ private function collectByRefSites(array $observations, array $siteIndexes): arr return $byRefSites; } + /** + * @param array, lowerBounds: list, unconstrainingSend: bool}> $observations + * @return array + */ + private function collectSentResolutionKeys(array $observations): array + { + $keys = []; + foreach ($observations as $key => $observation) { + if ($observation['sends'] === [] || ClosureSignatureInference::isClosureSignatureMarker($observation['marker'])) { + continue; + } + $keys[$key] = true; + } + + return $keys; + } + /** * @param array, lowerBounds: list, unconstrainingSend: bool}> $observations * @param array $resolutions diff --git a/src/Analyser/Traverser/GenericTypeTemplateTraverser.php b/src/Analyser/Traverser/GenericTypeTemplateTraverser.php index f1b0ddddacd..803df92063f 100644 --- a/src/Analyser/Traverser/GenericTypeTemplateTraverser.php +++ b/src/Analyser/Traverser/GenericTypeTemplateTraverser.php @@ -64,6 +64,13 @@ public function traverse(Type $type, callable $traverse): Type // markers - the real site re-keys and resolves them $synthetic = $this->site->getAttribute(TemplateArgumentFrame::SYNTHETIC_SITE_ATTRIBUTE) === true; if ($synthetic || ($this->allowUnresolved && $this->frame->isObserving())) { + if (!$synthetic) { + // a send in a body enclosing this one decided the site + $resolved = $this->frame->resolveSentFromEnclosingBody($this->site, $type->getName()); + if ($resolved !== null) { + return $resolved; + } + } if ($initialType instanceof UnresolvedTemplateArgumentType) { return $initialType; } diff --git a/src/Reflection/ResolvedFunctionVariantWithOriginal.php b/src/Reflection/ResolvedFunctionVariantWithOriginal.php index af85439f365..93e9fd7cb74 100644 --- a/src/Reflection/ResolvedFunctionVariantWithOriginal.php +++ b/src/Reflection/ResolvedFunctionVariantWithOriginal.php @@ -400,6 +400,11 @@ private function resolveResolvableTemplateTypes(Type $type, TemplateTypeVariance private function unresolvedOrResolvedTemplateArgument(TemplateType $template, Type $inferred, Expr $site, TemplateArgumentFrame $frame, bool $allowUnresolved, bool $onlyCovariant): Type { if ($allowUnresolved && $frame->isObserving()) { + // a send in a body enclosing this one decided the site + $resolved = $frame->resolveSentFromEnclosingBody($site, $template->getName()); + if ($resolved !== null) { + return $resolved; + } if ($inferred instanceof UnresolvedTemplateArgumentType) { return $inferred; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-15415.php b/tests/PHPStan/Analyser/nsrt/bug-15415.php new file mode 100644 index 00000000000..3fd9356d669 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-15415.php @@ -0,0 +1,67 @@ + + */ + public function keyBy(callable $callback): Coll + { + return new Coll(); + } +} + +/** + * @template T + * @param callable(): T $callback + * @return T + */ +function run(callable $callback) +{ + return $callback(); +} + +final class Test +{ + /** + * @param Coll $c + * @return Coll + */ + public function blockClosure(Coll $c): Coll + { + $r = run(function () use ($c): Coll { + $keyed = $c->keyBy(fn (int $i): string => $i > 0 ? 'positive' : 'negative'); + assertType('Bug15415Types\Coll', $keyed); + + return $keyed; + }); + assertType('Bug15415Types\Coll', $r); + + return $r; + } + + /** + * @param Coll $c + * @return Coll + */ + public function closureInVariable(Coll $c): Coll + { + $f = function () use ($c): Coll { + return $c->keyBy(fn (int $i): string => $i > 0 ? 'positive' : 'negative'); + }; + $r = $f(); + assertType('Bug15415Types\Coll', $r); + + return $r; + } +} diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 260fde1475f..5ce1b033cd3 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1419,4 +1419,10 @@ public function testBug13325(): void $this->analyse([__DIR__ . '/data/bug-13325.php'], []); } + #[RequiresPhp('>= 8.0.0')] + public function testBug15415(): void + { + $this->analyse([__DIR__ . '/data/bug-15415.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-15415.php b/tests/PHPStan/Rules/Methods/data/bug-15415.php new file mode 100644 index 00000000000..b191f0606cc --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-15415.php @@ -0,0 +1,260 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug15415; + +/** + * @template TKey of array-key + * @template TValue + */ +final class Coll +{ + /** + * @param array $items + */ + public function __construct(public array $items = []) + { + } + + /** + * @template TNewKey of array-key + * @param callable(TValue): TNewKey $callback + * @return Coll + */ + public function keyBy(callable $callback): Coll + { + return new Coll(); + } + + /** + * @template TMapKey of array-key + * @template TMapValue + * @param callable(TValue): Coll $callback + * @return Coll + */ + public function mapWithKeys(callable $callback): Coll + { + return new Coll(); + } + + /** + * @template TMakeKey of array-key + * @template TMakeValue + * @param array $items + * @return Coll + */ + public static function make(array $items): Coll + { + return new Coll($items); + } +} + +/** + * @template TFnKey of array-key + * @template TFnValue + * @param array $items + * @return Coll + */ +function collect(array $items): Coll +{ + return new Coll($items); +} + +/** @template T */ +class Box +{ +} + +/** @template T */ +class ParentBox +{ + /** @param T $value */ + public function __construct(public mixed $value) + { + } +} + +/** + * @template T + * @extends ParentBox + */ +class ChildBox extends ParentBox +{ +} + +/** + * @template T + * @param callable(): T $callback + * @return T + */ +function run(callable $callback): mixed +{ + return $callback(); +} + +final class Test +{ + /** + * @param Coll $c + * @return Coll + */ + public function blockClosure(Coll $c): Coll + { + return run(function () use ($c): Coll { + return $c->keyBy(fn (int $i): string => $i > 0 ? 'positive' : 'negative'); + }); + } + + /** + * @param Coll $c + * @return Coll + */ + public function arrowFunction(Coll $c): Coll + { + return run(fn (): Coll => $c->keyBy(fn (int $i): string => $i > 0 ? 'positive' : 'negative')); + } + + /** + * @param Coll $c + * @return Coll + */ + public function direct(Coll $c): Coll + { + return $c->keyBy(fn (int $i): string => $i > 0 ? 'positive' : 'negative'); + } + + /** + * @param Coll $c + * @return Coll + */ + public function closureInVariable(Coll $c): Coll + { + $f = function () use ($c): Coll { + return $c->keyBy(fn (int $i): string => $i > 0 ? 'positive' : 'negative'); + }; + + return $f(); + } + + /** + * @param Coll $c + * @return Coll + */ + public function staticBlockClosure(Coll $c): Coll + { + return run(static function () use ($c): Coll { + return $c->keyBy(fn (int $i): string => $i > 0 ? 'positive' : 'negative'); + }); + } + + /** + * @param Coll $c + * @return Coll + */ + public function nestedBlockClosures(Coll $c): Coll + { + return run(function () use ($c): Coll { + return run(function () use ($c): Coll { + return $c->keyBy(fn (int $i): string => $i > 0 ? 'positive' : 'negative'); + }); + }); + } + + /** + * @param Coll $c + * @return Coll + */ + public function blockClosureInArrowFunction(Coll $c): Coll + { + return run(fn (): Coll => run(function () use ($c): Coll { + return $c->keyBy(fn (int $i): string => $i > 0 ? 'positive' : 'negative'); + })); + } + + /** + * @param Coll $c + * @return Coll + */ + public function immediatelyInvoked(Coll $c): Coll + { + return (function () use ($c): Coll { + return $c->keyBy(fn (int $i): string => $i > 0 ? 'positive' : 'negative'); + })(); + } + + /** + * @return Coll + */ + public function newInBlockClosure(): Coll + { + return run(function (): Coll { + return new Coll(['a' => 1, 'b' => 2]); + }); + } + + /** + * @return Coll + */ + public function functionCallInBlockClosure(): Coll + { + return run(function (): Coll { + return collect(['a' => 1, 'b' => 2]); + }); + } + + /** + * @return Coll + */ + public function staticCallInBlockClosure(): Coll + { + return run(function (): Coll { + return Coll::make(['a' => 1, 'b' => 2]); + }); + } + + /** + * @param Coll> $c + * @return Coll + */ + public function mapWithKeysBlockCallback(Coll $c): Coll + { + return $c->mapWithKeys(function (Coll $inner): Coll { + return $inner->keyBy(fn (int $i): string => $i > 0 ? 'positive' : 'negative'); + }); + } + + /** + * @return Box + */ + public function newWithoutConstructorInClosureVariable(): Box + { + $f = function (): Box { + return new Box(); + }; + + return $f(); + } + + /** + * @return ChildBox + */ + public function newWithParentConstructorInBlockClosure(): ChildBox + { + return run(function (): ChildBox { + return new ChildBox('a'); + }); + } + + /** + * @return ChildBox + */ + public function newWithParentConstructorInClosureVariable(): ChildBox + { + $f = function (): ChildBox { + return new ChildBox('a'); + }; + + return $f(); + } +} diff --git a/turbo-ext/src/ClosureTypeResolver.cpp b/turbo-ext/src/ClosureTypeResolver.cpp index b1127d440d1..aaf83dfb1dd 100644 --- a/turbo-ext/src/ClosureTypeResolver.cpp +++ b/turbo-ext/src/ClosureTypeResolver.cpp @@ -1432,7 +1432,14 @@ class ClosureTypeResolver ZVAL_COPY_VALUE(&captures[4], closureImpurePoints.raw()); ZVAL_COPY_VALUE(&captures[5], invalidateExpressions.raw()); zv::Val callback = pt_native_closure_new(&closureWalkCallbackBody, 6, captures, 0b111110); - zv::Val context = pt_statement_context_create_top_level(); + // a body walked while the enclosing body observes keeps its + // unresolved template arguments for the enclosing body to resolve + zv::Val frame = pt_mutating_scope_get_current_template_argument_frame(Z_OBJ_P(scope)); + bool observing = false; + zv::Val context; + if (EXPECTED(!frame.isUndef()) && (frame.isNull() || EXPECTED(pt_template_argument_frame_is_observing(frame.raw(), observing)))) { + context = pt_statement_context_create_top_level(!observing); + } if (EXPECTED(!context.isUndef())) { zv::Val internalResult = pt_node_scope_resolver_process_stmt_nodes_internal(OBJ_PROP_NUM(self, slots::nodeScopeResolver), expr, stmtsHold.raw(), closureScope.raw(), walkStorage.raw(), callback.raw(), context.raw()); if (EXPECTED(!internalResult.isUndef())) { diff --git a/turbo-ext/src/NewHandler.cpp b/turbo-ext/src/NewHandler.cpp index 42e18fc70a4..4173a34cda4 100644 --- a/turbo-ext/src/NewHandler.cpp +++ b/turbo-ext/src/NewHandler.cpp @@ -1228,15 +1228,20 @@ class NewHandler if (UNEXPECTED(defaultType.isUndef()) || !defaultType.isNull()) return defaultType; return templateGetBound(templateType); } + zv::Val templateName = templateGetName(templateType); + if (UNEXPECTED(templateName.isUndef())) return zv::Val(); + if (UNEXPECTED(!requireString(templateName.raw(), "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame::resolve", "templateName"))) return zv::Val(); if (allowUnresolved) { bool observing; if (UNEXPECTED(!pt_template_argument_frame_is_observing(frame, observing))) return zv::Val(); - if (observing) return unresolvedWithSite(type, site, templateType); + if (observing) { + // a send in a body enclosing this one decided the site + zv::Val sent = pt_template_argument_frame_resolve_sent_from_enclosing_body(frame, site, Z_STR_P(templateName.raw())); + if (UNEXPECTED(sent.isUndef()) || !sent.isNull()) return sent; + return unresolvedWithSite(type, site, templateType); + } } - zv::Val templateName = templateGetName(templateType); - if (UNEXPECTED(templateName.isUndef())) return zv::Val(); - if (UNEXPECTED(!requireString(templateName.raw(), "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame::resolve", "templateName"))) return zv::Val(); zv::Val resolved = pt_template_argument_frame_resolve(frame, site, Z_STR_P(templateName.raw())); if (UNEXPECTED(resolved.isUndef()) || !resolved.isNull()) return resolved; zv::Val initialType = unresolvedGetInitialType(type); @@ -1718,6 +1723,18 @@ class NewHandler bool unresolved = synthetic; if (!unresolved && allowUnresolved) { if (UNEXPECTED(!pt_template_argument_frame_is_observing(frame, unresolved))) return; + if (unresolved) { + // a send in a body enclosing this one decided the site + zv::Val name = templateGetName(type); + if (UNEXPECTED(name.isUndef())) return; + if (UNEXPECTED(!requireString(name.raw(), "PHPStan\\Analyser\\Generics\\TemplateArgumentFrame::resolve", "templateName"))) return; + zv::Val resolved = pt_template_argument_frame_resolve_sent_from_enclosing_body(frame, site, Z_STR_P(name.raw())); + if (UNEXPECTED(resolved.isUndef())) return; + if (!resolved.isNull()) { + resolved.intoReturnValue(return_value); + return; + } + } } if (unresolved) { zval marker; diff --git a/turbo-ext/src/ResolvedFunctionVariantWithOriginal.cpp b/turbo-ext/src/ResolvedFunctionVariantWithOriginal.cpp index 1bffe1006fa..82d12dce91d 100644 --- a/turbo-ext/src/ResolvedFunctionVariantWithOriginal.cpp +++ b/turbo-ext/src/ResolvedFunctionVariantWithOriginal.cpp @@ -352,10 +352,20 @@ class ResolvedFunctionVariantWithOriginal /* Mirrors unresolvedOrResolvedTemplateArgument() (private). */ static zv::Val unresolvedOrResolvedTemplateArgument(zval *templateType, zval *inferred, zval *site, zval *frame, bool allowUnresolved, bool onlyCovariant) { + zv::Val name = templateName(templateType); + if (UNEXPECTED(name.isUndef())) return zv::Val(); + if (UNEXPECTED(Z_TYPE_P(name.raw()) != IS_STRING)) { + zend_type_error("phpstan_turbo: getName() must return string"); + return zv::Val(); + } if (allowUnresolved) { bool observing; if (UNEXPECTED(!pt_template_argument_frame_is_observing(frame, observing))) return zv::Val(); if (observing) { + // a send in a body enclosing this one decided the site + zv::Val resolved = pt_template_argument_frame_resolve_sent_from_enclosing_body(frame, site, Z_STR_P(name.raw())); + if (UNEXPECTED(resolved.isUndef())) return zv::Val(); + if (!resolved.isNull()) return resolved; if (Z_TYPE_P(inferred) == IS_OBJECT && instanceof_function(Z_OBJCE_P(inferred), pt_ce_unresolved_template_argument_type)) return zv::Val::copyOf(zv::Ref(inferred)); if (onlyCovariant && !(Z_TYPE_P(inferred) == IS_OBJECT && instanceof_function(Z_OBJCE_P(inferred), pt_ce_never_type))) return zv::Val::copyOf(zv::Ref(inferred)); zv::Val unresolved; @@ -363,12 +373,6 @@ class ResolvedFunctionVariantWithOriginal return unresolved; } } - zv::Val name = templateName(templateType); - if (UNEXPECTED(name.isUndef())) return zv::Val(); - if (UNEXPECTED(Z_TYPE_P(name.raw()) != IS_STRING)) { - zend_type_error("phpstan_turbo: getName() must return string"); - return zv::Val(); - } zv::Val resolved = pt_template_argument_frame_resolve(frame, site, Z_STR_P(name.raw())); if (UNEXPECTED(resolved.isUndef())) return zv::Val(); if (!resolved.isNull()) return resolved; diff --git a/turbo-ext/src/TemplateArgumentFrame.cpp b/turbo-ext/src/TemplateArgumentFrame.cpp index 001fd35d37d..a2f4f4f2811 100644 --- a/turbo-ext/src/TemplateArgumentFrame.cpp +++ b/turbo-ext/src/TemplateArgumentFrame.cpp @@ -93,9 +93,10 @@ class TemplateArgumentFrame * $settledClosureSites = [], private readonly bool $observingClosures = * false, private readonly array $byRefSites = [], private readonly array * $staticVariableTypes = [], private readonly array - * $staticVariableConditionalExpressions = []); NULL for null / []. false = + * $staticVariableConditionalExpressions = [], private readonly array + * $sentResolutionKeys = []); NULL for null / []. false = * pending exception (a repeated construction modifies readonly properties) */ - [[nodiscard]] bool construct(zval *parent, zval *resolutions, zval *siteStatementIndexes, zval *closureSignatureBody, zval *closureSignatureStmts, zval *settledClosureSites, bool observingClosures, zval *byRefSites, zval *staticVariableTypes, zval *staticVariableConditionalExpressions) const + [[nodiscard]] bool construct(zval *parent, zval *resolutions, zval *siteStatementIndexes, zval *closureSignatureBody, zval *closureSignatureStmts, zval *settledClosureSites, bool observingClosures, zval *byRefSites, zval *staticVariableTypes, zval *staticVariableConditionalExpressions, zval *sentResolutionKeys) const { if (UNEXPECTED(Z_TYPE_P(OBJ_PROP_NUM(self, slots::parent)) != IS_UNDEF)) { zend_throw_error(NULL, "Cannot modify readonly property %s::$parent", ZSTR_VAL(self->ce->name)); @@ -158,6 +159,12 @@ class TemplateArgumentFrame ZVAL_EMPTY_ARRAY(&value); pt_write_slot(self, slots::staticVariableConditionalExpressions, &value); } + if (sentResolutionKeys != NULL) { + pt_write_slot(self, slots::sentResolutionKeys, sentResolutionKeys); + } else { + ZVAL_EMPTY_ARRAY(&value); + pt_write_slot(self, slots::sentResolutionKeys, &value); + } return true; } @@ -215,7 +222,8 @@ class TemplateArgumentFrame Z_TYPE_P(OBJ_PROP_NUM(self, slots::observingClosures)) == IS_TRUE, OBJ_PROP_NUM(self, slots::byRefSites), staticVariableTypes, - conditionalExpressions + conditionalExpressions, + OBJ_PROP_NUM(self, slots::sentResolutionKeys) ); } @@ -269,8 +277,8 @@ class TemplateArgumentFrame /* Mirrors withObservedClosures(): new self($this->parent, * ($this->resolutions ?? []) + $closureResolutions, * $this->siteStatementIndexes + $closureSiteStatementIndexes, the body, - * its statements, $settledClosureSites, byRefSites: $byRefSites); UNDEF = - * pending exception */ + * its statements, $settledClosureSites, byRefSites: $byRefSites, the + * static variable types and sentResolutionKeys); UNDEF = pending exception */ zv::Val withObservedClosures(zval *closureResolutions, zval *closureSiteStatementIndexes, zval *settledClosureSites, zval *byRefSites) const { zval *parent = pt_typed_slot(self, slots::parent, self->ce, "parent"); @@ -292,7 +300,8 @@ class TemplateArgumentFrame false, byRefSites, OBJ_PROP_NUM(self, slots::staticVariableTypes), - OBJ_PROP_NUM(self, slots::staticVariableConditionalExpressions) + OBJ_PROP_NUM(self, slots::staticVariableConditionalExpressions), + OBJ_PROP_NUM(self, slots::sentResolutionKeys) ); } @@ -336,12 +345,12 @@ class TemplateArgumentFrame zval *closureSignatureStmts() const { return OBJ_PROP_NUM(self, slots::closureSignatureStmts); } /* new self(...); UNDEF = pending exception */ - static zv::Val create(zval *parent, zval *resolutions, zval *siteStatementIndexes, zval *closureSignatureBody, zval *closureSignatureStmts, zval *settledClosureSites, bool observingClosures, zval *byRefSites, zval *staticVariableTypes = NULL, zval *staticVariableConditionalExpressions = NULL) + static zv::Val create(zval *parent, zval *resolutions, zval *siteStatementIndexes, zval *closureSignatureBody, zval *closureSignatureStmts, zval *settledClosureSites, bool observingClosures, zval *byRefSites, zval *staticVariableTypes = NULL, zval *staticVariableConditionalExpressions = NULL, zval *sentResolutionKeys = NULL) { zval object; if (UNEXPECTED(object_init_ex(&object, pt_ce_template_argument_frame) != SUCCESS)) return zv::Val(); zv::Val frame = zv::Val::adopt(object); - if (UNEXPECTED(!TemplateArgumentFrame(Z_OBJ_P(frame.raw())).construct(parent, resolutions, siteStatementIndexes, closureSignatureBody, closureSignatureStmts, settledClosureSites, observingClosures, byRefSites, staticVariableTypes, staticVariableConditionalExpressions))) return zv::Val(); + if (UNEXPECTED(!TemplateArgumentFrame(Z_OBJ_P(frame.raw())).construct(parent, resolutions, siteStatementIndexes, closureSignatureBody, closureSignatureStmts, settledClosureSites, observingClosures, byRefSites, staticVariableTypes, staticVariableConditionalExpressions, sentResolutionKeys))) return zv::Val(); return frame; } @@ -492,6 +501,43 @@ class TemplateArgumentFrame } } + /* Mirrors resolveSentFromEnclosingBody(): the resolution of the key on + * the nearest frame that lists it in sentResolutionKeys; PHP null for + * none. UNDEF = pending exception */ + zv::Val resolveSentFromEnclosingBody(zval *site, zend_string *templateName) const + { + smart_str key = {}; + smart_str_append_long(&key, (zend_long) Z_OBJ_HANDLE_P(site)); + smart_str_appendc(&key, '#'); + smart_str_append(&key, templateName); + smart_str_0(&key); + zv::Str keyStr = zv::Str::adopt(key.s); + + zend_object *frame = self; + for (;;) { + zval *sentKeys = OBJ_PROP_NUM(frame, slots::sentResolutionKeys); + zval *resolutions = OBJ_PROP_NUM(frame, slots::resolutions); + if (Z_TYPE_P(sentKeys) == IS_ARRAY && Z_TYPE_P(resolutions) == IS_ARRAY) { + zval *sent = zend_symtable_find(Z_ARRVAL_P(sentKeys), keyStr.get()); + if (sent != NULL && Z_TYPE_P(sent) != IS_NULL) { + zval *found = zend_symtable_find(Z_ARRVAL_P(resolutions), keyStr.get()); + if (found != NULL && Z_TYPE_P(found) != IS_NULL) return zv::Val::copyOf(zv::Ref(found)); + } + } + zval *parent = pt_typed_slot(frame, slots::parent, frame->ce, "parent"); + if (UNEXPECTED(parent == NULL)) return zv::Val(); + if (Z_TYPE_P(parent) == IS_NULL) return zv::Val::null(); + if (UNEXPECTED(Z_OBJCE_P(parent) != frame->ce)) { + zval siteZv, nameZv; + ZVAL_COPY_VALUE(&siteZv, site); + ZVAL_STR(&nameZv, templateName); + zv::Args argv{&siteZv, &nameZv}; + return pt_type_call(Z_OBJ_P(parent), PT_LC("resolvesentfromenclosingbody"), 2, argv); + } + frame = Z_OBJ_P(parent); + } + } + /* Mirrors getResolutionCacheKeySuffix(). */ zv::Val getResolutionCacheKeySuffix() const { @@ -645,9 +691,9 @@ bool pt_template_argument_frame_is_settled_closure_site(zval *frame, zval *site, return true; } -zv::Val pt_template_argument_frame_new(zval *parent, zval *resolutions, zval *siteStatementIndexes, zval *closureSignatureBody, zval *closureSignatureStmts, zval *settledClosureSites, bool observingClosures, zval *byRefSites) +zv::Val pt_template_argument_frame_new(zval *parent, zval *resolutions, zval *siteStatementIndexes, zval *closureSignatureBody, zval *closureSignatureStmts, zval *settledClosureSites, bool observingClosures, zval *byRefSites, zval *sentResolutionKeys) { - return TemplateArgumentFrame::create(parent != NULL && Z_TYPE_P(parent) == IS_NULL ? NULL : parent, resolutions != NULL && Z_TYPE_P(resolutions) == IS_NULL ? NULL : resolutions, siteStatementIndexes, closureSignatureBody != NULL && Z_TYPE_P(closureSignatureBody) == IS_NULL ? NULL : closureSignatureBody, closureSignatureStmts, settledClosureSites, observingClosures, byRefSites); + return TemplateArgumentFrame::create(parent != NULL && Z_TYPE_P(parent) == IS_NULL ? NULL : parent, resolutions != NULL && Z_TYPE_P(resolutions) == IS_NULL ? NULL : resolutions, siteStatementIndexes, closureSignatureBody != NULL && Z_TYPE_P(closureSignatureBody) == IS_NULL ? NULL : closureSignatureBody, closureSignatureStmts, settledClosureSites, observingClosures, byRefSites, NULL, NULL, sentResolutionKeys); } zv::Val pt_template_argument_frame_with_observed_closures(zval *frame, zval *closureResolutions, zval *closureSiteStatementIndexes, zval *settledClosureSites, zval *byRefSites) @@ -703,6 +749,15 @@ zv::Val pt_template_argument_frame_resolve(zval *frame, zval *site, zend_string return pt_type_call(Z_OBJ_P(frame), PT_LC("resolve"), 2, argv); } +zv::Val pt_template_argument_frame_resolve_sent_from_enclosing_body(zval *frame, zval *site, zend_string *templateName) +{ + if (EXPECTED(Z_OBJCE_P(frame) == pt_ce_template_argument_frame)) return TemplateArgumentFrame(Z_OBJ_P(frame)).resolveSentFromEnclosingBody(site, templateName); + zval nameZv; + ZVAL_STR(&nameZv, templateName); + zv::Args argv{site, &nameZv}; + return pt_type_call(Z_OBJ_P(frame), PT_LC("resolvesentfromenclosingbody"), 2, argv); +} + zv::Val pt_template_argument_frame_resolve_or_unconstrained(zval *frame, zval *site, zval *templateType) { if (EXPECTED(Z_OBJCE_P(frame) == pt_ce_template_argument_frame)) return TemplateArgumentFrame(Z_OBJ_P(frame)).resolveOrUnconstrained(site, templateType); @@ -748,9 +803,9 @@ PT_MINIT_REGISTRATION(pt_register_template_argument_frame) }); cls.method(sigs::__construct, [](INTERNAL_FUNCTION_PARAMETERS) { - zval *parent, *resolutions = NULL, *siteStatementIndexes = NULL, *closureSignatureBody = NULL, *closureSignatureStmts = NULL, *settledClosureSites = NULL, *byRefSites = NULL, *staticVariableTypes = NULL, *staticVariableConditionalExpressions = NULL; + zval *parent, *resolutions = NULL, *siteStatementIndexes = NULL, *closureSignatureBody = NULL, *closureSignatureStmts = NULL, *settledClosureSites = NULL, *byRefSites = NULL, *staticVariableTypes = NULL, *staticVariableConditionalExpressions = NULL, *sentResolutionKeys = NULL; bool observingClosures = false; - ZEND_PARSE_PARAMETERS_START(1, 10) + ZEND_PARSE_PARAMETERS_START(1, 11) Z_PARAM_OBJECT_OR_NULL(parent) Z_PARAM_OPTIONAL Z_PARAM_ARRAY_OR_NULL(resolutions) @@ -762,8 +817,9 @@ PT_MINIT_REGISTRATION(pt_register_template_argument_frame) Z_PARAM_ARRAY(byRefSites) Z_PARAM_ARRAY(staticVariableTypes) Z_PARAM_ARRAY(staticVariableConditionalExpressions) + Z_PARAM_ARRAY(sentResolutionKeys) ZEND_PARSE_PARAMETERS_END(); - if (UNEXPECTED(!TemplateArgumentFrame(Z_OBJ_P(ZEND_THIS)).construct(parent, resolutions, siteStatementIndexes, closureSignatureBody, closureSignatureStmts, settledClosureSites, observingClosures, byRefSites, staticVariableTypes, staticVariableConditionalExpressions))) RETURN_THROWS(); + if (UNEXPECTED(!TemplateArgumentFrame(Z_OBJ_P(ZEND_THIS)).construct(parent, resolutions, siteStatementIndexes, closureSignatureBody, closureSignatureStmts, settledClosureSites, observingClosures, byRefSites, staticVariableTypes, staticVariableConditionalExpressions, sentResolutionKeys))) RETURN_THROWS(); }); cls.method(sigs::withObservedClosures, [](INTERNAL_FUNCTION_PARAMETERS) { @@ -853,6 +909,13 @@ PT_MINIT_REGISTRATION(pt_register_template_argument_frame) PT_RETURN_VAL(TemplateArgumentFrame(Z_OBJ_P(ZEND_THIS)).resolve(site, templateName)); }); + cls.method(sigs::resolveSentFromEnclosingBody, [](INTERNAL_FUNCTION_PARAMETERS) { + zval *site; + zend_string *templateName; + if (!zp::parse(execute_data, site, templateName)) RETURN_THROWS(); + PT_RETURN_VAL(TemplateArgumentFrame(Z_OBJ_P(ZEND_THIS)).resolveSentFromEnclosingBody(site, templateName)); + }); + cls.method<&TemplateArgumentFrame::getResolutionCacheKeySuffix>(sigs::getResolutionCacheKeySuffix); cls.shadow(&pt_ce_template_argument_frame); diff --git a/turbo-ext/src/TemplateArgumentResolver.cpp b/turbo-ext/src/TemplateArgumentResolver.cpp index 2f35a62c449..f9d98f06ab0 100644 --- a/turbo-ext/src/TemplateArgumentResolver.cpp +++ b/turbo-ext/src/TemplateArgumentResolver.cpp @@ -166,7 +166,9 @@ class TemplateArgumentResolver if (UNEXPECTED(!statsEnabled(enabled))) return zv::Val(); if (enabled && UNEXPECTED(!statsIncrement(PT_LC("closureObservationPasses"), 1))) return zv::Val(); - return pt_template_argument_frame_new(parent, templateResolutionsHolder.raw(), templateSiteStatementIndexes.raw(), closureSignatureBody, closureSignatureStmts, NULL, true); + zv::Val sentResolutionKeys = collectSentResolutionKeys(state.observations.raw()); + if (UNEXPECTED(sentResolutionKeys.isUndef())) return zv::Val(); + return pt_template_argument_frame_new(parent, templateResolutionsHolder.raw(), templateSiteStatementIndexes.raw(), closureSignatureBody, closureSignatureStmts, NULL, true, NULL, sentResolutionKeys.raw()); } } @@ -174,7 +176,9 @@ class TemplateArgumentResolver zv::Val siteStatementIndexes = collectSiteStatementIndexes(state.siteIndexes.raw(), settledClosureSites.raw()); zv::Val byRefSites = collectByRefSites(state.observations.raw(), state.siteIndexes.raw()); if (UNEXPECTED(byRefSites.isUndef())) return zv::Val(); - return pt_template_argument_frame_new(parent, resolutions.raw(), siteStatementIndexes.raw(), closureSignatureBody, closureSignatureStmts, settledClosureSites.raw(), false, byRefSites.raw()); + zv::Val sentResolutionKeys = collectSentResolutionKeys(state.observations.raw()); + if (UNEXPECTED(sentResolutionKeys.isUndef())) return zv::Val(); + return pt_template_argument_frame_new(parent, resolutions.raw(), siteStatementIndexes.raw(), closureSignatureBody, closureSignatureStmts, settledClosureSites.raw(), false, byRefSites.raw(), sentResolutionKeys.raw()); } /* Mirrors resolveObservedClosures(). */ @@ -269,6 +273,33 @@ class TemplateArgumentResolver return Z_TYPE_P(resolution) == IS_NULL ? NULL : resolution; } + /* Mirrors collectSentResolutionKeys(): the keys of the observations with + * a send that are not closure signature markers */ + static zv::Val collectSentResolutionKeys(zval *observations) + { + zv::Arr keys = zv::Arr::create(0); + for (auto entry : zv::ArrRef(observations)) { + zval *observation = entry.value().deref().raw(); + zval *sends = Z_TYPE_P(observation) == IS_ARRAY ? zend_hash_find(Z_ARRVAL_P(observation), pt_tar_sends) : NULL; + if (sends != NULL) ZVAL_DEREF(sends); + if (sends == NULL || Z_TYPE_P(sends) != IS_ARRAY || zend_hash_num_elements(Z_ARRVAL_P(sends)) == 0) continue; + zval *marker = observationMarker(observation); + if (UNEXPECTED(marker == NULL)) continue; + bool closureMarker; + if (UNEXPECTED(!pt_unresolved_template_argument_type_is_closure_signature(marker, closureMarker))) return zv::Val(); + if (closureMarker) continue; + zval marked; + ZVAL_TRUE(&marked); + zend_string *key = entry.stringKeyOrNull(); + if (key != NULL) { + zend_symtable_update(keys.table(), key, &marked); + } else { + zend_hash_index_update(keys.table(), entry.indexKey(), &marked); + } + } + return zv::Val(std::move(keys)); + } + /* Mirrors settleClosureSites(). */ static zv::Val settleClosureSites(zval *observations, zval *resolutions) { diff --git a/turbo-ext/src/generated/TemplateArgumentFrame.h b/turbo-ext/src/generated/TemplateArgumentFrame.h index 36c4b178245..bbd402a55e4 100644 --- a/turbo-ext/src/generated/TemplateArgumentFrame.h +++ b/turbo-ext/src/generated/TemplateArgumentFrame.h @@ -20,6 +20,7 @@ inline constexpr uint32_t observingClosures = 6; inline constexpr uint32_t byRefSites = 7; inline constexpr uint32_t staticVariableTypes = 8; inline constexpr uint32_t staticVariableConditionalExpressions = 9; +inline constexpr uint32_t sentResolutionKeys = 10; } // namespace slot inline void declareClass(reg::Class &cls) @@ -40,6 +41,7 @@ inline void declareProperties(reg::Class &cls) cls.property("byRefSites", ZEND_ACC_PRIVATE | ZEND_ACC_READONLY, reg::PropertyKind::Typed, MAY_BE_ARRAY); cls.property("staticVariableTypes", ZEND_ACC_PRIVATE | ZEND_ACC_READONLY, reg::PropertyKind::Typed, MAY_BE_ARRAY); cls.property("staticVariableConditionalExpressions", ZEND_ACC_PRIVATE | ZEND_ACC_READONLY, reg::PropertyKind::Typed, MAY_BE_ARRAY); + cls.property("sentResolutionKeys", ZEND_ACC_PRIVATE | ZEND_ACC_READONLY, reg::PropertyKind::Typed, MAY_BE_ARRAY); } /* the string and parameter tables the signatures below index into (see reg::Sig) */ @@ -70,39 +72,41 @@ inline constexpr char strings[] = "byRefSites\0" /* 359 */ "staticVariableTypes\0" /* 370 */ "staticVariableConditionalExpressions\0" /* 390 */ - "__construct\0" /* 427 */ - "var\0" /* 439 */ - "PhpParser\\Node\\Expr\\Variable\0" /* 443 */ - "getStaticVariableTypes\0" /* 472 */ - "stmt\0" /* 495 */ - "PhpParser\\Node\\Stmt\\Static_\0" /* 500 */ - "getStaticVariableConditionalExpressions\0" /* 528 */ - "statementIndexes\0" /* 568 */ - "conditionalExpressions\0" /* 585 */ - "withStaticVariableTypes\0" /* 608 */ - "getByRefSiteMode\0" /* 632 */ - "getLocalByRefSites\0" /* 649 */ - "closureResolutions\0" /* 668 */ - "closureSiteStatementIndexes\0" /* 687 */ - "withObservedClosures\0" /* 715 */ - "isSettledClosureSite\0" /* 736 */ - "getParent\0" /* 757 */ - "getClosureSignatureBody\0" /* 767 */ - "getClosureSignatureStmts\0" /* 791 */ - "isObserving\0" /* 816 */ - "isObservingClosures\0" /* 828 */ - "firstSiteStatementIndex\0" /* 848 */ - "statementIndex\0" /* 872 */ - "ownsSiteInStatement\0" /* 887 */ - "hasSiteAtOrAfter\0" /* 907 */ - "template\0" /* 924 */ - "PHPStan\\Type\\Generic\\TemplateType\0" /* 933 */ - "resolveOrUnconstrained\0" /* 967 */ - "resolve\0" /* 990 */ - "resolveUnconstrained\0" /* 998 */ - "templateName\0" /* 1019 */ - "getResolutionCacheKeySuffix\0" /* 1032 */ - "key"; /* 1060 */ + "sentResolutionKeys\0" /* 427 */ + "__construct\0" /* 446 */ + "var\0" /* 458 */ + "PhpParser\\Node\\Expr\\Variable\0" /* 462 */ + "getStaticVariableTypes\0" /* 491 */ + "stmt\0" /* 514 */ + "PhpParser\\Node\\Stmt\\Static_\0" /* 519 */ + "getStaticVariableConditionalExpressions\0" /* 547 */ + "statementIndexes\0" /* 587 */ + "conditionalExpressions\0" /* 604 */ + "withStaticVariableTypes\0" /* 627 */ + "getByRefSiteMode\0" /* 651 */ + "getLocalByRefSites\0" /* 668 */ + "closureResolutions\0" /* 687 */ + "closureSiteStatementIndexes\0" /* 706 */ + "withObservedClosures\0" /* 734 */ + "isSettledClosureSite\0" /* 755 */ + "getParent\0" /* 776 */ + "getClosureSignatureBody\0" /* 786 */ + "getClosureSignatureStmts\0" /* 810 */ + "isObserving\0" /* 835 */ + "isObservingClosures\0" /* 847 */ + "firstSiteStatementIndex\0" /* 867 */ + "statementIndex\0" /* 891 */ + "ownsSiteInStatement\0" /* 906 */ + "hasSiteAtOrAfter\0" /* 926 */ + "template\0" /* 943 */ + "PHPStan\\Type\\Generic\\TemplateType\0" /* 952 */ + "resolveOrUnconstrained\0" /* 986 */ + "resolve\0" /* 1009 */ + "resolveUnconstrained\0" /* 1017 */ + "templateName\0" /* 1038 */ + "resolveSentFromEnclosingBody\0" /* 1051 */ + "getResolutionCacheKeySuffix\0" /* 1080 */ + "key"; /* 1108 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 9), /* returnTypeOfCall $acceptor */ reg::packed(47, 0, 53), /* returnTypeOfCall $scope */ @@ -119,19 +123,20 @@ inline constexpr reg::PackedArg args[] = { reg::packed(359, MAY_BE_ARRAY, reg::NoString, false, false, 254), /* __construct $byRefSites */ reg::packed(370, MAY_BE_ARRAY, reg::NoString, false, false, 254), /* __construct $staticVariableTypes */ reg::packed(390, MAY_BE_ARRAY, reg::NoString, false, false, 254), /* __construct $staticVariableConditionalExpressions */ - reg::packed(439, 0, 443), /* getStaticVariableTypes $var */ + reg::packed(427, MAY_BE_ARRAY, reg::NoString, false, false, 254), /* __construct $sentResolutionKeys */ + reg::packed(458, 0, 462), /* getStaticVariableTypes $var */ reg::packed(130, MAY_BE_NULL | MAY_BE_ARRAY), /* getStaticVariableTypes return */ - reg::packed(495, 0, 500), /* getStaticVariableConditionalExpressions $stmt */ + reg::packed(514, 0, 519), /* getStaticVariableConditionalExpressions $stmt */ reg::packed(130, MAY_BE_ARRAY), /* getStaticVariableConditionalExpressions return */ reg::packed(370, MAY_BE_ARRAY), /* withStaticVariableTypes $staticVariableTypes */ - reg::packed(568, MAY_BE_ARRAY), /* withStaticVariableTypes $statementIndexes */ - reg::packed(585, MAY_BE_ARRAY, reg::NoString, false, false, 254), /* withStaticVariableTypes $conditionalExpressions */ + reg::packed(587, MAY_BE_ARRAY), /* withStaticVariableTypes $statementIndexes */ + reg::packed(604, MAY_BE_ARRAY, reg::NoString, false, false, 254), /* withStaticVariableTypes $conditionalExpressions */ reg::packed(130, 0, 173), /* withStaticVariableTypes return */ reg::packed(84, 0, 89), /* getByRefSiteMode $site */ reg::packed(130, MAY_BE_NULL | MAY_BE_STRING), /* getByRefSiteMode return */ reg::packed(130, MAY_BE_ARRAY), /* getLocalByRefSites return */ - reg::packed(668, MAY_BE_ARRAY), /* withObservedClosures $closureResolutions */ - reg::packed(687, MAY_BE_ARRAY), /* withObservedClosures $closureSiteStatementIndexes */ + reg::packed(687, MAY_BE_ARRAY), /* withObservedClosures $closureResolutions */ + reg::packed(706, MAY_BE_ARRAY), /* withObservedClosures $closureSiteStatementIndexes */ reg::packed(315, MAY_BE_ARRAY), /* withObservedClosures $settledClosureSites */ reg::packed(359, MAY_BE_ARRAY, reg::NoString, false, false, 254), /* withObservedClosures $byRefSites */ reg::packed(130, 0, 173), /* withObservedClosures return */ @@ -143,23 +148,26 @@ inline constexpr reg::PackedArg args[] = { reg::packed(130, MAY_BE_BOOL), /* isObserving return */ reg::packed(130, MAY_BE_BOOL), /* isObservingClosures return */ reg::packed(130, MAY_BE_NULL | MAY_BE_LONG), /* firstSiteStatementIndex return */ - reg::packed(872, MAY_BE_LONG), /* ownsSiteInStatement $statementIndex */ + reg::packed(891, MAY_BE_LONG), /* ownsSiteInStatement $statementIndex */ reg::packed(130, MAY_BE_BOOL), /* ownsSiteInStatement return */ - reg::packed(872, MAY_BE_LONG), /* hasSiteAtOrAfter $statementIndex */ + reg::packed(891, MAY_BE_LONG), /* hasSiteAtOrAfter $statementIndex */ reg::packed(130, MAY_BE_BOOL), /* hasSiteAtOrAfter return */ reg::packed(84, 0, 89), /* resolveOrUnconstrained $site */ - reg::packed(924, 0, 933), /* resolveOrUnconstrained $template */ + reg::packed(943, 0, 952), /* resolveOrUnconstrained $template */ reg::packed(130, 0, 131), /* resolveOrUnconstrained return */ reg::packed(84, 0, 89), /* resolveUnconstrained $site */ - reg::packed(924, 0, 933), /* resolveUnconstrained $template */ - reg::packed(990, MAY_BE_CALLABLE), /* resolveUnconstrained $resolve */ + reg::packed(943, 0, 952), /* resolveUnconstrained $template */ + reg::packed(1009, MAY_BE_CALLABLE), /* resolveUnconstrained $resolve */ reg::packed(130, 0, 131), /* resolveUnconstrained return */ reg::packed(84, 0, 89), /* resolve $site */ - reg::packed(1019, MAY_BE_STRING), /* resolve $templateName */ + reg::packed(1038, MAY_BE_STRING), /* resolve $templateName */ reg::packed(130, MAY_BE_NULL, 131), /* resolve return */ + reg::packed(84, 0, 89), /* resolveSentFromEnclosingBody $site */ + reg::packed(1038, MAY_BE_STRING), /* resolveSentFromEnclosingBody $templateName */ + reg::packed(130, MAY_BE_NULL, 131), /* resolveSentFromEnclosingBody return */ reg::packed(130, MAY_BE_STRING), /* getResolutionCacheKeySuffix return */ reg::packed(84, 0, 89), /* key $site */ - reg::packed(1019, MAY_BE_STRING), /* key $templateName */ + reg::packed(1038, MAY_BE_STRING), /* key $templateName */ reg::packed(130, MAY_BE_STRING), /* key return */ }; using Sig = reg::Sig; @@ -168,27 +176,28 @@ using Sig = reg::Sig; /* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ namespace sig { inline constexpr sigtab::Sig returnTypeOfCall = { { 149 /* returnTypeOfCall */, 3, 0, 4, 4, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig __construct = { { 427 /* __construct */, 1, 5, 10, reg::NoArg, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getStaticVariableTypes = { { 472 /* getStaticVariableTypes */, 1, 15, 1, 16, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getStaticVariableConditionalExpressions = { { 528 /* getStaticVariableConditionalExpressions */, 1, 17, 1, 18, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig withStaticVariableTypes = { { 608 /* withStaticVariableTypes */, 2, 19, 3, 22, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getByRefSiteMode = { { 632 /* getByRefSiteMode */, 1, 23, 1, 24, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getLocalByRefSites = { { 649 /* getLocalByRefSites */, 0, 25, 0, 25, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig withObservedClosures = { { 715 /* withObservedClosures */, 3, 26, 4, 30, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isSettledClosureSite = { { 736 /* isSettledClosureSite */, 1, 31, 1, 32, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getParent = { { 757 /* getParent */, 0, 33, 0, 33, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getClosureSignatureBody = { { 767 /* getClosureSignatureBody */, 0, 34, 0, 34, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getClosureSignatureStmts = { { 791 /* getClosureSignatureStmts */, 0, 35, 0, 35, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isObserving = { { 816 /* isObserving */, 0, 36, 0, 36, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isObservingClosures = { { 828 /* isObservingClosures */, 0, 37, 0, 37, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig firstSiteStatementIndex = { { 848 /* firstSiteStatementIndex */, 0, 38, 0, 38, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig ownsSiteInStatement = { { 887 /* ownsSiteInStatement */, 1, 39, 1, 40, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig hasSiteAtOrAfter = { { 907 /* hasSiteAtOrAfter */, 1, 41, 1, 42, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig resolveOrUnconstrained = { { 967 /* resolveOrUnconstrained */, 2, 43, 2, 45, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig resolveUnconstrained = { { 998 /* resolveUnconstrained */, 3, 46, 3, 49, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig resolve = { { 990 /* resolve */, 2, 50, 2, 52, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getResolutionCacheKeySuffix = { { 1032 /* getResolutionCacheKeySuffix */, 0, 53, 0, 53, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig key = { { 1060 /* key */, 2, 54, 2, 56, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig __construct = { { 446 /* __construct */, 1, 5, 11, reg::NoArg, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getStaticVariableTypes = { { 491 /* getStaticVariableTypes */, 1, 16, 1, 17, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getStaticVariableConditionalExpressions = { { 547 /* getStaticVariableConditionalExpressions */, 1, 18, 1, 19, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig withStaticVariableTypes = { { 627 /* withStaticVariableTypes */, 2, 20, 3, 23, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getByRefSiteMode = { { 651 /* getByRefSiteMode */, 1, 24, 1, 25, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getLocalByRefSites = { { 668 /* getLocalByRefSites */, 0, 26, 0, 26, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig withObservedClosures = { { 734 /* withObservedClosures */, 3, 27, 4, 31, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isSettledClosureSite = { { 755 /* isSettledClosureSite */, 1, 32, 1, 33, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getParent = { { 776 /* getParent */, 0, 34, 0, 34, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getClosureSignatureBody = { { 786 /* getClosureSignatureBody */, 0, 35, 0, 35, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getClosureSignatureStmts = { { 810 /* getClosureSignatureStmts */, 0, 36, 0, 36, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isObserving = { { 835 /* isObserving */, 0, 37, 0, 37, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isObservingClosures = { { 847 /* isObservingClosures */, 0, 38, 0, 38, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig firstSiteStatementIndex = { { 867 /* firstSiteStatementIndex */, 0, 39, 0, 39, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig ownsSiteInStatement = { { 906 /* ownsSiteInStatement */, 1, 40, 1, 41, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig hasSiteAtOrAfter = { { 926 /* hasSiteAtOrAfter */, 1, 42, 1, 43, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolveOrUnconstrained = { { 986 /* resolveOrUnconstrained */, 2, 44, 2, 46, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolveUnconstrained = { { 1017 /* resolveUnconstrained */, 3, 47, 3, 50, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig resolve = { { 1009 /* resolve */, 2, 51, 2, 53, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolveSentFromEnclosingBody = { { 1051 /* resolveSentFromEnclosingBody */, 2, 54, 2, 56, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getResolutionCacheKeySuffix = { { 1080 /* getResolutionCacheKeySuffix */, 0, 57, 0, 57, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig key = { { 1108 /* key */, 2, 58, 2, 60, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; } // namespace sig } // namespace ptdecl::TemplateArgumentFrame diff --git a/turbo-ext/src/generated/TemplateArgumentResolver.h b/turbo-ext/src/generated/TemplateArgumentResolver.h index 703c7ff4b75..5e01272a8b4 100644 --- a/turbo-ext/src/generated/TemplateArgumentResolver.h +++ b/turbo-ext/src/generated/TemplateArgumentResolver.h @@ -44,12 +44,13 @@ inline constexpr char strings[] = "settleClosureSites\0" /* 328 */ "siteIndexes\0" /* 347 */ "collectByRefSites\0" /* 359 */ - "hasChangedTemplateArgument\0" /* 377 */ - "settledClosureSites\0" /* 404 */ - "collectSiteStatementIndexes\0" /* 424 */ - "tokenPosition\0" /* 452 */ - "positions\0" /* 466 */ - "locateStatement"; /* 476 */ + "collectSentResolutionKeys\0" /* 377 */ + "hasChangedTemplateArgument\0" /* 403 */ + "settledClosureSites\0" /* 430 */ + "collectSiteStatementIndexes\0" /* 450 */ + "tokenPosition\0" /* 478 */ + "positions\0" /* 492 */ + "locateStatement"; /* 502 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 12), /* resolve $constraints */ reg::packed(66, MAY_BE_NULL, 73), /* resolve $parent */ @@ -72,14 +73,16 @@ inline constexpr reg::PackedArg args[] = { reg::packed(303, MAY_BE_ARRAY), /* collectByRefSites $observations */ reg::packed(347, MAY_BE_ARRAY), /* collectByRefSites $siteIndexes */ reg::packed(216, MAY_BE_ARRAY), /* collectByRefSites return */ + reg::packed(303, MAY_BE_ARRAY), /* collectSentResolutionKeys $observations */ + reg::packed(216, MAY_BE_ARRAY), /* collectSentResolutionKeys return */ reg::packed(303, MAY_BE_ARRAY), /* hasChangedTemplateArgument $observations */ reg::packed(316, MAY_BE_ARRAY), /* hasChangedTemplateArgument $resolutions */ reg::packed(216, MAY_BE_BOOL), /* hasChangedTemplateArgument return */ reg::packed(347, MAY_BE_ARRAY), /* collectSiteStatementIndexes $siteIndexes */ - reg::packed(404, MAY_BE_ARRAY), /* collectSiteStatementIndexes $settledClosureSites */ + reg::packed(430, MAY_BE_ARRAY), /* collectSiteStatementIndexes $settledClosureSites */ reg::packed(216, MAY_BE_ARRAY), /* collectSiteStatementIndexes return */ - reg::packed(452, MAY_BE_LONG), /* locateStatement $tokenPosition */ - reg::packed(466, MAY_BE_ARRAY), /* locateStatement $positions */ + reg::packed(478, MAY_BE_LONG), /* locateStatement $tokenPosition */ + reg::packed(492, MAY_BE_ARRAY), /* locateStatement $positions */ reg::packed(216, MAY_BE_LONG), /* locateStatement return */ }; using Sig = reg::Sig; @@ -92,9 +95,10 @@ inline constexpr sigtab::Sig resolveObservedClosures = { { 231 /* resolveObserve inline constexpr sigtab::Sig collectObservations = { { 283 /* collectObservations */, 4, 10, 4, 14, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig settleClosureSites = { { 328 /* settleClosureSites */, 2, 15, 2, 17, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig collectByRefSites = { { 359 /* collectByRefSites */, 2, 18, 2, 20, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig hasChangedTemplateArgument = { { 377 /* hasChangedTemplateArgument */, 2, 21, 2, 23, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig collectSiteStatementIndexes = { { 424 /* collectSiteStatementIndexes */, 2, 24, 2, 26, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig locateStatement = { { 476 /* locateStatement */, 2, 27, 2, 29, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectSentResolutionKeys = { { 377 /* collectSentResolutionKeys */, 1, 21, 1, 22, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig hasChangedTemplateArgument = { { 403 /* hasChangedTemplateArgument */, 2, 23, 2, 25, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig collectSiteStatementIndexes = { { 450 /* collectSiteStatementIndexes */, 2, 26, 2, 28, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig locateStatement = { { 502 /* locateStatement */, 2, 29, 2, 31, ZEND_ACC_PRIVATE } }; } // namespace sig } // namespace ptdecl::TemplateArgumentResolver diff --git a/turbo-ext/src/support.h b/turbo-ext/src/support.h index 043560faaa0..cb2391af194 100644 --- a/turbo-ext/src/support.h +++ b/turbo-ext/src/support.h @@ -2106,7 +2106,7 @@ zv::Val pt_template_argument_frame_return_type_of_call(zval *acceptor, zval *sco /* new TemplateArgumentFrame($parent, $resolutions, $siteStatementIndexes) * ($parent / $resolutions NULL or IS_NULL for null, $siteStatementIndexes * NULL for []) */ -zv::Val pt_template_argument_frame_new(zval *parent, zval *resolutions = NULL, zval *siteStatementIndexes = NULL, zval *closureSignatureBody = NULL, zval *closureSignatureStmts = NULL, zval *settledClosureSites = NULL, bool observingClosures = false, zval *byRefSites = NULL); +zv::Val pt_template_argument_frame_new(zval *parent, zval *resolutions = NULL, zval *siteStatementIndexes = NULL, zval *closureSignatureBody = NULL, zval *closureSignatureStmts = NULL, zval *settledClosureSites = NULL, bool observingClosures = false, zval *byRefSites = NULL, zval *sentResolutionKeys = NULL); /* $frame->withObservedClosures($closureResolutions, $closureSiteStatementIndexes, $settledClosureSites); UNDEF = pending exception */ zv::Val pt_template_argument_frame_with_observed_closures(zval *frame, zval *closureResolutions, zval *closureSiteStatementIndexes, zval *settledClosureSites, zval *byRefSites = NULL); /* TemplateArgumentFrame::getByRefSiteMode(): 'local' / 'escaped' / PHP null; UNDEF = pending exception */ @@ -2138,9 +2138,11 @@ zv::Val pt_template_argument_frame_get_parent(zval *frame); zv::Val pt_template_argument_frame_get_closure_signature_body(zval *frame); zv::Val pt_template_argument_frame_get_closure_signature_stmts(zval *frame); /* $frame->resolve($site, $templateName) (the type or null) / + * ->resolveSentFromEnclosingBody($site, $templateName) (the type or null) / * ->resolveOrUnconstrained($site, $template) / * ->getResolutionCacheKeySuffix() */ zv::Val pt_template_argument_frame_resolve(zval *frame, zval *site, zend_string *templateName); +zv::Val pt_template_argument_frame_resolve_sent_from_enclosing_body(zval *frame, zval *site, zend_string *templateName); zv::Val pt_template_argument_frame_resolve_or_unconstrained(zval *frame, zval *site, zval *templateType); zv::Val pt_template_argument_frame_resolution_cache_key_suffix(zval *frame);