Skip to content
This repository was archived by the owner on Nov 12, 2025. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Codegen/Builders/CompositeBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ use type Facebook\HackCodegen\{
abstract class CompositeBuilder extends OutputTypeBuilder<\Slack\GraphQL\__Private\CompositeType> {

public function __construct(
Context $ctx,
\Slack\GraphQL\__Private\CompositeType $type_info,
string $hack_type,
protected vec<FieldBuilder> $fields,
) {
parent::__construct($type_info, $hack_type);
parent::__construct($ctx, $type_info, $hack_type);
}

public function build(HackCodegenFactory $cg): CodegenClass {
Expand Down
40 changes: 24 additions & 16 deletions src/Codegen/Builders/Fields/FieldBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ abstract class FieldBuilder {

// Constructors

public function __construct(protected this::TField $data) {}
public function __construct(protected Context $ctx, protected this::TField $data) {}

/**
* Construct a GraphQL field from a Hack method.
*/
public static function fromReflectionMethod(
Context $ctx,
\Slack\GraphQL\Field $field,
\ReflectionMethod $rm,
bool $is_root_field = false,
Expand Down Expand Up @@ -61,36 +62,43 @@ abstract class FieldBuilder {
}

if (returns_connection_type($rm)) {
return new ConnectionFieldBuilder($data);
return new ConnectionFieldBuilder($ctx, $data);
} else {
return new MethodFieldBuilder($data);
return new MethodFieldBuilder($ctx, $data);
}
}

/**
* Construct a GraphQL field from a shape field.
*/
public static function fromShapeField<T>(string $name, TypeStructure<T> $ts): FieldBuilder {
return new ShapeFieldBuilder(shape(
'name' => $name,
'output_type' => output_type(type_structure_to_type_alias($ts), false),
'is_optional' => Shapes::idx($ts, 'optional_shape_field') ?? false,
));
public static function fromShapeField<T>(Context $ctx, string $name, TypeStructure<T> $ts): FieldBuilder {
return new ShapeFieldBuilder(
$ctx,
shape(
'name' => $name,
'output_type' => output_type(type_structure_to_type_alias($ts), false),
'is_optional' => Shapes::idx($ts, 'optional_shape_field') ?? false,
),
);
}

/**
* Construct a top-level GraphQL field.
*/
public static function forRootField(\Slack\GraphQL\Field $field, \ReflectionMethod $rm): FieldBuilder {
return FieldBuilder::fromReflectionMethod($field, $rm, true);
public static function forRootField(
Context $ctx,
\Slack\GraphQL\Field $field,
\ReflectionMethod $rm,
): FieldBuilder {
return FieldBuilder::fromReflectionMethod($ctx, $field, $rm, true);
}

public static function introspectSchemaField(): FieldBuilder {
return new IntrospectSchemaFieldBuilder();
public static function introspectSchemaField(Context $ctx): FieldBuilder {
return new IntrospectSchemaFieldBuilder($ctx);
}

public static function introspectTypeField(): FieldBuilder {
return new IntrospectTypeFieldBuilder();
public static function introspectTypeField(Context $ctx): FieldBuilder {
return new IntrospectTypeFieldBuilder($ctx);
}

// Codegen
Expand Down Expand Up @@ -141,7 +149,7 @@ abstract class FieldBuilder {

$hb->addLinef("'name' => %s,", $argument_name);

$type = input_type($param['type']);
$type = input_type($param['type'], $this->ctx->getCustomTypes());
$hb->addLinef("'type' => %s,", $type);

$default_value = $param['default_value'] ?? null;
Expand Down
13 changes: 8 additions & 5 deletions src/Codegen/Builders/Fields/IntrospectionSchemaFieldBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ final class IntrospectSchemaFieldBuilder extends FieldBuilder {
...
);

public function __construct() {
parent::__construct(shape(
'name' => '__schema',
'output_type' => shape('type' => '__Schema::nullableOutput()'),
));
public function __construct(Context $ctx) {
parent::__construct(
$ctx,
shape(
'name' => '__schema',
'output_type' => shape('type' => '__Schema::nullableOutput()'),
),
);
}

<<__Override>>
Expand Down
23 changes: 13 additions & 10 deletions src/Codegen/Builders/Fields/IntrospectionTypeFieldBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@ use type Facebook\HackCodegen\{HackBuilder};

final class IntrospectTypeFieldBuilder extends MethodFieldBuilder {

public function __construct() {
parent::__construct(shape(
'name' => '__type',
'method_name' => '_',
'output_type' => shape('type' => '__Type::nullableOutput()'),
'root_field_for_type' => 'Schema',
'parameters' => vec[
shape('name' => 'name', 'type' => 'HH\string', 'is_optional' => false),
],
));
public function __construct(Context $ctx) {
parent::__construct(
$ctx,
shape(
'name' => '__type',
'method_name' => '_',
'output_type' => shape('type' => '__Type::nullableOutput()'),
'root_field_for_type' => 'Schema',
'parameters' => vec[
shape('name' => 'name', 'type' => 'HH\string', 'is_optional' => false),
],
),
);
}
<<__Override>>
protected function generateResolverBody(HackBuilder $hb): void {
Expand Down
2 changes: 1 addition & 1 deletion src/Codegen/Builders/Fields/MethodFieldBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class MethodFieldBuilder extends FieldBuilder {
final protected function getArgumentInvocationString(Parameter $param): string {
return Str\format(
'%s->coerce%sNamedNode(%s, $args, $vars%s)',
input_type($param['type']),
input_type($param['type'], $this->ctx->getCustomTypes()),
$param['is_optional'] ? 'Optional' : '',
\var_export($param['name'], true),
Shapes::keyExists($param, 'default_value') ? ', '.$param['default_value'] : '',
Expand Down
20 changes: 12 additions & 8 deletions src/Codegen/Builders/InputObjectBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ class InputObjectBuilder extends InputTypeBuilder<\Slack\GraphQL\InputObjectType

const classname<\Slack\GraphQL\Types\InputObjectType> SUPERCLASS = \Slack\GraphQL\Types\InputObjectType::class;

public function __construct(\Slack\GraphQL\InputObjectType $type_info, private \ReflectionTypeAlias $type_alias) {
parent::__construct($type_info, $type_alias->getName());
public function __construct(
Context $ctx,
\Slack\GraphQL\InputObjectType $type_info,
private \ReflectionTypeAlias $type_alias,
) {
parent::__construct($ctx, $type_info, $type_alias->getName());
}

public function build(HackCodegenFactory $cg): CodegenClass {
Expand All @@ -36,7 +40,7 @@ class InputObjectBuilder extends InputTypeBuilder<\Slack\GraphQL\InputObjectType
->addParameter('KeyedContainer<arraykey, mixed> $fields')
->setReturnType('this::THackType')
->setBody(
self::getFieldCoercionMethodBody(
$this->getFieldCoercionMethodBody(
$cg,
$ts['fields'],
$field_name ==> Str\format('C\\contains_key($fields, %s)', $field_name),
Expand All @@ -49,7 +53,7 @@ class InputObjectBuilder extends InputTypeBuilder<\Slack\GraphQL\InputObjectType
->addParameter('dict<string, mixed> $vars')
->setReturnType('this::THackType')
->setBody(
self::getFieldCoercionMethodBody(
$this->getFieldCoercionMethodBody(
$cg,
$ts['fields'],
$field_name ==> Str\format('$this->hasValue(%s, $fields, $vars)', $field_name),
Expand All @@ -61,7 +65,7 @@ class InputObjectBuilder extends InputTypeBuilder<\Slack\GraphQL\InputObjectType
->addParameter('KeyedContainer<arraykey, mixed> $fields')
->setReturnType('this::THackType')
->setBody(
self::getFieldCoercionMethodBody(
$this->getFieldCoercionMethodBody(
$cg,
$ts['fields'],
$field_name ==> Str\format('C\\contains_key($fields, %s)', $field_name),
Expand All @@ -75,7 +79,7 @@ class InputObjectBuilder extends InputTypeBuilder<\Slack\GraphQL\InputObjectType
/**
* Shared logic for coerceFieldValues(), coerceFieldNodes() and assertValidFieldValues()
*/
private static function getFieldCoercionMethodBody<T>(
private function getFieldCoercionMethodBody<T>(
HackCodegenFactory $cg,
KeyedContainer<string, TypeStructure<T>> $fields,
(function(string): string) $get_if_condition,
Expand All @@ -94,7 +98,7 @@ class InputObjectBuilder extends InputTypeBuilder<\Slack\GraphQL\InputObjectType
);

$name_literal = \var_export($field_name, true);
$type = input_type(type_structure_to_type_alias($field_ts));
$type = input_type(type_structure_to_type_alias($field_ts), $this->ctx->getCustomTypes());

if ($is_optional) {
$hb->startIfBlock($get_if_condition($name_literal));
Expand Down Expand Up @@ -127,7 +131,7 @@ class InputObjectBuilder extends InputTypeBuilder<\Slack\GraphQL\InputObjectType
$hb->addLine('return shape(')->indent();
$hb->addLinef("'name' => %s,", \var_export($field_name, true));

$type = input_type(type_structure_to_type_alias($field_ts));
$type = input_type(type_structure_to_type_alias($field_ts), $this->ctx->getCustomTypes());
$hb->addLinef("'type' => %s,", $type);
// TODO: description, defaultValue
$hb->unindent()->addLine(');')->unindent();
Expand Down
3 changes: 2 additions & 1 deletion src/Codegen/Builders/InterfaceBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ final class InterfaceBuilder extends CompositeBuilder {

<<__Override>>
public function __construct(
Context $ctx,
\Slack\GraphQL\__Private\CompositeType $type_info,
string $hack_type,
vec<FieldBuilder> $fields,
private dict<string, string> $hack_class_to_graphql_object,
) {
parent::__construct($type_info, $hack_type, $fields);
parent::__construct($ctx, $type_info, $hack_type, $fields);
}

<<__Override>>
Expand Down
81 changes: 52 additions & 29 deletions src/Codegen/Builders/ObjectBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ class ObjectBuilder extends CompositeBuilder {

<<__Override>>
public function __construct(
Context $ctx,
\Slack\GraphQL\__Private\CompositeType $type_info,
string $hack_type,
vec<FieldBuilder> $fields,
private dict<string, string> $hack_class_to_graphql_interface,
) {
parent::__construct($type_info, $hack_type, $fields);
parent::__construct($ctx, $type_info, $hack_type, $fields);
}

<<__Override>>
Expand All @@ -45,6 +46,7 @@ class ObjectBuilder extends CompositeBuilder {
}

public static function fromTypeAlias<T>(
Context $ctx,
\Slack\GraphQL\ObjectType $type_info,
\ReflectionTypeAlias $type_alias,
): ObjectBuilder {
Expand All @@ -55,39 +57,48 @@ class ObjectBuilder extends CompositeBuilder {
TypeStructureKind::getNames()[$ts['kind']],
);
return new ObjectBuilder(
$ctx,
$type_info,
$type_alias->getName(),
Vec\map_with_key($ts['fields'], ($name, $ts) ==> FieldBuilder::fromShapeField($name, $ts)),
Vec\map_with_key($ts['fields'], ($name, $ts) ==> FieldBuilder::fromShapeField($ctx, $name, $ts)),
dict[], // Objects generated from shapes cannot implement interfaces
);
}

public static function forConnection(
Context $ctx,
string $name,
\Slack\GraphQL\ObjectType $object_type,
string $edge_name,
vec<FieldBuilder> $additional_fields,
): ObjectBuilder {
return new ObjectBuilder(
$ctx,
$object_type,
$name, // hack type
Vec\concat(
vec[ // fields
new MethodFieldBuilder(shape(
'name' => 'edges',
'method_name' => 'getEdges',
'output_type' => shape(
'type' => $edge_name.'::nonNullable()->nullableOutputListOf()',
'needs_await' => true,
new MethodFieldBuilder(
$ctx,
shape(
'name' => 'edges',
'method_name' => 'getEdges',
'output_type' => shape(
'type' => $edge_name.'::nonNullable()->nullableOutputListOf()',
'needs_await' => true,
),
'parameters' => vec[],
),
'parameters' => vec[],
)),
new MethodFieldBuilder(shape(
'name' => 'pageInfo',
'method_name' => 'getPageInfo',
'output_type' => shape('type' => 'PageInfo::nullableOutput()', 'needs_await' => true),
'parameters' => vec[],
)),
),
new MethodFieldBuilder(
$ctx,
shape(
'name' => 'pageInfo',
'method_name' => 'getPageInfo',
'output_type' => shape('type' => 'PageInfo::nullableOutput()', 'needs_await' => true),
'parameters' => vec[],
),
),
],
$additional_fields,
),
Expand All @@ -96,24 +107,36 @@ class ObjectBuilder extends CompositeBuilder {
}

// TODO: It should be possible to create user-defined edges which contain additional fields.
public static function forEdge(string $gql_type, string $hack_type, string $output_type): ObjectBuilder {
public static function forEdge(
Context $ctx,
string $gql_type,
string $hack_type,
string $output_type,
): ObjectBuilder {
$name = $gql_type.'Edge';
return new ObjectBuilder(
$ctx,
new \Slack\GraphQL\ObjectType($name, $gql_type.' Edge'), // TODO: Description
'Slack\GraphQL\Pagination\Edge<'.$hack_type.'>', // hack type
vec[ // fields
new MethodFieldBuilder(shape(
'name' => 'node',
'method_name' => 'getNode',
'output_type' => shape('type' => $output_type.'::nullableOutput()'),
'parameters' => vec[],
)),
new MethodFieldBuilder(shape(
'name' => 'cursor',
'method_name' => 'getCursor',
'output_type' => shape('type' => 'Types\StringType::nullableOutput()'),
'parameters' => vec[],
)),
new MethodFieldBuilder(
$ctx,
shape(
'name' => 'node',
'method_name' => 'getNode',
'output_type' => shape('type' => $output_type.'::nullableOutput()'),
'parameters' => vec[],
),
),
new MethodFieldBuilder(
$ctx,
shape(
'name' => 'cursor',
'method_name' => 'getCursor',
'output_type' => shape('type' => 'Types\StringType::nullableOutput()'),
'parameters' => vec[],
),
),
],
dict[],
);
Expand Down
2 changes: 1 addition & 1 deletion src/Codegen/Builders/TypeBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ abstract class TypeBuilder<T as \Slack\GraphQL\__Private\GraphQLTypeInfo> {

abstract const classname<\Slack\GraphQL\Types\BaseType> SUPERCLASS;

public function __construct(protected T $type_info, protected string $hack_type) {}
public function __construct(protected Context $ctx, protected T $type_info, protected string $hack_type) {}

final public function getGraphQLType(): string {
return $this->type_info->getType();
Expand Down
11 changes: 11 additions & 0 deletions src/Codegen/Context.hack
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@


namespace Slack\GraphQL\Codegen;

final class Context {
public function __construct(private dict<string, classname<\Slack\GraphQL\Types\NamedType>> $custom_types) {}

public function getCustomTypes(): dict<string, classname<\Slack\GraphQL\Types\NamedType>> {
return $this->custom_types;
}
}
Loading