Repository navigation
Track phpseclib 4.0.3's curTimeout type and raise the floor to match - #77
imanimanyara wants to merge 2 commits into
Conversation
Follow-up: PHPStan now passes; Rector was wrong and is exempted for one fileWith the property fixed, PHPStan is green. Rector then asked for a change that breaks the test: -$timeoutAfterClose = Closure::bind(function (bool $polling): int|float|null {
+$timeoutAfterClose = Closure::bind(function (bool $polling): int {
expect($timeoutAfterClose(true))->toBe(0.005)
->and($timeoutAfterClose(false))->toBe(5);Applying Rector's suggestion verbatim, verified on this machine 2026-11-13: So the honest type is This is the second time in this change that a tool's static view disagreed with the runtime, and in both cases the runtime was right: PHPStan's intl-style stub narrowing in |
Summary
phpseclib changed a typed property in a patch release, and PHP requires an overriding property to repeat its parent's type exactly.
ForwardingSsh2therefore fatals against 4.0.3.SSH2::$curTimeoutprotected int|float|null $curTimeout = null;protected int|float|false $curTimeout = false;This is not a lint finding. It is a class that will not load.
There is no version-agnostic declaration, which is why the floor moves
Verified in both directions on this machine:
int|float|nulldeclaration fatals on 4.0.3 (the error above);int|float|falsedeclaration fatals on 4.0.2 — "Type of parameter $value of hook … must be compatible with property type".One class cannot satisfy both parents, so
phpseclib/phpseclibgoes^4.0.1→^4.0.3inrequire-devand in thesuggeststring. Thesuggestline matters as much as the constraint: it is the instruction a consumer follows when installing the tunnel driver, and left at^4.0.1it would keep sending them to a resolution that fatals.The hook's semantics move with it
falseis phpseclib's "no timeout", asnullwas before 4.0.3, so the guard that excluded it from the numeric comparison moves with it:The intent is unchanged — a sentinel is not a duration, so comparing it against
POLL_TIMEOUTis meaningless either way. The three assignments inside the class passint|floatand are unaffected.How this surfaced
Not from a commit. This repository tracks no lockfile, so CI resolves fresh every run and picked up 4.0.3 the moment it published. The local
vendor/was still on 4.0.2, which is why it passed locally and failed in CI — a plaincomposer installdoes not upgrade a package that already satisfies the range.Verified
laranail-pint --testpasses.Not in this PR
Two further failures on db-console#76 are unrelated to this and are being looked at separately:
pest · prefer-stablelogging "GitHub refused the token on all 3 attempts", andlive SSH tunnelsfailing to install the PostgreSQL 18 client tools with dpkg error 100.