Skip to content

Commit

Permalink
Codegen: Generate ObjC structs for type aliases
Browse files Browse the repository at this point in the history
Summary:
**Motivation:**

Match the old codegen's behavior when generating structs for type alias derived objects.

**Problem description:**

Take, for example, the following spec:

```
export type MyTypeAlias = { /* ... */ }
export interface Spec extends TurboModule {
  // ...
  +myMethod: (paramName: MyTypeAlias) => void;
  // ...
}
```

The codegen needs to generate a struct to expose the properties of the type alias object. The codegen was producing the following output:

```
- (void)myMethod:(JS::MyModule::SpecMyMethodParamName &)paramName;
```

...which does not match the output from the original codegen:

```
- (void)myMethod:(JS::MyModule::MyTypeAlias &)paramName;
```

The original codegen generates a struct using the type alias name, while the new codegen was generating a struct that uses a combination of the property and parameter names.

Now that type alias names are exposed in the schema, the new codegen can match the original codegen's behavior.

**De-duplication of structs:**

Prior to these changes, type aliases were expanded into regular object types. This meant that any spec that used a type alias more than once would lead to redundant structs getting created. With these changes, we only generate the one struct per type alias, matching the old codegen.

**Expansion of type aliases:**

A new type was introduced in D22200700 (facebook@e261f02), TypeAliasTypeAnnotation:

```
export type TypeAliasTypeAnnotation = $ReadOnly<{|
  type: 'TypeAliasTypeAnnotation',
  name: string,
|}>;
```

This type may now appear in several locations where a `{| type: 'ObjectTypeAnnotation', properties: [] |}` otherwise would have been used. A `getTypeAliasTypeAnnotation` function is introduced which, given an alias name and an array of aliases provided by the module, will produce the actual object type annotation for the given property parameter.

Changelog: [Internal]

Reviewed By: RSNara

Differential Revision: D22244323

fbshipit-source-id: 125fbf0d6d887bd05a99bf8b81b30bdda4f1682b
  • Loading branch information
hramos authored and facebook-github-bot committed Jul 2, 2020
1 parent e255748 commit 7e7706c
Show file tree
Hide file tree
Showing 14 changed files with 2,504 additions and 190 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
'use strict';

import type {SchemaType} from '../../CodegenSchema';

const {getTypeAliasTypeAnnotation} = require('./ObjCppUtils/Utils');
type FilesOutput = Map<string, string>;

const propertyHeaderTemplate =
Expand Down Expand Up @@ -62,20 +62,25 @@ namespace react {
} // namespace facebook
`;

function traverseArg(arg, index): string {
function traverseArg(arg, index, aliases): string {
function wrap(suffix) {
return `args[${index}]${suffix}`;
}
const {typeAnnotation} = arg;
switch (typeAnnotation.type) {

const realTypeAnnotation =
typeAnnotation.type === 'TypeAliasTypeAnnotation'
? getTypeAliasTypeAnnotation(typeAnnotation.name, aliases)
: typeAnnotation;
switch (realTypeAnnotation.type) {
case 'ReservedFunctionValueTypeAnnotation':
switch (typeAnnotation.name) {
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrap('.getNumber()');
default:
(typeAnnotation.name: empty);
(realTypeAnnotation.name: empty);
throw new Error(
`Unknown prop type for "${arg.name}", found: "${typeAnnotation.name}"`,
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.name}"`,
);
}
case 'StringTypeAnnotation':
Expand All @@ -91,27 +96,26 @@ function traverseArg(arg, index): string {
case 'FunctionTypeAnnotation':
return `std::move(${wrap('.getObject(rt).getFunction(rt)')})`;
case 'GenericObjectTypeAnnotation':
case 'TypeAliasTypeAnnotation': // TODO: Handle aliases
case 'ObjectTypeAnnotation':
return wrap('.getObject(rt)');
case 'AnyTypeAnnotation':
throw new Error(`Any type is not allowed in params for "${arg.name}"`);
default:
// TODO (T65847278): Figure out why this does not work.
// (typeAnnotation.type: empty);
// (type: empty);
throw new Error(
`Unknown prop type for "${arg.name}", found: "${typeAnnotation.type}"`,
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
);
}
}

