Skip to content

Resolve inherited conditional types once the template type is bound - #6882

Open
APX403 wants to merge 1 commit into
phpstan:2.3.xfrom
APX403:resolve-inherited-conditional-types
Open

APX403 wants to merge 1 commit into
phpstan:2.3.xfrom
APX403:resolve-inherited-conditional-types

Conversation

@APX403

@APX403 APX403 commented Oct 29, 2026

Copy link
Copy Markdown

Closes phpstan/phpstan#15128

A child class that binds the parent's template type and overrides a method without a PHPDoc of its own gets an error about the parent's PHPDoc. The issue's snippet, shortened:

/**
 * @template T of int|string
 */
class A {
	/**
	 * @return (T is int ? string : int)
	 */
	public function foo(): int|string { /* ... */ }
}

/**
 * @extends A<string>
 */
class B extends A {
	#[Override]
	public function foo(): int { /* ... */ }
}

PHPStan reports on B::foo():

Conditional return type uses subject type string which is not part of PHPDoc @template tags.

B::foo() inherits the tags through ResolvedPhpDocBlock::resolveTemplateTypeInTag(), which replaces T with string but keeps the conditional type, so ConditionalReturnTypeRuleHelper checks (string is int ? string : int) as if it had been written on B::foo(). An inherited @param tag, an interface method and an abstract trait method get the same error, and a child declared with @template U of int and @extends A<U> gets Condition "U of int is int" in conditional return type is always true.

As suggested in the issue, the inherited conditional type is now resolved once its condition no longer depends on a template type, or the bound of that template type decides it. A static subject stays as it is, since it depends on the class the method is called on, and so does a conditional type that would resolve to another one, including a conditional on a parameter, because MethodSignatureRule compares it with the parent's conditional type branch by branch.

A conditional on a parameter, such as ($val is int ? string : int) with an inherited @param T $val, is left as it is. Whether it is decided depends on the parameter's final type, which a = null default or the native type can still change, and call sites already resolve it against the argument.

A method that overrides a generic parent's method without a PHPDoc of its
own inherits the parent's tags with the template types replaced by the
child's type arguments. The conditional type stayed a conditional type
with a concrete subject, so MethodConditionalReturnTypeRule reported
`(string is int ? string : int)` on the child as if it had been written
there.

A static subject is kept, since it depends on the class the method is
called on, and so is a conditional type that resolves to another one,
including a conditional on a parameter, because MethodSignatureRule
compares it with the parent's conditional type branch by branch.

Closes phpstan/phpstan#15128
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Conditional return type uses subject type string which is not part of PHPDoc @template tags.

1 participant