From 447b656e6a5849c0811fb21c9e2ba3aa5b21edd3 Mon Sep 17 00:00:00 2001
From: Peter Fox <peter.fox@peterfox.me>
Date: Sat, 15 Feb 2025 12:18:44 +0000
Subject: [PATCH 1/2] Proof of concept

---
 src/Illuminate/Container/Container.php        | 68 +++++++++++++++++--
 .../Contracts/Container/Container.php         | 18 ++---
 tests/Container/ContainerTest.php             | 18 +++++
 3 files changed, 88 insertions(+), 16 deletions(-)

diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php
index c6e4be09db1b..03621b1d20fe 100755
--- a/src/Illuminate/Container/Container.php
+++ b/src/Illuminate/Container/Container.php
@@ -9,6 +9,7 @@
 use Illuminate\Contracts\Container\CircularDependencyException;
 use Illuminate\Contracts\Container\Container as ContainerContract;
 use Illuminate\Contracts\Container\ContextualAttribute;
+use Illuminate\Support\Collection;
 use LogicException;
 use ReflectionAttribute;
 use ReflectionClass;
@@ -268,15 +269,27 @@ public function isAlias($name)
     /**
      * Register a binding with the container.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string|null  $concrete
      * @param  bool  $shared
      * @return void
      *
      * @throws \TypeError
+     * @throws ReflectionException
      */
     public function bind($abstract, $concrete = null, $shared = false)
     {
+        if ($abstract instanceof Closure) {
+            $abstracts = $this->closureReturnTypes($abstract);
+            $concrete = $abstract;
+
+            foreach ($abstracts as $abstract) {
+                $this->bind($abstract, $concrete, $shared);
+            }
+
+            return;
+        }
+
         $this->dropStaleInstances($abstract);
 
         // If no concrete type was given, we will simply set the concrete type to the
@@ -381,7 +394,7 @@ public function callMethodBinding($method, $instance)
      * Add a contextual binding to the container.
      *
      * @param  string  $concrete
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string  $implementation
      * @return void
      */
@@ -393,7 +406,7 @@ public function addContextualBinding($concrete, $abstract, $implementation)
     /**
      * Register a binding if it hasn't already been registered.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string|null  $concrete
      * @param  bool  $shared
      * @return void
@@ -408,7 +421,7 @@ public function bindIf($abstract, $concrete = null, $shared = false)
     /**
      * Register a shared binding in the container.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string|null  $concrete
      * @return void
      */
@@ -420,7 +433,7 @@ public function singleton($abstract, $concrete = null)
     /**
      * Register a shared binding if it hasn't already been registered.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string|null  $concrete
      * @return void
      */
@@ -434,7 +447,7 @@ public function singletonIf($abstract, $concrete = null)
     /**
      * Register a scoped binding in the container.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string|null  $concrete
      * @return void
      */
@@ -448,7 +461,7 @@ public function scoped($abstract, $concrete = null)
     /**
      * Register a scoped binding if it hasn't already been registered.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string|null  $concrete
      * @return void
      */
@@ -1635,4 +1648,45 @@ public function __set($key, $value)
     {
         $this[$key] = $value;
     }
+
+    /**
+     * Get the class names / types of the return type of the given Closure.
+     *
+     * @param  \Closure  $closure
+     * @return list<class-string>
+     *
+     * @throws \ReflectionException
+     */
+    protected function closureReturnTypes(Closure $closure)
+    {
+        $reflection = new ReflectionFunction($closure);
+
+        if (
+            $reflection->getReturnType() === null ||
+            $reflection->getReturnType() instanceof \ReflectionIntersectionType
+        ) {
+            return [];
+        }
+
+        if (
+            $reflection->getReturnType() instanceof \ReflectionUnionType
+        ) {
+            $types = $reflection->getReturnType()->getTypes();
+        } else {
+            $types = [$reflection->getReturnType()];
+        }
+
+        return (new Collection($types))
+            ->reject(function (\ReflectionNamedType $type) {
+                return $type->isBuiltin();
+            })
+            ->reject(function (\ReflectionNamedType $type) {
+                return in_array($type->getName(), ['static', 'self']);
+            })
+            ->map(function (\ReflectionNamedType $type) {
+                return $type->getName();
+            })
+            ->values()
+            ->all();
+    }
 }
