Types refactoring

master
Paul Loyd 2017-12-01 20:00:40 +03:00
parent edc8f4eb6a
commit beb5c408d1
8 changed files with 123 additions and 124 deletions

View File

@ -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"

View File

@ -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<T: ObjectTypeProperty | ClassProperty>(ctx: Context, nodes: T[]): RecordType {
@ -180,10 +169,7 @@ function makeRecord<T: ObjectTypeProperty | ClassProperty>(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 {

View File

@ -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 {

View File

@ -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;

View File

@ -16,7 +16,7 @@ export default function* traverse(node: Node): Generator<Node, void, boolean> {
}
for (const key of keys) {
const subNode = (node: Object)[key];
const subNode = (node: $FlowFixMe)[key];
if (subNode instanceof Array) {
for (const node of subNode) {

View File

@ -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());
}
}

View File

@ -9,22 +9,3 @@ export function last<T>(list: T[]): T {
return list[list.length - 1];
}
export function clone<T>(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);
}

View File

@ -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(() => {