Skip to content

feat: add PHPStan dynamic throw type extensions#112

Open
simPod wants to merge 1 commit intobrick:masterfrom
simPod:phpstan-throw-extensions
Open

feat: add PHPStan dynamic throw type extensions#112
simPod wants to merge 1 commit intobrick:masterfrom
simPod:phpstan-throw-extensions

Conversation

@simPod
Copy link
Contributor

@simPod simPod commented Mar 7, 2026

Add four PHPStan extensions that narrow throw types based on argument
types at call sites. Consumers using phpstan/extension-installer get
automatic discovery; otherwise include phpstan/extension.neon.

BigNumberOfThrowTypeExtension (static factory methods):

  • of(), ofNullable() on all BigNumber classes → no throw when arg is int or already the target type
  • of(), ofNullable() with int|numeric-string args → removes DivisionByZeroException (no / in input)
  • min(), max(), sum() on BigNumber → no throw when all args are int|BigNumber
  • gcdAll(), lcmAll() on BigInteger → no throw when all args are int|BigInteger
  • ofUnscaledValue() on BigDecimal → no throw when arg is int|BigInteger|BigDecimal
  • ofFraction() on BigRational → narrows to DivisionByZeroException when both args are int|BigInteger; removes DivisionByZeroException when denominator is non-zero
  • randomRange() on BigInteger → narrows to InvalidArgumentException|RandomSourceException when both args are BigInteger

BigNumberConversionThrowTypeExtension (type conversion methods):

  • toBigInteger() → no throw when caller is BigInteger
  • toBigDecimal() → no throw when caller is BigDecimal
  • toBigRational() → no throw when caller is BigRational
  • toInt() on BigInteger → narrows to IntegerOverflowException only (removes RoundingNecessaryException)

BigNumberOperationThrowTypeExtension (arithmetic & comparison methods):

  • No-throw when arg is int or correct type: plus, minus, multipliedBy, gcd, lcm, and, or, xor (BigInteger); plus, minus, multipliedBy (BigDecimal/BigRational); compareTo, isEqualTo, isLessThan, isLessThanOrEqualTo, isGreaterThan, isGreaterThanOrEqualTo (BigNumber)
  • Residual exceptions when arg is int or correct type: quotient/remainder/quotientAndRemainderDivisionByZeroException; modDivisionByZeroException|InvalidArgumentException; modInverse → +NoInverseException; modPow (checks both args); dividedByExactDivisionByZeroException|RoundingNecessaryException; dividedBy (BigRational) → DivisionByZeroException; clamp (checks both min/max args) → InvalidArgumentException
  • DivisionByZeroException removed when divisor is guaranteed non-zero (e.g. literal int, positive-int range)

RoundingModeThrowTypeExtension (rounding-mode-dependent methods):

  • toScale() on all BigNumber subclasses → independently narrows on both axes: non-negative scale removes InvalidArgumentException, non-Unnecessary rounding mode removes RoundingNecessaryException
  • dividedBy() on BigInteger/BigDecimal → independently narrows on three axes: int/correct-type divisor removes MathException, non-Unnecessary rounding mode removes RoundingNecessaryException, non-zero divisor removes DivisionByZeroException
  • sqrt() on BigInteger/BigDecimal → narrows to NegativeNumberException

@simPod
Copy link
Contributor Author

simPod commented Mar 7, 2026

This compensates for #107

@codecov
Copy link

codecov bot commented Mar 7, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.08%. Comparing base (f67c7fe) to head (e64b3a5).

Additional details and impacted files
@@            Coverage Diff            @@
##             master     #112   +/-   ##
=========================================
  Coverage     99.08%   99.08%           
  Complexity      663      663           
=========================================
  Files            18       18           
  Lines          1643     1643           
=========================================
  Hits           1628     1628           
  Misses           15       15           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@simPod simPod force-pushed the phpstan-throw-extensions branch 4 times, most recently from 824a7af to 273a199 Compare March 7, 2026 09:14
@simPod simPod marked this pull request as ready for review March 7, 2026 09:41
@simPod simPod force-pushed the phpstan-throw-extensions branch 10 times, most recently from 1651990 to 2718f1a Compare March 7, 2026 10:56
Add four PHPStan extensions that narrow throw types based on argument
types at call sites. Consumers using `phpstan/extension-installer` get
automatic discovery; otherwise include `phpstan/extension.neon`.

`BigNumberOfThrowTypeExtension` (static factory methods):
- `of()`, `ofNullable()` on all `BigNumber` classes → no throw when arg is `int` or already the target type
- `of()`, `ofNullable()` with `int|numeric-string` args → removes `DivisionByZeroException` (no `/` in input)
- `min()`, `max()`, `sum()` on `BigNumber` → no throw when all args are `int|BigNumber`
- `gcdAll()`, `lcmAll()` on `BigInteger` → no throw when all args are `int|BigInteger`
- `ofUnscaledValue()` on `BigDecimal` → no throw when arg is `int|BigInteger|BigDecimal`
- `ofFraction()` on `BigRational` → narrows to `DivisionByZeroException` when both args are `int|BigInteger`; removes `DivisionByZeroException` when denominator is non-zero
- `randomRange()` on `BigInteger` → narrows to `InvalidArgumentException`|`RandomSourceException` when both args are `BigInteger`

`BigNumberConversionThrowTypeExtension` (type conversion methods):
- `toBigInteger()` → no throw when caller is `BigInteger`
- `toBigDecimal()` → no throw when caller is `BigDecimal`
- `toBigRational()` → no throw when caller is `BigRational`
- `toInt()` on `BigInteger` → narrows to `IntegerOverflowException` only (removes `RoundingNecessaryException`)

`BigNumberOperationThrowTypeExtension` (arithmetic & comparison methods):
- No-throw when arg is `int` or correct type: `plus`, `minus`, `multipliedBy`, `gcd`, `lcm`, `and`, `or`, `xor` (`BigInteger`); `plus`, `minus`, `multipliedBy` (`BigDecimal`/`BigRational`); `compareTo`, `isEqualTo`, `isLessThan`, `isLessThanOrEqualTo`, `isGreaterThan`, `isGreaterThanOrEqualTo` (`BigNumber`)
- Residual exceptions when arg is `int` or correct type: `quotient`/`remainder`/`quotientAndRemainder` → `DivisionByZeroException`; `mod` → `DivisionByZeroException`|`InvalidArgumentException`; `modInverse` → +`NoInverseException`; `modPow` (checks both args); `dividedByExact` → `DivisionByZeroException`|`RoundingNecessaryException`; `dividedBy` (`BigRational`) → `DivisionByZeroException`; `clamp` (checks both min/max args) → `InvalidArgumentException`
- `DivisionByZeroException` removed when divisor is guaranteed non-zero (e.g. literal int, positive-int range)

`RoundingModeThrowTypeExtension` (rounding-mode-dependent methods):
- `toScale()` on all `BigNumber` subclasses → independently narrows on both axes: non-negative scale removes `InvalidArgumentException`, non-`Unnecessary` rounding mode removes `RoundingNecessaryException`
- `dividedBy()` on `BigInteger`/`BigDecimal` → independently narrows on three axes: `int`/correct-type divisor removes `MathException`, non-`Unnecessary` rounding mode removes `RoundingNecessaryException`, non-zero divisor removes `DivisionByZeroException`
- `sqrt()` on `BigInteger`/`BigDecimal` → narrows to `NegativeNumberException`
@simPod simPod force-pushed the phpstan-throw-extensions branch from 2718f1a to e64b3a5 Compare March 7, 2026 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant