Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
emitNumber,
emitInt32,
emitRootTag,
typeAliasResolution,
} = require('../parsers-primitives.js');

describe('emitBoolean', () => {
Expand Down Expand Up @@ -148,3 +149,98 @@ describe('emitDouble', () => {
});
});
});

describe('typeAliasResolution', () => {
const objectTypeAnnotation = {
type: 'ObjectTypeAnnotation',
properties: [
{
name: 'Foo',
optional: false,
typeAnnotation: {
type: 'StringTypeAnnotation',
},
},
],
};

describe('when typeAliasResolutionStatus is successful', () => {
const typeAliasResolutionStatus = {successful: true, aliasName: 'Foo'};

describe('when nullable is true', () => {
it('returns nullable TypeAliasTypeAnnotation and map it in aliasMap', () => {
const aliasMap = {};
const result = typeAliasResolution(
typeAliasResolutionStatus,
objectTypeAnnotation,
aliasMap,
true,
);

expect(aliasMap).toEqual({Foo: objectTypeAnnotation});
expect(result).toEqual({
type: 'NullableTypeAnnotation',
typeAnnotation: {
type: 'TypeAliasTypeAnnotation',
name: 'Foo',
},
});
});
});

describe('when nullable is false', () => {
it('returns non nullable TypeAliasTypeAnnotation and map it in aliasMap', () => {
const aliasMap = {};
const result = typeAliasResolution(
typeAliasResolutionStatus,
objectTypeAnnotation,
aliasMap,
false,
);

expect(aliasMap).toEqual({Foo: objectTypeAnnotation});
expect(result).toEqual({
type: 'TypeAliasTypeAnnotation',
name: 'Foo',
});
});
});
});

describe('when typeAliasResolutionStatus is not successful', () => {
const typeAliasResolutionStatus = {successful: false};

describe('when nullable is true', () => {
it('returns nullable ObjectTypeAnnotation', () => {
const aliasMap = {};
const result = typeAliasResolution(
typeAliasResolutionStatus,
objectTypeAnnotation,
aliasMap,
true,
);

expect(aliasMap).toEqual({});
expect(result).toEqual({
type: 'NullableTypeAnnotation',
typeAnnotation: objectTypeAnnotation,
});
});
});

describe('when nullable is false', () => {
it('returns non nullable ObjectTypeAnnotation', () => {
const aliasMap = {};
const result = typeAliasResolution(
typeAliasResolutionStatus,
objectTypeAnnotation,
aliasMap,
false,
);

expect(aliasMap).toEqual({});
expect(result).toEqual(objectTypeAnnotation);
});
});
});
});
49 changes: 7 additions & 42 deletions packages/react-native-codegen/src/parsers/flow/modules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const {
emitNumber,
emitInt32,
emitRootTag,
typeAliasResolution,
} = require('../../parsers-primitives');
const {
IncorrectlyParameterizedGenericParserError,
Expand Down Expand Up @@ -328,48 +329,12 @@ function translateTypeAnnotation(
.filter(Boolean),
};

if (!typeAliasResolutionStatus.successful) {
return wrapNullable(nullable, objectTypeAnnotation);
}

/**
* All aliases RHS are required.
*/
aliasMap[typeAliasResolutionStatus.aliasName] = objectTypeAnnotation;

/**
* Nullability of type aliases is transitive.
*
* Consider this case:
*
* type Animal = ?{
* name: string,
* };
*
* type B = Animal
*
* export interface Spec extends TurboModule {
* +greet: (animal: B) => void;
* }
*
* In this case, we follow B to Animal, and then Animal to ?{name: string}.
*
* We:
* 1. Replace `+greet: (animal: B) => void;` with `+greet: (animal: ?Animal) => void;`,
* 2. Pretend that Animal = {name: string}.
*
* Why do we do this?
* 1. In ObjC, we need to generate a struct called Animal, not B.
* 2. This design is simpler than managing nullability within both the type alias usage, and the type alias RHS.
* 3. What does it mean for a C++ struct, which is what this type alias RHS will generate, to be nullable? ¯\_(ツ)_/¯
* Nullability is a concept that only makes sense when talking about instances (i.e: usages) of the C++ structs.
* Hence, it's better to manage nullability within the actual TypeAliasTypeAnnotation nodes, and not the
* associated ObjectTypeAnnotations.
*/
return wrapNullable(nullable, {
type: 'TypeAliasTypeAnnotation',
name: typeAliasResolutionStatus.aliasName,
});
return typeAliasResolution(
typeAliasResolutionStatus,
objectTypeAnnotation,
aliasMap,
nullable,
);
}
case 'BooleanTypeAnnotation': {
return emitBoolean(nullable);
Expand Down
11 changes: 2 additions & 9 deletions packages/react-native-codegen/src/parsers/flow/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

'use strict';

import type {TypeAliasResolutionStatus} from '../utils';

const {ParserError} = require('../errors');

/**
Expand Down Expand Up @@ -55,15 +57,6 @@ export type ASTNode = Object;

const invariant = require('invariant');

type TypeAliasResolutionStatus =
| $ReadOnly<{
successful: true,
aliasName: string,
}>
| $ReadOnly<{
successful: false,
}>;

function resolveTypeAnnotation(
// TODO(T71778680): This is an Flow TypeAnnotation. Flow-type this
typeAnnotation: $FlowFixMe,
Expand Down
66 changes: 63 additions & 3 deletions packages/react-native-codegen/src/parsers/parsers-primitives.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
* @flow strict-local
*/

'use strict';

import type {
Nullable,
NativeModuleAliasMap,
NativeModuleBaseTypeAnnotation,
NativeModuleTypeAliasTypeAnnotation,
NativeModuleNumberTypeAnnotation,
BooleanTypeAnnotation,
DoubleTypeAnnotation,
Int32TypeAnnotation,
NativeModuleNumberTypeAnnotation,
Nullable,
ReservedTypeAnnotation,
ObjectTypeAnnotation,
} from '../CodegenSchema';
import type {TypeAliasResolutionStatus} from './utils';

const {wrapNullable} = require('./parsers-commons');

Expand Down Expand Up @@ -54,10 +59,65 @@ function emitDouble(nullable: boolean): Nullable<DoubleTypeAnnotation> {
});
}

