hershel-theodore-layton / expr-dump
将运行时值转储为类型化的Hack源代码。
Requires
- hhvm: ^4.102
- hershel-theodore-layton/type-visitor: <1
Requires (Dev)
- facebook/fbexpect: ^2.8
- hhvm/hacktest: ^2.3
- hhvm/hhast: ^4.102
- hhvm/hhvm-autoload: ^3.2
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_CAST
或HH_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
变体。