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-env": "^7.0.0-beta.32",
"@babel/preset-flow": "^7.0.0-beta.32", "@babel/preset-flow": "^7.0.0-beta.32",
"@babel/register": "^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", "jasmine": "^2.8.0",
"mocha": "^4.0.1", "mocha": "^4.0.1",
"nyc": "^11.3.0" "nyc": "^11.3.0"

View File

@ -20,6 +20,8 @@ import type {
MaybeType, NumberType, StringType, BooleanType, LiteralType, ReferenceType, MaybeType, NumberType, StringType, BooleanType, LiteralType, ReferenceType,
} from './types'; } from './types';
import * as t from './types';
import {extractPragmas} from './pragmas'; import {extractPragmas} from './pragmas';
import {invariant} from './utils'; import {invariant} from './utils';
@ -54,18 +56,16 @@ function processInterfaceDeclaration(ctx: Context, node: InterfaceDeclaration) {
invariant(type.id); invariant(type.id);
parts.push({ const reference = t.createReference(t.clone(type.id));
kind: 'reference',
to: type.id.slice(), parts.push(reference);
});
} }
parts.push(type); parts.push(type);
ctx.define(name, { const intersection = t.createIntersection(parts);
kind: 'intersection',
parts, ctx.define(name, intersection);
});
} }
// TODO: type params. // TODO: type params.
@ -87,27 +87,22 @@ function processClassDeclaration(ctx: Context, node: ClassDeclaration) {
invariant(base.id); invariant(base.id);
const baseRef = { const baseRef = t.createReference(t.clone(base.id));
kind: 'reference', const intersection = t.createIntersection([baseRef, type]);
to: base.id.slice(),
};
ctx.define(name, { ctx.define(name, intersection);
kind: 'intersection',
parts: [baseRef, type],
});
} }
function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type { function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type {
switch (node.type) { switch (node.type) {
case 'NullLiteralTypeAnnotation': case 'NullLiteralTypeAnnotation':
return {kind: 'literal', value: null}; return t.createLiteral(null);
case 'BooleanTypeAnnotation': case 'BooleanTypeAnnotation':
return {kind: 'boolean'}; return t.createBoolean();
case 'NumberTypeAnnotation': case 'NumberTypeAnnotation':
return {kind: 'number', repr: 'f64'}; return t.createNumber('f64');
case 'StringTypeAnnotation': case 'StringTypeAnnotation':
return {kind: 'string'}; return t.createString();
case 'TypeAnnotation': case 'TypeAnnotation':
return makeType(ctx, node.typeAnnotation); return makeType(ctx, node.typeAnnotation);
case 'NullableTypeAnnotation': case 'NullableTypeAnnotation':
@ -123,13 +118,13 @@ function makeType(ctx: Context, node: FlowTypeAnnotation): ?Type {
case 'IntersectionTypeAnnotation': case 'IntersectionTypeAnnotation':
return makeIntersection(ctx, node); return makeIntersection(ctx, node);
case 'StringLiteralTypeAnnotation': case 'StringLiteralTypeAnnotation':
return {kind: 'literal', value: node.value}; return t.createLiteral(node.value);
case 'GenericTypeAnnotation': case 'GenericTypeAnnotation':
return makeReference(ctx, node); return makeReference(ctx, node);
case 'AnyTypeAnnotation': case 'AnyTypeAnnotation':
return {kind: 'any'}; return t.createAny();
case 'MixedTypeAnnotation': case 'MixedTypeAnnotation':
return {kind: 'mixed'}; return t.createMixed();
case 'FunctionTypeAnnotation': case 'FunctionTypeAnnotation':
return null; return null;
default: default:
@ -144,10 +139,7 @@ function makeMaybe(ctx: Context, node: NullableTypeAnnotation): ?MaybeType {
return null; return null;
} }
return { return t.createMaybe(type);
kind: 'maybe',
value: type,
};
} }
function makeComplexType(ctx: Context, node: ObjectTypeAnnotation): 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; const parts = record.fields.length > 0 ? [record, ...maps] : maps;
return { return t.createIntersection(parts);
kind: 'intersection',
parts,
};
} }
function makeRecord<T: ObjectTypeProperty | ClassProperty>(ctx: Context, nodes: T[]): RecordType { function makeRecord<T: ObjectTypeProperty | ClassProperty>(ctx: Context, nodes: T[]): RecordType {
@ -180,10 +169,7 @@ function makeRecord<T: ObjectTypeProperty | ClassProperty>(ctx: Context, nodes:
.filter() .filter()
.toArray(); .toArray();
return { return t.createRecord(fields);
kind: 'record',
fields,
};
} }
function makeField(ctx: Context, node: ObjectTypeProperty | ClassProperty): ?Field { function makeField(ctx: Context, node: ObjectTypeProperty | ClassProperty): ?Field {
@ -238,11 +224,7 @@ function makeMap(ctx: Context, node: ObjectTypeIndexer): ?MapType {
return null; return null;
} }
return { return t.createMap(keys, values);
kind: 'map',
keys,
values,
};
} }
function makeArrayType(ctx: Context, node: ArrayTypeAnnotation): ?ArrayType { function makeArrayType(ctx: Context, node: ArrayTypeAnnotation): ?ArrayType {
@ -252,10 +234,7 @@ function makeArrayType(ctx: Context, node: ArrayTypeAnnotation): ?ArrayType {
return null; return null;
} }
return { return t.createArray(items);
kind: 'array',
items,
};
} }
function makeTupleType(ctx: Context, node: TupleTypeAnnotation): ?TupleType { function makeTupleType(ctx: Context, node: TupleTypeAnnotation): ?TupleType {
@ -266,10 +245,7 @@ function makeTupleType(ctx: Context, node: TupleTypeAnnotation): ?TupleType {
return null; return null;
} }
return { return t.createTuple(items);
kind: 'tuple',
items,
};
} }
function makeUnionType(ctx: Context, node: UnionTypeAnnotation): ?Type { function makeUnionType(ctx: Context, node: UnionTypeAnnotation): ?Type {
@ -286,10 +262,7 @@ function makeUnionType(ctx: Context, node: UnionTypeAnnotation): ?Type {
return variants[0]; return variants[0];
} }
return { return t.createUnion(variants);
kind: 'union',
variants,
};
} }
function makeIntersection(ctx: Context, node: IntersectionTypeAnnotation): ?Type { function makeIntersection(ctx: Context, node: IntersectionTypeAnnotation): ?Type {
@ -307,10 +280,7 @@ function makeIntersection(ctx: Context, node: IntersectionTypeAnnotation): ?Type
return parts[0]; return parts[0];
} }
return { return t.createIntersection(parts);
kind: 'intersection',
parts,
};
} }
function makeReference(ctx: Context, node: GenericTypeAnnotation): ?Type { function makeReference(ctx: Context, node: GenericTypeAnnotation): ?Type {
@ -324,10 +294,7 @@ function makeReference(ctx: Context, node: GenericTypeAnnotation): ?Type {
return type; return type;
} }
return { return t.createReference(t.clone(type.id));
kind: 'reference',
to: type.id.slice(),
};
} }
export default { export default {

View File

@ -1,35 +1,26 @@
import wu from 'wu'; import wu from 'wu';
import {invariant, clone} from './utils'; import {invariant} from './utils';
import type {Type, TypeId} from './types'; import type {Type, TypeId} from './types';
import * as t from './types';
function object(params: (?Type)[]): ?Type { function object(params: (?Type)[]): ?Type {
invariant(params.length === 0); invariant(params.length === 0);
return { return t.createMap(t.createMixed(), t.createMixed());
kind: 'map',
keys: {kind: 'mixed'},
values: {kind: 'mixed'},
};
} }
function buffer(params: (?Type)[]): ?Type { function buffer(params: (?Type)[]): ?Type {
invariant(params.length === 0); invariant(params.length === 0);
return { return t.createReference(['Buffer']);
kind: 'reference',
to: ['Buffer'],
};
} }
function array(params: (?Type)[]): ?Type { function array(params: (?Type)[]): ?Type {
invariant(params.length === 1); invariant(params.length === 1);
invariant(params[0]); invariant(params[0]);
return { return t.createArray(t.clone(params[0]));
kind: 'array',
items: params[0],
};
} }
function elemType(params: (?Type)[], resolve: TypeId => Type): ?Type { 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); const field = wu(record.fields).find(field => field.name === key.value);
// TODO: what about removing "id"? // 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 { 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. // TODO: support for unions and nested maybe.
if (maybe.kind !== '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 { function shape(params: (?Type)[], resolve: TypeId => Type): ?Type {
@ -83,14 +74,14 @@ function shape(params: (?Type)[], resolve: TypeId => Type): ?Type {
invariant(record.kind === 'record'); invariant(record.kind === 'record');
const fields = wu(record.fields) const fields = wu(record.fields)
.map(clone) .map(field => ({
.tap(field => field.required = false) name: field.name,
value: t.clone(field.value),
required: false,
}))
.toArray(); .toArray();
return { return t.createRecord(fields);
kind: 'record',
fields,
};
} }
function unwrap(params: (?Type)[]): ?Type { function unwrap(params: (?Type)[]): ?Type {
@ -98,7 +89,7 @@ function unwrap(params: (?Type)[]): ?Type {
const [type] = params; const [type] = params;
return type ? clone(type) : null; return type ? t.clone(type) : null;
} }
function keys(params: (?Type)[], resolve: TypeId => Type): ?Type { 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) const variants = wu(record.fields)
.pluck('name') .pluck('name')
.map(name => ({kind: 'literal', value: name})) .map(t.createLiteral)
.toArray(); .toArray();
// TODO: empty records. // TODO: empty records.
return { return t.createUnion(variants);
kind: 'union',
variants,
};
} }
function values(params: (?Type)[], resolve: TypeId => Type): ?Type { 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) const variants = wu(record.fields)
.pluck('value') .pluck('value')
.map(clone) .map(t.clone)
.toArray(); .toArray();
// TODO: empty records. // TODO: empty records.
// TODO: dedup values. // TODO: dedup values.
return { return t.createUnion(variants);
kind: 'union',
variants,
};
} }
export default { export default {

View File

@ -1,6 +1,7 @@
import {invariant} from './utils'; import {invariant} from './utils';
import type {Type} from './types'; import type {Type} from './types';
import * as t from './types';
export type Pragma = export type Pragma =
| TypePragma; | TypePragma;
@ -21,15 +22,10 @@ export function extractPragmas(text: string): Pragma[] {
invariant(['i32', 'i64', 'u32', 'u64', 'f32', 'f64'].includes(repr)); invariant(['i32', 'i64', 'u32', 'u64', 'f32', 'f64'].includes(repr));
const pragma = { pragmas.push({
kind: 'type', kind: 'type',
value: { value: t.createNumber(repr),
kind: 'number', });
repr,
},
};
pragmas.push(pragma);
} }
return pragmas; return pragmas;

View File

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

View File

@ -92,3 +92,73 @@ export type ReferenceType = BaseType & {
kind: 'reference', kind: 'reference',
to: TypeId, 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]; 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'; import collect from '../src';
function run(title) { function run(title) {
let actual, expected: any; let actual, expected: $FlowFixMe;
// Run the collector only if the suite will be checked. // Run the collector only if the suite will be checked.
before(() => { before(() => {