Typechecker: Special

There are times when you might want to just tell the typechecker to be quiet and move on. The most common case is when you know there is a typechecker error, you are not going to fix it for whatever reason, but you still want your code to be clean from typechecker errors when running hh_client ... e.g., you want the No errors! output.

There are a few ways to silence the typechecker. Some are more action-focused than the others.

HH_FIXME

HH_FIXME is the silencer you should normally use. It is meant to be used temporary reprieve from the wrath of typechecker errors so you can maintain an error-free codebase from the typechecker perspective.

The syntax for HH_FIXME is:

/* HH_FIXME[error #] string comment */
<block of code>

The error code is retrieved from the actual error message you receive when running hh_client. The string comment can be anything you want, but usually explains why you are using HH_FIXME to begin with.

<?hh // strict

namespace Hack\UserDocumentation\TypeChecker\Special\Examples\HHFixMe;

// This was the function before we went to strict mode and added annotations to
// to the annotating function. It was fine from the typechecker perspective.
/*

function annotating($x) {
  return $x > 5 ? "Hello" : $x;
}

*/

function annotating(?string $x): string {
  return $x === null ? "Hello" : "Bye";
}

function call_annotating(): void {
  /* HH_FIXME[4110] Will make 6 to "6" later */
  annotating(6);
}

function also_call_annotating(): void {
  /* HH_FIXME[4110] Will make true to "true" later */
  annotating(true);
}

/* HH_FIXME[1002] Will move this call to a partial file later */
call_annotating();
Output
Catchable fatal error: Argument 1 to Hack\UserDocumentation\TypeChecker\Special\Examples\HHFixMe\annotating() must be of type ?string, int given in /data/users/joelm/user-documentation/guides/hack/25-typechecker/07-special-examples/hhfixme.php on line 17

Let's assume that we were in partial mode and now we want to make this file strict, but we know that call sites will be affected by the annotation of the function because we did some questionable type conversions. We don't want to fix this yet or we don't know how to fix it (although you should fix before runtime since it will now be a runtime error). So we apply HH_FIXME to all the call sites that were affected by the change so that you or someone else knows that they need to be fixed.

Without HH_FIXME, you would have seen something like:

hhfixme.php:21:14,14: Invalid argument (Typing[4110])
  hhfixme.php:15:22,27: This is a string
  hhfixme.php:21:14,14: It is incompatible with an int
hhfixme.php:26:14,17: Invalid argument (Typing[4110])
  hhfixme.php:15:22,27: This is a string
  hhfixme.php:26:14,17: It is incompatible with a bool
hhfixme.php:29:1,15: Remove all toplevel statements except for requires (Parsing[1002])

In this example, you could have also placed the HH_FIXME comment on the function itself, with the same effect. But usually it is best to place HH_FIXME on the most specific block possible.

NOTE: You can have multiple HH_FIXME comments on a single line of code, representing the silencing of multiple Hack errors.

HH_IGNORE_ERROR

HH_IGNORE_ERROR is technically an alias of HH_FIXME, but can be used to provide better context to anyone looking at your code. While, HH_FIXME signals that some action will be taken to fix the Hack error you are silencing (either because of a bug in the Hack typechecker or your Hack code), HH_IGNORE_ERROR specifies that you are intentionally ignoring the error and do not plan to take any action to fix the error.

The syntax for HH_IGNORE_ERROR is:

/* HH_IGNORE_ERROR[error #] string comment */
<block of code>

A canonical use case for HH_IGNORE_ERROR is for linting. If you are using hh_client --lint, for example, you can suppress the linter errors as shown below.

<?hh

namespace Hack\UserDocumentation\Typechecker\Special\Examples\HHIE;

class A {
  // Normally if you use hh_client --lint hh_ignore_error.php without
  // the below HH_IGNORE_ERROR suppression comment, you will get a lint
  // error about using the uppercase TRUE.

  /* HH_IGNORE_ERROR[5001] We want to use uppercase TRUE */
  private bool $a = TRUE;
}

UNSAFE

UNSAFE (or synonymously UNSAFE_BLOCK) silences the typechecker too. But it isn't an action-to-be-taken silencing mechanism. When using UNSAFE, you are basically saying that you know this block of code is a problem, and you are just going to leave it that way.

The syntax for UNSAFE is:

// UNSAFE
<some block of code>
<?hh // strict

namespace Hack\UserDocumentation\TypeChecker\Special\Examples\Unsafe;

// This was the function before we went to strict mode and added annotations to
// to the annotating function. It was fine from the typechecker perspective.
/*

function annotating($x) {
  return $x > 5 ? "Hello" : $x;
}

*/

function annotating(?string $x): string {
  return $x === null ? "Hello" : "Bye";
}

function call_annotating(): void {
  // UNSAFE
  annotating(6);
}

function also_call_annotating(): void {
  // UNSAFE
  annotating(true);
}

/* HH_FIXME[1002] Will move this call to a partial file later */
call_annotating();
Output
Catchable fatal error: Argument 1 to Hack\UserDocumentation\TypeChecker\Special\Examples\Unsafe\annotating() must be of type ?string, int given in /data/users/joelm/user-documentation/guides/hack/25-typechecker/07-special-examples/unsafe.php on line 17

Using the example similar to HH_FIXME, we replaced two of the three with UNSAFE. Why not the third one? Well, UNSAFE isn't as powerful as HH_FIXME. UNSAFE cannot be used on top-level code.

Try not to use UNSAFE, opting for HH_FIXME instead. UNSAFE is less verbose and detailed than HH_FIXME, which might cause an issue for anyone maybe wanting to try to fix the problem later.

NOTE: It is very possible that UNSAFE may be deprecated or removed in the future. So all new silencing should be done with HH_FIXME.

UNSAFE_EXPR

UNSAFE_EXPR is similar to UNSAFE, except turns off the typechecker on a single expression rather than an entire block of code.

THe syntax for UNSAFE_EXPR is:

/* UNSAFE_EXPR */ <expression>

e.g.,

$foo = /* UNSAFE_EXPR */ $bar::baz();

Here is a somewhat convoluted, example showing the use of UNSAFE_EXPR. You probably wouldn't want to do this in the real world, but hopefully it is straightforward enough to hopefully get the point across.

<?hh

namespace Hack\UserDocumentation\Typechecker\Special\Examples\UnsafeExpr;

function foo(string $num): int {
  // Without UNSAFE_EXPR, couldn't add stringy number to a number and then
  // return that result later.
  $x = /* UNSAFE_EXPR */ $num + 2;
  echo "More statements here...\n";
  return $x;
}

function run(): void {
  var_dump(foo("1"));
}

run();
Output
More statements here...
int(3)

Note the /* */ style comments for UNSAFE_EXPR as opposed to the // for UNSAFE. This is important because // UNSAFE_EXPR will acutally be parsed as // UNSAFE, and may give you unexpected results.

Here is a more real-world example around dynamic property access. In Hack's strict mode, dynamic property access is not allowed. You can use UNSAFE_EXPR to get around that.

<?hh //strict

namespace Hack\UserDocumentation\Typechecker\Special\Examples\UnsafeDP;

class Ranking {
  public function __construct(
    protected int $first,
    protected int $second,
    protected int $third,
  ) {}

  public function copyFields(Ranking $from): Ranking {
    foreach (['first', 'second', 'third'] as $field) {
      /* UNSAFE_EXPR */ $this->$field = $from->$field;
    }
    return $this;
  }
}

/*
Without the UNSAFE_EXPR, you would get an error like this from the typechecker.

unsafe_expr_dynamic_prop.php:14:14,19: Dynamic method call (Naming[2011])
*/
Output