Repository: briannesbitt/Carbon (php)

Issue:
PHPStan macro doesn't support PHPStan >=1.7.0 with any arguments.
<!--
    🛑 DON'T REMOVE ME.
    This issue template apply to all
      - bug reports,
      - feature proposals,
      - and documentation requests

    Having all those informations will allow us to know exactly
    what you expect and answer you faster and precisely (answer
    that matches your Carbon version, PHP version and usage).
    
    Note: Comments between <!- - and - -> won't appear in the final
    issue (See [Preview] tab).
-->
Hello,

This issue is very similar to https://github.com/briannesbitt/Carbon/issues/2629. While that issue was resolved in version 2.59.0, it only works for macros without any arguments. If you add an argument to the phpunit tests/PHPStan:
```php
Carbon::macro('foo', function ($someArg) {
    return $someArg;
});
```

Then you'll get the crash again with a slightly different error:
>Internal error: Internal error: `PHPStan\Reflection\Php\PhpMethodReflection::PHPStan\Reflection\Php\{closure}()`: Argument <span>#</span>1 (`$reflection`) must be of type
`PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter`, `ReflectionParameter` given in file /Volumes/code/external/Carbon/tests/PHPStan/Fixture.php

Carbon version: 2.62.1
PHP version: 8.1.9
PHPStan version: 1.8.10 (but it seems like it fails for anything >=1.7.0)

Thanks!

Pull request description:
Fix PHPStan macro arguments check
Fix #2685

Patch:
diff --git a/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php b/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php
index 1504b0ab..ba7cf632 100644
--- a/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php
+++ b/lazy/Carbon/PHPStan/AbstractMacroBuiltin.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
 
 namespace Carbon\PHPStan;
 
