Skip to content

Commit

Permalink
invariant. skip reflection.
Browse files Browse the repository at this point in the history
  • Loading branch information
scobb committed Dec 19, 2023
1 parent 0010d7c commit b19a901
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 169 deletions.
340 changes: 173 additions & 167 deletions src/Codegen/Constraints/BaseBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,171 +7,177 @@

<<__ConsistentConstruct>>
abstract class BaseBuilder<T> implements IBuilder {
use Factory;

protected T $typed_schema;
protected static string $schema_name = '';

public function __construct(
protected Context $ctx,
protected string $suffix,
protected TSchema $schema,
protected ?CodegenClass $class = null,
) {
$this->typed_schema = type_assert_shape($this->schema, static::$schema_name);
}

/**
*
* Return a string which will get rendered as a literal value describing the
* output type of the schema. For example, the `StringBuilder` would return:
* `return 'string';`, the `NumberBuilder` would return: `return 'num';`.
*/
abstract public function getType(): string;

public function isArrayKeyType(): bool {
$type = $this->getType();
if ($type === 'string' || $type === 'int') {
return true;
}

$schema = type_assert_type($this->typed_schema, TSchema::class);
return Shapes::keyExists($schema, 'hackEnum');
}

/**
*
* Main method for building the class for the schema and appending it to the
* file.
*/
abstract public function build(): this;

protected function getHackBuilder(): HackBuilder {
return new HackBuilder($this->ctx->getHackCodegenFactory()->getConfig());
}

public function getClassName(): string {
return $this->generateClassName($this->ctx->getClassName(), $this->suffix);
}

protected function codegenClass(): CodegenClass {
if ($this->class) {
return $this->class;
}

return $this->ctx
->getHackCodegenFactory()
->codegenClass($this->getClassName())
->setIsFinal(true);
}

protected function codegenProperty(string $name): CodegenProperty {
return $this->ctx
->getHackCodegenFactory()
->codegenProperty($name)
->setIsStatic(true)
->setPrivate();
}

protected function codegenCheckMethod(): CodegenMethod {
return $this->ctx
->getHackCodegenFactory()
->codegenMethod('check')
->setPublic()
->setIsStatic(true);
}

protected function getEnumCodegenProperty(): ?CodegenProperty {
$property = null;
$schema = type_assert_shape($this->typed_schema, 'Slack\Hack\JsonSchema\Codegen\TSchema');
$enum = $schema['enum'] ?? null;
if ($enum is nonnull) {
$hb = $this->getHackBuilder()
->addValue($enum, HackBuilderValues::vec(HackBuilderValues::export()));

$property = $this->codegenProperty('enum')
->setType('vec<mixed>')
->setValue($hb->getCode(), HackBuilderValues::literal());
}
return $property;
}

protected function addEnumConstraintCheck(HackBuilder $hb): void {
$schema = type_assert_shape($this->typed_schema, 'Slack\Hack\JsonSchema\Codegen\TSchema');
if (($schema['enum'] ?? null) is nonnull) {
$hb->addMultilineCall('Constraints\EnumConstraint::check', vec['$typed', 'self::$enum', '$pointer']);
}
}

protected function addHackEnumConstraintCheck(HackBuilder $hb): void {
$schema = type_assert_type($this->typed_schema, TSchema::class);
if (!Shapes::keyExists($schema, 'hackEnum')) {
return;
}

try {
$rc = new \ReflectionClass($schema['hackEnum']);
} catch (\ReflectionException $e) {
throw new \Exception(Str\format("Hack enum '%s' does not exist", $schema['hackEnum']));
}

invariant($rc->isEnum(), "'%s' is not an enum", $schema['hackEnum']);

$schema_type = $schema['type'] ?? null;
$hack_enum_values = keyset[];
foreach ($rc->getConstants() as $hack_enum_value) {
if ($schema_type === TSchemaType::INTEGER_T) {
$hack_enum_value = $hack_enum_value ?as int;
} else {
$hack_enum_value = $hack_enum_value ?as string;
}
invariant(
$hack_enum_value is nonnull,
"'%s' must contain only values of type %s",
$rc->getName(),
$schema_type === TSchemaType::INTEGER_T ? 'int' : 'string',
);
$hack_enum_values[] = $hack_enum_value;
}

if (Shapes::keyExists($schema, 'enum')) {
// If both `enum` and `hackEnum` are specified, assert that `enum` is a subset of
// `hackEnum` values. Any value not also in `hackEnum` can't be valid.
foreach ($schema['enum'] as $enum_value) {
invariant(
$enum_value is string,
"Enum value '%s' is not a valid value for '%s'",
\print_r($enum_value, true),
$rc->getName(),
);
invariant(
C\contains_key($hack_enum_values, $enum_value),
"Enum value '%s' is unexpectedly not present in '%s'",
\print_r($enum_value, true),
$rc->getName(),
);
}
}

$hb->addMultilineCall(
'$typed = Constraints\HackEnumConstraint::check',
vec[
'$typed',
Str\format('\%s::class', $rc->getName()),
'$pointer',
],
);
}

public function addBuilderClass(CodegenClass $class): void {
if ($this->class) {
return;
}

$this->ctx->getFile()->addClass($class);
}

public function setSuffix(string $suffix): void {
$this->suffix = $suffix;
}
use Factory;

protected T $typed_schema;
protected static string $schema_name = '';

public function __construct(
protected Context $ctx,
protected string $suffix,
protected TSchema $schema,
protected ?CodegenClass $class = null,
) {
$this->typed_schema = type_assert_shape($this->schema, static::$schema_name);
}

/**
*
* Return a string which will get rendered as a literal value describing the
* output type of the schema. For example, the `StringBuilder` would return:
* `return 'string';`, the `NumberBuilder` would return: `return 'num';`.
*/
abstract public function getType(): string;

public function isArrayKeyType(): bool {
$type = $this->getType();
if ($type === 'string' || $type === 'int') {
return true;
}

$schema = type_assert_type($this->typed_schema, TSchema::class);
return Shapes::keyExists($schema, 'hackEnum');
}

/**
*
* Main method for building the class for the schema and appending it to the
* file.
*/
abstract public function build(): this;

protected function getHackBuilder(): HackBuilder {
return new HackBuilder($this->ctx->getHackCodegenFactory()->getConfig());
}

public function getClassName(): string {
return $this->generateClassName($this->ctx->getClassName(), $this->suffix);
}

protected function codegenClass(): CodegenClass {
if ($this->class) {
return $this->class;
}

return $this->ctx
->getHackCodegenFactory()
->codegenClass($this->getClassName())
->setIsFinal(true);
}

protected function codegenProperty(string $name): CodegenProperty {
return $this->ctx
->getHackCodegenFactory()
->codegenProperty($name)
->setIsStatic(true)
->setPrivate();
}

protected function codegenCheckMethod(): CodegenMethod {
return $this->ctx
->getHackCodegenFactory()
->codegenMethod('check')
->setPublic()
->setIsStatic(true);
}

protected function getEnumCodegenProperty(): ?CodegenProperty {
$property = null;
$schema = type_assert_shape($this->typed_schema, 'Slack\Hack\JsonSchema\Codegen\TSchema');
$enum = $schema['enum'] ?? null;
if ($enum is nonnull) {
$hb = $this->getHackBuilder()
->addValue($enum, HackBuilderValues::vec(HackBuilderValues::export()));

$property = $this->codegenProperty('enum')
->setType('vec<mixed>')
->setValue($hb->getCode(), HackBuilderValues::literal());
}
return $property;
}

protected function addEnumConstraintCheck(HackBuilder $hb): void {
$schema = type_assert_shape($this->typed_schema, 'Slack\Hack\JsonSchema\Codegen\TSchema');
if (($schema['enum'] ?? null) is nonnull) {
$hb->addMultilineCall('Constraints\EnumConstraint::check', vec['$typed', 'self::$enum', '$pointer']);
}
}

protected function addHackEnumConstraintCheck(HackBuilder $hb): void {
$schema = type_assert_type($this->typed_schema, TSchema::class);
if (!Shapes::keyExists($schema, 'hackEnum')) {
return;
}
$generateHackEnum = $schema['generateHackEnum'] ?? false;
if (!$generateHackEnum) {

try {
$rc = new \ReflectionClass($schema['hackEnum']);
} catch (\ReflectionException $e) {
throw new \Exception(Str\format("Hack enum '%s' does not exist", $schema['hackEnum']));
}

invariant($rc->isEnum(), "'%s' is not an enum", $schema['hackEnum']);

$schema_type = $schema['type'] ?? null;
$hack_enum_values = keyset[];
foreach ($rc->getConstants() as $hack_enum_value) {
if ($schema_type === TSchemaType::INTEGER_T) {
$hack_enum_value = $hack_enum_value ?as int;
} else {
$hack_enum_value = $hack_enum_value ?as string;
}
invariant(
$hack_enum_value is nonnull,
"'%s' must contain only values of type %s",
$rc->getName(),
$schema_type === TSchemaType::INTEGER_T ? 'int' : 'string',
);
$hack_enum_values[] = $hack_enum_value;
}

if (Shapes::keyExists($schema, 'enum')) {
// If both `enum` and `hackEnum` are specified, assert that `enum` is a subset of
// `hackEnum` values. Any value not also in `hackEnum` can't be valid.
foreach ($schema['enum'] as $enum_value) {
invariant(
$enum_value is string,
"Enum value '%s' is not a valid value for '%s'",
\print_r($enum_value, true),
$rc->getName(),
);
invariant(
C\contains_key($hack_enum_values, $enum_value),
"Enum value '%s' is unexpectedly not present in '%s'",
\print_r($enum_value, true),
$rc->getName(),
);
}
}
$enum_name = $rc->getName();
} else {
$enum_name = $schema['hackEnum'];
}

$hb->addMultilineCall(
'$typed = Constraints\HackEnumConstraint::check',
vec[
'$typed',
Str\format('\%s::class', $enum_name),
'$pointer',
],
);
}

public function addBuilderClass(CodegenClass $class): void {
if ($this->class) {
return;
}

$this->ctx->getFile()->addClass($class);
}

public function setSuffix(string $suffix): void {
$this->suffix = $suffix;
}
}
6 changes: 4 additions & 2 deletions src/Codegen/Constraints/StringBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ public function build(): this {
}

$enum = $this->getEnumCodegenProperty();
$generateHackEnum = $this->typed_schema['generateHackEnum'] ?? false;
if ($enum is nonnull) {
$generateHackEnum = $this->typed_schema['generateHackEnum'] ?? false;
if ($generateHackEnum) {
$enum = $this->typed_schema['enum'] ?? vec[];
$factory = $this->ctx->getHackCodegenFactory();
$members = Vec\map(
$members = \HH\Lib\Vec\map(
$enum,
$member ==> $factory->codegenEnumMember(Str\uppercase($member))
->setValue($member, HackBuilderValues::export()),
Expand All @@ -79,6 +79,8 @@ public function build(): this {
} else {
$properties[] = $enum;
}
} else {
invariant(!$generateHackEnum, 'enum is required when generating hack enum');
}

$coerce = $this->typed_schema['coerce'] ?? $this->ctx->getCoerceDefault();
Expand Down

0 comments on commit b19a901

Please sign in to comment.