Repository: carthage-software/mago (Rust)

Issue:
bug(analyzer): invalid type recognition inside traits
### 🐞 Describe the Bug

in the provided code, there are multiple errors reported by mago analyze, i'm pretty sure that all of them are invalid

### 🔄 Steps to Reproduce

-

### ⚙️ Configuration (mago.toml)

```toml
-
```

### 📜 Command Output

```shell
> MAGO_LOG=trace mago analyze                                               16:22:58
DEBUG Sourcing global configuration from /home/bleksak/mago.toml.
DEBUG Sourcing workspace configuration from /media/big_dick/dev/mago-test/mago.toml.
DEBUG Configuration specifies 16 threads.
DEBUG Configuration specifies a stack size of 12582912 bytes.
error[invalid-clone]: Invalid `clone` operation on non-cloneable type `Responseable&static`.
   ┌─ test2.php:18:19
   │
18 │         $cloned = clone $this;
   │                         ^^^^^ This expression is not an object
   │
   = The `clone` operator can only be used on objects. This will cause a fatal `Error`.
   = Help: Remove the `clone` operator or ensure the expression evaluates to a cloneable object instance.

error[impossible-assignment]: Invalid assignment: the right-hand side has type `never` and cannot produce a value.
   ┌─ test2.php:18:3
   │
18 │         $cloned = clone $this;
   │         ^^^^^^^ Cannot assign a `never` type value here
   │
   = An expression with type `never` is guaranteed to exit, throw, or loop indefinitely.
   = Help: This assignment is unreachable because the right-hand side never completes. Remove the assignment or refactor the preceding code.

error[invalid-property-access]: Attempting to access a property on a non-object type (`never`).
   ┌─ test2.php:19:12
   │
19 │         $cloned->response->status = $code;
   │         -------  ^^^^^^^^ Cannot access property here
   │         │
   │         This expression has type `never`

error[null-property-access]: Attempting to access a property on an expression that is always `null`.
   ┌─ test2.php:19:3
   │
19 │         $cloned->response->status = $code;
   │         ^^^^^^^^^^^^^^^^^ This expression is always `null` here
   │
   = In PHP, this will raise a warning and the expression will evaluate to `null`.
   = Help: This code path appears to be an error. You should either ensure this expression can be a valid object or remove the property access entirely.

error[never-return]: Cannot return value with type 'never' from this function.
   ┌─ test2.php:20:10
   │
20 │         return $cloned;
   │         -------^^^^^^^-
   │         │      │
   │         │      This expression has type 'never'.
   │         This return statement is effectively unreachable.
   │
   = A 'never' return type indicates that a function is guaranteed to exit the script, throw an exception, or loop indefinitely. Code following a call to such a function is unreachable.
   = Help: Since the preceding expression never returns, this 'return' statement cannot be reached. You can likely remove the 'return' keyword entirely.

error: found 5 issues: 5 error(s)
```

### 📂 PHP Code Sample (If Applicable)

```php
<?php

declare(strict_types=1);

interface ResponseInterface {}
class Response implements ResponseInterface {
    public int $status = 200;
}

trait Responseable
{
	private Response $response;

	public function withStatus(
		int $code,
	): static
	{
		$cloned = clone $this;
		$cloned->response->status = $code;
		return $cloned;
	}

}
```

### 🖥️ Operating System

Linux

### 📦 How did you install Mago?

Built from Source

### 📝 Additional Context

_No response_


Discussion:

Patch:
diff --git a/crates/analyzer/src/expression/clone.rs b/crates/analyzer/src/expression/clone.rs
index 9999e98f1..39b6349a3 100644
--- a/crates/analyzer/src/expression/clone.rs
+++ b/crates/analyzer/src/expression/clone.rs
@@ -1,6 +1,7 @@
 use std::rc::Rc;
 
 use mago_codex::class_or_interface_exists;
+use mago_codex::trait_exists;
 use mago_codex::ttype::TType;
 use mago_codex::ttype::atomic::TAtomic;
 use mago_codex::ttype::atomic::object::TObject;
@@ -51,7 +52,9 @@ impl<'ast, 'arena> Analyzable<'ast, 'arena> for Clone<'arena> {
                         invalid_clone_atomics.push(atomic_type);
                     }
                     TObject::Named(named_object) => {
-                        if !class_or_interface_exists(context.codebase, &named_object.name) {
+                        if !trait_exists(context.codebase, &named_object.name)
+                            && !class_or_interface_exists(context.codebase, &named_object.name)
+                        {
                             invalid_clone_atomics.push(atomic_type);
                         } else {
                             has_cloneable_object = true;


Test patch:
diff --git a/crates/analyzer/tests/cases/issue_415.php b/crates/analyzer/tests/cases/issue_415.php
new file mode 100644
index 000000000..47d27045b
--- /dev/null
+++ b/crates/analyzer/tests/cases/issue_415.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+interface ResponseInterface {}
+class Response implements ResponseInterface
+{
+    use Responseable;
+
+    public int $status = 200;
+}
+
+trait Responseable
+{
+    private Response $response;
+
+    public function withStatus(int $code): static
+    {
+        $cloned = clone $this;
+        $cloned->response->status = $code;
+        return $cloned;
+    }
+}
diff --git a/crates/analyzer/tests/mod.rs b/crates/analyzer/tests/mod.rs
index 603d45d98..e11a7f1c9 100644
--- a/crates/analyzer/tests/mod.rs
+++ b/crates/analyzer/tests/mod.rs
@@ -180,6 +180,7 @@ test_case!(issue_391);
 test_case!(issue_393);
 test_case!(issue_396);
 test_case!(issue_400);
+test_case!(issue_415);
 test_case!(issue_417);
 
 #[test]