Add UnaryOperatorTypeSpecifyingExtension for BcMath\Number so unary +/- infer BcMath\Number - #5994
Conversation
| return false; | ||
| } | ||
|
|
||
| $bcMathNumberType = new ObjectType('BcMath\Number'); |
There was a problem hiding this comment.
might make sense to put the object type on a property of the extension, so we don't need to create these objects over and over (and the ObjectType internals also has some per instance caching, which might benefit from it).
I have no proof though whether it would have a perf impact.
There was a problem hiding this comment.
Done. I addressed staabm's review by caching the BcMath\Number ObjectType on a private property so it's constructed once per extension instance instead of being recreated on every isOperatorSupported() / specifyType() call.
Changes in src/Type/Php/BcMathNumberUnaryOperatorTypeSpecifyingExtension.php:
- Added
private ObjectType $bcMathNumberType;initialized in the constructor. isOperatorSupported()andspecifyType()now reuse$this->bcMathNumberType.
Verification:
InvalidUnaryOperationRuleTest— 4 tests pass.NodeScopeResolverTest --filter bcmath— 2 tests pass.make phpstan— no errors.
Committed and pushed to create-pull-request/patch-het2rx0 (e8784877f).
|
|
||
| public function __construct(private PhpVersion $phpVersion) | ||
| { | ||
| $this->bcMathNumberType = new ObjectType('BcMath\Number'); |
There was a problem hiding this comment.
Lazy init the object so we don't eagerly trigger reflection
There was a problem hiding this comment.
Done. I addressed staabm's review by lazily initializing the BcMath\Number ObjectType instead of eagerly constructing it in the constructor.
Changes
src/Type/Php/BcMathNumberUnaryOperatorTypeSpecifyingExtension.php— Changed$bcMathNumberTypeto a nullable property (?ObjectType = null), removed the constructor assignment, and added a privategetBcMathNumberType()helper that lazily creates the type on first use (??=).isOperatorSupported()andspecifyType()now call the helper. This keeps the per-instance caching benefit while avoiding eager reflection when the extension is constructed but never exercised (e.g. on older PHP versions wheresupportsBcMathNumberOperatorOverloading()short-circuits first).
Verification
InvalidUnaryOperationRuleTest— 4 tests pass.NodeScopeResolverTest --filter bcmath— 2 tests pass.make phpstan— no errors.
Committed and pushed to create-pull-request/patch-het2rx0.
…ry `+`/`-` infer `BcMath\Number` - Add `BcMathNumberUnaryOperatorTypeSpecifyingExtension` mirroring the existing `GmpUnaryOperatorTypeSpecifyingExtension`, gated on `PhpVersion::supportsBcMathNumberOperatorOverloading()`. - The extension handles unary `-` and `+` (both return `BcMath\Number`); `~` is intentionally left unsupported because `BcMath\Number` does not overload bitwise not, so `~$x` still resolves to `*ERROR*` and is reported by `InvalidUnaryOperationRule`. - Not touching `ObjectType::toNumber()`/`toInt()`/`toFloat()` on purpose: those feed `(int)`/`(float)` casts which are invalid on `BcMath\Number` (they emit a runtime warning), so the operator-extension route is the correct fix. - Probed the parallel construct on the same axis: `GmpUnaryOperatorTypeSpecifyingExtension` already covers GMP; these two are the only operator-overloading classes with a binary `OperatorTypeSpecifyingExtension`, so the unary family is now complete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4c3e1e1 to
5ab8c40
Compare
This reverts commit 5ab8c40.
|
I don't think it's worth caching the objectType @staabm But if we do it should be consistent with BcMathNumberOperatorTypeSpecifyingExtension, GmpOperatorTypeSpecifyingExtension and GmpUnaryOperatorTypeSpecifyingExtension no ? |
Summary
Unary operations on
BcMath\Numberwere incorrectly flagged.$x++/$x--were alreadyfixed earlier, but unary
-$xand+$xstill resolved to*ERROR*and producedUnary operation "-"/"+" on BcMath\Number results in an error., even though PHP 8.4'sBcMath\Numberoverloads these operators and returns aBcMath\Number.This PR adds a
UnaryOperatorTypeSpecifyingExtensionforBcMath\Numberso that-$xand
+$xinferBcMath\Numberand no longer produce a false positive.Changes
src/Type/Php/BcMathNumberUnaryOperatorTypeSpecifyingExtension.php, an autowiredUnaryOperatorTypeSpecifyingExtensiongated onPhpVersion::supportsBcMathNumberOperatorOverloading(). It supports unary-and+on
BcMath\Numberoperands, returningBcMath\Number.~is deliberately not handled(see Root cause).
-$a,+$a,~$aintests/PHPStan/Analyser/nsrt/bcmath-number.php.tests/PHPStan/Rules/Operators/data/unary-bcmath-number.phpand atestBcMathNumbercase inInvalidUnaryOperationRuleTestasserting that-/+areaccepted and only
~reportsunaryOp.invalid.Root cause
Unary
+/-/~type resolution inInitializerExprTypeResolver::getUnaryPlusType(),getUnaryMinusType()andgetBitwiseNotType()first consults theUnaryOperatorTypeSpecifyingExtensionregistry and only then falls back toType::toNumber()/Type::toBitwiseNotType().ObjectType::toNumber()returnsErrorTypeforBcMath\Number— correctly, because a(int)/(float)cast of aBcMath\Numberis a runtime error — so without a dedicated unary extension the fallbackproduced
*ERROR*.GMP already had
GmpUnaryOperatorTypeSpecifyingExtension;BcMath\Numberhad a binaryBcMathNumberOperatorTypeSpecifyingExtensionbut no unary counterpart. The fix mirrorsthe GMP extension. Unlike GMP,
BcMath\Numberdoes not overload bitwise not, so~isleft unsupported and continues to be reported as an error.
I probed the parallel construct on this axis: the only two operator-overloading classes
with a binary
OperatorTypeSpecifyingExtensionare GMP andBcMath\Number; GMP's unaryextension already existed, so with this change the unary family is complete. I also
considered fixing
ObjectType::toNumber()/toInt()/toFloat()instead (as suggested inthe issue), but rejected it because those also drive
(int)/(float)casts, which areinvalid on
BcMath\Number.Test
tests/PHPStan/Analyser/nsrt/bcmath-number.phpnow asserts-$aand+$aareBcMath\Numberand~$ais*ERROR*.InvalidUnaryOperationRuleTest::testBcMathNumberasserts no error for-/+and asingle
Unary operation "~" on BcMath\Number results in an error.for~.Both tests fail without the new extension and pass with it.
Fixes phpstan/phpstan#13965