Repository: briannesbitt/Carbon (PHP)

Issue:
Unable to use named parameter with trait mixin
<!--
    🛑 DON'T REMOVE ME.
    This issue template applies 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!

I encountered an issue with the following code. I have a trait 
<!--
    ⚠️ Your code below (not the code from inside Carbon library)
    Be sure it's dependency-free and enough to reproduce the issue
    when we run it on:
    https://play.phpsandbox.io/embed/nesbot/carbon
-->
```php
<?php

namespace App\Traits\Carbon;

use App\Data\Services\TimezoneService;
use Carbon\Carbon;

trait CarbonTimezoneTrait
{
     public function toAppTz(bool $shift = false): Carbon
    {
        $tz = TimezoneService::app();

        return $shift
            ? self::this()->shiftTimezone($tz)
            : self::this()->timezone($tz);
    }
}
```
which is mixed in via `Carbon::mixin(CarbonTimezoneTrait::class);` However, the following code throws an error:
```php
now()->toAppTz(shift: true); // 'Unknown named parameter $shift'
now()->toAppTz(true); // works
```


Carbon version: **2.66.0**
<!--
    ⚠️ Run the command `composer show nesbot/carbon` to get "versions"
-->

PHP version: **8.1.10**

<!--
    Use `echo phpversion();`
    to get PHP version.

    Some issues can depends on your context, settings,
    macros, timezone, language. So to be sure the code
    you give is enough to reproduce your bug, try it
    first in:
    https://play.phpsandbox.io/embed/nesbot/carbon?input=%3C%3Fphp%0A%0Aecho%20Carbon%3A%3Aparse('2018-06-01')-%3EsubDay()-%3Emonth%3B

    You can use the [Options] button to change the version
    then when you get the bug with this editor, you can use
    the [Export] button, copy the link of the opened tab,
    then paste it in the issue. Then we can immediatly get
    your issue.
-->

Thanks!



Discussion:

Patch:
diff --git a/phpstan.neon b/phpstan.neon
index 6517deebb3..62c809ff60 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -27,6 +27,14 @@ parameters:
         - '#^Static property Carbon\\Carbon(Period|Interval|Immutable)?::\$translator \(Symfony\\Component\\Translation\\TranslatorInterface\) does not accept Carbon\\Translator\.$#'
         - '#^Call to static method get\(\) on an unknown class Symfony\\Component\\Translation\\PluralizationRules\.$#'
         - '#^Result of method Symfony\\Contracts\\Translation\\LocaleAwareInterface::setLocale\(\) \(void\) is used\.$#'
+        -
+            message: '#^Undefined variable: \$this$#'
+            paths:
+                - src/Carbon/Traits/Mixin.php
+        -
+            message: '#^Variable \$this in isset\(\) is never defined\.$#'
+            paths:
+                - src/Carbon/Traits/Mixin.php
     excludePaths:
         - '*/src/Carbon/Laravel/ServiceProvider.php'
         - '*/src/Carbon/PHPStan/*'
diff --git a/src/Carbon/Traits/Mixin.php b/src/Carbon/Traits/Mixin.php
index 595c287b37..4db010ebba 100644
--- a/src/Carbon/Traits/Mixin.php
+++ b/src/Carbon/Traits/Mixin.php
@@ -103,8 +103,7 @@ private static function loadMixinTrait($trait)
         foreach (self::getMixableMethods($context) as $name) {
             $closureBase = Closure::fromCallable([$context, $name]);
 
-            static::macro($name, function () use ($closureBase, $className) {
-                /** @phpstan-ignore-next-line */
+            static::macro($name, function (...$parameters) use ($closureBase, $className) {
                 $context = isset($this) ? $this->cast($className) : new $className();
 
                 try {
@@ -117,7 +116,7 @@ private static function loadMixinTrait($trait)
                 // in case of errors not converted into exceptions
                 $closure = $closure ?: $closureBase;
 
-                return $closure(...\func_get_args());
+                return $closure(...$parameters);
             });
         }
     }


Test patch:
diff --git a/tests/Carbon/MacroTest.php b/tests/Carbon/MacroTest.php
index ec9dbdd84d..af100aab1d 100644
--- a/tests/Carbon/MacroTest.php
+++ b/tests/Carbon/MacroTest.php
@@ -16,6 +16,7 @@
 use BadMethodCallException;
 use Carbon\Carbon;
 use Carbon\CarbonInterface;
+use CarbonTimezoneTrait;
 use DateTime;
 use Tests\AbstractTestCaseWithOldNow;
 use Tests\Carbon\Fixtures\FooBar;
@@ -267,4 +268,19 @@ public function testTraitMixin()
         $this->assertFalse(Carbon::noThis());
         $this->assertFalse(Carbon::now()->noThis());
     }
+
+    public function testTraitWithNamedParameters()
+    {
+        if (version_compare(PHP_VERSION, '8.0.0-dev', '<')) {
+            $this->markTestSkipped('This tests needs PHP 8 named arguments syntax.');
+        }
+
+        require_once __DIR__.'/../Fixtures/CarbonTimezoneTrait.php';
+
+        Carbon::mixin(CarbonTimezoneTrait::class);
+        $now = Carbon::now();
+        $now = eval("return \$now->toAppTz(tz: 'Europe/Paris');");
+
+        $this->assertSame('Europe/Paris', $now->format('e'));
+    }
 }
diff --git a/tests/CarbonImmutable/MacroTest.php b/tests/CarbonImmutable/MacroTest.php
index 2b92d76124..78fe56df3a 100644
--- a/tests/CarbonImmutable/MacroTest.php
+++ b/tests/CarbonImmutable/MacroTest.php
@@ -15,6 +15,7 @@
 
 use BadMethodCallException;
 use Carbon\CarbonImmutable as Carbon;
+use CarbonTimezoneTrait;
 use Tests\AbstractTestCaseWithOldNow;
 use Tests\Carbon\Fixtures\FooBar;
 use Tests\CarbonImmutable\Fixtures\Mixin;
@@ -135,4 +136,19 @@ public function testTraitMixin()
 
         $this->assertInstanceOf(Carbon::class, Carbon::me());
     }
+
+    public function testTraitWithNamedParameters()
+    {
+        if (version_compare(PHP_VERSION, '8.0.0-dev', '<')) {
+            $this->markTestSkipped('This tests needs PHP 8 named arguments syntax.');
+        }
+
+        require_once __DIR__.'/../Fixtures/CarbonTimezoneTrait.php';
+
+        Carbon::mixin(CarbonTimezoneTrait::class);
+        $now = Carbon::now();
+        $now = eval("return \$now->toAppTz(tz: 'Europe/Paris');");
+
+        $this->assertSame('Europe/Paris', $now->format('e'));
+    }
 }
diff --git a/tests/Fixtures/CarbonTimezoneTrait.php b/tests/Fixtures/CarbonTimezoneTrait.php
new file mode 100644
index 0000000000..85779c393f
--- /dev/null
+++ b/tests/Fixtures/CarbonTimezoneTrait.php
@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * This file is part of the Carbon package.
+ *
+ * (c) Brian Nesbitt <brian@nesbot.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Carbon\CarbonInterface;
+
+trait CarbonTimezoneTrait
+{
+    public function toAppTz(bool $shift = false, string $tz = 'UTC'): CarbonInterface
+    {
+        return $shift
+            ? self::this()->shiftTimezone($tz)
+            : self::this()->timezone($tz);
+    }
+}