From 4a407847481caa67ae25e4d09718995d4a01e7ab Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Sat, 17 Sep 2022 17:24:32 +0200 Subject: [PATCH] Consistently use floating point numbers This is only applied to the new codebase, not to the old one, as it is scheduled for removal. --- src/Ast/Sass/Expression/NumberExpression.php | 15 +- src/Parser/Parser.php | 16 +- src/Parser/StylesheetParser.php | 69 ++++----- src/Serializer/SerializeVisitor.php | 6 +- src/Util/NumberUtil.php | 140 ++++++------------ src/Value/ComplexSassNumber.php | 6 +- src/Value/SassColor.php | 127 +++++++--------- src/Value/SassNumber.php | 117 +++++++-------- src/Value/SingleUnitSassNumber.php | 30 ++-- src/Value/UnitlessSassNumber.php | 14 +- tests/Value/SassColor/SassColorTest.php | 3 +- .../Value/SassNumber/UnitlessIntegerTest.php | 2 +- 12 files changed, 223 insertions(+), 322 deletions(-) diff --git a/src/Ast/Sass/Expression/NumberExpression.php b/src/Ast/Sass/Expression/NumberExpression.php index 9a11cfc7..e0e939c0 100644 --- a/src/Ast/Sass/Expression/NumberExpression.php +++ b/src/Ast/Sass/Expression/NumberExpression.php @@ -14,6 +14,7 @@ use ScssPhp\ScssPhp\Ast\Sass\Expression; use ScssPhp\ScssPhp\SourceSpan\FileSpan; +use ScssPhp\ScssPhp\Value\SassNumber; use ScssPhp\ScssPhp\Visitor\ExpressionVisitor; /** @@ -24,7 +25,7 @@ final class NumberExpression implements Expression { /** - * @var float|int + * @var float * @readonly */ private $value; @@ -41,20 +42,14 @@ final class NumberExpression implements Expression */ private $unit; - /** - * @param int|float $value - */ - public function __construct($value, FileSpan $span, ?string $unit = null) + public function __construct(float $value, FileSpan $span, ?string $unit = null) { $this->value = $value; $this->span = $span; $this->unit = $unit; } - /** - * @return float|int - */ - public function getValue() + public function getValue(): float { return $this->value; } @@ -76,6 +71,6 @@ public function accept(ExpressionVisitor $visitor) public function __toString(): string { - return $this->value . ($this->unit ?? ''); + return (string) SassNumber::create($this->value, $this->unit); } } diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php index e5a0ea99..10f42178 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/Parser.php @@ -337,11 +337,11 @@ protected function string(): string } /** - * Consumes and returns a natural number (that is, a non-negative integer). + * Consumes and returns a natural number (that is, a non-negative integer) as a double. * * Doesn't support scientific notation. */ - protected function naturalNumber(): int + protected function naturalNumber(): float { $first = $this->scanner->readChar(); @@ -349,16 +349,14 @@ protected function naturalNumber(): int $this->scanner->error('Expected digit.', $this->scanner->getPosition() - 1); } - $number = $first; + $number = (float) intval($first); - $next = $this->scanner->peekChar(); - - while ($next !== null && Character::isDigit($next)) { - $number .= $this->scanner->readChar(); - $next = $this->scanner->peekChar(); + while (Character::isDigit($this->scanner->peekChar())) { + $number *= 10; + $number += intval($this->scanner->readChar()); } - return intval($number); + return $number; } /** diff --git a/src/Parser/StylesheetParser.php b/src/Parser/StylesheetParser.php index 2b874514..198847f4 100644 --- a/src/Parser/StylesheetParser.php +++ b/src/Parser/StylesheetParser.php @@ -2754,19 +2754,24 @@ private function number(): NumberExpression { $start = $this->scanner->getPosition(); $first = $this->scanner->peekChar(); - $sign = $first === '-' ? -1 : 1; if ($first === '+' || $first === '-') { $this->scanner->readChar(); } - $number = $this->scanner->peekChar() === '.' ? 0 : $this->naturalNumber(); + if ($this->scanner->peekChar() !== '.') { + $this->consumeNaturalNumber(); + } // Don't complain about a dot after a number unless the number starts with a // dot. We don't allow a plain ".", but we need to allow "1." so that // "1..." will work as a rest argument. - $number += $this->tryDecimal($this->scanner->getPosition() !== $start); - $number *= $this->tryExponent(); + $this->tryDecimal($this->scanner->getPosition() !== $start); + $this->tryExponent(); + + // Use PHP's built-in double parsing so that we don't accumulate + // floating-point errors for numbers with lots of digits. + $number = floatval($this->scanner->substring($start)); $unit = null; if ($this->scanner->scanChar('%')) { @@ -2775,32 +2780,41 @@ private function number(): NumberExpression $unit = $this->identifier(false, true); } - return new NumberExpression($sign * $number, $this->scanner->spanFrom($start), $unit); + return new NumberExpression($number, $this->scanner->spanFrom($start), $unit); + } + + /** + * Consumes a natural number (that is, a non-negative integer). + * + * Doesn't support scientific notation. + */ + private function consumeNaturalNumber(): void + { + if (!Character::isDigit($this->scanner->readChar())) { + $this->scanner->error('Expected digit.', $this->scanner->getPosition() - 1); + } + + while (Character::isDigit($this->scanner->peekChar())) { + $this->scanner->readChar(); + } } /** - * Consumes the decimal component of a number and returns its value, or 0 if - * there is no decimal component. + * Consumes the decimal component of a number if it exists. * * If $allowTrailingDot is `false`, this will throw an error if there's a * dot without any numbers following it. Otherwise, it will ignore the dot * without consuming it. - * - * @param bool $allowTrailingDot - * - * @return int|float */ - private function tryDecimal(bool $allowTrailingDot = false) + private function tryDecimal(bool $allowTrailingDot = false): void { - $start = $this->scanner->getPosition(); - if ($this->scanner->peekChar() !== '.') { - return 0; + return; } if (!Character::isDigit($this->scanner->peekChar(1))) { if ($allowTrailingDot) { - return 0; + return; } $this->scanner->error('Expected digit.', $this->scanner->getPosition() + 1); @@ -2810,34 +2824,26 @@ private function tryDecimal(bool $allowTrailingDot = false) while (Character::isDigit($this->scanner->peekChar())) { $this->scanner->readChar(); } - - // Use PHP's built-in float parsing so that we don't accumulate - // floating-point errors for numbers with lots of digits. - return floatval($this->scanner->substring($start)); } /** - * Consumes the exponent component of a number and returns its value, or 1 if - * there is no exponent component. - * - * @return int|float + * Consumes the exponent component of a number if it exists. */ - private function tryExponent() + private function tryExponent(): void { $first = $this->scanner->peekChar(); if ($first !== 'e' && $first !== 'E') { - return 1; + return; } $next = $this->scanner->peekChar(1); if (!Character::isDigit($next) && $next !== '-' && $next !== '+') { - return 1; + return; } $this->scanner->readChar(); - $exponentSign = $next === '-' ? -1 : 1; if ($next === '+' || $next === '-') { $this->scanner->readChar(); } @@ -2846,14 +2852,9 @@ private function tryExponent() $this->scanner->error('Expected digit.'); } - $exponent = 0.0; - while (Character::isDigit($this->scanner->peekChar())) { - $exponent *= 10; - $exponent += \ord($this->scanner->readChar()) - \ord('0'); + $this->scanner->readChar(); } - - return pow(10, $exponentSign * $exponent); } /** diff --git a/src/Serializer/SerializeVisitor.php b/src/Serializer/SerializeVisitor.php index c1817800..8365ea7a 100644 --- a/src/Serializer/SerializeVisitor.php +++ b/src/Serializer/SerializeVisitor.php @@ -13,13 +13,11 @@ namespace ScssPhp\ScssPhp\Serializer; use ScssPhp\ScssPhp\Ast\AstNode; -use ScssPhp\ScssPhp\Ast\Css\CssAtRule; use ScssPhp\ScssPhp\Ast\Css\CssComment; use ScssPhp\ScssPhp\Ast\Css\CssDeclaration; use ScssPhp\ScssPhp\Ast\Css\CssMediaQuery; use ScssPhp\ScssPhp\Ast\Css\CssNode; use ScssPhp\ScssPhp\Ast\Css\CssParentNode; -use ScssPhp\ScssPhp\Ast\Css\CssStyleRule; use ScssPhp\ScssPhp\Ast\Css\CssValue; use ScssPhp\ScssPhp\Ast\Selector\AttributeSelector; use ScssPhp\ScssPhp\Ast\Selector\ClassSelector; @@ -934,9 +932,9 @@ public function visitNumber(SassNumber $value) * Writes $number without exponent notation and with at most * {@see SassNumber::PRECISION} digits after the decimal point. * - * @param int|float $number + * @param float $number */ - private function writeNumber($number): void + private function writeNumber(float $number): void { if (is_nan($number)) { $this->buffer->write('NaN'); diff --git a/src/Util/NumberUtil.php b/src/Util/NumberUtil.php index ce48a61a..499d6426 100644 --- a/src/Util/NumberUtil.php +++ b/src/Util/NumberUtil.php @@ -12,6 +12,8 @@ namespace ScssPhp\ScssPhp\Util; +use ScssPhp\ScssPhp\Value\SassNumber; + /** * Utilities to deal with numbers with fuzziness for the Sass precision * @@ -19,108 +21,70 @@ */ final class NumberUtil { - public const EPSILON = 0.00000000001; // 10^(-PRECISION-1) - /** - * @param int|float $number1 - * @param int|float $number2 + * The power of ten to which to round Sass numbers to determine if they're + * fuzzy equal to one another + * + * This is also the minimum distance such that `a - b > EPSILON` implies that + * `a` isn't fuzzy-equal to `b`. Note that the inverse implication is not + * necessarily true! For example, if `a = 5.1e-11` and `b = 4.4e-11`, then + * `a - b < 1e-11` but `a` fuzzy-equals 5e-11 and b fuzzy-equals 4e-11. * - * @return bool + * @see https://github.com/sass/sass/blob/main/spec/types/number.md#fuzzy-equality */ - public static function fuzzyEquals($number1, $number2): bool + private const EPSILON = 10 ** (-SassNumber::PRECISION - 1); + private const INVERSE_EPSILON = 10 ** (SassNumber::PRECISION + 1); + + public static function fuzzyEquals(float $number1, float $number2): bool { - return abs($number1 - $number2) < self::EPSILON; + if ($number1 == $number2) { + return true; + } + return abs($number1 - $number2) <= self::EPSILON && round($number1 * self::INVERSE_EPSILON) === round($number2 * self::INVERSE_EPSILON); } - /** - * @param int|float $number1 - * @param int|float $number2 - * - * @return bool - */ - public static function fuzzyLessThan($number1, $number2): bool + public static function fuzzyLessThan(float $number1, float $number2): bool { return $number1 < $number2 && !self::fuzzyEquals($number1, $number2); } - /** - * @param int|float $number1 - * @param int|float $number2 - * - * @return bool - */ - public static function fuzzyLessThanOrEquals($number1, $number2): bool + public static function fuzzyLessThanOrEquals(float $number1, float $number2): bool { return $number1 <= $number2 || self::fuzzyEquals($number1, $number2); } - /** - * @param int|float $number1 - * @param int|float $number2 - * - * @return bool - */ - public static function fuzzyGreaterThan($number1, $number2): bool + public static function fuzzyGreaterThan(float $number1, float $number2): bool { return $number1 > $number2 && !self::fuzzyEquals($number1, $number2); } - /** - * @param int|float $number1 - * @param int|float $number2 - * - * @return bool - */ - public static function fuzzyGreaterThanOrEquals($number1, $number2): bool + public static function fuzzyGreaterThanOrEquals(float $number1, float $number2): bool { return $number1 >= $number2 || self::fuzzyEquals($number1, $number2); } - /** - * @param int|float $number - * - * @return bool - */ - public static function fuzzyIsInt($number): bool + public static function fuzzyIsInt(float $number): bool { - if (\is_int($number)) { - return true; + if (is_infinite($number) || is_nan($number)) { + return false; } - // Check against 0.5 rather than 0.0 so that we catch numbers that are both - // very slightly above an integer, and very slightly below. - return self::fuzzyEquals(fmod(abs($number - 0.5), 1), 0.5); + return self::fuzzyEquals($number, round($number)); } - /** - * @param int|float $number - * - * @return int|null - */ - public static function fuzzyAsInt($number): ?int + public static function fuzzyAsInt(float $number): ?int { - if (\is_int($number)) { - return $number; + if (is_infinite($number) || is_nan($number)) { + return null; } - if (self::fuzzyIsInt($number)) { - return (int) round($number); - } + $rounded = (int) round($number); - return null; + return self::fuzzyEquals($number, $rounded) ? $rounded : null; } - /** - * @param int|float $number - * - * @return int - */ - public static function fuzzyRound($number): int + public static function fuzzyRound(float $number): int { - if (\is_int($number)) { - return $number; - } - if ($number > 0) { return intval(self::fuzzyLessThan(fmod($number, 1), 0.5) ? floor($number) : ceil($number)); } @@ -128,14 +92,7 @@ public static function fuzzyRound($number): int return intval(self::fuzzyLessThanOrEquals(fmod($number, 1), 0.5) ? floor($number) : ceil($number)); } - /** - * @param int|float $number - * @param int|float $min - * @param int|float $max - * - * @return int|float|null - */ - public static function fuzzyCheckRange($number, $min, $max) + public static function fuzzyCheckRange(float $number, float $min, float $max): ?float { if (self::fuzzyEquals($number, $min)) { return $min; @@ -153,16 +110,16 @@ public static function fuzzyCheckRange($number, $min, $max) } /** - * @param int|float $number - * @param int|float $min - * @param int|float $max + * @param float $number + * @param float $min + * @param float $max * @param string|null $name * - * @return int|float + * @return float * * @throws \OutOfRangeException */ - public static function fuzzyAssertRange($number, $min, $max, ?string $name = null) + public static function fuzzyAssertRange(float $number, float $min, float $max, ?string $name = null): float { $result = self::fuzzyCheckRange($number, $min, $max); @@ -180,12 +137,12 @@ public static function fuzzyAssertRange($number, $min, $max, ?string $name = nul * * Sass allows dividing by 0. * - * @param int|float $num1 - * @param int|float $num2 + * @param float $num1 + * @param float $num2 * - * @return int|float + * @return float */ - public static function divideLikeSass($num1, $num2) + public static function divideLikeSass(float $num1, float $num2): float { if ($num2 == 0) { if ($num1 == 0) { @@ -203,16 +160,12 @@ public static function divideLikeSass($num1, $num2) } /** - * Returns $num1 module $num2, using Sass's modulo semantic, which is inherited from Ruby. - * - * PHP's fdiv has a different semantic when the 2 numbers have a different sign. - * - * @param int|float $num1 - * @param int|float $num2 + * Return $num1 modulo $num2, using Sass's [floored division] modulo + * semantics, which it inherited from Ruby and which differ from Dart's. * - * @return int|float + * [floored division]: https://en.wikipedia.org/wiki/Modulo_operation#Variants_of_the_definition */ - public static function moduloLikeSass($num1, $num2) + public static function moduloLikeSass(float $num1, float $num2): float { if ($num2 == 0) { return NAN; @@ -224,6 +177,7 @@ public static function moduloLikeSass($num1, $num2) return 0; } + // PHP's fdiv has a different semantic when the 2 numbers have a different sign. if ($num2 < 0 xor $num1 < 0) { $result += $num2; } diff --git a/src/Value/ComplexSassNumber.php b/src/Value/ComplexSassNumber.php index 52907f80..2f57794d 100644 --- a/src/Value/ComplexSassNumber.php +++ b/src/Value/ComplexSassNumber.php @@ -32,12 +32,12 @@ final class ComplexSassNumber extends SassNumber private $denominatorUnits; /** - * @param int|float $value + * @param float $value * @param list $numeratorUnits * @param list $denominatorUnits * @param array{SassNumber, SassNumber}|null $asSlash */ - public function __construct($value, array $numeratorUnits, array $denominatorUnits, array $asSlash = null) + public function __construct(float $value, array $numeratorUnits, array $denominatorUnits, array $asSlash = null) { assert(\count($numeratorUnits) > 1 || \count($denominatorUnits) > 0); @@ -78,7 +78,7 @@ public function hasPossiblyCompatibleUnits(SassNumber $other): bool throw new \BadMethodCallException(__METHOD__ . 'is not implemented.'); } - protected function withValue($value): SassNumber + protected function withValue(float $value): SassNumber { return new self($value, $this->numeratorUnits, $this->denominatorUnits); } diff --git a/src/Value/SassColor.php b/src/Value/SassColor.php index 80a4999a..27324c64 100644 --- a/src/Value/SassColor.php +++ b/src/Value/SassColor.php @@ -46,28 +46,28 @@ final class SassColor extends Value /** * This color's hue, between `0` and `360`. * - * @var int|float|null + * @var float|null */ private $hue; /** * This color's saturation, a percentage between `0` and `100`. * - * @var int|float|null + * @var float|null */ private $saturation; /** * This color's lightness, a percentage between `0` and `100`. * - * @var int|float|null + * @var float|null */ private $lightness; /** * This color's alpha channel, between `0` and `1`. * - * @var int|float + * @var float * @readonly */ private $alpha; @@ -85,13 +85,13 @@ final class SassColor extends Value * @param int $red * @param int $blue * @param int $green - * @param int|float|null $alpha + * @param float|null $alpha * * @return SassColor * * @throws \OutOfRangeException if values are outside the expected range. */ - public static function rgb(int $red, int $green, int $blue, $alpha = null): SassColor + public static function rgb(int $red, int $green, int $blue, ?float $alpha = null): SassColor { return self::rgbInternal($red, $green, $blue, $alpha); } @@ -104,7 +104,7 @@ public static function rgb(int $red, int $green, int $blue, $alpha = null): Sass * @param int $red * @param int $blue * @param int $green - * @param int|float|null $alpha + * @param float|null $alpha * @param SpanColorFormat|string|null $format * * @return SassColor @@ -113,10 +113,10 @@ public static function rgb(int $red, int $green, int $blue, $alpha = null): Sass * * @throws \OutOfRangeException if values are outside the expected range. */ - public static function rgbInternal(int $red, int $green, int $blue, $alpha = null, $format = null): SassColor + public static function rgbInternal(int $red, int $green, int $blue, ?float $alpha = null, $format = null): SassColor { if ($alpha === null) { - $alpha = 1; + $alpha = 1.0; } else { $alpha = NumberUtil::fuzzyAssertRange($alpha, 0, 1, 'alpha'); } @@ -129,16 +129,16 @@ public static function rgbInternal(int $red, int $green, int $blue, $alpha = nul } /** - * @param int|float $hue - * @param int|float $saturation - * @param int|float $lightness - * @param int|float|null $alpha + * @param float $hue + * @param float $saturation + * @param float $lightness + * @param float|null $alpha * * @return SassColor * * @throws \OutOfRangeException if values are outside the expected range. */ - public static function hsl($hue, $saturation, $lightness, $alpha = null): SassColor + public static function hsl(float $hue, float $saturation, float $lightness, ?float $alpha = null): SassColor { return self::hslInternal($hue, $saturation, $lightness, $alpha); } @@ -148,10 +148,10 @@ public static function hsl($hue, $saturation, $lightness, $alpha = null): SassCo * * @internal * - * @param int|float $hue - * @param int|float $saturation - * @param int|float $lightness - * @param int|float|null $alpha + * @param float $hue + * @param float $saturation + * @param float $lightness + * @param float|null $alpha * @param SpanColorFormat|string|null $format * * @return SassColor @@ -160,10 +160,10 @@ public static function hsl($hue, $saturation, $lightness, $alpha = null): SassCo * * @phpstan-param SpanColorFormat|ColorFormat::*|null $format */ - public static function hslInternal($hue, $saturation, $lightness, $alpha = null, $format = null): SassColor + public static function hslInternal(float $hue, float $saturation, float $lightness, ?float $alpha = null, $format = null): SassColor { if ($alpha === null) { - $alpha = 1; + $alpha = 1.0; } else { $alpha = NumberUtil::fuzzyAssertRange($alpha, 0, 1, 'alpha'); } @@ -176,14 +176,14 @@ public static function hslInternal($hue, $saturation, $lightness, $alpha = null, } /** - * @param int|float $hue - * @param int|float $whiteness - * @param int|float $blackness - * @param int|float|null $alpha + * @param float $hue + * @param float $whiteness + * @param float $blackness + * @param float|null $alpha * * @return SassColor */ - public static function hwb($hue, $whiteness, $blackness, $alpha = null): SassColor + public static function hwb(float $hue, float $whiteness, float $blackness, ?float $alpha = null): SassColor { $scaledHue = fmod($hue , 360) / 360; $scaledWhiteness = NumberUtil::fuzzyAssertRange($whiteness, 0, 100, 'whiteness') / 100; @@ -216,15 +216,15 @@ public static function hwb($hue, $whiteness, $blackness, $alpha = null): SassCol * @param int|null $red * @param int|null $green * @param int|null $blue - * @param int|float|null $hue - * @param int|float|null $saturation - * @param int|float|null $lightness - * @param int|float $alpha + * @param float|null $hue + * @param float|null $saturation + * @param float|null $lightness + * @param float $alpha * @param SpanColorFormat|string|null $format * * @phpstan-param SpanColorFormat|ColorFormat::*|null $format */ - private function __construct(?int $red, ?int $green, ?int $blue, $hue, $saturation, $lightness, $alpha, $format = null) + private function __construct(?int $red, ?int $green, ?int $blue, ?float $hue, ?float $saturation, ?float $lightness, float $alpha, $format = null) { $this->red = $red; $this->green = $green; @@ -266,10 +266,7 @@ public function getBlue(): int return $this->blue; } - /** - * @return int|float - */ - public function getHue() + public function getHue(): float { if (\is_null($this->hue)) { $this->rgbToHsl(); @@ -279,10 +276,7 @@ public function getHue() return $this->hue; } - /** - * @return int|float - */ - public function getSaturation() + public function getSaturation(): float { if (\is_null($this->saturation)) { $this->rgbToHsl(); @@ -292,10 +286,7 @@ public function getSaturation() return $this->saturation; } - /** - * @return int|float - */ - public function getLightness() + public function getLightness(): float { if (\is_null($this->lightness)) { $this->rgbToHsl(); @@ -305,26 +296,17 @@ public function getLightness() return $this->lightness; } - /** - * @return float|int - */ - public function getWhiteness() + public function getWhiteness(): float { return min($this->getRed(), $this->getGreen(), $this->getBlue()) / 255 * 100; } - /** - * @return float|int - */ - public function getBlackness() + public function getBlackness(): float { return 100 - max($this->getRed(), $this->getGreen(), $this->getBlue()) / 255 * 100; } - /** - * @return int|float - */ - public function getAlpha() + public function getAlpha(): float { return $this->alpha; } @@ -358,47 +340,47 @@ public function assertColor(?string $name = null): SassColor * @param int|null $red * @param int|null $green * @param int|null $blue - * @param int|float|null $alpha + * @param float|null $alpha * * @return SassColor */ - public function changeRgb(?int $red = null, ?int $green = null, ?int $blue = null, $alpha = null): SassColor + public function changeRgb(?int $red = null, ?int $green = null, ?int $blue = null, ?float $alpha = null): SassColor { return self::rgb($red ?? $this->getRed(), $green ?? $this->getGreen(), $blue ?? $this->getBlue(), $alpha ?? $this->alpha); } /** - * @param int|float|null $hue - * @param int|float|null $saturation - * @param int|float|null $lightness - * @param int|float|null $alpha + * @param float|null $hue + * @param float|null $saturation + * @param float|null $lightness + * @param float|null $alpha * * @return SassColor */ - public function changeHsl($hue = null, $saturation = null, $lightness = null, $alpha = null): SassColor + public function changeHsl(?float $hue = null, ?float $saturation = null, ?float $lightness = null, ?float $alpha = null): SassColor { return self::hsl($hue ?? $this->getHue(), $saturation ?? $this->getSaturation(), $lightness ?? $this->getLightness(), $alpha ?? $this->alpha); } /** - * @param int|float|null $hue - * @param int|float|null $whiteness - * @param int|float|null $blackness - * @param int|float|null $alpha + * @param float|null $hue + * @param float|null $whiteness + * @param float|null $blackness + * @param float|null $alpha * * @return SassColor */ - public function changeHwb($hue = null, $whiteness = null, $blackness = null, $alpha = null): SassColor + public function changeHwb(?float $hue = null, ?float $whiteness = null, ?float $blackness = null, ?float $alpha = null): SassColor { return self::hwb($hue ?? $this->getHue(), $whiteness ?? $this->getWhiteness(), $blackness ?? $this->getBlackness(), $alpha ?? $this->alpha); } /** - * @param int|float $alpha + * @param float $alpha * * @return SassColor */ - public function changeAlpha($alpha): SassColor + public function changeAlpha(float $alpha): SassColor { return new self( $this->red, @@ -508,14 +490,7 @@ private function hslToRgb(): void $this->blue = NumberUtil::fuzzyRound(self::hueToRgb($m1, $m2, $scaledHue - 1 / 3) * 255); } - /** - * @param int|float $m1 - * @param int|float $m2 - * @param int|float $hue - * - * @return int|float - */ - private static function hueToRgb($m1, $m2, $hue) + private static function hueToRgb(float $m1, float $m2, float $hue): float { if ($hue < 0) { $hue += 1; diff --git a/src/Value/SassNumber.php b/src/Value/SassNumber.php index 4f4767cd..2ed497a5 100644 --- a/src/Value/SassNumber.php +++ b/src/Value/SassNumber.php @@ -33,30 +33,30 @@ abstract class SassNumber extends Value */ private const CONVERSIONS = [ 'in' => [ - 'in' => 1, - 'pc' => 6, - 'pt' => 72, - 'px' => 96, + 'in' => 1.0, + 'pc' => 6.0, + 'pt' => 72.0, + 'px' => 96.0, 'cm' => 2.54, 'mm' => 25.4, 'q' => 101.6, ], 'deg' => [ - 'deg' => 360, - 'grad' => 400, - 'rad' => 6.28318530717958647692528676, // 2 * M_PI - 'turn' => 1, + 'deg' => 360.0, + 'grad' => 400.0, + 'rad' => 2 * M_PI, + 'turn' => 1.0, ], 's' => [ - 's' => 1, - 'ms' => 1000, + 's' => 1.0, + 'ms' => 1000.0, ], 'Hz' => [ - 'Hz' => 1, + 'Hz' => 1.0, 'kHz' => 0.001, ], 'dpi' => [ - 'dpi' => 1, + 'dpi' => 1.0, 'dpcm' => 1 / 2.54, 'dppx' => 1 / 96, ], @@ -99,7 +99,7 @@ abstract class SassNumber extends Value ]; /** - * @var int|float + * @var float * @readonly */ private $value; @@ -114,10 +114,10 @@ abstract class SassNumber extends Value private $asSlash; /** - * @param int|float $value + * @param float $value * @param array{SassNumber, SassNumber}|null $asSlash */ - protected function __construct($value, array $asSlash = null) + protected function __construct(float $value, array $asSlash = null) { $this->value = $value; $this->asSlash = $asSlash; @@ -129,12 +129,12 @@ protected function __construct($value, array $asSlash = null) * This matches the numbers that can be written as literals. * {@see SassNumber::withUnits} can be used to construct more complex units. * - * @param int|float $value + * @param float $value * @param string|null $unit * * @return self */ - final public static function create($value, ?string $unit = null): SassNumber + final public static function create(float $value, ?string $unit = null): SassNumber { if ($unit === null) { return new UnitlessSassNumber($value); @@ -146,13 +146,13 @@ final public static function create($value, ?string $unit = null): SassNumber /** * Creates a number with full $numeratorUnits and $denominatorUnits. * - * @param int|float $value + * @param float $value * @param list $numeratorUnits * @param list $denominatorUnits * * @return self */ - final public static function withUnits($value, array $numeratorUnits = [], array $denominatorUnits = []): SassNumber + final public static function withUnits(float $value, array $numeratorUnits = [], array $denominatorUnits = []): SassNumber { if (empty($numeratorUnits) && empty($denominatorUnits)) { return new UnitlessSassNumber($value); @@ -213,10 +213,8 @@ final public static function withUnits($value, array $numeratorUnits = [], array * float even if $this represents an int from Sass's perspective. Use * {@see isInt} to determine whether this is an integer, {@see asInt} to get its * integer value, or {@see assertInt} to do both at once. - * - * @return float|int */ - public function getValue() + public function getValue(): float { return $this->value; } @@ -249,11 +247,11 @@ public function accept(ValueVisitor $visitor) /** * Returns a SassNumber with this value and the same units. * - * @param int|float $value + * @param float $value * * @return self */ - abstract protected function withValue($value): SassNumber; + abstract protected function withValue(float $value): SassNumber; /** * @param SassNumber $numerator @@ -337,15 +335,15 @@ public function assertInt(?string $name = null): int * came from a function argument, $name is the argument name (without the * `$`). It's used for error reporting. * - * @param int|float $min - * @param int|float $max + * @param float $min + * @param float $max * @param string|null $name * - * @return int|float + * @return float * * @throws SassScriptException if the value is outside the range */ - public function valueInRange($min, $max, ?string $name = null) + public function valueInRange(float $min, float $max, ?string $name = null): float { $result = NumberUtil::fuzzyCheckRange($this->value, $min, $max); @@ -366,18 +364,18 @@ public function valueInRange($min, $max, ?string $name = null) * and should be removed once https://github.com/sass/sass/issues/3374 fully lands and unitless values * are required in these positions. * - * @param int|float $min - * @param int|float $max - * @param string $name - * @param string $unit + * @param float $min + * @param float $max + * @param string $name + * @param string $unit * - * @return int|float + * @return float * * @throws SassScriptException if the value is outside the range * * @internal */ - public function valueInRangeWithUnit($min, $max, string $name, string $unit) + public function valueInRangeWithUnit(float $min, float $max, string $name, string $unit): float { $result = NumberUtil::fuzzyCheckRange($this->value, $min, $max); @@ -503,11 +501,11 @@ public function convert(array $newNumeratorUnits, array $newDenominatorUnits, ?s * @param list $newDenominatorUnits * @param string|null $name The argument name if this is a function argument * - * @return int|float + * @return float * * @throws SassScriptException if this number's units are not compatible with $newNumeratorUnits and $newDenominatorUnits, or if either number is unitless but the other is not. */ - public function convertValue(array $newNumeratorUnits, array $newDenominatorUnits, ?string $name = null) + public function convertValue(array $newNumeratorUnits, array $newDenominatorUnits, ?string $name = null): float { return $this->convertOrCoerceValue($newNumeratorUnits, $newDenominatorUnits, false, $name); } @@ -538,11 +536,11 @@ public function convertToMatch(SassNumber $other, ?string $name = null, ?string * @param string|null $name The argument name if this is a function argument * @param string|null $otherName The argument name for $other if this is a function argument * - * @return int|float + * @return float * * @throws SassScriptException if the units are not compatible or if either number is unitless but the other is not. */ - public function convertValueToMatch(SassNumber $other, ?string $name = null, ?string $otherName = null) + public function convertValueToMatch(SassNumber $other, ?string $name = null, ?string $otherName = null): float { return $this->convertOrCoerceValue($other->getNumeratorUnits(), $other->getDenominatorUnits(), false, $name, $other, $otherName); } @@ -583,11 +581,11 @@ public function coerce(array $newNumeratorUnits, array $newDenominatorUnits, ?st * @param list $newDenominatorUnits * @param string|null $name The argument name if this is a function argument * - * @return int|float + * @return float * * @throws SassScriptException if this number's units are not compatible with $newNumeratorUnits and $newDenominatorUnits */ - public function coerceValue(array $newNumeratorUnits, array $newDenominatorUnits, ?string $name = null) + public function coerceValue(array $newNumeratorUnits, array $newDenominatorUnits, ?string $name = null): float { return $this->convertOrCoerceValue($newNumeratorUnits, $newDenominatorUnits, true, $name); } @@ -598,9 +596,9 @@ public function coerceValue(array $newNumeratorUnits, array $newDenominatorUnits * @param string $unit * @param string|null $name The argument name if this is a function argument * - * @return int|float + * @return float */ - public function coerceValueToUnit(string $unit, ?string $name = null) + public function coerceValueToUnit(string $unit, ?string $name = null): float { return $this->coerceValue([$unit], [], $name); } @@ -639,11 +637,11 @@ public function coerceToMatch(SassNumber $other, ?string $name = null, ?string $ * @param string|null $name The argument name if this is a function argument * @param string|null $otherName The argument name for $other if this is a function argument * - * @return int|float + * @return float * * @throws SassScriptException if the units are not compatible */ - public function coerceValueToMatch(SassNumber $other, ?string $name = null, ?string $otherName = null) + public function coerceValueToMatch(SassNumber $other, ?string $name = null, ?string $otherName = null): float { return $this->convertOrCoerceValue($other->getNumeratorUnits(), $other->getDenominatorUnits(), true, $name, $other, $otherName); } @@ -815,22 +813,15 @@ public function equals(object $other): bool /** * @param list $units - * - * @return float|int */ - private static function getCanonicalMultiplier(array $units) + private static function getCanonicalMultiplier(array $units): float { return array_reduce($units, function ($multiplier, $unit) { return $multiplier * self::getCanonicalMultiplierForUnit($unit); - }, 1); + }, 1.0); } - /** - * @param string $unit - * - * @return float|int - */ - private static function getCanonicalMultiplierForUnit(string $unit) + private static function getCanonicalMultiplierForUnit(string $unit): float { foreach (self::CONVERSIONS as $canonicalUnit => $conversions) { if (isset($conversions[$unit])) { @@ -838,7 +829,7 @@ private static function getCanonicalMultiplierForUnit(string $unit) } } - return 1; + return 1.0; } /** @@ -882,8 +873,8 @@ private static function canonicalizeUnitList(array $units): array /** * @template T * - * @param SassNumber $other - * @param callable(int|float, int|float): T $operation + * @param SassNumber $other + * @param callable(float, float): T $operation * * @return T */ @@ -909,11 +900,11 @@ private function coerceUnits(SassNumber $other, callable $operation) * @param SassNumber|null $other * @param string|null $otherName The argument name for $other if this is a function argument * - * @return int|float + * @return float * * @throws SassScriptException if this number's units are not compatible with $newNumeratorUnits and $newDenominatorUnits */ - private function convertOrCoerceValue(array $newNumeratorUnits, array $newDenominatorUnits, bool $coerceUnitless, ?string $name = null, SassNumber $other = null, ?string $otherName = null) + private function convertOrCoerceValue(array $newNumeratorUnits, array $newDenominatorUnits, bool $coerceUnitless, ?string $name = null, SassNumber $other = null, ?string $otherName = null): float { assert($other === null || ($other->getNumeratorUnits() === $newNumeratorUnits && $other->getDenominatorUnits() === $newDenominatorUnits), sprintf("Expected %s to have units %s.", $other, self::buildUnitString($newNumeratorUnits, $newDenominatorUnits))); @@ -1015,13 +1006,13 @@ private function compatibilityException(bool $otherHasUnits, array $newNumerator } /** - * @param int|float $value + * @param float $value * @param list $otherNumerators * @param list $otherDenominators * * @return SassNumber */ - protected function multiplyUnits($value, array $otherNumerators, array $otherDenominators): SassNumber + protected function multiplyUnits(float $value, array $otherNumerators, array $otherDenominators): SassNumber { $newNumerators = array(); @@ -1072,9 +1063,9 @@ protected function multiplyUnits($value, array $otherNumerators, array $otherDen * @param string $unit1 * @param string $unit2 * - * @return float|int|null + * @return float|null */ - protected static function getConversionFactor(string $unit1, string $unit2) + protected static function getConversionFactor(string $unit1, string $unit2): ?float { if ($unit1 === $unit2) { return 1; diff --git a/src/Value/SingleUnitSassNumber.php b/src/Value/SingleUnitSassNumber.php index 3e42b618..744fe058 100644 --- a/src/Value/SingleUnitSassNumber.php +++ b/src/Value/SingleUnitSassNumber.php @@ -62,11 +62,11 @@ final class SingleUnitSassNumber extends SassNumber private $unit; /** - * @param int|float $value + * @param float $value * @param string $unit * @param array{SassNumber, SassNumber}|null $asSlash */ - public function __construct($value, string $unit, array $asSlash = null) + public function __construct(float $value, string $unit, array $asSlash = null) { parent::__construct($value, $asSlash); $this->unit = $unit; @@ -87,7 +87,7 @@ public function hasUnits(): bool return true; } - protected function withValue($value): SassNumber + protected function withValue(float $value): SassNumber { return new self($value, $this->unit); } @@ -143,7 +143,7 @@ public function coerceToMatch(SassNumber $other, ?string $name = null, ?string $ return parent::coerceToMatch($other, $name, $otherName); } - public function coerceValueToMatch(SassNumber $other, ?string $name = null, ?string $otherName = null) + public function coerceValueToMatch(SassNumber $other, ?string $name = null, ?string $otherName = null): float { if ($other instanceof SingleUnitSassNumber) { $coerced = $this->tryCoerceValueToUnit($other->unit); @@ -171,7 +171,7 @@ public function convertToMatch(SassNumber $other, ?string $name = null, ?string return parent::convertToMatch($other, $name, $otherName); } - public function convertValueToMatch(SassNumber $other, ?string $name = null, ?string $otherName = null) + public function convertValueToMatch(SassNumber $other, ?string $name = null, ?string $otherName = null): float { if ($other instanceof SingleUnitSassNumber) { $coerced = $this->tryCoerceValueToUnit($other->unit); @@ -199,7 +199,7 @@ public function coerce(array $newNumeratorUnits, array $newDenominatorUnits, ?st return parent::coerce($newNumeratorUnits, $newDenominatorUnits, $name); } - public function coerceValue(array $newNumeratorUnits, array $newDenominatorUnits, ?string $name = null) + public function coerceValue(array $newNumeratorUnits, array $newDenominatorUnits, ?string $name = null): float { if (\count($newNumeratorUnits) === 1 && \count($newDenominatorUnits) === 0) { $coerced = $this->tryCoerceValueToUnit($newNumeratorUnits[0]); @@ -213,7 +213,7 @@ public function coerceValue(array $newNumeratorUnits, array $newDenominatorUnits return parent::coerceValue($newNumeratorUnits, $newDenominatorUnits, $name); } - public function coerceValueToUnit(string $unit, ?string $name = null) + public function coerceValueToUnit(string $unit, ?string $name = null): float { $coerced = $this->tryCoerceValueToUnit($unit); @@ -242,13 +242,13 @@ public function equals(object $other): bool } /** - * @param int|float $value + * @param float $value * @param list $otherNumerators * @param list $otherDenominators * * @return SassNumber */ - protected function multiplyUnits($value, array $otherNumerators, array $otherDenominators): SassNumber + protected function multiplyUnits(float $value, array $otherNumerators, array $otherDenominators): SassNumber { $newNumerators = $otherDenominators; $removed = false; @@ -275,11 +275,6 @@ protected function multiplyUnits($value, array $otherNumerators, array $otherDen return SassNumber::withUnits($value, $newNumerators, $otherDenominators); } - /** - * @param string $unit - * - * @return SassNumber|null - */ private function tryCoerceToUnit(string $unit): ?SassNumber { if ($unit === $this->unit) { @@ -295,12 +290,7 @@ private function tryCoerceToUnit(string $unit): ?SassNumber return new SingleUnitSassNumber($this->getValue() * $factor, $unit); } - /** - * @param string $unit - * - * @return float|int|null - */ - private function tryCoerceValueToUnit(string $unit) + private function tryCoerceValueToUnit(string $unit): ?float { $factor = self::getConversionFactor($unit, $this->unit); diff --git a/src/Value/UnitlessSassNumber.php b/src/Value/UnitlessSassNumber.php index 2fe2ca97..96b28f12 100644 --- a/src/Value/UnitlessSassNumber.php +++ b/src/Value/UnitlessSassNumber.php @@ -22,10 +22,10 @@ final class UnitlessSassNumber extends SassNumber { /** - * @param int|float $value + * @param float $value * @param array{SassNumber, SassNumber}|null $asSlash */ - public function __construct($value, array $asSlash = null) + public function __construct(float $value, array $asSlash = null) { parent::__construct($value, $asSlash); } @@ -45,7 +45,7 @@ public function hasUnits(): bool return false; } - protected function withValue($value): SassNumber + protected function withValue(float $value): SassNumber { return new self($value); } @@ -80,7 +80,7 @@ public function coerceToMatch(SassNumber $other, ?string $name = null, ?string $ return $other->withValue($this->getValue()); } - public function coerceValueToMatch(SassNumber $other, ?string $name = null, ?string $otherName = null) + public function coerceValueToMatch(SassNumber $other, ?string $name = null, ?string $otherName = null): float { return $this->getValue(); } @@ -95,7 +95,7 @@ public function convertToMatch(SassNumber $other, ?string $name = null, ?string return parent::convertToMatch($other, $name, $otherName); } - public function convertValueToMatch(SassNumber $other, ?string $name = null, ?string $otherName = null) + public function convertValueToMatch(SassNumber $other, ?string $name = null, ?string $otherName = null): float { if (!$other->hasUnits()) { return $this->getValue(); @@ -110,12 +110,12 @@ public function coerce(array $newNumeratorUnits, array $newDenominatorUnits, ?st return SassNumber::withUnits($this->getValue(), $newNumeratorUnits, $newDenominatorUnits); } - public function coerceValue(array $newNumeratorUnits, array $newDenominatorUnits, ?string $name = null) + public function coerceValue(array $newNumeratorUnits, array $newDenominatorUnits, ?string $name = null): float { return $this->getValue(); } - public function coerceValueToUnit(string $unit, ?string $name = null) + public function coerceValueToUnit(string $unit, ?string $name = null): float { return $this->getValue(); } diff --git a/tests/Value/SassColor/SassColorTest.php b/tests/Value/SassColor/SassColorTest.php index 989abe83..ce2e0c23 100644 --- a/tests/Value/SassColor/SassColorTest.php +++ b/tests/Value/SassColor/SassColorTest.php @@ -13,7 +13,6 @@ namespace ScssPhp\ScssPhp\Tests\Value\SassColor; use ScssPhp\ScssPhp\Tests\Value\ValueTestCase; -use ScssPhp\ScssPhp\Util\NumberUtil; use ScssPhp\ScssPhp\Value\SassColor; class SassColorTest extends ValueTestCase @@ -22,7 +21,7 @@ public function testAnRgbaColorHasAnAlphaChannel() { /** @var SassColor $color */ $color = self::parseValue('rgba(10, 20, 30, 0.7)'); - $this->assertEqualsWithDelta(0.7, $color->getAlpha(), NumberUtil::EPSILON); + $this->assertEqualsWithDelta(0.7, $color->getAlpha(), 1e-11); } /** diff --git a/tests/Value/SassNumber/UnitlessIntegerTest.php b/tests/Value/SassNumber/UnitlessIntegerTest.php index f0984fdc..2e7cdea8 100644 --- a/tests/Value/SassNumber/UnitlessIntegerTest.php +++ b/tests/Value/SassNumber/UnitlessIntegerTest.php @@ -34,7 +34,7 @@ protected function setUp(): void public function testHasTheCorrectValue() { $this->assertEquals(123, $this->value->getValue()); - $this->assertIsInt($this->value->getValue()); + $this->assertIsFloat($this->value->getValue()); } public function testHasNoUnits()