function typeAliasResolution(
typeAliasResolutionStatus: TypeAliasResolutionStatus,
objectTypeAnnotation: ObjectTypeAnnotation<
Nullable<NativeModuleBaseTypeAnnotation>,
>,
aliasMap: {...NativeModuleAliasMap},
nullable: boolean,
):
| Nullable<NativeModuleTypeAliasTypeAnnotation>
| Nullable<ObjectTypeAnnotation<Nullable<NativeModuleBaseTypeAnnotation>>> {
if (!typeAliasResolutionStatus.successful) {
return wrapNullable(nullable, objectTypeAnnotation);
}

/**
* All aliases RHS are required.
*/
aliasMap[typeAliasResolutionStatus.aliasName] = objectTypeAnnotation;

/**
* Nullability of type aliases is transitive.
*
* Consider this case:
*
* type Animal = ?{
* name: string,
* };
*
* type B = Animal
*
* export interface Spec extends TurboModule {
* +greet: (animal: B) => void;
* }
*
* In this case, we follow B to Animal, and then Animal to ?{name: string}.
*
* We:
* 1. Replace `+greet: (animal: B) => void;` with `+greet: (animal: ?Animal) => void;`,
* 2. Pretend that Animal = {name: string}.
*
* Why do we do this?
* 1. In ObjC, we need to generate a struct called Animal, not B.
* 2. This design is simpler than managing nullability within both the type alias usage, and the type alias RHS.
* 3. What does it mean for a C++ struct, which is what this type alias RHS will generate, to be nullable? ¯\_(ツ)_/¯
* Nullability is a concept that only makes sense when talking about instances (i.e: usages) of the C++ structs.
* Hence, it's better to manage nullability within the actual TypeAliasTypeAnnotation nodes, and not the
* associated ObjectTypeAnnotations.
*/
return wrapNullable(nullable, {
type: 'TypeAliasTypeAnnotation',
name: typeAliasResolutionStatus.aliasName,
});
}