diff --git a/src/Illuminate/Contracts/Container/Container.php b/src/Illuminate/Contracts/Container/Container.php
index acd5dcf5eb9b..c00ddf5cafdb 100644
--- a/src/Illuminate/Contracts/Container/Container.php
+++ b/src/Illuminate/Contracts/Container/Container.php
@@ -56,7 +56,7 @@ public function tagged($tag);
     /**
      * Register a binding with the container.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string|null  $concrete
      * @param  bool  $shared
      * @return void
@@ -75,7 +75,7 @@ public function bindMethod($method, $callback);
     /**
      * Register a binding if it hasn't already been registered.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string|null  $concrete
      * @param  bool  $shared
      * @return void
@@ -85,7 +85,7 @@ public function bindIf($abstract, $concrete = null, $shared = false);
     /**
      * Register a shared binding in the container.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string|null  $concrete
      * @return void
      */
@@ -94,7 +94,7 @@ public function singleton($abstract, $concrete = null);
     /**
      * Register a shared binding if it hasn't already been registered.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string|null  $concrete
      * @return void
      */
@@ -103,7 +103,7 @@ public function singletonIf($abstract, $concrete = null);
     /**
      * Register a scoped binding in the container.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string|null  $concrete
      * @return void
      */
@@ -112,7 +112,7 @@ public function scoped($abstract, $concrete = null);
     /**
      * Register a scoped binding if it hasn't already been registered.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string|null  $concrete
      * @return void
      */
@@ -121,7 +121,7 @@ public function scopedIf($abstract, $concrete = null);
     /**
      * "Extend" an abstract type in the container.
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure  $closure
      * @return void
      *
@@ -134,7 +134,7 @@ public function extend($abstract, Closure $closure);
      *
      * @template TInstance of mixed
      *
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  TInstance  $instance
      * @return TInstance
      */
@@ -144,7 +144,7 @@ public function instance($abstract, $instance);
      * Add a contextual binding to the container.
      *
      * @param  string  $concrete
-     * @param  string  $abstract
+     * @param  \Closure|string  $abstract
      * @param  \Closure|string  $implementation
      * @return void
      */
diff --git a/tests/Container/ContainerTest.php b/tests/Container/ContainerTest.php
index fbe4414521fe..2ef9189cba19 100755
--- a/tests/Container/ContainerTest.php
+++ b/tests/Container/ContainerTest.php
@@ -40,6 +40,24 @@ public function testClosureResolution()
         $this->assertSame('Taylor', $container->make('name'));
     }
 
+    public function testAbstractCanBeBoundFromConcreteReturnType()
+    {
+        $container = new Container;
+        $container->bind(function (): IContainerContractStub|ContainerImplementationStub {
+            return new ContainerImplementationStub;
+        });
+        $container->singleton(function (): ContainerConcreteStub {
+            return new ContainerConcreteStub;
+        });
+
+        $this->assertInstanceOf(
+            IContainerContractStub::class,
+            $container->make(IContainerContractStub::class)
+        );
+
+        $this->assertTrue($container->isShared(ContainerConcreteStub::class));
+    }
+
     public function testBindIfDoesntRegisterIfServiceAlreadyRegistered()
     {
         $container = new Container;

From d0d835ffc144c4d9b24edafe8133e439505a7c5c Mon Sep 17 00:00:00 2001
From: Taylor Otwell <taylor@laravel.com>
Date: Mon, 17 Feb 2025 10:36:22 -0600
Subject: [PATCH 2/2] formatting

---
 src/Illuminate/Container/Container.php | 102 +++++++++++++------------
 1 file changed, 53 insertions(+), 49 deletions(-)

diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php
index 03621b1d20fe..bdfe7dd13cc5 100755
--- a/src/Illuminate/Container/Container.php
+++ b/src/Illuminate/Container/Container.php
@@ -15,7 +15,9 @@
 use ReflectionClass;
 use ReflectionException;
 use ReflectionFunction;
+use ReflectionIntersectionType;
 use ReflectionParameter;
+use ReflectionUnionType;
 use TypeError;
 
 class Container implements ArrayAccess, ContainerContract
@@ -280,14 +282,9 @@ public function isAlias($name)
     public function bind($abstract, $concrete = null, $shared = false)
     {
         if ($abstract instanceof Closure) {
-            $abstracts = $this->closureReturnTypes($abstract);
-            $concrete = $abstract;
-
-            foreach ($abstracts as $abstract) {
-                $this->bind($abstract, $concrete, $shared);
-            }
-
-            return;
+            return $this->bindBasedOnClosureReturnTypes(
+                $abstract, $concrete, $shared
+            );
         }
 
         $this->dropStaleInstances($abstract);
@@ -472,6 +469,54 @@ public function scopedIf($abstract, $concrete = null)
         }
     }
 