+use PHPStan\BetterReflection\Reflection;
 use ReflectionMethod;
 
 if (!class_exists(AbstractReflectionMacro::class, false)) {
@@ -23,6 +24,10 @@ if (!class_exists(AbstractReflectionMacro::class, false)) {
          */
         public function getReflection(): ?ReflectionMethod
         {
+            if ($this->reflectionFunction instanceof Reflection\ReflectionMethod) {
+                return new Reflection\Adapter\ReflectionMethod($this->reflectionFunction);
+            }
+
             return $this->reflectionFunction instanceof ReflectionMethod
                 ? $this->reflectionFunction
                 : null;
diff --git a/lazy/Carbon/PHPStan/AbstractMacroStatic.php b/lazy/Carbon/PHPStan/AbstractMacroStatic.php
index 450dceb8..bd4c8e80 100644
--- a/lazy/Carbon/PHPStan/AbstractMacroStatic.php
+++ b/lazy/Carbon/PHPStan/AbstractMacroStatic.php
@@ -28,6 +28,10 @@ if (!class_exists(AbstractReflectionMacro::class, false)) {
                 return $this->reflectionFunction;
             }
 
+            if ($this->reflectionFunction instanceof Reflection\ReflectionMethod) {
+                return new Reflection\Adapter\ReflectionMethod($this->reflectionFunction);
+            }
+
             return $this->reflectionFunction instanceof ReflectionMethod
                 ? new Reflection\Adapter\ReflectionMethod(
                     Reflection\ReflectionMethod::createFromName(
diff --git a/lazy/Carbon/PHPStan/MacroStrongType.php b/lazy/Carbon/PHPStan/MacroStrongType.php
index 47002c47..f615b3a6 100644
--- a/lazy/Carbon/PHPStan/MacroStrongType.php
+++ b/lazy/Carbon/PHPStan/MacroStrongType.php
@@ -21,7 +21,9 @@ if (!class_exists(LazyMacro::class, false)) {
          */
         public function getFileName(): ?string
         {
-            return $this->reflectionFunction->getFileName();
+            $file = $this->reflectionFunction->getFileName();
+
+            return (($file ? realpath($file) : null) ?: $file) ?: null;
         }
 
         /**
diff --git a/lazy/Carbon/PHPStan/MacroWeakType.php b/lazy/Carbon/PHPStan/MacroWeakType.php
index c47d70ed..bf64c1dd 100644
--- a/lazy/Carbon/PHPStan/MacroWeakType.php
+++ b/lazy/Carbon/PHPStan/MacroWeakType.php
@@ -23,7 +23,9 @@ if (!class_exists(LazyMacro::class, false)) {
          */
         public function getFileName()
         {
-            return $this->reflectionFunction->getFileName();
+            $file = $this->reflectionFunction->getFileName();
+
+            return (($file ? realpath($file) : null) ?: $file) ?: null;
         }
 
         /**
diff --git a/src/Carbon/Lang/de.php b/src/Carbon/Lang/de.php
index 3b70750e..ff00c974 100644
--- a/src/Carbon/Lang/de.php
+++ b/src/Carbon/Lang/de.php
@@ -105,13 +105,4 @@ return [
     'first_day_of_week' => 1,
     'day_of_first_week_of_year' => 4,
     'list' => [', ', ' und '],
-    'ordinal_words' => [
-        'of' => 'im',
-        'first' => 'erster',
-        'second' => 'zweiter',
-        'third' => 'dritter',
-        'fourth' => 'vierten',
-        'fifth' => 'fünfter',
-        'last' => 'letzten',
-    ],
 ];
diff --git a/src/Carbon/Lang/es.php b/src/Carbon/Lang/es.php
index 1c4fcfd0..f77ec39c 100644
--- a/src/Carbon/Lang/es.php
+++ b/src/Carbon/Lang/es.php
@@ -26,7 +26,6 @@
  * - quinterocesar
  * - Daniel Commesse Liévanos (danielcommesse)
  * - Pete Scopes (pdscopes)
- * - gam04
  */
 
 use Carbon\CarbonInterface;
@@ -109,13 +108,4 @@ return [
     'day_of_first_week_of_year' => 4,
     'list' => [', ', ' y '],
     'meridiem' => ['a. m.', 'p. m.'],
-    'ordinal_words' => [
-        'of' => 'de',
-        'first' => 'primer',
-        'second' => 'segundo',
-        'third' => 'tercer',
-        'fourth' => 'cuarto',
-        'fifth' => 'quinto',
-        'last' => 'último',
-    ],
 ];
diff --git a/src/Carbon/Lang/fr.php b/src/Carbon/Lang/fr.php
index f4c7247b..8c81ec65 100644
--- a/src/Carbon/Lang/fr.php
+++ b/src/Carbon/Lang/fr.php
@@ -111,13 +111,4 @@ return [
     'first_day_of_week' => 1,
     'day_of_first_week_of_year' => 4,
     'list' => [', ', ' et '],
-    'ordinal_words' => [
-        'of' => 'de',
-        'first' => 'premier',
-        'second' => 'deuxième',
-        'third' => 'troisième',
-        'fourth' => 'quatrième',
-        'fifth' => 'cinquième',
-        'last' => 'dernier',
-    ],
 ];
diff --git a/src/Carbon/Lang/it.php b/src/Carbon/Lang/it.php
index 49875d7e..30c0ffd5 100644
--- a/src/Carbon/Lang/it.php
+++ b/src/Carbon/Lang/it.php
@@ -103,13 +103,4 @@ return [
     'first_day_of_week' => 1,
     'day_of_first_week_of_year' => 4,
     'list' => [', ', ' e '],
-    'ordinal_words' => [
-        'of' => 'di',
-        'first' => 'primo',
-        'second' => 'secondo',
-        'third' => 'terzo',
-        'fourth' => 'quarto',
-        'fifth' => 'quinto',
-        'last' => 'ultimo',
-    ],
 ];
diff --git a/src/Carbon/Lang/pt.php b/src/Carbon/Lang/pt.php
index bb6359b1..0af89499 100644
--- a/src/Carbon/Lang/pt.php
+++ b/src/Carbon/Lang/pt.php
@@ -104,13 +104,4 @@ return [
     'first_day_of_week' => 1,
     'day_of_first_week_of_year' => 4,
     'list' => [', ', ' e '],
-    'ordinal_words' => [
-        'of' => 'de',
-        'first' => 'primeira',
-        'second' => 'segunda',
-        'third' => 'terceira',
-        'fourth' => 'quarta',
-        'fifth' => 'quinta',
-        'last' => 'última',
-    ],
 ];
diff --git a/src/Carbon/Lang/sl.php b/src/Carbon/Lang/sl.php
index b000e30e..2e197212 100644
--- a/src/Carbon/Lang/sl.php
+++ b/src/Carbon/Lang/sl.php
@@ -49,8 +49,8 @@ return [
     'a_second' => '{1}nekaj sekund|:count sekunda|:count sekundi|:count sekunde|:count sekund',
     's' => ':count s',
 
-    'year_ago' => ':count letom|:count letoma|:count leti|:count leti',
-    'y_ago' => ':count letom|:count letoma|:count leti|:count leti',
+    'year_ago' => ':count letom|:count leti|:count leti|:count leti',
+    'y_ago' => ':count letom|:count leti|:count leti|:count leti',
     'month_ago' => ':count mesecem|:count meseci|:count meseci|:count meseci',
     'week_ago' => ':count tednom|:count tednoma|:count tedni|:count tedni',
     'day_ago' => ':count dnem|:count dnevoma|:count dnevi|:count dnevi',
diff --git a/src/Carbon/PHPStan/AbstractMacro.php b/src/Carbon/PHPStan/AbstractMacro.php
index 0369e809..dd6dfdf4 100644
--- a/src/Carbon/PHPStan/AbstractMacro.php
+++ b/src/Carbon/PHPStan/AbstractMacro.php
@@ -14,6 +14,12 @@ declare(strict_types=1);
 namespace Carbon\PHPStan;
 
 use Closure;
+use InvalidArgumentException;
+use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter as AdapterReflectionParameter;
+use PHPStan\BetterReflection\Reflection\Adapter\ReflectionType as AdapterReflectionType;
+use PHPStan\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass;
+use PHPStan\BetterReflection\Reflection\ReflectionFunction as BetterReflectionFunction;
+use PHPStan\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter;
 use PHPStan\Reflection\Php\BuiltinMethodReflection;
 use PHPStan\TrinaryLogic;
 use ReflectionClass;
@@ -74,14 +80,26 @@ abstract class AbstractMacro implements BuiltinMethodReflection
     {
         $this->className = $className;
         $this->methodName = $methodName;
-        $this->reflectionFunction = \is_array($macro)
+        $rawReflectionFunction = \is_array($macro)
             ? new ReflectionMethod($macro[0], $macro[1])
             : new ReflectionFunction($macro);
-        $this->parameters = $this->reflectionFunction->getParameters();
+        $this->reflectionFunction = self::hasModernParser()
+            ? $this->getReflectionFunction($macro)
+            : $rawReflectionFunction; // @codeCoverageIgnore
+        $this->parameters = array_map(
+            function ($parameter) {
+                if ($parameter instanceof BetterReflectionParameter) {
+                    return new AdapterReflectionParameter($parameter);
+                }
 
-        if ($this->reflectionFunction->isClosure()) {
+                return $parameter; // @codeCoverageIgnore
+            },
+            $this->reflectionFunction->getParameters()
+        );
+
+        if ($rawReflectionFunction->isClosure()) {
             try {
-                $closure = $this->reflectionFunction->getClosure();
+                $closure = $rawReflectionFunction->getClosure();
                 $boundClosure = Closure::bind($closure, new stdClass());
                 $this->static = (!$boundClosure || (new ReflectionFunction($boundClosure))->getClosureThis() === null);
             } catch (Throwable $e) {
@@ -90,6 +108,31 @@ abstract class AbstractMacro implements BuiltinMethodReflection
         }
     }
 
+    private function getReflectionFunction($spec)
+    {
+        if (\is_array($spec) && \count($spec) === 2 && \is_string($spec[1])) {
+            \assert($spec[1] !== '');
+
+            if (\is_object($spec[0])) {
+                return BetterReflectionClass::createFromInstance($spec[0])
+                    ->getMethod($spec[1]);
+            }
+
+            return BetterReflectionClass::createFromName($spec[0])
+                ->getMethod($spec[1]);
+        }
+
+        if (\is_string($spec)) {
+            return BetterReflectionFunction::createFromName($spec);
+        }
+
+        if ($spec instanceof Closure) {
+            return BetterReflectionFunction::createFromClosure($spec);
+        }
+
+        throw new InvalidArgumentException('Could not create reflection from the spec given'); // @codeCoverageIgnore
+    }
+
     /**
      * {@inheritdoc}
      */
@@ -175,7 +218,13 @@ abstract class AbstractMacro implements BuiltinMethodReflection
      */
     public function getReturnType(): ?ReflectionType
     {
-        return $this->reflectionFunction->getReturnType();
+        $type = $this->reflectionFunction->getReturnType();
+
+        if ($type instanceof ReflectionType) {
+            return $type; // @codeCoverageIgnore
+        }
+
+        return self::adaptType($type);
     }
 
     /**
@@ -209,4 +258,26 @@ abstract class AbstractMacro implements BuiltinMethodReflection
     {
         return null;
     }
+
+    private static function adaptType($type)
+    {
+        $method = method_exists(AdapterReflectionType::class, 'fromTypeOrNull')
+            ? 'fromTypeOrNull'
+            : 'fromReturnTypeOrNull'; // @codeCoverageIgnore
+
+        return AdapterReflectionType::$method($type);
+    }
+
+    private static function hasModernParser(): bool
+    {
+        static $modernParser = null;
+
+        if ($modernParser !== null) {
+            return $modernParser;
+        }
+
+        $modernParser = method_exists(AdapterReflectionType::class, 'fromTypeOrNull');
+
+        return $modernParser;
+    }
 }
diff --git a/src/Carbon/Traits/Localization.php b/src/Carbon/Traits/Localization.php
index ff249566..bd641743 100644
--- a/src/Carbon/Traits/Localization.php
+++ b/src/Carbon/Traits/Localization.php
@@ -357,13 +357,6 @@ trait Localization
             $weekdays = $messages['weekdays'] ?? [];
             $meridiem = $messages['meridiem'] ?? ['AM', 'PM'];
 
-            if (isset($messages['ordinal_words'])) {
-                $timeString = self::replaceOrdinalWords(
-                    $timeString,
-                    $key === 'from' ? array_flip($messages['ordinal_words']) : $messages['ordinal_words']
-                );
-            }
-
             if ($key === 'from') {
                 foreach (['months', 'weekdays'] as $variable) {
                     $list = $messages[$variable.'_standalone'] ?? null;
@@ -828,11 +821,4 @@ trait Localization
 
         return $list;
     }
-
-    private static function replaceOrdinalWords(string $timeString, array $ordinalWords): string
-    {
-        return preg_replace_callback('/(?<![a-z])[a-z]+(?![a-z])/i', function (array $match) use ($ordinalWords) {
-            return $ordinalWords[mb_strtolower($match[0])] ?? $match[0];
-        }, $timeString);
-    }
 }


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index b681d2a9..20cdffc7 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -102,7 +102,7 @@ jobs:
         with:
           php-version: ${{ matrix.php }}
           extensions: json, msgpack${{ matrix.laravel && ', dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, redis-phpredis/phpredis@5.3.5, igbinary, msgpack, lzf, zstd, lz4, memcached, gmp' || '' }}
-          ini-values: error_reporting=E_ALL
+          ini-values: error_reporting=${{ (matrix.setup != 'lowest') && '32767' || '8191' }}
           tools: composer:v2
           coverage: ${{ matrix.coverage && 'xdebug' || 'none' }}
         env:
diff --git a/tests/Jenssegers/DateTest.php b/tests/Jenssegers/DateTest.php
index 06137d03..6798a36e 100644
--- a/tests/Jenssegers/DateTest.php
+++ b/tests/Jenssegers/DateTest.php
@@ -189,13 +189,4 @@ class DateTest extends TestCaseBase
 
         $this->assertSame('Foobar', Carbon::translateTimeString('Foobar', 'xx'));
     }
-
-    public function testTranslateTimeStringWithOrdinalWords()
-    {
-        $date = Carbon::translateTimeString('Premier lundi de mai', 'fr', 'en');
-        $this->assertSame('first monday of may', mb_strtolower($date));
-
-        $date = Carbon::translateTimeString('Premier lundi de mai', 'fr', 'es');
-        $this->assertSame('primer lunes de mayo', mb_strtolower($date));
-    }
 }
diff --git a/tests/Localization/SlSiTest.php b/tests/Localization/SlSiTest.php
index ea71159a..872d113d 100644
--- a/tests/Localization/SlSiTest.php
+++ b/tests/Localization/SlSiTest.php
@@ -331,11 +331,11 @@ class SlSiTest extends LocalizationTestCase
 
         // Carbon::now()->subYears(2)->diffForHumans()
         // '2 years ago'
-        'pred 2 letoma',
+        'pred 2 leti',
 
         // Carbon::now()->subYears(2)->diffForHumans(null, false, true)
         // '2yrs ago'
-        'pred 2 letoma',
+        'pred 2 leti',
 
         // Carbon::now()->addSecond()->diffForHumans()
         // '1 second from now'
@@ -399,7 +399,7 @@ class SlSiTest extends LocalizationTestCase
 
         // Carbon::now()->subYears(2)->subMonths(3)->subDay()->subSecond()->diffForHumans(null, null, true, 4)
         // '2yrs 3mos 1d 1s ago'
-        'pred 2 letoma 3 mes. 1 dnem 1 s',
+        'pred 2 leti 3 mes. 1 dnem 1 s',
 
         // Carbon::now()->addWeek()->addHours(10)->diffForHumans(null, true, false, 2)
         // '1 week 10 hours'
diff --git a/tests/Localization/SlTest.php b/tests/Localization/SlTest.php
index c6878835..e53c4db3 100644
--- a/tests/Localization/SlTest.php
+++ b/tests/Localization/SlTest.php
@@ -331,11 +331,11 @@ class SlTest extends LocalizationTestCase
 
         // Carbon::now()->subYears(2)->diffForHumans()
         // '2 years ago'
-        'pred 2 letoma',
+        'pred 2 leti',
 
         // Carbon::now()->subYears(2)->diffForHumans(null, false, true)
         // '2yrs ago'
-        'pred 2 letoma',
+        'pred 2 leti',
 
         // Carbon::now()->addSecond()->diffForHumans()
         // '1 second from now'
@@ -399,7 +399,7 @@ class SlTest extends LocalizationTestCase
 
         // Carbon::now()->subYears(2)->subMonths(3)->subDay()->subSecond()->diffForHumans(null, null, true, 4)
         // '2yrs 3mos 1d 1s ago'
-        'pred 2 letoma 3 mes. 1 dnem 1 s',
+        'pred 2 leti 3 mes. 1 dnem 1 s',
 
         // Carbon::now()->addWeek()->addHours(10)->diffForHumans(null, true, false, 2)
         // '1 week 10 hours'
diff --git a/tests/PHPStan/Fixture.php b/tests/PHPStan/Fixture.php
index 068d017f..63db8170 100644
--- a/tests/PHPStan/Fixture.php
+++ b/tests/PHPStan/Fixture.php
@@ -19,11 +19,11 @@ class Fixture
 {
     public function testCarbonMacroCalledStatically(): string
     {
-        return Carbon::foo();
+        return Carbon::foo(15);
     }
 
     public function testCarbonMacroCalledDynamically(): string
     {
-        return Carbon::now()->foo();
+        return Carbon::now()->foo(42);
     }
 }
diff --git a/tests/PHPStan/MacroExtensionTest.php b/tests/PHPStan/MacroExtensionTest.php
index 12f9c296..e0e6142b 100644
--- a/tests/PHPStan/MacroExtensionTest.php
+++ b/tests/PHPStan/MacroExtensionTest.php
@@ -27,7 +27,7 @@ class MacroExtensionTest extends AbstractTestCase
 
         $this->assertFalse($scanner->hasMethod(Carbon::class, 'foo'));
 
-        Carbon::macro('foo', function () {
+        Carbon::macro('foo', function ($someArg) {
         });
 
         $this->assertTrue($scanner->hasMethod(Carbon::class, 'foo'));
diff --git a/tests/PHPStan/MacroTest.php b/tests/PHPStan/MacroTest.php
index 4c436a5c..a3d17265 100644
--- a/tests/PHPStan/MacroTest.php
+++ b/tests/PHPStan/MacroTest.php
@@ -113,10 +113,8 @@ class MacroTest extends AbstractTestCase
         );
 
         $this->assertSame(
-            '/**
-             * Foo.
-             */',
-            $macro->getDocComment()
+            "/**\n* Foo.\n*/",
+            preg_replace('/^[\t ]+/m', '', $macro->getDocComment())
         );
     }
 
diff --git a/tests/PHPStan/bootstrap-non-static.php b/tests/PHPStan/bootstrap-non-static.php
index 962e4420..c4faee26 100644
--- a/tests/PHPStan/bootstrap-non-static.php
+++ b/tests/PHPStan/bootstrap-non-static.php
@@ -11,6 +11,6 @@
 
 use Carbon\Carbon;
 
-Carbon::macro('foo', function (): string {
+Carbon::macro('foo', function ($someArg): string {
     return 'foo';
 });
diff --git a/tests/PHPStan/bootstrap.php b/tests/PHPStan/bootstrap.php
index 9cd201f9..4291606e 100644
--- a/tests/PHPStan/bootstrap.php
+++ b/tests/PHPStan/bootstrap.php
@@ -11,6 +11,6 @@
 
 use Carbon\Carbon;
 
-Carbon::macro('foo', static function (): string {
+Carbon::macro('foo', static function ($someArg): string {
     return 'foo';
 });