From beb5c408d1555807d60b482d67f5cac46760dbbe Mon Sep 17 00:00:00 2001 From: Paul Loyd Date: Fri, 1 Dec 2017 20:00:40 +0300 Subject: [PATCH] Types refactoring --- package.json | 2 +- src/definitions.js | 87 ++++++++++++++-------------------------------- src/globals.js | 53 ++++++++++------------------ src/pragmas.js | 12 +++---- src/traverse.js | 2 +- src/types.js | 70 +++++++++++++++++++++++++++++++++++++ src/utils.js | 19 ---------- tests/run.js | 2 +- 8 files changed, 123 insertions(+), 124 deletions(-) diff --git a/package.json b/package.json index 1100faa..6e31749 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@babel/preset-env": "^7.0.0-beta.32", "@babel/preset-flow": "^7.0.0-beta.32", "@babel/register": "^7.0.0-beta.32", - "flow-bin": "^0.59.0", + "flow-bin": "^0.60.0", "jasmine": "^2.8.0", "mocha": "^4.0.1", "nyc": "^11.3.0" diff --git a/src/definitions.js b/src/definitions.js index c5d4416..baf526a 100644 --- a/src/definitions.js +++ b/src/definitions.js @@ -20,6 +20,8 @@ import type { MaybeType, NumberType, StringType, BooleanType, LiteralType, ReferenceType, } from './types'; +import * as t from './types'; + import {extractPragmas} from './pragmas'; import {invariant} from './utils'; @@ -54,18 +56,16 @@ function processInterfaceDeclaration(ctx: Context, node: InterfaceDeclaration) { invariant(type.id); - parts.push({ - kind: 'reference', - to: type.id.slice(), - }); + const reference = t.createReference(t.clone(type.id)); + + parts.push(reference); } parts.push(type); - ctx.define(name, { - kind: 'intersection', - parts, - }); + const intersection = t.createIntersection(parts); + + ctx.define(name, intersection); } // TODO: type params. @@ -87,27 +87,22 @@ function processClassDeclaration(ctx: Context, node: ClassDeclaration) { invariant(base.id); - const baseRef = { - kind: 'reference', - to: base.id.slice(), - }; + const baseRef = t.createReference(t.clone(base.id)); + const intersection = t.createIntersection([baseRef, type]); - ctx.define(name, { - kind: 'intersection', - parts: [baseRef, type], - }); + ctx.define(name, intersection); } function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type { switch (node.type) { case 'NullLiteralTypeAnnotation': - return {kind: 'literal', value: null}; + return t.createLiteral(null); case 'BooleanTypeAnnotation': - return {kind: 'boolean'}; + return t.createBoolean(); case 'NumberTypeAnnotation': - return {kind: 'number', repr: 'f64'}; + return t.createNumber('f64'); case 'StringTypeAnnotation': - return {kind: 'string'}; + return t.createString(); case 'TypeAnnotation': return makeType(ctx, node.typeAnnotation); case 'NullableTypeAnnotation': @@ -123,13 +118,13 @@ function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type { case 'IntersectionTypeAnnotation': return makeIntersection(ctx, node); case 'StringLiteralTypeAnnotation': - return {kind: 'literal', value: node.value}; + return t.createLiteral(node.value); case 'GenericTypeAnnotation': return makeReference(ctx, node); case 'AnyTypeAnnotation': - return {kind: 'any'}; + return t.createAny(); case 'MixedTypeAnnotation': - return {kind: 'mixed'}; + return t.createMixed(); case 'FunctionTypeAnnotation': return null; default: @@ -144,10 +139,7 @@ function makeMaybe(ctx: Context, node: NullableTypeAnnotation): ?MaybeType { return null; } - return { - kind: 'maybe', - value: type, - }; + return t.createMaybe(type); } function makeComplexType(ctx: Context, node: ObjectTypeAnnotation): Type { @@ -168,10 +160,7 @@ function makeComplexType(ctx: Context, node: ObjectTypeAnnotation): Type { const parts = record.fields.length > 0 ? [record, ...maps] : maps; - return { - kind: 'intersection', - parts, - }; + return t.createIntersection(parts); } function makeRecord(ctx: Context, nodes: T[]): RecordType { @@ -180,10 +169,7 @@ function makeRecord(ctx: Context, nodes: .filter() .toArray(); - return { - kind: 'record', - fields, - }; + return t.createRecord(fields); } function makeField(ctx: Context, node: ObjectTypeProperty | ClassProperty): ?Field { @@ -238,11 +224,7 @@ function makeMap(ctx: Context, node: ObjectTypeIndexer): ?MapType { return null; } - return { - kind: 'map', - keys, - values, - }; + return t.createMap(keys, values); } function makeArrayType(ctx: Context, node: ArrayTypeAnnotation): ?ArrayType { @@ -252,10 +234,7 @@ function makeArrayType(ctx: Context, node: ArrayTypeAnnotation): ?ArrayType { return null; } - return { - kind: 'array', - items, - }; + return t.createArray(items); } function makeTupleType(ctx: Context, node: TupleTypeAnnotation): ?TupleType { @@ -266,10 +245,7 @@ function makeTupleType(ctx: Context, node: TupleTypeAnnotation): ?TupleType { return null; } - return { - kind: 'tuple', - items, - }; + return t.createTuple(items); } function makeUnionType(ctx: Context, node: UnionTypeAnnotation): ?Type { @@ -286,10 +262,7 @@ function makeUnionType(ctx: Context, node: UnionTypeAnnotation): ?Type { return variants[0]; } - return { - kind: 'union', - variants, - }; + return t.createUnion(variants); } function makeIntersection(ctx: Context, node: IntersectionTypeAnnotation): ?Type { @@ -307,10 +280,7 @@ function makeIntersection(ctx: Context, node: IntersectionTypeAnnotation): ?Type return parts[0]; } - return { - kind: 'intersection', - parts, - }; + return t.createIntersection(parts); } function makeReference(ctx: Context, node: GenericTypeAnnotation): ?Type { @@ -324,10 +294,7 @@ function makeReference(ctx: Context, node: GenericTypeAnnotation): ?Type { return type; } - return { - kind: 'reference', - to: type.id.slice(), - }; + return t.createReference(t.clone(type.id)); } export default { diff --git a/src/globals.js b/src/globals.js index 82d0279..f6c7460 100644 --- a/src/globals.js +++ b/src/globals.js @@ -1,35 +1,26 @@ import wu from 'wu'; -import {invariant, clone} from './utils'; +import {invariant} from './utils'; import type {Type, TypeId} from './types'; +import * as t from './types'; function object(params: (?Type)[]): ?Type { invariant(params.length === 0); - return { - kind: 'map', - keys: {kind: 'mixed'}, - values: {kind: 'mixed'}, - }; + return t.createMap(t.createMixed(), t.createMixed()); } function buffer(params: (?Type)[]): ?Type { invariant(params.length === 0); - return { - kind: 'reference', - to: ['Buffer'], - }; + return t.createReference(['Buffer']); } function array(params: (?Type)[]): ?Type { invariant(params.length === 1); invariant(params[0]); - return { - kind: 'array', - items: params[0], - }; + return t.createArray(t.clone(params[0])); } function elemType(params: (?Type)[], resolve: TypeId => Type): ?Type { @@ -51,7 +42,7 @@ function elemType(params: (?Type)[], resolve: TypeId => Type): ?Type { const field = wu(record.fields).find(field => field.name === key.value); // TODO: what about removing "id"? - return field ? clone(field.value) : null; + return field ? t.clone(field.value) : null; } function stripMaybe(params: (?Type)[], resolve: TypeId => Type): ?Type { @@ -65,10 +56,10 @@ function stripMaybe(params: (?Type)[], resolve: TypeId => Type): ?Type { // TODO: support for unions and nested maybe. if (maybe.kind !== 'maybe') { - return clone(ref); + return t.clone(ref); } - return clone(maybe.value); + return t.clone(maybe.value); } function shape(params: (?Type)[], resolve: TypeId => Type): ?Type { @@ -83,14 +74,14 @@ function shape(params: (?Type)[], resolve: TypeId => Type): ?Type { invariant(record.kind === 'record'); const fields = wu(record.fields) - .map(clone) - .tap(field => field.required = false) + .map(field => ({ + name: field.name, + value: t.clone(field.value), + required: false, + })) .toArray(); - return { - kind: 'record', - fields, - }; + return t.createRecord(fields); } function unwrap(params: (?Type)[]): ?Type { @@ -98,7 +89,7 @@ function unwrap(params: (?Type)[]): ?Type { const [type] = params; - return type ? clone(type) : null; + return type ? t.clone(type) : null; } function keys(params: (?Type)[], resolve: TypeId => Type): ?Type { @@ -114,15 +105,12 @@ function keys(params: (?Type)[], resolve: TypeId => Type): ?Type { const variants = wu(record.fields) .pluck('name') - .map(name => ({kind: 'literal', value: name})) + .map(t.createLiteral) .toArray(); // TODO: empty records. - return { - kind: 'union', - variants, - }; + return t.createUnion(variants); } function values(params: (?Type)[], resolve: TypeId => Type): ?Type { @@ -138,16 +126,13 @@ function values(params: (?Type)[], resolve: TypeId => Type): ?Type { const variants = wu(record.fields) .pluck('value') - .map(clone) + .map(t.clone) .toArray(); // TODO: empty records. // TODO: dedup values. - return { - kind: 'union', - variants, - }; + return t.createUnion(variants); } export default { diff --git a/src/pragmas.js b/src/pragmas.js index c5110ac..64922d0 100644 --- a/src/pragmas.js +++ b/src/pragmas.js @@ -1,6 +1,7 @@ import {invariant} from './utils'; import type {Type} from './types'; +import * as t from './types'; export type Pragma = | TypePragma; @@ -21,15 +22,10 @@ export function extractPragmas(text: string): Pragma[] { invariant(['i32', 'i64', 'u32', 'u64', 'f32', 'f64'].includes(repr)); - const pragma = { + pragmas.push({ kind: 'type', - value: { - kind: 'number', - repr, - }, - }; - - pragmas.push(pragma); + value: t.createNumber(repr), + }); } return pragmas; diff --git a/src/traverse.js b/src/traverse.js index b837b8a..01136f3 100644 --- a/src/traverse.js +++ b/src/traverse.js @@ -16,7 +16,7 @@ export default function* traverse(node: Node): Generator { } for (const key of keys) { - const subNode = (node: Object)[key]; + const subNode = (node: $FlowFixMe)[key]; if (subNode instanceof Array) { for (const node of subNode) { diff --git a/src/types.js b/src/types.js index d71cecd..beb2ffe 100644 --- a/src/types.js +++ b/src/types.js @@ -92,3 +92,73 @@ export type ReferenceType = BaseType & { kind: 'reference', to: TypeId, }; + +export const createRecord = (fields: *): RecordType => ({kind: 'record', fields}); +export const createArray = (items: *): ArrayType => ({kind: 'array', items}); +export const createTuple = (items: *): TupleType => ({kind: 'tuple', items}); +export const createMap = (keys: *, values: *): MapType => ({kind: 'map', keys, values}); +export const createUnion = (variants: *): UnionType => ({kind: 'union', variants}); +export const createIntersection = (parts: *): IntersectionType => ({kind: 'intersection', parts}); +export const createMaybe = (value: *): MaybeType => ({kind: 'maybe', value}); +export const createNumber = (repr: *): NumberType => ({kind: 'number', repr}); +export const createString = (): StringType => ({kind: 'string'}); +export const createBoolean = (): BooleanType => ({kind: 'boolean'}); +export const createLiteral = (value: *): LiteralType => ({kind: 'literal', value}); +export const createAny = () => ({kind: 'any'}); +export const createMixed = () => ({kind: 'mixed'}); +export const createReference = (to: *) => ({kind: 'reference', to}); + +declare function clone(Type): Type; +declare function clone(TypeId): TypeId; +declare function clone(?Type): ?Type; +export function clone(type: ?Type | TypeId): ?Type | TypeId { + if (!type) { + return null; + } + + if (type instanceof Array) { + return type.slice(); + } + + return cloneType(type); +} + +function cloneType(type: Type): Type { + switch (type.kind) { + case 'record': + const fields = type.fields.map(field => ({ + name: field.name, + value: clone(field.value), + required: field.required, + })); + + return createRecord(fields); + case 'array': + return createArray(clone(type.items)); + case 'tuple': + return createTuple(type.items.map(clone)); + case 'map': + return createMap(clone(type.keys), clone(type.values)); + case 'union': + return createUnion(type.variants.map(clone)); + case 'intersection': + return createIntersection(type.parts.map(clone)); + case 'maybe': + return createMaybe(clone(type.value)); + case 'number': + return createNumber(type.repr); + case 'string': + return createString(); + case 'boolean': + return createBoolean(); + case 'literal': + return createLiteral(type.value); + case 'any': + return createAny(); + case 'mixed': + return createMixed(); + case 'reference': + default: + return createReference(type.to.slice()); + } +} diff --git a/src/utils.js b/src/utils.js index 64839c0..0e528b2 100644 --- a/src/utils.js +++ b/src/utils.js @@ -9,22 +9,3 @@ export function last(list: T[]): T { return list[list.length - 1]; } - -export function clone(that: T): T { - if (that == null || typeof that !== 'object') { - return that; - } - - if (that instanceof Array) { - return that.map(clone); - } - - const obj = {}; - - for (const key in that) { - obj[key] = clone(that[key]); - } - - // TODO: we skip complex objects. - return (obj: $FlowFixMe); -} diff --git a/tests/run.js b/tests/run.js index 9c2dafe..0159885 100644 --- a/tests/run.js +++ b/tests/run.js @@ -7,7 +7,7 @@ import wu from 'wu'; import collect from '../src'; function run(title) { - let actual, expected: any; + let actual, expected: $FlowFixMe; // Run the collector only if the suite will be checked. before(() => {