hack推理

2018-10-02 10:33 更新

您可能已經(jīng)注意到,并非所有內(nèi)容都被注釋(例如局部變量)。然而,類型檢查器仍然能夠?qū)︻愋筒黄ヅ渥龀龊侠淼臄嘌?。它通過(guò)類型推斷填充注釋空白。

基本類型推理是基于給定的變量的已知類型來(lái)推斷變量的類型。而類型檢查器可以根據(jù)它所看到的注釋以及程序的當(dāng)前流程來(lái)做推理。

局部變量

局部變量沒(méi)有注釋類型。它們的類型是根據(jù)程序的流程推斷的。實(shí)際上,您可以將不同類型的不同值分配給局部變量。當(dāng)從函數(shù)或方法返回一個(gè)局部變量時(shí),或者當(dāng)它被比較或以其他方式與已知類型的變量進(jìn)行比較時(shí),是唯一重要的是本地變量的類型。

<?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)

未解決的類型

上面的例子顯示了一個(gè)變量被分配給int兩個(gè)分支的情況if/else。這使得類型檢查器很容易確定變量可以而且只有int當(dāng)它遇到該變量時(shí)return。

但是,如果不是將變量分配給條件的兩個(gè)分支中的相同類型,那么會(huì)決定在每個(gè)分支中將其分配給其他類型?

<?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知道變量可以是兩種類型之一,但不知道哪一種。所以在這一點(diǎn)上,只允許在兩種類型上執(zhí)行的操作。

類屬性

通常,類屬性被注釋,所以類型檢查器最初知道它們的預(yù)期類型。但有時(shí)候,類型檢查器必須做出一些假設(shè),這使得推斷進(jìn)一步使用屬性比本地變量復(fù)雜得多。

<?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)

類型檢查器僅將本機(jī)推送到功能。例如,如果一個(gè)函數(shù)調(diào)用另一個(gè)函數(shù),它不會(huì)對(duì)函數(shù)外部可能發(fā)生什么的假設(shè)。這就是為什么typechecker會(huì)拋出一個(gè)錯(cuò)誤,即使我們知道眼睛測(cè)試,沒(méi)有null問(wèn)題。

通過(guò)使用設(shè)置為屬性值的局部變量或通過(guò)使用來(lái)解決此問(wèn)題invariant()。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)