+    /**
+     * Register a binding with the container based on the given Closure's return types.
+     *
+     * @param  \Closure|string  $abstract
+     * @param  \Closure|string|null  $concrete
+     * @param  bool  $shared
+     * @return void
+     */
+    protected function bindBasedOnClosureReturnTypes($abstract, $concrete = null, $shared = false)
+    {
+        $abstracts = $this->closureReturnTypes($abstract);
+
+        $concrete = $abstract;
+
+        foreach ($abstracts as $abstract) {
+            $this->bind($abstract, $concrete, $shared);
+        }
+    }
+
+    /**
+     * Get the class names / types of the return type of the given Closure.
+     *
+     * @param  \Closure  $closure
+     * @return list<class-string>
+     *
+     * @throws \ReflectionException
+     */
+    protected function closureReturnTypes(Closure $closure)
+    {
+        $reflection = new ReflectionFunction($closure);
+
+        if ($reflection->getReturnType() === null ||
+            $reflection->getReturnType() instanceof ReflectionIntersectionType) {
+            return [];
+        }
+
+        $types = $reflection->getReturnType() instanceof ReflectionUnionType
+            ? $reflection->getReturnType()->getTypes()
+            : [$reflection->getReturnType()];
+
+        return (new Collection($types))
+            ->reject(fn ($type) => $type->isBuiltin())
+            ->reject(fn ($type) => in_array($type->getName(), ['static', 'self']))
+            ->map(fn ($type) => $type->getName())
+            ->values()
+            ->all();
+    }
+
     /**
      * "Extend" an abstract type in the container.
      *
@@ -1648,45 +1693,4 @@ public function __set($key, $value)
     {
         $this[$key] = $value;
     }
-
-    /**
-     * Get the class names / types of the return type of the given Closure.
-     *
-     * @param  \Closure  $closure
-     * @return list<class-string>
-     *
-     * @throws \ReflectionException
-     */
-    protected function closureReturnTypes(Closure $closure)
-    {
-        $reflection = new ReflectionFunction($closure);
-
-        if (
-            $reflection->getReturnType() === null ||
-            $reflection->getReturnType() instanceof \ReflectionIntersectionType
-        ) {
-            return [];
-        }
-
-        if (
-            $reflection->getReturnType() instanceof \ReflectionUnionType
-        ) {
-            $types = $reflection->getReturnType()->getTypes();
-        } else {
-            $types = [$reflection->getReturnType()];
-        }
-
-        return (new Collection($types))
-            ->reject(function (\ReflectionNamedType $type) {
-                return $type->isBuiltin();
-            })
-            ->reject(function (\ReflectionNamedType $type) {
-                return in_array($type->getName(), ['static', 'self']);
-            })
-            ->map(function (\ReflectionNamedType $type) {
-                return $type->getName();
-            })
-            ->values()
-            ->all();
-    }
 }