function traverseProperty(property): string {
function traverseProperty(property, aliases): string {
const propertyTemplate =
property.typeAnnotation.returnTypeAnnotation.type === 'VoidTypeAnnotation'
? voidPropertyTemplate
: nonvoidPropertyTemplate;
const traversedArgs = property.typeAnnotation.params
.map(traverseArg)
.map((p, i) => traverseArg(p, i, aliases))
.join(', ');
return propertyTemplate
.replace(/::_PROPERTY_NAME_::/g, property.name)
Expand All @@ -138,9 +142,9 @@ module.exports = {

const modules = Object.keys(nativeModules)
.map(name => {
const {properties} = nativeModules[name];
const {aliases, properties} = nativeModules[name];
const traversedProperties = properties
.map(property => traverseProperty(property))
.map(property => traverseProperty(property, aliases))
.join('\n');
return moduleTemplate
.replace(/::_MODULE_PROPERTIES_::/g, traversedProperties)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ import type {
SchemaType,
FunctionTypeAnnotationParamTypeAnnotation,
FunctionTypeAnnotationReturn,
TypeAliasTypeAnnotation,
ObjectTypeAliasTypeShape,
} from '../../CodegenSchema';

const {getTypeAliasTypeAnnotation} = require('./ObjCppUtils/Utils');

type FilesOutput = Map<string, string>;

const moduleTemplate = `
Expand Down Expand Up @@ -51,17 +55,23 @@ namespace react {
function translatePrimitiveJSTypeToCpp(
typeAnnotation:
| FunctionTypeAnnotationParamTypeAnnotation
| FunctionTypeAnnotationReturn,
| FunctionTypeAnnotationReturn
| TypeAliasTypeAnnotation,
createErrorMessage: (typeName: string) => string,
aliases: $ReadOnly<{[aliasName: string]: ObjectTypeAliasTypeShape, ...}>,
) {
switch (typeAnnotation.type) {
const realTypeAnnotation =
typeAnnotation.type === 'TypeAliasTypeAnnotation'
? getTypeAliasTypeAnnotation(typeAnnotation.name, aliases)
: typeAnnotation;
switch (realTypeAnnotation.type) {
case 'ReservedFunctionValueTypeAnnotation':
switch (typeAnnotation.name) {
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'double';
default:
(typeAnnotation.name: empty);
throw new Error(createErrorMessage(typeAnnotation.name));
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
Expand All @@ -74,7 +84,6 @@ function translatePrimitiveJSTypeToCpp(
return 'int';
case 'BooleanTypeAnnotation':
return 'bool';
// case 'TypeAliasTypeAnnotation': // TODO: Handle aliases
case 'GenericObjectTypeAnnotation':
case 'ObjectTypeAnnotation':
return 'jsi::Object';
Expand All @@ -86,8 +95,8 @@ function translatePrimitiveJSTypeToCpp(
return 'jsi::Value';
default:
// TODO (T65847278): Figure out why this does not work.
// (typeAnnotation.type: empty);
throw new Error(createErrorMessage(typeAnnotation.type));
// (type: empty);
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}

Expand All @@ -114,7 +123,7 @@ module.exports = {

const modules = Object.keys(nativeModules)
.map(name => {
const {properties} = nativeModules[name];
const {aliases, properties} = nativeModules[name];
const traversedProperties = properties
.map(prop => {
const traversedArgs = prop.typeAnnotation.params
Expand All @@ -123,6 +132,7 @@ module.exports = {
param.typeAnnotation,
typeName =>
`Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`,
aliases,
);
const isObject = translatedParam.startsWith('jsi::');
return (
Expand All @@ -140,6 +150,7 @@ module.exports = {
prop.typeAnnotation.returnTypeAnnotation,
typeName =>
`Unsupported return type for ${prop.name}. Found: ${typeName}`,
aliases,
),
)
.replace(
Expand Down
Loading

0 comments on commit 7e7706c

Please sign in to comment.