Psalm抽象类及其实现的类型推断疑问:模板类型不匹配
问题与解答
问题背景
以下PHP代码使用Psalm进行静态类型检查时,出现如下错误:
ERROR: InvalidArgument - 52:14 - Argument 1 of someFunction expects Pet<Cat|Dog>, but Cat|Dog provided
相关代码:
<?php /** * @template T */ abstract class Animal { } /** * @template T of Cat|Dog * @extends Animal<T> */ abstract class Pet extends Animal { abstract public function say(): string; } /** * @extends Pet<Cat> */ class Cat extends Pet { public function say(): string { return 'meow'; } } /** * @extends Pet<Dog> */ class Dog extends Pet { public function say(): string { return 'woof'; } } function someFunction(Pet $pet): void { echo $pet->say(); } $pet = rand(0,1) === 0 ? new Dog() : new Cat() ; someFunction($pet);
需要解决的疑问:
- 代码中的泛型模板定义是否存在问题?
- 为何Psalm不认为
Pet<Cat|Dog>与Cat|Dog是相同类型? - 除基线或抑制错误外,是否有其他解决办法?
解答
1. 模板定义是否存在问题?
原代码的泛型模板定义本身是正确的:
Animal类定义了基础模板TPet类继承Animal并约束模板T为Cat|DogCat和Dog类分别继承Pet并绑定具体的模板参数,符合泛型继承的规则
2. 类型不匹配的原因
Psalm的类型系统严格区分两种不同的类型:
Pet<Cat|Dog>:指单个Pet实例,其泛型参数是Cat与Dog的联合类型(即该实例的模板参数同时兼容两种类型)Cat|Dog:指Cat实例或Dog实例的联合类型,而Cat本质是Pet<Cat>的子类,Dog是Pet<Dog>的子类,因此这个联合类型等价于Pet<Cat>|Pet<Dog>
这两种类型并非同一概念:前者是单一泛型类的联合参数实例,后者是两个不同泛型实例的联合,因此Psalm判定类型不匹配。
3. 可行的解决办法
办法一:显式声明函数参数接受联合泛型类型
修改someFunction的类型注解,明确接受Pet<Cat>或Pet<Dog>的实例:
/** * @param Pet<Cat>|Pet<Dog> $pet */ function someFunction(Pet $pet): void { echo $pet->say(); }
这样Psalm就能识别Cat|Dog的联合类型符合参数要求。
办法二:显式标注变量类型为Pet
在$pet赋值前添加类型声明,强制将变量类型视为Pet(兼容所有符合Pet泛型约束的实例):
/** @var Pet $pet */ $pet = rand(0,1) === 0 ? new Dog() : new Cat() ;
此方式会让Psalm忽略具体的泛型参数差异,匹配函数的参数类型。
办法三:调整函数参数的类型声明
直接将函数参数类型改为Pet<Cat>|Pet<Dog>,替代原有的Pet:
function someFunction(Pet<Cat>|Pet<Dog> $pet): void { echo $pet->say(); }
这种方式最直接,明确告知Psalm参数接受的具体类型范围。
内容的提问来源于stack exchange,提问作者Storm
相关产品推荐
相关产品推荐

