src/__Private/codegen/CodegenBase.hack (317 lines of code) (raw):
/*
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
namespace Facebook\HHAST\__Private;
use type Facebook\HackCodegen\{
HackCodegenConfig,
HackCodegenFactory,
HackfmtFormatter,
};
use namespace Facebook\HHAST;
use namespace HH\Lib\{C, Dict, Keyset, Str, Vec};
abstract class CodegenBase {
// Note to future maintainers:
// This object is cloned using `clone` in `->withoutHackfmt()`.
// If you add a reference (object) typed property, you must ensure
// proper cloning semantics manually.
private Schema\TSchema $schema;
private bool $useHackfmt = true;
public function __construct(
Schema\TSchema $schema,
private dict<string, keyset<string>> $relationships,
) {
$this->schema = shape(
'trivia' => Vec\sort_by($schema['trivia'], $t ==> $t['trivia_kind_name']),
'tokens' => Vec\sort_by($schema['tokens'], $t ==> $t['token_kind']),
'AST' => Vec\sort_by($schema['AST'], $t ==> $t['kind_name']),
'version' => $schema['version'],
);
}
abstract public function generate(): void;
protected function getOutputDirectory(): string {
return __DIR__.'/../../../codegen';
}
final public function withoutHackfmt(): this {
invariant($this->useHackfmt, "can't disable hackfmt twice");
/* Use of the native `clone` operator.
This will become problematic when reference (object) typed properties are added.
All properties are primitive, structural, or value arrays as of November 2021.
Calling the constructor explcitly would cost a lot of needless re-`Vec\sort_by()` work.
HHAST_FIXME[5562] */
$new = clone $this;
$new->useHackfmt = false;
return $new;
}
final protected function getCodegenFactory(): HackCodegenFactory {
$config = new HackCodegenConfig();
if ($this->useHackfmt) {
$config = $config->withFormatter(new HackfmtFormatter($config));
}
return new HackCodegenFactory($config);
}
final protected function getSchema(): Schema\TSchema {
return $this->schema;
}
final protected function getRelationships(): dict<string, keyset<string>> {
return $this->relationships;
}
final protected function getSchemaTokens(
): shape(
'noText' => vec<Schema\TToken>,
'fixedText' => vec<Schema\TToken>,
'variableText' => vec<Schema\TToken>,
) {
$ret = shape(
'noText' => vec[],
'fixedText' => vec[],
'variableText' => vec[],
);
$asts = $this->getSchemaASTByKindName();
foreach ($this->schema['tokens'] as $token) {
if ($token['token_text'] !== null) {
$ret['fixedText'][] = $token;
continue;
}
if (C\contains_key($asts, $token['token_kind'])) {
$ret['noText'][] = $token;
continue;
}
$ret['variableText'][] = $token;
}
return $ret;
}
<<__Memoize>>
final protected function getSchemaASTByKindName(): dict<string, Schema\TAST> {
return Dict\pull($this->schema['AST'], $v ==> $v, $v ==> $v['kind_name']);
}
final protected static function getHandWrittenSyntaxKinds(): keyset<string> {
return keyset['Missing', 'SyntaxList', 'Token', 'ListItem'];
}
private static function flipDictOfKeysets<Tk as arraykey, Tv as arraykey>(
KeyedTraversable<Tk, keyset<Tv>> $in,
): dict<Tv, keyset<Tk>> {
$flipped = dict[];
foreach ($in as $k => $vs) {
foreach ($vs as $v) {
$flipped[$v] ??= keyset[];
$flipped[$v][] = $k;
}
}
return $flipped;
}
<<__Memoize>>
final protected function getTraitsByImplementingClass(
): dict<string, keyset<string>> {
return self::flipDictOfKeysets($this->getTraits());
}
<<__Memoize>>
final protected function getTraits(
): dict<string, keyset<string>> {
return dict[
HHAST\AttributeAsAttributeSpecTrait::class => keyset[
HHAST\ClassishDeclaration::class,
HHAST\ParameterDeclaration::class,
],
]
|> Dict\map_keys($$, $k ==> Str\strip_prefix($k, 'Facebook\\HHAST\\'))
|> Dict\map($$,
$vs ==> Keyset\map($vs, $v ==> Str\strip_prefix($v, 'Facebook\\HHAST\\'))
);
}
<<__Memoize>>
final protected function getMarkerInterfacesByImplementingClass(
): dict<string, keyset<string>> {
return self::flipDictOfKeysets($this->getMarkerInterfacesByInterface());
}
<<__Memoize>>
final protected function getMarkerInterfacesByInterface(
): dict<string, keyset<string>> {
$ifs = dict[
HHAST\IControlFlowStatement::class => keyset[
HHAST\ILoopStatement::class,
HHAST\ElseClause::class,
HHAST\ElseifClause::class,
HHAST\IfStatement::class,
HHAST\SwitchStatement::class,
],
HHAST\IClassBodyDeclaration::class => keyset[
HHAST\MethodishDeclaration::class,
HHAST\MethodishTraitResolution::class,
HHAST\ContextConstDeclaration::class,
HHAST\ConstDeclaration::class,
HHAST\PropertyDeclaration::class,
HHAST\RequireClause::class,
HHAST\TraitUse::class,
HHAST\TraitUseConflictResolution::class,
HHAST\TypeConstDeclaration::class,
HHAST\XHPCategoryDeclaration::class,
HHAST\XHPClassAttributeDeclaration::class,
HHAST\XHPChildrenDeclaration::class,
],
HHAST\IComment::class => keyset[
HHAST\SingleLineComment::class,
HHAST\DelimitedComment::class,
],
HHAST\IDeclaration::class => keyset[
HHAST\ClassishDeclaration::class,
// IFunctionishDeclaration
],
HHAST\IFunctionishDeclaration::class => keyset[
HHAST\FunctionDeclaration::class,
HHAST\MethodishDeclaration::class,
],
HHAST\IFunctionCallishExpression::class => keyset[
HHAST\FunctionCallExpression::class,
HHAST\ObjectCreationExpression::class,
],
HHAST\IHasFunctionBody::class => keyset[
HHAST\IFunctionishDeclaration::class,
HHAST\AnonymousFunction::class,
HHAST\AwaitableCreationExpression::class,
HHAST\LambdaExpression::class,
],
HHAST\IHasTypeHint::class => keyset[
HHAST\IParameter::class,
HHAST\PropertyDeclaration::class,
],
HHAST\IHackArray::class => keyset[
HHAST\DictionaryIntrinsicExpression::class,
HHAST\KeysetIntrinsicExpression::class,
HHAST\VectorIntrinsicExpression::class,
],
HHAST\IContainer::class => keyset[
HHAST\IHackArray::class,
HHAST\DarrayIntrinsicExpression::class,
HHAST\VarrayIntrinsicExpression::class,
HHAST\CollectionLiteralExpression::class,
],
HHAST\IHasOperator::class => keyset[
HHAST\BinaryExpression::class,
HHAST\PrefixUnaryExpression::class,
HHAST\PostfixUnaryExpression::class,
],
HHAST\ILambdaBody::class => keyset[
HHAST\IExpression::class,
HHAST\CompoundStatement::class,
],
HHAST\ILambdaSignature::class => keyset[
HHAST\VariableExpression::class,
HHAST\LambdaSignature::class,
],
HHAST\ILoopStatement::class => keyset[
HHAST\DoStatement::class,
HHAST\ForStatement::class,
HHAST\ForeachStatement::class,
HHAST\WhileStatement::class,
],
HHAST\IHasAttributeSpec::class => keyset[
HHAST\AliasDeclaration::class,
HHAST\AwaitableCreationExpression::class,
HHAST\AnonymousFunction::class,
HHAST\ClassishDeclaration::class,
HHAST\MethodishDeclaration::class,
HHAST\EnumDeclaration::class,
HHAST\FunctionDeclaration::class,
HHAST\ParameterDeclaration::class,
HHAST\PropertyDeclaration::class,
HHAST\LambdaExpression::class,
// HHAST\Php7AnonymousFunction::class : not valid in hack. No attributes
// if not hack
],
HHAST\INameishNode::class => keyset[
HHAST\NameToken::class,
HHAST\QualifiedName::class,
HHAST\XHPCategoryNameToken::class,
HHAST\XHPClassNameToken::class,
],
HHAST\INamespaceBody::class => keyset[
HHAST\NamespaceBody::class,
HHAST\NamespaceEmptyBody::class,
],
HHAST\INamespaceUseDeclaration::class => keyset[
HHAST\NamespaceUseDeclaration::class,
HHAST\NamespaceGroupUseDeclaration::class,
],
HHAST\IParameter::class => keyset[
HHAST\ParameterDeclaration::class,
HHAST\VariadicParameter::class,
],
HHAST\IExpression::class => Keyset\union(
keyset[
// Constants aren't here as they need to be wrapped in NameExpressions
HHAST\AnonymousFunction::class,
HHAST\IHasOperator::class,
HHAST\VariableToken::class,
HHAST\XHPChildrenDeclaration::class,
HHAST\XHPChildrenParenthesizedList::class,
],
Vec\filter(
$this->getSchema()['AST'],
$node ==> Str\ends_with($node['kind_name'], 'Expression'),
)
|> Keyset\map($$, $node ==> $node['kind_name']),
),
HHAST\ISimpleCreationSpecifier::class => keyset[
HHAST\SimpleTypeSpecifier::class,
HHAST\GenericTypeSpecifier::class,
],
HHAST\IStatement::class => Keyset\union(
keyset[
HHAST\InclusionDirective::class,
HHAST\SwitchFallthrough::class,
HHAST\UsingStatementBlockScoped::class,
HHAST\UsingStatementFunctionScoped::class,
],
Vec\filter(
$this->getSchema()['AST'],
$node ==> Str\ends_with($node['kind_name'], 'Statement'),
)
|> Keyset\map($$, $node ==> $node['kind_name']),
),
HHAST\IStringLiteral::class => keyset[
HHAST\SingleQuotedStringLiteralToken::class,
HHAST\DoubleQuotedStringLiteralToken::class,
HHAST\HeredocStringLiteralToken::class,
HHAST\NowdocStringLiteralToken::class,
],
HHAST\ISwitchLabel::class => keyset[
HHAST\CaseLabel::class,
HHAST\DefaultLabel::class,
],
HHAST\ITraitUseItem::class => keyset[
HHAST\TraitUseAliasItem::class,
HHAST\TraitUsePrecedenceItem::class,
],
HHAST\ITypeSpecifier::class => Keyset\union(
keyset[
HHAST\ReifiedTypeArgument::class,
// Dubious:
HHAST\TypeConstant::class,
HHAST\VariadicParameter::class,
HHAST\XHPEnumType::class,
],
Vec\filter(
$this->getSchema()['AST'],
$node ==> Str\ends_with($node['kind_name'], 'Specifier'),
)
|> Keyset\map($$, $node ==> $node['kind_name']),
),
HHAST\IXHPAttribute::class => keyset[
HHAST\XHPClassAttribute::class,
HHAST\XHPSimpleAttribute::class,
],
IWrappableWithSimpleTypeSpecifier::class => keyset[
HHAST\NameToken::class,
HHAST\NoreturnToken::class,
HHAST\ParentToken::class,
HHAST\QualifiedName::class,
HHAST\SelfToken::class,
HHAST\StaticToken::class,
HHAST\ThisToken::class,
HHAST\VariableToken::class,
HHAST\XHPClassNameToken::class,
],
]
|> Dict\map_keys($$, $key ==> Str\strip_prefix($key, 'Facebook\\HHAST\\'))
|> Dict\map(
$$,
$impls ==> Keyset\map(
$impls,
$impl ==> Str\strip_prefix($impl, 'Facebook\\HHAST\\'),
),
);
foreach ($ifs as $if => $impls) {
foreach ($impls as $impl) {
if (Str\starts_with($impl, 'I') && C\contains_key($ifs, $impl)) {
unset($impls[$impl]);
$impls = Keyset\union($impls, $ifs[$impl]);
}
}
$ifs[$if] = $impls;
}
return $ifs;
}
}