hershel-theodore-layton / expr-dump
Dump runtime values to typed Hack source code.
Installs: 10
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
Language:Hack
Requires
- hhvm: ^4.102
- hershel-theodore-layton/type-visitor: ^1
Requires (Dev)
- facebook/fbexpect: ^2.8
- hhvm/hacktest: ^2.3
- hhvm/hhvm-autoload: ^3.2
This package is auto-updated.
Last update: 2024-12-07 19:04:53 UTC
README
Dump runtime values to typed Hack source code.
Why do you need this?
Run code during build-time, codegen the values you were interested in back to Hack code, write it to a Hack source file, and you have build-time compute.
The following snippet1 embodies the essence of this library perfectly.
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, ); }
All data that enters your program at runtime is typed as mixed
, which is why
you must cast it to a typed value in order to process it. If you do this safely,
this incurs runtime costs. Doing it using an unsafe mechanism, such as
HH\FIXME\UNSAFE_CAST
or HH_FIXME[4110]
is trading correctness for performance.
But with burn_a_value_to_constant
, the data doesn't enter the program at runtime.
This data is already typed, so there is no need for casting.
Why the existing tools can not meet this need
In Hack, the same runtime value can represent two different types.
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 is unable to distinguish between these types, but Hack is, and will emit errors if you initialize a value with the wrong type, even if the runtime value would be exactly the same as the correct initializer.
const (int, int) A_TUPLE = vec[1, 2]; // type error
ExprDump\dump<reify T>(T $value): string
takes a type and a value.
This gives it enough information to represent shapes as shapes, tuples as tuples,
enums as enums, whilst not confusing them for dicts, vecs, and arraykeys.
Usage
// 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);
Note about the stability of this api
This library depends on HTL\TypeVisitor
to provide its functionality. TypeVisitor
depends on unstable Hack apis,
TypeStructure<T>
and \HH\ReifiedGenerics\get_type_structure<T>()
. For more
details, see stability.
This api has been unstable since 2016, so take this with a grain of salt.
In order to minimize the potential impact of a removal of these apis, you should
not use this library in places where the performance of bootstrapping the dumper
is critical. A far less performant variant of TypeVisitor
could be written, even
without these api affordances.