将运行时值转储为类型化的Hack源代码。

v0.2.0 2024-02-15 06:53 UTC

This package is auto-updated.

Last update: 2024-09-26 18:47:38 UTC


README

将运行时值转储为类型化的Hack源代码。

为什么你需要这个?

在构建时运行代码,将你感兴趣的数据值代码生成回Hack代码,将其写入Hack源文件,你就有构建时的计算。

以下片段1完美地体现了这个库的精髓。

function burn_a_value_to_constant<reify T>(
  string $constant_name,
  T $value,
  ExprDump\DumpOptions $dumper_options = shape(),
)[]: string {
  $type_name = TypeVisitor\visit<T, _, _>(new TypeVisitor\TypenameVisitor());
  $serialized_value = ExprDump\dump<T>($value, $dumper_options);
  return Str\format(
    'const %s %s = %s;',
    $type_name,
    $constant_name,
    $serialized_value,
  );
}

所有在运行时进入你程序的数據都被类型化为mixed,这就是为什么你必须将其强制转换为类型值以进行处理的原因。如果你这样做是安全的,这会带来运行时的开销。使用不安全机制(如HH\FIXME\UNSAFE_CASTHH_FIXME[4110])会以性能为代价来换取正确性。但是,使用burn_a_value_to_constant,数据不会在运行时进入程序。这些数据已经是类型化的,因此不需要进行强制转换。

为什么现有工具无法满足这一需求

在Hack中,相同的运行时值可以表示两种不同的类型。

shape('name' => 'value') === dict['name' => 'value']; // true
shape('name' => 'value') === shape(SomeClass::CLASS_CONSTANT => 'value'); // true
vec[1, 2, 3] === tuple(1, 2, 3); // true
6 === MyEnum::ENUMERATOR; // true

HHVM无法区分这些类型,但Hack可以,如果你使用错误类型初始化值,即使运行时值将与正确初始化值完全相同,它也会发出错误。

const (int, int) A_TUPLE = vec[1, 2]; // 类型错误

ExprDump\dump<reify T>(T $value): string接受一个类型和一个。这为它提供了足够的信息来表示形状为形状、元组为元组、枚举为枚举,而不会将它们与字典、vec和arraykeys混淆。

用法

// Like var_dump(), with Hack syntax, all type information is lost.
//  - shapes become dicts
//  - tuples become vecs
//  - enums become ints / strings
ExprDump\dump<mixed>($x);

// There are multiple options that can be combined in any way.

// Encode Hack types and aliasses how you please.
// \App\user_id_from_int(4) types the value as an App\UserId.
ExprDump\dump<vec<shape('name' => string, 'friends' => vec<App\UserId>)>>(
  $users,
  shape(
    'custom_dumpers' => dict[
      App\UserId::class => (mixed $id)[] ==>
        '\App\user_id_from_int('.($id as int).')',
    ],
  ),
);

// Serialize enums as enumeration expression, such as Roles::ADMIN.
ExprDump\dump<vec<Roles>>(
  $roles,
  shape(
    'enum_definitions' => ExprDump\EnumDefinition::create(Roles::class),
  ),
);

// Keep class constant names in the dumped output.
// shape(\SomeClass::CONSTANT => 1) instead of shape('val' => 1)
ExprDump\dump<shape(SomeClass::CONSTANT => int)>(
  $shape,
  shape(
    'shape_key_namer' => (
      ?string $_parent_shape_name,
      arraykey $key,
    )[] ==> {
      $prefix = '\\'.SomeClass::class.'::';
      return dict<arraykey, string>[
        SomeClass::CONSTANT => $prefix.'CONSTANT',
      ][$key] ?? null;
    },
  ),
);

// Create a reusable dumper, call `->dump()`
$dumper = ExprDump\create_dumper<SomeType>(shape(
  // The options go here
));

$dumper->dump($value_of_that_given_type);

有关此API稳定性的说明

这个库依赖于HTL\TypeVisitor来提供其功能。TypeVisitor依赖于不稳定的Hack API,TypeStructure<T>\HH\ReifiedGenerics\get_type_structure<T>()。有关更多详细信息,请参阅稳定性。这个API自从2016年以来一直不稳定,所以请谨慎使用。

为了最小化删除这些API可能带来的影响,你不应该在启动dumper性能至关重要的地方使用这个库。即使没有这些API支持,也可以编写一个性能较差的TypeVisitor变体。