hack推理
您可能已经注意到,并非所有内容都被注释(例如局部变量)。然而,类型检查器仍然能够对类型不匹配做出合理的断言。它通过类型推断填充注释空白。
基本类型推理是基于给定的变量的已知类型来推断变量的类型。而类型检查器可以根据它所看到的注释以及程序的当前流程来做推理。
局部变量
局部变量没有注释类型。它们的类型是根据程序的流程推断的。实际上,您可以将不同类型的不同值分配给局部变量。当从函数或方法返回一个局部变量时,或者当它被比较或以其他方式与已知类型的变量进行比较时,是唯一重要的是本地变量的类型。
<?hh
namespace Hack\UserDocumentation\Types\Inference\Examples\LocalVariables;
function foo(): int {
$a = str_shuffle("ABCDEF"); // $a is a string
if (strpos($a, "A") === false) {
$a = 4; // $a is an int
} else {
$a = 2; // $a is an int
}
// Based on the flow of the program, $a is guaranteed to be an int at this
// point, so it is safe to return as an int.
return $a;
}
function run(): void {
var_dump(foo());
}
run();
Output
int(2)
未解决的类型
上面的例子显示了一个变量被分配给int两个分支的情况if/else。这使得类型检查器很容易确定变量可以而且只有int当它遇到该变量时return。
但是,如果不是将变量分配给条件的两个分支中的相同类型,那么会决定在每个分支中将其分配给其他类型?
<?hh
namespace Hack\UserDocumentation\Types\Inference\Examples\Unresolved;
function foo(): arraykey {
$a = str_shuffle("ABCDEF"); // $a is a string
if (strpos($a, "A") === false) {
$a = 4; // $a is an int
} else {
$a = "Hello"; // $a is string
}
// Based on the flow of the program, at this point $a is either an int or
// string. You have an unresolved type; or, to look at it another way, you
// the union of an int and string. So you can only perform operations that
// can be performed on both of those types.
var_dump($a + 20); // Nope. This isn't good for a string
$arr = array();
$arr[$a] = 4; // Fine. Since an array key can be an int or string
// arraykey is fine since it is either an int or string
return $a;
}
var_dump(foo());
Output
int(20)
string(5) "Hello"
在条件分支中,我们将相同的局部变量分配给两种类型之一。这使得局部变量未解决,这意味着typechecker知道变量可以是两种类型之一,但不知道哪一种。所以在这一点上,只允许在两种类型上执行的操作。
类属性
通常,类属性被注释,所以类型检查器最初知道它们的预期类型。但有时候,类型检查器必须做出一些假设,这使得推断进一步使用属性比本地变量复杂得多。
<?hh
namespace Hack\UserDocumentation\Types\Inference\Examples\Props;
class A {
protected ?int $x;
public function __construct() {
$this->x = 3;
}
public function setPropToNull(): void {
$this->x = null;
}
public function checkPropBad(): void {
// Typechecker knows $x isn't null after this validates
if ($this->x !== null) {
// We know that this doesn't call A::setPropToNull(), but the typechecker
// does not since inferences is local to the function.
// Commenting out so typechecker passes on all examples
does_not_set_to_null();
// We know that $x is still not null, but the typechecker doesn't
take_an_int($this->x);
}
}
public function checkPropGood(): void {
// Typechecker knows $x isn't null after this validates
if ($this->x !== null) {
// We know that this doesn't call A::setPropToNull(), but the typechecker
// does not since inferences is local to the function.
does_not_set_to_null();
// Use this invariant to tell the typechecker what's happening.
invariant($this->x !== null, "We know it is not null");
// We know that $x is still not null, and now the typechecker does too
// Could also have used a local variable here saying:
// $local = $this->x;
// takes_an_int($local);
take_an_int($this->x);
}
}
}
function does_not_set_to_null(): void {
echo "I don't set A::x to null" . PHP_EOL;
}
function take_an_int(int $x): void {
var_dump($x);
}
function run(): void {
$a = new A();
$a->checkPropBad();
$a->checkPropGood();
}
run();
Output
I don't set A::x to null
int(3)
I don't set A::x to null
int(3)
类型检查器仅将本机推送到功能。例如,如果一个函数调用另一个函数,它不会对函数外部可能发生什么的假设。这就是为什么typechecker会抛出一个错误,即使我们知道眼睛测试,没有null问题。
通过使用设置为属性值的局部变量或通过使用来解决此问题invariant()。