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!


Pull request description:
Support named arguments for mixins
Fix #2794

Patch:
diff --git a/phpstan.neon b/phpstan.neon
index 6517deeb..62c809ff 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/CarbonPeriod.php b/src/Carbon/CarbonPeriod.php
index 007a4cf8..01679142 100644
--- a/src/Carbon/CarbonPeriod.php
+++ b/src/Carbon/CarbonPeriod.php
@@ -632,10 +632,6 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable
      */
     public function __construct(...$arguments)
     {
-        if (is_a($this->dateClass, DateTimeImmutable::class, true)) {
-            $this->options = static::IMMUTABLE;
-        }
-
         // Parse and assign arguments one by one. First argument may be an ISO 8601 spec,
         // which will be first parsed into parts and then processed the same way.
 
diff --git a/src/Carbon/Traits/Difference.php b/src/Carbon/Traits/Difference.php
index 1f8a3f6a..a9868ebf 100644
--- a/src/Carbon/Traits/Difference.php
+++ b/src/Carbon/Traits/Difference.php
@@ -298,9 +298,9 @@ trait Difference
      */
     public function diffInWeekdays($date = null, $absolute = true)
     {
-        return $this->diffInDaysFiltered(static function (CarbonInterface $date) {
+        return $this->diffInDaysFiltered(function (CarbonInterface $date) {
             return $date->isWeekday();
-        }, $this->resolveCarbon($date)->modify($this->format('H:i:s.u')), $absolute);
+        }, $date, $absolute);
     }
 
     /**
@@ -313,9 +313,9 @@ trait Difference
      */
     public function diffInWeekendDays($date = null, $absolute = true)
     {
-        return $this->diffInDaysFiltered(static function (CarbonInterface $date) {
+        return $this->diffInDaysFiltered(function (CarbonInterface $date) {
             return $date->isWeekend();
-        }, $this->resolveCarbon($date)->modify($this->format('H:i:s.u')), $absolute);
+        }, $date, $absolute);
     }
 
     /**
diff --git a/src/Carbon/Traits/Mixin.php b/src/Carbon/Traits/Mixin.php
index 595c287b..4db010eb 100644
--- a/src/Carbon/Traits/Mixin.php
+++ b/src/Carbon/Traits/Mixin.php
@@ -103,8 +103,7 @@ trait Mixin
         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 @@ trait Mixin
                 // 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/DiffTest.php b/tests/Carbon/DiffTest.php
index b778fa72..dd39fc13 100644
--- a/tests/Carbon/DiffTest.php
+++ b/tests/Carbon/DiffTest.php
@@ -357,66 +357,28 @@ class DiffTest extends AbstractTestCase
         $this->assertSame(0, $start->diffInWeekdays($end));
     }
 
-    public function testBug2798ConsistencyWithDiffInDays()
-    {
-        // 0 hour diff
-        $s1 = Carbon::create(2023, 6, 6, 15, 0, 0);
-        $e1 = Carbon::create(2023, 6, 6, 15, 0, 0);
-
-        $this->assertSame(0, $s1->diffInWeekdays($e1));
-        $this->assertSame(0, $s1->diffInDays($e1));
-
-        // 1 hour diff
-        $s2 = Carbon::create(2023, 6, 6, 15, 0, 0);
-        $e2 = Carbon::create(2023, 6, 6, 16, 0, 0);
-
-        $this->assertSame(0, $s2->diffInWeekdays($e2));
-        $this->assertSame(0, $s2->diffInDays($e2));
-
-        // 23 hour diff
-        $s3 = Carbon::create(2023, 6, 6, 15, 0, 0);
-        $e3 = Carbon::create(2023, 6, 7, 14, 0, 0);
-
-        $this->assertSame(1, $s3->diffInWeekdays($e3));
-        $this->assertSame(1, $s3->diffInDays($e3));
-
-        // 24 hour diff
-        $s4 = Carbon::create(2023, 6, 6, 15, 0, 0);
-        $e4 = Carbon::create(2023, 6, 7, 15, 0, 0);
-
-        $this->assertSame(1, $s4->diffInWeekdays($e4));
-        $this->assertSame(1, $s4->diffInDays($e4));
-
-        // 25 hour diff
-        $s5 = Carbon::create(2023, 6, 6, 15, 0, 0);
-        $e5 = Carbon::create(2023, 6, 7, 16, 0, 0);
-
-        $this->assertSame(1, $s5->diffInWeekdays($e5));
-        $this->assertSame(1, $s5->diffInDays($e5));
-    }
-
     public function testDiffInWeekdaysPositive()
     {
         $dt = Carbon::createFromDate(2000, 1, 1);
-        $this->assertSame(21, $dt->diffInWeekdays($dt->copy()->addMonth()));
+        $this->assertSame(21, $dt->diffInWeekdays($dt->copy()->endOfMonth()));
     }
 
     public function testDiffInWeekdaysNegativeNoSign()
     {
         $dt = Carbon::createFromDate(2000, 1, 31);
-        $this->assertSame(20, $dt->diffInWeekdays($dt->copy()->startOfMonth()));
+        $this->assertSame(21, $dt->diffInWeekdays($dt->copy()->startOfMonth()));
     }
 
     public function testDiffInWeekdaysNegativeWithSign()
     {
         $dt = Carbon::createFromDate(2000, 1, 31);
-        $this->assertSame(-20, $dt->diffInWeekdays($dt->copy()->startOfMonth(), false));
+        $this->assertSame(-21, $dt->diffInWeekdays($dt->copy()->startOfMonth(), false));
     }
 
     public function testDiffInWeekendDaysPositive()
     {
         $dt = Carbon::createFromDate(2000, 1, 1);
-        $this->assertSame(10, $dt->diffInWeekendDays($dt->copy()->addMonth()));
+        $this->assertSame(10, $dt->diffInWeekendDays($dt->copy()->endOfMonth()));
     }
 
     public function testDiffInWeekendDaysNegativeNoSign()
diff --git a/tests/Carbon/MacroTest.php b/tests/Carbon/MacroTest.php
index ec9dbdd8..af100aab 100644
--- a/tests/Carbon/MacroTest.php
+++ b/tests/Carbon/MacroTest.php
@@ -16,6 +16,7 @@ namespace Tests\Carbon;
 use BadMethodCallException;
 use Carbon\Carbon;
 use Carbon\CarbonInterface;
+use CarbonTimezoneTrait;
 use DateTime;
 use Tests\AbstractTestCaseWithOldNow;
 use Tests\Carbon\Fixtures\FooBar;
@@ -267,4 +268,19 @@ class MacroTest extends AbstractTestCaseWithOldNow
         $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/DiffTest.php b/tests/CarbonImmutable/DiffTest.php
index d3639fe9..517afa37 100644
--- a/tests/CarbonImmutable/DiffTest.php
+++ b/tests/CarbonImmutable/DiffTest.php
@@ -307,19 +307,19 @@ class DiffTest extends AbstractTestCase
     public function testDiffInWeekdaysPositive()
     {
         $dt = Carbon::createFromDate(2000, 1, 1);
-        $this->assertSame(21, $dt->diffInWeekdays($dt->copy()->addMonth()));
+        $this->assertSame(21, $dt->diffInWeekdays($dt->copy()->endOfMonth()));
     }
 
     public function testDiffInWeekdaysNegativeNoSign()
     {
         $dt = Carbon::createFromDate(2000, 1, 31);
-        $this->assertSame(20, $dt->diffInWeekdays($dt->copy()->startOfMonth()));
+        $this->assertSame(21, $dt->diffInWeekdays($dt->copy()->startOfMonth()));
     }
 
     public function testDiffInWeekdaysNegativeWithSign()
     {
         $dt = Carbon::createFromDate(2000, 1, 31);
-        $this->assertSame(-20, $dt->diffInWeekdays($dt->copy()->startOfMonth(), false));
+        $this->assertSame(-21, $dt->diffInWeekdays($dt->copy()->startOfMonth(), false));
     }
 
     public function testDiffInWeekendDaysPositive()
diff --git a/tests/CarbonImmutable/MacroTest.php b/tests/CarbonImmutable/MacroTest.php
index 2b92d761..78fe56df 100644
--- a/tests/CarbonImmutable/MacroTest.php
+++ b/tests/CarbonImmutable/MacroTest.php
@@ -15,6 +15,7 @@ namespace Tests\CarbonImmutable;
 
 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 @@ class MacroTest extends AbstractTestCaseWithOldNow
 
         $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/CarbonPeriod/CreateTest.php b/tests/CarbonPeriod/CreateTest.php
index 9d73dc36..4f104c3c 100644
--- a/tests/CarbonPeriod/CreateTest.php
+++ b/tests/CarbonPeriod/CreateTest.php
@@ -779,44 +779,4 @@ class CreateTest extends AbstractTestCase
         $periodClass = $this->periodClass;
         $periodClass::instance(Carbon::now());
     }
-
-    public function testMutability()
-    {
-        $this->assertSame(
-            [Carbon::class, Carbon::class, Carbon::class],
-            iterator_to_array(
-                CarbonPeriod::between(Carbon::today(), Carbon::today()->addDays(2))->map('get_class')
-            )
-        );
-        $this->assertSame(
-            [Carbon::class, Carbon::class, Carbon::class],
-            iterator_to_array(
-                CarbonPeriod::between(CarbonImmutable::today(), CarbonImmutable::today()->addDays(2))->map('get_class')
-            )
-        );
-        $this->assertSame(
-            [Carbon::class, Carbon::class, Carbon::class],
-            iterator_to_array(
-                CarbonPeriod::between('today', 'today + 2 days')->map('get_class')
-            )
-        );
-        $this->assertSame(
-            [CarbonImmutable::class, CarbonImmutable::class, CarbonImmutable::class],
-            iterator_to_array(
-                CarbonPeriodImmutable::between(Carbon::today(), Carbon::today()->addDays(2))->map('get_class')
-            )
-        );
-        $this->assertSame(
-            [CarbonImmutable::class, CarbonImmutable::class, CarbonImmutable::class],
-            iterator_to_array(
-                CarbonPeriodImmutable::between(CarbonImmutable::today(), CarbonImmutable::today()->addDays(2))->map('get_class')
-            )
-        );
-        $this->assertSame(
-            [CarbonImmutable::class, CarbonImmutable::class, CarbonImmutable::class],
-            iterator_to_array(
-                CarbonPeriodImmutable::between('today', 'today + 2 days')->map('get_class')
-            )
-        );
-    }
 }
diff --git a/tests/Fixtures/CarbonTimezoneTrait.php b/tests/Fixtures/CarbonTimezoneTrait.php
new file mode 100644
index 00000000..85779c39
--- /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);
+    }
+}