hershel-theodore-layton / static-type-assertion-code-generator
与类型测试 `as` 表达式等价的代码生成函数。
Requires
- hhvm: ^4.102
- hershel-theodore-layton/type-visitor: ^0.2
Requires (Dev)
- facebook/fbexpect: ^2.8
- hhvm/hacktest: ^2.3
- hhvm/hhast: ^4.102
- hhvm/hhvm-autoload: ^3.2
README
代码生成函数,等同于类型测试 as
表达式。
为什么选择 static-type-assertion-code-generator?
编写这个库时,主要考虑了以下三个因素,按重要性排序:
- 不惜一切代价确保正确性,向 Hack 证明你正在做正确的事情。
- 通过尽可能少的工作来实现尽可能快的速度。
- 假设运行时类型是正确的,针对最佳路径进行优化。
从外部世界到达 Hack 的每一份数据,无论是 apc、数据库、json、POST 数据等,都是无类型的。在一个正确的 Hack 程序中,你需要在做任何有用的事情之前,将这些数据转换为有类型的数据。手动进行此验证是乏味的工作,可以轻松自动化。为你生成的代码是我所知的最快的纯 Hack 方法,可以将无类型数据转换为有类型数据。
生成的代码不依赖于任何库函数。因此,建议您将该库作为开发依赖项安装。您应从生产构建中排除调用代码生成器的文件。这样,当库代码在生产构建中移除时,Hack 不会报错。
当前版本的 static-type-assertion-code-generator 仅支持验证以下类型
arraykey
(a, b)
bool
dict<_, _>
float
int
keyset<_>
mixed
nonnull
null
num
shape(...)
string
vec<_>
vec_or_dict<...>
可以使用别名和传递给 from_type<T>()
的 $type_alias_asserters
参数来添加对其他类型的支持。
如果这个 static-type-assertion-code-generator 不能满足您的所有需求,我建议您考虑使用 TypeAssert。
这个库深受 TypeAssert 的启发。TypeAssert 做出了不同的设计决策,并且是为了不同的目的而编写的。TypeAssert 在运行时检查具体化的泛型,并组合对象来模拟对您提供的类型的完全正确的断言。TypeAssert 几乎支持 Hack 中的所有类型,但其中一些断言并不正确。性能在 TypeAssert 开发者的心中很重要,但易用性和友好的错误信息对他们来说更重要。TypeAssert 通常是一个不错的选择,因为它在运行时类型和预期类型不匹配时提供了非常有帮助的错误信息,这将在调试时极大地帮助您。
from_type<T>() 中的 $type_alias_asserters
默认情况下,static-type-assertion-code-generator 会递归到底层原语,并在一个函数中生成所有代码。这对于大型形状类型来说,代码会很快变得庞大。您可以通过指向将验证特定类型别名的函数来减小生成的代码的大小。我在 基准测试 中使用了这种技术,以去重 "entities"
和 "user"
键,这两个键在 json 中都出现了两次,并且在这两个地方的结构完全相同。
使代码保持简洁可以减少JIT需要单独优化的内容数量。JIT为热点代码预留了一部分空间。如果你经常通过这些函数运行大量数据,JIT会将它们放入热点部分。保持代码简洁可以使更多函数适应热点区域。
新类型
Hack有type
和newtype
别名。使用type
关键字创建的别名等同于其右侧内容。因此,对于type A = int
,你可以在期望int
的地方传递A
,在期望A
的地方传递int
。使用newtype
关键字创建的别名(部分)是透明的。因此,对于newtype UserID = int
,你不能简单地使用$x as int
并完成操作。我们需要你提供执行此提升操作的函数。你可能已经有一个将某些内容提升为UserID
的函数。你可以将此函数名称传递给静态类型断言代码生成器,它将生成一个带有mixed
值的函数调用。验证类型*和UserID
的应用特定不变量取决于你。如果你没有为UserID
提供函数,代码生成将失败,因为它不知道你的提升函数的名称。
*注意,你可以创建你的newtype
的两个副本。newtype TActualType = TRawType; type TRawType = <complex>
。然后,你可以在将TActualType
提升为的函数中使用TRawType
代码生成,以节省一些精力。
为什么我必须为枚举提供断言器?
Hack和hhvm无法就as
在枚举上的工作方式达成一致。在hhvm版本3.28.0中引入is
和as
时,团队注意到is
和as
将执行整数键转换,以与BuildInEnum::isValid()
保持主要兼容性。给定枚举enum ThereCanBeOnly: int { ONE = 1; }
,as
表达式'1' as ThereCanBeOnly
将产生string(1) "1"
。这不是正确的,因为接受ThereCanBeOnly
的函数在提供字符串"1"
时将抛出TypeError
。我希望hhvm团队可以弃用并删除此行为,因为它真的很令人困惑。一旦这种行为被删除,我将更新此库。如果您的hhvm版本满足合理as
检查的最小版本要求,将生成默认的as
检查。
在此之前,你必须为枚举提供自己的断言函数。你了解$x as YourEnum
是否是一个安全的实现(对于YourEnum
,整数键转换不会发生)。如果它包含那些值,你可以使用C\contains_key(YourEnum::getNames(), $x)
进行验证,但前提是你的枚举没有重复值。如果它有重复值,你可以使用C\contains(YourEnum::getValues(), $x)
。你也可以决定使用YourEnum::assert()
,它实际上会将整数转换为字符串,反之亦然。由于这些自由度,默认实现是停止代码生成。