module.exports = {
emitBoolean,
emitDouble,
emitInt32,
emitNumber,
emitRootTag,
typeAliasResolution,
};
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const {
emitNumber,
emitInt32,
emitRootTag,
typeAliasResolution,
} = require('../../parsers-primitives');
const {
IncorrectlyParameterizedGenericParserError,
Expand Down Expand Up @@ -364,48 +365,12 @@ function translateTypeAnnotation(
.filter(Boolean),
};

if (!typeAliasResolutionStatus.successful) {
return wrapNullable(nullable, objectTypeAnnotation);
}

/**
* All aliases RHS are required.
*/
aliasMap[typeAliasResolutionStatus.aliasName] = objectTypeAnnotation;

/**
* Nullability of type aliases is transitive.
*
* Consider this case:
*
* type Animal = ?{
* name: string,
* };
*
* type B = Animal
*
* export interface Spec extends TurboModule {
* +greet: (animal: B) => void;
* }
*
* In this case, we follow B to Animal, and then Animal to ?{name: string}.
*
* We:
* 1. Replace `+greet: (animal: B) => void;` with `+greet: (animal: ?Animal) => void;`,
* 2. Pretend that Animal = {name: string}.
*
* Why do we do this?
* 1. In ObjC, we need to generate a struct called Animal, not B.
* 2. This design is simpler than managing nullability within both the type alias usage, and the type alias RHS.
* 3. What does it mean for a C++ struct, which is what this type alias RHS will generate, to be nullable? ¯\_(ツ)_/¯
* Nullability is a concept that only makes sense when talking about instances (i.e: usages) of the C++ structs.
* Hence, it's better to manage nullability within the actual TypeAliasTypeAnnotation nodes, and not the
* associated ObjectTypeAnnotations.
*/
return wrapNullable(nullable, {
type: 'TypeAliasTypeAnnotation',
name: typeAliasResolutionStatus.aliasName,
});
return typeAliasResolution(
typeAliasResolutionStatus,
objectTypeAnnotation,
aliasMap,
nullable,
);
}
case 'TSBooleanKeyword': {
return emitBoolean(nullable);
Expand Down
11 changes: 2 additions & 9 deletions packages/react-native-codegen/src/parsers/typescript/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

'use strict';

import type {TypeAliasResolutionStatus} from '../utils';

const {ParserError} = require('../errors');
const {parseTopLevelType} = require('./parseTopLevelType');

Expand Down Expand Up @@ -50,15 +52,6 @@ export type ASTNode = Object;

const invariant = require('invariant');

type TypeAliasResolutionStatus =
| $ReadOnly<{
successful: true,
aliasName: string,
}>
| $ReadOnly<{
successful: false,
}>;

function resolveTypeAnnotation(
// TODO(T108222691): Use flow-types for @babel/parser
typeAnnotation: $FlowFixMe,
Expand Down
9 changes: 9 additions & 0 deletions packages/react-native-codegen/src/parsers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@

const path = require('path');

export type TypeAliasResolutionStatus =
| $ReadOnly<{
successful: true,
aliasName: string,
}>
| $ReadOnly<{
successful: false,
}>;

function extractNativeModuleName(filename: string): string {
// this should drop everything after the file name. For Example it will drop:
// .android.js, .android.ts, .android.tsx, .ios.js, .ios.ts, .ios.tsx, .js, .ts, .tsx
Expand Down