Skip to content

Commit bdb2ced

Browse files
committed
Fix property type narrowing after assignment to $this->prop
1 parent 10b7ce5 commit bdb2ced

25 files changed

Lines changed: 1385 additions & 586 deletions

docs/CHANGELOG.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
### Fixed
2323

2424
- **Short-name collisions eliminated project-wide.** Two unrelated classes sharing a short name (e.g. `App\Carbon` vs `Vendor\Carbon`) were treated as identical across diagnostics, go-to-implementation, trait conflict resolution, throws analysis, and catch-clause filtering. Argument type mismatch diagnostics now use the class hierarchy walker for all Traversable, Stringable, and Countable checks instead of matching short names or suffixes.
25+
- **Lowercase built-in class names recognized as subtypes of `object`.** PHP classes with lowercase names (e.g. `finfo`, `stdClass` in stubs) are now correctly treated as class-like names in subtype checks, where previously the uppercase-first heuristic rejected them.
26+
- **Union-typed method calls no longer lose resolution on second occurrence.** When a variable has a union type (e.g. `ClassA|ClassB`) and a method exists only on a later branch, repeated calls in the same file now resolve correctly instead of returning a stale negative cache entry from the first branch.
2527
- **Transitive interface inheritance.** A class implementing an interface that extends another interface (e.g. `ResponseInterface` extends `MessageInterface`) is now correctly recognized as a subtype of the parent interface. Previously only directly-declared interfaces were checked.
2628
- **Fluent method chains in namespaced classes.** Chaining methods that return `static` or `self` now resolves correctly across namespaces, fixing broken completion and false diagnostics on builder patterns.
27-
- **Property narrowing after guard clauses.** `if (!$this->prop instanceof Foo) { return; }` now correctly narrows `$this->prop` to `Foo` in subsequent code. Previously all accesses to the same property shared a single cached resolution, ignoring guards.
29+
- **Property narrowing after guard clauses.** `if (!$this->prop instanceof Foo) { return; }` now correctly narrows `$this->prop` to `Foo` in subsequent code. Previously all accesses to the same property shared a single cached resolution, ignoring guards. Assignment-based narrowing (e.g. `$this->mock = $this->createMock(Foo::class)`) is guarded so it can only narrow to a subtype of the declared property type, preventing widening assignments from discarding type information.
2830
- **Hover on generic methods shows concrete return type.** Hovering over a method inherited from a generic trait now shows the substituted return type (e.g. `Model|null`) instead of the raw template parameter name. Applies to both method return types and property type hints on generic classes.
2931
- **False-positive undefined variable for by-reference parameters in namespaces.** Passing an undefined variable to a by-reference parameter no longer produces a false "undefined variable" diagnostic when the function or method is in a namespace. Instance method calls with by-reference parameters are also handled.
3032
- **Missing blank line between namespace and first import.** When auto-importing a class into a file that has a namespace declaration but no existing `use` statements and no blank line after the namespace, the inserted `use` statement is now preceded by a blank line (PSR-12 convention). Files that already have a blank line after the namespace are left unchanged.
@@ -33,9 +35,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3335
- **Exception types in `catch` clauses matched incorrectly across namespaces.** Thrown exception types are now matched against `catch` clause types regardless of whether short names or fully-qualified names are used. The `@throws` tag comparison in docblock update actions now resolves names through the class loader, fixing false "missing @throws" suggestions for root-namespace exceptions (e.g. `RuntimeException`) in namespaced files without an explicit `use` import.
3436
- **False "undefined variable" diagnostics for by-reference parameters in inherited static methods and constructors.** By-reference parameters in parent class static methods and constructors are now correctly resolved when called on a child class, preventing false diagnostics for variables defined via the call. Class lookup for by-reference resolution now tries the fully-qualified name first, preventing incorrect matches when multiple classes share a short name across namespaces.
3537
- **Nested `match(true)` expressions.** Multiple `match(true)` expressions sharing narrowed variable names no longer produce incorrect diagnostics.
36-
- **False-positive type errors on generic class methods resolved through return types.** When a method returns a generic class (e.g. `@return HasMany<Translation, Tag>`), calling a method on the result whose parameter references a class-level template parameter (e.g. `@param TRelatedModel $model`) no longer produces a spurious "expects TRelatedModel, got Translation" diagnostic. The return type's generic arguments are now preserved and used for template substitution.
38+
- **False-positive type errors on generic class methods resolved through return types.** When a method returns a generic class (e.g. `@return HasMany<Translation, Tag>`), calling a method on the result whose parameter references a class-level template parameter (e.g. `@param TRelatedModel $model`) no longer produces a spurious "expects TRelatedModel, got Translation" diagnostic. The return type's generic arguments are now preserved and used for template substitution. Covers instance methods, static methods, standalone functions, inherited methods, trait methods, and nullable generic return types.
3739
- **Variables prefixed with `$this` receiving stale type information.** Variables like `$thisItem` or `$thisValue` no longer receive stale type information from `$this`-related narrowing.
38-
- **False-positive type errors on generic class methods and templated static methods.** Class-level `@template` parameters (e.g. `Collection<User>`) are now substituted into method parameter types before checking argument compatibility. Method-level `@template` parameters (e.g. PHPUnit's `assertSame`) are resolved from call-site argument types, covering literals, variables, enum cases, property accesses, and method call return types.
40+
- **False-positive type errors on generic class methods and templated static methods.** Class-level `@template` parameters (e.g. `Collection<User>`) are now substituted into method parameter types before checking argument compatibility. Method-level `@template` parameters (e.g. PHPUnit's `assertSame`) are resolved from call-site argument types, covering literals, variables, enum cases, property accesses, and method call return types. Argument type checking correctly rejects `float` passed where an int-range type like `positive-int` is expected.
41+
- **Unbound template parameters resolved to their declared bounds.** When a template parameter cannot be inferred from call-site arguments (e.g. `new Collection()` without a generic annotation, `collect()` with no arguments, or a method-level `@template` with no matching parameter), the parameter is now replaced with its declared upper bound (`@template T of Foo` resolves to `Foo`) or `mixed` when no bound exists. The same resolution now applies to interface-level template parameters: when a class implements a generic interface without providing `@implements` generics, the interface's template params are substituted with their bounds instead of leaking as raw names into inherited method signatures. Previously the raw template name leaked through as a type (e.g. `TValue`, `TReduceReturnType`, `TKey`), causing false "expects TValue, got string" diagnostics on every call site that used the unresolved generic.
42+
- **Foreach key variable type from static properties and scalar keys.** Iterating over a static property with a generic array annotation (e.g. `self::$map` typed as `array<string, string>`) now correctly resolves the foreach key variable's type. Static property access (`self::$prop`, `static::$prop`, `ClassName::$prop`) is resolved through the class's property type hints. Scalar key types (`string`, `int`, `array-key`) are now extracted from generic iterables for key variable resolution, where previously only class-typed keys were handled.
43+
- **Foreach loop prescan no longer leaks reassigned types into same-statement RHS.** When a foreach key variable is reassigned inside the loop body (e.g. `$type = DeviationType::from($type)`), the `$type` argument in `from()` now correctly resolves to the foreach key type (`string`) instead of the reassigned type (`DeviationType`). The loop-body prescan results are deferred until after the normal body walk completes.
3944

4045
## [0.7.0] - 2026-04-08
4146

0 commit comments

Comments
 (0)