PHPStan 错误标识符 conditionalType.subjectNotFound 完全解读:条件返回类型的 subject 必须引用模板类型或参数
【免费下载链接】phpstanPHP Static Analysis Tool - discover bugs in your code without running it!项目地址: https://gitcode.com/gh_mirrors/ph/phpstan
导读
conditionalType.subjectNotFound是 PHPStan 在分析 PHPDoc 条件返回类型(conditional return type)时报告的一类静态分析错误:当@return中的条件表达式使用了一个既未通过@template声明、也未通过$param is Type语法引用参数的裸类型作为 subject 时,PHPStan 将无法求值该条件并给出此标识符。本文基于 conditionalType.subjectNotFound.md 官方错误文档,结合错误标识符注册表与同类文档,从触发场景、报错原理、修复方案三个层面完整拆解这一错误,帮助你正确书写可被 PHPStan 求值的高阶条件类型。
错误概览
- 错误标识符:
conditionalType.subjectNotFound - shortDescription(官方一句话描述):
Conditional return type subject does not reference a template or parameter.(条件返回类型的 subject 未引用模板类型或参数) - ignorable:
true(可通过ignoreErrors配置忽略) - 所属规则:
PHPStan\Rules\PhpDoc\FunctionConditionalReturnTypeRule与PHPStan\Rules\PhpDoc\MethodConditionalReturnTypeRule(分别负责函数与方法层面 PHPDoc 条件返回类型的校验),二者最终都汇总到ConditionalReturnTypeRuleHelper中的同一处判定逻辑。这一映射关系可在错误标识符注册表 website/src/errorsIdentifiers.json(conditionalType.subjectNotFound条目)中确认。
触发示例:一个无法求值的条件返回类型
以下 PHP 代码会触发conditionalType.subjectNotFound:
<?php declare(strict_types = 1); /** * @return (int is string ? true : false) */ function doFoo(): bool { return true; }关键点在于@return中的(int is string ? true : false)。这是 PHPStan 的条件返回类型语法:(subject is Type ? true分支 : false分支),其含义是"当 subject 类型为 string 时返回true,否则返回false"。但这里的 subject 是裸类型int,它既不是函数签名中声明的模板类型,也没有通过$value is string这类参数引用语法绑定到任何真实输入,因此 PHPStan 没有任何依据去判断"这个int到底是不是string",条件无从求值,于是报告conditionalType.subjectNotFound。
值得注意的是,上述写法在语法层面是合法的(条件返回类型允许使用任意类型表达式),问题出在语义层面:条件的主体(subject)必须能够映射到调用处的实际类型参数或函数入参,裸类型int无法在调用点被实例化。
为什么会报告这个错误
PHPStan 官方文档在"Why is it reported?"一节中给出了直接解释:
A conditional return type uses a subject type (
int) that does not reference any@templatetag or function parameter. The subject of a conditional return type must be either a template type declared via@templateor a parameter reference using the$param is Typesyntax.(条件返回类型使用了未引用任何
@template标签或函数参数的 subject 类型int。条件返回类型的 subject 必须是经由@template声明的模板类型,或使用$param is Type语法引用参数。)
展开来说,条件返回类型之所以存在,本质是为了在调用方视角根据传入的具体类型"分流"返回类型。要让分流成为可能,subject 必须具备以下两种身份之一:
- 模板类型(template type):通过
@template T声明的类型变量。调用方传入Foo时,PHPStan 将T实例化为Foo,进而求值T is Bar条件并挑选分支; - 参数引用(parameter reference):通过
$param is Type直接引用函数/方法的某个参数,条件基于该参数的运行时类型求值。
而示例中的int只是一个孤立的类型名,既不是模板变量也没有绑定参数,因此它在任何调用点都保持原样,条件既不会被实例化也不会被求值。此时(int is string ? true : false)这种条件表达就退化成了一段死逻辑——无论调用方传什么,分支都无从选择。
如何修复
官方文档给出了两条修复路径,分别对应上述两种合法 subject 身份。
方案一:条件依赖参数时,改用$param is Type语法
如果条件本意是"根据入参的实际类型决定返回类型",就把 subject 从裸类型改为参数引用:
<?php declare(strict_types = 1); /** + * @param string|int $value - * @return (int is string ? true : false) + * @return ($value is string ? true : false) */ -function doFoo(): bool +function doFoo($value): bool { - return true; + return is_string($value); }修复要点:
@param string|int $value声明入参为string|int联合类型,为条件提供可判定的类型空间;@return ($value is string ? true : false)将 subject 换成$value,PHPStan 在调用处根据实参的实际类型求值:传入string返回类型收敛为true,传入int返回类型收敛为false;- 函数体内使用
is_string($value)让运行时行为与 PHPDoc 声明保持一致。
方案二:打算使用模板类型时,用@template声明
如果条件本意是"针对任意泛型输入做类型分流",就先声明模板类型再把 subject 指向它:
<?php declare(strict_types = 1); /** + * @template T - * @return (int is string ? true : false) + * @param T $value + * @return (T is string ? true : false) */ -function doFoo(): bool +function doFoo($value): bool { - return true; + return is_string($value); }修复要点:
@template T声明一个类型变量;@param T $value将模板类型绑定到参数,使T能够从调用实参中实例化;@return (T is string ? true : false)的 subject 是模板类型T:调用方传入string时,PHPStan 实例化T = string并命中true分支;传入其他类型时落入false分支。
两条方案的取舍
| 维度 | 方案一(参数引用) | 方案二(模板类型) |
|---|---|---|
| subject 写法 | $value is Type | T is Type |
是否需要@template | 不需要 | 必须声明 |
| 适用场景 | 入参类型集合已知、希望按运行时类型分流 | 需要泛型抽象、类型由调用方决定 |
| 对参数的要求 | 参数需有可判定的类型空间(如联合类型) | 参数类型声明为T |
两条路径的共同原则是:让 subject 能够被调用点的真实类型实例化,这是条件返回类型能够求值的前提。
同类错误辨析:与 conditionalType.alwaysTrue / alwaysFalse 的关系
conditionalType.subjectNotFound属于 PHPStan 的conditionalType.*错误标识符族。为帮助区分,这里一并说明同族另外两个标识符:
- conditionalType.alwaysTrue:subject 合法(引用了参数),但条件恒为真。例如
@param int $i搭配@return ($i is int ? non-empty-array : array),$i原生类型就是int,条件毫无意义,应直接返回true分支类型或放宽参数类型; - conditionalType.alwaysFalse:subject 合法,但条件恒为假。例如
@param int $i搭配@return ($i is string ? non-empty-array : array),int永远不可能是string,通常意味着 PHPDoc 注解写错了,应修正被测试的类型或放宽参数类型。
三者对比可总结为:
subjectNotFound:subject 本身不合法(未引用模板或参数),无法求值;alwaysTrue/alwaysFalse:subject 合法但条件退化(恒真/恒假),可以求值但没有意义。
判断顺序是:先确认 subject 是否引用了模板类型或参数(解决subjectNotFound),再确认条件在该类型空间下是否恒定(解决alwaysTrue/alwaysFalse)。
如何定位与验证:错误标识符的源码溯源
本仓库虽未直接包含 PHPStan 引擎本体(引擎代码位于独立的phpstan-src仓库),但错误标识符注册表 website/src/errorsIdentifiers.json 提供了可靠的溯源信息:在conditionalType.subjectNotFound条目下,FunctionConditionalReturnTypeRule与MethodConditionalReturnTypeRule均映射到phpstan-src中src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php的第 91 行附近(该 helper 文件同时承载了conditionalType.alwaysTrue/conditionalType.alwaysFalse的判定,分别位于 L121 附近)。从源码结构可以推断,函数规则与方法规则共用同一份 helper 逻辑来校验条件返回类型的 subject 合法性,这也解释了为什么同一个标识符会由两个规则类共同产生。
你可以在自己的项目中验证修复效果:运行vendor/bin/phpstan analyse,确认修复后不再报告conditionalType.subjectNotFound;再尝试为修复后的函数传入不同类型的实参(如string与int),观察 PHPStan 推断出的返回类型是否随之在true/false之间切换,从而直观理解条件返回类型的分流机制。
小结
conditionalType.subjectNotFound的核心教训只有一句话:条件返回类型的 subject 必须引用@template模板类型或$param is Type参数引用,裸类型不能作为求值主体。遇到该错误时,先判断条件的真实意图——"按入参类型分流"就用方案一(参数引用),"按泛型类型分流"就用方案二(@template),修复后即可让 PHPStan 在调用点正确实例化并求值你的条件返回类型。
【免费下载链接】phpstanPHP Static Analysis Tool - discover bugs in your code without running it!项目地址: https://gitcode.com/gh_mirrors/ph/phpstan
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考